HotPDF konvertuje balíky XPS a OpenXPS do PDF vnútri Delphi a C++Builder bez tlačového ovládača, mapujúc každú súradnicu fixed-page 96-DPI cez jednu stranovú maticu s prevrátením Y a mierkou 0,75, publikujúc každý VisualBrush ako zdieľaný Form XObject a meniac tile režimy ImageBrush na natívne dlaždicové patterny PDF namiesto opakovaného kreslenia obrázkov
Scenár, ktorý ťahá väčšinu Windows dielní sem, je nudný a nevyhnutný. Niečo už tlačí do Microsoft XPS Document Writer — staršia správa ERP, podpísaný formulár, dávka výpisov — a archivačná politika hovorí PDF. XPS je úplne dobrý formát záchytu a hrozný na podanie systému evidencie o dekádu neskôr. Takže spool súbor sa musí stať PDF stranu za stranou a v momente, keď začnete písať ten konvertor, objavíte, že zaujímavá časť nie je XML. Je to, že XPS a PDF nesúhlasia o tom, kde je počiatok, čo znamená jednotka a čo môže byť brush
Z balíka do PDF jedným prechodom
Vstupný bod je registr handlerov dokumentov, nie špeciálna trieda XPS. THPDFDocumentHandlerRegistry.RegisterStandardHandlers inštaluje handlery XPS, EPUB a CBZ; rozpoznanie je obsahové, takže balík nesúci [Content_Types].xml plus aspoň jednu časť .fpage skóruje 95 aj keď prípona súboru klame, kým holá prípona .xps alebo .oxps skóruje len 10. To poradie záleží, keď prijímate nahrávania, lebo útočníkovi, ktorý premenoval EPUB na .xps, nesmie prejsť riadenie pipeline
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 úprimné skóre tejto konverzie
finally
Output.Free;
Registry.Free;
end;
end;
Všetko okolo konverzie má rozpočet skôr, než sa pokúsi. THPDFDocumentHandlerOptions.Default stropuje archívne položky na 10 000, roztiahnuté archívne bajty na 1 GiB, kompresný pomer na 200, zdroje na 4 096 a strany na 10 000 a nesie voliteľný CancellationToken, takže serverovú úlohu možno zastaviť v polovici balíka. Potom si prečítajte Info.UnsupportedFeatureCount a zaobchádzajte s nenulovou hodnotou ako so skutočným nálezom: HotPDF zámerne počíta to, čo nemohol namapovať, namiesto kreslenia aproximácie a ticha o tom
Prečo potrebuje XPS strana maticu namiesto prepísaných súradníc?
Pretože prepísanie súradníc stráca zásobník transformácií. XPS FixedPage je špecifikovaný v jednotkách 96-DPI s počiatkom vľavo hore a Y rastie nadol; PDF user space je 72-DPI s počiatkom vľavo dole a Y rastie nahor. Naivná oprava je vynásobiť každé číslo 0,75 a odpočítať každé Y od výšky strany pri vysypaní. To funguje na jednu plochú cestu a rozpadne sa v momente, keď vstúpi RenderTransform, vnorený Canvas alebo brush-local matica, lebo tie transformácie sú definované v priestore XPS a vaše prepisovanie na súradnicu už z neho odišlo. HotPDF preto drží projekciu ako maticu a komponuje ju. HPDFXPSPageMatrix vracia pevné konštanty raz na stranu, HPDFMultiplyXPSMatrix ju zreťazí s akumulovanou transformáciou cesty a výsledok sa vysype ako jediný operátor cm pred geometriou. Cestové dáta sa potom zapisujú v nezmenených číslach XPS, čo je aj dôvod, prečo skrátená geometrická syntax môže zdieľať ten istý ohraničený parser používaný pre dáta SVG path — len úvodný token fill-rule F0 alebo F1 obsluhuje adaptér XPS. Ak ste sledovali rovnaké uvažovanie pri importoch vektorov EMF a WMF, tvar argumentu je známy: importné formáty sa konvertujú maticou, nikdy aritmetikou na listových súradniciach
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // jednotka XPS 96-DPI na bod PDF 72-DPI
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // Y XPS rastie nadol, Y PDF nahor
Result.E := 0;
Result.F := PageHeight; // výška strany PDF v bodoch
end;
// jedna komponovaná CTM na visual, vysypaná pred akýmkoľvek operátorom cesty
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Ako znovu použiť VisualBrush bez dvojitého kreslenia?
VisualBrush natiera ľubovoľný vizuálny strom — deti Canvas, Path a Glyphs — do regiónu, možno opakovane cez neho. HotPDF skompiluje ten vizuál raz do PDF Form XObject a potom ho umiestňuje, čo je tá istá zdrojová stratégia popísaná v článku import SVG cez Form XObjects. Dva detaily rozhodujú, či to funguje. Po prvé, vizuál sa musí prechádzať ako priame deti XML: plochý scan na prvky hodené do dlaždice vytiahne vnorené vizuály na vrchol strany a zničí obeh zdrojov aj poradie natierania. Po druhé, obsah sa zachytáva s aplikovanou stranovou maticou XPS-to-PDF, takže publikovanie Formu vyžaduje násobenie inverzom tej matice, inak každé umiestnenie znovu aplikuje mierku 0,75 a prevrátenie Y. Form musí tiež vlastniť svoje zdroje: HotPDF kopíruje len fonty, XObjects, patterny, ExtGStates a farebné priestory, na ktoré zachytený obsahový stream reálne odkazuje; klonovanie celého zdrojového slovníka strany by vtiahlo registrovaný Form do jeho vlastného zdrojového grafu a postavilo slučku. Fonty zostávajú v priamom slovníku na obyčajných stranách a povýšia sa na zdieľaný nepriamy slovník len keď zachytený obsah reálne obsahuje Tf, takže dokument bez znovupoužiteľných vizuálov neplatí za mechanizmus. Všimnite si jednu hranicu špecifikácie, ktorú stojí za poznanie skôr, než podáte bug: ECMA-388 sekcia 13.4 vyžaduje na VisualBrush obe ViewboxUnits aj ViewportUnits ako Absolute, takže relatívne jednotky nie sú chýbajúca funkcia — sú to nezlučiteľné vstupy a HotPDF odmieta vymýšľať sémantiku súradníc pre ne
Dlaždicovanie ImageBrush: štyri režimy, štyri veľkosti buniek
Tile režimy XPS sa mapujú na dlaždicové patterny PDF z ISO 32000-1 sekcie 8.7.3 namiesto rozťahovania do opakovaných umiestnení obrázkov cez pokrytú plochu, čo drží veľkosť výstupu a čas konverzie nezávislé od toho, koľko strany brush pokrýva. Mapovanie je mechanické, keď ho uvidíte: odraz sa vyjadruje vložením zrkadlených umiestnení do jednej bunky patternu a zväčšením bunky, aby sedela
Tile— jedno umiestnenie, bunka zostáva 1×1 viewportFlipX— dve umiestnenia, bunka rozšírená na 2×1FlipY— dve umiestnenia, bunka zdvihnutá na 1×2FlipXY— štyri umiestnenia, bunka rozšírená na 2×2
Každé umiestnenie nesie vlastný obdĺžnik výrezu, lebo mapovanie Viewbox, ktoré prekročí svoju sub-bunku, by krvácalo do susedného odrazu. /Matrix patternu je časť, ktorá chytá ľudí. Dlaždicový pattern je ukotvený do predvoleného user space rodičovského obsahového streamu, nie do grafického stavu aktuálneho pri výbere patternu, takže matica musí explicitne skomponovať všetky tri vrstvy — fixed-page projekciu, transformáciu Path a brush-local Transform — namiesto spoliehania sa na okolitú CTM. HotPDF tiež validuje pred alokáciou: RegisterImageTilingPattern limituje pattern na 1 024 umiestnení a zamieta degenerované výrezy, neinvertovateľné matice a neplatné indexy obrázkov. Ak chcete všeobecný model na strane PDF za tým, dlaždicové patterny a farebný priestor Pattern pokrýva podkladové operátory
// fixed-page projekcia zložená do matice patternu, potom brush-local
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);
Čo sa stane, keď radiálny gradient nie je kruh?
XPS definuje RadialGradientBrush s GradientOrigin, Center, RadiusX a RadiusY, takže brush je elipsa. PDF tienenie typu 3, v ISO 32000-1 sekcii 8.7.4.5.4, mieša medzi dvomi kruhmi a nemá spôsob vyjadriť elipsu priamo. Priemernovať obe polomery do jedného čísla je lákavá skratka a viditeľne zlá na každom brush, ktorý nie je blízko kruhu. HotPDF namiesto toho presúva problém do súradnicového systému: škáluje Y o RadiusY / RadiusX, registruje úprimné kruhové tienenie v tom škálovanom priestore, vyberie pattern a bezodkladne vysype recipročnú mierku, takže geometria cesty zapísaná ďalej je stále v pôvodnom user space 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); // pattern zachytáva CTM presne tu
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
Poradie v tom útržku je celý trik a nie je štylistické. PDF tienenie pattern zachytáva aktuálnu transformačnú maticu v momente, keď je vybraný ako aktuálna farba, takže dočasná mierka sa musí vysypať pred SetFillPattern alebo SetStrokePattern a reciprocita musí nasledovať po výbere, ale pred operátormi cesty. Zobrať poradie zle ktorýmkoľvek smerom znamená gradient, ktorý sa vykreslí správne na prvej ceste a driftuje na každej nasledujúcej. Súvisiace obmedzenie platí pre režim relatívnych súradníc: RadiusX a RadiusY sa musia vyriešiť proti šírke a výške cesty oddelene, keďže škálovanie oboch jedinou dĺžkou hrany poticho mení pomer strán elipsy na každej neštvorcovej ceste
Kde je konverzia úprimná k vlastným limitom
Niektoré konštrukty XPS sa konvertujú približne a niektoré sa nekonvertujú vôbec a návrhová voľba po celý čas ich počíta namiesto falšovania. Časti TIFF a JPEG XR sa rasterizujú cez WIC a nenosia sľub o zachovanej alfa, kým PNG s platným alfa kanálom sa rozdelí na základný obrázok plus /SMask. Vnútorná veľkosť obrázku sa odvodzuje ako pixel * 96 / DPI, čítajúc najprv PNG pHYs alebo hustotu JPEG JFIF a padajúc späť na 96 DPI, takže zlá hlavička hustoty dopadne na predvídateľnú veľkosť namiesto ľubovoľnej. Nevyriešené zdroje matíc, neštandardné relatívne transformácie, ColorConvertedBitmap, nepodporované režimy rozptylu gradientu a znetvorená geometria všetky inkrementujú UnsupportedFeatureCount a znetvorený vstup zlyhá bezpečne namiesto degradácie do poticho iného kreslenia
To je užitočné postavenie pre archívny konvertor: konverzia, ktorá poticho približuje, je horšia než tá, ktorá vám povie, ktoré štyri elementy nemohla zastúpiť, lebo len druhá vám dá niečo na skontrolovanie skôr, než sa dokument zapečatí do systému evidencie. Ak vyhodnocujete konverziu XPS a OpenXPS po boku zvyšku dokumentového pipeline — kompozícia strán, fonty, podpisovanie, výstup PDF/A — stránka komponentu HotPDF Delphi PDF vymenúva kompletnú sadu funkcií a podporované verzie Delphi a C++Builder