HotPDF llena campos comb de AcroForm con texto árabe, hebreo, marcas combinantes y de dirección mixta mediante THPDFHeadlessDocument.SetTextFieldValueWithFamilies. La cuadrícula comb se cuenta en escalares Unicode, una ligadura o una letra base con sus marcas se centra como un cluster a lo largo de las k celdas que sus escalares poseen, y toda la línea pasa por el algoritmo bidireccional Unicode antes de asignar cualquier celda
Cada una de esas tres reglas reemplaza un atajo que se ve bien en una demo latina y se desmorona en el primer formulario real. Imagine una solicitud gubernamental con un campo comb de diez casillas para un nombre escrito en árabe. Llénelo con lógica por carácter y obtiene letras en formas aisladas marchando de izquierda a derecha, una ligadura lam-alef apretada en una casilla mientras su vecina está vacía, y un fatha flotando sobre la celda equivocada. Cada uno de esos defectos viene de contar la unidad equivocada u ordenar el texto en el momento equivocado
¿Qué le pide en realidad la bandera Comb a un writer de PDF?
La bandera Comb le pide al writer dividir el campo en exactamente MaxLen posiciones igualmente espaciadas y poner un carácter en cada una. ISO 32000-1 §12.7.4.3 define Comb como la posición de bit 25 en la Tabla 228, las banderas de campo específicas de campos de texto, y le da sentido solo cuando la entrada MaxLen de la Tabla 229 está presente y las banderas Multiline, Password y FileSelect están limpias. El bit mismo es $1000000 en el valor /Ff de 32 bits; si alguna vez vio campos comb portarse mal tras un viaje de ida y vuelta por una variable de punto flotante, el artículo sobre banderas de campo AcroForm de 32 bits explica por qué ese bit es el primero en perderse
La especificación dice "carácter" y hasta ahí llega. Fue escrita pensando en casillas latinas de paso fijo y no dice qué es un carácter cuando un par de letras árabes se vuelve un glifo, o cuando una marca de vocal no tiene ancho propio. HotPDF tiene que elegir una unidad, y hace cumplir las reglas estructurales estrictamente mientras lo hace: un campo comb que además sea multiline, o que no lleve un MaxLen positivo, se rechaza con un diagnóstico, y los campos de contraseña no pueden ser campos comb en absoluto
¿Por qué contar escalares Unicode en vez de unidades UTF-16, glifos o graphemes?
HotPDF cuenta las celdas comb en escalares Unicode porque es la misma unidad que usa para hacer cumplir MaxLen, y la cuadrícula y la verificación de longitud deben concordar o un valor que pasa la validación no cabrá en sus casillas. Las tres alternativas tentadoras rompen ese acuerdo cada una de una forma distinta
- Las unidades de código UTF-16 son lo que devuelve el
Lengthde Delphi, así que un carácter suplementario como U+20BB7 se comería dos casillas y dos slots del presupuesto del campo - Los conteos de glifos dependen de la fuente: la mayoría de las fuentes árabes convierten lam más alef en un glifo, así que el conteo de celdas cambiaría en cuanto cambie de tipografía
- Los conteos de graphemes fusionan una letra base con sus marcas, lo que significa que la cuadrícula discreparía de una verificación
MaxLenque cuenta cada escalar que el usuario tecleó
La validación MaxLen en THPDFHeadlessDocument solo recorre la cadena cuando su longitud UTF-16 excede el límite, y cuando lo hace, cuenta un par surrogate válido una vez y rechaza un surrogate sin pareja sin más. Si su UI quiere advertir antes de la llamada en vez de capturar una excepción después, cuente de la misma forma
// Cuenta escalares Unicode como lo hace la cuadrícula comb:
// un par surrogate válido es un escalar
function CountScalars(const S: UnicodeString): Integer;
var
I: Integer;
begin
Result := 0;
I := 1;
while I <= Length(S) do
begin
if (Ord(S[I]) >= $D800) and (Ord(S[I]) <= $DBFF) and
(I < Length(S)) and
(Ord(S[I + 1]) >= $DC00) and (Ord(S[I + 1]) <= $DFFF) then
Inc(I, 2)
else
Inc(I);
Inc(Result);
end;
end;
Algunos valores muestran qué tanto se separan las unidades, y por qué solo la columna de escalares coincide con lo que un campo comb presupuesta
| Valor | Unidades UTF-16 | Escalares (celdas) | Glifos típicos |
|---|---|---|---|
| Lam + alef (لا) | 2 | 2 | 1 ligadura |
| Beh + fatha (بَ) | 2 | 2 | 2, marca unida a la base |
| U+20BB7 (𠮷) | 2 | 1 | 1 |
| e + U+0301 descompuesto | 2 | 2 | 1 o 2, depende de la fuente |
¿Cómo comparten sus celdas las ligaduras y las marcas combinantes?
Un cluster con shaping que cubre k escalares ocupa k celdas contiguas y se centra como una sola unidad a través de ellas. Tras el shaping, cada cluster se mapea de vuelta al tramo exacto de texto fuente del que vino, así que HotPDF sabe qué escalares, y por tanto qué celdas, posee. La ligadura lam-alef de arriba recibe dos casillas y queda centrada a lo largo de ambas. Una beh que lleva una shadda y un fatha son tres escalares, así que recibe tres casillas, con las marcas todavía unidas a la letra
Centrar es donde la implementación obvia se equivoca. Centrar cada glifo en su propia casilla separa una marca de su base, porque la posición de la marca solo significa algo relativo a la letra que decora. HotPDF mantiene cada glifo dentro de un cluster en su avance con shaping y sus offsets X e Y, y luego centra el cluster usando la extensión horizontal combinada del avance natural, los orígenes de glifo posicionados y la tinta real del glifo. El cluster se mueve como bloque; nada dentro de él se rearma
Una consecuencia es fácil de pasar por alto. Los controles direccionales como U+200F RIGHT-TO-LEFT MARK, U+061C ARABIC LETTER MARK, y los controles de embedding e isolate son escalares Unicode sin glifo. Cuentan contra MaxLen y sostienen una celda que queda en blanco. Si sus datos de origen vienen de un sistema que espolvorea estas marcas en los nombres, quítelas antes de llenar, o el campo mostrará un hueco y rechazará valores un carácter antes de lo que el usuario espera
¿Por qué el reordenamiento bidi debe ocurrir antes de asignar celdas?
HotPDF pasa la línea completa por el algoritmo bidireccional Unicode (UAX #9) primero, y solo entonces le da a cada escalar su celda según su posición visual. Asignar celdas en orden lógico y voltear el resultado después funciona para una cadena puramente árabe y falla tan pronto la línea contiene dígitos o un token latino, porque esos tramos deben seguir leyéndose de izquierda a derecha dentro de la línea de derecha a izquierda. Un valor como un código de referencia con prefijo árabe y un número mantiene sus dígitos en el orden correcto solo si el reordenamiento ve toda la línea de una vez. La visión general del shaping de texto complex-script cubre el problema de orden lógico contra visual con más profundidad
La alineación luego posiciona toda la cuadrícula de celdas usadas dentro del campo. La entrada /Q heredada del campo gana cuando está presente; Options.Alignment aplica solo cuando el formulario no tiene ninguna. Eso importa para formularios de derecha a izquierda: un valor árabe corto en un campo comb alineado a la izquierda llena las casillas de la izquierda, lo que es correcto según la especificación y casi nunca lo que el diseñador del formulario pretendía. Arregle el /Q del formulario, o ponga htaRight cuando el campo no tenga quadding propio. La rotación del widget viene de la entrada /R de /MK, y a 90 o 270 grados el viewport lógico del texto intercambia ancho y alto mientras la lógica de celdas queda igual
Llenar un campo comb de fuentes mixtas desde Delphi
El texto comb de fuentes mixtas usa una sola cuadrícula de celdas, un solo tamaño de fuente y una sola baseline para cada fuente provista. Con FallbackFontFamilies en True, HotPDF elige una fuente para cada grapheme completo de las familias provistas en su orden configurado, así que una fuente árabe puede llevar las letras mientras una latina lleva ASCII incrustado. Cada tramo de fuente se moldea con toda la línea como contexto, una marca nunca se separa de su base entre fuentes, y no se inventa ninguna ligadura a través de una frontera de fuente. Maquetar cada tramo con su propia cuadrícula local o escalarlo a su propio bounding box sería más simple, y rompería tanto las posiciones de celdas como los campos rotados en cuanto el fallback cambiara de fuente a mitad de línea
uses
System.SysUtils, System.IOUtils,
HPDFHeadlessDocument, HPDFHeadlessAppearance;
procedure FillArabicComb(const SourceFile, TargetFile, Value: string);
var
Doc: THPDFHeadlessDocument;
Field: THPDFHeadlessFormField;
Families: THPDFHeadlessRichTextFontFamilies;
Options: THPDFHeadlessTextAppearanceOptions;
FieldIndex: Integer;
begin
Doc := THPDFHeadlessDocument.Create;
try
Doc.LoadFromFile(SourceFile);
FieldIndex := Doc.FindFormField('applicant.name_ar');
if FieldIndex < 0 then
raise Exception.Create('Field not found');
Field := Doc.GetFormField(FieldIndex);
if (Field.Flags and $1000000) = 0 then // bit 25: Comb
raise Exception.Create('Not a comb field');
SetLength(Families, 2); // de una a dieciséis familias
Families[0].FamilyName := 'Noto Naskh Arabic';
Families[0].Fonts[hrfsRegular] :=
TFile.ReadAllBytes('NotoNaskhArabic-Regular.ttf');
Families[1].FamilyName := 'Noto Sans';
Families[1].Fonts[hrfsRegular] :=
TFile.ReadAllBytes('NotoSans-Regular.ttf');
Options := THPDFHeadlessTextAppearanceOptions.Default;
Options.ShapeComplexText := True; // ya es el valor por defecto
Options.FallbackFontFamilies := True; // el valor por defecto es False
Options.FontSize := 0; // ajuste automático, tope en 12
Options.Alignment := htaRight; // se usa solo cuando /Q está ausente
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
Doc.SaveToFile(TargetFile); // debe diferir de SourceFile
finally
Doc.Free;
end;
end;
Note lo que el ejemplo no pone. Durante el llenado del documento, Width, Height, Rotation y CombCells se derivan del rectángulo de cada widget, su entrada /MK y el MaxLen del campo, así que los valores que ponga en esos miembros se sobrescriben. Un FontSize cero pide dimensionado automático, que verifica la extensión de cada cluster contra el ancho de sus k celdas y la altura métrica y de tinta compartida de todas las fuentes participantes; un tamaño fijo simplemente se recorta al widget. CharacterSpacing y WordSpacing nunca mueven posiciones comb, ya que la cuadrícula sola decide dónde aterriza un cluster. HotPDF carga sus runtimes HarfBuzz y FriBidi solo cuando un valor realmente requiere shaping, y los valores que no lo requieren conservan el layout comb directo existente sin cambios. Para el flujo general de llenado de un PDF cargado, incluidos permisos y guardado, vea poner valores de campos de formulario en un PDF cargado
¿Qué no garantiza una apariencia comb con shaping?
Una apariencia comb con shaping garantiza asignación correcta de celdas, clusters intactos y un valor lógico preservado; no garantiza uniones cursivas sin costuras ni extracción de texto perfecta en toda herramienta. Ambos límites se derivan de lo que es un campo comb, y debería conocerlos antes de prometerle nada a un cliente
Las uniones cursivas entre celdas son la primera. Las letras árabes se siguen moldeando con el contexto de toda la línea, así que cada una recibe su forma inicial, medial o final correcta, pero el paso fijo de celdas pone espacio entre clusters separados y los trazos conectores no van a encontrarse como lo hacen en texto corrido. Eso es inherente a dibujar una escritura de unión en casillas, y ningún centrado lo esconde
La extracción de texto es la segunda. El /V del campo conserva el valor lógico plano, y el Form XObject padre de la apariencia lleva un tramo /ActualText (ISO 32000-1 §14.9.4) con la entrada completa; los Forms hijos no llevan ninguno. Los extractores que respetan ActualText recuperan la cadena original. Los que la ignoran reconstruyen el texto desde posiciones de glifo y pueden devolver otro orden o texto de cluster duplicado, porque un cluster de varios glifos no tiene un único valor Unicode obvio por glifo. Si un sistema aguas abajo necesita el valor, lea el campo, no el texto de la página
Las fronteras restantes son mecánicas. El texto comb debe ser una sola línea sin tabs, y un valor con más escalares que celdas falla en vez de truncarse. Los fallos son transaccionales: un glifo faltante, una fuente inválida, un exceso de presupuesto o una cancelación dejan el valor anterior y las apariencias intactas, así que un llenado rechazado nunca deja un campo medio dibujado
try
Doc.SetTextFieldValueWithFamilies(FieldIndex, Value, Families, Options);
except
on E: EHPDFHeadlessDocumentError do
if E.Code = hdLimitExceeded then
ShowFieldError('The value has more characters than the field allows')
else
raise;
on E: EHPDFHeadlessAppearanceError do
if E.Failure = haMissingGlyph then
ShowFieldError('None of the supplied fonts covers this text')
else
raise;
end;
// Ante cualquiera de los dos errores el campo conserva su valor y apariencia anteriores
Dónde encajan los campos comb con shaping en un pipeline de formularios
El soporte comb con shaping importa más para formularios que ya existen: declaraciones de impuestos, solicitudes de visa, paquetes de onboarding bancario y registros de identidad diseñados con campos en casillas y ahora llenados en árabe, hebreo, persa o urdu. Cuente en escalares, conserve el valor original en /V, deje que una sola cuadrícula gobierne cada fuente, y pruebe con un valor que mezcle letras de derecha a izquierda, dígitos y una marca combinante, porque esa única cadena ejercita cada regla de este artículo. La API de documentos headless, el shaping comb y el fallback de fuentes descritos aquí son parte del componente PDF HotPDF para Delphi