PDFium Component convierte un PDF de diseño fijo en un modelo semántico que puede reflujarse, usando BuildReflowDocument, y exporta ese modelo como HTML autocontenido mediante ToHtml. Los encabezados siguen siendo encabezados, los elementos de lista siguen siendo elementos de lista, y las tablas detectadas en la página salen como marcado de tabla real con celdas de encabezado y combinaciones preservadas. Nada en la salida hace referencia a un script o una hoja de estilos externos
La razón para querer esto es que una página PDF es un conjunto de glifos posicionados, que es exactamente lo incorrecto para una pantalla de teléfono, un lector de pantalla o un índice de búsqueda. Todo intento de resolverlo extrayendo texto plano pierde la estructura que hacía legible el documento, y todo intento de resolverlo convirtiendo páginas en imágenes pierde el texto por completo. Un modelo de reflujo conserva ambas cosas: las palabras y las relaciones entre ellas
¿De dónde proviene la información semántica?
Todo empieza en GetStructuredText, la única fuente de texto y semántica en el componente. Cuando el PDF lleva un árbol de estructura, PDF etiquetado según lo define ISO 32000-1 cláusula 14.7, el modelo sigue la jerarquía lógica que registró el productor. Cuando no lo lleva, y la mayoría de los PDF que circulan no lo llevan, el modelo recurre al orden de diseño físico ya calculado con fines de orden de lectura
Esa elección mantiene un límite estricto: no se introduce un segundo analizador de PDF ni un segundo motor de renderizado para responder preguntas que el existente ya puede responder. La maquinaria de orden de lectura subyacente se describe en bloques de texto estructurado y orden de lectura, y el modelo de reflujo es una capa semántica encima de ella, no un reemplazo
Cada nodo registra de dónde provino su información, así que un consumidor puede distinguir un encabezado que el documento declaró de un encabezado que las heurísticas de diseño infirieron. Las canalizaciones sensibles a la confianza deberían leer ese campo en lugar de tratar todos los nodos como igualmente autorizados
Un árbol plano, y por qué no es un árbol de objetos
El modelo es un árbol aplanado en preorden: un arreglo de nodos donde cada nodo lleva un ParentIndex y una Depth, en lugar de un registro recursivo o un grafo de objetos con propiedad. Páginas, encabezados, párrafos, listas, elementos de lista, figuras, leyendas, tablas, filas y celdas viven todos en ese único arreglo lineal
De ahí se derivan dos beneficios. Los consumidores pueden transmitir el arreglo en orden sin recursión, lo que convierte la emisión de HTML, Markdown o una vista de árbol en un simple bucle. Y el diseño se mantiene portable entre Delphi, C++Builder y Free Pascal, que difieren en cómo manejan los tipos administrados recursivos a través de un límite ABI. Un registro recursivo de arreglos dinámicos es exactamente el tipo de construcción que compila en todas partes y se comporta de forma sutilmente distinta en cada una
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfReflowOptions;
Doc: TPdfReflowDocument;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.LoadDocument;
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.DetectTables := True;
Options.IncludeCss := True; // bloque de estilo en línea, sin archivo externo
Options.MaxNodes := 200000; // presupuesto que falla de forma cerrada
Options.MaxCharacters := 4000000;
Doc := Pdf.BuildReflowDocument(Options);
for I := 0 to High(Doc.Nodes) do
case Doc.Nodes[I].Kind of
prnkHeading:
Writeln(Format('%sH%d: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Doc.Nodes[I].HeadingLevel, Doc.Nodes[I].Text]));
prnkParagraph:
Writeln(Format('%sp: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Copy(Doc.Nodes[I].Text, 1, 60)]));
prnkTable:
Writeln(Format('table on page %d', [Doc.Nodes[I].PageNumber]));
end;
Writeln(Format('%d node(s), %d table(s), %d character(s)',
[Length(Doc.Nodes), Doc.TableCount, Doc.CharacterCount]));
finally
Pdf.Free;
end;
end;
¿Cómo se evita que las tablas aparezcan dos veces?
La detección de tablas se ejecuta después de que se recolectó el texto estructurado de una página, lo que crea un riesgo evidente: el mismo contenido de celda existe tanto en los bloques de texto como en la tabla detectada. Emitir ambos produce HTML donde cada tabla va seguida de su propio contenido otra vez como párrafos sueltos
La regla que resuelve esto es geométrica. Cuando una tabla detectada cubre más de la mitad del área de un bloque de texto, el nodo de tabla reemplaza a ese bloque en lugar de unírsele. La indexación de celdas dentro de una fila se construye contando en cubetas, así que construir el modelo se mantiene lineal en celdas más filas en lugar de volver a escanear cada celda por cada fila, lo cual importa en documentos financieros donde una sola página puede llevar cientos de celdas
La estructura detectada es honesta respecto a ser detección. Una tabla con líneas de regla se reconoce de forma más confiable que una alineada solo por espacios en blanco, y la confianza del nodo refleja eso. Para contenido donde una tabla equivocada es mejor que ninguna tabla, deja la detección activada; para conversión de archivo donde una tabla equivocada es peor, filtra según la confianza
Exportar HTML que se mantenga autocontenido
ToHtml recorre el modelo que ya se construyó y nunca vuelve a consultar PDFium, así que exportar dos veces no cuesta nada extra y no puede producir un resultado distinto a partir del mismo modelo. El texto y los valores de atributo se escapan de manera uniforme, los niveles de encabezado se acotan al rango h1 a h6 que HTML realmente define, y las celdas de encabezado, RowSpan y ColumnSpan pasan tal como se escribieron
El CSS opcional es un simple bloque de estilo en línea. No hay script, ni fuente web, ni ningún recurso externo de ningún tipo, que es lo que hace segura la salida para incrustarla en un correo electrónico, un visor de ayuda o un control de navegador en sandbox:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // mantener visibles los límites de página
Options.PreserveLineBreaks := False; // dejar que el navegador ajuste los párrafos
Html := Pdf.BuildReflowDocument(Options).ToHtml;
Bytes := TEncoding.UTF8.GetBytes(string(Html));
Stream := TFileStream.Create('report.html', fmCreate);
try
if Length(Bytes) > 0 then
Stream.WriteBuffer(Bytes[0], Length(Bytes));
finally
Stream.Free;
end;
end;
PreserveLineBreaks es la opción que más vale la pena considerar. Un salto de línea en PDF es una decisión de composición tipográfica tomada para un ancho de página fijo, así que preservarlo en una pantalla angosta reproduce exactamente el problema que el reflujo existe para resolver. Preserva los saltos para poesía, listados de código y direcciones; descártalos para prosa
Presupuestos, cancelación y estado de página
Los caracteres, los nodos, las tablas y las celdas tienen cada uno un tope, y cada uno se verifica antes de la asignación de memoria y no después, así que un documento malformado u hostil falla de forma limpia en lugar de consumir memoria hasta que algo más lo haga. El token de cancelación se verifica en los límites de página, bloque, tabla, fila y celda, lo que mantiene responsivo un escaneo cancelado de un documento de mil páginas
Un comportamiento importa específicamente para aplicaciones con interfaz gráfica: todo el escaneo del documento se ejecuta dentro de un alcance que restaura la página activa, así que el éxito, el fallo de presupuesto y la cancelación dejan todos intacta la página actual de quien llama. Un visor que permite al usuario exportar mientras mira la página 340 se encuentra todavía en la página 340 después
Para qué sirve el reflujo, y para qué no
La salida de reflujo es una entrada excelente para indexación de búsqueda, vistas de lectura accesibles, visualización móvil y migración de contenido. No es un conversor que preserve fidelidad: las posiciones absolutas, las fuentes exactas, las ilustraciones vectoriales y la geometría precisa de página quedan fuera de su propósito por diseño. Cuando un trabajo necesita que la página se vea igual, renderízala; cuando necesita que la página sea legible en otro lugar, reflúyela
Específicamente para tecnología de asistencia, el modelo de reflujo se combina con las funciones de lectura descritas en construir un lector accesible, y los documentos que llevan un árbol de estructura genuino producen modelos notablemente mejores, lo cual es un buen argumento para validar el etiquetado más arriba en la canalización, como se describe en validación del árbol de estructura PDF/UA
El reflujo, el texto estructurado, la validación de etiquetado y el renderizado comparten un objeto de documento en Delphi, C++Builder y Lazarus; la API completa se describe en la página de PDFium Component para Delphi