Šrafování, které se vykreslí jako jeden plochý šedý blok, je klasické selhání dlaždicového vzoru. HotPDF, nativní VCL komponenta PDF pro Delphi a C++Builder, vykresluje PatternType 1 tak, že aktuální cestu promění v dočasný ořez a přehraje content stream vzoru jednou za každou viditelnou dlaždici, přičemž výběr vzoru je uchováván ve stavu grafiky a obnovován pomocí q a Q
Příznaky přicházejí ve dvou variantách a vypadají nesouvisle, dokud nepoznáte příčinu. CAD výkres ztratí šrafování řezu a vrátí se jako plné výplně, protože renderer vyhodnotil vzor jako průměrnou barvu a tu vykreslil. Nebo šrafování unikne ven: popisové pole, které má být čistě bílé, převezme diagonální čáry z detailního pohledu o pár cest dříve. Oba případy jsou problémy se stavem vzoru, a jen jeden z nich se skutečně týká vykreslování dlaždic
Proč se dlaždicový vzor rozlévá do další cesty?
Protože vybraný název vzoru je součástí stavu grafiky, ne vlastností operátoru, který jej použil. ISO 32000-1 §8.6.6.2 definuje barevný prostor Pattern jako takový, jehož barevná hodnota je název vzoru předaný do scn nebo SCN, a každá další složka barevného stavu je uložena pomocí q a obnovena pomocí Q. Název vzoru musí dodržovat stejné pravidlo. HotPDF jej udržuje v záznamu stavu jako FillPatternName a StrokePatternName, vedle rodiny barevného prostoru pro výplň a tah, takže Q vrátí předchozí výběr přesně tak, jak vrací předchozí CTM
Uložíte-li tento název místo toho do lokální proměnné uvnitř dispečeru operátorů, přežije každé Q ve streamu. Chyba se pak objeví někde nečekaně: Form XObject vykreslený po šrafované cestě zdědí výběr vzoru, který jeho vlastní content stream nikdy nenastavil, a jeho výplně vyjdou vyšrafované. Vnořené formuláře to zhoršují, protože každá úroveň vnoření ukládá a obnovuje stav, který zatoulaná proměnná ignoruje. Nastavení nevzorového barevného prostoru pomocí cs nebo CS, nebo vydání obyčejného g / rg / k, musí rovněž vymazat název vzoru, jinak zastaralý výběr přežije barevný prostor, který mu dával smysl
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
Vzor se vykresluje přes ořez, nikdy jako výplň
Správný model je odečítací: omezit ořez zařízení na tvar, který se vykresluje, a teprve pak uvnitř něj spustit obsah vzoru. HotPDF nikdy nejprve nenakreslí plnou aproximaci a nepřemaluje ji, protože mezilehlá plná plocha by byla vidět skrz mezery mezi dlaždicemi a bila by se s jakoukoli průhledností v obsahu dlaždice. §8.7.3.2 popisuje dlaždicový vzor jako content stream replikovaný v pevných horizontálních a vertikálních intervalech, a replikace dává smysl pouze vůči ořezu, který už má správný tvar. Pro výplně je převod přímý: HPDFSelectFillPathClip nastaví mód výplně polygonu na ALTERNATE pro f*, B* a b* a na WINDING pro varianty nonzero, sestaví GDI cestu a protne ji do ořezu pomocí SelectClipPath. Právě tento jeden řádek zajišťuje, že vzorovaná výplň s pravidlem sudý-lichý zanechá stejné díry jako plná výplň se sudým-lichým pravidlem, což je přesně to, co potřebuje šrafovaná oblast ve tvaru koblihy
Tahy jsou částí, kde se snadno chybuje. Tažená cesta nemá vnitřek, takže protnutí samotné cesty do ořezu vytvoří prázdný region a nic se nevykreslí. HPDFSelectStrokePathClip proto nejprve sestaví geometrické pero z aktuálního stavu pomocí PS_GEOMETRIC s koncovým zakončením z J, spojem z j, limitem ostrého hrotu z M a PS_USERSTYLE, je-li aktivní pole čárkování, a poté volá WidenPath, aby převedl tažený obrys na vyplnitelnou oblast ještě před ořezem. Chování zakončení, spoje, limitu hrotu a čárkování na cestě tažené vzorem pak odpovídá běžnému tahu díky konstrukci, ne díky druhé implementaci. Zde žijí dva poctivé limity: šířky čar pod jednu jednotku zařízení jsou přiskřípnuty na jeden pixel a pole čárkování je oříznuto na šestnáct položek, což je strop, který přijímá ExtCreatePen
Které dlaždice jsou skutečně viditelné?
Viditelný rozsah se získává spuštěním transformace pozpátku. Umístění dlaždic probíhá v prostoru vzoru, ale jediná věc, která ví, kolik ze stránky je zasaženo, je ořezový box zařízení, který je v prostoru zařízení. HotPDF složí BaseMatrix := CTM * PatternMatrix, invertuje ji a namapuje čtyři rohy GDI ořezového boxu zpět přes inverzní matici. Osově zarovnané hranice těchto čtyř namapovaných rohů dávají obdélník v prostoru vzoru, který může být pokryt, a vydělení tohoto obdélníku hodnotami XStep a YStep vůči BBox vzoru dává uzavřené indexové rozsahy. Každá buňka se pak vykresluje s CTM rovným CTM * PatternMatrix * Translate(i * XStep, j * YStep) a je ořezána podruhé do vlastního transformovaného polygonu BBox. Tento druhý ořez má význam, když je XStep menší než šířka ohraničujícího boxu, což je způsob, jakým se vyjadřují překrývající se návrhy dlaždic; bez něj by sousední buňky malovaly přes sebe navzájem mimo svůj deklarovaný rozsah. Vrátí-li se ořez jednotlivé buňky jako NULLREGION, buňka se přeskočí bez tokenizace nebo spuštění čehokoli
// 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));
Nebarvené vzory a barva, která přichází zvenčí
Vzor typu PaintType 2 nese tvar, ale žádnou barvu, a barva přichází spolu s názvem vzoru. §8.7.3.2 stanovuje, že nebarvený vzor se používá pouze s barevným prostorem Pattern, který deklaruje podkladový prostor, takže scn nejprve přijme hodnoty složek a název vzoru jako poslední. HotPDF vyhodnocuje tyto složky přes podkladový prostor uložený v položce barevného prostoru vzoru, což znamená, že nebarvenou šrafuru lze tónovat inkoustem Separation nebo kombinací DeviceN úplně stejně jako jakoukoli jinou výplň; mechanika tohoto vyhodnocení je popsána v článku o vykreslování přímých barev Separation a DeviceN. Uvnitř dlaždice se oba typy vykreslování ostře rozcházejí. Pro PaintType 2 renderer po dobu dlaždice nastaví příznak potlačení barevných operátorů, takže jakékoli g, rg, k nebo scn v obsahu vzoru je ignorováno a každá značka přebírá barvu dodanou zvenčí. Pro PaintType 1 platí opak: stav výplně a tahu se resetuje na výchozí hodnoty PDF, DeviceGray černá s identitním barevným prostorem, a dlaždice si barvu určuje sama. Vynechání tohoto resetu umožní, aby barva, která byla náhodou aktuální v okamžiku operátoru f, prosákla do vzoru, jenž měl být sám o sobě popisný
Proč musí být hloubka zásobníku stavu grafiky obnovena po každé dlaždici?
Protože content stream vzoru smí být nevyvážený a škoda se násobí napříč buňkami. Dlaždice, jejíž stream obsahuje tři operátory q a dva operátory Q, ponechá zásobník o jeden snímek hlubší, než na počátku. Obnovíte-li mezi buňkami pouze aktuální záznam stavu, hloubka dál narůstá, takže buňka číslo dvě stě se provede ze snímku zásobníku, který patří buňce číslo sto devadesát devět, se vším CTM a ořezem, které tento snímek nesl. HotPDF proto před smyčkou dlaždic pořídí snímek záznamu stavu a hloubky zásobníku a na začátku každé iterace zavolá RestoreSnapshot, což zásobník zkrátí zpět na uloženou délku a v jednom kroku znovu nainstaluje uložený stav. Slovník zdrojů stránky Resources a příznak potlačení barevných operátorů se obnovují na stejné hranici, protože dlaždice může odkazovat na vlastní zdroje a nesmí je předat sousední dlaždici. Stav ořezu GDI dostává stejné zacházení přes dvojici SaveDC / RestoreDC kolem každé buňky, takže dlaždice, která si nainstaluje vlastní ořez W n, nemůže zmenšit oblast dostupnou pro tu další
Rozpočty, odmítnutí a co renderer nevykreslí
Dlaždicové vzory jsou nejsnadnějším místem v PDF, kde napsat soubor pro odepření služby, takže limity jsou pevná čísla, ne heuristiky. Vnoření vzorů je omezeno na hloubku 4, stejnou ochranu používanou pro rekurzi Form XObject, což zastaví vzor, který odkazuje sám na sebe přes vlastní slovník zdrojů. Jedno vykreslení cesty smí celkem provést nejvýše 16 384 dlaždic, počítáno napříč vnořenými vzory a resetováno pouze při zahájení vykreslování nejvnějšího vzoru. Mřížka dlaždic, jejíž plánovaný počet buněk překračuje to, co zbývá z tohoto rozpočtu, je rovnou odmítnuta, ještě před spuštěním jediné buňky
Degenerovaná geometrie je odmítnuta, ne aproximována. Chybějící nebo nulová plocha BBox, hodnota XStep nebo YStep s velikostí pod 1e-6, součin CTM * PatternMatrix bez inverze, namapované souřadnice ořezu za hranicí 1e9 nebo velikost indexu za milionem — to vše způsobí, že se vykreslení vzoru vrátí bez kreslení. Výsledkem je nevykreslená oblast místo zaseknutého vykreslovacího vlákna, což je přesně ten kompromis, který chcete v dávkovém konvertoru. Výkon plyne z jednoho rozhodnutí: stream vzoru je tokenizován jednou za vykreslení pomocí HPDFTokenizeContentStream a pole tokenů se znovu použije pro každou viditelnou buňku, takže počet dlaždic násobí náklady na provedení, ale nikdy náklady na lexikální analýzu
Vykreslení vzorované stránky z Delphi
Podpora vzorů nijak nemění volající kód. Načtěte dokument, vyžádejte si stránku a práce s dlaždicemi proběhne uvnitř interpretu content streamu, který už řídí vykreslování stránky do bitmapy. Tentýž interpret napájí bitmapové, metasouborové i tiskárnové device kontexty, takže šrafovaný výkres, který vypadá správně v náhledové miniatuře, se stejnou geometrií dlaždic i vytiskne. Stínovací vzory typu PatternType 2 jdou jinou větví, která sdílí svou vyhodnocovací cestu s holým operátorem sh, popsanou podrobně v článku o vykreslování axiálních a radiálních stínování
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;
Když vzorovaná oblast stále vypadá špatně, projděte tři třídy selhání v pořadí. Oblast, která je zcela prázdná, obvykle znamená odmítnutí: zkontrolujte XStep, YStep a BBox na degenerované hodnoty, nebo spočítejte, kolik dlaždic by mřížka potřebovala, proti stropu 16 384. Oblast vykreslená v jedné ploché barvě znamená, že název vzoru se nikdy nedostal k vykreslovacímu operátoru, což ukazuje na pořadí cs a scn ve streamu. Vzor, který se objeví tam, kam nepatří, znamená problém s obnovením stavu, a místem, kam se podívat, je zpracování q / Q kolem formuláře nebo cesty, která jej zdědila
Dlaždicové vzory patří mezi funkce PDF, které zůstávají neviditelné, dokud vám do schránky nepřistane soubor, který je potřebuje — a pak jsou celou náplní práce. Pokud stavíte prohlížeče výkresů, konvertory technických dokumentů nebo renderery reportů na Delphi nebo C++Builder, kompletní komponenta i její vykreslovací API jsou zdokumentovány na stránce komponenty HotPDF pro Delphi