Una primera línea escrita desde el punto de vista de la especificación aterriza en y = 770. El razonamiento suena sólido sobre el papel: ISO 32000-1 pone el origen del user space en la esquina inferior izquierda, una página A4 mide 842 puntos de alto, así que 770 queda una pulgada por debajo del borde superior. Corra ese programa contra un componente que mide desde arriba y el saludo aterriza como a una pulgada por encima del borde inferior, con el resto de la página vacío por encima. Agregue una segunda página, y la fuente elegida con tanto cuidado vuelve a ser Arial de 12 puntos. Las dos sorpresas vienen de las mismas dos decisiones que todo programa PDF toma en su primera página: qué fuente está activa, y desde qué esquina se cuentan los números
El programa HotPDF más chico en Delphi
El Hello World de HotPDF es un programa de consola con cuatro llamadas en un orden fijo: asignar propiedades del documento, BeginDoc, dibujar con SetFont y TextOut, y luego EndDoc. Todo lo demás es Delphi corriente. Aquí está, escribiendo una página que saluda al lector en once idiomas:
program HelloWorld;
{$APPTYPE CONSOLE}
uses
Winapi.Windows, // DEFAULT_CHARSET; en Delphi anterior a XE2 use Windows a secas
HPDFDoc; // unidad principal de HotPDF
procedure CreateHelloWorld(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
// Configuración del documento completo: todo esto va antes de BeginDoc
Pdf.FileName := FileName;
Pdf.AutoLaunch := False; // el default es True: abre el visor después de EndDoc
Pdf.Compression := cmFlateDecode; // el default es cmNone
Pdf.FontEmbedding := True; // ya es el default, se declara para el lector
Pdf.EnableFontSubsetting := True; // embebe solo los glifos realmente dibujados
Pdf.BeginDoc; // crea la página 1 (A4)
// El 4to argumento tiene default ANSI_CHARSET; pase DEFAULT_CHARSET explícitamente
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
// X, Y en puntos desde la esquina SUPERIOR IZQUIERDA; Y es el borde superior de la línea
Pdf.CurrentPage.TextOut(72, 72, 0, 'Hello, Delphi PDF world!'); // Inglés
Pdf.CurrentPage.TextOut(72, 102, 0, 'Hola, mundo Delphi PDF!'); // Español
Pdf.CurrentPage.TextOut(72, 132, 0, 'Hallo, Delphi PDF Welt!'); // Alemán
Pdf.CurrentPage.TextOut(72, 162, 0, 'Bonjour, monde PDF Delphi!'); // Francés
Pdf.CurrentPage.TextOut(72, 192, 0, 'Ciao, mondo Delphi PDF!'); // Italiano
Pdf.CurrentPage.TextOut(72, 222, 0, 'Olá, mundo Delphi PDF!'); // Portugués
Pdf.CurrentPage.TextOut(72, 252, 0, 'Здравствуйте, Delphi PDF мир!'); // Russian
Pdf.CurrentPage.TextOut(72, 282, 0, 'こんにちは、Delphi PDFの世界!'); // Japanese
Pdf.CurrentPage.TextOut(72, 312, 0, 'Merhaba, Delphi PDF dünyası!'); // Turkish
Pdf.CurrentPage.TextOut(72, 342, 0, '你好,Delphi PDF世界'); // Chinese
Pdf.CurrentPage.TextOut(72, 372, 0, '여보세요, Delphi PDF 세계!'); // Korean
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
begin
CreateHelloWorld('HelloWorld.pdf');
Writeln('Wrote HelloWorld.pdf');
end.

THotPDF.Create(nil) junto con try/finally ... Free es el patrón de ownership usual de Delphi, y producir un PDF no lo cambia. Las líneas avanzan hacia abajo desde Y = 72, a 30 puntos de distancia, lo que deja un interlineado cómodo para texto de 14 puntos. Córralo y HelloWorld.pdf aparece en el directorio de trabajo: una página, contenido comprimido, la fuente embebida como subset
Las propiedades del documento van antes de BeginDoc
BeginDoc arranca el documento y crea su primera página, así que cualquier cosa que moldee el archivo completo tiene que asignarse antes. HotPDF lo hace valer para algunas propiedades en vez de dejarlo a la convención. Asigne Compression después de BeginDoc y el setter levanta una excepción con el mensaje Cannot set Compression value - document in progress.; AutoLaunch está custodiado igual. Que sea ruidoso es bueno aquí. Ponga cada ajuste del documento en un solo bloque arriba de BeginDoc y nunca verá ese mensaje
Dos de los defaults merecen una segunda mirada. Compression arranca en cmNone, así que un programa que jamás lo toca escribe content streams sin comprimir; para una página de texto es apenas un desperdicio, para un reporte de cientos de páginas es lo primero que cualquiera nota del tamaño del archivo. AutoLaunch arranca en True, lo que significa que una herramienta de consola abre el PDF terminado en el visor asociado en cuanto EndDoc devuelve. Eso es agradable en su escritorio y está mal dentro de un servicio, un job programado o un build agent. Para esos ambientes, asígnelo a False en código; para corridas de CI donde no puede cambiar el código, la variable de ambiente HOTPDF_SUPPRESS_AUTO_LAUNCH puesta en 1 también apaga el launch
El manejo de fuentes es donde tomamos partido. FontEmbedding es True por defecto, así que la fuente viaja con el archivo y renderiza en una máquina que nunca tuvo instalado Arial Unicode MS. EnableFontSubsetting, en cambio, está apagado por defecto para mantener las fuentes completas disponibles para flujos de edición. Para un saludo, una factura o un reporte generado, active el subsetting. Arial Unicode MS cubre una gama enorme de escrituras, y embeber la fuente completa para mostrar once líneas cortas haría el archivo megabytes más grande sin beneficio visible. Déjelo apagado solo cuando alguien río abajo vaya a editar el texto y necesite glifos que usted no dibujó
¿Dónde está el (0, 0) en una página de HotPDF?
HotPDF mide las coordenadas de página desde la esquina superior izquierda con Y creciendo hacia abajo, el mismo marco que usa un TCanvas de VCL. En el Resolution por defecto de 72, una unidad es un punto, 1/72 de pulgada. El archivo PDF en sí sigue guardando coordenadas bottom-up, como exige ISO 32000-1 §8.3.2.3; HotPDF convierte cada Y que usted pasa restándolo de la altura de página cuando escribe el content stream
La aritmética detrás del ejemplo de apertura es corta. La página por defecto que BeginDoc y AddPage crean es A4, 595 por 842 puntos. Una línea pensada para quedar a una pulgada del tope es Y = 72 en términos de HotPDF y y = 770 en términos de PDF crudo, porque 842 − 72 = 770. Pase 770 a TextOut y el texto va 770 puntos hacia abajo desde el tope, o sea 72 puntos por encima del borde inferior
Hay una segunda trampa, más chica, en la misma llamada. Por defecto la Y que usted pasa es el borde superior del texto, y HotPDF baja por la altura del texto antes de colocar la baseline. Código portado desde GDI suele esperar exactamente eso. Código portado desde otra librería PDF, o desde su propio escritor de content streams, suele esperar que Y sea la baseline, y cada línea sale una altura de texto más abajo de lo planeado. THPDFPage.TextFromBaseLine(True) cambia la página actual a posicionamiento por baseline:
// Puente para coordenadas que vienen de una fuente bottom-up (un layout PDF,
// un diagrama de spec, otra librería). Helper escrito para este artículo,
// no forma parte de HotPDF. Válido en el Resolution por defecto de 72
function FromBottomUp(Page: THPDFPage; PdfY: Single): Single;
begin
Result := Page.Height - PdfY;
end;
procedure DrawFromPdfCoordinates(Pdf: THotPDF);
begin
Pdf.CurrentPage.SetFont('Arial', [], 12);
// Y ahora nombra la baseline, como colocan glifos los operadores de texto del propio PDF
Pdf.CurrentPage.TextFromBaseLine(True);
// Baseline 770 puntos por encima del borde inferior, o sea Y = 72 desde el tope
Pdf.CurrentPage.TextOut(72, FromBottomUp(Pdf.CurrentPage, 770), 0,
'Baseline one inch below the top edge');
end;
Resolution es la otra configuración que cambia lo que significan los números. Ponga THotPDF.Resolution en 96 y las coordenadas X/Y, los márgenes y los resultados de TextWidth se expresan en 1/96 de pulgada, útil cuando el layout se diseñó en pixels de pantalla. El Width y el Height de página siguen reportando puntos, así que el código de layout que mezcle límites de página con coordenadas de dibujo debería leer CurrentPage.UserWidth y CurrentPage.UserHeight, que vuelven en las mismas unidades que X e Y. En 72 los dos pares coinciden. Si prefiere pensar en milímetros, la propiedad MeasurementUnit (muPoints por defecto, con muMM, muInches, muCentimeters, muTwips y muPixels disponibles) es el switch más limpio. Elija una convención por programa. Nunca las apile
¿Por qué la página dos se imprime en Arial de 12 puntos?
Porque cada página nueva arranca de un estado conocido. THotPDF.AddPage hace actual la página nueva, la dimensiona A4 y le llama SetFont('Arial', [], 12), manteniendo el tamaño en 12 puntos sin importar el Resolution. BeginDoc crea la página uno por el mismo camino, razón por la cual el Hello World asigna su fuente justo después de BeginDoc y no antes. La consecuencia para salida multipágina es fácil de perder: el SetFont que usted llamó en la página uno no se traslada. Las líneas latinas en la página dos se ven bien en Arial, solo que más chicas. Las líneas CJK son el delator, porque Arial no tiene glifos para ellas y el visor muestra cajas vacías o nada
Una fuente inicial predecible es el diseño correcto para un objeto de página: el código de dibujo de la página cinco no depende silenciosamente de lo que la página cuatro dejó tirado. El costo es que la configuración de página hay que repetirla, y la forma más limpia de repetirla es un solo procedimiento por el que pasa cada página, incluida la primera:
uses
Winapi.Windows, Vcl.Graphics, HPDFDoc;
// Su propio helper: un solo lugar que sabe lo que una página nueva necesita
procedure PreparePage(Pdf: THotPDF; IsFirst: Boolean);
begin
if not IsFirst then
Pdf.AddPage; // resetea la fuente a Arial 12
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
end;
procedure WriteGreetings(Pdf: THotPDF; Lines: array of string);
const
TopMargin = 72;
Leading = 30;
LinesPerPage = 20;
var
I, Row: Integer;
begin
PreparePage(Pdf, True); // la página 1 ya existe después de BeginDoc
Row := 0;
for I := 0 to High(Lines) do
begin
if Row = LinesPerPage then
begin
PreparePage(Pdf, False);
Row := 0;
end;
Pdf.CurrentPage.TextOut(72, TopMargin + Row * Leading, 0, Lines[I]);
Inc(Row);
end;
// Un encabezado en negrita necesita su propio SetFont, y después vuelve la fuente del cuerpo
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial Unicode MS', [fsBold], 18, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 72, 0, 'Appendix');
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 110, 0, 'Body text after the heading');
end;
Si sus páginas también cambian a posicionamiento por baseline con TextFromBaseLine, ponga esa llamada en PreparePage también, junto a la fuente. Mantener cada configuración por página en un solo procedimiento significa que un requisito nuevo, un color o un tamaño distinto se agregan una vez y llegan a todas las páginas
Una fuente para muchas escrituras, y dónde se queda corta
Un solo SetFont puede llevar latino, cirílico, japonés y chino en una página porque Arial Unicode MS junta glifos para una gama muy amplia de escrituras en una sola fuente. El cuarto argumento de SetFont es el character set de Windows, y su default es ANSI_CHARSET, que ata la selección de fuente a la code page occidental. Pasar DEFAULT_CHARSET (valor 1) deja que Windows elija la fuente por nombre, que es lo que una página multilingüe quiere
La fuente tiene que existir en la máquina que construye el PDF. HotPDF embebe la fuente a la que Windows resuelva el nombre, y cuando falta Arial Unicode MS, Windows sustituye otra cara sin quejarse. La salida entonces embebe la sustituta, y las líneas en escrituras que esa cara no cubre salen como cajas vacías. Un build server es el lugar donde esto suele aparecer, porque nadie instaló ahí las fuentes de Office. Revise la lista de fuentes embebidas del primer archivo que produzca una máquina nueva antes de confiarle una corrida por lotes
Cuando ninguna cara cubre todos los strings, TextOutWithFallback toma los mismos argumentos que TextOut, resuelve una fuente para cada carácter, dibuja cada tanda con la cara que puede renderizarla y restaura su fuente original después. Las escrituras de derecha a izquierda como árabe y hebreo necesitan más que cobertura de glifos: necesitan shaping y orden de tanda invertido, que es el territorio de la función RtLTextOut, normalmente emparejada con un archivo TrueType cargado vía RegisterUnicodeTTF
De aquí los siguientes pasos naturales son el tamaño, el estilo y la rotación del texto, y colocar varios bloques a propósito en una página, que es lo que trabaja la muestra de TextOut. Reglas, cajas e imágenes usan el mismo objeto CurrentPage y las mismas coordenadas superiores izquierdas, así que todo lo de arriba sobre orígenes y configuración por página aplica a ellas sin cambios
La página del HotPDF Delphi Component cubre el resto de la API de dibujo para Delphi y C++Builder, desde shapes e imágenes hasta formularios, una vez que este Hello World corre limpio contra sus propias fuentes