HotPDF, XPS ve OpenXPS paketlerini Delphi ve C++Builder içinde yazdırma sürücüsü olmadan PDF biçimine dönüştürür: her 96 DPI sabit sayfa koordinatını tek bir 0.75 ölçekli, Y eksenini çeviren sayfa matrisinden geçirir, her VisualBrush öğesini paylaşılan bir Form XObject olarak yayınlar ve ImageBrush döşeme kiplerini yinelenen görüntü çizimleri yerine yerli PDF döşeme desenlerine çevirir
Çoğu Windows şirketini bu işe sürükleyen senaryo sıkıcı ve kaçınılmazdır. Bir şeyler zaten Microsoft XPS Document Writer yazıcısına yazdırılıyordur — eski bir ERP raporu, imzalı bir form, bir ekstre toplusu — ve arşiv politikası PDF diyordur. XPS mükemmel bir yakalama biçimidir ama on yıl sonra bir kayıt sistemine teslim edilecek korkunç bir biçimdir. Bu yüzden biriktirici dosyasının sayfa sayfa PDF olması gerekir ve o dönüştürücüyü yazmaya başladığınız anda ilginç kısmın XML olmadığını keşfedersiniz. Asıl mesele XPS ile PDF biçiminin orijin konumu, birimin değeri ve bir fırçanın ne olabileceği konularında uyuşmamasıdır
Paketten PDF biçimine tek geçişte
Giriş noktası özel bir XPS sınıfı değil, belge işleyici kayıt defteridir. THPDFDocumentHandlerRegistry.RegisterStandardHandlers, XPS, EPUB ve CBZ işleyicilerini kurar; tanıma içerik tabanlıdır, bu yüzden [Content_Types].xml artı en az bir .fpage parçası taşıyan bir paket, dosya uzantısı yalan söylediğinde bile 95 puan alırken yalın bir .xps ya da .oxps uzantısı ancak 10 puan alır. Karşıya yükleme kabul ettiğinizde bu sıralama önemlidir; çünkü bir EPUB dosyasını .xps olarak yeniden adlandıran bir saldırgan işlem hattını yönlendirememelidir
var
Handled: IHPDFHandledDocument;
Info: THPDFDocumentHandlerInfo;
Options: THPDFDocumentHandlerOptions;
Registry: THPDFDocumentHandlerRegistry;
Output: TFileStream;
begin
Registry := THPDFDocumentHandlerRegistry.Create;
Output := TFileStream.Create('spool.pdf', fmCreate);
try
Registry.RegisterStandardHandlers;
Options := THPDFDocumentHandlerOptions.Default;
if not Registry.OpenFromFile('spool.xps', '', Options, Handled, Info) then
raise Exception.Create('no registered handler recognised the package');
if Handled.Format = hdfXPS then
Handled.WritePDF(Output, Options, Info);
// Info.UnsupportedFeatureCount bu dönüşümün dürüst puanıdır
finally
Output.Free;
Registry.Free;
end;
end;
Dönüşümle ilgili her şey denenmeden önce bütçelenir. THPDFDocumentHandlerOptions.Default, arşiv girdilerini 10.000, açılmış arşiv baytlarını 1 GiB, sıkıştırma oranını 200, kaynakları 4.096 ve sayfaları 10.000 ile sınırlar ve isteğe bağlı bir CancellationToken taşır; böylece sunucu tarafı bir iş paket ortasında durdurulabilir. Sonradan Info.UnsupportedFeatureCount değerini okuyun ve sıfır olmayan bir değeri gerçek bir bulgu sayın: HotPDF, eşleyemediği şeyi yaklaşık bir çizim yapıp susmak yerine bilerek sayar
Bir XPS sayfası neden yeniden yazılmış koordinatlar yerine matrise ihtiyaç duyar?
Çünkü koordinatları yeniden yazmak dönüşüm yığınını kaybeder. Bir XPS FixedPage, orijin sol üstte ve Y aşağı büyüyen 96 DPI birimlerinde belirtilir; PDF kullanıcı uzayı ise orijin sol altta ve Y yukarı büyüyen 72 DPI birimidir. Saf çözüm, her sayıyı 0.75 ile çarpmak ve yayarken her Y değerini sayfa yüksekliğinden çıkarmaktır. Bu tek düz bir yol için işler ama bir RenderTransform, iç içe bir Canvas ya da fırça yereli matris girer girmez dağılır; çünkü o dönüşümler XPS uzayında tanımlıdır ve koordinat başına yeniden yazımınız o uzayı çoktan terk etmiştir. HotPDF bu yüzden izdüşümü bir matris olarak tutar ve bileştirir. HPDFXPSPageMatrix sabit katsayıları sayfa başına bir kez döndürür, HPDFMultiplyXPSMatrix onu birikmiş yol dönüşümüyle art arda bağlar ve sonuç geometriden önce tek bir cm işleci olarak yayılır. Yol verisi sonra değiştirilmemiş XPS sayılarıyla yazılır; kısaltılmış geometri söz diziminin SVG yol verisi için kullanılan sınırlı ayrıştırıcıyı paylaşabilmesinin nedeni de budur — yalnızca baştaki F0 ya da F1 dolgu kuralı simgesi XPS bağdaştırıcısı tarafından işlenir. EMF ve WMF vektör içe aktarımı için aynı akıl yürütmeyi izlediyseniz argümanın biçimi tanıdıktır: içe aktarma biçimleri matrisle dönüştürülür, asla yaprak koordinatlar üzerinde aritmetikle değil
function HPDFXPSPageMatrix(PageHeight: Double): THPDFXPSMatrix;
begin
Result.A := 0.75; // 96 DPI XPS biriminden 72 DPI PDF punto birimine
Result.B := 0;
Result.C := 0;
Result.D := -0.75; // XPS tarafında Y aşağı büyür, PDF tarafında Y yukarı büyür
Result.E := 0;
Result.F := PageHeight; // PDF sayfa yüksekliği, punto cinsinden
end;
// Görsel başına tek bileşik CTM, herhangi bir yol işlecinden önce yayılır
Effective := HPDFMultiplyXPSMatrix(HPDFXPSPageMatrix(PageHeightPDF), PathMatrix);
Page.AppendRawContent(HPDFXPSMatrixOperator(Effective));
Bir VisualBrush öğesini iki kez çizmeden nasıl yeniden kullanırsınız?
Bir VisualBrush, keyfi bir görsel ağacı — Canvas, Path ve Glyphs çocukları — bir bölgeye boyar, olasılıkla bölge boyunca yinelenir. HotPDF o görseli bir kez PDF Form XObject olarak derler ve sonra yerleştirir; bu, Form XObject ile SVG içe aktarma konusunda anlatılan kaynak stratejisinin aynısıdır. İki ayrıntı işe yarayıp yaramayacağına karar verir. Birincisi, görsel doğrudan XML çocukları olarak gezilmelidir: döşenebilir öğeler için düz bir tarama, iç içe görselleri sayfa üst düzeyine çeker ve hem kaynak kapsamını hem boyama sırasını bozar. İkincisi, içerik XPS ile PDF arası sayfa matrisi zaten uygulanmış olarak yakalanır; bu yüzden Form öğesini yayınlamak o matrisin tersiyle çarpmayı gerektirir, aksi halde her yerleştirme 0.75 ölçeğini ve Y çevirisini yeniden uygular. Form ayrıca kaynaklarına sahip olmalıdır: HotPDF yalnızca yakalanan içerik akışının gerçekten başvurduğu yazı tiplerini, XObject öğelerini, desenleri, ExtGState öğelerini ve renk uzaylarını kopyalar; tüm sayfa kaynak sözlüğünü klonlamak, kaydedilmekte olan Form öğesini kendi kaynak grafiğine sürükler ve bir döngü kurardı. Yazı tipleri sıradan sayfalarda doğrudan sözlükte kalır ve ancak yakalanan içerik gerçekten bir Tf içerdiğinde paylaşılan dolaylı sözlüğe yükseltilir; böylece yeniden kullanılabilir görseli olmayan bir belge bu mekanizmanın bedelini ödemez. Hata bildirmeden önce bilmeye değer bir belirtim sınırı: ECMA-388 bölüm 13.4, bir VisualBrush üzerindeki ViewboxUnits ve ViewportUnits öğelerinin ikisinin de Absolute olmasını ister; bu yüzden göreli birimler eksik özellik değildir — uyumsuz girdidir ve HotPDF onlar için koordinat anlambilimi uydurmayı reddeder
ImageBrush döşemesi: dört kip, dört hücre boyutu
XPS döşeme kipleri, kapsanan alan boyunca yinelenen görüntü yerleştirmelerine açılmak yerine ISO 32000-1 bölüm 8.7.3 içindeki PDF döşeme desenlerine eşlenir; bu, çıktı boyutunu ve dönüşüm süresini fırçanın sayfanın ne kadarını kapladığından bağımsız tutar. Eşleme, gördüğünüzde mekaniktir: yansıma, bir desen hücresinin içine aynalanmış yerleştirmeler koyarak ve hücreyi ona göre büyüterek ifade edilir
Tile— tek yerleştirme, hücre 1×1 görünüm penceresi kalırFlipX— iki yerleştirme, hücre 2×1 boyutuna genişlerFlipY— iki yerleştirme, hücre 1×2 boyutuna yükselirFlipXY— dört yerleştirme, hücre 2×2 boyutuna büyür
Her yerleştirme kendi kırpma dikdörtgenini taşır; çünkü alt hücresini aşan bir Viewbox eşlemesi komşu yansımaya taşardı. Desen /Matrix öğesi insanları yakalayan kısımdır. Bir döşeme deseni, desen seçildiği andaki grafik durumuna değil üst içerik akışının varsayılan kullanıcı uzayına bağlanır; bu yüzden matrisin üç katmanı da açıkça bileştirmesi gerekir — sabit sayfa izdüşümü, Path dönüşümü ve fırça yereli Transform — ortamdaki CTM değerine bel bağlamak yerine. HotPDF ayrıca ayırmadan önce doğrular: RegisterImageTilingPattern bir deseni 1.024 yerleştirmeyle sınırlar ve yozlaşmış kırpmaları, tersinmez matrisleri ve geçersiz görüntü dizinlerini reddeder. Bunun arkasındaki genel PDF tarafı modelini istiyorsanız döşeme desenleri ve Pattern renk uzayı temel işleçleri kapsar
// Sabit sayfa izdüşümü desen matrisine katlanır, sonra fırça yereli matris
PatternMatrix := HPDFMatFromOps( 0.75 * PathMatrix.A, -0.75 * PathMatrix.B,
0.75 * PathMatrix.C, -0.75 * PathMatrix.D,
0.75 * PathMatrix.E,
PageHeight - 0.75 * PathMatrix.F);
if Brush.HasTransform then
PatternMatrix := HPDFMatMul(PatternMatrix, BrushMatrix);
PatternName := Document.RegisterImageTilingPattern(Resource.ImageIndex,
Brush.Viewport.Left, Brush.Viewport.Top,
Brush.Viewport.Left + CellWidth, Brush.Viewport.Top + CellHeight,
CellWidth, CellHeight, Placements, pttNoDistortion, PatternMatrix);
Radyal gradyan daire değilse ne olur?
XPS, RadialGradientBrush öğesini GradientOrigin, Center, RadiusX ve RadiusY ile tanımlar; bu yüzden fırça bir elipstir. ISO 32000-1 bölüm 8.7.4.5.4 içindeki PDF gölgelendirme tipi 3, iki daire arasında karıştırma yapar ve bir elipsi doğrudan ifade etmenin yolu yoktur. İki yarıçapı tek sayıya ortalamak cazip kestirmedir ve daireye yakın olmayan her fırçada gözle görülür biçimde yanlıştır. HotPDF bunun yerine sorunu koordinat sistemine taşır: Y eksenini RadiusY / RadiusX ile ölçekler, o ölçekli uzayda dürüst bir dairesel gölgelendirme kaydeder, deseni seçer ve ardından yazılacak yol geometrisi hâlâ özgün XPS kullanıcı uzayında olsun diye hemen ters ölçeği yayar
ScaleY := Brush.RadiusY / Brush.RadiusX;
PatternName := Document.RegisterMultiStopRadialGradient(
Brush.StartX, Brush.StartY / ScaleY, 0,
Brush.EndX, Brush.EndY / ScaleY, Brush.RadiusX,
StopPositions, StopColours, 3);
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(ScaleY) + ' 0 0 cm'#10);
Page.SetFillPattern(PatternName); // desen CTM değerini tam burada yakalar
if Abs(ScaleY - 1) > 0.000001 then
Page.AppendRawContent('1 0 0 ' + HPDFPDFNumber(1 / ScaleY) + ' 0 0 cm'#10);
O parçacıktaki sıralama numaranın tamamıdır ve üslup değildir. Bir PDF gölgelendirme deseni, geçerli renk olarak seçildiği anda geçerli dönüşüm matrisini yakalar; bu yüzden geçici ölçek SetFillPattern ya da SetStrokePattern işleminden önce yayılmalı, ters ölçek ise seçimi izlemeli ama yol işleçlerinden önce gelmelidir. Sırayı her iki yönde de yanlış yaparsanız ilk yolda doğru işlenen ve sonraki her yolda kayan bir gradyan elde edersiniz. Göreli koordinat kipi için ilgili bir kısıt geçerlidir: RadiusX ve RadiusY, yol genişliği ve yüksekliğine karşı ayrı ayrı çözümlenmelidir; çünkü ikisini tek kenar uzunluğuyla ölçeklemek, kare olmayan her yolda elipsin en boy oranını sessizce değiştirir
Dönüşümün sınırları konusunda dürüst olduğu yer
Bazı XPS yapıları yaklaşık olarak dönüştürülür, bazıları hiç dönüştürülmez ve baştan sona tasarım seçimi onları taklit etmek yerine saymaktır. TIFF ve JPEG XR parçaları WIC üzerinden rasterleştirilir ve korunan alfa hakkında hiçbir söz taşımaz; geçerli alfa kanallı PNG ise temel görüntü artı bir /SMask olarak bölünür. Görüntünün doğal boyutu pixel * 96 / DPI olarak türetilir; önce PNG pHYs ya da JPEG JFIF yoğunluğu okunur ve 96 DPI değerine geri düşülür, böylece bozuk bir yoğunluk başlığı keyfi yerine öngörülebilir bir boyuta iner. Çözümlenememiş matris kaynakları, standart dışı göreli dönüşümler, ColorConvertedBitmap, desteklenmeyen gradyan yayılım kipleri ve bozuk geometri, hepsi UnsupportedFeatureCount değerini artırır ve bozuk girdi, sessizce farklı bir çizime bozulmak yerine kapalı başarısız olur
Arşiv amaçlı bir dönüştürücü için yararlı duruş budur: sessizce yaklaşık sonuç üreten bir dönüşüm, hangi dört öğeyi temsil edemediğini söyleyen bir dönüşümden daha kötüdür; çünkü belge bir kayıt sistemine mühürlenmeden önce denetleyecek bir şeyi yalnızca ikincisi verir. XPS ve OpenXPS dönüşümünü belge işlem hattının geri kalanıyla — sayfa birleştirme, yazı tipleri, imzalama, PDF/A çıktısı — birlikte değerlendiriyorsanız, HotPDF Delphi PDF component sayfası tam özellik kümesini ve desteklenen Delphi ile C++Builder sürümlerini listeler