Techninis straipsnis

Visiškas PDF teksto lygiavimas abipus Delphi aplinkoje su HotPDF

Visiškas lygiavimas abipus – tai toks išdėstymas, kai teksto stulpelis sulygiuojamas tiek kairiajame, tiek dešiniajame krašte; taip, kaip tikitės iš atspausdintos knygos ar oficialios ataskaitos. Tai lengva apibūdinti, bet stebėtinai lengva suklysti, nes atsakymas į klausimą, „kur dingsta papildoma erdvė“, anglų kalbai nėra toks pat kaip japonų, ir dėl to, kad naiviai matuojant kiekvieną eilutę greitas puslapis tampa lėtu. HotPDF suteikia lygiavimą atsižvelgiant į raštą, naudojant vieną laukelio išdėstymo iškvietimą, o po šiuo iškvietimu slypi vadovėlinis našumo sprendimas, kurį verta suprasti ir atskirai

Šiame straipsnyje aptariami abu aspektai. Pirmiausia, tipografinė taisyklė, nusprendžianti, kaip paskirstomas laisvumas raštams su tarpais tarp žodžių, palyginti su raštais be jų. Antra, matavimo pakeitimas, kuris vieno puslapio lygiavimo abipus išlaidas sumažino maždaug aštuoniasdešimt kartų be jokio vizualaus skirtumo išvestyje. Abu šie dalykai yra svarbūs, jei generuojate dokumentus dideliais kiekiais ir norite, kad jie atrodytų kaip tikras maketas, o ne į plotį ištemptas lygiaplotis tekstas

Ko iš tikrųjų reikalauja visiškas lygiavimas abipus

Teksto eilutė, nupiešta natūraliu pločiu, beveik niekada nepasiekia dešiniojo stulpelio krašto. Visada lieka likutis – laisvumas – tarp to, kur baigiasi paskutinis glifas, ir stulpelio ribos. Lygiavimas kairėje palieka šį laisvumą dešinėje. Lygiavimas dešinėje perkelia jį į kairę. Centravimas jį padalina. Visiškas lygiavimas abipus jį pašalina praplėsdamas pačią eilutę, kol abu kraštai pasiekia laukelį, ir vienintelis sąžiningas būdas tai padaryti – atstumti glifus vieną nuo kito iš vidaus

Taisyklė, atskirianti gerą lygiavimą nuo blogo, yra ta, kur padedate laisvumą. Raštas, kuriame rašomi žodžiai su tarpais, pvz., anglų ir likusi lotyniška šeima, turi natūralias siūles ties kiekvienu tarpu tarp žodžių. Šių tarpų praplėtimas akiai nematomas, nes skaitytojai jau priima, kad tarpai tarp žodžių kinta. Raštas, kuriame rašoma be tarpų tarp žodžių, pvz., kinų Han rašmenys, japonų kana ar korėjiečių hangul, tokių siūlių neturi. Ten laisvumas turi būti tolygiai paskirstytas tarp gretimų glifų, ir tai yra principas, kurį japonų tipografai vadina kintou-waritsuke, tolygiu intervalų išdėstymu. Jei CJK eilutei pritaikysite lotyniško stiliaus tarpų tarp žodžių tempimą arba sugrūsite visą laisvumą į vieną vietą, kurioje CJK eilutėje atsitiktinai yra tarpas, gausite „upes“ ir spragas, kurios išduoda mėgėjišką išvestį

Kaip HotPDF nusprendžia, kur dingsta erdvė

HotPDF šį sprendimą priima pagal kiekvieną tarpą, o ne pagal eilutę. Kai jis lygiuoja eilutę, jis eina per kiekvieną gretimų glifų porą ir klausia, ar tarp jų yra ištempiama riba. Riba yra ištempiama, kai iš abiejų pusių yra tarpas arba tabuliacija – lotyniškas atvejis, arba kai abiejose pusėse yra CJK laužomi rašmenys – tolygių intervalų atvejis. Jis suskaičiuoja tas ribas, padalija eilutės laisvumą joms po lygiai ir prideda tą dalį prie kiekvieno atitinkančio tarpo

Pasekmė atsiranda natūraliai. Angliška eilutė turi ištempiamas ribas tik žodžių tarpuose, todėl visas laisvumas patenka ten, o žodžiai išsisklaido, kai raidės kiekvieno žodžio viduje išlaiko natūralius intervalus. Han arba kana eilutė turi ištempiamą ribą tarp beveik kiekvienos glifų poros, todėl laisvumas tolygiai pasiskirsto per visą eilutę – tiksliai tokie lygūs intervalai tarp glifų, kurių reikalauja tie raštai. Eilutė, kurią sudaro vienas ilgas lotyniškas žodis be vidinio tarpo, apskritai neturi ištempiamos ribos, todėl HotPDF palieka jos natūralų plotį, užuot skaidęs žodį raidė po raidės. Ta pati logika apdoroja mišrias lotyniškas ir CJK sekas vienoje eilutėje be specialių atvejų, nes sprendimas yra lokalus kiekvienai ribai

Viena riba sąmoningai atmetama visur. Pozicija po paskutinio eilutės glifo niekada nelaikoma tarpu, nes tempimas toje vietoje tiesiog vėl sugrąžintų dešinės pusės likutį, kas yra lygiavimo priešingybė

Kodėl paskutinė eilutė paliekama ramybėje

Paskutinė pastraipos eilutė yra ypatinga, o ją klaidingai sulygiuoti yra dažniausia lygiavimo abipus klaida. Pastraipos paskutinė eilutė paprastai yra trumpa, dažnai vos kelių žodžių, o ištempus ją iki viso stulpelio pločio, tie žodžiai per puslapį nutempiami į retą, sulaužytą eilę. Taisyklinga tipografija paskutinę eilutę palieka natūralaus pločio, lygiuotą kairėje

HotPDF aptinka paskutinę eilutę pagal padėtį. Kai jis laužia tekstą į eilutes, jis žino, kada ką tik atskirta eilutė pasiekia pateiktos eilutės pabaigą. Ta paskutinė eilutė išvedama paprastai lygiuojant kairėje ir išlaiko natūralų plotį. Kiekviena prieš tai esanti eilutė sulygiuojama abiejuose kraštuose. Griežti eilučių lūžiai, kuriuos įrašote į tekstą, atsižvelgiami taip, kaip parašyta, todėl sąmoningai trumpa eilutė taip pat niekada neištempiama. Skaitytojas mato švarų stačiakampį teksto bloką, kurio paskutinė eilutė baigiasi natūraliai – tai ir yra tai, ko akis tikisi

Matavimo išlaidos, kurios lygiavimą pavertė lėtu

Norint sulygiuoti eilutę, reikia žinoti jos tikslų plotį ir kiekvieno glifo postūmį, kad papildomą erdvę būtų galima patalpinti tiksliai. Pirmasis įgyvendinimas gavo šiuos skaičius akivaizdžiu būdu. Jis išmatavo visą eilutę visiškos „Unicode“ pločio užklausos pagalba, tada matavo prefiksą po prefikso, kad skirtumais atgautų kiekvieno glifo postūmį. Eilutei, turinčiai N glifų, tai yra N+1 iškvietimų į matavimo variklį, ir kiekvienas iškvietimas yra visa GDI kelionė pirmyn ir atgal (angl. round-trip), paprašant operacinės sistemos suformuoti bei išmatuoti tekstą ir grąžinti atsakymą

Vienai eilutei tai skamba pigiai. Visame puslapyje tai nėra taip. Paimkite tankų A4 formato pagrindinio teksto puslapį, kurį sudaro maždaug keturiasdešimt penkios eilutės po maždaug aštuoniasdešimt simbolių. Esant N+1 kelionių pirmyn ir atgal eilutei, tai sudaro apie 81 kelionę kiekvienai eilutei ir maždaug 3645 puslapiui, iš kurių beveik visos išleidžiamos pakartotinai matuojant tekstą, kurį variklis jau ištyrė prieš akimirką. Paketinio apdorojimo užduotyje, kurios metu sugeneruojami tūkstančiai puslapių, šios papildomos išlaidos dominuoja maketavimo laike, o kiekviena kelionė kerta ribą tarp jūsų proceso ir grafinės posistemės

Vienas iškvietimas vietoj N plius vieno

Šis pataisymas yra toks pokytis, kuris atrodo mažas, bet atsiperka su kaupu. GDI jau gali pranešti viso eilutės pločio ir kiekvieno glifo poziciją vienoje užklausoje. HotPDF atskleidžia tai per GetWideCharAdvances, kuris užpildo masyvą natūraliais kiekvieno glifo postūmiais, įskaitant kerningą, ir grąžina bendrą plotį, naudojant vieną iškvietimą, o ne N+1. Lygiavimo rutina, vidinė _HPDFEmitJustifiedWideLine, vieną kartą prašo visų postūmių, apskaičiuoja laisvumą, paskirsto jį per ištempiamas ribas ir išveda eilutę

Tam pačiam A4 puslapiui vienos eilutės matavimas nukrenta nuo maždaug 81 kelionių iki vienos, todėl puslapio rodiklis krinta nuo maždaug 3645 kelionių pirmyn ir atgal iki maždaug 45, o tai yra beveik aštuoniasdešimt kartų sumažėjimas. Išvestis baitas po baito yra identiška, nes matavime nepasikeitė niekas, išskyrus tai, kiek kartų jo prašoma. Tas pats GDI variklis, ta pati šriftų metrika, tas pats kerningas paduoda tuos pačius skaičius. Sumažėjo tik kelionių skaičius. Kai matavimas jau yra teisingas, tinkamas optimizavimas yra nustoti jo prašyti pakartotinai, o ne jį apytiksliai įvertinti

Kaip eilutė pasiekia puslapį

Kai laisvumas yra proporcingai padalijamas, HotPDF išveda eilutę naudodamas ExtTextOut ir atskiro glifo postūmio masyvą, Dx masyvą. Kiekvienas įrašas yra atstumas nuo vieno glifo pradžios taško iki kito, kuris yra natūralus to glifo postūmis plius jam tenkanti laisvumo dalis, kai po jo seka ištempiama riba. Tai tiesiogiai atitinka PDF atvaizdavimo modelį. Pozicionuotas tekstas rašomas naudojant TJ operatorių – masyvą, kuris perkelia glifų sekas su aiškiais horizontaliais koregavimais, o Dx reikšmės tampa lygiai tais pačiais koregavimais. Būtent dėl šios priežasties papildoma erdvė atsiranda tarp glifų tiksliose subtaškio pozicijose, užuot imituojant jas pildymo simboliais, ir todėl sulygiuota HotPDF eilutė matuojama teisingai, jei pasroviui esantis įrankis ją perskaito atgal

Jūs patys nekviečiate ExtTextOut sulygiuotoms abipus pastraipoms. Įvesties taškas yra WideTextOutBox, kuris apgaubia „Unicode“ eilutę į laukelį ir taiko jūsų prašomą lygiavimą. Jis laužia tekstą į eilutes, kurios atitinka laukelio plotį, padeda kiekvieną eilutę per laukelio aukštį ir grąžina simbolių skaičių, kurį pavyko sutalpinti prieš baigiantis vertikaliai erdvei. Lygiavimas pasirenkamas naudojant lygiavimo „enum“

type
  THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);

Pirmieji trys yra savaime suprantami lygiavimai: kairėje, centre ir dešinėje. Ketvirtasis, jtJustify, yra čia aprašytas visiškas lygiavimas abipus, ir būtent šią reikšmę nuskaito WideTextOutBox, kad įjungtų į raštą atsižvelgiantį tarpų išdėstymą

Pastraipos lygiavimas praktikoje

Išsamus pavyzdys sukuria dokumentą, nustato šriftą ir supila pastraipą į laukelį su visišku lygiavimu abipus. Tas pats kodas lygiuoja lotynišką ir CJK tekstą be vėliavėlės pakeitimo, nes atsižvelgimas į raštą veikia po API

uses
  HPDFDoc;

procedure JustifyParagraph;
var
  Pdf: THotPDF;
  Body: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Justified.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', 11);

    Body :=
      'Full justification spreads the slack on each filled line so both ' +
      'edges meet the column, while the last line keeps its natural width. ' +
      'For scripts with word gaps the space lands between words; for ' +
      'scripts without them it spreads evenly between glyphs.';

    // X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
    Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Norint nupiešti tą patį bloką lygiuotą kairėje, centruotą arba lygiuotą dešinėje, pakeiskite tik paskutinį argumentą į jtLeft, jtCenter arba jtRight. Laužymas teksto, eilučių išdėstymas ir grąžinama reikšmė išlieka tie patys. Išmatuotas plotis, kuris valdo visus keturis kelius, gaunamas iš GetWideTextWidth – į „Unicode“ atsižvelgiančios pločio užklausos, kuri teisingai išmatuoja WideString ten, kur senesnis matavimas baitais neteisingai nustatytų bet kokio teksto už „Latin-1“ ribų dydį, ir tai iš pat pradžių priverčia laukelį tinkamoje vietoje laužyti CJK ir pakaitalų porų tekstą

Lygiavimas abipus yra vienas didesnio teksto formavimo steko sluoksnis. Kai eilutėje yra raštų, kurie pertvarko arba sujungia savo glifus, čia aprašyti tarpų išdėstymo sprendimai dedasi ant darbo, aprašyto mūsų straipsnyje apie sudėtingo rašto teksto formavimą, o kai šriftas turi tipografinius variantus, kuriuos norite pasirinkti, sužinokite, kaip valdyti „OpenType“ GSUB stiliaus alternatyvas. Visa tai pristatoma „Delphi“ ir „C++Builder“ skirtame HotPDF komponente, kartu su platesniais teksto, išdėstymo ir dokumentų API, aptariamais šiame tinklaraštyje