Teknisk artikkel

PDFlibPas: orddeling og balanserte tekstspalter i Delphi

PDFlibPas flyter en beholdt tekstpassasje gjennom én til 64 like brede spalter med DrawTextFlowColumns, og bryter linjene med begrenset, språkbevisst orddeling når du kaller SetTextFlowLanguage og SetTextFlowHyphenation. Ni språk støttes, og språket kan arves fra dokumentkatalogens /Lang-verdi i stedet for å settes per flyt

Begge funksjonene finnes av samme grunn: en smal spalte er der naiv linjebrytning slutter å se ut som satt tekst og begynner å se ut som en feilrapport

Hvorfor faller blokkjustert tekst fra hverandre i smale spalter?

Fordi blokkjustering fordeler overskytende plass i ordmellomrommene på en linje, og mengden som er til overs, avhenger av hva som får plass. I et bredt spaltemål er overskuddet lite og øyet legger aldri merke til det. Halver bredden, og et enkelt langt ord som ikke får plass, presses til neste linje, slik at ordene foran det må absorbere all den plassen. Tre slike linjer etter hverandre skaper de vertikale hvite kanalene typografer kaller elver, og leserne opplever teksten som vanskelig å følge uten å vite hvorfor

Orddeling retter årsaken i stedet for symptomet ved å tillate et brudd inne i ordet. Tyske og nederlandske sammensatte ord gjør dette ikke-forhandlingsbart: et substantiv på 24 tegn i en spalte på 60 millimeter har ingen god løsning uten et bruddpunkt. Engelsk tåler fraværet bedre, som er grunnen til at engelskspråklige produkter ofte leverer layoutkode som kollapser første gang en tysk kunde kjører den

Hvilke språk, og hvor kommer språket fra?

Orddeling dekker engelsk, tysk, nederlandsk, fransk, spansk, italiensk, portugisisk, russisk og tyrkisk. Sett det eksplisitt per flyt med SetTextFlowLanguage, eller la det arves fra dokumentkatalogens /Lang-oppføring, som er verdien et tagget og tilgjengelig dokument allerede bærer

Den arven er verdt å bruke i stedet for å overstyre. Et dokument som erklærer språket sitt i katalogen, forteller skjermlesere, søkeindekserere og orddeling det samme faktumet fra ett sted, og ett sted er der et faktum bør bo. Hvis du allerede produserer tagget utdata som beskrevet i automatisk tagging for tilgjengelige PDF-er, er språkoppføringen allerede satt, og flyten kan ganske enkelt følge den

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');
      // Aktiver, minst 3 tegn før bruddet, 3 etter
      Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
      Lib.SetTextFlowMinLines(Flow, 2);   // la aldri en enkelt linje bli strandet

      repeat
        // Tre spalter over et 480 pt-område, 18 pt riller, balansert
        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 og MinSuffix handler om typografi, ikke validering

De to heltallene etter aktiveringsflagget setter minste antall tegn som må stå igjen før og etter et brudd. Tre og tre er en konservativ standard som de fleste husstiler godtar. To og to gir flere bruddmuligheter og merkbart styggere resultater, fordi et tobokstavsfragment som henger igjen på slutten av en linje, leses som en skrivefeil

Hev minimumsverdiene når skriftstørrelsen er stor og hvert fragment er visuelt fremtredende, og senk dem bare når spalten virkelig er smal og du har bestemt at et tett mål betyr mer enn et rent. Dette er en husstilbeslutning snarere enn en teknisk en, som er nøyaktig grunnen til at det er en parameter og ikke en konstant

Hva betyr egentlig «balansert» her?

Balance-parameteren endrer atferd bare på slutten av en passasje. Med balansering på blir spaltene forkortet til nøyaktig likt antall linjer når alt som gjenstår, får plass innenfor området, og det er dette som hindrer en siste side i å vise to fulle spalter og en tredje med bare én ensom linje. Når passasjen ikke får plass, beholder hver spalte sin fulle høyde, slik at siden bærer så mye tekst den kan, og resten fortsetter på neste side

Denne asymmetrien er den riktige standarden for løpende dokumenter. Å balansere midt i en flytende artikkel ville sløst vertikal plass på hver side for en kosmetisk effekt ingen ser, siden spaltene uansett er fulle. Balansering på slutten er der øyet faktisk legger merke til det, og det er nøyaktig der det gjelder

Linjebrytningen måler hele ord

Bruddalgoritmen måler hele ord i stedet for å akkumulere tegnbredder, og reserverer et begrenset søk for altfor store tokener som ikke får plass på en linje i det hele tatt, som en URL eller et aksesjonsnummer. Det holder det vanlige tilfellet raskt og det patologiske tilfellet begrenset, i stedet for omvendt

Diskresjonære myke bindestreker og automatiske bindestreker rendres bare når bruddet de markerer, er bruddet som faktisk velges. Dette høres opplagt ut og er en klassisk feil: en naiv implementasjon skriver bindestrektegnet under målingen, og hvis bruddet flyttes, blir bindestreken liggende igjen midt i en linje. Ingenting ser mer ut som en ødelagt tekstmotor enn en villfaren bindestrek midt inne i et ord

var
  Lib: TPDFlib;
  Flow, Needed: Integer;
begin
  // Bestem layouten før noe tegnes
  Flow := Lib.NewTextFlow(ArticleBody);
  try
    Lib.SetTextFlowLanguage(Flow, 'fr');
    Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);

    // Linjer resten av passasjen trenger ved én spaltebredde
    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;

Hold skriftinnstillingene identiske på tvers av bokser

Én regel styrer all flytbasert layout, og den er verdt å si rett ut: DrawTextFlow, DrawTextFlowColumns og MeasureTextFlow bryter alle linjer med skriften som er valgt i det øyeblikket de kalles. Endre skrift eller størrelse mellom to bokser i samme flyt, eller start en ny side uten å velge på nytt, og den andre boksen brytes annerledes enn det den første målte

Symptomet er provoserende nettopp fordi det ser periodisk ut: tekst som får plass på side én, flyter over på side to, eller et målt linjeantall stemmer ikke med det som ble tegnet. Velg skriften én gang før løkken, velg den på nytt etter hver NewPage, og flyten oppfører seg. Når blandede skriftsystemer opptrer i samme passasje, gjelder løsningen beskrevet i automatisk skriftfallback for CJK og emoji-tekst for både måling og tegning, så breddene forblir konsistente også på tvers av fallback-sekvensene

For rapportlayouter der flyten er ett element blant topptekster, bunntekster og datadrevne blokker, kombineres komposisjonsmønstrene i datasettrapportmotoren rent med spalteflyter: mål først, plasser de faste elementene, og gi deretter flyten det området som er igjen

PDFlibPas er et PDF-bibliotek for Delphi, C++Builder og Lazarus, og hele TextFlow-livssyklusen, opprettelse, tegning, måling, inspeksjon, tilbakespoling og frigjøring, er også eksponert gjennom DLL- og ActiveX-grensesnittene. Full dokumentasjon finnes på PDFlibPas sin side for Delphi PDF-bibliotek