Tehnični članak

XPS in OpenXPS v PDF v Delphiju: koordinate in čopiči

HotPDF pretvarja pakete XPS in OpenXPS v PDF znotraj Delphi in C++Builder brez tiskalnega gonilnika, pri čemer vsako koordinato fiksne strani 96 DPI preslika skozi eno samo matriko strani s skaliranjem 0.75 in obratom Y, vsak VisualBrush objavi kot skupni Form XObject in načine tlakovanja ImageBrush spremeni v izvorne tlakovalne vzorce PDF namesto ponavljajočih risb slik

Scenarij, ki vanj potegne večino Windows podjetij, je dolgočasen in neizogiben. Nekaj se že tiska na Microsoft XPS Document Writer — zastarelo poročilo ERP, podpisan obrazec, serija izpiskov — politika arhiva pa pravi PDF. XPS je odličen format zajema in grozen, če ga desetletje pozneje izročite sistemu evidenc. Tako mora čakalna datoteka postati PDF stran po strani, in ko začnete pisati ta pretvornik, odkrijete, da zanimiv del ni XML. Je pa to, da se XPS in PDF ne strinjata o tem, kje je izhodišče, koliko je enota vredna in kaj sme biti čopič

Od paketa do PDF v enem prehodu

Vstopna točka je register upravljavcev dokumentov in ne poseben razred XPS. THPDFDocumentHandlerRegistry.RegisterStandardHandlers namesti upravljavce XPS, EPUB in CBZ; prepoznavanje temelji na vsebini, zato paket z [Content_Types].xml in vsaj enim delom .fpage doseže 95, tudi ko laže pripona datoteke, gola pripona .xps ali .oxps pa le 10. Ta vrstni red šteje, ko sprejemate prenose, ker napadalec, ki preimenuje EPUB v .xps, ne bi smel krmariti po cevovodu

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 je iskrena ocena te pretvorbe
  finally
    Output.Free;
    Registry.Free;
  end;
end;

Vse pri pretvorbi je proračunano, preden se poskusi. THPDFDocumentHandlerOptions.Default omeji vnose arhiva na 10.000, razširjene bajte arhiva na 1 GiB, razmerje stiskanja na 200, vire na 4.096 in strani na 10.000, nosi pa tudi neobvezen CancellationToken, da je strežniško delo mogoče ustaviti sredi paketa. Potem preberite Info.UnsupportedFeatureCount in vrednost različno od nič obravnavajte kot pravo odkritje: HotPDF namenoma šteje, česa ni zmoglo preslikati, namesto da bi risalo približek in molčalo o tem

Zakaj stran XPS potrebuje matriko in ne prepisanih koordinat?

Ker prepisovanje koordinat izgubi sklad transformacij. XPS FixedPage je določen v enotah 96 DPI z izhodiščem levo zgoraj in Y, ki raste navzdol; uporabniški prostor PDF je 72 DPI z izhodiščem levo spodaj in Y, ki raste navzgor. Naivna rešitev je vsako število pomnožiti z 0.75 in vsak Y odšteti od višine strani ob izdaji. To deluje za eno ravno pot in se razsuje v trenutku, ko vstopi RenderTransform, ugnezden Canvas ali matrika, lokalna čopiču, ker so te transformacije definirane v prostoru XPS, vaš prepis na koordinato pa ga je že zapustil. HotPDF zato projekcijo ohrani kot matriko in jo komponira. HPDFXPSPageMatrix vrne fiksne konstante enkrat na stran, HPDFMultiplyXPSMatrix jo stakne z nabranim transformom poti, rezultat pa se izda kot en sam operator cm pred geometrijo. Podatki poti se nato zapišejo v nespremenjenih številih XPS, kar je tudi razlog, da se skrajšana skladnja geometrije lahko deli z istim omejenim razčlenjevalnikom, uporabljenim za podatke poti SVG — le vodilni žeton pravila polnjenja F0 ali F1 obravnava adapter XPS. Če ste isti sklep sledili pri uvozu vektorjev EMF in WMF, je oblika argumenta znana: uvozni formati se pretvarjajo z matriko, nikoli z aritmetiko na listnih koordinatah

HotPDF komponira fiksno matriko strani XPS z nabranim transformom poti, tako da koordinatni sistem XPS 96 DPI z izhodiščem levo zgoraj doseže uporabniški prostor PDF 72 DPI z izhodiščem levo spodaj, izdan kot en sam operator cm na vizual
Projekcija ostane matrika in se komponira z vsako ugnezdeno transformacijo, tako da se podatki poti lahko zapišejo v nespremenjenih številih XPS
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
  Result.A :=  0.75;       // enota XPS 96 DPI v točko PDF 72 DPI
  Result.B :=  0;
  Result.C :=  0;
  Result.D := -0.75;       // Y XPS raste navzdol, Y PDF navzgor
  Result.E :=  0;
  Result.F := PageHeight;  // višina strani PDF, v točkah
end;

// ena komponirana CTM na vizual, izdana pred vsakim operatorjem poti
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));

Kako ponovno uporabiti VisualBrush, ne da bi ga narisali dvakrat?

VisualBrush pobarva poljubno vizualno drevo — otroke Canvas, Path in Glyphs — v območje, morda čez njega ponavljajoče. HotPDF ta vizual enkrat prevede v PDF Form XObject in ga nato umesti, kar je ista strategija virov, opisana v uvozu SVG prek Form XObjectov. Dve podrobnosti odločata, ali deluje. Prvič, vizual je treba obhoditi kot neposredne otroke XML: raven pregled za elementi, vrednimi tlakovanja, potegne ugnezdene vizuale na vrhnjo raven strani in uniči tako obseg virov kot vrstni red risanja. Drugič, vsebina je zajeta z že uporabljeno matriko strani XPS-v-PDF, zato objava Forma zahteva množenje z inverzom te matrike, sicer vsaka umestitev znova uporabi skalo 0.75 in obrat Y. Form mora tudi lastiti svoje vire: HotPDF kopira le pisave, XObjecte, vzorce, ExtGState in barvne prostore, na katere zajeti tok vsebine dejansko sklicuje; kloniranje celotnega slovarja virov strani bi Form, ki se registrira, povleklo v njegov lasten graf virov in zgradilo cikel. Pisave ostanejo v neposrednem slovarju na navadnih straneh in so povišane v skupni posredni slovar šele, ko zajeta vsebina res vsebuje Tf, tako da dokument brez ponovno uporabnih vizualov ne plača mehanike. Upoštevajte eno mejo specifikacije, vredno poznavanja, preden prijavite hrošča: ECMA-388 odsek 13.4 zahteva, da sta ViewboxUnits in ViewportUnits na VisualBrush oba Absolute, relativne enote torej niso manjkajoča zmožnost — to je neskladen vhod, HotPDF pa noče izmišljati semantike koordinat zanje

HotPDF enkrat prevede vizualno drevo VisualBrush iz XPS v PDF Form XObject, objavi ga skozi inverz fiksne matrike strani, da umestitve znova ne uporabijo skale, in kopira le vire, na katere zajeta vsebina dejansko sklicuje
Vsebina je zajeta z že uporabljeno matriko strani, zato je Form objavljen skozi njen inverz in nosi le vire, na katere sklicuje njegov lasten tok vsebine

Tlakovanje ImageBrush: štirje načini, štiri velikosti celic

Načini tlakovanja XPS se preslikajo na tlakovalne vzorce PDF iz odseka 8.7.3 ISO 32000-1 in se ne razširijo v ponavljajoče umestitve slik čez pokrito območje, kar velikost izhoda in čas pretvorbe naredi neodvisnima od tega, koliko strani pokrije čopič. Preslikava je mehanska, ko jo vidite: zrcaljenje je izraženo z ogledalnimi umestitvami znotraj ene celice vzorca in povečanjem celice, da se ujame

  • Tile — ena umestitev, celica ostane vidik 1×1
  • FlipX — dve umestitvi, celica razširjena na 2×1
  • FlipY — dve umestitvi, celica povišana na 1×2
  • FlipXY — štiri umestitve, celica razširjena na 2×2

Vsaka umestitev nosi svoj pravokotnik izreza, ker bi preslikava Viewbox, ki preseže svojo podcelico, poškropila v sosednje zrcaljenje. Del, ki ujame ljudi, je /Matrix vzorca. Tlakovalni vzorec je zasidran na privzeti uporabniški prostor svojega starševskega toka vsebine in ne na grafično stanje, dejavno ob izbiri vzorca, zato mora matrika izrecno komponirati vse tri plasti — projekcijo fiksne strani, transform poti in Transform, lokalno čopiču — namesto da bi se zanašala na CTM okolja. HotPDF tudi preveri, preden dodeli: RegisterImageTilingPattern omeji vzorec na 1.024 umestitev in zavrne degenerirane izreze, neobratljive matrike in neveljavne indekse slik. Če želite splošni model na strani PDF za tem, tlakovalni vzorci in barvni prostor Pattern pokrivajo temeljne operatorje

// projekcija fiksne strani zvita v matriko vzorca, nato tista lokalna čopiču
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);

Kaj se zgodi, ko radialni gradient ni krog?

XPS definira RadialGradientBrush z GradientOrigin, Center, RadiusX in RadiusY, zato je čopič elipsa. Senčenje PDF tipa 3, v odseku 8.7.4.5.4 ISO 32000-1, se preliva med dvema krožnicama in nima načina, da bi elipso izrazil neposredno. Povprečiti dva polmera v eno število je mamljiva bližnjica in vidno narobe na vsakem čopiču, ki ni blizu krogu. HotPDF namesto tega preseli težavo v koordinatni sistem: skalira Y z RadiusY / RadiusX, registrira pošteno krožno senčenje v tem skaliranem prostoru, izbere vzorec in takoj izda recipročno skalo, tako da je geometrija poti, zapisana naslednja, še vedno v izvornem uporabniškem prostoru XPS

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);   // vzorec zajame CTM točno tukaj
if Abs(ScaleY - 1) > 0.000001 then
  Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);

Vrstni red v tem odlomku je celoten trik in ni stilski. Senčilni vzorec PDF zajame trenutno transformacijsko matriko v trenutku, ko je izbran kot trenutna barva, zato mora začasna skala biti izdana pred SetFillPattern ali SetStrokePattern, recipročna pa mora slediti izbiri in predhoditi operatorjem poti. Zmešajte vrstni red v katero koli smer in imate gradient, ki se pravilno izriše na prvi poti in zdrifta na vsaki naslednji. Sorodna omejitev velja za način relativnih koordinat: RadiusX in RadiusY morata biti razrešena proti širini in višini poti posebej, saj skaliranje obeh z eno samo dolžino roba tiho spremeni razmerje stranic elipse na vsaki nekvadratni poti

HotPDF preslika elipsni RadialGradientBrush iz XPS na senčenje PDF tipa 3 s skaliranjem Y, registracijo krožnega senčenja v skaliranem prostoru in izdajo recipročne skale šele, ko vzorec zajame trenutno transformacijsko matriko
Elipso vsrka koordinatni sistem in ne senčenje, začasna skala pa mora oklepati izbiro vzorca točno v tem vrstnem redu

Kjer je pretvorba iskrena glede svojih mej

Nekatere konstrukte XPS se pretvori približno, nekateri pa sploh ne, oblikovna odločitev pa je ves čas, da se jih prešteje in ne ponaredi. Deli TIFF in JPEG XR se rastrejo skozi WIC in ne nosijo nobenega obljuba o ohranjenem alfi, PNG z veljavnim kanalom alfa pa se razdeli na osnovno sliko in /SMask. Lastna velikost slike se izpelje kot pixel * 96 / DPI, najprej se prebere gostota PNG pHYs ali JPEG JFIF, s povratkom na 96 DPI, zato slaba glava gostote pristane na napovedljivi velikosti in ne poljubni. Nerazrešeni viri matrik, nestandardne relativne transformacije, ColorConvertedBitmap, nepodprti načini širjenja gradienta in deformirana geometrija vsi povečajo UnsupportedFeatureCount, deformiran vhod pa odpove varno namesto da bi se poslabšal v tiho drugačno risbo

To je koristna drža za arhivski pretvornik: pretvorba, ki tiho približuje, je hujša od tiste, ki vam pove, katerih štiri elemente ni zmogla predstaviti, ker le druga vam da nekaj, kar lahko preverite, preden je dokument zaprt v sistem evidenc. Če vrednotite pretvorbo XPS in OpenXPS ob preostanku dokumentnega cevovoda — kompozicija strani, pisave, podpisovanje, izhod PDF/A — stran HotPDF Delphi PDF component našteva celoten nabor zmožnosti in podprte različice Delphi in C++Builder