A HotPDF XPS és OpenXPS csomagokat konvertál PDF-be Delphi és C++Builder belsejében nyomtatási meghajtó nélkül, minden 96 DPI-s fixoldal-koordinátát egyetlen 0,75-ös léptékű, Y-t tükröző oldalmátrixon képez le, minden VisualBrush-t megosztott Form XObjectként publikál, és az ImageBrush csemmódokat natív PDF csempézési mintákká alakítja ismételt képrajzolások helyett
A forgatókönyv, amely a legtöbb Windows házat ebbe rántja, unalmas és elkerülhetetlen. Valami már nyomtat a Microsoft XPS Document Writerre — egy legacy ERP jelentés, egy aláírt űrlap, egy köteg kimutatás —, és az archívumszabályzat PDF-et mond. Az XPS kiváló rögzítési formátum, és rettenetes választás egy nyilvántartó rendszernek, amelynek egy évtized múlva kell átadni. Tehát a spool fájlnak oldalanként pontos PDF-e kell válnia, és amint nekilát a konvertálónak, kiderül, hogy az érdekes rész nem az XML: az, hogy az XPS és a PDF nem ért egyet abban, hol az origó, mennyit ér egy egység, és mi lehet egy ecset
Csomagtól PDF-ig egyetlen átmenésben
A belépőpont a dokumentumkezelő nyilvántartás, nem egy különleges XPS osztály. A THPDFDocumentHandlerRegistry.RegisterStandardHandlers telepíti az XPS, EPUB és CBZ kezelőket; a felismerés tartalomalapú, így egy [Content_Types].xml-t és legalább egy .fpage részt viselő csomag 95 pontot ér akkor is, amikor a fájlkiterjesztés hazudik, míg egy puszta .xps vagy .oxps kiterjesztés csak 10 pontot ér. Ez a sorrend akkor számít, amikor feltöltéseket fogad, mert egy EPUB-ot .xps-re átnevező támadónak nem szabad a pipeline-t irányítania
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);
// Az Info.UnsupportedFeatureCount az őszinte pontszám ehhez a konverzióhoz
finally
Output.Free;
Registry.Free;
end;
end;
A konverzió minden eleme keretezve van, mielőtt megkísérelnék. A THPDFDocumentHandlerOptions.Default az archívbejegyzéseket 10 000-en, a kibontott archívbájtokat 1 GiB-en, a tömörítési arányt 200-on, az erőforrásokat 4 096-on és az oldalakat 10 000-en plafonozza, és opcionális CancellationToken-t visel, hogy egy kiszolgáló oldali feladat a csomag fele közben leállítható legyen. Utána olvassa az Info.UnsupportedFeatureCount-ot, és a nem nulla értéket valódi leletként kezelje: a HotPDF szándékosan megszámolja, amit nem tudott leképezni, ahelyett hogy közelítést rajzolna és elhallgatná
Miért kér egy XPS oldal mátrixot átírt koordináták helyett?
Mert a koordináták átírása elveszíti a transzformációvermet. Egy XPS FixedPage 96 DPI-s egységekben van megadva, az origó a bal felső sarokban és az Y lefelé nő; a PDF felhasználói tere 72 DPI-s, az origó a bal alsó sarokban és az Y felfelé nő. A naiv javítás minden számot 0,75-tel szoroz, és minden Y-t kivon az oldal magasságából kibocsátáskor. Ez egyetlen lapos útvonalon működik, és abban a pillanatban omlik össze, amikor egy RenderTransform, egy beágyazott Canvas vagy egy ecset-lokális mátrix belép, mert azok a transzformációk XPS térben vannak definiálva, az Ön koordinátánkénti átírása pedig már elhagyta azt. A HotPDF ezért a vetítést mátrixként tartja és komponálja. A HPDFXPSPageMatrix oldalanként egyszer adja vissza a rögzített konstansokat, a HPDFMultiplyXPSMatrix a felgyülemlett útvonaltranszformációval fűzi össze, az eredmény pedig egyetlen cm operátorként kerül ki a geometria elé. Az útvonaladat ezután módosítatlan XPS számokban íródik, ami az oka annak is, hogy a rövidített geometriaszintaxis megoszthatja az SVG útvonaladathoz használt határolt parseolt — csak a vezető F0 vagy F1 kitöltési szabály tokent kezeli az XPS adapter. Ha ugyanezt a gondolkodást már követte a EMF és WMF vektorimportálásnál, az érvelés alakja ismerős: az importformátumokat mátrix útján konvertálják, sosem levélkoordinátákon végzett számtannal
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // 96 DPI-s XPS egység 72 DPI-s PDF ponttá
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // az XPS Y lefelé nő, a PDF Y felfelé
Result.E := 0;
Result.F := PageHeight; // PDF oldalmagasság, pontokban
end;
// Vizuálonként egy komponált CTM, bármely útvonaloperátor előtt kibocsátva
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Hogyan hasznosítson újra egy VisualBrush-t kétszeres rajzolás nélkül?
Egy VisualBrush tetszőleges vizuális fát fest egy régióba — Canvas, Path és Glyphs gyerekeket —, esetleg ismételve azt. A HotPDF azt a vizuált egyszer lefordítja PDF Form XObjectté, majd elhelyezi, ami ugyanaz az erőforrás-stratégia, amelyet a SVG import Form XObjecteken keresztül cikk ír le. Két részlet dönti el, hogy működik-e. Először: a vizuált közvetlen XML gyerekekként kell bejárni: a csemmétehető elemekre irányuló lapos szkennelés a beágyazott vizuálokat az oldal legfelső szintjére húzza, és elpusztítja mind az erőforrás-hatókörölést, mind a festési sorrendet. Másodszor: a tartalom akkor rögzül, amikor az XPS-ből PDF-be menő oldalmátrix már alkalmazva van, így a Form publikálásához a mátrix inverzével kell szorozni, különben minden elhelyezés újraalkalmazza a 0,75-ös léptéket és az Y-tükrözést. A Formnak a saját erőforrásai is birtokolnia kell: a HotPDF csak azokat a betűtípusokat, XObjecteket, mintákat, ExtGState-eket és színtereket másolja, amelyekre a rögzített tartalomstream ténylegesen hivatkozik; a teljes oldal-erőforrásszótár klónozása a regisztrálandó Formot a saját erőforrásgráfjába húzná, és kört építene. A betűtípusok közvetlen szótárban maradnak a közönséges oldalakon, és csak akkor lépnek megosztott közvetett szótárba, amikor a rögzített tartalom valóban tartalmaz Tf-et, így egy újrahasznosítható vizuálok nélküli dokumentum nem fizet a gépezetért. Jegyezzen meg egy specifikációhatárt, mielőtt hibát jelentene: az ECMA-388 13.4 szakasza megköveteli, hogy egy VisualBrush ViewboxUnits és ViewportUnits beállítása egyaránt Absolute legyen, tehát a relatív egységek nem hiányzó funkciók — nem megfelelő bemenetek, és a HotPDF nem hajlandó koordinátaszemantikát kitalálni nekik
ImageBrush csempézés: négy mód, négy cellaméret
Az XPS csemmódok az ISO 32000-1 8.7.3 szakaszának PDF csempézési mintáira képződnek, ahelyett hogy a lefedett területen ismételt képelhelyezésekké bővítnék, ami a kimenet méretét és a konverzió idejét a kefe lefedésétől függetleníti. A leképezés mechanikus, ha egyszer látja: a visszaverődés úgy fejeződik ki, hogy a tükrözött elhelyezéseket egyetlen mintacellán belül helyezik el, és a cellát az egyezéshez megnagyobbítják
Tile— egy elhelyezés, a cella 1×1 viewport maradFlipX— két elhelyezés, a cella 2×1-re szélesedikFlipY— két elhelyezés, a cella 1×2-re magasodikFlipXY— négy elhelyezés, a cella 2×2-re bővül
Minden elhelyezés a saját vágótéglalapját viseli, mert egy alcelláján túlcsorduló Viewbox leképezés a szomszédos visszaverődésbe vérezne bele. A minta /Matrix-a az a rész, amely elkapja az embereket. A csempézési minta a szülő tartalomstream alapértelmezett felhasználói teréhez horgonyzik, nem ahhoz a grafikai állapothoz, amely a minta kiválasztásakor aktuális, így a mátrixnak mindhárom réteget explicit módon kell komponálnia — a fixoldal-vetítést, a Path transzformációt és az ecset-lokális Transform-ot —, ahelyett hogy egy környezeti CTM-re támaszkodna. A HotPDF emellett allokálás előtt validál: a RegisterImageTilingPattern egy mintát 1 024 elhelyezésre korlátoz, és elutasítja a degenerált vágásokat, a nem invertálható mátrixokat és az érvénytelen képindexeket. Ha a mögöttes általános PDF oldali modell érdekli, a csempézési minták és a Pattern színtér cikk lefedi az alapoperátorokat
// A fixoldal-vetítés belegyúrve a mintamátrixba, majd az ecset-lokális
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);
Mi történik, amikor egy radiális átmenet nem kör?
Az XPS RadialGradientBrush-t definiál GradientOrigin, Center, RadiusX és RadiusY értékekkel, így a kefe ellipszis. A PDF 3-as árnyalástípus, az ISO 32000-1 8.7.4.5.4 szakaszában, két kör között kever, és nincs módja az ellipszis közvetlen kifejezésére. A két sugár egyetlen számba átlagolása a kísérteties rövidút, és minden, a körtől messze álló kefén láthatóan rossz. A HotPDF ehelyett a problémát a koordinátarendszerbe teszi: Y-t RadiusY / RadiusX-tel skálázza, egy őszinte körkörös árnyalást regisztrál abban a skálázott térben, kiválasztja a mintát, és azonnal kibocsátja a reciprokváltozót, hogy a következőnek írt útvonalgeometria továbbra is az eredeti XPS felhasználói térben legyen
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); // a minta pontosan itt rögzíti a CTM-et
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
A sorrend abban a töredékben az egész trükk, és nem stiláris. Egy PDF árnyalási minta abban a pillanatban rögzíti az aktuális transzformációs mátrixot, amikor aktuális színként kiválasztják, tehát az ideiglenes skálát a SetFillPattern vagy a SetStrokePattern előtt kell kibocsátani, a reciprokot pedig a kiválasztás után, de az útvonaloperátorok előtt kell követnie. Kapja el rosszul a sorrendet bármely irányban, és olyan átmenete lesz, amely az első útvonalon helyesen renderel, és minden továbbin sodródik. Egy rokon megszorítás a relatív koordinátamódra vonatkozik: a RadiusX-et és a RadiusY-et külön-külön kell az útvonal szélességére és magasságára feloldani, mert mindkettő egyetlen élhosszal való skálázása csendesen megváltoztatja az ellipszis képarányát bármely nem négyzetes útvonalon
Ahol a konverzió őszinte a korlátai felől
Néhány XPS konstrukció közelítőleg konvertálódik, néhány pedig egyáltalán nem, és a tervezési döntés végig az, hogy megszámolják őket, nem pedig elhitetik. A TIFF és JPEG XR részek WIC-en keresztül raszterizálódnak, és nem hordoznak ígéretet a megőrzött alfáról, míg az érvényes alfa-csatornás PNG alapkép plusz /SMask párosra bomlik. A kép belső mérete pixel * 96 / DPI képlettel származik, előbb a PNG pHYs-t vagy a JPEG JFIF sűrűséget olvassa, és 96 DPI-re esik vissza, így egy rossz sűrűség-fejléc kiszámítható méretnél landol, nem tetszőlegesnél. A feloldatlan mátrix-erőforrások, a nem szabványos relatív transzformációk, a ColorConvertedBitmap, a nem támogatott átmenet-szórási módok és a hibás geometria mind növelik az UnsupportedFeatureCount-ot, és a hibás bemenet zárt módon bukik meg, ahelyett hogy csendesen eltérő rajzolattá silányulna
Ez a hasznos tartás egy archiváló konvertálóhoz: egy konverzió, amely csendesen közelít, rosszabb annál, amelyik megmondja, mely négy elemet nem tudta ábrázolni, mert csak a második ad ellenőriznivalót, mielőtt a dokumentum nyilvántartó rendszerbe pecsételődik. Ha az XPS és OpenXPS konverziót a dokumentum-pipeline többi része — oldal-komponálás, betűtípusok, aláírás, PDF/A kimenet — mellett értékeli, a HotPDF Delphi PDF component lap felsorolja a teljes funkciókészletet és a támogatott Delphi és C++Builder verziókat