Bài viết kỹ thuật

Render Tiling Pattern PDF trong Delphi với HotPDF

Hatching (gạch chéo) render thành một khối xám phẳng duy nhất là lỗi tiling-pattern kinh điển. HotPDF, component PDF VCL bản địa cho Delphi và C++Builder, vẽ PatternType 1 bằng cách biến đường path hiện tại thành một clip tạm thời và phát lại content stream của pattern một lần cho mỗi tile hiển thị, với việc chọn pattern được giữ trong graphics state và khôi phục bởi qQ

Triệu chứng xuất hiện dưới hai dạng, và chúng trông không liên quan cho đến khi bạn biết nguyên nhân. Một bản vẽ CAD mất phần gạch chéo mặt cắt và trả về thành các vùng fill đặc, vì trình render đã quy pattern về một màu trung bình rồi tô màu đó. Hoặc phần gạch chéo lan ra ngoài: một khung tiêu đề lẽ ra phải trắng trơn lại nhặt các đường chéo từ một view chi tiết trước đó hai path. Cả hai đều là vấn đề trạng thái pattern, và chỉ một trong hai liên quan đến việc vẽ tile

Vì sao một tiling pattern lại lan sang path tiếp theo?

Vì tên pattern đã chọn là một phần của graphics state, không phải một thuộc tính của toán tử đã dùng nó. ISO 32000-1 §8.6.6.2 định nghĩa một Pattern color space là một không gian mà giá trị màu của nó là một tên pattern được cấp cho scn hoặc SCN, và mọi thành phần khác của trạng thái màu được lưu bởi q và khôi phục bởi Q. Tên pattern phải tuân theo cùng quy tắc đó. HotPDF giữ nó trong bản ghi trạng thái dưới dạng FillPatternNameStrokePatternName, cùng với họ color space fill và stroke, nên một Q đặt lại lựa chọn trước đó y hệt cách nó đặt lại CTM trước đó

Thay vào đó, lưu tên đó trong một biến cục bộ bên trong bộ điều phối toán tử, và nó sẽ sống sót qua mọi Q trong stream. Thất bại sau đó xuất hiện ở một nơi không ngờ tới: một Form XObject được vẽ sau đường path có pattern thừa hưởng một lựa chọn pattern mà content stream của chính nó chưa bao giờ đặt ra, và các fill của nó xuất hiện với gạch chéo. Form lồng nhau khiến việc này tệ hơn, vì mỗi cấp lồng đẩy và bật trạng thái mà biến lạc kia bỏ qua. Đặt một color space không phải pattern bằng cs hoặc CS, hoặc phát ra một g / rg / k trơn, cũng phải xóa tên pattern, nếu không lựa chọn cũ sẽ sống lâu hơn cả color space đã cho nó ý nghĩa

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

Một pattern được vẽ qua một clip, không bao giờ như một fill

Mô hình đúng là kiểu trừ (subtractive): giới hạn clip thiết bị vào đúng hình dạng đang được vẽ, rồi chạy nội dung pattern bên trong nó. HotPDF không bao giờ vẽ trước một phép xấp xỉ đặc rồi tô đè lên, vì hình đặc trung gian sẽ hiện ra qua các khoảng hở giữa các tile và sẽ xung đột với bất kỳ độ trong suốt nào trong nội dung tile. §8.7.3.2 mô tả một tiling pattern là một content stream được nhân bản theo các khoảng cách ngang và dọc cố định, và việc nhân bản chỉ có ý nghĩa khi đối chiếu với một clip đã có đúng hình dạng. Với fill, phép chuyển đổi trực tiếp: HPDFSelectFillPathClip đặt chế độ fill đa giác thành ALTERNATE cho f*, B*b* và thành WINDING cho các biến thể nonzero, xây dựng đường path GDI, và giao nó vào clip bằng SelectClipPath. Chỉ một dòng đó là thứ khiến một fill có pattern kiểu even-odd để lại đúng những lỗ hổng giống như một fill đặc kiểu even-odd, chính xác là điều một vùng gạch chéo hình bánh donut cần

Stroke mới là phần dễ làm sai. Một đường path đã stroke không có phần bên trong, nên giao chính đường path đó vào clip cho ra một vùng rỗng và không gì được vẽ. Vì vậy HPDFSelectStrokePathClip trước tiên xây một bút hình học từ trạng thái hiện tại, dùng PS_GEOMETRIC với đầu mút từ J, kiểu nối từ j, giới hạn miter từ M, và PS_USERSTYLE khi có mảng nét đứt đang hoạt động, rồi gọi WidenPath để chuyển đường viền đã stroke thành một vùng có thể fill trước khi clip. Hành vi cap, join, miter và dash trên một đường path pattern-stroke sau đó khớp với một stroke bình thường nhờ cách xây dựng chứ không phải nhờ một triển khai thứ hai. Có hai giới hạn thực tế ở đây: độ rộng đường nhỏ hơn một đơn vị thiết bị được kẹp về một pixel, và mảng nét đứt bị cắt ở mười sáu mục, đó là trần mà ExtCreatePen chấp nhận

Những tile nào thực sự hiển thị?

Dải hiển thị đến từ việc chạy phép biến đổi theo chiều ngược. Việc đặt tile diễn ra trong pattern space, nhưng thứ duy nhất biết được bao nhiêu phần của trang đang bị chạm tới là hộp clip thiết bị, nằm trong device space. HotPDF tổ hợp BaseMatrix := CTM * PatternMatrix, đảo ngược nó, và ánh xạ bốn góc của hộp clip GDI trở lại qua ma trận nghịch đảo. Biên giới hạn theo trục của bốn góc đã ánh xạ đó cho ra hình chữ nhật trong pattern space có thể bị phủ tới, và chia hình chữ nhật đó cho XStepYStep đối chiếu với BBox của pattern cho ra các dải chỉ số đóng. Mỗi ô sau đó render với một CTM là CTM * PatternMatrix * Translate(i * XStep, j * YStep), và bị clip lần thứ hai vào chính đa giác BBox đã biến đổi của nó. Lần clip thứ hai đó quan trọng khi XStep nhỏ hơn độ rộng bounding box, đó là cách các thiết kế tile chồng lấn được biểu diễn; nếu không có nó, các ô lân cận sẽ vẽ đè lên nhau ngoài phạm vi khai báo của chúng. Nếu clip theo từng ô trả về NULLREGION, ô đó bị bỏ qua mà không tokenize hay thực thi bất cứ điều gì

// 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 không màu và màu đến từ bên ngoài

Một pattern PaintType 2 mang hình dạng nhưng không mang màu, và màu đến cùng với tên pattern. §8.7.3.2 quy định rằng một pattern không màu chỉ được dùng với một Pattern color space khai báo một không gian nền, nên scn nhận các giá trị thành phần trước và tên pattern sau cùng. HotPDF phân giải các thành phần đó qua không gian nền được lưu trên mục color space của pattern, nghĩa là một hatch không màu có thể được nhuộm bằng một mực Separation hoặc một tổ hợp DeviceN y hệt như bất kỳ fill nào khác; cơ chế của việc phân giải đó được trình bày trong render màu spot Separation và DeviceN. Bên trong tile, hai kiểu paint tách biệt rõ rệt. Với PaintType 2, trình render đặt một cờ ngăn toán tử màu trong suốt thời gian của tile, nên bất kỳ g, rg, k hoặc scn nào trong nội dung pattern đều bị bỏ qua và mọi nét vẽ dùng màu được cung cấp từ bên ngoài. Với PaintType 1, điều ngược lại xảy ra: trạng thái fill và stroke được reset về mặc định PDF, DeviceGray đen với color space identity, và tile tự tô màu cho chính nó. Bỏ qua bước reset đó khiến màu tình cờ đang hiện hành tại toán tử f rò rỉ vào một pattern lẽ ra phải tự mô tả chính nó

Vì sao độ sâu ngăn xếp graphics state phải được khôi phục sau mỗi tile?

Vì một content stream của pattern được phép mất cân bằng, và thiệt hại cộng dồn qua từng ô. Một tile mà stream của nó chứa ba toán tử q và hai toán tử Q để lại ngăn xếp sâu hơn một khung so với lúc bắt đầu. Chỉ khôi phục bản ghi trạng thái hiện tại giữa các ô và độ sâu cứ tiếp tục lớn lên, nên ô số hai trăm thực thi từ một khung ngăn xếp thuộc về ô số một trăm chín mươi chín, với bất kỳ CTM và clip nào khung đó mang theo. Vì vậy HotPDF chụp snapshot bản ghi trạng thái và độ sâu ngăn xếp trước vòng lặp tile và gọi RestoreSnapshot ở đầu mỗi lần lặp, việc này cắt ngăn xếp trở lại độ dài đã lưu và cài đặt lại trạng thái đã lưu trong một bước. Dictionary Resources của trang và cờ ngăn toán tử màu được khôi phục trên cùng một ranh giới, vì một tile có thể tham chiếu tài nguyên riêng của nó và không được truyền chúng cho tile lân cận. Trạng thái clip GDI nhận cùng cách xử lý qua một cặp SaveDC / RestoreDC quanh mỗi ô, nên một tile cài đặt clip W n riêng của nó không thể thu nhỏ vùng khả dụng cho ô tiếp theo

Ngân sách, từ chối, và những gì trình render sẽ không vẽ

Tiling pattern là nơi dễ nhất trong một PDF để viết ra một file từ chối dịch vụ, nên các giới hạn là những con số cứng chứ không phải phỏng đoán. Việc lồng pattern bị giới hạn ở độ sâu 4, cùng cơ chế bảo vệ dùng cho đệ quy Form XObject, ngăn một pattern tự tham chiếu chính nó qua dictionary tài nguyên của riêng nó. Một lần tô một đường path có thể thực thi tối đa 16.384 tile tổng cộng, được đếm giảm dần qua các pattern lồng nhau và chỉ reset khi lần tô pattern ngoài cùng bắt đầu. Một lưới tile mà số ô dự kiến vượt quá phần còn lại của ngân sách đó bị từ chối thẳng, trước khi bất kỳ ô nào chạy

Hình học suy biến bị từ chối thay vì được xấp xỉ. Một BBox thiếu hoặc có diện tích bằng không, một XStep hoặc YStep có độ lớn dưới 1e-6, một tích CTM * PatternMatrix không có nghịch đảo, tọa độ clip đã ánh xạ vượt quá 1e9, hoặc một chỉ số có độ lớn vượt một triệu đều khiến việc tô pattern trả về mà không vẽ gì. Kết quả là một vùng không được tô thay vì một luồng render bị treo, đó là sự đánh đổi bạn muốn trong một trình chuyển đổi hàng loạt. Hiệu năng đến từ một quyết định: stream pattern được tokenize một lần cho mỗi lần tô bằng HPDFTokenizeContentStream và mảng token được tái sử dụng qua mọi ô hiển thị, nên số lượng tile nhân lên chi phí thực thi nhưng không bao giờ nhân lên chi phí phân tích từ vựng

Render một trang có pattern từ Delphi

Không có gì trong việc hỗ trợ pattern làm thay đổi code gọi. Tải tài liệu, yêu cầu một trang, và công việc tiling diễn ra bên trong trình thông dịch content-stream mà render trang thành bitmap đã điều khiển sẵn. Cùng trình thông dịch đó cấp cho bitmap, metafile và device context máy in, nên một bản vẽ có gạch chéo trông đúng trong một thumbnail xem trước sẽ in ra với cùng hình học tile. Shading pattern PatternType 2 đi theo một nhánh khác chia sẻ đường đánh giá với toán tử sh trơn, được trình bày chi tiết trong render shading trục và xuyên tâm

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;

Khi một vùng có pattern vẫn trông sai, hãy kiểm tra ba nhóm lỗi theo thứ tự. Một vùng hoàn toàn trống thường có nghĩa là một sự từ chối: kiểm tra XStep, YStepBBox xem có giá trị suy biến hay không, hoặc đếm số tile mà lưới đó cần so với trần 16.384. Một vùng được tô bằng một màu phẳng duy nhất nghĩa là tên pattern chưa bao giờ đến được toán tử tô, điều này trỏ tới thứ tự csscn trong stream. Một pattern xuất hiện ở nơi nó không thuộc về nghĩa là vấn đề khôi phục trạng thái, và nơi cần xem là cách xử lý q / Q quanh form hoặc path đã thừa hưởng nó

Tiling pattern là một trong những tính năng PDF luôn vô hình cho đến khi file cần đến chúng rơi vào hộp thư của bạn, và rồi chúng trở thành toàn bộ công việc. Nếu bạn đang xây trình xem bản vẽ, trình chuyển đổi tài liệu kỹ thuật hoặc trình render báo cáo trên Delphi hoặc C++Builder, component đầy đủ và API render của nó được tài liệu hóa trên trang component PDF Delphi HotPDF