Todo extractor de texto geométrico está adivinando. Lee los glifos que una página dibuja, los ordena por línea base y posición horizontal, y espera que la disposición visual coincida con el orden en que una persona leería. En un reporte de una sola columna esa adivinanza acierta. En un artículo de revista a dos columnas, un formulario con barra lateral o una tabla cuyas celdas se emitieron columna por columna, se equivoca de maneras difíciles de notar y costosas de descubrir aguas abajo. HotPDF responde a esto con ExtractLoadedPageStructureText, que ignora por completo la geometría: recorre el árbol de estructura del documento en el orden de autoría definido por ISO 32000-1 §14.8.4 y después reensambla los glifos de la página por su identificador de contenido marcado. Para un PDF etiquetado eso no es una heurística, es el orden que la aplicación productora declaró
La función devuelve False cuando la página no tiene un árbol de estructura utilizable, que es la señal de replegarse al extractor geométrico en lugar de fallar. Ese diseño de dos vías importa más que el algoritmo: la admisión real de documentos ve formularios gubernamentales etiquetados y salida de escáner en la misma carpeta, y un pipeline que solo atiende a uno de ellos no es un pipeline
Por qué la extracción geométrica se equivoca en el orden de lectura
Porque un flujo de contenido PDF no lleva ningún orden de lectura. Es una secuencia de operadores de dibujo, y un productor es libre de emitirlos en la secuencia que le convenga a su propio motor de maquetación. Los procesadores de texto suelen emitir en orden de flujo y el ordenamiento geométrico se ve bien. Las herramientas de maquetación, los diseñadores de formularios y los generadores de reportes con frecuencia no: un pie de página puede emitirse antes del cuerpo, una tabla puede llenarse por columnas y una página a dos columnas puede intercalar líneas de ambas columnas porque el compositor las resolvió juntas
El modo de fallo es silencioso. Un extractor geométrico nunca reporta un error, simplemente devuelve prosa cuyas oraciones están empalmadas de dos columnas. Cualquier cosa que consuma ese texto, un índice de búsqueda, un mapeador de campos de factura electrónica, un pipeline de recuperación que alimenta a un modelo de lenguaje, hereda el daño sin aviso. HotPDF también incluye los extractores geométricos para documentos cargados, y siguen siendo la herramienta correcta para archivos sin etiquetas; el punto de la vía por orden estructural es dejar de adivinar cuando el documento ya lleva la respuesta
Qué guarda realmente el árbol de estructura
Un PDF etiquetado guarda una segunda descripción paralela de la página. El catálogo apunta a un /StructTreeRoot cuyos hijos /K forman un árbol de elementos de estructura: /Document, /Sect, /P, /Table, /TR, /TD y demás. Las hojas de ese árbol son referencias de contenido marcado, enteros que nombran un tramo del flujo de contenido de la página. Del lado del contenido, esos tramos se abren con un operador BDC que lleva un /MCID y se cierran con EMC. Cada elemento de estructura lleva además una entrada /Pg que nombra la página a la que pertenece, que es lo que hace posible el recorrido por página en un documento cuyo árbol de estructura abarca cientos de páginas
HotPDF recorre ese árbol con un tope de profundidad de 128 niveles y filtra por /Pg de modo que solo la página actual contribuya. La salida del recorrido no es texto, es una lista ordenada de valores MCID: el orden de autoría de los tramos de contenido marcado de esta página. Reensamblar el texto es entonces cuestión de reproducir los glifos en ese orden
El MCID se registra durante la extracción de glifos, no se busca después
Este es el detalle de implementación que vuelve barata la función. HotPDF ya registra el identificador de contenido marcado activo en cada glifo que extrae, en el campo MCID de THPDFGlyphRecord, porque el intérprete del flujo de contenido sabe qué ámbito BDC está abierto en el momento de procesar cada operador Tj o TJ. La extracción por orden estructural no necesita entonces una segunda pasada por el flujo de contenido. Recoge la secuencia MCID del árbol de estructura, luego agrupa los glifos ya extraídos por MCID y los emite en esa secuencia
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // sumidero de diagnósticos del que llama
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('accessible-form.pdf');
AllText := '';
for I := 0 to PageCount - 1 do
begin
if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
begin
// Orden de autoría directo del árbol de estructura
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// Sin árbol de estructura utilizable en esta página: respaldo geométrico
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
Los glifos sin etiqueta se cuentan, nunca se descartan en silencio
Una página puede estar etiquetada en parte. Los productores añaden una línea decorativa, un número de página o una marca de agua tardía fuera de todo ámbito BDC, y esos glifos no pertenecen a ningún MCID. Descartarlos sería la implementación prolija y la equivocada, porque el mismo hueco aparece también cuando un productor etiqueta el cuerpo pero olvida la tabla, y perderían la tabla sin notarlo
HotPDF anexa los glifos sin dueño como una cola geométrica después del texto ordenado estructuralmente y reporta su número mediante el parámetro de salida UntaggedGlyphCount. Ese número es una señal de calidad sobre la que pueden actuar. Un puñado de glifos en una página de dos mil es mobiliario de página y puede ignorarse. Cuarenta por ciento de la página fuera del árbol de estructura significa que el etiquetado es decorativo y que el extractor geométrico es la respuesta más honesta para ese archivo
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
Untagged, TotalGlyphs: Integer;
Glyphs: THPDFGlyphArray;
begin
UsedStructure := False;
if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
begin
TotalGlyphs := 0;
if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
TotalGlyphs := Length(Glyphs);
// Confiar en el árbol de estructura solo cuando reclama la mayor parte de la página
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
Qué hace que la función devuelva False
Tres casos, y vale distinguirlos porque solo uno de ellos es un defecto del documento. El primero es un PDF sin etiquetas corriente: sin /StructTreeRoot, nada que recorrer, y False es sencillamente la verdad. El segundo es una página escaneada cuyo texto viene de una capa OCR que nunca se etiquetó. El tercero es el interesante: contenido que lleva operadores BDC con valores /MCID pero cuya página no tiene entrada /StructParents y cuyo árbol de estructura nunca referencia esos identificadores. El contenido marcado existe, el lado de la estructura no, y no hay orden que recuperar. HotPDF reporta False en lugar de inventar uno
Ese último caso aparece en archivos editados a mano y en salidas de herramientas que emiten contenido marcado con fines de contenido opcional o de artefacto sin construir un árbol de estructura. Si ustedes mismos producen PDF etiquetados, la misma asimetría es la que comprueba la validación PDF/UA, y la contraparte del lado del escritor se cubre en el DOM de maquetación que emite salida etiquetada y paginada
Dónde el orden estructural se paga solo
La auditoría de accesibilidad es la obvia: si están certificando un documento contra PDF/UA, el orden de lectura que anunciará un lector de pantalla es exactamente el orden estructural, así que extraerlo es la forma de revisarlo sin un lector de pantalla. La captura de datos es el caso comercial mayor. Los formularios gubernamentales etiquetados, los reportes regulatorios y los adjuntos de factura electrónica llevan etiquetas y valores de campos en el orden declarado, y leerlos en ese orden elimina toda una clase de errores de mapeo que la extracción geométrica crea en maquetaciones de varias columnas
El consumidor más nuevo es la recuperación para modelos de lenguaje. Trocear un documento para embeddings es solo tan bueno como el orden del texto, y un trozo que empalma dos columnas produce oraciones que nunca existieron. La extracción por orden estructural es el arreglo más barato disponible para eso, porque en los documentos etiquetados el orden correcto ya está en el archivo y solo hay que leerlo
HotPDF es un componente VCL nativo para Delphi y C++Builder, así que el recorrido del árbol de estructura y la reproducción de glifos corren ambos en proceso contra un documento cargado sin ningún renderizador externo. Los detalles completos de la API de la familia de extracción de documentos cargados están en la página del producto HotPDF Delphi PDF component