Cada carácter visible en un PDF contiene una referencia a la fuente con la que se dibujó, y el PDFium Component le permite seguir esa referencia hasta el objeto de fuente y leer la información disponible. La unidad de acceso es el carácter, no el documento: se selecciona un carácter mediante su índice en el texto de la página y se solicita el nombre de la familia, el nombre base, el grosor (weight), el ángulo de cursiva y si la fuente real está contenida dentro del archivo. Esta última propiedad es la que más interesa en los análisis, ya que una fuente incrustada viaja con el documento, mientras que una no incrustada es una suposición de que el equipo del lector tiene instalada la misma tipografía
El componente expone estas propiedades a través de los mismos objetos TPdf and TPdfView que usted utiliza para el renderizado y la extracción de texto. No hay un objeto "tabla de fuentes" independiente que abrir. Una vez que se ha analizado el texto de la página, las propiedades de la fuente dependen del índice del carácter, y se leen un glifo a la vez. Ese diseño coincide con la forma en que PDF almacena la información originalmente: una sola página puede cambiar de fuente docenas de veces, y la única respuesta correcta a "en qué fuente está este documento" es "depende de a qué carácter se refiera"
Leer la fuente asociada a un carácter
La operación útil más pequeña consiste en tomar un índice de carácter y volcar todo lo que PDFium pueda indicar sobre su fuente. Cada propiedad de fuente en TPdf y TPdfView está indexada por la posición del carácter, por lo que el índice las conecta a todas. La página también debe ser la página actual para que el índice se resuelva frente al texto correcto, lo cual es importante una vez que se avanza más allá de la primera página
procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
Report: TStringList;
PtSize: Single;
begin
Report := TStringList.Create;
try
PtSize := Pdf.FontSize[CharIndex];
Report.Add('Character : ' + Pdf.Character[CharIndex]);
Report.Add('Family : ' + Pdf.FontFamilyName[CharIndex]);
Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
Report.Add('Weight : ' + IntToStr(Pdf.FontWeight[CharIndex]));
Report.Add('Italic : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
Report.Add('Size : ' + FormatFloat('0.0', PtSize) + ' pt');
Report.Add('Ascent : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
Report.Add('Descent : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
Report.Add('Embedded : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));
ShowMessage(Report.Text);
finally
Report.Free;
end;
end;
Un par de declaraciones sorprenden a quienes provienen de otras bibliotecas. FontAscent y FontDescent toman dos argumentos, el índice de carácter y un tamaño en puntos, debido a que PDFium reporta esas métricas en unidades de espacio de glifo que solo se convierten en píxeles una vez que las escala por el tamaño asignado al texto. Pase el valor que ya leyó de FontSize[CharIndex] y obtendrá el ascenso (ascent) y descenso (descent) en los mismos puntos que el resto del diseño. El descenso se devuelve como valor negativo, ya que se mide por debajo de la línea base. El nombre de la familia y el nombre base son cadenas separadas intencionalmente: el nombre base es la entrada /BaseFont directa del PDF, que a menudo incluye un prefijo de subconjunto como ABCDEF+, mientras que el nombre de la familia es el nombre limpio que el renderizador resuelve
Convertir un clic en un índice de carácter
En un visor rara vez se conoce el índice de antemano. El usuario hace clic en un glifo y usted debe traducir la coordenada de píxeles al carácter situado debajo. CharacterIndexAtPos realiza exactamente eso, tomando la posición del mouse y un rango de tolerancia, y devolviendo el índice del carácter más cercano, o un valor negativo si el clic se realizó sobre un espacio en blanco o una página vacía
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// 4 px of slack in each direction so a near-miss still hits the glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // clicked between glyphs; leave the panel alone
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Vale la pena ajustar la tolerancia. Si es demasiado estricta, los usuarios sentirán que deben hacer clic exactamente sobre el trazo de una letra; si es demasiado holgada, un clic en un margen se asociará a algún carácter lejano que no tiene relación con lo que buscaban. De tres a cinco píxeles de dispositivo es un punto de partida razonable para la visualización en pantalla. El índice devuelto corresponde al texto procesado de la página actual, el mismo espacio de índice que esperan todas las propiedades de fuente, por lo que puede pasarlo directamente a la rutina anterior. Almacenarlo en CurrentCharIndex es opcional pero conveniente: la vista lo mantiene como su referencia del glifo enfocado, lo cual resulta útil si otras secciones de la interfaz gráfica desean leer la selección sin tener que volver a calcularla
La incrustación es la propiedad clave
Para la mayoría de los trabajos reales, la única pregunta importante es si cada fuente está incrustada. Un documento cuyas fuentes están todas integradas se renderiza igual en el RIP de una imprenta, en la computadora portátil de un colega y en un servidor sin interfaz gráfica. Un documento que depende de una fuente Helvetica no incrustada asume el riesgo de que cada uno de esos equipos tenga una fuente coincidente, y cuando el supuesto falla, el lector la sustituye por algo similar, las métricas cambian y un diseño de formulario bien estructurado se reorganiza lo suficiente como para dañarse. Recorrer el texto de la página y clasificar las fuentes por su estado de incrustación le proporciona esa respuesta de manera económica
procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
Embedded, External: TStringList;
I: Integer;
Name: string;
begin
Embedded := TStringList.Create;
External := TStringList.Create;
try
Embedded.Sorted := True;
Embedded.Duplicates := dupIgnore;
External.Sorted := True;
External.Duplicates := dupIgnore;
for I := 0 to Pdf.CharacterCount - 1 do
begin
Name := Pdf.FontBaseName[I];
if Name = '' then
Continue; // generated spaces and the like have no font
if Pdf.FontIsEmbedded[I] then
Embedded.Add(Name)
else
External.Add(Name);
end;
if External.Count > 0 then
ShowMessage(IntToStr(External.Count) +
' non-embedded font(s):' + sLineBreak + External.Text)
else
ShowMessage('All ' + IntToStr(Embedded.Count) +
' font(s) on this page are embedded.');
finally
Embedded.Free;
External.Free;
end;
end;
Dos detalles aclaran el panorama. Primero, CharacterCount es por página, por lo que una auditoría de todo el documento implica configurar Pdf.PageNumber en cada página de forma consecutiva y volver a ejecutar el bucle, combinando los resultados. Segundo, la capa de texto contiene caracteres generados, como los espacios que el lector deduce entre palabras, y estos no tienen un objeto de fuente asociado; la comprobación de nombre base vacío los descarta en lugar de registrar un elemento falso. El nombre base es la clave correcta para la eliminación de duplicados en este caso, debido a que el prefijo del subconjunto que incluye distingue dos subconjuntos diferentes de la misma familia, lo cual suele ser la información requerida
Extraer la fuente incrustada
Cuando una fuente está incrustada, puede leer sus bytes directamente. FontData devuelve el programa de fuentes sin procesar (los mismos datos TrueType o CFF que contiene el PDF), lo cual es suficiente para escribir un archivo de fuente independiente o para contrastar la tipografía con una biblioteca conocida. Devuelve un arreglo vacío si la fuente no está incrustada, por lo que la comprobación de incrustación y la comprobación de longitud protegen la escritura
procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
const OutputFile: string);
var
Data: TBytes;
Stream: TFileStream;
begin
if not Pdf.FontIsEmbedded[CharIndex] then
begin
ShowMessage('That glyph''s font is not embedded; nothing to extract.');
Exit;
end;
Data := Pdf.FontData[CharIndex];
if Length(Data) = 0 then
Exit;
Stream := TFileStream.Create(OutputFile, fmCreate);
try
Stream.WriteBuffer(Data[0], Length(Data));
finally
Stream.Free;
end;
ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;
Los bytes corresponden al subconjunto incrustado, no a la fuente comercial original, por lo que lo obtenido normalmente cubre solo los glifos que el documento realmente utilizó. Eso es lo correcto para análisis forenses y de verificación, pero resulta deficiente para la reutilización; un subconjunto de Times New Roman que contenga treinta glifos no es una fuente que usted pueda instalar y con la que pueda escribir. Considere la extracción como una forma de inspeccionar lo que se distribuyó, no como una herramienta de recuperación de fuentes. Si necesita el nombre base correspondiente para etiquetar la salida, lea FontBaseName[CharIndex] junto con los datos, y elimine la etiqueta de subconjunto inicial si desea la familia limpia
Interpretar el valor del grosor
FontWeight devuelve la clase de grosor numérico, la misma escala de 100 a 900 que utiliza CSS, donde 400 es regular y 700 es negrita (bold). FontWeight reporta lo que la fuente declare, que no siempre es una centena redonda; una tipografía puede declarar 350 o 650, y considerar cualquier valor igual o superior a 600 como "suficientemente marcado" resulta más práctico que buscar exactamente 700. El ángulo de cursiva es una señal complementaria: un valor distinto de cero (generalmente negativo) indica que la fuente es un diseño oblicuo o cursiva real, y cero significa vertical. Juntos le permiten distinguir un fragmento en negrita y cursiva de uno normal sin renderizar nada, que es el tipo de comprobación que requiere una verificación previa o una auditoría de accesibilidad en grandes volúmenes de datos
Ninguna de estas lecturas requiere un mapa de bits renderizado. Provienen de la capa de texto analizada, por lo que un documento abierto en la página correcta es toda la configuración necesaria, lo que hace que la inspección de fuentes sea económica de ejecutar en todo un archivo. Si combina esto con la extracción de texto, los mismos índices de caracteres coinciden con el texto extraído, por lo que la fuente de un glifo y su valor Unicode son dos lees frente a un solo índice. El companion artículo en extraer texto de documentos PDF con PDFium Component cubre esa parte de la capa de texto en mayor profundidad
Las propiedades de fuentes mostradas aquí forman parte del PDFium Delphi VCL Component