Technisch artikel

PDFlibPas: afbreken en gebalanceerde tekstkolommen

PDFlibPas laat een behouden passage door één tot 64 kolommen van gelijke breedte vloeien met DrawTextFlowColumns, en breekt de regels af met begrensd taalbewust afbreken zodra u SetTextFlowLanguage en SetTextFlowHyphenation aanroept. Negen talen worden ondersteund, en de taal kan worden overgenomen uit de /Lang-waarde van de documentcatalogus in plaats van per flow te worden ingesteld

Beide functies bestaan om dezelfde reden: een smalle kolom is waar naïef regelafbreken ophoudt op zetwerk te lijken en op een bugrapport begint te lijken

Waarom valt uitgevulde tekst uiteen in smalle kolommen?

Omdat uitvulling overgebleven ruimte over de woordtussenruimtes van een regel verdeelt, en de hoeveelheid overschot afhangt van wat er past. Bij een brede kolombreedte is het overschot klein en merkt het oog het nooit op. Halveer de breedte en een enkel lang woord dat niet past, schuift naar de volgende regel, waardoor de voorgaande woorden alle ruimte moeten opvangen. Drie zulke regels na elkaar produceren de verticale witte kanalen die zetters rivieren noemen, en die lezers ervaren als tekst die moeilijk te volgen is zonder te weten waarom

Afbreken pakt de oorzaak aan in plaats van het symptoom, door een breuk binnen het woord toe te staan. Duitse en Nederlandse samenstellingen maken dit niet-onderhandelbaar: een zelfstandig naamwoord van 24 tekens in een kolom van 60 millimeter heeft geen goede uitkomst zonder afbreekpunt. Engels verdraagt het ontbreken ervan beter, wat verklaart waarom Engels-eerst-producten vaak lay-outcode uitleveren die onderuitgaat zodra een Duitse klant hem gebruikt

Welke talen, en waar komt de taal vandaan?

Afbreken dekt Engels, Duits, Nederlands, Frans, Spaans, Italiaans, Portugees, Russisch en Turks. Stel de taal expliciet per flow in met SetTextFlowLanguage, of laat haar overnemen uit het /Lang-item van de documentcatalogus, de waarde die een getagd en toegankelijk document al bij zich draagt

Die overerving is de moeite waard om te gebruiken in plaats van te overschrijven. Een document dat zijn taal in de catalogus declareert, vertelt schermlezers, zoekindexeerders en het afbreekmechanisme hetzelfde feit vanuit één plek, en één plek is waar een feit hoort te leven. Als u al getagde output produceert zoals beschreven in automatisch taggen voor toegankelijke PDF's, staat het taalitem al ingesteld en kan de flow het simpelweg volgen

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');
      // Inschakelen, minstens 3 tekens vóór de breuk, 3 erna
      Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
      Lib.SetTextFlowMinLines(Flow, 2);   // laat nooit één regel alleen achter

      repeat
        // Drie kolommen over een gebied van 480 pt, gootjes van 18 pt, gebalanceerd
        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 en MinSuffix zijn typografie, geen validatie

De twee gehele getallen na de inschakelvlag bepalen het minimumaantal tekens dat vóór en na een afbreking moet overblijven. Drie en drie is een conservatieve standaard die de meeste huisstijlen accepteren. Twee en twee levert meer afbreekmogelijkheden op en merkbaar lelijkere resultaten, omdat een fragment van twee letters aan het einde van een regel als een typefout leest

Verhoog de minima wanneer het lettertype groot is, waar elk fragment visueel opvalt, en verlaag ze alleen wanneer de kolom werkelijk smal is en u hebt besloten dat een krappe zetbreedte zwaarder weegt dan een nette. Dit is een huisstijlbeslissing en geen technische, en precies daarom is het een parameter in plaats van een constante

Wat betekent "gebalanceerd" hier eigenlijk?

De parameter Balance verandert het gedrag alleen aan het einde van een passage. Met balanceren aan worden de kolommen ingekort tot een exact gelijk aantal regels wanneer alles wat overblijft binnen het gebied past, wat voorkomt dat een laatste pagina twee volle kolommen toont en een derde met slechts één eenzame regel. Wanneer de passage niet past, behoudt elke kolom haar volledige hoogte zodat de pagina zoveel mogelijk tekst draagt en de rest verdergaat op de volgende pagina

Die asymmetrie is de juiste standaard voor doorlopende documenten. Balanceren halverwege een vloeiend artikel zou op elke pagina verticale ruimte verspillen aan een cosmetisch effect dat niemand ziet, aangezien de kolommen toch al vol zijn. Balanceren aan het einde is waar het oog het daadwerkelijk opmerkt, en precies daar wordt het toegepast

Regelafbreking meet hele woorden

Het afbreekalgoritme meet complete woorden in plaats van tekenbreedtes op te tellen, en reserveert een begrensde zoektocht voor te grote tokens die helemaal niet op een regel passen, zoals een URL of een dossiernummer. Dat houdt het gangbare geval snel en het pathologische geval begrensd, in plaats van andersom

Facultatieve zachte afbreektekens en automatische afbreektekens worden alleen weergegeven wanneer de breuk die ze markeren ook de breuk is die uiteindelijk gekozen wordt. Dit klinkt vanzelfsprekend en is een klassiek defect: een naïeve implementatie schrijft het afbreekteken al tijdens het meten, en als de breuk verschuift, blijft het afbreekteken midden in een regel achter. Niets doet een tekst-engine kapotter lijken dan een verdwaald afbreekteken midden in een woord

var
  Lib: TPDFlib;
  Flow, Needed: Integer;
begin
  // Bepaal de lay-out voordat er iets getekend wordt
  Flow := Lib.NewTextFlow(ArticleBody);
  try
    Lib.SetTextFlowLanguage(Flow, 'fr');
    Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);

    // Regels die de rest van de passage nodig heeft bij één kolombreedte
    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;

Houd de fontinstellingen gelijk over alle vakken

Eén regel beheerst elke flow-gebaseerde lay-out en die is het waard om ronduit te stellen: DrawTextFlow, DrawTextFlowColumns en MeasureTextFlow breken allemaal regels af met het font dat geselecteerd is op het moment dat ze worden aangeroepen. Verander het font of de grootte tussen twee vakken van dezelfde flow, of begin een nieuwe pagina zonder opnieuw te selecteren, en het tweede vak breekt anders af dan wat het eerste gemeten had

Het symptoom is bijzonder frustrerend omdat het willekeurig lijkt: tekst die op pagina één past, loopt over op pagina twee, of een gemeten regelaantal komt niet overeen met wat er getekend werd. Selecteer het font eenmaal vóór de lus, selecteer het opnieuw na elke NewPage, en de flow gedraagt zich. Wanneer gemengde schriften in dezelfde passage voorkomen, is het gedrag beschreven in automatische fontfallback voor CJK- en emojitekst van toepassing op zowel meten als tekenen, zodat de breedtes ook over de fallbackruns consistent blijven

Voor rapportlay-outs waarin de flow één element is naast koppen, voetteksten en datagedreven blokken, combineren de compositiepatronen in de dataset-rapportage-engine netjes met kolomflows: meet eerst, plaats het vaste meubilair, en geef de flow daarna welk gebied er nog overblijft

PDFlibPas is een PDF-bibliotheek voor Delphi, C++Builder en Lazarus, en de complete levenscyclus van TextFlow, aanmaken, tekenen, meten, inspecteren, terugspoelen en vrijgeven, is ook via de DLL- en ActiveX-interfaces beschikbaar. Volledige documentatie staat op de PDFlibPas Delphi PDF-bibliotheekpagina