HotPDF puede conservar subconjuntos de fuentes TrueType y OpenType en disco y reutilizarlos entre documentos y entre ejecuciones del proceso, de modo que un lote que renderiza diez mil extractos con las mismas tres fonts genera los subconjuntos una sola vez en lugar de diez mil. La caché se configura con dos propiedades, se inspecciona con un registro, y es seguro dejarla activa: un fallo de caché recurre al subsetting normal en memoria y nunca impide producir un documento
El subsetting es caro por un motivo. Construir un subconjunto significa recorrer la clausura de glifos, reescribir loca y glyf, reconstruir cmap y hmtx, y emitir una correspondencia CID que el PDF pueda direccionar. Para un documento ese coste desaparece en el ruido. Para un servidor de informes que produce documentos en bucle, suele ser el mayor bloque individual de tiempo de CPU de toda la ejecución
Qué hace posible un acierto de caché
Cuatro cosas deben coincidir: el contenido de la fuente, el conjunto de glifos usados, el modo de subsetting y el esquema de la caché. Falla una sola y HotPDF genera el subconjunto desde cero, porque un subconjunto solo es reutilizable cuando habría sido idéntico byte a byte de todos modos
El conjunto de glifos es la condición que sorprende. Dos facturas que difieren en un único nombre de cliente usan conjuntos de glifos distintos, y por tanto producen subconjuntos distintos y entradas de caché distintas. La caché rentabiliza cuando los documentos comparten repertorio de glifos —extractos de una plantilla fija, formularios cuyos datos variables son numéricos, catálogos extraídos de una única base de datos de productos— y no aporta nada cuando cada documento extrae una porción distinta de una fuente CJK grande. Mide antes de asumir en qué caso estás
var
Pdf: THotPDF;
Info: THPDFFontSubsetCacheInfo;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.EnableFontSubsetting := True;
Pdf.FontSubsetCacheFolder := 'C:\ProgramData\Reports\fontcache';
Pdf.FontSubsetCacheMaxBytes := 64 * 1024 * 1024; // 64 MiB, default is 256
// ... generate the batch ...
Info := Pdf.GetFontSubsetCacheInfo;
LogFmt('subset cache: %d hits, %d misses, %d bytes in %d files',
[Info.HitCount, Info.MissCount, Info.CurrentBytes, Info.FileCount]);
finally
Pdf.Free;
end;
end;
¿Cómo sabes si la caché está haciendo algo?
GetFontSubsetCacheInfo devuelve nueve contadores, y el cociente entre los dos primeros responde la pregunta directamente. HitCount y MissCount dan la tasa de aciertos. WriteCount y EvictionCount muestran si las entradas sobreviven el tiempo suficiente para reutilizarse o están siendo expulsadas por un presupuesto demasiado pequeño. CurrentBytes y FileCount informan de lo que hay en disco ahora mismo
Los tres restantes son sobre los que vale la pena alertar. CorruptCount cuenta entradas que fallaron la validación y fueron retiradas —unas pocas tras un apagado incorrecto son normales, un flujo constante significa que el almacenamiento no es fiable—. RejectedCount cuenta entradas rechazadas antes de su uso. WriteFailureCount cuenta entradas que no pudieron escribirse en absoluto, lo que normalmente indica un problema de permisos en la carpeta y no nada relacionado con las fuentes. Ninguno de los tres detiene la generación de documentos, que es justamente por lo que tienes que mirarlos: una caché que silenciosamente nunca escribe se ve desde fuera igual que una caché que funciona, salvo en la factura de CPU
Expulsión, presupuestos y el momento de reducir uno
FontSubsetCacheMaxBytes vale por defecto 268435456 bytes, es decir, 256 MiB, y se puede bajar en tiempo de ejecución. Bajarlo dispara la expulsión inmediata por menor uso reciente en lugar de esperar a la próxima escritura, de modo que un servicio que reacciona a la presión de disco puede liberar espacio en el instante en que decide hacerlo, no en un punto posterior que no controla
Poner FontSubsetCacheFolder a una cadena vacía deshabilita el nivel de disco sin borrar nada ya almacenado, y sin cambiar ni un byte de la salida de fuentes. Esa es la propiedad a la que asir cuando quieras aislar la caché durante el diagnóstico: apágala, ejecuta el mismo lote y compara los PDF producidos. Deberían ser idénticos, porque la caché almacena un resultado, no una política
Qué hace la caché cuando una entrada está dañada
La retira y genera el subconjunto con normalidad. Las entradas malformadas o truncadas se rechazan antes de que el subconjunto pueda alcanzar un flujo PDF, que es la parte del diseño que más importa: una entrada de caché corrupta que llegara a un documento produciría un PDF con un programa de fuente roto, y ese fallo afloraría lejos de su causa —en un visor, en la máquina de un cliente, semanas después—
Las escrituras son atómicas, de modo que un lector nunca observa una entrada a medio escribir, y un cuelgue en medio de una escritura deja la caché consistente en lugar de envenenada. Las entradas de subconjunto compacto conservan los datos de reasignación CID que exigen los diccionarios de fuente PDF/A, así que un subconjunto cacheado sigue siendo un subconjunto conforme —la salida archivable no tiene que saltarse la caché para mantenerse válida—
// Reset the disk tier after a font upgrade or a schema change
Pdf.ClearFontSubsetCache;
// Or move it somewhere writable and let the budget apply immediately
Pdf.SetFontSubsetCacheFolder('D:\cache\fonts');
Dónde poner la carpeta en un despliegue real
Tres propiedades deciden esto: la carpeta debe ser escribible por la cuenta con la que se ejecuta el servicio, debe estar en almacenamiento local y no en un recurso compartido de red, y no debe estar dentro de un directorio que un paso de despliegue borre. Una caché en un recurso compartido convierte cada fallo en un ir y venir y cada acierto en dos; una caché bajo una carpeta de aplicación que el instalador recrea es una caché que arranca fría tras cada actualización
Para servicios multi-instancia, da a cada instancia su propia carpeta salvo que hayas confirmado que el almacenamiento maneja el reemplazo atómico concurrente como esperas. El coste de una entrada duplicada es una pasada de subsetting adicional; el coste de depurar una carrera en caché compartida es una tarde entera
Cuándo recurrir a otra cosa
La caché reduce trabajo repetido. No reduce el trabajo del primer documento, y no ayuda a una carga cuyos conjuntos de glifos nunca se repiten. Si tu salida está dominada por una fuente CJK enorme usada sobre texto impredecible, la palanca más eficaz es la clausura del subsetting en sí —qué glifos se incorporan, y por qué—, cubierta en las notas sobre la clausura de subconjuntos de fuente y el shaping de glifos. Si tu lento lote lo es por motivos que al final no son las fuentes, el recorrido por la salida de informes con fuentes e imágenes muestra a dónde suele ir el resto del tiempo, y el caso de estudio sobre el bug de ordenación de subconjuntos de fuente en EndDoc recuerda que la corrección del subsetting y la velocidad del subsetting son problemas separados
HotPDF es un componente PDF VCL nativo para Delphi y C++Builder, y la caché de subconjuntos forma parte de la librería en lugar de ser un servicio añadido, de modo que un servidor de informes la obtiene fijando una ruta de carpeta; consulta la página del componente HotPDF para la lista completa de características de fuentes y rendimiento