ลายขีดที่เรนเดอร์ออกมาเป็นก้อนสีเทาแบนราบก้อนเดียวคือความล้มเหลวคลาสสิกของ tiling pattern HotPDF component แบบ VCL เนทีฟสำหรับ Delphi และ C++Builder วาด PatternType 1 โดยเปลี่ยน path ปัจจุบันให้เป็น clip ชั่วคราว แล้วเล่น content stream ของ pattern ซ้ำหนึ่งครั้งต่อหนึ่ง tile ที่มองเห็นได้ โดยการเลือก pattern ถูกเก็บไว้ใน graphics state และถูกคืนค่าด้วย q กับ Q
อาการปรากฏออกมาสองแบบ และดูไม่เกี่ยวข้องกันจนกว่าคุณจะรู้สาเหตุ ภาพวาด CAD สูญเสียลาย hatching ของหน้าตัดและกลับมาเป็นสีทึบ เพราะตัวเรนเดอร์แปลง pattern เป็นสีเฉลี่ยแล้ววาดสีนั้นแทน หรือลาย hatching หลุดออกไป title block ที่ควรเป็นสีขาวล้วนกลับได้ลายเส้นทแยงจาก detail view สองพาธก่อนหน้ามาด้วย ทั้งสองคือปัญหาสถานะ pattern และมีแค่หนึ่งในนั้นที่เกี่ยวกับการวาด tile จริง ๆ
ทำไม tiling pattern ถึงเลอะไปยัง path ถัดไป
เพราะชื่อ pattern ที่เลือกไว้เป็นส่วนหนึ่งของ graphics state ไม่ใช่คุณสมบัติของ operator ที่ใช้มัน ISO 32000-1 §8.6.6.2 กำหนด color space แบบ Pattern เป็นค่าสีที่เป็นชื่อ pattern ที่ป้อนให้กับ scn หรือ SCN และทุกองค์ประกอบอื่นของสถานะสีจะถูกบันทึกด้วย q และคืนค่าด้วย Q ชื่อ pattern ก็ต้องทำตามกฎเดียวกัน HotPDF เก็บมันไว้ใน state record เป็น FillPatternName และ StrokePatternName ควบคู่ไปกับตระกูล color space ของ fill และ stroke ดังนั้น Q จึงคืนการเลือกก่อนหน้ากลับมาแบบเดียวกับที่มันคืนค่า CTM ก่อนหน้า
ถ้าเก็บชื่อนั้นไว้ในตัวแปร local ภายใน operator dispatcher แทน มันจะรอดจากทุก Q ใน stream ความล้มเหลวจะปรากฏขึ้นในที่ที่ไม่คาดคิด Form XObject ที่วาดหลัง path ที่มี pattern จะสืบทอดการเลือก pattern ที่ content stream ของมันเองไม่เคยกำหนด และ fill ของมันจะออกมาเป็นลาย hatching form ที่ซ้อนกันทำให้แย่ลงไปอีก เพราะแต่ละระดับการซ้อนจะ push และ pop สถานะที่ตัวแปรที่หลงเหลือไม่สนใจ การตั้ง color space ที่ไม่ใช่ pattern ด้วย cs หรือ CS หรือการออกคำสั่ง g / rg / k ธรรมดา ต้องล้างชื่อ pattern ด้วย มิฉะนั้นการเลือกที่ค้างอยู่จะรอดเกินกว่า color space ที่ให้ความหมายกับมัน
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 ถูกวาดผ่าน clip ไม่ใช่ผ่าน fill
โมเดลที่ถูกต้องคือแบบลบออก จำกัด device clip ให้เป็นรูปทรงที่กำลังถูกวาด แล้วรัน pattern content ภายในนั้น HotPDF ไม่เคยวาดค่าประมาณสีทึบก่อนแล้วทับด้วยสี เพราะสีทึบระหว่างกลางนั้นจะมองเห็นได้ผ่านช่องว่างระหว่าง tile และจะขัดกับความโปร่งใสใด ๆ ใน tile content §8.7.3.2 อธิบาย tiling pattern ว่าเป็น content stream ที่ถูกทำซ้ำในช่วงแนวนอนและแนวตั้งคงที่ และการทำซ้ำจะมีความหมายก็ต่อเมื่อเทียบกับ clip ที่มีรูปทรงถูกต้องอยู่แล้ว สำหรับ fill การแปลงตรงไปตรงมา HPDFSelectFillPathClip ตั้งโหมด fill ของ polygon เป็น ALTERNATE สำหรับ f*, B* และ b* และเป็น WINDING สำหรับตัวแปร nonzero สร้าง GDI path และ intersect เข้ากับ clip ด้วย SelectClipPath บรรทัดเดียวนั้นคือสิ่งที่ทำให้ patterned fill แบบ even-odd เหลือรูโหว่แบบเดียวกับ solid fill แบบ even-odd ซึ่งเป็นสิ่งที่พื้นที่ hatching รูปโดนัทต้องการพอดี
Stroke คือส่วนที่ผิดพลาดได้ง่าย path ที่ stroke ไม่มีพื้นที่ภายใน ดังนั้นการ intersect path เองเข้ากับ clip จะได้พื้นที่ว่างเปล่าและไม่มีอะไรถูกวาด HPDFSelectStrokePathClip จึงสร้างปากกาเชิงเรขาคณิตจากสถานะปัจจุบันก่อน โดยใช้ PS_GEOMETRIC ด้วย end cap จาก J, join จาก j, miter limit จาก M และ PS_USERSTYLE เมื่อมี dash array ทำงานอยู่ แล้วเรียก WidenPath เพื่อแปลง outline ของ stroke ให้เป็นพื้นที่ที่ fill ได้ก่อนที่จะ clip พฤติกรรม cap, join, miter และ dash บน path ที่มี pattern stroke จึงตรงกับ stroke ปกติโดยโครงสร้าง ไม่ใช่โดยการทำงานที่สอง มีขีดจำกัดตรงไปตรงมาสองอย่างอยู่ตรงนี้ ความกว้างของเส้นที่ต่ำกว่าหนึ่ง device unit จะถูก clamp เป็นหนึ่งพิกเซล และ dash array จะถูกตัดที่สิบหกรายการ ซึ่งเป็นเพดานที่ ExtCreatePen รับได้
tile ไหนที่มองเห็นได้จริง
ช่วงที่มองเห็นได้มาจากการรัน transform ย้อนกลับ การวาง tile เกิดขึ้นใน pattern space แต่สิ่งเดียวที่รู้ว่ามีส่วนไหนของหน้าที่ถูกแตะต้องคือ device clip box ซึ่งอยู่ใน device space HotPDF ประกอบ BaseMatrix := CTM * PatternMatrix กลับด้าน แล้วแม็ปสี่มุมของ GDI clip box ย้อนกลับผ่าน inverse ขอบเขตแบบ axis-aligned ของสี่มุมที่แม็ปแล้วให้สี่เหลี่ยม pattern-space ที่อาจถูกครอบคลุมได้ และการหารสี่เหลี่ยมนั้นด้วย XStep และ YStep เทียบกับ pattern BBox ให้ช่วงดัชนีแบบปิด แต่ละ cell จะเรนเดอร์ด้วย CTM ของ CTM * PatternMatrix * Translate(i * XStep, j * YStep) และถูก clip อีกครั้งเป็นครั้งที่สองเข้ากับ polygon BBox ที่แปลงแล้วของตัวมันเอง clip ครั้งที่สองนั้นสำคัญเมื่อ XStep เล็กกว่าความกว้างของ bounding box ซึ่งเป็นวิธีแสดงลาย tile ที่ทับซ้อนกัน ถ้าไม่มี clip นี้ cell ข้างเคียงจะวาดทับกันนอกขอบเขตที่ประกาศไว้ ถ้า clip ต่อ cell กลับมาเป็น NULLREGION cell นั้นจะถูกข้ามไปโดยไม่ tokenize หรือรันอะไรเลย
// 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));
Pattern แบบไม่มีสีและสีที่มาจากภายนอก
PaintType 2 pattern พกรูปทรงแต่ไม่มีสี และสีมาพร้อมกับชื่อ pattern §8.7.3.2 ระบุว่า uncolored pattern ถูกใช้เฉพาะกับ Pattern color space ที่ประกาศ underlying space เท่านั้น ดังนั้น scn จึงรับค่า component ก่อนแล้วชื่อ pattern ทีหลัง HotPDF คำนวณ component เหล่านั้นผ่าน underlying space ที่เก็บไว้บน entry ของ pattern color space ซึ่งหมายความว่าลาย hatch แบบไม่มีสีสามารถแต้มสีด้วยหมึก Separation หรือ DeviceN combination ได้เหมือน fill อื่น ๆ ทุกประการ กลไกของการคำนวณนั้นครอบคลุมใน การเรนเดอร์สี spot แบบ Separation และ DeviceN ภายใน tile ทั้งสอง paint type ต่างกันอย่างชัดเจน สำหรับ PaintType 2 ตัวเรนเดอร์ตั้ง flag ระงับ color-operator ตลอดระยะเวลาของ tile ดังนั้น g, rg, k หรือ scn ใด ๆ ใน pattern content จะถูกละเว้น และทุกรอยวาดจะใช้สีที่ป้อนจากภายนอก สำหรับ PaintType 1 ตรงกันข้ามเลย สถานะ fill และ stroke ถูกรีเซ็ตเป็นค่าเริ่มต้นของ PDF คือ DeviceGray สีดำพร้อม color space แบบ identity และ tile จะแต้มสีตัวเอง การข้ามการรีเซ็ตนั้นจะปล่อยให้สีที่บังเอิญเป็นค่าปัจจุบันตอน operator f รั่วไหลเข้าไปใน pattern ที่ควรจะอธิบายตัวเองได้
ทำไมต้องคืนความลึกของ graphics state stack หลังทุก tile
เพราะ pattern content stream ได้รับอนุญาตให้ไม่สมดุลได้ และความเสียหายสะสมข้าม cell tile ที่ stream ของมันมี q operator สามตัวและ Q operator สองตัว จะเหลือ stack ลึกกว่าตอนเริ่มต้นหนึ่งเฟรม ถ้าคืนค่าแค่ state record ปัจจุบันระหว่าง cell ความลึกจะเพิ่มขึ้นเรื่อย ๆ ดังนั้น cell ที่สองร้อยจะรันจาก stack frame ที่เป็นของ cell ที่หนึ่งร้อยเก้าสิบเก้า พร้อม CTM และ clip ที่เฟรมนั้นพกมา HotPDF จึงถ่าย snapshot ของ state record และความลึกของ stack ก่อน tile loop และเรียก RestoreSnapshot ที่จุดเริ่มต้นของทุก iteration ซึ่งตัด stack กลับไปที่ความยาวที่บันทึกไว้และติดตั้ง state ที่บันทึกไว้กลับคืนในขั้นตอนเดียว Resources dictionary ของหน้าและ flag ระงับ color-operator ถูกคืนค่าที่ขอบเขตเดียวกัน เพราะ tile อาจอ้างอิง resource ของตัวเองและต้องไม่ส่งต่อให้ tile ข้างเคียง สถานะ clip ของ GDI ก็ได้รับการปฏิบัติเดียวกันผ่านคู่ SaveDC / RestoreDC รอบแต่ละ cell ดังนั้น tile ที่ติดตั้ง clip W n ของตัวเองจะไม่สามารถหด region ที่มีให้กับ cell ถัดไปได้
งบประมาณ การปฏิเสธ และสิ่งที่ตัวเรนเดอร์จะไม่วาด
Tiling pattern คือจุดที่ง่ายที่สุดใน PDF ในการเขียนไฟล์แบบ denial-of-service ดังนั้น limit จึงเป็นตัวเลขที่แน่นอน ไม่ใช่ heuristic การซ้อน pattern ถูกจำกัดที่ความลึก 4 ซึ่งเป็นตัวป้องกันเดียวกับที่ใช้กับ Form XObject recursion ซึ่งหยุด pattern ที่อ้างอิงตัวเองผ่าน resource dictionary ของตัวเอง การวาด path เดียวสามารถรัน tile ได้สูงสุด 16,384 tile รวมกัน นับถอยหลังข้าม pattern ที่ซ้อนกัน และรีเซ็ตเมื่อการวาด pattern ชั้นนอกสุดเริ่มต้นเท่านั้น grid ของ tile ที่มีจำนวน cell ที่วางแผนไว้เกินสิ่งที่เหลือของงบประมาณนั้นจะถูกปฏิเสธทันที ก่อนที่ cell แม้แต่ตัวเดียวจะรัน
รูปทรงที่เสื่อมสภาพจะถูกปฏิเสธ ไม่ใช่ประมาณค่า BBox ที่ขาดหายไปหรือมีพื้นที่เป็นศูนย์ XStep หรือ YStep ที่มีขนาดต่ำกว่า 1e-6 ผลคูณ CTM * PatternMatrix ที่ไม่มี inverse พิกัด clip ที่แม็ปแล้วเกิน 1e9 หรือขนาดของดัชนีเกินหนึ่งล้าน ทั้งหมดนี้ทำให้การวาด pattern คืนค่าโดยไม่วาดอะไรเลย ผลลัพธ์คือพื้นที่ที่ไม่ถูกวาดแทนที่จะเป็น render thread ที่ค้าง ซึ่งเป็นการแลกเปลี่ยนที่คุณต้องการในตัวแปลงไฟล์แบบแบทช์ ประสิทธิภาพมาจากการตัดสินใจหนึ่งอย่าง pattern stream จะถูก tokenize ครั้งเดียวต่อการวาดหนึ่งครั้งด้วย HPDFTokenizeContentStream และ array ของ token จะถูกใช้ซ้ำในทุก cell ที่มองเห็นได้ ดังนั้นจำนวน tile จะคูณต้นทุนการรัน แต่ไม่เคยคูณต้นทุนการ lex
การเรนเดอร์หน้าที่มีลายจาก Delphi
ไม่มีอะไรเกี่ยวกับการรองรับ pattern ที่เปลี่ยนโค้ดที่เรียกเลย โหลดเอกสาร ขอหน้า แล้วงาน tiling จะเกิดขึ้นภายใน content-stream interpreter ที่ การเรนเดอร์หน้าเป็น bitmap ขับเคลื่อนอยู่แล้ว interpreter เดียวกันนี้ป้อน bitmap, metafile และ printer device context ดังนั้นภาพวาดที่มีลายที่ดูถูกต้องใน thumbnail ตัวอย่างก็จะพิมพ์ออกมาด้วยเรขาคณิตของ tile แบบเดียวกัน PatternType 2 shading pattern ใช้สาขาที่ต่างออกไปซึ่งใช้เส้นทางการคำนวณร่วมกับ operator sh เปล่า ๆ อธิบายรายละเอียดไว้ใน การเรนเดอร์ shading แบบ axial และ radial
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;
เมื่อบริเวณที่มี pattern ยังดูผิดอยู่ ให้ตรวจสอบสามประเภทความล้มเหลวตามลำดับ บริเวณที่ว่างเปล่าทั้งหมดมักหมายถึงการปฏิเสธ ตรวจสอบ XStep, YStep และ BBox ว่ามีค่าที่เสื่อมสภาพหรือไม่ หรือนับจำนวน tile ที่ grid ต้องการเทียบกับเพดาน 16,384 บริเวณที่ถูกวาดด้วยสีทึบเดียวหมายความว่าชื่อ pattern ไม่เคยไปถึง paint operator ซึ่งชี้ไปที่ลำดับ cs กับ scn ใน stream pattern ที่ปรากฏในที่ที่ไม่ควรอยู่หมายถึงการคืนค่าสถานะ และจุดที่ควรดูคือการจัดการ q / Q รอบ form หรือ path ที่สืบทอดมัน
Tiling pattern เป็นหนึ่งในฟีเจอร์ PDF ที่มองไม่เห็นจนกว่าไฟล์ที่ต้องการมันจะมาถึงกล่องข้อความของคุณ แล้วมันก็กลายเป็นงานทั้งหมด ถ้าคุณกำลังสร้าง drawing viewer ตัวแปลงเอกสารทางวิศวกรรม หรือตัวเรนเดอร์รายงานบน Delphi หรือ C++Builder component ฉบับเต็มและ API การเรนเดอร์ของมันมีเอกสารอยู่ที่หน้า HotPDF Delphi PDF component