HotPDF converteert XPS- en OpenXPS-pakketten naar PDF binnen Delphi en C++Builder zonder print driver, door elke 96-DPI fixed-page-coördinaat door één paginamatrix met schaal 0.75 en Y-flip te mappen, elke VisualBrush te publiceren als een gedeelde Form XObject, en ImageBrush-tile-modi om te zetten in native PDF tiling-patronen in plaats van herhaalde image-draws
Het scenario dat de meeste Windows-shops hiertoe brengt is saai en onvermijdelijk. Iets print al naar de Microsoft XPS Document Writer — een legacy ERP-rapport, een getekend formulier, een batch afschriften — en het archiefbeleid zegt PDF. XPS is een prima capture-formaat en een verschrikkelijk formaat om over tien jaar aan een recordsysteem te geven. Dus het spoelbestand moet een pagina-voor-pagina PDF worden, en op het moment dat u die converter begint te schrijven ontdekt u dat het interessante niet de XML is. Het is dat XPS en PDF het oneens zijn over waar de oorsprong ligt, wat een eenheid waard is, en wat een brush mag zijn
Van pakket naar PDF in één passage
Het entrypoint is het document-handlerregister, geen speciale XPS-klasse. THPDFDocumentHandlerRegistry.RegisterStandardHandlers installeert de XPS-, EPUB- en CBZ-handlers; herkenning is content-based, dus een pakket met [Content_Types].xml plus minstens één .fpage-onderdeel scoort 95 zelfs wanneer de bestandsextensie liegt, terwijl een blote .xps- of .oxps-extensie slechts 10 scoort. Die volgorde telt wanneer u uploads accepteert, want een aanvaller die een EPUB hernoemt naar .xps mag de pipeline niet sturen
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 is de eerlijke score voor deze conversie
finally
Output.Free;
Registry.Free;
end;
end;
Alles rond de conversie wordt gebudgetteerd voordat het wordt geprobeerd. THPDFDocumentHandlerOptions.Default plafonneert archiefentries op 10.000, uitgepakte archive-bytes op 1 GiB, de compressieverhouding op 200, resources op 4.096, en pagina's op 10.000, en hij draagt een optionele CancellationToken zodat een server-side job halverwege het pakket kan worden gestopt. Lees daarna Info.UnsupportedFeatureCount en behandel een waarde ongelijk nul als een echte bevinding: HotPDF telt bewust wat hij niet kon mappen in plaats van een benadering te tekenen en erover te zwijgen
Waarom heeft een XPS-pagina een matrix nodig in plaats van herschreven coördinaten?
Omdat coördinaten herschrijven de transformstack verliest. Een XPS FixedPage is gespecificeerd in 96-DPI-eenheden met de oorsprong linksboven en Y omlaag groeiend; PDF user space is 72-DPI met de oorsprong linksonder en Y omhoog groeiend. De naïeve oplossing is elk getal met 0.75 vermenigvuldigen en elke Y van de paginahoogte aftrekken bij het emitteeren. Dat werkt voor één plat pad en valt uiteen op het moment dat een RenderTransform, een geneste Canvas, of een brush-lokale matrix binnenkomt, want die transforms zijn gedefinieerd in XPS-ruimte en uw herschrijving per coördinaat heeft die al verlaten. HotPDF houdt de projectie daarom als matrix vast en componeert hem. HPDFXPSPageMatrix geeft de vaste constanten één keer per pagina terug, HPDFMultiplyXPSMatrix concateneert hem met de opgetelde path-transform, en het resultaat wordt als één enkele cm-operator vóór de geometrie geëmitteerd. Path-data wordt daarna in ongewijzigde XPS-getallen geschreven, wat ook de reden is dat de afgekorte geometriesyntaxis dezelfde begrensde parser kan delen die voor SVG path-data wordt gebruikt — alleen het leidende F0- of F1-vulregeltoken wordt door de XPS-adapter afgehandeld. Als u dezelfde redenering hebt gevolgd bij EMF- en WMF-vectorimport, is de vorm van het argument vertrouwd: importformaten worden per matrix geconverteerd, nooit met rekenkunst op bladcoördinaten
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // 96-DPI XPS-eenheid naar 72-DPI PDF-punt
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // XPS Y groeit naar beneden, PDF Y groeit naar boven
Result.E := 0;
Result.F := PageHeight; // PDF-paginahoogte, in punten
end;
// Eén samengestelde CTM per visual, geëmitteerd vóór elke path-operator
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Hoe hergebruikt u een VisualBrush zonder hem twee keer te tekenen?
Een VisualBrush verft een willekeurige visuele boom — Canvas-, Path- en Glyphs-kinderen — in een gebied, eventueel herhaald over dat gebied. HotPDF compileert die visual één keer tot een PDF Form XObject en plaatst hem daarna, wat dezelfde resourcestrategie is als beschreven in SVG-import via Form XObjects. Twee details bepalen of het werkt. Ten eerste moet de visual als directe XML-kinderen worden doorlopen: een platte scan naar tile-waardige elementen trekt geneste visuals omhoog naar het paginatopniveau en vernielt zowel resourcescoping als tekenvolgorde. Ten tweede wordt content vastgelegd met de XPS-naar-PDF-paginamatrix al toegepast, dus het publiceren van de Form vereist vermenigvuldigen met de inverse van die matrix, anders past elke plaatsing opnieuw de schaal 0.75 en de Y-flip toe. De Form moet ook zijn eigen resources bezitten: HotPDF kopieert alleen de fonts, XObjects, patronen, ExtGStates en kleurruimten die de vastgelegde contentstream werkelijk referentieert; de hele pagina-resource-dictionary klonen zou de Form die wordt geregistreerd in zijn eigen resourcegraph slepen en een cyclus bouwen. Fonts blijven in een directe dictionary op gewone pagina's en worden alleen gepromoveerd naar een gedeelde indirecte dictionary wanneer vastgelegde content werkelijk een Tf bevat, dus een document zonder herbruikbare visuals betaalt niet voor de machinerie. Merk één specificatiegrens op die de moeite waard is te weten voordat u een bug meldt: ECMA-388 sectie 13.4 vereist dat zowel ViewboxUnits als ViewportUnits op een VisualBrush Absolute is, dus relatieve eenheden zijn geen ontbrekende feature — het is niet-conforme input, en HotPDF weigert coördinaatsemantiek voor hen te verzinnen
ImageBrush tiling: vier modi, vier celgroottes
XPS-tile-modi worden gemapt op PDF tiling-patronen uit ISO 32000-1 sectie 8.7.3 in plaats van te worden uitgeklapt naar herhaalde image-plaatsingen over het gedekte gebied, wat de uitvoergrootte en conversietijd onafhankelijk houdt van hoeveel van de pagina de brush dekt. De mapping is mechanisch zodra u hem ziet: spiegelbeeld wordt uitgedrukt door gespiegelde plaatsingen binnen één patrooncel te zetten en de cel te vergroten om te matchen
Tile— één plaatsing, cel blijft 1×1 viewportFlipX— twee plaatsingen, cel verbreed naar 2×1FlipY— twee plaatsingen, cel verhoogd naar 1×2FlipXY— vier plaatsingen, cel uitgebreid naar 2×2
Elke plaatsing draagt zijn eigen clip-rechthoek, want een Viewbox-mapping die zijn subcel overstroomt zou in de buurspiegeling bloeden. De patroon-/Matrix is het deel dat mensen vangt. Een tiling-patroon is geankerd aan de default user space van zijn ouder-contentstream, niet aan de grafische toestand die heerst wanneer het patroon wordt geselecteerd, dus de matrix moet alle drie de lagen expliciet componeren — de fixed-page-projectie, de Path-transform, en de brush-lokale Transform — in plaats van te leunen op een omgevings-CTM. HotPDF valideert ook voordat hij alloceert: RegisterImageTilingPattern beperkt een patroon tot 1.024 plaatsingen en wijst degenerische clips, niet-inverteerbare matrices en ongeldige image-indices af. Wilt u het algemene PDF-side model erachter, dan behandelt tiling-patronen en de Pattern-kleurruimte de onderliggende operatoren
// Fixed-page-projectie in de patroonmatrix gevouwen, daarna die van de brush zelf
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);
Wat gebeurt er wanneer een radiaal gradiënt geen cirkel is?
XPS definieert een RadialGradientBrush met GradientOrigin, Center, RadiusX en RadiusY, dus de brush is een ellips. PDF shading type 3, in ISO 32000-1 sectie 8.7.4.5.4, blendeert tussen twee cirkels en heeft geen manier om een ellips rechtstreeks uit te drukken. De twee stralen tot één getal middelen is de verleidelijke shortcut en hij is zichtbaar fout op elke brush die niet bij benadering rond is. HotPDF verplaatst het probleem in plaats daarvan naar het coördinatensysteem: hij schaalt Y met RadiusY / RadiusX, registreert een eerderlijke cirkelvormige shading in die geschaalde ruimte, selecteert het patroon, en emitteert onmiddellijk de reciproke schaal zodat de path-geometrie die erna wordt geschreven nog steeds in de oorspronkelijke XPS user space staat
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); // het patroon vangt de CTM precies hier
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
De volgorde in dat fragment is de hele truc, en hij is niet stilistisch. Een PDF shading-patroon vangt de huidige transformatiematrix op het moment dat hij als huidige kleur wordt gekozen, dus de tijdelijke schaal moet vóór SetFillPattern of SetStrokePattern worden geëmitteerd, en de reciproke moet op de selectie volgen maar vóór de path-operators komen. Haal de volgorde in één van beide richtingen fout en u hebt een gradiënt die correct rendert op het eerste pad en afdwaalt op elk daaropvolgend. Een verwante beperking geldt voor relatieve coördinaatmodus: RadiusX en RadiusY moeten apart worden geresolveerd tegen de padbreedte en -hoogte, want beide met één zijlengte schalen verandert stilletjes de beeldverhouding van de ellips op elk niet-vierkant pad
Waar de conversie eerlijk is over zijn grenzen
Sommige XPS-constructen worden benaderd geconverteerd en sommige helemaal niet, en de ontwerpkeuze overal is ze te tellen in plaats van te faken. TIFF- en JPEG XR-onderdelen worden via WIC gerasterd en dragen geen belofte over bewaarde alpha, terwijl PNG met een geldig alphakanaal wordt gesplitst in een base-image plus een /SMask. De intrinsieke image-grootte wordt afgeleid als pixel * 96 / DPI, eerst PNG pHYs of JPEG JFIF-dichtheid lezend en terugvallend op 96 DPI, dus een slechte dichtheidsheader landt op een voorspelbare grootte in plaats van een willekeurige. Onopgeloste matrix-resources, niet-standaard relatieve transforms, ColorConvertedBitmap, niet-ondersteunde gradiënt-spread-modi en misvormde geometrie verhogen allemaal UnsupportedFeatureCount, en misvormde input faalt dicht in plaats van te degraderen naar stilletjes een andere tekening
Dat is de nuttige houding voor een archiefconverteerder: een conversie die stilletjes benadert is erger dan één die u vertelt welke vier elementen hij niet kon representeren, want alleen de tweede geeft u iets om te controleren voordat het document wordt verzegeld in een recordsysteem. Evalueert u XPS- en OpenXPS-conversie naast de rest van de documentpipeline — pagina-opbouw, fonts, signeren, PDF/A-uitvoer — dan zet de pagina HotPDF Delphi PDF component de volledige featureset en de ondersteunde Delphi- en C++Builder-versies op een rij