Hașurarea care se randează ca un singur bloc gri plat este eșecul clasic al modelului tip tiling. HotPDF, componenta VCL PDF nativă pentru Delphi și C++Builder, pictează PatternType 1 transformând calea curentă într-un decupaj temporar și rerulând fluxul de conținut al modelului o dată per dală vizibilă, cu selecția modelului păstrată în starea grafică și restaurată prin q și Q
Simptomele apar în două forme, și par fără legătură până când cunoașteți cauza. Un desen CAD își pierde hașurarea de secțiune și revine ca umpleri solide, deoarece randorul a rezolvat modelul la o culoare medie și a pictat asta. Sau hașurarea evadează: un chenar de titlu care ar trebui să fie alb simplu preia liniile diagonale dintr-o vedere de detaliu de mai devreme. Ambele sunt probleme de stare a modelului, și doar una dintre ele are de-a face cu desenarea dalelor propriu-zisă
De ce se scurge un model de tiling pe calea următoare?
Pentru că numele modelului selectat face parte din starea grafică, nu o proprietate a operatorului care l-a folosit. ISO 32000-1 §8.6.6.2 definește un spațiu de culoare Pattern ca unul a cărui valoare de culoare este un nume de model furnizat lui scn sau SCN, iar fiecare altă componentă a stării de culoare este salvată de q și restaurată de Q. Numele modelului trebuie să urmeze aceeași regulă. HotPDF îl păstrează în înregistrarea de stare ca FillPatternName și StrokePatternName, alături de familia de spațiu de culoare pentru umplere și contur, astfel încât un Q pune înapoi selecția anterioară exact cum pune înapoi CTM-ul anterior
Stocați acel nume într-o variabilă locală în interiorul dispatcher-ului de operatori, și supraviețuiește oricărui Q din flux. Eșecul apare atunci undeva neașteptat: un Form XObject desenat după calea cu model moștenește o selecție de model pe care propriul lui flux de conținut nu a făcut-o niciodată, iar umplerile lui ies hașurate. Formularele imbricate înrăutățesc lucrurile, deoarece fiecare nivel de imbricare pune și scoate stare pe care variabila rătăcită o ignoră. Setarea unui spațiu de culoare non-model cu cs sau CS, sau emiterea unui simplu g / rg / k, trebuie de asemenea să șteargă numele modelului, altfel selecția învechită supraviețuiește spațiului de culoare care i-a dat sens
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
Un model este pictat printr-un decupaj, niciodată ca o umplere
Modelul corect este subtractiv: restrângeți decupajul dispozitivului la forma care este pictată, apoi rulați conținutul modelului în interiorul lui. HotPDF nu desenează niciodată o aproximare solidă mai întâi și o suprapictează, deoarece solidul intermediar ar fi vizibil prin golurile dintre dale și ar intra în conflict cu orice transparență din conținutul dalei. §8.7.3.2 descrie un model tip tiling ca un flux de conținut replicat la intervale orizontale și verticale fixe, iar replicarea are sens doar față de un decupaj care are deja forma corectă. Pentru umpleri, conversia este directă: HPDFSelectFillPathClip setează modul de umplere a poligonului la ALTERNATE pentru f*, B* și b* și la WINDING pentru variantele nonzero, construiește calea GDI și o intersectează cu decupajul via SelectClipPath. Acea singură linie este ceea ce face ca o umplere cu model par-impar să lase aceleași găuri ca o umplere solidă par-impar, exact ce are nevoie o regiune hașurată în formă de gogoașă
Conturile sunt partea ușor de greșit. O cale conturată nu are interior, așa că intersectarea căii în sine cu decupajul dă o regiune goală și nimic nu se pictează. HPDFSelectStrokePathClip construiește deci mai întâi un condei geometric din starea curentă, folosind PS_GEOMETRIC cu capătul din J, îmbinarea din j, limita miter din M și PS_USERSTYLE când un array de liniuțe este activ, apoi apelează WidenPath pentru a converti conturul trasat într-o regiune care poate fi umplută înainte de decupare. Comportamentul capătului, îmbinării, miter-ului și liniuțelor pe o cale conturată cu model se potrivește apoi cu un contur normal prin construcție, nu printr-o a doua implementare. Trăiesc aici două limite oneste: lățimile de linie sub o unitate de dispozitiv sunt fixate la un pixel, iar array-ul de liniuțe este trunchiat la șaisprezece intrări, plafonul pe care ExtCreatePen îl acceptă
Care dale sunt de fapt vizibile?
Intervalul vizibil vine din rularea transformării înapoi. Plasarea dalelor are loc în spațiul modelului, dar singurul lucru care știe cât din pagină este atins este caseta de decupaj a dispozitivului, care este în spațiul dispozitivului. HotPDF compune BaseMatrix := CTM * PatternMatrix, o inversează și mapează cele patru colțuri ale casetei de decupaj GDI înapoi prin inversă. Limitele aliniate la axă ale acelor patru colțuri mapate dau dreptunghiul din spațiul modelului care poate fi posibil acoperit, iar împărțirea acelui dreptunghi la XStep și YStep față de BBox al modelului dă intervale de index închise. Fiecare celulă se randează apoi cu un CTM de CTM * PatternMatrix * Translate(i * XStep, j * YStep), și este decupată a doua oară la propriul ei poligon BBox transformat. Acel al doilea decupaj contează când XStep este mai mic decât lățimea casetei de delimitare, care este modul în care sunt exprimate designurile de dale suprapuse; fără el, celulele vecine s-ar picta una peste alta în afara întinderii lor declarate. Dacă decupajul per celulă revine ca NULLREGION, celula este omisă fără a se tokeniza sau executa nimic
// 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));
Modele necolorate și culoarea care vine din exterior
Un model PaintType 2 poartă formă dar nicio culoare, iar culoarea sosește odată cu numele modelului. §8.7.3.2 specifică faptul că un model necolorat este folosit doar cu un spațiu de culoare Pattern care declară un spațiu subiacent, așa că scn primește mai întâi valorile componentelor și numele modelului la sfârșit. HotPDF rezolvă acele componente prin spațiul subiacent stocat pe intrarea spațiului de culoare al modelului, ceea ce înseamnă că o hașură necolorată poate fi tentată cu o cerneală Separation sau o combinație DeviceN exact ca orice altă umplere; mecanica acelei rezolvări este tratată în randarea culorilor spot Separation și DeviceN. În interiorul dalei, cele două tipuri de pictare diverg puternic. Pentru PaintType 2, randorul setează un flag de suprimare a operatorului de culoare pe durata dalei, astfel încât orice g, rg, k sau scn din conținutul modelului este ignorat, iar fiecare marcaj preia culoarea furnizată extern. Pentru PaintType 1 se aplică opusul: starea de umplere și contur este resetată la valorile implicite PDF, negru DeviceGray cu un spațiu de culoare identitate, iar dala se colorează singură. Omiterea acelei resetări lasă culoarea care se întâmpla să fie curentă la operatorul f să se scurgă într-un model care ar fi trebuit să fie auto-descriptiv
De ce trebuie restaurată adâncimea stivei de stare grafică după fiecare dală?
Pentru că un flux de conținut al modelului are voie să fie dezechilibrat, iar paguba se acumulează pe celule. O dală al cărei flux conține trei operatori q și doi operatori Q lasă stiva o cadru mai adâncă decât a început. Restaurați doar înregistrarea de stare curentă între celule și adâncimea continuă să crească, așa că celula numărul două sute execută dintr-un cadru de stivă care aparține celulei numărul o sută nouăzeci și nouă, cu orice CTM și decupaj purta acel cadru. HotPDF face deci un instantaneu al înregistrării de stare și al adâncimii stivei înainte de bucla de dale și apelează RestoreSnapshot la începutul fiecărei iterații, care trunchiază stiva înapoi la lungimea salvată și reinstalează starea salvată într-un singur pas. Dicționarul de resurse al paginii și flag-ul de suprimare a operatorului de culoare sunt restaurate la aceeași graniță, deoarece o dală poate referenția propriile ei resurse și nu trebuie să le predea vecinei sale. Starea de decupaj GDI primește același tratament printr-o pereche SaveDC / RestoreDC în jurul fiecărei celule, astfel încât o dală care își instalează propriul decupaj W n nu poate micșora regiunea disponibilă pentru următoarea
Bugete, refuzuri și ce nu va desena randorul
Modelele de tiling sunt locul cel mai ușor dintr-un PDF pentru a scrie un fișier de refuz de serviciu, așa că limitele sunt numere stricte, nu euristici. Imbricarea modelelor este plafonată la adâncimea 4, aceeași gardă folosită pentru recursiunea Form XObject, care oprește un model care se referențiază pe sine prin propriul dicționar de resurse. O singură pictare de cale poate executa cel mult 16.384 de dale în total, numărate descrescător de-a lungul modelelor imbricate și resetate doar când începe pictarea celui mai exterior model. O grilă de dale al cărei număr planificat de celule depășește ce a mai rămas din acel buget este respinsă direct, înainte ca vreo celulă să ruleze
Geometria degenerată este refuzată, nu aproximată. Un BBox lipsă sau cu suprafață zero, un XStep sau YStep a cărui magnitudine este sub 1e-6, un produs CTM * PatternMatrix fără inversă, coordonate de decupaj mapate peste 1e9, sau o magnitudine de index peste un milion, toate cauzează ca pictarea modelului să revină fără a desena. Rezultatul este o regiune nepictată în loc de un fir de randare blocat, ceea ce este compromisul dorit într-un convertor batch. Performanța vine dintr-o singură decizie: fluxul modelului este tokenizat o singură dată per pictare cu HPDFTokenizeContentStream, iar array-ul de token-uri este reutilizat pentru fiecare celulă vizibilă, astfel încât numărul de dale multiplică costul de execuție, dar niciodată costul de lexare
Randarea unei pagini cu model din Delphi
Nimic legat de suportul modelelor nu schimbă codul apelant. Încărcați documentul, cereți o pagină, iar lucrul de tiling are loc în interiorul interpretorului de flux de conținut pe care randarea pagină-la-bitmap îl conduce deja. Același interpretor alimentează contexte de dispozitiv bitmap, metafile și imprimantă, așa că un desen hașurat care arată corect într-un thumbnail de previzualizare se imprimă cu aceeași geometrie de dale. Modelele de umbrire PatternType 2 iau o ramură diferită care își împarte calea de evaluare cu operatorul simplu sh, descris în detaliu la randarea umbririlor axiale și radiale
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;
Când o regiune cu model tot arată greșit, verificați cele trei clase de eșec în ordine. O regiune complet goală înseamnă de obicei un refuz: inspectați XStep, YStep și BBox pentru valori degenerate, sau numărați dalele de care ar avea nevoie grila față de plafonul de 16.384. O regiune pictată într-o singură culoare plată înseamnă că numele modelului nu a ajuns niciodată la operatorul de pictare, ceea ce indică spre ordinea cs și scn din flux. Un model care apare unde nu îi este locul înseamnă restaurare de stare, iar locul de verificat este gestionarea q / Q în jurul formularului sau căii care l-a moștenit
Modelele de tiling sunt una din acele funcționalități PDF care rămân invizibile până când fișierul care are nevoie de ele aterizează în căsuța dumneavoastră de mesaje, iar apoi devin întreaga sarcină. Dacă construiți vizualizatoare de desene, convertoare de documente de inginerie sau randoare de rapoarte pe Delphi sau C++Builder, componenta completă și API-ul ei de randare sunt documentate pe pagina componentei PDF HotPDF pentru Delphi