PDFiumPas devuelve el texto de página como una estructura en lugar de una cadena. GetStructuredText produce una TPdfStructuredTextPage que contiene bloques, cada uno con líneas, cada una con spans estilizados, con límites en el espacio de la página en cada nivel y los índices de carácter de origen preservados, de modo que cualquier fragmento se puede rastrear de vuelta hasta la página de texto subyacente
La extracción de cadena plana con la que empieza la mayoría del código sigue estando ahí y sigue siendo correcta para su propósito. Deja de ser suficiente en el momento en que usted necesita saber cuáles palabras eran un encabezado, cuáles pertenecían a la columna izquierda, o dónde en la página se ubica realmente una coincidencia
¿Por qué una cadena plana es la salida equivocada para la mayoría de los trabajos?
Porque las preguntas que la gente le hace al texto extraído casi nunca son "qué caracteres hay en esta página". Son "cuál es el título", "es esto una tabla", "este párrafo pertenece a la sección 4", "dónde dibujo el resaltado". Una sola cadena no responde ninguna de ellas, y cada respuesta que usted reconstruye a partir de ella es una heurística que ahora es suya
Los diseños de dos columnas ilustran el punto con claridad. Extraiga un artículo de dos columnas como cadena y, dependiendo de cómo el productor haya escrito el content stream, puede obtener la columna uno seguida de la columna dos, o puede obtener la línea uno de la columna uno, la línea uno de la columna dos, la línea dos de la columna uno, y así sucesivamente hacia abajo en la página. Ambos casos salen de un PDF conforme. Ninguno está mal a nivel de formato, porque PDF describe marcas en una página, no un esquema de documento. Un modelo basado en bloques permite que el extractor tome la decisión de orden explícitamente y le diga cuál decisión tomó
¿Orden de contenido u orden físico?
TPdfStructuredTextOptions.ReadingOrder elige entre roContentOrder y roPhysicalLayout, y la respuesta correcta depende de en quién confíe más, en el productor o en la geometría
El orden de contenido devuelve el texto en la secuencia en que el content stream lo dibuja. Eso es rápido, y para documentos generados por un productor bien comportado, típicamente es el orden de lectura previsto. El orden físico ignora la secuencia del stream y reconstruye el orden a partir de dónde están realmente los caracteres, agrupándolos en líneas y luego en columnas. Eso es lo que usted quiere para páginas escaneadas y luego pasadas por OCR, para salidas de herramientas que emiten texto en orden de fuente en lugar de orden de lectura, y para cualquier cosa donde el resultado visual es lo único en lo que puede confiar
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfStructuredTextOptions;
Page: TPdfStructuredTextPage;
B, L: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'article.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1; // basado en 1
Options := TPdfStructuredTextOptions.Default;
Options.ReadingOrder := roPhysicalLayout;
Options.IncludeFontInfo := True;
Options.IncludeSemantics := True;
Options.MaxCharacters := 200000; // presupuesto de fallo cerrado
Page := Pdf.GetStructuredText(Options);
for B := 0 to High(Page.Blocks) do
begin
if Page.Blocks[B].Kind = cfHeading then
Emit(Format('H%d: %s',
[Page.Blocks[B].HeadingLevel, Page.Blocks[B].Text]))
else
for L := 0 to High(Page.Blocks[B].Lines) do
Emit(Page.Blocks[B].Lines[L].Text);
end;
finally
Pdf.Free;
end;
end;
¿Qué agrega el etiquetado que la geometría no puede?
Intención. Con IncludeSemantics activado, los bloques de un PDF etiquetado llevan un Kind tomado del árbol de estructura, así que un encabezado es un encabezado porque el productor lo dijo, no porque su fuente era más grande que el promedio. Los tipos cubren las formas que importan para la reutilización: cfParagraph, cfHeading con un HeadingLevel, cfListItem, cfTableCell, cfCaption, cfFigure y el respaldo sin etiquetar cfPlain
El campo Source registra de dónde vino cada clasificación, rosStructure para el árbol de estructura y rosHeuristic para la inferencia, que es el campo que hay que registrar cuando usted está decidiendo hasta qué punto confiar en un pipeline de extracción a lo largo de un conjunto de documentos. Las figuras son un caso especial que vale la pena conocer: para un bloque cfFigure el texto proviene de la descripción alternativa en lugar de de cualquier glifo, ya que una figura no tiene caracteres propios. El texto alternativo sin coincidencia todavía se representa en lugar de descartarse, lo cual es lo que le permite a una auditoría de accesibilidad ver que existe una descripción incluso cuando nada en la página la dibuja. El modelo de etiquetado mismo se cubre en validación del árbol de estructura PDF/UA
Los spans llevan el estilo y la procedencia
Cada TPdfStructuredTextSpan lleva su texto, sus límites en el espacio de página, FontName, FontSize, FontWeight y Angle, más SourceStartIndex y SourceCharacterCount. Los spans se cortan donde cambia el estilo, así que una oración con tres palabras en negrita se convierte en tres spans, y reconstruir el énfasis en HTML o Markdown es cuestión de leer propiedades en lugar de adivinar a partir de nombres de fuente
Los dos campos de índice de origen son los que convierten la extracción en una función y no solo en un reporte. Apuntan de vuelta a la secuencia de caracteres de la página, lo que significa que un bloque que usted encontró en una búsqueda puede convertirse en geometría de selección a nivel de carácter o en un rectángulo de resaltado sin necesitar un segundo recorrido, ordenado de forma distinta, sobre el texto; la mecánica se describe en selección visual de línea de texto con cajas de carácter. El campo Angle importa más de lo que parece: el texto rotado en un sello o una marca de agua queda en el mismo espacio de coordenadas que el texto del cuerpo, y un pipeline que ignora el ángulo felizmente fusionará un "DRAFT" diagonal en medio de un párrafo
Presupuesto, y los dos contadores de calidad
MaxCharacters es un presupuesto de fallo cerrado, no una configuración de truncamiento: una página que lo supera se detiene en lugar de devolver silenciosamente parte del contenido. En una ruta de ingesta no confiable, ese es el comportamiento que usted quiere, porque una página con un millón de caracteres es un monstruo generado por máquina o un intento de convertir su extractor en la parte más lenta del sistema
Dos contadores en la página devuelta describen directamente la calidad de la extracción. UnmappedCharacterCount cuenta caracteres sin un mapeo Unicode utilizable, que es el síntoma clásico de una fuente de subconjunto incrustada sin un CMap /ToUnicode; un texto así se renderiza perfectamente y se extrae como nada útil. GeometryFailureCount cuenta caracteres cuyo cuadro delimitador no pudo determinarse, lo cual degrada el ordenamiento de diseño físico. Registre ambos. Un conjunto de documentos donde esos números están consistentemente cerca de cero puede indexarse con confianza, y uno donde no lo están le está diciendo que algunos productores de su pipeline necesitan atención antes de que cualquier resultado posterior sea confiable
var
Page: TPdfStructuredTextPage;
B, S, L: Integer;
Emphasised: Boolean;
begin
Page := Pdf.GetStructuredText(Options);
if Page.UnmappedCharacterCount > 0 then
Log(Format('page %d: %d characters without a Unicode mapping',
[Page.PageNumber, Page.UnmappedCharacterCount]));
if Page.GeometryFailureCount > 0 then
Log(Format('page %d: %d characters without geometry',
[Page.PageNumber, Page.GeometryFailureCount]));
for B := 0 to High(Page.Blocks) do
for L := 0 to High(Page.Blocks[B].Lines) do
for S := 0 to High(Page.Blocks[B].Lines[L].Spans) do
begin
Emphasised := Page.Blocks[B].Lines[L].Spans[S].FontWeight >= 600;
AppendRun(Page.Blocks[B].Lines[L].Spans[S].Text, Emphasised,
Page.Blocks[B].Lines[L].Spans[S].SourceStartIndex);
end;
end;
Rendimiento en páginas reales
La extracción con diseño físico es el modo costoso, y la implementación está construida para páginas que son realmente grandes: el ordenamiento de caracteres corre en O(n log n) en lugar de mediante escaneo repetido, los búferes de línea y span crecen geométricamente en lugar de reasignarse por carácter, el texto Unicode se construye en búferes en lugar de mediante concatenación de cadenas, y las búsquedas de fuente para objetos de texto adyacentes se cachean. Esa combinación es lo que mantiene predecible una página densa de 5,000 caracteres en lugar de cuadrática
Para un trabajo con muchas páginas, sigue valiendo la pena elegir el modo más barato cuando se pueda. Use roContentOrder con semántica activada para documentos etiquetados en los que confía, y reserve roPhysicalLayout para el material escaneado y heredado donde la geometría es la única señal disponible. Si todo lo que necesita es una cadena simple, la API más sencilla descrita en extracción de texto de documentos PDF sigue siendo la ruta más rápida, y cuando necesite rastrear texto hasta identificadores de contenido marcado, lectura y escritura de contenido marcado BDC y MCID cubre esa capa
El modelo de bloques también se ajusta bien a lo que quieren los pipelines de recuperación de información: un encabezado con sus párrafos es un fragmento con un título, y los límites permiten que una cita apunte a una ubicación en una página en lugar de a un documento entero. PDFiumPas es un componente para Delphi y Lazarus construido alrededor del motor PDFium, documentado con ejemplos en la página del componente PDFium Delphi