Tekninen artikkeli

XPS ja OpenXPS PDF:ksi Delphissä: koordinaatit ja pensselit

HotPDF muuntaa XPS- ja OpenXPS-paketit PDF:ksi Delphin ja C++Builderin sisällä ilman tulostinajuria, kartoittaa jokaisen 96-DPI:n kiinteän sivun koordinaatin yhden 0.75-skaalaisen, Y-akselin kääntävän sivumatriisin kautta, julkaisee jokaisen VisualBrushin jaettuna Form XObjectina ja muuttaa ImageBrushin ruututilat natiiveiksi PDF-ruutukuvioiksi toistuvien kuvapiirtojen sijaan

Tilanne, joka raahaa useimmat Windows-yritykset tähän, on tylsä ja väistämätön. Jokin tulostaa jo Microsoft XPS Document Writerille — vanha ERP-raportti, allekirjoitettu lomake, erä tiliotteita — ja arkistointikäytäntö sanoo PDF. XPS on täysin hyvä kaappausmuoto ja kauhea annettavaksi arkistojärjestelmälle kymmenen vuoden päästä. Joten spool-tiedoston on tultava sivu sivulta PDF:ksi, ja sillä hetkellä kun aloitat kyseisen muuntimen kirjoittamisen, huomaat, että kiinnostava osa ei ole XML. Se on, että XPS ja PDF ovat eri mieltä siitä, missä origo on, mitä yksikkö on arvoltaan ja mitä pensselin saa olla

Paketista PDF:ksi yhdellä läpiviennillä

Sisääntulopiste on dokumenttikäsittelijärekisteri, ei erityinen XPS-luokka. THPDFDocumentHandlerRegistry.RegisterStandardHandlers asentaa XPS-, EPUB- ja CBZ-käsittelijät; tunnistus on sisältöpohjaista, joten paketti, joka kantaa [Content_Types].xml-tiedoston ja vähintään yhden .fpage-osan, saa pisteet 95 vaikka tiedostopääte valehtelee, kun taas pelkkä .xps- tai .oxps-pääte saa vain 10. Tuo järjestys merkitsee, kun hyväksyt latauksia, koska hyökkääjä, joka nimeää EPUBin uudelleen .xps-päätteeksi, ei saa ohjata putkea

var
  Handled: IHPDFHandledDocument;
  Info: THPDFDocumentHandlerInfo;
  Options: THPDFDocumentHandlerOptions;
  Registry: THPDFDocumentHandlerRegistry;
  Output: TFileStream;
begin
  Registry := THPDFDocumentHandlerRegistry.Create;
  Output := TFileStream.Create('spool.pdf', fmCreate);
  try
    Registry.RegisterStandardHandlers;
    Options := THPDFDocumentHandlerOptions.Default;
    if not Registry.OpenFromFile('spool.xps', '', Options, Handled, Info) then
      raise Exception.Create('no registered handler recognised the package');
    if Handled.Format = hdfXPS then
      Handled.WritePDF(Output, Options, Info);
    // Info.UnsupportedFeatureCount on rehellinen pistemäärä tälle muunnokselle
  finally
    Output.Free;
    Registry.Free;
  end;
end;

Kaikki muunnoksesta budjetoidaan ennen kuin se yritetään. THPDFDocumentHandlerOptions.Default kattoi arkistomerkinnät 10 000:een, laajennetut arkistotavut 1 GiB:hen, puristussuhteen 200:aan, resurssit 4 096:een ja sivut 10 000:een, ja se kantaa valinnaisen CancellationTokenin, jotta palvelinpuolen työ voidaan pysäyttää paketin keskellä. Lue Info.UnsupportedFeatureCount jälkeenpäin ja kohtele nollasta poikkeavaa arvoa todellisena löytönä: HotPDF laskee tarkoituksella sen, mitä se ei voinut kartoittaa, piirtämättä likimääräistä ja pysyen hiljaa siitä

Miksi XPS-sivu tarvitsee matriisin uudelleenkirjoitettujen koordinaattien sijaan?

Koska koordinaattien uudelleenkirjoittaminen menettää muunnospinon. XPS:n FixedPage määritellään 96-DPI:n yksiköissä, origo vasemmassa yläkulmassa ja Y kasvaa alaspäin; PDF:n käyttäjäavaruus on 72-DPI, origo vasemmassa alakulmassa ja Y kasvaa ylöspäin. Naiivi korjaus on kertoa jokainen luku 0.75:llä ja vähentää jokainen Y sivun korkeudesta sitä tuottaessa. Se toimii yhdelle tasaiselle polulle ja hajoaa sillä hetkellä, kun RenderTransform, sisäkkäinen Canvas tai pensselikohtainen matriisi astuu sisään, koska nuo muunnokset on määritelty XPS-avaruudessa ja koordinaattikohtainen uudelleenkirjoituksesi on jo lähtenyt siitä. HotPDF pitää siksi projektion matriisina ja yhdistää sen. HPDFXPSPageMatrix palauttaa kiinteät vakiot kerran sivua kohden, HPDFMultiplyXPSMatrix ketjuttaa sen kertyneen polkumuunnoksen kanssa, ja tulos tuotetaan yhtenä cm-operaattorina geometrian edellä. Polkudata kirjoitetaan sitten muuttamattomina XPS-lukuina, minkä vuoksi lyhennetty geometriasyntaksi voi jakaa saman rajatun jäsentäjän, jota käytetään SVG-polkudatalle — vain johtavaa F0- tai F1-täyttösääntötunnusta käsittelee XPS-sovitin. Jos olet seurannut samaa päättelyä EMF- ja WMF-vektorituonnille, päättelyn muoto on tuttu: tuontimuodot muunnetaan matriisilla, ei koskaan lehtikoordinaattien aritmetiikalla

HotPDF yhdistää kiinteän XPS-sivumatriisin kertyneen polkumuunnoksen kanssa niin, että 96-DPI:n vasemman yläkulman XPS-koordinaatisto saavuttaa 72-DPI:n PDF-käyttäjäavaruuden origo vasemmassa alakulmassa, tuotettuna yhtenä cm-operaattorina visualia kohden
Projektio pysyy matriisina ja yhdistetään jokaisen sisäkkäisen muunnoksen kanssa, joten polkudata voidaan kirjoittaa muuttamattomina XPS-lukuina
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
  Result.A :=  0.75;       // 96-DPI:n XPS-yksikkö 72-DPI:n PDF-pisteeksi
  Result.B :=  0;
  Result.C :=  0;
  Result.D := -0.75;       // XPS:n Y kasvaa alas, PDF:n Y ylös
  Result.E :=  0;
  Result.F := PageHeight;  // PDF-sivun korkeus pisteissä
end;

// Yksi yhdistetty CTM visualia kohden, tuotettu ennen mitään polkuoperaattoria
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));

Miten VisualBrush käytetään uudelleen piirtämättä sitä kahdesti?

VisualBrush maalaa mielivaltaisen visuaalipuun — Canvas-, Path- ja Glyphs-lapset — alueelle, mahdollisesti sen yli toistettuna. HotPDF kääntää kyseisen visuaalin kerran PDF Form XObjectiksi ja sijoittaa sen sitten, mikä on sama resurssistrategia, jota kuvataan SVG-tuonnissa Form XObjectien kautta. Kaksi yksityiskohtaa päättää, toimiiko se. Ensinnäkin visuaali on käveltävä suorina XML-lapsina: litteä skannaus ruudullisesti merkittävien elementtien varalta vetää sisäkkäiset visuaalit sivun ylätasolle ja tuhoaa sekä resurssien laajuuden että maalausjärjestyksen. Toiseksi sisältö kaapataan XPS:stä PDF:ään -sivumatriisin jo sovellettuna, joten Formin julkaiseminen vaatii kertomisen kyseisen matriisin käänteisarvolla, muuten jokainen sijoitus soveltaa 0.75-skaalan ja Y-käännön uudelleen. Formin on myös omistettava resurssinsa: HotPDF kopioi vain fontit, XObjectit, kuviot, ExtGStatet ja väriavaruudet, joita kaapattu sisältövirta oikeasti viittaa; koko sivun resurssisanakirjan kloonaaminen raahaisi rekisteröitävän Formin omaan resurssigraafiinsa ja rakentaisi syklin. Fontit pysyvät suorassa sanakirjassa tavallisilla sivuilla ja ylennetään jaettuun epäsuoraan sanakirjaan vain, kun kaapattu sisältö oikeasti sisältää Tf-kutsun, joten dokumentti, jossa ei ole uudelleenkäytettäviä visuaaleja, ei maksa koneistosta. Huomaa yksi määrittelyraja, joka on syytä tietää ennen bugin kirjaamista: ECMA-388:n jakso 13.4 vaatii sekä ViewboxUnitsin että ViewportUnitsin olevan Absolute VisualBrushilla, joten suhteelliset yksiköt eivät ole puuttuva ominaisuus — ne ovat määritelmän vastainen syöte, ja HotPDF kieltäytyy keksimästä koordinaattisemantiikkaa niille

HotPDF kääntää XPS VisualBrush -visuaalipuun kerran PDF Form XObjectiksi, julkaisee sen kiinteän sivumatriisin käänteisarvon kautta niin, etteivät sijoitukset sovella skaalaa uudelleen, ja kopioi vain resurssit, joita kaapattu sisältö oikeasti viittaa
Sisältö kaapataan sivumatriisin jo sovellettuna, joten Form julkaistaan sen käänteisarvon kautta ja kantaa vain resurssit, joita sen oma sisältövirta viittaa

ImageBrush-ruudutus: neljä tilaa, neljä solukokoa

XPS:n ruututilat kartoittuvat ISO 32000-1:n jakson 8.7.3 PDF-ruutukuvioiksi laajenematta toistuviksi kuvasijoituksiksi katetulle alueelle, mikä pitää tulosteen koon ja muunnosajan riippumattomana siitä, kuinka paljon sivua pensseli kattaa. Kartoitus on mekaaninen, kun sen näkee: heijastus ilmaistaan asettamalla peilatut sijoitukset yhden kuvioston sisään ja suurentamalla solu vastaamaan

  • Tile — yksi sijoitus, solu pysyy 1×1 viewportina
  • FlipX — kaksi sijoitusta, solu levennetty 2×1:een
  • FlipY — kaksi sijoitusta, solu korotettu 1×2:een
  • FlipXY — neljä sijoitusta, solu laajennettu 2×2:een

Jokainen sijoitus kantaa oman leikkaussuorakulmansa, koska alisolunsa ylittävä Viewbox-kartoitus vuotaisi naapuripeilaukseen. Kuvioston /Matrix on osa, joka kaappaa ihmiset. Ruutukuvio ankkuroituu emosisältövirtansa oletuskäyttäjäavaruuteen, ei grafiikkatilaan, joka vallitsi kuviota valittaessa, joten matriisin on yhdistettävä kaikki kolme kerrosta eksplisiittisesti — kiinteän sivun projektio, Path-muunnos ja pensselikohtainen Transform — ympäröivään CTM:ään tukeutumisen sijaan. HotPDF myös validoi ennen kuin varaa: RegisterImageTilingPattern rajoittaa kuvioston 1 024 sijoitukseen ja hylkää degeneroituneet leikkaukset, ei-invertöivät matriisit ja virheelliset kuviaindeksit. Jos haluat yleisen PDF-puolisen mallin tämän takana, ruutukuviot ja Pattern-väriavaruus kattaa taustalla olevat operaattorit

// Kiinteän sivun projektio taitettuna kuviomatriisiin, sitten pensselikohtainen
PatternMatrix := HPDFMatFromOps( 0.75 * PathMatrix.A, -0.75 * PathMatrix.B,
                                 0.75 * PathMatrix.C, -0.75 * PathMatrix.D,
                                 0.75 * PathMatrix.E,
                                 PageHeight - 0.75 * PathMatrix.F);
if Brush.HasTransform then
  PatternMatrix := HPDFMatMul(PatternMatrix, BrushMatrix);

PatternName := Document.RegisterImageTilingPattern(Resource.ImageIndex,
  Brush.Viewport.Left, Brush.Viewport.Top,
  Brush.Viewport.Left + CellWidth, Brush.Viewport.Top + CellHeight,
  CellWidth, CellHeight, Placements, pttNoDistortion, PatternMatrix);

Mitä tapahtuu, kun säteittäinen liukuväri ei ole ympyrä?

XPS määrittelee RadialGradientBrushin GradientOriginilla, Centerillä, RadiusXillä ja RadiusYillä, joten pensseli on ellipsi. PDF:n varjostustyyppi 3, ISO 32000-1:n jaksossa 8.7.4.5.4, sekoittaa kahden ympyrän välillä eikä pysty ilmaisemaan ellipsiä suoraan. Kahden säteen keskiarvoistaminen yhdeksi luvuksi on houkutteleva oikotie, ja se on näkyvästi väärin millä tahansa pensselillä, joka ei ole lähellä pyöreää. HotPDF siirtää sen sijaan ongelman koordinaatistoon: se skaalaa Y:n RadiusY / RadiusX-suhteella, rekisteröi rehellisen ympyrävarjostuksen siinä skaalatussa avaruudessa, valitsee kuvioston ja tuottaa välittömästi käänteisskaalan, joten seuraavaksi kirjoitettava polkugeometria on edelleen alkuperäisessä XPS-käyttäjäavaruudessa

ScaleY := Brush.RadiusY / Brush.RadiusX;
PatternName := Document.RegisterMultiStopRadialGradient(
  Brush.StartX, Brush.StartY / ScaleY, 0,
  Brush.EndX,   Brush.EndY   / ScaleY, Brush.RadiusX,
  StopPositions, StopColours, 3);

if Abs(ScaleY - 1) > 0.000001 then
  Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(ScaleY) + ' 0 0 cm'#10);
Page.SetFillPattern(PatternName);   // kuvio kaappaa CTM:n täsmälleen tässä
if Abs(ScaleY - 1) > 0.000001 then
  Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);

Järjestys kyseisessä katkelmassa on koko temppu, eikä se ole tyyliseikkaa. PDF:n varjostuskuvio kaappaa nykyisen muunnosmatriisin sillä hetkellä, kun se valitaan nykyiseksi väriksi, joten väliaikainen skaala on tuotettava ennen SetFillPatternia tai SetStrokePatternia, ja käänteisskaalan on seurattava valintaa mutta edettävä polkuoperaattoreita. Kun järjestys menee väärin kumpaankin suuntaan, sinulla on liukuväri, joka renderöityy oikein ensimmäisellä polulla ja ajautuu jokaista seuraavaa kohden. Aiheeseen liittyvä rajoitus koskee suhteellista koordinaattitilaa: RadiusXin ja RadiusYin on ratkaistava polun leveyttä ja korkeutta vasten erikseen, koska molempien skaalaaminen yhdellä sivun pituudella muuttaa hiljaisesti ellipsin kuvasuhteen millä tahansa ei-neliömuotoisella polulla

HotPDF kartoittaa ellipsimäisen XPS RadialGradientBrushin PDF-varjostustyyppiin 3 skaalaamalla Y:n, rekisteröimällä ympyrävarjostuksen skaalatussa avaruudessa ja tuottamalla käänteisskaalan vasta, kun kuvio on kaapannut nykyisen muunnosmatriisin
Ellipsi imeytyy koordinaatistoon varjostuksen sijaan, ja väliaikaisen skaalan on sijoittauduttava kuvion valinnan ympärille täsmälleen siinä järjestyksessä

Missä muunnos on rehellinen rajoistaan

Jotkut XPS-rakenteet muunnetaan likimääräisesti ja jotkut eivät lainkaan, ja suunnittelavalinta koko matkan varrella on laskea ne muistiin sen sijaan, että huijaisi niiden olemassaolon. TIFF- ja JPEG XR -osat rasteroidaan WIC:n kautta eivätkä kanna lupausta säilytetystä alfasta, kun taas kelvollisen alfakanavan omaava PNG jaetaan peruskuveksi ja /SMaskiksi. Kuvan luontainen koko johdetaan kaavana pixel * 96 / DPI, lukemalla ensin PNG:n pHYs tai JPEG:n JFIF-tiheys ja pudotumalla 96 DPI:hen, joten huono tiheysotsikko laskeutuu ennustettavaan kokoon mielivaltaisen sijaan. Ratkaisemattomat matriisiresurssit, epästandardit suhteelliset muunnokset, ColorConvertedBitmap, tukemattomat liukuvärin levitystilat ja epämuodostunut geometria kasvattavat kaikkia UnsupportedFeatureCountia, ja epämuodostunut syöte epäonnistuu suljettuun tilaan heiketemättä hiljaisesti erilaiseksi piirrokseksi

Tuo on hyödyllinen asenne arkistomuuntimelle: muunnos, joka likimääräistää hiljaa, on huonompi kuin se, joka kertoo, mitkä neljä elementtiä se ei voinut esittää, koska vain toinen antaa sinulle jotain tarkistettavaa ennen kuin dokumentti sinetöidään arkistojärjestelmään. Jos arvioit XPS:n ja OpenXPS:n muunnosta muun dokumenttiputken rinnalla — sivujen sommittelu, fontit, allekirjoitus, PDF/A-tuloste — HotPDF Delphi PDF component -sivu luetteloi koko ominaisuusjoukon ja tuetut Delphi- ja C++Builder-versiot