HotPDF konvertuoja XPS ir OpenXPS paketus į PDF Delphi ir C++Builder viduje be spausdinimo tvarkyklės: kiekvieną 96 DPI fiksuoto puslapio koordinatę perkelia per vieną 0,75 skalės, Y apverčiančią puslapio matricą, kiekvieną VisualBrush publikuoja kaip bendrą Form XObject, o ImageBrush plytelių režimus paverčia natyviais PDF tiling raštais vietoj pakartotinių vaizdų braižymų
Scenarijus, į kurį įtraukia daugumą Windows dirbtuvių, nuobodus ir neišvengiamas. Kažkas jau spausdina į Microsoft XPS Document Writer — senas ERP ataskaita, pasirašyta forma, krūva išrašų — o archyvo politika sako PDF. XPS yra visiškai tinkamas fiksavimo formatas ir siaubingas tas, kurį po dešimties metų atiduosite archyvų sistemai. Taigi spool failas turi tapti puslapis į puslapį atitinkančiu PDF, ir vos pradėjus rašyti tą konverterį paaiškėja, kad įdomioji dalis nėra XML. Tai kad XPS ir PDF nesutaria, kur koordinatės pradžia, kiek vertas vienetas ir kuo leidžiama būti teptukui
Iš paketo į PDF vienu pravažiavimu
Įėjimo taškas yra dokumentų tvarkytojų registras, o ne speciali XPS klasė. THPDFDocumentHandlerRegistry.RegisterStandardHandlers įdiegia XPS, EPUB ir CBZ tvarkytojus; atpažinimas grindžiamas turiniu, tad paketas su [Content_Types].xml ir bent viena .fpage dalimi surenka 95 net tada, kai failo plėtinys meluoja, o vien plėtinys .xps ar .oxps surenka tik 10. Ta tvarka svarbi, kai priimate įkėlimus, nes puolėjas, pervadinęs EPUB į .xps, neturi valdyti konvejerio
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 yra sąžiningas šios konversijos balas
finally
Output.Free;
Registry.Free;
end;
end;
Viskas apie konversiją apibrėžta biudžetais dar prieš bandant. THPDFDocumentHandlerOptions.Default lubas archyvo įrašams nustato 10 000, išskleistų archyvo baitų — 1 GiB, glaudinimo santykiui — 200, ištekliams — 4 096, puslapiams — 10 000, ir neša pasirinktiną CancellationToken, kad serverio užduotį būtų galima sustabdyti paketo viduryje. Po to perskaitykite Info.UnsupportedFeatureCount ir ne nulio reikšmę laikykite tikru radiniu: HotPDF sąmoningai suskaičiuoja, ko nepajėgė atvaizduoti, užuot braižęs apytikslę ir apie tai nutilėjęs
Kodėl XPS puslapiui reikia matricos, o ne perrašytų koordinačių?
Nes koordinačių perrašymas praranda transformacijų krūvą. XPS FixedPage apibrėžtas 96 DPI vienetais su koordinačių pradžia viršuje kairėje ir Y augančiu žemyn; PDF naudotojo erdvė yra 72 DPI su pradžia apačioje kairėje ir Y augančiu aukštyn. Naivus taisymas — kiekvieną skaičių padauginti iš 0,75 ir išduodant kiekvieną Y atimti iš puslapio aukščio. Tai veikia vienam plokščiam kontūrui ir subyra akimirką, kai įeina RenderTransform, įdėtas Canvas arba teptukui savoji matrica, nes tos transformacijos apibrėžtos XPS erdvėje, o jūsų koordinatėms skirtas perrašymas jos jau paliko. HotPDF todėl projekciją laiko matrica ir ją sudeda. HPDFXPSPageMatrix grąžina fiksuotąsias konstantas po kartą puslapiui, HPDFMultiplyXPSMatrix jos sukabina su sukauptąja kontūro transformacija, o rezultatas išduodamas kaip vienas cm operatorius prieš geometriją. Kontūro duomenys paskui rašomi nepakeistais XPS skaičiais — todėl ir sutrumpintoji geometrijos sintaksė gali dalintis ta pačia ribota analizatore, kuri naudojama SVG kontūro duomenims; XPS adapteris apdoroja tik pirmąjį F0 ar F1 užpildymo taisyklės tokeną. Jei ta pati logika jau sekėte EMF ir WMF vektorių importe, argumento forma pažįstama: importiniai formatai konvertuojami matrica, niekada aritmetika ant lapinių koordinačių
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // 96 DPI XPS vienetas į 72 DPI PDF tašką
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // XPS Y auga žemyn, PDF Y auga aukštyn
Result.E := 0;
Result.F := PageHeight; // PDF puslapio aukštis taškais
end;
// Vienas sudėtas CTM kiekvienam vizualui, išduodamas prieš bet kurį kontūro operatorių
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Kaip pakartotinai panaudoti VisualBrush, nebraižant jo du kartus?
VisualBrush nudaiko savavališką vizualų medį — Canvas, Path ir Glyphs vaikus — į sritį, galbūt pakartotinai per visą ją. HotPDF tą vizualą sukonkompiliuoja vieną kartą į PDF Form XObject ir tada jį išdėsto — tai ta pati išteklių strategija, aprašyta SVG importas per Form XObjects. Dvi detalės lemia, ar tai veikia. Pirma, vizualą reikia eiti kaip tiesioginius XML vaikus: plokščias žvalgymas po plytelėmis vertus elementus ištrauktų įdėtus vizualus iki puslapio viršaus ir sunaikintų ir išteklių taikymo sritis, ir dažymo tvarką. Antra, turinys užfiksuojamas jau pritaikius XPS į PDF puslapio matricą, tad Form publikavimas reikalauja daugybos iš atvirkštinės tos matricos, kitaip kiekvienas išdėstymas pakartotinai pritaikytų 0,75 skalę ir Y apvertimą. Form taip pat turi turėti savus išteklius: HotPDF kopijuoja tik šriftus, XObjects, raštus, ExtGStates ir spalvų erdves, kurių užfiksuotas turinio srautas tikrai mini; viso puslapio išteklių žodyno klonavimas įtrauktų registruojamą Form į jo paties išteklių grafą ir sukurtų ciklą. Šriftai lieka tiesioginiame žodyne paprastuose puslapiuose ir pakeliami į bendrą netiesioginį žodyną tik tada, kai užfiksuotas turinys tikrai turi Tf, tad dokumentas be pakartotinai naudojamų vizualų už mechanizmą nemoka. Pastebėkite vieną specifikacijos ribą, vertą žinoti prieš rašant klaidos pranešimą: ECMA-388 13.4 skirsnis reikalauja, kad ir ViewboxUnits, ir ViewportUnits ant VisualBrush būtų Absolute, tad santykiniai vienetai nėra trūkstama galimybė — tai neatitinkanti įvestis, ir HotPDF atsisako jai išgalvoti koordinačių semantiką
ImageBrush plytelės: keturi režimai, keturi langelio dydžiai
XPS plytelių režimai atvaizduojami į PDF tiling raštus iš ISO 32000-1 8.7.3 skirsnio, užuot būtų išskleisti į pakartotinius vaizdų išdėstymus per dengiamą sritį, — tai laiko išvesties dydį ir konversijos laiką nepriklausomais nuo to, kiek puslapio dengia teptukas. Atvaizdavimas mechaninis, tik jį pamačius: atspindys išreiškiamas sudėjus veidrodinius išdėstymus į vieno rašto langelį ir padidinus langelį, kad atitiktų
Tile— vienas išdėstymas, langelis lieka 1×1 vaizdo langasFlipX— du išdėstymai, langelis pratęstas iki 2×1FlipY— du išdėstymai, langelis paaukštintas iki 1×2FlipXY— keturi išdėstymai, langelis išplėstas iki 2×2
Kiekvienas išdėstymas neša savą iškirpimo stačiakampį, nes Viewbox atvaizdavimas, išlindęs už savo sub langelio, nutekėtų į gretimą atspindį. Rašto /Matrix — dalis, kuri pagauna žmones. Tiling raštas inkaruojamas prie savo tėvinio turinio srauto numatytosios naudotojo erdvės, o ne prie grafikos būsenos, buvusios dabartine renkant raštą, tad matrica turi aiškiai sudėti visus tris sluoksnius — fiksuoto puslapio projekciją, Path transformaciją ir teptukui savąją Transform — vietoj pasitikėjimo aplinkiniu CTM. HotPDF taip pat tikrina prieš skirdamas: RegisterImageTilingPattern riboja raštą iki 1 024 išdėstymų ir atmeta degradavusius iškirpimus, neapsukamas matricas ir neteisingus vaizdų indeksus. Jei norite bendrojo PDF pusės modelio už to, tiling raštai ir Pattern spalvų erdvė dengia po juo esančius operatorius
// Fiksuoto puslapio projekcija įvyniota į rašto matricą, paskui teptuko savoji
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);
Kas nutinka, kai spindulinis gradientas nėra apskritimas?
XPS apibrėžia RadialGradientBrush su GradientOrigin, Center, RadiusX ir RadiusY, tad teptukas yra elipsė. PDF atspalvio tipas 3, ISO 32000-1 8.7.4.5.4 skirsnyje, maišo tarp dviejų apskritimų ir neturi būdo tiesiogiai išreikšti elipsės. Abiejų spindulių suvidurkinimas į vieną skaičių — viliojantis trumpasis kelias, ir jis aiškiai klaidingas bet kuriame teptuke, toli nuo apvalaus. HotPDF vietoj to perkelia problemą į koordinačių sistemą: suskaluoja Y dydžiu RadiusY / RadiusX, užregistruoja sąžiningą apskritą atspalvį toje suskaluotoje erdvėje, parenka raštą ir iškart išduoda atvirkštinę skalę, tad toliau rašoma kontūro geometrija vis dar yra originalioje XPS naudotojo erdvėje
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); // raštas čia pat užfiksuoja CTM
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
Tvarka tame fragmente yra visas triukas, ir tai nėra stilistika. PDF atspalvio raštas užfiksuoja dabartinę transformacijų matricą akimirką, kai išrenkamas dabartine spalva, tad laikinoji skalė turi būti išduota prieš SetFillPattern ar SetStrokePattern, o atvirkštinė — sekti po pasirinkimo, bet prieš kontūro operatorius. Sudėkite tvarką neteisingai bet kuria kryptimi, ir gausite gradientą, teisingai atvaizduojamą pirmame kontūre ir nukrystantį kiekviename sekančiame. Susijusi sąlyga galioja santykinio koordinačių režimo: RadiusX ir RadiusY turi būti išspręsti atskirai pagal kontūro plotį ir aukštį, nes abiejų skalavimas vienu kraštinės ilgiu nepastebimai keičia elipsės proporciją bet kuriame nekvadratiniame kontūre
Kur konversija sąžininga dėl savo ribų
Kai kurie XPS konstruktai konvertuojami apytiksliai, o kai kurie — iš viso nekonvertuojami, ir visame projekte pasirinkta jas skaičiuoti, užuot suklastojus. TIFF ir JPEG XR dalys rastrinamos per WIC ir neša jokio pažado apie išsaugotą alfa, o PNG su teisingu alfa kanalu skaidomas į pagrindinį vaizdą ir /SMask. Vaizdo vidinis dydis išvedamas kaip pixel * 96 / DPI — pirmiausiai skaitomas PNG pHYs ar JPEG JFIF tankis, nepavykus grįžtama į 96 DPI, tad bloga tankio antraštė nusileidžia numanomame dydyje, o ne savavališkame. Išspręsti nepavykę matricų ištekliai, nestandartinės santykinės transformacijos, ColorConvertedBitmap, nepalaikomi gradientų sklaidos režimai ir sugadinta geometrija visi didina UnsupportedFeatureCount, o sugadinta įvestis žlunga uždarai, užuot smukus į tyliai kitokį piešinį
Tai naudinga laikysena archyviniam konverteriui: konversija, kuri tyliai aproksimuoja, blogesnė už tą, kuri pasako, kurių keturių elementų nepajėgė atvaizduoti, nes tik antroji duoda ką tikrinti, kol dokumentas neuždarytas į archyvų sistemą. Jei vertinate XPS ir OpenXPS konversiją kartu su likusiomis dokumentų grandinėmis — puslapių kompozicija, šriftais, pasirašymu, PDF/A išvestimi — HotPDF Delphi PDF component puslapis išvardija visą galimybių rinkinį ir palaikomas Delphi bei C++Builder versijas