No HotPDF Component, a THotPDF.Resolution define a unidade de desenho: toda coordenada X e Y, toda margem, o tamanho passado ao SetFont, e os resultados de TextWidth e GetWideTextWidth são medidos em 1/Resolution inch. THPDFPage.Width e Height não a seguem e ficam em points, então os limites de layout precisam vir da UserWidth e da UserHeight somente leitura. O motivo habitual para mexer na Resolution é um port: um motor de relatórios que já pensa em 1/96 ou 1/144 inch é mais fácil de mover quando o lado do PDF fala a mesma unidade do que quando todo call site ganha um fator de conversão. Isso funciona bem, desde que você saiba quais números se mudaram para a unidade nova e quais ficaram para trás
O que a THotPDF.Resolution muda de fato?
A THotPDF.Resolution muda só como o HotPDF lê os números que você passa; o PDF que ele grava é o mesmo. O setter tem duas linhas: SetResolution guarda o valor e define DocScale := Value / 72. De então em diante, XProjection e YProjection dividem toda coordenada pelo DocScale a caminho do content stream, e o SetFont divide o tamanho do mesmo jeito antes de registrá-lo. O user space do PDF tem default de 1/72 inch (ISO 32000-1 §8.3.2.3), então na Resolution default de 72 a projeção é a identidade e a 144 uma unidade de desenho é meio point. Nenhuma entrada de /UserUnit é gravada. Esse atributo de página, acrescentado no PDF 1.6, é uma coisa separada que o HotPDF expõe como THPDFPage.SetUserUnit. Um detalhe que pega quem vem dos tutoriais de TextOut: as coordenadas de página correm do canto superior esquerdo com Y crescendo para baixo, porque o YProjection computa o topo da MediaBox menos o Y escalado, e isso vale em toda Resolution
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1 unidade de desenho = 1/144 inch
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4: Width = 595, UserWidth = 1190
Margin := 144; // um inch em unidades de desenho
Page.SetFont('Arial', [fsBold], 28); // 28/144 inch, uma fonte de 14 pt
Title := 'INVOICE 2026-0417';
// Alinhe à direita contra a borda da página medida na mesma unidade
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // linha de 1 pt
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Por que o Page.Width discorda das minhas coordenadas na Resolution 144?
O THPDFPage.Width e o Height reportam a página em points não importa qual seja a Resolution do documento, enquanto as suas coordenadas estão em 1/Resolution inch, então a 144 a página parece metade da largura que realmente é. Uma página A4 lê Width = 595 e Height = 842 na Resolution 72 e ainda lê 595 e 842 a 144, onde a borda direita de fato está em X = 1190. UserWidth e UserHeight, acrescentadas na v2.766.0, retornam Width * DocScale, que é o tamanho da página na unidade em que você desenha. Antes delas existirem, a biblioteca misturava as duas internamente, e os sintomas na Resolution 144 eram dramáticos: parágrafos quebravam depois de cada caractere, o THPDFTable.Render empurrava cada row para uma página nova, e tanto o importador HTML quanto o flattener de XFA desenhavam o conteúdo deles na metade do tamanho, o form achatado amontoado no canto superior esquerdo. Layout de parágrafo, renderização de tabelas, import HTML, centralização de EMF, o clip de página WMF e diagnósticos de layout agora todos leem o tamanho em user unit. O seu próprio código de layout também deveria: qualquer coisa que compare contra uma coordenada de desenho (uma margem direita, um teste de quebra de página, um cálculo de centralização) pertence à UserWidth e à UserHeight, nunca ao Width e ao Height
Armadilha um: atribuir Width ou Height muda a página para points
Definir Page.Width ou Page.Height muda silenciosamente a página para UserDefined, e uma página UserDefined ignora o DocScale por completo, então tudo que você desenhar nela depois fica em points, não em 1/Resolution inch. O setter é antigo e recebe points de propósito, e é por isso que o significado dele ficou intocado. A projeção para uma página UserDefined é simplesmente X + MinX, e o SetFont guarda o tamanho sem mudança. Na Resolution 144 o resultado é uma página cujo conteúdo de repente sai duas vezes maior que o da página anterior. A biblioteca cometeu exatamente esse erro ela mesma: páginas de continuação de parágrafo costumavam copiar o tamanho da página anterior pelo Width, e toda página de overflow mudava para points. Essas páginas agora copiam Size, Orientation e a Resolution da página em vez disso, e só caem para Width e Height quando a página original já era UserDefined
Duas saídas, dependendo do que você precisa. Se uma folha padrão serve, defina Page.Size e Page.Orientation e continue desenhando na unidade da sua Resolution. Se você realmente precisa de um tamanho de página customizado, aceite que ela é uma página em points e desenhe em points; a UserWidth iguala o Width ali, então código de layout que sempre lê a UserWidth continua funcionando nos dois tipos de página. O teste unitário crava isso: na Resolution 144 uma página A4 reporta uma UserWidth de 1190, mas depois de Width := 500 e Height := 400 ela reporta 500 e 400. Páginas carregadas se comportam do mesmo jeito, porque uma página reconstruída de um PDF existente só conhece a MediaBox dela em points e desenha em points. Páginas que este documento criou mantêm as unidades próprias delas quando você sai e volta pelo CurrentPageNumber, o que é o caso desde a v2.766.26
Armadilha dois: por que tamanhos de fonte saem com metade do tamanho?
Um tamanho de fonte que começou como points sai com metade do tamanho na Resolution 144 porque o SetFont trata o argumento de tamanho dele como unidades de desenho e o converte para points antes de guardar. Internamente, o SetFont guarda ASize / DocScale * DPI no objeto de fonte atual, então o valor guardado é sempre points. A biblioteca tropeçou nisso duas vezes: o fallback de fonte no WideTextOutBoxEx e a página de continuação de parágrafo ambos entregavam esse valor em points guardado de volta ao SetFont, que o escalava uma segunda vez e reduzia o texto à metade. O seu código não consegue ler o tamanho guardado, mas o mesmo bug aparece sempre que um valor em points de outro lugar chega ao SetFont: um TFont.Size de um form VCL, um tamanho numa definição de relatório, um comprimento pt de CSS. Converta primeiro, e inclua a Resolution da própria página e o caso UserDefined no fator, como o playback de metafile faz quando ele repoduz o Canvas da página (veja como o HotPDF importa gráficos vetoriais EMF e WMF para esse caminho):
// Unidades de desenho por point na página atual. Espelha a projeção
// que o HotPDF usa: 1 numa página dimensionada por Width/Height, caso contrário
// (document Resolution / 72) * (page Resolution / 72)
function UnitsPerPoint(Pdf: THotPDF): Single;
begin
if Pdf.CurrentPage.Size = UserDefined then
Result := 1
else
Result := (Pdf.Resolution / 72) * (Pdf.CurrentPage.Resolution / 72);
end;
procedure SetFontFromVcl(Pdf: THotPDF; Font: TFont);
begin
// TFont.Size está em points; SetFont espera unidades de desenho
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
A biblioteca aplica a mesma regra às próprias constantes em points dela. A fonte de 12 points com que toda página nova começa agora é multiplicada pelo fator interno de unidades por point, então ela tem 12 points em qualquer Resolution. O DrawChart, cujas margens, tamanhos de label e larguras de linha são todos points hard-coded, agora roda com a escala temporariamente definida como 1. O que fica em unidades de desenho, de propósito, são defaults de parâmetros públicos como o tamanho de módulo do DrawQRCode e o tamanho de fonte default de tabela: eles fazem parte do contrato da API, então na Resolution 144 significam metade do que significam a 72. Se você dimensiona relatórios a partir de um template, o guia de saída de relatório com fontes e imagens no HotPDF cobre de onde esses valores normalmente vêm
Como você verifica que um layout é independente de Resolution?
A checagem mais confiável é uma comparação de bytes: renderize a mesma página na Resolution 72 e de novo a 144 com toda coordenada e tamanho dobrados, e os content streams descomprimidos precisam ser idênticos. As duas execuções caem nos mesmos valores em points depois da projeção, então qualquer diferença é um valor que pulou a conversão. É assim que a suíte de testes do HotPDF confere parágrafos, tabelas, import HTML, achatamento de XFA, arcos, metafiles e imagens. A mesma técnica funciona para o seu próprio código de relatório com quase nenhum harness:
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // content streams legíveis
Pdf.FileName := FileName;
Pdf.Resolution := Res;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 10 * K);
Pdf.CurrentPage.TextOut(36 * K, 36 * K, 0, 'Line 1');
Pdf.CurrentPage.Rectangle(36 * K, 60 * K, 200 * K, 40 * K);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
// RenderPage('r72.pdf', 72, 1) e RenderPage('r144.pdf', 144, 2)
// precisam produzir content streams de página idênticos byte a byte
Confira os operadores que carregam números: Td, Tm, Tf, re, w e os arrays de TJ. Bytes a nível de arquivo ainda vão divergir na data de criação e no /ID, então compare os streams, não arquivos inteiros. Uma divergência quase sempre aponta para uma das duas armadilhas acima: uma página redimensionada pelo Width, ou um valor em points passado direto ao SetFont. Se você é novo nas chamadas de desenho em si, comece pelo passo a passo de TextOut do HotPDF para tamanho, estilo e rotação, depois volte e mude a Resolution quando o seu layout ler UserWidth. Detalhes completos de API e downloads de teste estão na página do componente HotPDF Delphi PDF