Tekninen artikkeli

Upota puuttuvat fontit olemassa oleviin PDF-tiedostoihin PDF/A-tarkoitusta varten Delphissä

losLab PDF Library voi upottaa jo ladatun PDF-tiedoston puuttuvat fonttiohjelmat yhdellä kutsulla: EmbedMissingFonts käy läpi dokumentin jokaisen fonttihakemiston, etsii vastaavan asennetun järjestelmäfontin sen BaseFont-nimen perusteella ja kirjoittaa fonttiohjelman takaisin tiedostoon. Tämä on korjaus, joka poistaa esitarkistusvirheen (preflight error) 00030 tiimeiltä, jotka korjaavat kolmannen osapuolen dokumentteja, joiden PDF/A-validointi epäonnistuu fonttien upottamisen puuttumisen vuoksi

Skenaario on harmillisen yleinen. Arkistointijärjestelmä vastaanottaa PDF-tiedostoja toimittajilta, asiakkailta tai skannauspalvelusta; dokumentit näkyvät hienosti kaikilla toimiston näytöillä; ja sitten PDF/A-validointi hylkää koko erän toistaen saman virheen jokaisessa tiedostossa: vähintään yhtä fonttia ei ole upotettu. Kukaan ketjussa aiemmin oleva ei aio luoda tiedostoja uudelleen, joten järjestelmän on korjattava ne. Tämä artikkeli käsittelee tätä korjauspolkua. Se on kumppani esitarkistusta käsittelevälle artikkelille, joka kattaa PDF/A- ja PDF/UA-rikkomusten havaitsemisen: kyseinen artikkeli kertoo, mitkä dokumentit ovat rikki, ja tämä artikkeli korjaa yleisimmän syyn niiden rikkoutumiseen

Miksi PDF/A vaatii jokaisen fontin upottamista?

ISO 19005-1 §6.3.4 vaatii, että jokaisen vaatimustenmukaisen dokumentin käyttämän fontin on sisällettävä fonttiohjelmansa tiedoston sisällä, koska PDF/A:n koko lupaus on toistettavuus: dokumentin on näyttävä täsmälleen samalta koneella viidenkymmenen vuoden päästä, vaikka sillä ei olisi yhteisiä fontteja dokumentin luoneen koneen kanssa. Upottamaton fontti on ohje etsiä Arial jostain katselujärjestelmästä, ja standardin kanta on, että "jostain katselujärjestelmästä" ei ole arkistointitakuu. Lukija saa sen, mitä korvaavalla fontilla on tarjota glyyfeinä, mittasuhteina ja kattavuutena, eikä se välttämättä vastaa sitä, mitä kirjoittaja näki

Historiallinen syyllinen on Standard 14 -käytäntö. PDF 1.0 lupasi, että jokainen katseluohjelma sisältää Helvetica-, Times-, Courier-, Symbol- ja ZapfDingbats-fontit, joten luontiohjelmat oppivat viittaamaan näihin fontteihin nimellä upottamatta mitään, ja kolmekymmentä vuotta kestänyt työkalukehitys tekee edelleen juuri niin. losLab PDF Library ottaa vaatimuksen niin vakavasti, että PDF/A-luontitilassa AddStandardFont on tarkoituksella tyhjä toiminto (no-op): kirjasto ei toimita Standard 14 -fonttiohjelmia, ei voi upottaa sellaista, mitä sillä ei ole, ja kieltäytyy kirjoittamasta upottamatonta viitettä dokumenttiin, joka väittää olevansa yhteensopiva. Se palauttaa arvon 0 valitsematta fonttia, joten PDF/A-dokumentin on sen sijaan käytettävä AddTrueTypeFont-funktiota upotuksen kanssa, ja kaikki Embed=0-pyynnöt korotetaan hiljaisesti tilaan Embed=1, korottaen PDF/A-tilan jo voimassa pitämän takuun jokaiseen dokumenttiin. Tämä on kirjoituspuoli. Vaikeampi ongelma on lukijapuoli: dokumentti, jonka joku muu on jo kirjoittanut ja joka on täynnä fonttihakemistoja, joita et ole itse luonut

Miten EmbedMissingFonts korjaa ladatun dokumentin?

losLab PDF Library korjaa fontit paikoillaan sen sijaan, että rakentaisi ne uudelleen. Kun PDF-luontiohjelma kirjoittaa upottamattoman TrueType-fontin, sen tuottama FontDescriptor-hakemisto on jo valmis: FontName, FontBBox, Flags, Ascent, Descent, StemV ovat kaikki läsnä. Ainoa asia, joka erottaa sen upotetusta fontista, on yhden merkinnän puuttuminen: todellisen fonttiohjelman sisältävä /FontFile2-tietovirtaviite. Siksi EmbedMissingFonts ei koske fonttihakemistoon, koodaukseen, leveystaulukkoon tai mihinkään sisältövirtaan, joka viittaa fonttiin resurssinimellä. Se lukee vastaavan fonttiohjelman järjestelmästä, pakkaa sen uudeksi virtaobjektiksi ja lisää yhden /FontFile2-viitteen (tai /FontFile3-viitteen CIDFontType0-fonteille) jo olemassa olevaan FontDescriptoriin. Kaikki dokumentin sivujen osoittamat kohteet pysyvät täsmälleen siellä missä ne olivat, mikä tekee toiminnosta turvallisen suorittaa tiedostoille, joita et hallitse

Kattavuus sisältää molemmat käytännössä vastaan tulevat fonttiarkkitehtuurit: yksinkertaiset TrueType-fontit ja yhdistetyt Type0/CID-fontit, joita käytetään CJK-tekstille ja nykyaikaiselle Unicode-tulosteelle. Läpikäynti luettelee tarkoituksella jokaisen Font-hakemiston dokumentin objektipuussa sen sijaan, että tukeutuisi sivuittain tehtävään resurssien läpikäyntiin, jotta myös huomautuksista (annotations) viitatut tai sivujen kesken jaetut fontit saadaan mukaan. Sovellusliittymä (API) on yksi kutsu ladatulle dokumentille

var
  PDF: TPDFlib;
  Repaired: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
      raise Exception.Create('Could not load PDF');

    // Walks every Font dictionary; returns how many fonts
    // gained a font program. Fonts whose program cannot be
    // found on the system are skipped, not failed.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

    PDF.SaveToFile('supplier-invoice-repaired.pdf');
  finally
    PDF.Free;
  end;
end;

Korjauksen varmistaminen esitarkistusraportilla

CreatePreflightReport on varmistusvaihe, ja kierto on tarkoituksella suljettu: saman tarkistuksen, joka hylkäsi tiedoston, tulisi olla se, joka hyväksyy sen. Virhekoodi 00030 on PDF/A-syvätarkistuksen löydös, joka kuuluu "Vähintään yhtä fonttia ei ole upotettu (FontFile/FontFile2/FontFile3 puuttuu)", ja siitä raportoidaan koko tiedoston tasolla, joten yksikin huomiotta jätetty fontti pitää virheen aktiivisena. Aja raportti lähdetiedostolle, korjaa, tallenna ja aja se uudelleen tulosteelle

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 selects the PDF/A checks
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

Jos haluat tiedostokohtaisen tuomion sijaan fonttikohtaisen näkymän, lataa korjattu dokumentti uudelleen ja luettele fontit: FindFonts, jota seuraa SelectFont ja GetFontIsEmbedded, raportoi upotustilan fontti fontilta. Tämä on oikea työkalu silloin, kun eräajon on kirjattava tarkasti, mitä fonttia missäkin tiedostossa ei voitu korjata. Sama luettelointimalli esiintyy artikkelissa, joka käsittelee tekstin, kuvien ja fonttien purkamista ladatuista PDF-tiedostoista, jossa se palvelee purkamista korjaamisen sijaan

Mitä tapahtuu, jos fonttia ei ole asennettu järjestelmään?

EmbedMissingFonts ohittaa kaikki fontit, joiden ohjelmaa se ei löydä, ja raportoi ohituksesta palautusarvonsa kautta: jos määrä on pienempi kuin laskemasi upottamattomien fonttien määrä, erotus johtuu fonteista, joita järjestelmässä ei ole. Tämä on rehellinen vikatila ja se on parempi vaihtoehto kuin muut ratkaisut, sillä korvaavan ohjelman keksiminen dokumentissa nimetylle fontille muuttaisi hahmontamista, mitä arkistointikorjaus ei saa koskaan tehdä. Näitä tapauksia varten losLab PDF Library tarjoaa funktion EmbedFontProgramFromFile, joka upottaa kutsujan toimittaman .ttf- tai .otf-tiedoston nimettyyn fonttiin, jotta prosessi voi toimittaa mukana odotetut yritysfontit ja turvautua niihin hallitusti

var
  I, FontID: Integer;
begin
  PDF.FindFonts;
  for I := 1 to PDF.FontCount do
  begin
    FontID := PDF.GetFontID(I);
    if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
      if PDF.GetFontIsEmbedded = 0 then
        // Try the installed system font first, then fall back
        // to a font file shipped alongside the application
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

Kaksi rajoitusta on syytä todeta selkeästi. Ensinnäkin Type1-fontteja ei korjata nykyisessä toteutuksessa: niiden /FontFile-merkintä vaatii kolmiosaista PFB-rakennetta selkeillä pituusavaimilla, ja kirjasto ohittaa ne virheellisen tietovirran kirjoittamisen sijaan; ne ovat harvinaisia nykyisissä dokumenteissa, mutta niitä esiintyy vanhoissa arkistoissa. Toiseksi fontin upottaminen on lisensointiteko. TrueType-fontin upotusoikeudet kuuluvat sen valmistajalle, ja lisensoituja fonttiohjelmia organisaatiosta lähteviin dokumenttejaan täyttävän korjausprosessin tulisi varmistaa jonkun toimesta, että fonttilisenssit todella sallivat tämän. Kirjasto tekee sen, mitä pyydät; se, saatko pyytää sitä, on kysymys lakiosastollesi, ei kääntäjällesi

Upottaminen on välttämätöntä, mutta ei riittävää

Fonttien korjaaminen poistaa vain virheen 00030 eikä mitään muuta. Dokumentti, joka epäonnistuu PDF/A-määrityksessä salauksen, puuttuvien XMP-metatietojen, ilman OutputIntentia olevan laiteriippuvaisen väriavaruuden tai puuttuvien ToUnicode-karttojen vuoksi, epäonnistuu edelleen senkin jälkeen, kun jokainen fontti on upotettu. Siksi korjauksen tulisi tapahtua esitarkistukseen perustuvan kierron sisällä sen sijaan, että se korvaisi sen. Aja täysi raportti, korjaa sen nimeämät virheet ja anna raportin kertoa, kun olet valmis. Lisäksi on otettava huomioon tiedostokoko: täydellinen CJK-fonttiohjelma voi olla kooltaan megatavuja, joten useamman sellaisen upottaminen voi kasvattaa pienen dokumentin kokoa huomattavasti. Vastapainona on osittaminen (subsetting), jota käsitellään PDF-tiedostokoon optimointia ja fonttien osittamista käsittelevässä artikkelissa, mikä karsii jokaisen upotetun ohjelman vain niihin glyyfeihin, joita dokumentti todella hahmontaa

Uusien dokumenttien laadun heikkenemisen estäminen

SetEmbedAllFonts on saman ominaisuuden ennaltaehkäisevä puolisko: kirjoittajapuolen suoja, joka estää omaa koodiasi luomasta sellaisia dokumentteja, joita tämä artikkeli korjaa. Kun SetEmbedAllFonts(1) on aktiivinen, kaikki myöhemmät AddTrueTypeFont-kutsut, jotka pyytävät tilaa Embed=0-pyyntöjä korotetaan upotetuksi viitteeksi, mikä laajentaa PDF/A-tilan jo voimassa pitämän takuun jokaiseen dokumenttiin. Se vaikuttaa kutsun jälkeen lisättyihin fontteihin, ei ladatussa tiedostossa jo oleviin fontteihin, joten työnjako on selkeä: SetEmbedAllFonts luomillesi dokumenteille ja EmbedMissingFonts perimillesi dokumenteille

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file

Molemmat puoliskot, kirjoituspuolen suoja ja lataa-korjaa-tallenna-polku, ovat osa losLab PDF Library -kirjastoa Delphille, C#:lle ja VB.NET:lle yhdessä tuloksen todentavan esitarkistusmoottorin kanssa; tuotesivu sisältää täydellisen fontti-API-viitteen upotus- ja osituskohtaisine kutsuineen