Artikel Teknis

Rendering Tiling Pattern PDF di Delphi dengan HotPDF

Hatching yang ter-render sebagai satu blok abu-abu rata adalah kegagalan tiling-pattern yang klasik. HotPDF, komponen VCL PDF native untuk Delphi dan C++Builder, melukis PatternType 1 dengan mengubah path saat ini menjadi clip sementara dan memutar ulang content stream pattern sekali per tile yang terlihat, dengan pemilihan pattern disimpan dalam graphics state dan dipulihkan oleh q dan Q

Gejalanya datang dalam dua rasa, dan terlihat tidak berhubungan sampai Anda tahu penyebabnya. Gambar CAD kehilangan hatching bagiannya dan kembali sebagai fill solid, karena renderer menyelesaikan pattern menjadi warna rata-rata dan melukis itu. Atau hatching-nya bocor: title block yang seharusnya putih polos malah mengambil garis diagonal dari gambar detail dua path sebelumnya. Keduanya adalah masalah state pattern, dan hanya satu di antaranya yang benar-benar soal menggambar tile

Kenapa tiling pattern bocor ke path berikutnya?

Karena nama pattern yang dipilih adalah bagian dari graphics state, bukan properti dari operator yang memakainya. ISO 32000-1 §8.6.6.2 mendefinisikan color space Pattern sebagai color space yang nilai warnanya adalah nama pattern yang diberikan ke scn atau SCN, dan setiap komponen lain dari state warna disimpan oleh q dan dipulihkan oleh Q. Nama pattern-nya harus mengikuti aturan yang sama. HotPDF menyimpannya dalam record state sebagai FillPatternName dan StrokePatternName, di samping keluarga color space fill dan stroke, sehingga Q mengembalikan pilihan sebelumnya persis seperti ia mengembalikan CTM sebelumnya

Simpan nama itu dalam variabel lokal di dalam dispatcher operator sebagai gantinya, dan ia akan bertahan lewat setiap Q dalam stream. Kegagalannya lalu muncul di tempat yang tidak terduga: Form XObject yang digambar setelah path berpattern mewarisi pilihan pattern yang tidak pernah dibuat oleh content stream-nya sendiri, dan fill-nya keluar sebagai hatching. Form bersarang membuatnya lebih buruk, karena setiap level bersarang mendorong dan menarik state yang diabaikan oleh variabel yang tersasar itu. Mengatur color space bukan-pattern dengan cs atau CS, atau menerbitkan g / rg / k biasa, juga harus mengosongkan nama pattern, kalau tidak pilihan basi itu bertahan melewati color space yang memberinya makna

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

Sebuah pattern dilukis lewat clip, bukan pernah sebagai fill

Model yang benar bersifat subtraktif: batasi clip device pada bentuk yang sedang dilukis, lalu jalankan konten pattern di dalamnya. HotPDF tidak pernah menggambar aproksimasi solid lebih dulu lalu melapisi cat di atasnya, karena solid perantara itu akan terlihat lewat celah antar tile dan akan bentrok dengan transparansi apa pun dalam konten tile. §8.7.3.2 menjelaskan tiling pattern sebagai content stream yang direplikasi pada interval horizontal dan vertikal tetap, dan replikasi hanya masuk akal terhadap clip yang sudah punya bentuk yang benar. Untuk fill, konversinya langsung: HPDFSelectFillPathClip mengatur mode fill poligon ke ALTERNATE untuk f*, B* dan b* dan ke WINDING untuk varian nonzero, membangun GDI path, dan memotongnya (intersect) ke dalam clip dengan SelectClipPath. Satu baris itulah yang membuat fill berpattern even-odd meninggalkan lubang yang sama dengan fill solid even-odd, yang persis dibutuhkan region hatching berbentuk donat

Stroke adalah bagian yang mudah keliru. Path yang di-stroke tidak punya interior, jadi memotongkan path itu sendiri ke dalam clip menghasilkan region kosong dan tidak ada apa pun yang terlukis. HPDFSelectStrokePathClip karena itu membangun pena geometris dari state saat ini lebih dulu, memakai PS_GEOMETRIC dengan end cap dari J, join dari j, miter limit dari M, dan PS_USERSTYLE saat array dash aktif, lalu memanggil WidenPath untuk mengubah outline stroke menjadi region yang bisa diisi sebelum di-clip. Perilaku cap, join, miter dan dash pada path berpattern-stroke lalu cocok dengan stroke normal berdasarkan konstruksi, bukan lewat implementasi kedua. Ada dua batas jujur di sini: lebar garis di bawah satu unit device dijepit ke satu piksel, dan array dash dipotong pada enam belas entri, yang merupakan ceiling yang diterima ExtCreatePen

Tile mana yang sebenarnya terlihat?

Rentang yang terlihat berasal dari menjalankan transform terbalik. Penempatan tile terjadi di pattern space, tapi satu-satunya yang tahu berapa banyak bagian halaman yang tersentuh adalah kotak clip device, yang berada di device space. HotPDF menyusun BaseMatrix := CTM * PatternMatrix, membaliknya, dan memetakan keempat sudut kotak clip GDI kembali lewat inversnya. Batas axis-aligned dari keempat sudut yang dipetakan itu memberikan persegi panjang pattern-space yang mungkin bisa dicakup, dan membagi persegi panjang itu dengan XStep dan YStep terhadap BBox pattern memberikan rentang indeks tertutup. Setiap sel lalu ter-render dengan CTM CTM * PatternMatrix * Translate(i * XStep, j * YStep), dan di-clip untuk kedua kalinya ke poligon BBox yang sudah ditransformasi miliknya sendiri. Clip kedua itu penting saat XStep lebih kecil dari lebar bounding box, yang merupakan cara desain tile yang tumpang tindih diekspresikan; tanpanya, sel-sel tetangga akan saling melukis di luar cakupan yang dideklarasikan. Jika clip per-sel kembali sebagai NULLREGION, sel itu dilewati tanpa melakukan tokenisasi atau eksekusi apa pun

// 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 tak berwarna dan warna yang datang dari luar

Pattern PaintType 2 membawa bentuk tapi tidak membawa warna, dan warnanya tiba bersama nama pattern. §8.7.3.2 menetapkan bahwa pattern tak berwarna hanya dipakai dengan color space Pattern yang mendeklarasikan underlying space, jadi scn menerima nilai komponen lebih dulu dan nama pattern terakhir. HotPDF menyelesaikan komponen-komponen itu lewat underlying space yang tersimpan pada entri color space pattern, yang berarti hatching tak berwarna bisa diwarnai dengan tinta Separation atau kombinasi DeviceN persis seperti fill lainnya; mekanisme penyelesaian itu dibahas di rendering warna spot Separation dan DeviceN. Di dalam tile, kedua tipe cat itu berbeda tajam. Untuk PaintType 2, renderer mengatur flag penekan operator warna selama durasi tile, sehingga g, rg, k atau scn apa pun dalam konten pattern diabaikan dan setiap goresan memakai warna yang disuplai dari luar. Untuk PaintType 1 berlaku sebaliknya: state fill dan stroke direset ke default PDF, DeviceGray hitam dengan color space identitas, dan tile-nya mewarnai dirinya sendiri. Melewatkan reset itu membuat warna yang kebetulan aktif pada operator f bocor ke dalam pattern yang seharusnya mendeskripsikan dirinya sendiri

Kenapa kedalaman graphics state stack harus dipulihkan setelah setiap tile?

Karena content stream pattern diizinkan tidak seimbang, dan kerusakannya menumpuk lintas sel. Tile yang stream-nya berisi tiga operator q dan dua operator Q meninggalkan stack satu frame lebih dalam dari awalnya. Pulihkan hanya record state saat ini di antara sel dan kedalamannya terus bertambah, sehingga sel nomor dua ratus dieksekusi dari stack frame milik sel nomor seratus sembilan puluh sembilan, dengan CTM dan clip apa pun yang dibawa frame itu. HotPDF karena itu mengambil snapshot record state dan kedalaman stack sebelum loop tile dan memanggil RestoreSnapshot di awal setiap iterasi, yang memotong stack kembali ke panjang yang tersimpan dan memasang kembali state yang tersimpan dalam satu langkah. Dictionary Resources halaman dan flag penekan operator warna dipulihkan pada batas yang sama, karena tile boleh merujuk resource-nya sendiri dan tidak boleh menyerahkannya ke tetangganya. State clip GDI mendapat perlakuan yang sama lewat pasangan SaveDC / RestoreDC di sekitar setiap sel, sehingga tile yang memasang clip W n-nya sendiri tidak bisa mengecilkan region yang tersedia untuk yang berikutnya

Budget, penolakan, dan apa yang tidak akan digambar renderer

Tiling pattern adalah tempat paling mudah dalam PDF untuk menulis file denial-of-service, jadi limitnya adalah angka keras, bukan heuristik. Nesting pattern dibatasi pada kedalaman 4, penjaga yang sama yang dipakai untuk rekursi Form XObject, yang menghentikan pattern yang merujuk dirinya sendiri lewat dictionary resource-nya sendiri. Satu pengecatan path boleh mengeksekusi paling banyak 16.384 tile total, dihitung mundur lintas pattern bersarang dan hanya direset saat pengecatan pattern terluar dimulai. Grid tile yang jumlah selnya yang direncanakan melebihi sisa budget itu ditolak sepenuhnya, sebelum satu sel pun berjalan

Geometri degeneratif ditolak, bukan diaproksimasi. BBox yang hilang atau berluas nol, XStep atau YStep yang magnitudonya di bawah 1e-6, hasil kali CTM * PatternMatrix tanpa invers, koordinat clip yang dipetakan melewati 1e9, atau magnitudo indeks melewati satu juta semuanya menyebabkan pengecatan pattern kembali tanpa menggambar. Hasilnya adalah region yang tidak terlukis alih-alih thread render yang menggantung, yang merupakan trade-off yang Anda inginkan pada konverter batch. Performa berasal dari satu keputusan: stream pattern di-tokenisasi sekali per pengecatan dengan HPDFTokenizeContentStream dan array token-nya dipakai ulang lintas setiap sel yang terlihat, sehingga jumlah tile mengalikan biaya eksekusi tapi tidak pernah biaya lexing

Me-render halaman berpattern dari Delphi

Tidak ada apa pun tentang dukungan pattern yang mengubah kode pemanggilnya. Muat dokumen, minta halaman, dan pekerjaan tiling terjadi di dalam interpreter content-stream yang sudah dijalankan oleh rendering page-to-bitmap. Interpreter yang sama memasok device context bitmap, metafile dan printer, jadi gambar berhatching yang terlihat benar di thumbnail preview akan tercetak dengan geometri tile yang sama. Pattern shading PatternType 2 mengambil cabang berbeda yang berbagi jalur evaluasi dengan operator sh polos, dijelaskan secara detail di rendering shading axial dan 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;

Ketika region berpattern masih terlihat salah, periksa tiga kelas kegagalan secara berurutan. Region yang sepenuhnya kosong biasanya berarti penolakan: periksa XStep, YStep dan BBox untuk nilai degeneratif, atau hitung tile yang dibutuhkan grid terhadap ceiling 16.384. Region yang dilukis dalam satu warna rata berarti nama pattern tidak pernah mencapai operator pengecatan, yang mengarah ke urutan cs dan scn dalam stream. Pattern yang muncul di tempat yang bukan miliknya berarti masalah pemulihan state, dan tempat untuk diperiksa adalah penanganan q / Q di sekitar form atau path yang mewarisinya

Tiling pattern adalah salah satu fitur PDF yang tetap tidak terlihat sampai file yang membutuhkannya mendarat di kotak masuk Anda, dan setelah itu ia menjadi keseluruhan pekerjaan. Jika Anda membangun viewer gambar, konverter dokumen teknik atau renderer laporan di Delphi atau C++Builder, komponen lengkap dan API rendering-nya didokumentasikan di halaman komponen PDF Delphi HotPDF