Arcering die als één vlak grijs blok rendert, is het klassieke faalpatroon voor tiling-patterns. HotPDF, het native VCL PDF-component voor Delphi en C++Builder, tekent PatternType 1 door het huidige pad in een tijdelijke clip om te zetten en de contentstream van het patroon eenmaal per zichtbare tegel opnieuw af te spelen, met patroonselectie vastgehouden in de grafische staat en hersteld door q en Q
De symptomen komen in twee smaken, en ze lijken ongerelateerd totdat je de oorzaak kent. Een CAD-tekening verliest zijn doorsnede-arcering en komt terug als effen vlakken, omdat de renderer het patroon oploste naar een gemiddelde kleur en die schilderde. Of de arcering lekt weg: een titelblok dat gewoon wit zou moeten zijn, pikt de diagonale lijnen op van een detailweergave twee paden eerder. Beide zijn patroonstaatproblemen, en slechts één daarvan gaat werkelijk over het tekenen van tegels
Waarom lekt een tiling-pattern naar het volgende pad?
Omdat de geselecteerde patroonnaam deel uitmaakt van de grafische staat, niet van de operator die hem gebruikte. ISO 32000-1 §8.6.6.2 definieert een Pattern-kleurruimte als een waarvan de kleurwaarde een patroonnaam is die aan scn of SCN geleverd wordt, en elk ander onderdeel van de kleurstaat wordt door q bewaard en door Q hersteld. De patroonnaam moet dezelfde regel volgen. HotPDF bewaart hem in het staatrecord als FillPatternName en StrokePatternName, naast de fill- en stroke-kleurruimtefamilie, zodat een Q de vorige selectie precies zo terugzet als hij de vorige CTM terugzet
Bewaar die naam in plaats daarvan in een lokale variabele binnen de operator-dispatcher, en hij overleeft elke Q in de stream. Het falen duikt dan ergens onverwachts op: een Form-XObject dat na het gearceerde pad getekend wordt, erft een patroonselectie die zijn eigen contentstream nooit gemaakt heeft, en zijn fills komen gearceerd uit. Geneste forms maken het erger, omdat elk nestingsniveau staat pusht en popt die de verdwaalde variabele negeert. Het instellen van een niet-patroon-kleurruimte met cs of CS, of het uitvoeren van een gewone g / rg / k, moet ook de patroonnaam wissen, anders overleeft de verouderde selectie de kleurruimte die hem betekenis gaf
q
/Pattern cs % pattern colour space, ISO 32000-1 8.6.6.2
/P1 scn % coloured tiling pattern, PaintType 1
10 10 200 120 re f % this rectangle is hatched
Q
0 0 300 200 re f % must be black again, not hatched
q
/Cs2 cs % [/Pattern /DeviceCMYK] array
0 0.6 1 0 /P2 scn % uncoloured pattern plus its underlying colour
20 20 160 90 re f*
Q
Een patroon wordt geschilderd via een clip, nooit als een fill
Het correcte model is subtractief: beperk de device-clip tot de vorm die geschilderd wordt, en draai dan de patrooncontent daarbinnen. HotPDF tekent nooit eerst een effen benadering en overschildert die, omdat de tussenliggende effen vorm zichtbaar zou zijn door de gaten tussen tegels en zou botsen met eventuele transparantie in de tegelinhoud. §8.7.3.2 beschrijft een tiling-pattern als een contentstream die op vaste horizontale en verticale intervallen gerepliceerd wordt, en replicatie is alleen zinvol tegen een clip die al de juiste vorm heeft. Voor fills is de conversie direct: HPDFSelectFillPathClip zet de polygon-fillmodus op ALTERNATE voor f*, B* en b* en op WINDING voor de nonzero-varianten, bouwt het GDI-pad, en snijdt het in de clip met SelectClipPath. Die ene regel is wat een even-odd-gearceerde fill dezelfde gaten laat achterlaten als een even-odd effen fill, precies wat een donutvormig gearceerd gebied nodig heeft
Strokes zijn het onderdeel dat makkelijk fout gaat. Een gestroked pad heeft geen binnenkant, dus het pad zelf in de clip snijden levert een lege regio op en er wordt niets geschilderd. HPDFSelectStrokePathClip bouwt daarom eerst een geometrische pen uit de huidige staat, met PS_GEOMETRIC met de eindkap uit J, de join uit j, de miterlimiet uit M, en PS_USERSTYLE wanneer een dash-array actief is, en roept dan WidenPath aan om de gestrokede omtrek om te zetten in een vulbare regio voordat er geclipt wordt. Kap-, join-, miter- en dashgedrag op een met een patroon gestroked pad komt dan overeen met een normale stroke door constructie, niet door een tweede implementatie. Twee eerlijke grenzen zitten hier: lijndiktes onder één device-eenheid worden vastgeklemd op één pixel, en de dash-array wordt afgekapt bij zestien items, wat het plafond is dat ExtCreatePen accepteert
Welke tegels zijn daadwerkelijk zichtbaar?
Het zichtbare bereik komt van het achterwaarts draaien van de transformatie. Tegelplaatsing gebeurt in patroonruimte, maar het enige dat weet hoeveel van de pagina geraakt wordt, is de device-clipbox, die in devicespace staat. HotPDF stelt BaseMatrix := CTM * PatternMatrix samen, inverteert die, en mapt de vier hoeken van de GDI-clipbox terug door de inverse. De as-uitgelijnde grenzen van die vier gemapte hoeken geven de patroonruimte-rechthoek die mogelijk gedekt kan worden, en het delen van die rechthoek door XStep en YStep tegen de patroon-BBox geeft gesloten index-bereiken. Elke cel rendert dan met een CTM van CTM * PatternMatrix * Translate(i * XStep, j * YStep), en wordt een tweede keer geclipt tegen zijn eigen getransformeerde BBox-polygon. Die tweede clip doet ertoe wanneer XStep kleiner is dan de breedte van de bounding box, wat de manier is waarop overlappende tegelontwerpen uitgedrukt worden; zonder die clip zouden aangrenzende cellen elkaar overschilderen buiten hun verklaarde bereik. Als de clip per cel terugkomt als NULLREGION, wordt de cel overgeslagen zonder ook maar iets te tokenizen of uit te voeren
// Map the device clip box back into pattern space through the inverse of
// CTM * PatternMatrix, then convert those bounds into tile index ranges.
BaseMatrix := HPDFMatMul(FGSStack.State.CTM, PatternMatrix);
if not HPDFMatInvert(BaseMatrix, InverseMatrix) then Exit; // singular: refuse
if GetClipBox(FDC, ClipRect) = ERROR then Exit;
// MinX..MaxY are the axis-aligned bounds of the four mapped clip corners.
I0 := Floor((MinX - BBox[2]) / StepXAbs);
I1 := Ceil ((MaxX - BBox[0]) / StepXAbs);
J0 := Floor((MinY - BBox[3]) / StepYAbs);
J1 := Ceil ((MaxY - BBox[1]) / StepYAbs);
PlannedTiles := Int64(I1 - I0 + 1) * Int64(J1 - J0 + 1);
if (PlannedTiles <= 0) or (PlannedTiles > FPatternTilesRemaining) then Exit;
Dec(FPatternTilesRemaining, Integer(PlannedTiles));
Ongekleurde patronen en de kleur die van buiten komt
Een PaintType-2-patroon draagt vorm maar geen kleur, en de kleur arriveert met de patroonnaam. §8.7.3.2 specificeert dat een ongekleurd patroon alleen gebruikt wordt met een Pattern-kleurruimte die een onderliggende ruimte verklaart, dus scn ontvangt eerst de componentwaarden en de patroonnaam als laatste. HotPDF lost die componenten op via de onderliggende ruimte die op het item van de patroonkleurruimte staat, wat betekent dat een ongekleurde arcering getint kan worden met een Separation-inkt of een DeviceN-combinatie precies zoals elke andere fill; de mechaniek van die resolutie wordt behandeld in het renderen van Separation- en DeviceN-steunkleuren. Binnen de tegel lopen de twee schildertypen sterk uiteen. Voor PaintType 2 zet de renderer een onderdrukkingsvlag voor kleuroperators voor de duur van de tegel, zodat elke g, rg, k of scn in de patrooncontent genegeerd wordt en elke markering de van buiten geleverde kleur aanneemt. Voor PaintType 1 geldt het omgekeerde: fill- en strokestaat worden gereset naar de PDF-standaarden, DeviceGray-zwart met een identiteits-kleurruimte, en de tegel kleurt zichzelf. Die reset overslaan laat de kleur die toevallig actief was bij de f-operator lekken in een patroon dat zelfbeschrijvend hoorde te zijn
Waarom moet de dieptestapel van de grafische staat na elke tegel hersteld worden?
Omdat een patrooncontentstream onbalans mag hebben, en de schade stapelt zich op over cellen heen. Een tegel waarvan de stream drie q-operators en twee Q-operators bevat, laat de stapel één frame dieper achter dan hij begon. Herstel je alleen het huidige staatrecord tussen cellen, dan blijft de diepte groeien, dus cel nummer tweehonderd draait vanuit een stapelframe dat bij cel nummer honderdnegenennegentig hoort, met welke CTM en clip dat frame ook droeg. HotPDF maakt daarom een momentopname van het staatrecord en de stapeldiepte vóór de tegellus en roept RestoreSnapshot aan bovenaan elke iteratie, wat de stapel terugbrengt tot de bewaarde lengte en de bewaarde staat in één stap herinstalleert. Het Resources-dictionary van de pagina en de onderdrukkingsvlag voor kleuroperators worden op dezelfde grens hersteld, aangezien een tegel naar zijn eigen resources kan verwijzen en die niet aan zijn buurman mag doorgeven. GDI-clipstaat krijgt dezelfde behandeling via een SaveDC / RestoreDC-paar rond elke cel, zodat een tegel die zijn eigen W n-clip installeert de beschikbare regio voor de volgende cel niet kan verkleinen
Budgetten, weigeringen, en wat de renderer niet tekent
Tiling-patterns zijn de makkelijkste plek in een PDF om een denial-of-service-bestand te schrijven, dus de limieten zijn harde getallen in plaats van heuristieken. Patroonnesting is begrensd op diepte 4, dezelfde bescherming die voor Form-XObject-recursie gebruikt wordt, wat een patroon tegenhoudt dat naar zichzelf verwijst via zijn eigen resource-dictionary. Één padschildering mag in totaal maximaal 16.384 tegels uitvoeren, afgeteld over geneste patronen heen en alleen gereset wanneer de buitenste patroonschildering begint. Een tegelraster waarvan het geplande celaantal overschrijdt wat er nog over is van dat budget, wordt ronduit geweigerd, voordat ook maar één cel draait
Gedegenereerde geometrie wordt geweigerd in plaats van benaderd. Een ontbrekende of nul-oppervlakte-BBox, een XStep of YStep waarvan de grootte onder 1e-6 ligt, een product CTM * PatternMatrix zonder inverse, gemapte clipcoördinaten voorbij 1e9, of een indexgrootte voorbij één miljoen veroorzaken allemaal dat de patroonschildering terugkeert zonder te tekenen. Het resultaat is een ongeschilderd gebied in plaats van een vastgelopen renderthread, wat de afweging is die je wilt in een batchconverter. Prestatie komt van één beslissing: de patroonstream wordt eenmaal per schildering getokenized met HPDFTokenizeContentStream en de tokenarray wordt hergebruikt over elke zichtbare cel, zodat het aantal tegels de uitvoeringskost vermenigvuldigt maar nooit de lexeerkost
Een gepatroneerde pagina renderen vanuit Delphi
Niets aan de patroonondersteuning verandert de aanroepende code. Laad het document, vraag om een pagina, en het tegelwerk gebeurt binnen de contentstream-interpreter die page-to-bitmap-rendering al aandrijft. Dezelfde interpreter voedt bitmap-, metafile- en printer-devicecontexten, dus een gearceerde tekening die er goed uitziet in een voorbeeldthumbnail print met dezelfde tegelgeometrie. PatternType-2-shadingpatronen nemen een andere tak die zijn evaluatiepad deelt met de kale sh-operator, in detail beschreven onder axiale en radiale shading-rendering
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('assembly-drawing.pdf') > 0 then
begin
// Section hatching that previously flattened to a solid block now
// replays the tile content once per visible cell.
Bmp := Pdf.RenderLoadedPageToBitmap(0, 200);
if Assigned(Bmp) then
try
Bmp.SaveToFile('sheet1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Wanneer een gepatroneerd gebied er nog steeds verkeerd uitziet, controleer dan de drie faalklassen in volgorde. Een gebied dat volledig leeg is, betekent meestal een weigering: inspecteer XStep, YStep en BBox op gedegenereerde waarden, of tel de tegels die het raster nodig zou hebben tegen het plafond van 16.384. Een gebied dat in één effen kleur geschilderd is, betekent dat de patroonnaam de schilderoperator nooit bereikte, wat wijst op de volgorde van cs en scn in de stream. Een patroon dat opduikt waar het niet hoort, betekent staatherstel, en de plek om te kijken is de q / Q-afhandeling rond het form of pad dat het erfde
Tiling-patterns zijn een van die PDF-functies die onzichtbaar blijven totdat het bestand dat ze nodig heeft in je inbox landt, en dan zijn ze de hele klus. Bouw je tekeningviewers, technische documentconverters of rapportrenderers op Delphi of C++Builder, dan staan het volledige component en zijn render-API gedocumenteerd op de pagina van het HotPDF Delphi PDF-component