HotPDF konverterer XPS- og OpenXPS-pakker til PDF inne i Delphi og C++Builder uten en utskriftsdriver, ved å mappe hver 96-DPI fastside-koordinat gjennom én 0.75-skala Y-vendende sidematrise, publisere hver VisualBrush som en delt Form XObject, og gjøre ImageBrush flis-moduser om til native PDF flisleggingsmønstre i stedet for gjentatte bildetegninger
Scenariet som drar de fleste Windows-butikker inn i dette er kjedelig og uunngåelig. Noe skriver allerede ut til Microsoft XPS Document Writer — en eldre ERP-rapport, et signert skjema, en bunke kontoutskrifter — og arkivpolicyen sier PDF. XPS er et helt grept fangstformat og et forferdelig et å gi et arkivsystem et tiår fra nå. Så spool-filen må bli en side-for-side PDF, og i det øyeblikket du begynner å skrive den konverteren oppdager du at den interessante delen ikke er XML-en. Det er at XPS og PDF er uenige om hvor origo er, hva en enhet er verdt, og hva en pensel får lov å være
Fra pakke til PDF i én gjennomgang
Inngangspunktet er dokumenthåndterer-registeret, ikke en spesiell XPS-klasse. THPDFDocumentHandlerRegistry.RegisterStandardHandlers installerer XPS-, EPUB- og CBZ-håndtererne; gjenkjenning er innholdsbasert, så en pakke som bærer [Content_Types].xml pluss minst én .fpage-del scorer 95 selv når filetternavnet lyver, mens et bart .xps- eller .oxps-etternavn bare scorer 10. Den ordningen betyr noe når du aksepterer opplastinger, for en angriper som omdøper en EPUB til .xps skal ikke styre pipelinen
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 er den ærlige poengsummen for denne konverteringen
finally
Output.Free;
Registry.Free;
end;
end;
Alt om konverteringen er budsjettert før det forsøkes. THPDFDocumentHandlerOptions.Default takler arkivoppføringer ved 10,000, utvidede arkivbyte ved 1 GiB, komprimeringsforholdet ved 200, ressurser ved 4,096, og sider ved 10,000, og den bærer en valgfri CancellationToken så en serversidejobb kan stoppes midt i pakken. Les Info.UnsupportedFeatureCount etterpå og behandle en ikke-null verdi som et reelt funn: HotPDF teller villet hva den ikke kunne mape i stedet for å tegne en tilnærming og tie om det
Hvorfor trenger en XPS-side en matrise i stedet for omskrevne koordinater?
Fordi å omskrive koordinater mister transformstakken. En XPS FixedPage er spesifisert i 96-DPI enheter med origo øverst til venstre og Y voksende nedover; PDF brukerrom er 72-DPI med origo nederst til venstre og Y voksende oppover. Den naive fiks er å multiplisere hvert tall med 0.75 og subtrahere hver Y fra sidehøyden etter hvert som du avgir den. Det virker for én flat bane og faller fra hverandre i det øyeblikket en RenderTransform, en nestet Canvas, eller en pensel-lokal matrise entrer, for de transformene er definert i XPS-rom og din per-koordinat omskriving har allerede forlatt det. HotPDF beholder derfor projeksjonen som en matrise og komponerer den. HPDFXPSPageMatrix returnerer de faste konstantene én gang per side, HPDFMultiplyXPSMatrix lenker den med den akkumulerte banetransformen, og resultatet avgis som en enkelt cm-operator før geometrien. Banedata skrives deretter i umodifiserte XPS-tall, noe som også er hvorfor den forkortede geometrisyntaksen kan dele den samme avgrensede parseren brukt for SVG-banedata — bare det ledende F0- eller F1-fyllesregel-symbolet håndteres av XPS-adapteren. Har du fulgt samme resonnement for EMF- og WMF-vektorimport, er argumentets form kjent: importformater konverteres ved matrise, aldri ved aritmetikk på bladkoordinater
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // 96-DPI XPS-enhet til 72-DPI PDF-punkt
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // XPS Y vokser ned, PDF Y vokser opp
Result.E := 0;
Result.F := PageHeight; // PDF-sidehøyde, i punkter
end;
// Én komponert CTM per visuell, avgitt før enhver baneoperator
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Hvordan gjenbruker du en VisualBrush uten å tegne den to ganger?
En VisualBrush maler et vilkårlig visuelt tre — Canvas-, Path- og Glyphs-barn — inn i en region, muligens gjentatt på tvers av den. HotPDF kompilerer den visuelle én gang til en PDF Form XObject og plasserer den så, noe som er samme ressursstrategi beskrevet i SVG-import via Form XObjects. To detaljer avgjør om det virker. For det første må det visuelle gås gjennom som direkte XML-barn: en flat skanning for flisverdige elementer trekker nestede visuelle opp til sidenivå topp og ødelegger både ressursskoping og maleorden. For det andre fanges innhold med XPS-til-PDF sidematrisen allerede anvendt, så å publisere Form-en krever multiplikasjon med inversen av den matrisen, ellers re-anvender hver plassering 0.75-skalaen og Y-vendingen. Form-en må også eie sine ressurser: HotPDF kopierer bare skrifttypene, XObjects, mønstrene, ExtGStates og fargerommene den fangede innholdsstrømmen faktisk refererer; å klone hele sideressursordboken ville dra Form-en som registreres inn i sin egen ressursgraf og bygge en syklus. Skrifttyper forblir i en direkte ordbok på vanlige sider og forfremmes til en delt indirekte ordbok bare når fanget innhold virkelig inneholder en Tf, så et dokument uten gjenbrukbare visuelle betaler ikke for maskineriet. Merk én spesifikasjonsgrense verdt å vite før du arkiverer en feil: ECMA-388 seksjon 13.4 krever at både ViewboxUnits og ViewportUnits på en VisualBrush er Absolute, så relative enheter er ikke en manglende funksjon — de er ikke-konformt inndata, og HotPDF nekter å finne opp koordinatsemantikk for dem
ImageBrush flislegging: fire moduser, fire cellestørrelser
XPS flis-moduser mappes onto PDF flisleggingsmønstre fra ISO 32000-1 seksjon 8.7.3 i stedet for å utvides til gjentatte bildeplasseringer på tvers av det dekte området, noe som holder utdatastørrelse og konverteringstid uavhengig av hvor mye av siden penselen dekker. Mappingen er mekanisk når du ser den: refleksjon uttrykkes ved å legge speilede plasseringer inne i én mønstercelle og utvide cellen til å matche
Tile— én plassering, cellen forblir 1×1 viewportFlipX— to plasseringer, cellen utvidet til 2×1FlipY— to plasseringer, cellen forhøyet til 1×2FlipXY— fire plasseringer, cellen ekspandert til 2×2
Hver plassering bærer sitt eget klipprektangel, for en Viewbox-mapping som overskrider sin sub-celle ville blø inn i den naborefleksjonen. Mønsterets /Matrix er delen som fanger folk. Et flisleggingsmønster er forankret til standard brukerrom til sin foreldreinnholdsstrøm, ikke til grafikktilstanden gjeldende når mønsteret velges, så matrisen må komponere alle tre lagene eksplisitt — fastside-projeksjonen, Path-transformen, og pensel-lokal Transform — i stedet for å stole på en omgivende CTM. HotPDF validerer også før den tildeler: RegisterImageTilingPattern begrenser et mønster til 1,024 plasseringer og avviser degenererte klipp, ikke-inverterbare matriser og ugyldige bildeindekser. Vil du ha den generelle PDF-side modellen bak dette, dekker flisleggingsmønstre og Pattern fargerommet de underliggende operatorene
// Fastside-projeksjon foldet inn i mønstermatrisen, så den pensel-lokale
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);
Hva skjer når en radial gradient ikke er en sirkel?
XPS definerer en RadialGradientBrush med GradientOrigin, Center, RadiusX og RadiusY, så penselen er en ellipse. PDF shading type 3, i ISO 32000-1 seksjon 8.7.4.5.4, blander mellom to sirkler og har ingen måte å uttrykke en ellipse direkte. Å gjennomsnittliggi de to radiene til ett tall er den fristende snarveien, og den er synlig galt på enhver pensel som ikke er nær rund. HotPDF flytter i stedet problemet inn i koordinatsystemet: den skalerer Y med RadiusY / RadiusX, registrerer en ærlig sirkulær shading i det skalerte rommet, velger mønsteret, og avgir umiddelbart den resiproke skalaen så banegeometrien skrevet neste fortsatt er i originalt XPS brukerrom
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); // mønsteret fanger CTM-en akkurat her
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
Ordningen i det utdraget er hele trikset, og den er ikke stilistisk. Et PDF shading-mønster fanger den gjeldende transformasjonsmatrisen i øyeblikket den velges som gjeldende farge, så den midlertidige skalaen må avgis før SetFillPattern eller SetStrokePattern, og den resiproke må følge valget men gå foran baneoperatorene. Får du ordenen galt i noen retning har du en gradient som gjengis korrekt på den første banen og driver på hver etterfølgende. En relatert begrensning gjelder relativ koordinatmodus: RadiusX og RadiusY må løses mot banens bredde og høyde separat, siden å skalere begge med én enkelt kantlengde stille endrer ellipseaspektforholdet på enhver ikke-kvadratisk bane
Der konverteringen er ærlig om sine grenser
Noen XPS-konstruksjoner konverteres tilnærmet og noen konverteres ikke i det hele tatt, og designvalget gjennomgående er å telle dem snarere enn å forfalske dem. TIFF- og JPEG XR-deler rasteriseres gjennom WIC og bærer intet løfte om bevart alfa, mens PNG med en gyldig alfakanal splittes i et grunnbilde pluss en /SMask. Bilde inntrinnende størrelse deriveres som pixel * 96 / DPI, ved å lese PNG pHYs eller JPEG JFIF tetthet først og falle tilbake til 96 DPI, så en dårlig tetthet-header lander ved en forutsigbar størrelse snarere enn en vilkårlig. Uløste matriseressurser, ikke-standard relative transformer, ColorConvertedBitmap, ustøttede gradient spredningsmoduser og feilformet geometri øker alle UnsupportedFeatureCount, og feilformet inndata feiler lukket i stedet for å degraderes til en stille annerledes tegning
Det er den nyttige holdningen for en arkivkonverterer: en konvertering som stille tilnærmer er verre enn en som forteller deg hvilke fire elementer den ikke kunne representere, for bare den andre gir deg noe å sjekke før dokumentet forsegles inn i et arkivsystem. Evaluerer du XPS- og OpenXPS-konvertering sammen med resten av dokumentpipelinen — sidekomposisjon, skrifttyper, signering, PDF/A-utdata — lister HotPDF Delphi PDF component-siden det fullstendige funksjonssettet og de støttede Delphi- og C++Builder-versjonene