Tekninen artikkeli

PDF-version preflight Delphissä: /Measure RL- ja GEO-säännöt

PDFlibPas (PDF Library for Delphi) tarkistaa jokaisen objektin PDF-versiosääntöjen taulukkoa vasten ennen kuin se kirjoittaa tiedoston, ja vielä äskettäin kyseinen PDF-version preflight sekoitti tavalliset CAD-mittaussanakirjat geospatiaalisiin. Yksisivuinen CAD-piirustus latautui hyvin, sitten SaveToFile palautti 0:n LastErrorCodeilla 602 ja vaati arvoa 1.7 ExtensionLevel 3. Korjatut säännöt käsittelevät suorakulmaiset /Measure-sanakirjat (/Subtype /RL) tavallisena PDF 1.6:na ja varaavat laajennusportin oikeille geospatiaalisille merkeille

Tiedosto tuli sisään corpus-hyväksynnän kautta: yksi sivu, yksi valinnaisen sisällön ryhmä, kaksi suorakulmaista mittausviewportia, sellaista tuotosta kuin arkkitehtuurin CAD-paketti kirjoittaa, jotta katselin voi lukea etäisyyksiä pohjapiirroksesta. Siinä ei ollut mitään eksoottista, ja juuri siksi kieltäytyminen oli merkityksellinen. Preflight, joka estää kelvollisen tiedoston, on pahempi kuin hidas, sillä kutsuja saa auktoriteettimäisen diagnoosin, joka osoittaa ominaisuuteen, jota asiakirja ei sisällä. Korjaus vei kaksi osaa: yhden säännön takana olevan spesifikaatiotulkinnan ja tajunnan siitä, ettei sääntö kyennyt erottamaan kahta sanakirjatyyppiä tasolla, jolla se katsoi

Miten PDFlibPasin tallennuksen version preflight toimii?

Tallennusportti, PrepareAndCheckSaveVersion, vertaa jokaista epäsuoraa objektia PDFFeatureRules-taulukkoa vasten ja kaatuu ensimmäiseen sääntöön, joka sekä täsmää että tarvitsee enemmän kuin kohde sallii. Kohde on asiakirjan versio (tai LockSaveVersionin lukitsema versio) plus /Extensions /ADBE-kohdalla ilmoitettu Adobe-laajennustaso. Jokainen TPDFFeatureRule-tietue kantaa MinVersion- ja MinExtensionLevel-kentät, MatchKind-tyypin kuten fmkDictKey tai fmkDictSubtype, Match-merkkijonon, ihmisluettavan Feature-nimen ja valinnaisen takaisinkutsun. AddRule rekisteröi tavallisen versiosäännön; AddExtensionRule lukitsee MinVersionin aina arvoon 17 ja lisää laajennustason päälle, joten laajennussäännön voi täyttää vain PDF 1.7 plus oikea /Extensions-merkintä. Kun portti lauhtuu, vaadittu versio ja ominaisuuden nimi säilytetään kutsujalle, ja GetInformationin avaimet 311, 312 ja 313 paljastavat ne

Tallennuksen version preflight PDFlibPasissa: PrepareAndCheckSaveVersion vertaa jokaista objektia PDFFeatureRules-tietueisiin, jotka kantavat MinVersionia, laajennustasoa ja täsmäystyyppiä, AddExtensionRule lukitsee vaatimuksen PDF 1.7:ään plus laajennustasoon, ja ensimmäinen täsmäys, jota kohde ei voi täyttää, pysäyttää tallennuksen virheeseen 602
GetInformationin avaimet 311, 312 ja 313 muuttavat kieltäytymisen diagnoosiksi ja raportoivat vaaditun version, sen laukaisseen ominaisuuden ja lukitun kohteen, joten kutsuja voi korjata tiedoston tai säännön arvaamisen sijaan
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('floor-plan.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    if Pdf.SaveToFile('floor-plan-out.pdf') <> 1 then
      if Pdf.LastErrorCode = PDFLIB_ERROR_VERSION_COMPLIANCE then
        // 311: vaadittu versio, 312: ominaisuus joka lauksi sen,
        // 313: versio, johon tallennuskohde on lukittu ('' kun ei lukittu)
        Writeln('Needs ', Pdf.GetInformation(311),
          ' for ', Pdf.GetInformation(312),
          ', locked at [', Pdf.GetInformation(313), ']');
  finally
    Pdf.Free;
  end;
end;

Miksi tavallinen CAD-piirustus kaatui virheeseen 602?

Sääntötaulukko sisälsi kutsun AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil), joka lauhtui missä tahansa sanakirjassa, jossa oli pelkkä /Measure-avain, ja jokaisessa mittausviewportissa sellainen on. Sivun /VP-taulukko pitää sisällään viewport-sanakirjat, jokainen viewport osoittaa mittaussanakirjaansa /Measurein kautta, ja avaimen läsnäolon täsmäys pysähtyi siihen katsomatta, mitä mittaussanakirja oikeastaan oli. Latausaikainen ominaisuusskannaus saattoi sitten nostaa asiakirjan versionumeron 1.7:ään, mutta se ei koskaan kirjoita /Extensions-ilmoitusta syötetiedoston puolesta, joten tallennusportti näki PDF 1.7:n laajennustasolla 0 ja raportoi arvon 1.7 ExtensionLevel 3. Kyseinen kieltäytyminen keksimästä laajennusilmoitusta on tahallista: kirjasto ei hiljaa ylennä syötetiedostoa peittääkseen väärän säännön

Spesifikaatio on yksiselitteinen suorakulmaisen tapauksen suhteen. Measure-sanakirjat saapuivat PDF 1.6:een, ja ISO 32000-1 §12.9 antaa /Subtypelle oletuksen RL, suorakulmainen koordinaatisto, joka kuvataan omalla merkintäjoukollaan: skaalasuhde, X- ja Y-numeromuodot, etäisyys ja pinta-ala. Geospatiaalinen mittaus on myöhempi lisäys Adoben Extension Level 3:sta PDF 1.7:n päälle, tunnistettu /Subtype /GEO:lla ja kantaen maantieteellisiä pistetaulukoita, koordinaatistosanakirjoja ja näyttöyksiköitä — rakenteet, joita kävellään läpi artikkelissa GeoPDF-viewporttien, GPTS- ja LPTS-taulukoiden lukemisesta Delphissä. Molemmat sanakirjat roikkuvat samasta /Measure-avaimesta, joten mikään sääntö, joka pysähtyy avaimeen, ei voi olla oikeassa molemmille. Erotteleva tieto istuu yhden tason alempana, mittaussanakirjassa itsessään

Yksi /Measure-avain, kaksi sanakirjaa PDFlibPasissa: suorakulmainen mittaus, jossa on /RL tai alatyyppi jätetty pois, tarvitsee vain PDF 1.6:n, kun taas geospatiaalinen sanakirja tarvitsee 1.7 ExtensionLevel 3:n, joten CB_GeospatialDictionary päättää sisällön perusteella siellä, missä vanha avainsääntö ei erottanut niitä toisistaan
Preflight, joka estää kelvollisen tiedoston, on pahempi kuin hidas, sillä kutsuja saa auktoriteettimäisen diagnoosin ominaisuudesta, jota asiakirja ei koskaan sisältänyt, ja siksi erotteleva tarkistus siirtyi yhden tason alemmaksi

Mitä korjattu sääntöjoukko yhä valvoo?

Korjaus poistaa ehdon avainsäännön ja jättää paikoilleen portit, jotka kuvaavat todellisia versiovaatimuksia. Sivu, joka kantaa /VP- tai /UserUnit-merkintää, tarvitsee yhä PDF 1.6:n CB_PagePDF16Entriesin kautta, /PtData-avain tarvitsee yhä laajennustason 3, ja CB_GeospatialDictionary päättää sisällön perusteella, onko mittaussanakirja geospatiaalinen, sen avaimen sijaan jolla se saapui

// Poistettu: jokainen /Measure-avaimen sisältävä sanakirja laskettiin geospatiaaliseksi
// AddExtensionRule(3, fmkDictKey, 'Measure', '/Measure geospatial dictionary', Nil);

AddRule(16, fmkCustom, '', 'Page PDF 1.6 entry /UserUnit /VP', CB_PagePDF16Entries);
AddExtensionRule(3, fmkDictKey, 'PtData', '/PtData geospatial dictionary', Nil);
AddExtensionRule(3, fmkCustom, '', 'geospatial measure dictionary', CB_GeospatialDictionary);

function CB_GeospatialDictionary(Obj: TPDFObject; const Ctx: TPDFRuleContext): Boolean;
var
  Dict: TPDFDictionary;
begin
  Result := False;
  if not (Obj is TPDFDictionary) then
    Exit;
  Dict := TPDFDictionary(Obj);
  Result := (Dict.StringValue('Subtype') = 'GEO') or
    (Dict.FindIndexByKeyName('GCS') >= 0) or (Dict.FindIndexByKeyName('DCS') >= 0) or
    (Dict.FindIndexByKeyName('GPTS') >= 0) or (Dict.FindIndexByKeyName('LPTS') >= 0) or
    (Dict.FindIndexByKeyName('PDU') >= 0);
end;

Yhteiset Delphi- ja FPC-regressiot naulaavat kyseisen rajan kummaltakin puolelta. Viewport, jonka mittaussanakirja jättää /Subtypein pois, ja viewport, joka kirjoittaa /RL:n auki, menestyvät molemmat PDF 1.6:lla, sama sivu hylätään yhä PDF 1.5:llä, ja ominaisuustunnistus ei enää raportoi laajennusta sille. /GPTS-taulukon lisääminen kääntää tuomion takaisin muotoon 1.7 ExtensionLevel 3, joka menee läpi heti kun laajennustaso on ilmoitettu, ja paljas /Subtype /GEO-sanakirja hylätään ilman sitä. Takaisinkutsu on konservatiivinen tahallaan: suorakulmainen sanakirja, joka kantaa lisäksi harhailevan /GCS- tai /PDU-avaimen, käsitellään geospatiaalisena, sillä noilla avaimilla ei ole merkitystä RL-mallissa

LockSaveVersion on paikka, jossa tämä muutos tulee kutsujen näkyviin. TPDFlib.LockSaveVersion hyväksyy arvot '1.0'–'1.7', palauttaa 0:n muille, lukitsee asiakirjan version ja estää kirjoittajan puolen kutsuja nostamasta sitä hiljaa, silti tallennusportti ajaa lukittua arvoa vasten. Korjatuilla säännöillä CAD-tiedosto, joka on lukittu 1.6:een, tallentuu siististi. Aito GeoPDF, joka on lukittu 1.6:een, saa yhä 602:n, mikä on oikea vastaus, ja geospatiaaliset kirjoituskutsut kuten SetMeasureDictCoordinateSystem ilmoittavat laajennustason 3 itse, kun rakennat kyseistä sisältöä API:n kautta

if Pdf.LockSaveVersion('1.6') <> 1 then
  raise Exception.Create('unsupported version string');
if Pdf.SaveToFile('floor-plan-16.pdf') <> 1 then
begin
  if Pdf.LastErrorCode = PDFLIB_ERROR_VERSION_COMPLIANCE then
    // Oikeaa sisältöä yli 1.6:n, esimerkiksi GEO-mittaussanakirja
    raise Exception.CreateFmt('Locked at 1.6 but %s needs %s',
      [string(Pdf.GetInformation(312)), string(Pdf.GetInformation(311))]);
end;
Pdf.UnlockSaveVersion;

Miksi versiosääntöjen skannaus oli hitaampi kuin sen piti olla?

Skannaus kopioi jokaisen TPDFFeatureRule-tietueen paikalliseen tietueeseen ennen sen testaamista, ja koska tietue pitää sisällään kaksi AnsiString-kenttää, jokainen kopio sääti kahta viitemäärää ja vapautti edelliset arvot. Preflight käy läpi jokaisen objektipuun jokaisen solmun, skalaarit mukaan lukien, joten tuo kustannus kertoi objektien määrän sääntöjen määrällä, ja säännöt, jotka eivät edes koskeneet kohdeversiota, kopioitiin ensin ja ohitettiin sitten. Koska PDFFeatureRules täytetään kerran yksikön alustuksessa ja sitä pidetään vain luku -tilassa, v3.539.17 välittää taulukkomerkinnät suoraan MatchSingleRulelle ja RuleExceedsTargetille, joiden const Rule-parametrit ottavat viittauksen koskematta merkkijonoihin

Sääntöskannauksen nopeutus PDFlibPasissa: preflight kopioi aiemmin jokaisen TPDFFeatureRule-tietueen ennen sen testaamista säätäen AnsiString-viitemääriä jokaista käytyä objektia kohden, kun taas const-parametrit lukevat nyt vain luku -taulukon paikoillaan, lyhentäen sääntötäsmäyksen kierroksen mediaanin arvosta 0,711 s arvoon 0,203 s
Hyöty on oikeaa mutta kapeaa: täysi tallennus maksaa myös viivästetystä ominaisuustunnistuksesta, objektien purusta ja serialisoinnista, joten mitattu suhde kuuluu sääntötäsmäyksen polulle, ei koko tallennusaikaan
// Ennen: hallittu tietuekopio per sääntö, per käyty objekti
Rule := PDFFeatureRules[X];
if not RuleExceedsTarget(Rule, TargetVersion, TargetExtensionLevel) then
  Continue;

// Jälkeen: const-parametrit lukevat muuttumattoman taulukkomerkinnän paikoillaan
if not RuleExceedsTarget(PDFFeatureRules[X], TargetVersion, TargetExtensionLevel) then
  Continue;
if MatchSingleRule(Obj, Ctx, PDFFeatureRules[X]) then
begin
  RequiredVersion := RequiredVersionString(PDFFeatureRules[X]);
  FeatureName := PDFFeatureRules[X].Feature;
  Result := False;
  Exit;
end;

Mitattu vaikutus on kapea, ja se kannattaa siteerata niin. Benchmark tarkistaa 20 000 numeerisen objektin taulukon PDF 1.4 -kohdetta vasten kymmenen kertaa per kierros; FPC Win64:llä -O2lla käännettynä viiden kierroksen mediaani putosi arvosta 0,711 s arvoon 0,203 s, ja kahden koosteen ajaminen käänteisessä järjestyksessä antoi 0,459 s vastaan 0,150 s. Se on suunnilleen kolminkertainen hyöty pelkästään sääntötäsmäyksen polulla. Oikea tallennus maksaa myös viivästetystä ominaisuustunnistuksesta, objektien purusta ja serialisoinnista, joten suhde ei siirry koko tallennusaikaan. Sääntöjen järjestys, takaisinkutsut, versiokynnykset ja ensimmäisen epäonnistumisen diagnoosi ovat muuttumattomia, eikä yhtään sääntöä välimuistitettu tallennusten yli eikä ohitettu päästäkseen tähän

Mitä kannattaa tarkistaa, kun ladattu PDF kaatuu version preflightiin?

Lue avaimet 311 ja 312 ennen kuin kosket versiota. Jos ominaisuus nimeää geospatiaalisen sanakirjan ja tiedosto piirtää vain suorakulmaisia mittauksia, kyseessä oli tämä väärä hälytys, ja ajankohtainen kooste tallentaa tiedoston muuttumattomana. Jos ominaisuus on aito, ilmoita joko laajennus tai lukitse versioon, joka rehellisesti sisältää sisällön; version nostaminen vain portin vaientamiseksi kätkee kysymyksen siitä, voivatko alavirran kuluttajat lukea sen, mitä toimitat. Sama rajattujen ja näyttöön perustuvien tarkistusten periaate ohjaa insinööriasiakirjojen PDF/E-1-author-mode-preflightia, jossa CAD-piirustukset kohtaavat vaatimustason standardin versioluvun sijaan

Versiovaatimustenmukaisuustarkistukset, mittaus- ja geospatiaaliset sanakirjat sekä tallennusversion lukitseminen kuuluvat kaikki osaksi PDF Library for Delphiä, PDFlibPas-työkalupakkia Delphi-, C++Builder- ja Lazarus-kehittäjille