Artigo Técnico

PDFlibPas: Hifenização e Colunas Equilibradas no Delphi

O PDFlibPas distribui uma passagem de texto retido por uma a 64 colunas de largura igual com DrawTextFlowColumns, e quebra as suas linhas com hifenização limitada e sensível ao idioma assim que se chama SetTextFlowLanguage e SetTextFlowHyphenation. São suportados nove idiomas, e o idioma pode ser herdado do valor /Lang do Catalog do documento em vez de ser definido por cada fluxo

Ambas as funcionalidades existem pela mesma razão: uma coluna estreita é o ponto em que a quebra de linha ingénua deixa de parecer composição tipográfica e começa a parecer um relatório de erro

Porque se desfaz o texto justificado em colunas estreitas?

Porque a justificação distribui o espaço sobrante pelos espaços entre palavras de uma linha, e a quantidade sobrante depende do que cabe. Numa medida larga, o sobrante é pequeno e o olho nunca dá por isso. Reduza a largura a metade e uma única palavra longa que não caiba é empurrada para a linha seguinte, deixando as anteriores a absorver todo esse espaço. Três linhas seguidas assim produzem os canais brancos verticais a que os tipógrafos chamam rios, e que os leitores experimentam como texto difícil de seguir sem saberem porquê

A hifenização corrige a causa e não o sintoma, ao permitir uma quebra dentro da palavra. Os compostos do alemão e do neerlandês tornam isto inegociável: um substantivo de 24 caracteres numa coluna de 60 milímetros não tem bom resultado sem um ponto de quebra. O inglês tolera melhor a ausência, motivo pelo qual produtos pensados primeiro para o inglês tantas vezes distribuem código de esquema que falha na primeira vez que um cliente alemão o utiliza

Que idiomas, e de onde vem o idioma?

A hifenização abrange inglês, alemão, neerlandês, francês, espanhol, italiano, português, russo e turco. Defina-a explicitamente por fluxo com SetTextFlowLanguage, ou deixe-a ser herdada da entrada /Lang do Catalog do documento, o valor que um documento etiquetado e acessível já transporta

Vale a pena usar essa herança em vez de a substituir. Um documento que declara o seu idioma no Catalog está a comunicar o mesmo facto, a partir de um único local, a leitores de ecrã, indexadores de pesquisa e à hifenização, e um único local é onde um facto deve residir. Se já produz saída etiquetada conforme descrito em etiquetagem automática para PDFs acessíveis, a entrada de idioma já está definida e o fluxo pode simplesmente segui-la

var
  Lib: TPDFlib;
  Flow, Drawn: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('Georgia', 1);
    Lib.SetTextSize(10.5);

    Flow := Lib.NewTextFlow(ArticleBody);
    try
      Lib.SetTextFlowLanguage(Flow, 'de');
      // Ativar, pelo menos 3 caracteres antes da quebra, 3 depois
      Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
      Lib.SetTextFlowMinLines(Flow, 2);   // nunca deixar uma única linha isolada

      repeat
        // Três colunas numa região de 480 pt, calhas de 18 pt, equilibradas
        Drawn := Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);
        if (Drawn = 0) or (Lib.TextFlowFinished(Flow) = 1) then
          Break;
        Lib.NewPage;
      until False;
    finally
      Lib.ReleaseTextFlow(Flow);
    end;

    Lib.SaveToFile('newsletter.pdf');
  finally
    Lib.Free;
  end;
end;

MinPrefix e MinSuffix são tipografia, não validação

Os dois inteiros a seguir à flag de ativação definem o número mínimo de caracteres que têm de permanecer antes e depois de uma quebra. Três e três é uma predefinição conservadora que a maioria dos manuais de estilo aceita. Dois e dois produz mais oportunidades de quebra e resultados visivelmente mais feios, porque um fragmento de duas letras a pender no fim de uma linha lê-se como um erro tipográfico

Aumente os mínimos quando o corpo de letra é grande, em que cada fragmento se torna visualmente proeminente, e reduza-os apenas quando a coluna é genuinamente estreita e decidiu que uma medida apertada importa mais do que uma limpa. Esta é uma decisão de estilo editorial e não uma decisão técnica, o que explica exatamente por que motivo é um parâmetro em vez de uma constante

O que significa "equilibrado" neste contexto?

O parâmetro Balance só altera o comportamento no final de uma passagem. Com o equilíbrio ativo, as colunas são encurtadas para um número exato e igual de linhas quando o restante cabe todo dentro da região, o que evita que uma página final mostre duas colunas completas e uma terceira com uma única linha solitária. Quando a passagem não cabe, todas as colunas mantêm a sua altura total, para que a página transporte o máximo de texto possível, continuando o restante na página seguinte

Essa assimetria é a predefinição correta para documentos contínuos. Equilibrar a meio de um artigo em fluxo desperdiçaria espaço vertical em cada página por um efeito cosmético que ninguém vê, uma vez que as colunas já estão cheias. Equilibrar no final é onde o olho realmente repara, e é exatamente aí que a funcionalidade se aplica

A quebra de linha mede palavras inteiras

O algoritmo de quebra mede palavras completas em vez de acumular larguras de caracteres, e reserva uma pesquisa limitada para tokens de dimensão excessiva que não cabem de todo numa linha, como um URL ou um número de conta. Isso mantém o caso comum rápido e o caso patológico limitado, em vez do contrário

Os hífenes opcionais e os hífenes automáticos só são desenhados quando a quebra que assinalam é efetivamente a quebra escolhida. Isto parece óbvio e é um defeito clássico: uma implementação ingénua escreve o carácter de hífen durante a medição e, se a quebra mudar de posição, o hífen fica esquecido no meio de uma linha. Nada denuncia mais um motor de texto avariado do que um hífen perdido dentro de uma palavra

var
  Lib: TPDFlib;
  Flow, Needed: Integer;
begin
  // Decidir o esquema antes de desenhar seja o que for
  Flow := Lib.NewTextFlow(ArticleBody);
  try
    Lib.SetTextFlowLanguage(Flow, 'fr');
    Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);

    // Linhas de que o resto da passagem precisa numa largura de coluna
    Needed := Lib.MeasureTextFlow(Flow, 148);
    if Needed > 3 * LinesPerColumn then
      UseTwoPageSpread
    else
      UseSinglePage;

    Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);

    if Lib.TextFlowFinished(Flow) <> 1 then
      CarryOver(Lib.GetTextFlowRemaining(Flow));
  finally
    Lib.ReleaseTextFlow(Flow);
  end;
end;

Mantenha as definições de tipo de letra idênticas entre caixas

Uma regra rege todos os esquemas baseados em fluxo, e vale a pena afirmá-la sem rodeios: DrawTextFlow, DrawTextFlowColumns e MeasureTextFlow quebram todas as linhas usando o tipo de letra selecionado no momento em que são chamadas. Se mudar o tipo de letra ou o tamanho entre duas caixas do mesmo fluxo, ou iniciar uma nova página sem voltar a selecionar um, a segunda caixa quebra de forma diferente daquela que a primeira mediu

O sintoma é irritante precisamente porque parece intermitente: texto que cabe na página um transborda na página dois, ou uma contagem de linhas medida discorda do que foi efetivamente desenhado. Selecione o tipo de letra uma vez antes do ciclo, volte a selecioná-lo após cada NewPage, e o fluxo comporta-se corretamente. Quando surgem sistemas de escrita mistos na mesma passagem, a resolução descrita em substituição automática de tipo de letra para texto CJK e emoji aplica-se tanto à medição como ao desenho, pelo que as larguras permanecem consistentes também nos segmentos de substituição

Para esquemas de relatório em que o fluxo é um elemento entre cabeçalhos, rodapés e blocos gerados por dados, os padrões de composição em o motor de relatórios de conjuntos de dados combinam-se bem com fluxos de colunas: meça primeiro, coloque os elementos fixos e depois entregue ao fluxo a região que sobrar

O PDFlibPas é uma biblioteca PDF para Delphi, C++Builder e Lazarus, e todo o ciclo de vida do TextFlow, criação, desenho, medição, inspeção, retrocesso e libertação, é igualmente exposto através das interfaces DLL e ActiveX. A documentação completa encontra-se na página da biblioteca PDF PDFlibPas para Delphi