Odborný článok

Renderovanie PDF tiling patternov v Delphi s HotPDF

Šrafovanie, ktoré sa vykreslí ako jeden plochý sivý blok, je klasické zlyhanie tiling patternu. HotPDF, natívna VCL PDF komponenta pre Delphi a C++Builder, maľuje PatternType 1 tým, že premení aktuálnu cestu na dočasný orez a prehrá obsahový stream patternu raz za každú viditeľnú dlaždicu, pričom výber patternu je uložený v grafickom stave a obnovovaný pomocou q a Q

Príznaky prichádzajú v dvoch príchutiach, a vyzerajú nesúvisiace, kým nepoznáte príčinu. CAD výkres stratí svoje sekčné šrafovanie a vráti sa ako plné výplne, pretože renderer vyriešil pattern na priemernú farbu a namaľoval ju. Alebo šrafovanie unikne: title block, ktorý má byť čisto biely, prevezme diagonálne čiary z detailného pohľadu o niekoľko ciest skôr. Oba sú problémy stavu patternu, a len jeden z nich má vôbec niečo spoločné s kreslením dlaždíc

Prečo tiling pattern presakuje do ďalšej cesty?

Pretože vybrané meno patternu je súčasťou grafického stavu, nie vlastnosťou operátora, ktorý ho použil. ISO 32000-1 §8.6.6.2 definuje farebný priestor Pattern ako taký, ktorého hodnota farby je meno patternu dodané operátoru scn alebo SCN, a každá ďalšia zložka stavu farby sa ukladá pomocou q a obnovuje pomocou Q. Meno patternu musí nasledovať to isté pravidlo. HotPDF ho drží v zázname stavu ako FillPatternName a StrokePatternName, popri rodine farebného priestoru výplne a ťahu, takže Q vráti predchádzajúci výber presne tak, ako vracia predchádzajúci CTM

Uložte toto meno namiesto toho do lokálnej premennej vnútri dispečera operátorov, a prežije každé Q v streame. Zlyhanie sa potom objaví niekde neočakávane: Form XObject nakreslený po ceste s patternom zdedí výber patternu, ktorý jeho vlastný obsahový stream nikdy neurobil, a jeho výplne vyjdú šrafované. Vnorené formuláre to zhoršujú, pretože každá úroveň vnorenia ukladá a obnovuje stav, ktorý zablúdená premenná ignoruje. Nastavenie ne-patternového farebného priestoru pomocou cs alebo CS, alebo vydanie obyčajného g / rg / k, musí tiež vymazať meno patternu, inak zastaraný výber prežije farebný priestor, ktorý mu dal zmysel

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

Pattern sa maľuje cez orez, nikdy ako výplň

Správny model je odčítavací: obmedziť orez zariadenia na tvar, ktorý sa maľuje, potom spustiť obsah patternu vnútri neho. HotPDF nikdy najprv nenakreslí plnú aproximáciu a nepremaľuje ju, pretože medziľahlá plná farba by bola viditeľná cez medzery medzi dlaždicami a bojovala by proti akejkoľvek priehľadnosti v obsahu dlaždice. §8.7.3.2 opisuje tiling pattern ako obsahový stream replikovaný v pevných horizontálnych a vertikálnych intervaloch, a replikácia dáva zmysel iba voči orezu, ktorý už má správny tvar. Pre výplne je konverzia priama: HPDFSelectFillPathClip nastaví mód výplne polygónu na ALTERNATE pre f*, B* a b* a na WINDING pre nonzero varianty, zostaví GDI cestu a prienikom ju vloží do orezu pomocou SelectClipPath. Práve tento jediný riadok robí to, že výplň patternom s even-odd pravidlom zanechá tie isté diery ako plná even-odd výplň, čo je presne to, čo potrebuje šrafovaná oblasť v tvare šišky

Ťahy sú tá časť, ktorú je ľahké pokaziť. Ťahaná cesta nemá vnútro, takže prienik samotnej cesty do orezu vyprodukuje prázdnu oblasť a nič sa nenamaľuje. HPDFSelectStrokePathClip preto najprv zostaví geometrické pero z aktuálneho stavu, pomocou PS_GEOMETRIC s koncovým uzáverom z J, spojením z j, limitom skosenia z M, a PS_USERSTYLE, keď je aktívne pole čiarkovania, potom zavolá WidenPath, aby previedol ťahaný obrys na vyplniteľnú oblasť pred orezaním. Správanie uzáveru, spojenia, skosenia a čiarkovania na ceste ťahanej patternom potom zodpovedá normálnemu ťahu konštrukciou, nie druhou implementáciou. Tu žijú dva úprimné limity: šírky čiar pod jednu jednotku zariadenia sa orezávajú na jeden pixel, a pole čiarkovania sa oreže na šestnásť položiek, čo je strop, ktorý akceptuje ExtCreatePen

Ktoré dlaždice sú vlastne viditeľné?

Viditeľný rozsah pochádza zo spustenia transformácie pospiatky. Umiestnenie dlaždíc sa deje v priestore patternu, ale jediná vec, ktorá vie, koľko strany sa dotýka, je orezová oblasť zariadenia, ktorá je v priestore zariadenia. HotPDF zloží BaseMatrix := CTM * PatternMatrix, invertuje ju, a namapuje štyri rohy GDI orezovej oblasti späť cez inverziu. Osiam zarovnané hranice týchto štyroch namapovaných rohov dávajú obdĺžnik v priestore patternu, ktorý môže byť potenciálne pokrytý, a delenie tohto obdĺžnika hodnotami XStep a YStep voči BBox patternu dáva uzavreté rozsahy indexov. Každá bunka sa potom renderuje s CTM rovným CTM * PatternMatrix * Translate(i * XStep, j * YStep), a orezáva sa druhýkrát na svoj vlastný transformovaný polygón BBox. Tento druhý orez je dôležitý, keď je XStep menšie než šírka bounding boxu, čo je spôsob, akým sa vyjadrujú prekrývajúce sa dizajny dlaždíc; bez neho by susedné bunky maľovali cez seba mimo svojho deklarovaného rozsahu. Ak sa orez jednotlivej bunky vráti ako NULLREGION, bunka sa preskočí bez tokenizácie alebo vykonania čohokoľvek

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

Nefarebné patterny a farba, ktorá prichádza zvonka

Pattern PaintType 2 nesie tvar, ale žiadnu farbu, a farba prichádza spolu s menom patternu. §8.7.3.2 stanovuje, že nefarebný pattern sa používa iba s farebným priestorom Pattern, ktorý deklaruje podkladový priestor, takže scn dostane najprv hodnoty zložiek a meno patternu ako posledné. HotPDF vyrieši tieto zložky cez podkladový priestor uložený na položke farebného priestoru patternu, čo znamená, že nefarebné šrafovanie môže byť tónované atramentom Separation alebo kombináciou DeviceN úplne rovnako ako akákoľvek iná výplň; mechanika tohto rozlíšenia je pokrytá v renderovaní priamych farieb Separation a DeviceN. Vnútri dlaždice sa oba typy maľovania ostro rozchádzajú. Pre PaintType 2 renderer nastaví príznak potlačenia farebných operátorov na dobu trvania dlaždice, takže akékoľvek g, rg, k alebo scn v obsahu patternu sa ignoruje a každá značka preberá externe dodanú farbu. Pre PaintType 1 platí opak: stav výplne a ťahu sa resetuje na predvolené hodnoty PDF, DeviceGray čiernu s identitným farebným priestorom, a dlaždica si farbí sama. Vynechanie tohto resetu umožní, aby farba, ktorá bola náhodou aktuálna pri operátore f, unikla do patternu, ktorý mal byť samopopisný

Prečo sa musí hĺbka zásobníka grafického stavu obnoviť po každej dlaždici?

Pretože obsahový stream patternu môže byť nevyvážený, a škoda sa naprieč bunkami hromadí. Dlaždica, ktorej stream obsahuje tri operátory q a dva operátory Q, zanechá zásobník o jeden rámec hlbší, než začal. Obnovte medzi bunkami iba aktuálny záznam stavu, a hĺbka bude naďalej narastať, takže bunka číslo dvesto vykonáva zo stackového rámca, ktorý patrí bunke číslo stodeväťdesiatdeväť, s akýmkoľvek CTM a orezom, ktoré tento rámec niesol. HotPDF preto pred slučkou dlaždíc urobí snímok záznamu stavu a hĺbky zásobníka a na začiatku každej iterácie zavolá RestoreSnapshot, ktorý orezá zásobník späť na uloženú dĺžku a v jednom kroku znovu nainštaluje uložený stav. Slovník Resources strany a príznak potlačenia farebných operátorov sa obnovujú na tej istej hranici, keďže dlaždica môže odkazovať na svoje vlastné zdroje a nesmie ich odovzdať svojmu susedovi. Stav GDI orezu dostáva rovnaké zaobchádzanie cez pár SaveDC / RestoreDC okolo každej bunky, takže dlaždica, ktorá nainštaluje svoj vlastný orez W n, nemôže zmenšiť oblasť dostupnú pre ďalšiu

Rozpočty, odmietnutia a čo renderer nenakreslí

Tiling patterny sú najľahšie miesto v PDF, kde napísať súbor spôsobujúci odopretie služby, takže limity sú tvrdé čísla, nie heuristiky. Vnáranie patternov je zastropené na hĺbke 4, rovnaká poistka použitá pre rekurziu Form XObject, ktorá zastaví pattern odkazujúci sám na seba cez svoj vlastný slovník zdrojov. Jedno maľovanie cesty môže spolu vykonať najviac 16 384 dlaždíc, počítané dole naprieč vnorenými patternami a resetované iba vtedy, keď začne maľovanie najvonkajšieho patternu. Mriežka dlaždíc, ktorej plánovaný počet buniek prekračuje to, čo z tohto rozpočtu zostáva, sa rovno odmietne, skôr než sa spustí čo i len jedna bunka

Degenerovaná geometria sa odmieta, nie aproximuje. Chýbajúci alebo nulová plocha BBox, XStep alebo YStep, ktorých veľkosť je pod 1e-6, súčin CTM * PatternMatrix bez inverzie, namapované orezové súradnice nad 1e9, alebo veľkosť indexu nad milión, to všetko spôsobí, že maľovanie patternu sa vráti bez kreslenia. Výsledkom je nenamaľovaná oblasť namiesto zaseknutého renderovacieho vlákna, čo je kompromis, ktorý chcete v dávkovom konvertore. Výkon pochádza z jedného rozhodnutia: stream patternu sa tokenizuje raz za maľovanie pomocou HPDFTokenizeContentStream a pole tokenov sa opätovne používa pre každú viditeľnú bunku, takže počet dlaždíc znásobuje náklady na vykonanie, ale nikdy náklady na lexikálnu analýzu

Renderovanie strany s patternom z Delphi

Podpora patternov nič nemení na volajúcom kóde. Načítajte dokument, vyžiadajte stranu, a práca s dlaždicami sa deje vnútri interpretu obsahového streamu, ktorý už riadi renderovanie strany do bitmapy. Ten istý interpret napája bitmapové, metafilové a tlačiarenské kontexty zariadenia, takže šrafovaný výkres, ktorý vyzerá správne v náhľadovej miniatúre, sa vytlačí s rovnakou geometriou dlaždíc. Shading patterny PatternType 2 idú inou vetvou, ktorá zdieľa svoju vyhodnocovaciu cestu s holým operátorom sh, opísaným podrobne v renderovaní axiálnych a radiálnych shadingov

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;

Keď šrafovaná oblasť stále vyzerá zle, skontrolujte tri triedy zlyhania v poradí. Oblasť, ktorá je úplne prázdna, zvyčajne znamená odmietnutie: preskúmajte XStep, YStep a BBox na degenerované hodnoty, alebo spočítajte dlaždice, ktoré by mriežka potrebovala, voči stropu 16 384. Oblasť namaľovaná jednou plochou farbou znamená, že meno patternu sa nikdy nedostalo k operátoru maľovania, čo ukazuje na poradie cs a scn v streame. Pattern, ktorý sa objaví tam, kam nepatrí, znamená obnovu stavu, a miesto, na ktoré sa treba pozrieť, je zaobchádzanie s q / Q okolo formulára alebo cesty, ktorá ho zdedila

Tiling patterny sú jednou z tých funkcií PDF, ktoré zostávajú neviditeľné, kým súbor, ktorý ich potrebuje, nepristane vo vašej schránke, a potom sú celou úlohou. Ak staviate prehliadače výkresov, konvertory technických dokumentov alebo rendery správ na Delphi alebo C++Builder, kompletná komponenta a jej rendrovacie API sú zdokumentované na stránke HotPDF Delphi PDF komponenty