Tehnički članak

XPS i OpenXPS u PDF u Delphi-ju: koordinate i četkice

HotPDF konvertuje XPS i OpenXPS pakete u PDF unutar Delphi-ja i C++Builder-a bez print drajvera, preslikavajući svaku 96-DPI koordinatu fiksne stranice kroz jednu matricu stranice sa razmerom 0.75 i okretanjem Y ose, objavljujući svaki VisualBrush kao deljeni Form XObject, i pretvarajući ImageBrush tile režime u nativne PDF tiling obrasce umesto ponovljenih iscrtavanja slika

Scenario koji u ovo uvlači većinu Windows timova je dosadan i neizbežan. Nešto već štampa na Microsoft XPS Document Writer — izveštaj iz zastarelog ERP-a, potpisan obrazac, serija izvoda — a arhivska politika kaže PDF. XPS je sasvim dobar format za hvatanje i užasan onaj koji ćete za deceniju predati sistemu evidencije. Zato spool fajl mora postati PDF stranicu po stranicu, i onog trenutka kad počnete pisati taj konvertor otkrivate da zanimljivi deo nije XML. To je to da se XPS i PDF ne slažu oko toga gde je koordinatni početak, koliko vredi jedinica i kakva četkica sme da bude

Od paketa do PDF-a u jednom prolazu

Ulazna tačka je registar dokument rukovaoca, ne posebna XPS klasa. THPDFDocumentHandlerRegistry.RegisterStandardHandlers instalira XPS, EPUB i CBZ rukovaoce; prepoznavanje se zasniva na sadržaju, pa paket koji nosi [Content_Types].xml plus najmanje jedan .fpage deo dobija 95 čak i kada ekstenzija fajla laže, dok gola .xps ili .oxps ekstenzija dobija samo 10. Taj redosled je bitan kada primate upload-e, jer napadač koji preimenuje EPUB u .xps ne sme upravljati procesnom linijom

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 za ovu konverziju     
  finally
    Output.Free;
    Registry.Free;
  end;
end;

Sve oko konverzije je budžetirano pre nego što se pokuša. THPDFDocumentHandlerOptions.Default ograničava arhivske unose na 10.000, proširene arhivske bajtove na 1 GiB, odnos kompresije na 200, resurse na 4.096 i stranice na 10.000, i nosi opcionu CancellationToken da se posao na strani servera može zaustaviti usred paketa. Pročitajte posle Info.UnsupportedFeatureCount i tretirajte nenultu vrednost kao stvaran nalaz: HotPDF namerno broji ono što nije moglo da se preslika, umesto da nacrta približno i ćuti o tome

Zašto XPS stranica treba matricu umesto prepisanih koordinata?

Jer prepisivanje koordinata gubi stek transformacija. XPS FixedPage je specificiran u 96-DPI jedinicama sa početkom gore levo i Y osom koja raste nadole; PDF korisnički prostor je 72-DPI sa početkom dole levo i Y osom koja raste nagore. Naivna popravka je da pomnožite svaki broj sa 0.75 i oduzmete svako Y od visine stranice pri ispisivanju. To radi za jedan ravan put i raspada se onog trenutka kad uđe RenderTransform, ugnježden Canvas ili matrica lokalna za četkicu, jer su te transformacije definisane u XPS prostoru, a vaše prepisivanje po koordinati ga je već napustilo. HotPDF zato drži projekciju kao matricu i komponuje je. HPDFXPSPageMatrix vraća fiksne konstante jednom po stranici, HPDFMultiplyXPSMatrix nadovezuje je na akumuliranu transformaciju puta, a rezultat se ispisuje kao jedan cm operator pre geometrije. Podaci puta se zatim pišu u nemodifikovanim XPS brojevima, što je i razlog zašto skraćena geometrijska sintaksa može da deli isti ograničeni parser korišćen za SVG path podatke — samo vodeći F0 ili F1 fill-rule token rukuje XPS adapter. Ako ste pratili isto razmišljanje kod EMF i WMF vektorskog uvoza, oblik argumenta vam je poznat: uvozni formati se konvertuju matricom, nikad aritmetikom po koordinatama listova

HotPDF komponuje fiksnu XPS matricu stranice sa akumuliranom transformacijom puta tako da 96-DPI XPS koordinatni sistem sa početkom gore levo stigne u 72-DPI PDF korisnički prostor sa početkom dole levo, ispisano kao jedan cm operator po vizuelu
Projekcija ostaje matrica i komponuje se sa svakom ugnježdenom transformacijom, pa podaci puta mogu da se pišu u nemodifikovanim XPS brojevima
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
  Result.A :=  0.75;       // 96-DPI XPS jedinica u 72-DPI PDF poen
  Result.B :=  0;
  Result.C :=  0;
  Result.D := -0.75;       // XPS Y raste nadole, PDF Y nagore
  Result.E :=  0;
  Result.F := PageHeight;  // visina PDF stranice, u poenima
end;

// Jedna komponovana CTM po vizuelu, ispisana pre bilo kog operatora puta
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));

Kako iskoristiti VisualBrush bez dvostrukog iscrtavanja?

VisualBrush boji proizvoljno vizuelno drvo — decu Canvas, Path i Glyphs — u region, moguće ponovljeno preko njega. HotPDF to vizuelno jednom iskompajlira u PDF Form XObject pa ga postavlja, što je ista strategija resursa opisana u SVG uvozu preko Form XObject-ova. Dva detalja odlučuju da li radi. Prvo, vizuelno drvo mora se obići kroz direktnu XML decu: ravan sken za elemente dostojne tiling-a izvlači ugnježdene vizuele na vrh stranice i uništava i opseg resursa i red bojenja. Drugo, sadržaj se hvata sa već primenjenom XPS-u-PDF matricom stranice, pa objavljivanje Form-a zahteva množenje inverzom te matrice, inače svako postavljanje ponovo primenjuje razmeru 0.75 i okretanje Y ose. Form takođe mora posedovati svoje resurse: HotPDF kopira samo fontove, XObject-e, obrasce, ExtGState-ove i prostore boja na koje uhvaćeni content tok stvarno referencira; kloniranje celog rečnika resursa stranice bi uvolilo Form koji se registruje u sopstveni graf resursa i napravilo ciklus. Fontovi ostaju u direktnom rečniku na običnim stranicama i bivaju unapređeni u deljeni indirektan rečnik tek kad uhvaćeni sadržaj zaista sadrži Tf, pa dokument bez ponovo upotrebljivih vizuela ne plaća tu mašineriju. Zapamtite jednu granicu specifikacije pre nego što prijavite bug: ECMA-388 sekcija 13.4 zahteva da i ViewboxUnits i ViewportUnits na VisualBrush-u budu Absolute, pa relativne jedinice nisu nedostajuća funkcija — to je ulaz van specifikacije, i HotPDF odbija da izmisli semantiku koordinata za njih

HotPDF jednom iskompajlira XPS VisualBrush vizuelno drvo u PDF Form XObject, objavljuje ga kroz inverz fiksne matrice stranice da postavljanja ne bi ponovo primenila razmeru, i kopira samo resurse na koje uhvaćeni sadržaj stvarno referencira
Sadržaj se hvata sa već primenjenom matricom stranice, pa se Form objavljuje kroz njen inverz i nosi samo resurse na koje sopstveni content tok referencira

ImageBrush tiling: četiri režima, četiri veličine ćelije

XPS tile režimi preslikavaju se na PDF tiling obrasce iz ISO 32000-1 sekcije 8.7.3 umesto da se rašire u ponovljena postavljanja slika preko pokrivene oblasti, što veličinu izlaza i vreme konverzije drži nezavisnim od toga koliko stranice četkica pokriva. Preslikavanje je mehaničko jednom kad ga vidite: ogledanje se izražava stavljanjem ogledalnih postavljanja unutar jedne ćelije obrasca i uvećanjem ćelije da odgovori

  • Tile — jedno postavljanje, ćelija ostaje 1×1 viewport
  • FlipX — dva postavljanja, ćelija proširena na 2×1
  • FlipY — dva postavljanja, ćelija podignuta na 1×2
  • FlipXY — četiri postavljanja, ćelija raširena na 2×2

Svako postavljanje nosi svoj clip pravougaonik, jer bi Viewbox preslikavanje koje preplavi svoju pod-ćeliju krvarilo u susedno ogledanje. /Matrix obrasca je deo koji ljude iznenadi. Tiling obrazac sidren je na podrazumevani korisnički prostor svog roditeljskog content toka, ne na grafičko stanje tekuće u trenutku izbora obrasca, pa matrica mora eksplicitno komponovati sva tri sloja — projekciju fiksne stranice, transformaciju Path-a i Transform lokalnu za četkicu — umesto da se oslanja na ambijentalnu CTM. HotPDF takođe validira pre alokacije: RegisterImageTilingPattern ograničava obrazac na 1.024 postavljanja i odbija degenerisane clipove, neinvertibilne matrice i nevažeće indekse slika. Ako želite opšti model sa PDF strane iza ovoga, tiling obrasci i Pattern prostor boja pokriva donje operatore

// Projekcija fiksne stranice uvijena u matricu obrasca, pa tek ona lokalna za četkicu
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);

Šta se dešava kad radijalni gradijent nije krug?

XPS definiše RadialGradientBrush sa GradientOrigin, Center, RadiusX i RadiusY, pa je četkica elipsa. PDF shading tip 3, u ISO 32000-1 sekciji 8.7.4.5.4, meša između dva kruga i nema načina da elipsu izrazi direktno. Prosečiti dva poluprečnika u jedan broj je primamljiva prečica i vidno je pogrešna na svakoj četkici koja nije blizu okrugle. HotPDF umesto toga pomera problem u koordinatni sistem: razmeri Y po RadiusY / RadiusX, registruje iskreno kružno senčenje u tom razmerenom prostoru, bira obrazac, i odmah ispisuje recipročnu razmeru da geometrija puta upisana dalje ostane u originalnom XPS korisničkom prostoru

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);   // obrazac hvata CTM baš ovde  
if Abs(ScaleY - 1) > 0.000001 then
  Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);

Redosled u tom isečku je celi trik, i nije stvar stila. PDF shading obrazac hvata matricu tekuće transformacije u trenutku kad je izabran kao tekuća boja, pa privremena razmera mora biti ispisana pre SetFillPattern ili SetStrokePattern, a recipročna mora doći posle izbora ali pre operatora puta. Pogrešite redosled u bilo kom smeru i dobijate gradijent koji se ispravno renderuje na prvom putu a na svakom narednom raste odstupanje. Srodno ograničenje važi za režim relativnih koordinata: RadiusX i RadiusY moraju se razrešiti odvojeno prema širini i visini puta, jer razmera obe jednom dužinom ivice tiho menja odnos stranica elipse na svakom nekvadratnom putu

HotPDF preslikava eliptični XPS RadialGradientBrush na PDF shading tip 3 razmerom Y ose, registrujući kružno senčenje u razmerenom prostoru i ispisujući recipročnu razmeru tek posle toga što je obrazac uhvatio matricu tekuće transformacije
Elipsu upija koordinatni sistem umesto senčenja, a privremena razmera mora da okviri izbor obrasca baš tim redosledom

Gde je konverzija iskrena prema svojim granicama

Neki XPS konstrkti konvertuju se približno, a neki se ne konvertuju uopšte, i dizajnerski izbor kroz celinu je da se broje umesto da se lažu. TIFF i JPEG XR delovi rasterizuju se kroz WIC i ne nose obećanje o očuvanom alfakanalu, dok se PNG sa važećim alfakanalom deli na baznu sliku plus /SMask. Unutrašnja veličina slike izvodi se kao pixel * 96 / DPI, čitajući prvo PNG pHYs ili JPEG JFIF gustinu i vraćajući se na 96 DPI, pa loš header gustine dospeva u predvidivu veličinu umesto proizvoljne. Ne razrešeni resursi matrica, nestandardne relativne transformacije, ColorConvertedBitmap, nepodržani režimi rasprostiranja gradijenta i deformisana geometrija svi uvećavaju UnsupportedFeatureCount, i deformisan ulaz je fail closed umesto da se degradira u tiho drugačiji crtež

To je koristan stav za arhivski konvertor: konverzija koja tiho aproksimira je gora od one koja vam kaže koja četiri elementa nije mogla da predstavi, jer samo druga daje nešto što možete proveriti pre nego što dokument zapečati u sistem evidencije. Ako procenjujete XPS i OpenXPS konverziju uz ostatak dokumentne procesne linije — kompoziciju stranica, fontove, potpisivanje, PDF/A izlaz — stranica HotPDF Delphi PDF component izlaže kompletan skup funkcija i podržane Delphi i C++Builder verzije