PDFium Component escribe texto en una página PDF a nivel de glifo mediante AddCidType2Text, incrustando los datos TrueType que le suministres como una fuente CID Type 2. Asigna a cada instancia de glifo un CID secuencial, genera el mapa explícito CID-a-GID y el CMap ToUnicode, y combina los glifos sobre la misma línea base en un único objeto de texto nativo. El subconjuntado opcional mantiene el archivo pequeño, con una política explícita para lo que ocurre si el subconjuntado falla
La escritura a nivel de glifo es lo que necesitas cuando el texto ya se ha conformado (shaping). El árabe, el devanagari y cualquier escritura con formas contextuales producen una secuencia de identificadores de glifo que ya no se corresponde uno a uno con los caracteres, así que una API que recibe una cadena y un nombre de fuente no puede expresar el resultado. Entregar directamente glifos y posiciones es la única forma de colocar en un PDF texto de escritura compleja correctamente conformado
Por qué cada instancia de glifo recibe su propio CID
La optimización tentadora es deduplicar: un CID por cada identificador de glifo distinto, reutilizado allí donde ese glifo aparezca. Produce una fuente más pequeña y rompe la extracción de texto, porque el mismo glifo puede corresponder legítimamente a contenido Unicode distinto en lugares distintos
Un glifo de ligadura es el caso claro. El mismo glifo «ffi» puede representar los tres caracteres en una palabra y, tras una decisión de conformado distinta, representar otra cosa en otro lugar. El mapa ToUnicode se indexa por CID, así que un CID compartido solo puede llevar una correspondencia, y el texto que pierda esa disputa se vuelve inextraíble
Por eso cada instancia de glifo recibe su propio CID, y cada correspondencia es libre de constar de varias unidades de código UTF-16. Un glifo sin texto significativo — un elemento decorativo, un glifo puramente de posicionamiento — se asigna explícitamente a U+200B, un espacio de ancho cero, de modo que cada CID tenga un resultado de extracción observable en lugar de un hueco
uses
PDFium;
var
Pdf: TPdf;
Glyphs: TPdfCidGlyphs;
Options: TPdfCidFontOptions;
Report: TPdfCidFontReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'label.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1;
// Una entrada por cada glifo conformado: el id de glifo, el texto que
// representa, y su avance y desplazamientos en el espacio de texto
SetLength(Glyphs, 3);
Glyphs[0].GlyphID := 402; Glyphs[0].UnicodeText := 'ffi';
Glyphs[0].Advance := 18.4;
Glyphs[1].GlyphID := 71; Glyphs[1].UnicodeText := 'c';
Glyphs[1].Advance := 9.8;
Glyphs[2].GlyphID := 74; Glyphs[2].UnicodeText := 'e';
Glyphs[2].Advance := 9.2;
Options := TPdfCidFontOptions.Default; // subconjunto preferido
Options.VerifyExtraction := True;
if Pdf.AddCidType2Text(LoadFileBytes('NotoSans.ttf'), Glyphs,
11, 72, 700, Options, Report) then
Writeln(Format('%d glyphs, %d unique, font %d -> %d bytes, match=%s',
[Report.GlyphCount, Report.UniqueGlyphCount,
Report.OriginalFontBytes, Report.EmbeddedFontBytes,
BoolToStr(Report.ExtractionMatches, True)]));
finally
Pdf.Free;
end;
end;
Subconjuntado, y las comprobaciones que un código de retorno no te da
Reducir una fuente a los glifos realmente usados es la diferencia entre incrustar 300 KB e incrustar 12 KB, y en un documento con varias fuentes eso decide si el archivo se puede enviar por correo. La ruta de subconjuntado de la plataforma acepta directamente una lista de glifos, lo que encaja perfectamente con esta API, ya que el llamador ya conoce cada identificador de glifo en uso
Lo que no te da es confianza. Una llamada de subconjuntado puede reportar éxito y devolver una salida inservible, así que el componente valida tres propiedades antes de aceptar el resultado: la salida debe ser más pequeña que el original, debe volver a analizarse como un sfnt válido con una tabla maxp legible, y debe conservar los glifos hasta el identificador original más alto solicitado. Cualquier fallo significa que el subconjunto se rechaza
Lo que ocurre a continuación es política del llamador. Bajo pcfemSubsetPreferred, el valor por defecto, un subconjunto rechazado recurre a incrustar la fuente completa, así que la página queda correcta y simplemente más grande. Bajo pcfemSubsetRequired, la operación falla antes de que la página se modifique, que es lo que quiere un pipeline con restricciones de tamaño. pcfemFull se salta el subconjuntado por completo. El informe te dice qué ruta se tomó mediante SubsetAttempted, SubsetApplied, UsedFullFontFallback y SubsetErrorCode
Una verificación que no puede producir un falso positivo
Con VerifyExtraction activado, el componente confirma que lo que escribió puede volver a leerse. La forma ingenua de hacerlo es extraer toda la página y buscar la cadena esperada, y es errónea: una página que ya contenía ese texto pasa la comprobación incluso cuando el texto nuevo se escribió mal
En su lugar, la página de texto se reconstruye y los manejadores de objeto insertados por esta llamada se leen individualmente, en el orden de inserción, y se concatenan. El resultado se compara con el texto esperado, y el informe expone ambas cadenas junto con el booleano, de modo que una discrepancia pueda diagnosticarse y no solo detectarse
Actívalo durante el desarrollo y en cualquier pipeline donde la extraíbilidad sea un requisito — archivos con capacidad de búsqueda, cumplimiento de accesibilidad, minería de texto posterior. El coste es una reconstrucción de la página de texto por llamada, razón por la cual no está activado por defecto en bucles ajustados
Presupuestos y fallo atómico
Los bytes de fuente, las instancias de glifo y las unidades de código Unicode tienen todos un tope antes de la asignación de memoria, y el formato de fuente, el índice TTC, el rango de identificador de glifo y los valores de geometría se validan antes de escribir nada. La geometría debe ser finita, lo cual suena obvio hasta que un motor de conformado te entrega un avance NaN procedente de una fuente malformada
El fallo es atómico a nivel de página. Si falla la carga de la fuente, la escritura de objetos, la generación de contenido o la verificación de extracción, cada objeto insertado por esa llamada se elimina en orden inverso y el contenido de la página se regenera. Una llamada fallida deja la página como estaba, no con medio trazo de texto sobre ella
Dónde encaja esto en un pipeline de texto
Vale la pena precisar la división de trabajo. El conformado (shaping) — convertir caracteres en glifos posicionados — no es tarea de esta API; pertenece a un motor de conformado, y el soporte propio del componente para bidireccionalidad y escrituras complejas se trata en manejo de emoji, CJK y pares subrogados. Esta API es la que llamas después, con la secuencia de glifos que produjo el conformador
Para texto latino ordinario sin conformado contextual, las API de texto basadas en cadenas más simples son la herramienta adecuada y producen código más pequeño. Recurre a la escritura CID Type 2 cuando tengas salida ya conformada, cuando necesites control exacto de los identificadores de glifo, o cuando la fuente deba incrustarse a partir de bytes que ya tienes en lugar de resolverse por nombre — la alternativa de resolución de fuentes es el mecanismo de proveedor descrito en controlar la sustitución de fuentes en PDF
Una nota de despliegue: incrustar una fuente es tanto una cuestión de licencia como técnica. Las fuentes varían en si la incrustación está permitida en absoluto, permitida solo para visualización, o permitida para edición. La biblioteca incrustará los bytes que le des, y comprobar la licencia es responsabilidad tuya, no del formato de archivo
La escritura de texto a nivel de glifo, el aprovisionamiento de fuentes y la extracción de texto comparten el mismo modelo de página en Delphi, C++Builder y Lazarus; la API completa se describe en la página de PDFium Component para Delphi