O HotPDF desenha elementos vetoriais construindo um caminho na página atual e, em seguida, solicitando a sua renderização. Não existe nenhuma etapa intermédia com imagens bitmap. Uma linha desenhada com MoveTo e LineTo traduz-se em operadores de caminhos PDF no fluxo de conteúdo, mantendo-se como um vetor real: nítido a 50% de zoom, nítido a 1600% e com uma fração do tamanho que uma versão rasterizada ocuparia. Para diagramas, linhas de tabelas, eixos de gráficos e elementos decorativos de formulários, isto é exatamente o que se pretende, e a API necessária é simples o suficiente para ser assimilada de uma só vez.
Toda a área de desenho reside em THotPDF.CurrentPage. Entre BeginDoc e EndDoc, define a cor e a espessura da linha nesse objeto de página, cria a geometria e chama um operador de desenho para a aplicar. As quatro primitivas mais utilizadas são MoveTo e LineTo para caminhos livres, Rectangle para caixas, Circle para círculos e os operadores de pintura Stroke (contorno) e Fill (preenchimento).
O sistema de coordenadas tem origem no canto inferior esquerdo
Este aspeto costuma surpreender quem está habituado à VCL. O TCanvas utilizado para desenhar controlos coloca a origem no canto superior esquerdo, com o Y a crescer para baixo. O PDF faz o oposto. O HotPDF mede a partir do canto inferior esquerdo da página em pontos (1/72 de polegada), com o Y a aumentar à medida que se sobe. Um ponto em Y := 720 situa-se perto do topo de uma página US Letter (que tem 792 pontos de altura) e Y := 50 situa-se perto do fundo. Se o seu primeiro desenho aparecer invertido verticalmente, o motivo é este: código portado de interfaces gráficas de ecrã assume a direção incorreta e acaba por desenhar fora do limite inferior.
Esta mesma convenção aplica-se ao TextOut, pelo que texto e formas partilham o mesmo modelo mental depois de assimilado. Planeie o seu layout determinando onde fica a base de cada elemento, e não o topo, e o resto do trabalho será direto.
Caminhos: MoveTo, LineTo e Stroke
Um caminho com contorno funciona como uma caneta que é levantada, posicionada e arrastada. O método MoveTo levanta a caneta e define o ponto de partida sem desenhar nada. Cada chamada a LineTo prolonga o caminho atual até um novo ponto. Nada é apresentado na página até chamar o Stroke, que desenha o caminho acumulado com a cor de contorno e a espessura de linha atuais, limpando em seguida o caminho para que o próximo MoveTo comece de novo.
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'DrawPaths.pdf';
Pdf.BeginDoc;
// Line width is in points and applies until you change it.
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
// A horizontal rule near the top of the page (Y measured from bottom).
Pdf.CurrentPage.MoveTo(72, 720);
Pdf.CurrentPage.LineTo(523, 720);
Pdf.CurrentPage.Stroke; // commit the path; nothing drew before this
// A thicker connected polyline: three segments in one path.
Pdf.CurrentPage.SetLineWidth(3);
Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
Pdf.CurrentPage.MoveTo(72, 640);
Pdf.CurrentPage.LineTo(172, 690);
Pdf.CurrentPage.LineTo(272, 620);
Pdf.CurrentPage.LineTo(372, 680);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Dois detalhes evitam perda de tempo na depuração. A espessura da linha é um estado do componente e não um argumento: o método SetLineWidth define-a uma vez e todos os métodos Stroke seguintes utilizam esse valor até que o altere novamente, sendo esta a razão pela qual a polilinha do exemplo é mais espessa do que a primeira linha. Adicionalmente, o caminho é limpo após cada Stroke, pelo que um Stroke esquecido fará com que a geometria desenhada não seja renderizada. Se faltar alguma forma no resultado obtido, a chamada de pintura é o primeiro local a verificar.
As coordenadas são expressas em pontos, e os pontos suportam frações. Os métodos MoveTo e LineTo aceitam valores do tipo Single, pelo que uma linha muito fina com 0.5 pontos ou uma posição em 72.25 são válidas e úteis, não sendo arredondadas para o valor inteiro mais próximo. Esta precisão é importante em dois aspetos. Uma espessura de linha inferior a 0.5 pode resultar numa linha de dimensão mínima dependente do dispositivo que desaparece no ecrã e surge apenas na impressão; como tal, uma linha visível deve ter uma espessura definida intencionalmente. Por outro lado, alinhar as grelhas e limites de tabelas a coordenadas com pontos inteiros evita que uma malha densa de linhas pareça irregular devido a arredondamentos diferentes de linhas adjacentes. Estabeleça o espaçamento da grelha em pontos logo de início e todo o layout beneficiará com isso.
Formas preenchidas e cores
As primitivas fechadas podem ser preenchidas em vez de contornadas. O método Rectangle recebe uma posição e um tamanho, o Circle recebe um centro e um raio, e ambos são consolidados com Fill, que pinta o interior com a cor de preenchimento atual, ou com Stroke para desenhar apenas o contorno. A cor de preenchimento e a cor de contorno são estados independentes, definidos respetivamente por SetRGBFillColor and SetRGBStrokeColor, aceitando ambos um valor do tipo TColor. Isto significa que pode reutilizar diretamente as constantes de cores do Delphi e a função RGB.
// Rectangle(X, Y, Width, Height): X and Y are the lower-left corner.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;
// Circle(X, Y, Radius): X and Y are the center.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;
// Outline only: set a stroke color and a width, then Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;
Tenha em atenção a ordem dos argumentos no método Rectangle. Este recebe posição e tamanho (X, Y, Width, Height) e não os cantos opostos. O método TCanvas.Rectangle comum no Delphi recebe (Left, Top, Right, Bottom), pelo que a memória muscular pode levá-lo a passar ao HotPDF as coordenadas do segundo canto em vez da largura e altura, resultando numa caixa de tamanho incorreto. O par (X, Y) corresponde ao canto inferior esquerdo, alinhado com a origem da página. No caso do círculo, (X, Y) define o centro e o terceiro argumento corresponde ao raio em pontos.
Uma escolha de cor incorreta no exemplo original
Uma versão antiga deste exemplo definia as cores das formas com Random($FFFFFF). Embora pareça apelativo, representa uma má prática no desenvolvimento de documentos gerados por código. Um PDF gerado programaticamente necessita de ser testado, e o uso de cores aleatórias impossibilita a comparação de resultados entre execuções: uma comparação de bytes contra um ficheiro de referência falhará sempre sem motivo válido. Defina cores explícitas. Se necessitar de variedade num conjunto de formas, baseie-a nos dados ou num array de paleta fixo, garantindo que a mesma entrada de dados produza sempre o mesmo ficheiro. A consistência é muito mais valiosa do que a novidade quando o ficheiro integra um fluxo de integração e entrega contínua.
Onde o desenho vetorial é vantajoso, e onde não o é
Utilize as chamadas de caminhos e formas quando a geometria for gerada dinamicamente: eixos de gráficos, linhas de grelha, limites de tabelas em faturas, caixas de anotação em diagramas ou logótipos descritos por um pequeno conjunto de caminhos. Tudo isto é escalado sem perder definição e quase não afeta o tamanho do ficheiro, porque um retângulo representa apenas alguns números em vez de milhares de píxeis. Por outro lado, se pretender incluir uma fotografia ou uma captura de ecrã, desenhe-a como uma imagem através dos métodos AddImage e ShowImage; desenhar os contornos de um bitmap com funções vetoriais não traz qualquer benefício. Curvas complexas também se encontram fora deste âmbito. As primitivas apresentadas cobrem segmentos retos, retângulos e círculos, que respondem à maioria das necessidades de relatórios de dados; a criação de curvas de Bezier complexas requer outras chamadas específicas da API.
Outra boa prática consiste em realizar verificações. Geometrias geradas no seu ambiente de desenvolvimento podem falhar no cliente, geralmente devido a substituições de fontes no texto complementar ou a premissas de tamanho de página incorretas. Abra o ficheiro gerado com diferentes níveis de zoom para assegurar que as margens se mantêm nítidas e confirme que todas as formas se posicionam na área útil prevista. Com um esquema de cores previsível, este teste pode ser automatizado por comparação contra um PDF de referência em vez de ser feito visualmente.
As chamadas de MoveTo, LineTo, Stroke, Fill e de cores aqui apresentadas fazem parte do Componente HotPDF para Delphi e C++Builder.