Tehnički članak

XPS i OpenXPS u PDF u Delphiju: koordinate i četkice

HotPDF pretvara XPS i OpenXPS pakete u PDF unutar Delphija i C++Buildera bez upravljačkog programa za ispis, preslikavajući svaku koordinatu fiksne stranice od 96 DPI kroz jednu matricu stranice s razmjerom 0.75 i okretanjem Y-osi, objavljujući svaki VisualBrush kao dijeljeni Form XObject i pretvarajući načine popločavanja ImageBrush-a u izvorne PDF obrasce popločavanja umjesto ponovljenih iscrtavanja slike

Scenarij koji većinu Windows radionica uvuče u ovo dosadan je i neizbježan. Nešto se već ispisuje na Microsoft XPS Document Writer — izvještaj naslijeđenog ERP-a, potpisan obrazac, serija izvoda — a arhivska politika kaže PDF. XPS je sasvim dobar format za hvatanje ispisa i užasan format za predati sustavu evidencije deset godina od sada. Dakle, spool datoteka mora postati PDF stranica za stranicu, i onog trenutka kad počnete pisati taj pretvarač otkrivate da zanimljivi dio nije XML. Nego to što se XPS i PDF ne slažu oko toga gdje je ishodište, koliko vrijedi jedinica i čime smije biti četkica

Iz paketa u PDF u jednom prolazu

Ulazna točka jest registar rukovatelja dokumentima, a ne poseban XPS razred. THPDFDocumentHandlerRegistry.RegisterStandardHandlers instalira rukovatelje za XPS, EPUB i CBZ; prepoznavanje se temelji na sadržaju, pa paket koji nosi [Content_Types].xml plus barem jedan dio .fpage dobiva 95 bodova čak i kada ekstenzija datoteke laže, dok gola ekstenzija .xps ili .oxps dobiva samo 10. Taj redoslijed važan je kada primate učitavanja, jer napadač koji preimenuje EPUB u .xps ne smije upravljati cjevovodom

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 jest pošten rezultat ove pretvorbe
  finally
    Output.Free;
    Registry.Free;
  end;
end;

Sve oko pretvorbe budžetira se prije nego što se pokuša. THPDFDocumentHandlerOptions.Default ograničava arhivske unose na 10.000, proširene arhivske bajtove na 1 GiB, omjer sažimanja na 200, resurse na 4.096 i stranice na 10.000, i nosi neobavezan CancellationToken kako bi se posao na strani poslužitelja mogao zaustaviti usred paketa. Nakon toga pročitajte Info.UnsupportedFeatureCount i vrijednost različitu od nule tretirajte kao stvaran nalaz: HotPDF namjerno broji što nije mogao preslikati umjesto da nacrta približnu zamjenu i ostane tih o tome

Zašto XPS stranica treba matricu umjesto prepisanih koordinata?

Zato što prepisivanje koordinata gubi stog transformacija. XPS FixedPage specificiran je u jedinicama od 96 DPI s ishodištem gore lijevo i Y-osi koja raste prema dolje; PDF korisnički prostor ima 72 DPI s ishodištem dolje lijevo i Y-osi koja raste prema gore. Naivno rješenje jest pomnožiti svaki broj s 0.75 i oduzeti svaki Y od visine stranice pri ispisivanju. To djeluje za jedan ravan put i raspada se onog trenutka kad uđe RenderTransform, ugniježđeni Canvas ili matrica lokalna za četkicu, jer su te transformacije definirane u XPS prostoru, a vaše prepisivanje po koordinati već ga je napustilo. HotPDF zato drži projekciju kao matricu i komponira je. HPDFXPSPageMatrix vraća fiksne konstante jednom po stranici, HPDFMultiplyXPSMatrix spaja ih s nakupljenom transformacijom putanje, a rezultat se ispisuje kao jedan operator cm prije geometrije. Podaci putanje zatim se zapisuju nepromijenjenim XPS brojevima, i zato skraćena geometrijska sintaksa može dijeliti isti ograničeni parser korišten za podatke SVG putanje — samo vodeći token pravila ispune F0 ili F1 rukuje XPS adapter. Ako ste slijedili isto rasuđivanje kod uveza vektora EMF i WMF, oblik argumenta vam je poznat: formati uvoza pretvaraju se matricom, nikada aritmetikom nad koordinatama listova

HotPDF komponira fiksnu matricu XPS stranice s nakupljenom transformacijom putanje tako da XPS koordinatni sustav od 96 DPI s ishodištem gore lijevo dođe u PDF korisnički prostor od 72 DPI s ishodištem dolje lijevo, ispisano kao jedan operator cm po vizualu
Projekcija ostaje matrica i komponira se sa svakom ugniježđenom transformacijom, pa se podaci putanje mogu zapisati nepromijenjenim XPS brojevima
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
  Result.A :=  0.75;       // 96-DPI XPS jedinica u 72-DPI PDF točku
  Result.B :=  0;
  Result.C :=  0;
  Result.D := -0.75;       // XPS Y raste prema dolje, PDF Y raste prema gore
  Result.E :=  0;
  Result.F := PageHeight;  // Visina PDF stranice, u točkama
end;

// Jedna komponirana CTM po vizualu, ispisana prije svakog operatora putanje
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));

Kako ponovno koristiti VisualBrush bez dvostrukog iscrtavanja?

VisualBrush slika proizvoljno vizualno stablo — djecu Canvas, Path i Glyphs — u regiju, moguće ponovljeno preko nje. HotPDF taj vizual jednom kompajlira u PDF Form XObject i zatim ga postavlja, što je ista strategija resursa opisana u tekstu o uvozu SVG-a preko Form XObjecta. Dvije pojedinosti odlučuju hoće li to raditi. Prvo, vizual se mora prolaziti kao izravna XML djeca: ravno pretraživanje za elementima vrijednim popločavanja izvlači ugniježđene vizuale na najvišu razinu stranice i razara i opseg resursa i redoslijed slikanja. Drugo, sadržaj se hvata s već primijenjenom matricom stranice iz XPS-a u PDF, pa objavljivanje Form-a zahtijeva množenje inverzom te matrice, inače svako postavljanje ponovno primjenjuje razmjer 0.75 i okretanje Y-osi. Form također mora posjedovati svoje resurse: HotPDF kopira samo fontove, XObjecte, obrasce, ExtGStates i prostore boja na koje uhvaćeni sadržajni tok stvarno upućuje; kloniranje cijelog rječnika resursa stranice povuklo bi Form koji se registrira u njegov vlastiti graf resursa i izgradilo ciklus. Fontovi ostaju u izravnom rječniku na običnim stranicama i unose se u dijeljeni indirektni rječnik samo kada uhvaćeni sadržaj stvarno sadrži Tf, pa dokument bez višekratno upotrebljivih vizuala ne plaća taj stroj. Napomenite jednu granicu specifikacije vrijednu znanja prije prijave greške: ECMA-388 sekcija 13.4 zahtijeva da su i ViewboxUnits i ViewportUnits na VisualBrush Absolute, pa relativne jedinice nisu nedostajuća značajka — to je nesukladan ulaz, i HotPDF odbija izmišljati koordinatnu semantiku za njih

HotPDF jednom kompajlira XPS vizualno stablo VisualBrush u PDF Form XObject, objavljuje ga kroz inverz matrice fiksne stranice tako da postavljanja ne primjenjuju razmjer ponovno, i kopira samo resurse na koje uhvaćeni sadržaj stvarno upućuje
Sadržaj se hvata s već primijenjenom matricom stranice, pa se Form objavljuje kroz njezin inverz i nosi samo resurse na koje upućuje njegov vlastiti sadržajni tok

Popločavanje ImageBrush: četiri načina, četiri veličine ćelije

XPS načini popločavanja preslikavaju se na PDF obrasce popločavanja iz ISO 32000-1 sekcije 8.7.3 umjesto da se rašire u ponovljena postavljanja slike preko pokrivene površine, što izlaznu veličinu i vrijeme pretvorbe drži neovisnima o tome koliko stranice četkica pokriva. Preslikavanje je mehaničko jednom kad ga vidite: zrcaljenje se izražava stavljanjem zrcaljenih postavljanja unutar jedne ćelije obrasca i proširenjem ćelije da tome odgovara

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

Svako postavljanje nosi vlastiti pravokutnik za isjecanje, jer bi preslikavanje Viewbox koje prelazi svoju pod-ćeliju zakrvavilo u susjedno zrcaljenje. /Matrix obrasca je dio koji ljude iznenadi. Obrazac popločavanja sidri se u zadani korisnički prostor svog nadređenog sadržajnog toka, a ne u grafičko stanje važeće u trenutku odabira obrasca, pa matrica mora izričito komponirati sva tri sloja — projekciju fiksne stranice, transformaciju Path i Transform lokalnu za četkicu — umjesto da se oslanja na ambijentalnu CTM. HotPDF također provjerava prije alociranja: RegisterImageTilingPattern ograničava obrazac na 1.024 postavljanja i odbija degenerirane isjece, neinvertibilne matrice i nevažeće indekse slika. Ako želite opći model s PDF strane iza ovoga, tekst o obrascima popločavanja i Pattern prostoru boja pokriva temeljne operatore

// Projekcija fiksne stranice savijena u matricu obrasca, zatim 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);

Što se dogodi kada radijalni gradijent nije krug?

XPS definira RadialGradientBrush s GradientOrigin, Center, RadiusX i RadiusY, pa je četkica elipsa. PDF sjenčanje tipa 3, u ISO 32000-1 sekciji 8.7.4.5.4, stapa dva kruga i nema načina da izravno izrazi elipsu. Prosječivanje dva radijusa u jedan broj zamamni je prečac i vidljivo je pogrešan na svakoj četkici koja nije blizu okrugle. HotPDF umjesto toga seli problem u koordinatni sustav: razmjeri Y s RadiusY / RadiusX, registrira pošteno kružno sjenčanje u tom razmjeriranom prostoru, odabere obrazac i odmah ispisuje recipročni razmjer tako da geometrija putanje zapisana dalje ostane u izvornom 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 upravo ovdje
if Abs(ScaleY - 1) > 0.000001 then
  Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);

Redoslijed u tom isječku cijela je trik, i nije stilski. PDF obrazac sjenčanja hvata trenutnu matricu transformacije u trenutku kada je odabran kao trenutna boja, pa privremeni razmjer mora biti ispisan prije SetFillPattern ili SetStrokePattern, a recipročni mora slijediti odabir ali prethoditi operatorima putanje. Pokvarite redoslijed u bilo kojem smjeru i imate gradijent koji se ispravno renderira na prvoj putanji a odmiče na svakoj sljedećoj. Srodno ograničenje vrijedi za način relativnih koordinata: RadiusX i RadiusY moraju se razriješiti naspram širine i visine putanje odvojeno, jer razmjerivanje oboje jednom duljinom bridova tiho mijenja omjer slike elipse na svakoj ne-kvadratnoj putanji

HotPDF preslikava eliptični XPS RadialGradientBrush na PDF sjenčanje tipa 3 razmjerivanjem Y-osi, registriranjem kružnog sjenčanja u razmjeriranom prostoru i ispisivanjem recipročnog razmjera tek nakon što je obrazac hvatio trenutnu matricu transformacije
Elipsu upija koordinatni sustav, a ne sjenčanje, i privremeni razmjer mora oklopiti odabir obrasca baš tim redoslijedom

Gdje je pretvorba iskrena prema svojim granicama

Neke XPS konstrukcije pretvaraju se približno, a neke se uopće ne pretvaraju, i dizajnerski izbor kroz sve jest brojati ih umjesto lažirati. TIFF i JPEG XR dijelovi rasteriziraju se kroz WIC i ne nose obećanje o očuvanom alfa kanalu, dok se PNG s valjanim alfa kanalom dijeli na osnovnu sliku plus /SMask. Unutarnja veličina slike izvodi se kao pixel * 96 / DPI, čitajući prvo PNG pHYs ili JPEG JFIF gustoću s padom na 96 DPI, pa loše zaglavlje gustoće završi na predvidivoj veličini umjesto na proizvoljnoj. Neriješeni resursi matrica, nestandardne relativne transformacije, ColorConvertedBitmap, nepodržani načini širenja gradijenta i neispravna geometrija svi povećavaju UnsupportedFeatureCount, i neispravan ulaz propada zatvoreno umjesto da se degradira u tiho drugačiji crtež

To je koristan stav za arhivski pretvarač: pretvorba koja tiho aproksimira gora je od one koja vam kaže koje četiri elemente nije mogla predstaviti, jer samo druga daje nešto za provjeriti prije nego što se dokument zapečati u sustav evidencije. Ako procjenjujete pretvorbu XPS i OpenXPS uz ostatak cjevovoda dokumenata — kompoziciju stranica, fontove, potpisivanje, PDF/A izlaz — stranica HotPDF Delphi PDF komponente navodi potpuni skup značajki i podržane verzije Delphija i C++Buildera