Teknisk artikkel

PDF-flismønster-rendering i Delphi med HotPDF

Skravur som tegnes som én flat grå blokk er den klassiske flismønster-feilen. HotPDF, den native VCL-PDF-komponenten for Delphi og C++Builder, tegner PatternType 1 ved å gjøre den gjeldende banen om til en midlertidig klipp og spille av mønsterinnholdsstrømmen på nytt én gang per synlig flis, med mønstervalg holdt i grafikktilstanden og gjenopprettet av q og Q

Symptomene kommer i to varianter, og de ser urelaterte ut inntil du kjenner årsaken. En CAD-tegning mister sin snittskravur og kommer tilbake som solide fyllinger, fordi rendereren løste mønsteret til en gjennomsnittsfarge og tegnet den. Eller skraveringen slipper unna: en tittelblokk som skulle vært ren hvit plukker opp de diagonale linjene fra en detaljvisning to baner tidligere. Begge er mønstertilstandsproblemer, og bare ett av dem handler i det hele tatt om å tegne fliser

Hvorfor blør et flismønster over på neste bane?

Fordi det valgte mønsternavnet er del av grafikktilstanden, ikke en egenskap ved operatoren som brukte det. ISO 32000-1 §8.6.6.2 definerer et Pattern-fargerom som ett hvis fargeverdi er et mønsternavn levert til scn eller SCN, og hver annen komponent av fargetilstanden lagres av q og gjenopprettes av Q. Mønsternavnet må følge samme regel. HotPDF holder det i tilstandsposten som FillPatternName og StrokePatternName, ved siden av fyll- og strøkfargeromfamilien, slik at en Q setter det forrige valget tilbake nøyaktig slik den setter tilbake den forrige CTM-en

Lagre i stedet det navnet i en lokal variabel inne i operatordispatcheren, og den overlever hver Q i strømmen. Feilen dukker da opp et uventet sted: et Form-XObject tegnet etter den mønstrede banen arver et mønstervalg dets egen innholdsstrøm aldri gjorde, og fyllingene kommer ut skravert. Nøstede skjemaer gjør det verre, fordi hvert nøstingsnivå skyver og popper tilstand som den villfarne variabelen ignorerer. Å sette et ikke-mønster-fargerom med cs eller CS, eller å utstede en ren g / rg / k, må også nullstille mønsternavnet, ellers overlever det foreldede valget fargerommet som ga det mening

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

Et mønster tegnes gjennom en klipp, aldri som et fyll

Den korrekte modellen er subtraktiv: begrens enhetsklippet til formen som tegnes, kjør deretter mønsterinnholdet inni den. HotPDF tegner aldri en solid tilnærming først og overmaler den, fordi den mellomliggende solide fargen ville vært synlig gjennom gapene mellom flisene og ville stride mot enhver transparens i flisinnholdet. §8.7.3.2 beskriver et flismønster som en innholdsstrøm gjentatt med faste horisontale og vertikale intervaller, og gjentakelse gir bare mening mot en klipp som allerede har riktig form. For fyllinger er konverteringen direkte: HPDFSelectFillPathClip setter polygonfyllmodusen til ALTERNATE for f*, B* og b* og til WINDING for nonzero-variantene, bygger GDI-banen, og skjærer den inn i klippet med SelectClipPath. Den ene linjen er det som gjør at et even-odd-mønstret fyll etterlater samme hull som et even-odd solid fyll, som er nøyaktig hva en smultringformet skravert region trenger

Strøk er delen som er lett å gjøre feil. En strøket bane har ikke noe indre, så å skjære selve banen inn i klippet gir en tom region og ingenting blir tegnet. HPDFSelectStrokePathClip bygger derfor først en geometrisk penn fra gjeldende tilstand, med PS_GEOMETRIC med endehetten fra J, sammenføyningen fra j, gjærgrensen fra M, og PS_USERSTYLE når en strekarray er aktiv, og kaller deretter WidenPath for å konvertere det strøkede omrisset til en fyllbar region før klipping. Hette-, sammenføynings-, gjær- og strekoppførsel på en mønster-strøket bane matcher da et normalt strøk ved konstruksjon snarere enn ved en andre implementasjon. To ærlige grenser bor her: linjebredder under én enhet klippes til én piksel, og strekarrayet avkortes ved seksten oppføringer, som er taket ExtCreatePen aksepterer

Hvilke fliser er faktisk synlige?

Det synlige intervallet kommer fra å kjøre transformasjonen baklengs. Flisplassering skjer i mønsterrom, men det eneste som vet hvor mye av siden som blir rørt er enhetsklippboksen, som er i enhetsrom. HotPDF setter sammen BaseMatrix := CTM * PatternMatrix, inverterer den, og mapper de fire hjørnene av GDI-klippboksen tilbake gjennom inversen. De akseparallelle grensene til de fire mappede hjørnene gir mønsterrom-rektangelet som mulig kan dekkes, og å dele det rektangelet med XStep og YStep mot mønsterets BBox gir lukkede indeksintervaller. Hver celle rendres deretter med en CTM på CTM * PatternMatrix * Translate(i * XStep, j * YStep), og klippes en andre gang mot sin egen transformerte BBox-polygon. Den andre klippingen betyr noe når XStep er mindre enn boundingboks-bredden, som er hvordan overlappende flisdesign uttrykkes; uten den ville naboceller tegne over hverandre utenfor sin erklærte utstrekning. Hvis klippet per celle kommer tilbake som NULLREGION, hoppes cellen over uten å tokeniseres eller kjøres

// 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));

Ufargede mønstre og fargen som kommer utenfra

Et PaintType 2-mønster bærer form, men ingen farge, og fargen ankommer sammen med mønsternavnet. §8.7.3.2 spesifiserer at et ufarget mønster bare brukes med et Pattern-fargerom som erklærer et underliggende rom, så scn mottar komponentverdiene først og mønsternavnet sist. HotPDF løser opp de komponentene gjennom det underliggende rommet lagret på mønsterfargerom-oppføringen, som betyr at en ufarget skravur kan tones med en Separation-farge eller en DeviceN-kombinasjon nøyaktig som ethvert annet fyll; mekanikken bak den oppløsningen er dekket i rendering av Separation- og DeviceN-spotfarger. Inne i flisen divergerer de to malingstypene skarpt. For PaintType 2 setter rendereren et flagg som undertrykker fargeoperatorer for varigheten av flisen, slik at enhver g, rg, k eller scn i mønsterinnholdet ignoreres, og hvert merke tar den eksternt leverte fargen. For PaintType 1 gjelder det motsatte: fyll- og strøktilstand tilbakestilles til PDF-standardene, DeviceGray svart med et identitetsfargerom, og flisen farger seg selv. Å hoppe over den tilbakestillingen lar fargen som tilfeldigvis var gjeldende ved f-operatoren lekke inn i et mønster som skulle vært selvbeskrivende

Hvorfor må grafikktilstands-stakkdybden gjenopprettes etter hver flis?

Fordi en mønsterinnholdsstrøm har lov til å være ubalansert, og skaden akkumuleres på tvers av celler. En flis hvis strøm inneholder tre q-operatorer og to Q-operatorer etterlater stakken én ramme dypere enn den startet. Gjenopprett bare den gjeldende tilstandsposten mellom celler, og dybden fortsetter å vokse, så celle nummer to hundre kjøres fra en stakkramme som tilhører celle nummer hundre og nittini, med hvilken CTM og klipp den rammen enn bar. HotPDF tar derfor et øyeblikksbilde av tilstandsposten og stakkdybden før flisløkken og kaller RestoreSnapshot ved toppen av hver iterasjon, som trunkerer stakken tilbake til den lagrede lengden og reinstallerer den lagrede tilstanden i ett steg. Side-Resources-ordboken og flagget for undertrykking av fargeoperatorer gjenopprettes på samme grense, siden en flis kan referere sine egne ressurser og ikke må gi dem videre til naboen. GDI-klipptilstanden får samme behandling gjennom et SaveDC / RestoreDC-par rundt hver celle, slik at en flis som installerer sin egen W n-klipp ikke kan krympe regionen tilgjengelig for den neste

Budsjetter, avvisninger, og hva rendereren ikke vil tegne

Flismønstre er det letteste stedet i en PDF å skrive en tjenestenektfil (denial-of-service), så grensene er harde tall snarere enn heuristikk. Mønsternøsting er begrenset til dybde 4, samme vakt som brukes for Form-XObject-rekursjon, som stopper et mønster som refererer til seg selv gjennom sin egen ressursordbok. En enkelt banemaling kan kjøre maksimalt 16 384 fliser totalt, telt ned på tvers av nøstede mønstre og bare nullstilt når det ytterste mønstermalingen begynner. Et flisgitter hvis planlagte celleantall overstiger hva som er igjen av det budsjettet avvises umiddelbart, før en eneste celle kjøres

Degenerert geometri avvises i stedet for å tilnærmes. En manglende eller null-areal BBox, en XStep eller YStep hvis størrelse er under 1e-6, et CTM * PatternMatrix-produkt uten invers, mappede klippkoordinater forbi 1e9, eller en indeksstørrelse forbi én million forårsaker alle at mønstermalingen returnerer uten å tegne. Resultatet er en umalt region i stedet for en hengt render-tråd, som er avveiningen du vil ha i en batch-konverterer. Ytelse kommer fra én beslutning: mønsterstrømmen tokeniseres én gang per maling med HPDFTokenizeContentStream, og tokenarrayet gjenbrukes på tvers av hver synlig celle, slik at flisantall multipliserer utførelseskostnad, men aldri leksekostnad

Å rendre en mønstret side fra Delphi

Ingenting ved mønsterstøtte endrer kallekoden. Last inn dokumentet, be om en side, og flisarbeidet skjer inne i innholdsstrøm-tolkeren som side-til-bitmap-rendering allerede driver. Samme tolker mater bitmap-, metafile- og skriverenhetskontekster, så en skravert tegning som ser riktig ut i en forhåndsvisnings-miniatyr skriver ut med samme flisgeometri. PatternType 2-skyggemønstre tar en annen gren som deler sin evalueringssti med den bare sh-operatoren, beskrevet i detalj under rendering av aksial og radial skyggelegging

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;

Når en mønstret region fortsatt ser feil ut, sjekk de tre feilklassene i rekkefølge. En region som er helt tom betyr vanligvis en avvisning: undersøk XStep, YStep og BBox for degenererte verdier, eller tell hvor mange fliser gitteret ville trenge mot 16 384-taket. En region malt i én flat farge betyr at mønsternavnet aldri nådde malingsoperatoren, som peker mot cs- og scn-rekkefølgen i strømmen. Et mønster som dukker opp der det ikke hører hjemme betyr tilstandsgjenoppretting, og stedet å se er q / Q-håndteringen rundt skjemaet eller banen som arvet det

Flismønstre er en av de PDF-funksjonene som forblir usynlige inntil filen som trenger dem lander i innboksen din, og da er de hele jobben. Hvis du bygger tegningsvisere, ingeniørdokument-konverterere eller rapportrenderere på Delphi eller C++Builder, er hele komponenten og dens rendering-API dokumentert på siden HotPDF Delphi PDF-komponent