HotPDF Component puede buscar y reemplazar texto dentro de un PDF existente desde Delphi y C++Builder. SearchLoadedPageText y SearchLoadedDocumentText localizan cada aparición de una cadena con precisión a nivel de glifo, y ReplaceLoadedPageText y ReplaceLoadedDocumentText reescriben los bytes coincidentes en su lugar — siempre que cada carácter de reemplazo pueda codificarse de nuevo a través de la fuente original, una restricción física que este artículo trata con honestidad en lugar de ocultarla en una nota al pie
La solicitud detrás de esta característica siempre es común. Una empresa cambia de nombre y tres mil facturas archivadas todavía llevan el nombre anterior. Una plantilla de contrato se envió con la fecha de vencimiento del año pasado. Se retiró un código de producto y cada hoja de datos que lo menciona necesita el código sucesor en su lugar. En un procesador de textos, cada uno de estos es un trabajo de treinta segundos. En un PDF es un problema genuinamente difícil, y entender el porqué hace la diferencia entre usar bien la API y enviar un informe de error que en realidad es una cita de la especificación
¿Por qué es tan difícil reemplazar texto en un PDF?
Reemplazar texto en un PDF es difícil porque una página PDF no contiene texto editable — contiene glifos posicionados. Bajo el modelo de visualización de texto de la norma ISO 32000-1 §9.4, un flujo de contenido controla operadores como Tj y TJ que dibujan secuencias de códigos de caracteres en coordenadas establecidas por la matriz de texto. Esos códigos no son Unicode; son índices en cualquier codificación que declare la fuente de la página, y el mapeo de regreso a caracteres legibles puede residir en un CMap /ToUnicode, una matriz de diferencias de codificación o una cadena de mapeo CID. No hay un objeto de párrafo, ni flujo de texto, ni garantía de que una palabra visual esté almacenada como una sola cadena
El reemplazo añade una segunda capa de dificultad además de la decodificación: debe saber exactamente qué bytes del flujo original produjeron cada glifo, de modo que pueda empalmar nuevos bytes precisamente en ese tramo y nada más. Un extractor de texto puede permitirse descartar las posiciones de los bytes una vez que obtiene el Unicode. Un reemplazador no puede hacerlo. Es por eso que HotPDF dividió el trabajo en dos lanzamientos — la versión v2.251.0 construyó la capa de búsqueda y seguimiento de desplazamientos, y la versión v2.252.0 construyó la capa de reescritura sobre ella
Buscar texto: búsqueda a nivel de glifos con seguimiento de desplazamiento de bytes
El método SearchLoadedDocumentText de HotPDF encuentra cada aparición del término buscado al compararlo con la secuencia de glifos Unicode decodificada de cada página, no contra los bytes de flujo sin formato, por lo que una coincidencia es válida independientemente de cómo la haya codificado la fuente. La infraestructura subyacente se introdujo en la versión v2.251.0: el tokenizador de flujo de contenido registra un rango de bytes StartOfs/EndOfs para cada operando de cadena — incluidos sus delimitadores ( ) o < > — y cada glifo decodificado contiene una terna TokenIndex/ItemIndex/ByteOffset que apunta de regreso al operando exacto, al elemento de la matriz TJ y a la unidad de código que lo produjo. El mismo intérprete de glifos potencia la API de extracción descrita en extracción de texto de un PDF cargado en Delphi; la búsqueda simplemente conserva la procedencia que la extracción descarta
Cada coincidencia se devuelve como un registro THPDFTextMatch que contiene el índice de la página, el rango de glifos inclusivo, el origen X/Y del espacio de usuario y el ancho de la coincidencia, el token de origen y el índice del elemento, y el texto coincidente en sí. Eso es suficiente para controlar una superposición de resaltado, una interfaz de usuario de revisión o el paso de reemplazo. Una búsqueda que no encuentra nada devuelve una matriz vacía en lugar de fallar, por lo que el patrón de llamada sigue siendo simple
var
Pdf: THotPDF;
Matches: THPDFTextMatchArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoices-2025.pdf') > 0 then
begin
if Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches) then
for I := 0 to Length(Matches) - 1 do
WriteLn(Format('page %d at (%.1f, %.1f): "%s"',
[Matches[I].PageIndex, Matches[I].X, Matches[I].Y,
Matches[I].Text]));
end;
finally
Pdf.Free;
end;
end;
Una elección de diseño deliberada merece una nota. Cuando CaseSensitive es False, la comparación ignora mayúsculas y minúsculas solo para caracteres ASCII, por diseño: la conversión completa de mayúsculas y minúsculas de Unicode se comporta de manera diferente en las cadenas de herramientas de Delphi 5 a XE que admite HotPDF, y una API de búsqueda que encuentra diferentes coincidencias según el compilador que creó su aplicación es peor que una con un límite documentado y predecible. Para texto comercial latino — nombres, códigos, fechas — la conversión ASCII cubre los casos prácticos
Reemplazar texto: codificación inversa y empalme quirúrgico
El método ReplaceLoadedDocumentText, añadido en HotPDF v2.252.0, reescribe cada aparición del término buscado ejecutando la maquinaria de decodificación a la inversa. La función HPDFEncodeUnicode es the inverse del decodificador de códigos de caracteres: recorre la misma cadena de estrategias a la inversa — búsqueda de bfchar y bfrange en /ToUnicode, mapeo CID de flujo de codificación, mapeos de identidad de Type0 y las tablas predefinidas WinAnsi y MacRoman — para convertir cada carácter de reemplazo de regreso a los bytes de código de caracteres que la fuente original espera. Los bytes codificados de nuevo se serializan luego en un literal de cadena bien formado o en una cadena hexadecimal, reflejando las propias reglas de escape del tokenizador para que el ciclo completo de análisis y nueva serialización sea estable
El empalme en sí es quirúrgico en lugar de total. Solo el rango de bytes de código cubierto por la coincidencia se reemplaza dentro del operando de cadena; los bytes no coincidentes en el mismo operando, el espacio en blanco entre tokens y cada operador circundante se conservan textualmente, byte por byte. Reemplazar bca dentro de abcabc produce a + reemplazo + bc, no un operando dañado. Los reemplazos pueden ser más cortos o más largos que el término buscado — el literal se vuelve a serializar y la longitud /Length del flujo se actualiza — y cada flujo /Contents de una página con múltiples flujos se procesa de forma aislada para que la página se mantenga bien formada
var
Pdf: THotPDF;
ReplaceCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-draft.pdf') > 0 then
begin
if Pdf.ReplaceLoadedDocumentText('2025-12-31', '2026-12-31',
True, ReplaceCount) then
WriteLn(Format('%d operand rewrites performed', [ReplaceCount]));
Pdf.SaveLoadedDocument('contract-final.pdf');
end;
finally
Pdf.Free;
end;
end;
Tenga en cuenta lo que la API no hace: no vuelve a diagramar el texto en la página. PDF no tiene reflujo de texto, por lo que un reemplazo que es visualmente más ancho que el original simplemente ocupará más espacio horizontal y puede amontonar lo que esté dibujado a su derecha. Las sustituciones de igual longitud o de longitud cercana — fechas, cadenas de versión, números de pieza, correcciones de nombres — son el escenario ideal. La reescritura total pertenece al documento de origen, no al PDF
¿Por qué no se puede reemplazar texto con caracteres que el subconjunto de la fuente nunca incluyó?
No se puede reemplazar texto con un carácter que el subconjunto de fuente incrustado nunca incluyó, porque la secuencia de bytes que seleccionaría ese carácter simplemente no existe en las tablas de mapeo de la fuente. Cuando un productor de PDF incrusta una fuente de subconjunto, su CMap /ToUnicode y sus estructuras de codificación cubren solo los glifos que el documento original realmente utilizó. HPDFEncodeUnicode solo puede revertir un mapeo que esté presente: si el documento nunca contuvo la letra E en esa fuente, no hay código de carácter para E al cual revertir. Esta es una propiedad física del archivo, no una limitación de una biblioteca en particular — ninguna herramienta puede conjurar un mapeo de glifos que nunca fue incrustado
HotPDF maneja la falla de manera conservadora. Si un solo carácter del reemplazo no se puede codificar de nuevo, se omite toda la aparición de ese término — sin excepciones, sin texto parcial dañado, y la aparición simplemente no se cuenta en ReplaceCount. La consecuencia práctica: compare ReplaceCount con el recuento de coincidencias de una búsqueda previa y trate un faltante como una señal. En el ejemplo de la fecha anterior, el dígito 6 debe aparecer en algún lugar del texto del documento con esa misma fuente para que la reescritura tenga éxito — probablemente en una factura, lo cual nunca está garantizado en general. Cuando los caracteres que necesita simplemente no están disponibles y el objetivo es eliminar texto confidencial en lugar de reescribirlo, la eliminación real de contenido es la mejor herramienta; consulte censura y reestructuración de archivos PDF cargados en Delphi para esa ruta
var
Matches: THPDFTextMatchArray;
Expected, Replaced: Integer;
begin
Pdf.SearchLoadedDocumentText('Acme Corp', True, Matches);
Expected := Length(Matches);
Pdf.ReplaceLoadedDocumentText('Acme Corp', 'Apex Corp', True, Replaced);
if Replaced < Expected then
WriteLn(Format('%d occurrence(s) skipped: characters missing ' +
'from the font subset, or match spans multiple operands',
[Expected - Replaced]));
end;
La segunda condición de omisión en ese mensaje es el otro límite documentado: un término que abarca múltiples operandos de cadena — por ejemplo, Hello dividido en elementos [(He)(llo)] TJ — se encuentra mediante la búsqueda, porque la búsqueda coincide con la secuencia de glifos decodificada, pero el reemplazo lo omite, porque reescribir a través de los límites del operando requeriría fusionar tramos de bytes adyacentes. Buscar y luego verificar hace que ambos límites sean visibles en lugar de silenciosos
Qué cambia en el archivo al guardar
Un flujo /Contents reemplazado se guarda sin comprimir. Los flujos comprimidos con FlateDecode se descomprimen para su edición, y cuando HotPDF escribe los bytes reconstruidos, elimina la entrada /Filter del flujo y actualiza /Length en lugar de volver a comprimir. El PDF resultante es completamente válido y se representa normalmente en los visores habituales; el compromiso es un archivo más grande para cada flujo editado. Para un flujo de trabajo por lotes que procesa miles de documentos, presupueste ese crecimiento o ejecute una pasada de compresión independiente en los pasos siguientes. Cómo interactúan los objetos reescritos con la estructura de referencia cruzada del documento al guardar es un tema propio, cubierto en flujos de objetos y actualizaciones incrementales en HotPDF
Todo lo demás en el archivo se deja intacto. Los flujos no tocados conservan su compresión, las fuentes y las imágenes no se vuelven a escribir, y el empalme a nivel de operando significa que incluso los flujos editados difieren del original solo en el lugar donde se produjo una coincidencia. Ese conservadurismo es deliberado: cuanto más reescribe una biblioteca un documento cargado, más oportunidades tiene de romper alguna peculiaridad del productor que no anticipó
La búsqueda y reemplazo de texto se une a la extracción, censura y representación de páginas en el conjunto de herramientas para documentos cargados de HotPDF, todo controlado por el mismo intérprete de flujo de contenido y disponible desde Delphi 5 hasta las versiones actuales de RAD Studio sin dependencias externas. La referencia completa de la API y la descarga de prueba se encuentran en la página del producto HotPDF Component