Todo extractor de texto geométrico está adivinando. Lee los glifos que dibuja una página, los ordena por línea base y posición horizontal, y espera que la disposición visual coincida con el orden en que leería una persona. En un informe de una sola columna esa suposición es correcta. En un artículo de revista a dos columnas, un formulario con barra lateral o una tabla cuyas celdas se emitieron columna a columna, está mal de formas difíciles de notar y caras de descubrir aguas abajo. HotPDF responde a esto con ExtractLoadedPageStructureText, que ignora la geometría por completo: recorre el árbol de estructura del documento en orden de autoría tal como lo define ISO 32000-1 §14.8.4 y luego 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 para caer al extractor geométrico en lugar de fallar. Ese diseño de dos rutas 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 maneja uno de ellos no es un pipeline
Por qué el extractor geométrico se equivoca con el orden de lectura?
Porque un content stream de PDF no lleva orden de lectura alguno. Es una secuencia de operadores de dibujo, y un productor es libre de emitirlos en la secuencia que convenga a su propio motor de maquetación. Los procesadores de texto suelen emitir en orden de flujo y el ordenamiento geométrico parece correcto. Las herramientas de maquetación, los diseñadores de formularios y los generadores de informes con frecuencia no: un pie de página puede emitirse antes del cuerpo, una tabla puede rellenarse 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 informa de un error, solo devuelve prosa cuyas frases están empalmadas desde 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 ficheros sin etiquetas; el objetivo de la ruta de 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, etcétera. Las hojas de ese árbol son referencias de contenido marcado, enteros que nombran un tramo del content stream de la página. En el 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 también una entrada /Pg que nombra la página a la que pertenece, y eso es lo que hace posible el recorrido por páginas 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 contribuye la página actual. 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
Ese es el detalle de implementación que hace 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 content stream sabe qué ámbito BDC está abierto en el momento de procesar cada operador Tj o TJ. La extracción en orden estructural no necesita, por tanto, una segunda pasada sobre el content stream. Recolecta la secuencia de MCID del árbol de estructura, 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óstico propiedad de quien 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: fallback 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 a medias. Los productores añaden un filete decorativo, un número de página o una marca de agua tardía fuera de cualquier ámbito BDC, y esos glifos no pertenecen a ningún MCID. Descartarlos sería la implementación ordenada y la equivocada, porque el mismo hueco aparece también cuando un productor etiqueta el cuerpo pero olvida la tabla, y perderíais la tabla sin enteraros
HotPDF añade los glifos sin reclamar como cola geométrica tras el texto ordenado estructuralmente e informa de su número mediante el parámetro de salida UntaggedGlyphCount. Ese número es una señal de calidad sobre la que podéis actuar. Un puñado de glifos en una página de dos mil es mobiliario de página y puede ignorarse. Que el cuarenta por ciento de la página esté fuera del árbol de estructura significa que el etiquetado es decorativo y el extractor geométrico es la respuesta más honesta para ese fichero
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 merece la pena distinguirlos porque solo uno de ellos es un defecto del documento. El primero es un PDF ordinario sin etiquetas: sin /StructTreeRoot, nada que recorrer, y False es simplemente 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 informa de False en lugar de inventárselo
Ese último caso aparece en ficheros editados a mano y en la salida de herramientas que emiten contenido marcado para optional content o artefactos sin construir un árbol de estructura. Si vosotros producís PDF etiquetados, la misma asimetría es lo que comprueba la validación PDF/UA, y la contrapartida en el 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 el caso obvio: si estáis 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 lector de pantalla. La captura de datos es el caso comercial mayor. Los formularios gubernamentales etiquetados, las declaraciones reguladas y los adjuntos de factura electrónica llevan etiquetas de campo y valores en orden declarado, y leerlos en ese orden elimina toda una clase de errores de mapeo que la extracción geométrica crea en maquetaciones a varias columnas
El consumidor más nuevo es la recuperación para modelos de lenguaje. Trocear un documento para embeddings vale solo tanto como el orden del texto, y un trozo que empalma dos columnas produce frases que nunca existieron. La extracción en orden estructural es el arreglo más barato disponible para eso, porque en los documentos etiquetados el orden correcto ya está en el fichero 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 renderizador externo implicado. Los detalles completos de la API de la familia de extracción de documentos cargados están en la página de producto de HotPDF Delphi PDF component