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
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
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
// 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