Techninis straipsnis

PDFlibPas: automatiniai atsarginiai šriftai CJK ir Emoji

PDFlibPas išsprendžia simbolius, kurių pasirinktas šriftas negali nupiešti, ieškodamas atsarginėje įdiegtų šriftų grandinėje, klasteris po klasterio, tuo pačiu išsaugodamas formavimą ir dvikrypčių sekų tvarką. Šią funkciją įjungiate su SetAutomaticFontFallback, grandinę praplečiate su AddFontFallback, o į failą įterpiami tik tie atsarginiai šriftai, kurie iš tikrųjų buvo panaudoti išvedimui

Problema, kurią tai sprendžia, kiekvieną dokumentų generavimo sistemą pasiveja pirmą kartą, kai kliento vardas atkeliauja rašmenimis, kurių šablono šriftas niekada nenumatė. Gedimas tylus, ir būtent tai jį daro brangų

Kodėl nepalaikomas tekstas dingsta, o ne meta klaidą?

Todėl, kad PDF neturi sąvokos „šriftas, kuris negali nupiešti simbolio". Paprastas šriftas per koduotę susieja baitų kodus su glifų vardais; sudėtinis šriftas per CMap susieja kodus su glifų indeksais. Paprašykite glifo, kurio šriftas neturi, ir gausite nulinį glifo indeksą, .notdef, kurį dauguma šriftų nupiešia kaip tuštumą arba tuščią stačiakampį. Failas struktūriškai galiojantis, teksto operatorius taisyklingai suformuotas, o puslapis atvaizduojamas. Tiesiog tuščia ten, kur turėtų būti vardas

ISO 32000-1 niekur nereikalauja, kad generatorius tai pastebėtų. Generatorius, kuris rašo tekstą nepatikrinęs padengimo, sukuria techniškai atitinkantį PDF, kuris tyliai prarado turinį, ir šis praradimas paaiškėja kliento ekrane tik po kelių savaičių. Būtent todėl atsarginio šrifto funkcija ir trūkstamų glifų ataskaita pateikiamos kartu: išspręsti tai, ką galima išspręsti, yra tik pusė darbo, o pranešti apie tai, ko išspręsti nepavyko, yra antroji pusė

Atsarginis variantas taikomas klasteriui, ne kodo taškui

Detalumo lygis yra tas dalykas, kuris atskiria veikiančią realizaciją nuo tikėtinos. Tekstas nėra nepriklausomų simbolių seka. Devanagarų skiemuo, Emoji su odos tono modifikatoriumi, pagrindinė raidė su jungiamaisiais ženklais: kiekvienas jų yra vienas klasteris, kurį turi atvaizduoti vienas šriftas, nes formavimo sprendimai jo viduje priklauso nuo lentelių tame konkrečiame šrifte

PDFlibPas išsprendžia klasterius, todėl klasteris, kurį padengia atsarginis šriftas, visas nupiešiamas tuo šriftu. Skaidymas viduryje klasterio, pusę piešiant pagrindiniu šriftu, o pusę atsarginiu, sukurtų techniškai esantį, bet vizualiai sugadintą rezultatą, kuris tikriausiai būtų blogesnis nei tuštuma, nuo kurios pradėjote. Sekos tvarka taip pat išsaugoma, todėl atsarginis variantas dešinės į kairę sekoje neperrikiuoja aplinkinio teksto; ta pati mechanika naudojama vertikaliame maketavime, aprašytame straipsnyje apie vertikalų rašymą japonų ir kinų kalboms

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.SetAutomaticFontFallback(1);

    // Paieškos tvarka: laimi pirmas atitikmuo, todėl plačiausius šriftus dėkite paskiausiai
    Lib.AddFontFallback('Microsoft YaHei');   // Supaprastinta kinų
    Lib.AddFontFallback('Meiryo');            // Japonų
    Lib.AddFontFallback('Segoe UI Symbol');
    Lib.AddFontFallback('Segoe UI Emoji');

    Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);

    Lib.AddTrueTypeFont('Arial', 1);          // 1 = įterpti šriftą
    Lib.SetTextSize(11);
    Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
    Lib.DrawText(72, 700, 'Delivery status: on time');

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

Grandinę tvarkykite sąmoningai. Sprendimas paima pirmą šriftą, padengiantį klasterį, todėl plataus spektro visa Unicode aprėpianti šriftas, padėtas pirmas, laimės beveik viską, o jūsų kruopščiai parinkti konkretiems raštams skirti šriftai niekada nebus panaudoti. Konkrečius šriftus dėkite pirmus, o universalų – paskutinį

Ataskaita ar nutraukimas: kurią klaidos formą renkatės?

SetMissingGlyphPolicy priima PDF_MISSING_GLYPH_REPORT, suderinamą numatytąją reikšmę, arba PDF_MISSING_GLYPH_ABORT. Taikant ataskaitos politiką, teksto operacija tęsiasi, neišsprendžiami kodo taškai praleidžiami kaip anksčiau, ir kiekvienas jų užregistruojamas. Taikant nutraukimo politiką, teksto operacija atmetama dar prieš įrašant bet kokį turinį, o LastErrorCode nustatomas į 521

Pasirinkite pagal tai, kam skirtas dokumentas. Vidinių ataskaitų paketas turėtų toliau atvaizduoti ir registruoti spragas, nes šiek tiek nepilna ataskaita šiandien yra geriau nei jokios ataskaitos. Teisiškai įpareigojanti sutartis, sąskaita faktūra ar bet kas su vardu ant jo turėtų būti nutraukiama, nes tyliai praleistas simbolis šalies pavadinime yra defektas, kurį norite aptikti savo pačių procese, o ne ginče. Nutraukimo politika suveikia dar prieš įrašymą, todėl pusiau suformuotas turinio srautas nelieka

var
  Lib: TPDFlib;
  Report: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
    // ... sukurkite dokumentą ...

    if Lib.DrawText(72, 660, CustomerName) <> 1 then
      if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
      begin
        Report := Lib.GetMissingGlyphReportJSON;
        // {"valid":false,"policy":1,"eventCount":1,"events":[
        //   {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
        //    "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
        //    "fontType":"TrueType","operation":"DrawText"}]}
        EscalateToOperator(Report);
      end;
  finally
    Lib.Free;
  end;
end;

Ataskaita sąmoningai sukurta mašininiam skaitymui ir ribota. Kiekvienas įvykis nurodo puslapį, UTF-16 indeksą eilutės viduje, kodo tašką tiek skaitine, tiek U+XXXX forma, pasirinktą šriftą, jo tipą ir operaciją, kurioje kilo problema, todėl pagalbos užklausoje galima nurodyti konkretų simbolį, o ne aprašyti simptomą. Sekėjas išsaugo naujausius 256 įvykius, kurių pakanka dokumentui diagnozuoti, tačiau pakankamai mažai, kad patologinis vykdymas negalėtų diagnostikos paversti atminties problema

Matavimas ir piešimas turi sutapti

Pločio matavimas naudoja tuos pačius klasteriais grįstus atsarginio šrifto sprendimus kaip ir piešimas. Tai skamba akivaizdžiai ir yra dalykas, kurį daugelis savadarbių atsarginių sluoksnių padaro klaidingai: jie pataiso piešimo kelią, palieka matavimą pagrindiniame šrifte, ir kiekvienas teksto laukas, dešinysis lygiavimas ir lentelės stulpelis galiausiai apskaičiuojamas iš pločių, kurie neatitinka to, kas buvo nupiešta

Kadangi abu keliai dalijasi sprendimu, prieš piešimą pamatuota eilutė užima tą patį plotį, kuriuo buvo pamatuota, įskaitant atsarginio šrifto sekas. Būtent tai leidžia saugiai įjungti atsarginį variantą visur, o ne tik vietose, kurias patikrinote rankiniu būdu

Įterpiama tik tai, kas panaudota

Atsarginiai šriftai įterpiami tinginiu būdu: šriftas grandinėje, kuris niekada neišsprendė nė vieno klasterio, į išvestį nieko neprideda. Dokumentas su vienu kinišku simboliu ir 5000 lotyniškų neneša viso CJK šrifto; jis neša tai, ką poaibio generavimo procesas sukūrė tam vienam glifui, o toks elgesys aprašytas straipsnyje apie failo dydžio optimizavimą ir šriftų poaibio generavimą

Šis tingumas leidžia pigiai sukonfigūruoti plačią grandinę. Registruokite šriftus, kurių gali prireikti jūsų dokumentų rinkiniui visose aptarnaujamose lokalėse, ir kiekvienas atskiras PDF sumoka tik už tai, ką iš tikrųjų panaudojo. Dokumentams, kurių jūs nesukūrėte ir kuriuose trūkstami šriftai jau yra esamame faile, taisymo kelias kitoks ir aprašytas straipsnyje apie trūkstamų šriftų įterpimą į esamą PDF

Vieną diegimo įspėjimą verta pasakyti tiesiai: atsarginis variantas sprendžiamas pagal šriftus, įdiegtus mašinoje, kurioje veikia kodas. Serveris be įdiegtų CJK šriftų neturi į ką pereiti, ir ataskaita tai pasakys jau su pirmuoju dokumentu, o ne po pirmojo skundo. Pristatykite šriftus, nuo kurių priklausote, ir patikrinkite jų įterpimo licencijavimo sąlygas

PDFlibPas yra Delphi, C++Builder ir Lazarus PDF biblioteka su atitinkamomis DLL ir ActiveX sąsajomis, todėl atsarginio šrifto ir trūkstamų glifų API pasiekiami ir ne Pascal kalbos programoms. Pilna dokumentacija pateikta PDFlibPas Delphi PDF bibliotekos puslapyje