Bir kullanıcı, Delphi tabanlı bir viewer'da PDF formunu doldurur, Yazdır'a basar ve kağıt, her alanı boş, Letter boyutundaki sayfa A4'ü dolduracak şekilde gerilmiş olarak çıkar. İki kusur da yazdırma döngüsündedir: PDFium Component, alan değerlerini yalnızca bir bitmap'e boyar (ya da düzleştirilmiş bir kopyaya pişirir) ve her sayfa, eni boyunu koruyan tek bir ölçek çarpanı ister
Form katmanı, bitmap isteyen ikinci bir geçiştir
Windows'ta device-context overload'u TPdf.RenderPage(DC, Left, Top, Width, Height, Rotation, Options), yazıcının HDC'sini doğrudan FPDF_RenderPage'e verir. O çağrı sayfa içeriğini boyar; reAnnotations kuruluysa işaretleme annotation'larını da. Etkileşimli form katmanı DC'ye hiç ulaşmaz — o, PDFium'un form-fill ortamına aittir ve FPDF_FFLDraw tarafından çizilir; onun tek hedefi bir FPDF_BITMAP'dir. HDC varyantı yoktur. TPdf.RenderPage'in iki bitmap overload'u, FPDF_RenderPageBitmap ile FPDF_FFLDraw'u — belge FormFill True ile, yani varsayılanla, açılmışsa — aynı piksellerde çalıştırır. Seçeneklerde rePrinting varken PDFium Component, form geçişinden önce alan vurgusunu kaldırır ve sonra varsayılan vurgu rengini ile alfasını geri getirir; böylece düzenlenebilir alanların etrafındaki renkli kutular ekranda kalır. rePrinting ayrıca motorun FPDF_PRINTING bayrağına eşlenir; bu bayrak çıktıyı yazdırma olarak işaretler — ISO 32000-1 §12.5.3'ün (Table 165) annotation bayrağının 3. bitiyle ele aldığı durum budur: kurallara uyan bir okuyucu, Print biti temiz bir annotation'ı ekranda nasıl görünüyorsa görünsün kağıda koymaz
Geri kalanına platform karar verir. PDFium motorunda FPDF_RenderPage, Windows GDI üzerinden bir device context'e çizer ve yalnızca Windows derlemelerinde vardır; FPDF_FFLDraw ise, her platformda, başka hiçbir şeye değil yalnızca bir bitmap'e çizer. Linux'ta ve öteki Windows dışı hedeflerde GDI çağrısı yapacak bir şey yoktur; bu yüzden HDC overload'u, bir bitmap render edilerek — form katmanı dahil — handle'ın arkasındaki canvas üzerine çizilerek karşılanır. Dolayısıyla device-context çağrısı, alan değerlerini Windows dışında kağıda taşır, Windows'ta ise dışarıda bırakır. Delphi ile Lazarus arasında paylaşılan kodda bu dalı açık tutun: belgenin bir form ortamı varsa ve değerleri kağıda gitmek zorundaysa, her platformda bitmap isteyin
Bir yazdırma işi hangi yolu izlemeli?
Kağıt neyi taşıyacaksa ona göre seçin. Canlı form değerleri bitmap yoluna, vektör çıktı HDC yoluna işaret eder; düzleştirilmiş bir kopya ise ikisini birden verir — bir kaydetme ve bir yeniden yükleme bedeline. SetPdfPrintMode kütüphane geneldir; işler arasında değiştirin, sayfalar arasında asla
| Yol | Kağıtta form değerleri | Çıktı | Maliyet |
|---|---|---|---|
RenderPage(DC, …) | Hayır, Windows'ta | Vektör GDI; SetPdfPrintMode, EMF'i (varsayılan) ya da bir PostScript seviyesini seçer | En küçük kuyruk, en hızlı |
RenderPage, bir TBitmap'e; ardından StretchDIBits | Evet, kaydedilmemiş düzenlemeler dahil | İstediğiniz piksel boyutunda raster | Sayfa başına Width × Height × 4 bayt, artı DIB kopyası |
RenderPageBanded | Evet | Raster, bir kerede bir şerit | Bütün sayfa yerine bellekte bir bant |
Düzleştirilmiş kopya, ardından RenderPage(DC, …) | Evet, pişirilmiş sayfa içeriği olarak | Vektör | Kaydetme, yeniden yükleme, GenerateFormAppearances, FlattenAllPages(FLAT_PRINT) |
600 dpi'da tam bir A4 sayfası kabaca 4960 × 7016 aygıt pikselidir ve piksel başına 32 bitlikte bu, tek bir sayfa için 139,197,440 bayt demektir; onu StretchDIBits için bir DIB'e kopyalamak zirveyi ikiye katlar — 32-bit bir süreç için ağır bir yük. Bir viewer'ın Yazdır düğmesi için bitmap yolu yine de doğru varsayılandır: alanına bir şey yazıp Yazdır'a basan kullanıcı, o değeri kağıtta görmeyi bekler ve banding belleği sınırlı tutar. Arşivlenen ekstreler gibi uygulamadan çıkan belgelerse düzleştirilmiş kopyayla daha iyi hizmet alır — değerler sıradan sayfa içeriğine dönüşür ve yazıcı vektör çıktı alır
uses
System.SysUtils, System.IOUtils, PDFium, FPdfUtils; // FPdfUtils: FLAT_PRINT
function OpenFlattenedPrintCopy(Source: TPdf; out TempName: string): TPdf;
begin
TempName := TPath.Combine(TPath.GetTempPath, TPath.GetRandomFileName + '.pdf');
if not Source.SaveAs(TempName) then
raise Exception.Create('Could not write the print copy');
Result := TPdf.Create(nil);
try
Result.FileName := TempName; // FormFill True kalır (varsayılan)
Result.Password := Source.Password; // kopya şifrelemesini korur
Result.Active := True;
if not Result.Active then // başarısız yüklemede TPdf etkin kalmaz
raise Exception.Create('Could not reopen the print copy');
Result.GenerateFormAppearances; // güncel /V'den /AP'yi yeniden kurar
if not Result.FlattenAllPages(FLAT_PRINT) then
raise Exception.Create('Flattening failed');
except
Result.Free;
TFile.Delete(TempName);
raise;
end;
end;
Düzleştirme yolunun, bayraktan daha önemli bir sıra kuralı vardır. FlattenAllPages, her widget'ın appearance stream'ini (ISO 32000-1 §12.5.5) sayfa içeriğine pişirir ve bir appearance stream, son üreticinin yazdığı her neyse odur; /V'deki alan değerinin bunda payı yoktur. Görünümlerini hiç yeniden üretmemiş bir araçla doldurulmuş bir form, eski metne ya da hiçbir şeye düzleşir; bu yüzden GenerateFormAppearances önce koşar: text-field görünümlerini güncel değerlerden yeniden kurar ve checkbox ile radio /AS durumlarını eşitler; bunun için de belgenin FormFill True ile açılmış olması gerekir. Bütün bunları bir kopyada yapın; çünkü düzleştirme, üzerinde koştuğu TPdf'in sayfalarını yeniden yazar ve açık bir viewer, alanlarının düzenlenebilir kalmasını ister. Kopya, aşağıdaki yazdırma rutininin vektör kolundan form katmanı kapatılmış olarak geçer; iş bitince serbest bırakın ve geçici dosyayı silin
Her sayfayı yazdırılabilir alana sığdırmak
Bir kez ölçekleyin, iki çarpanın küçüğüyle, ve sonucu ortalayın. Printer.PageWidth × Printer.PageHeight'a gerilmek, biçimi kağıttan farklı olan her sayfayı — A4 üzerinde bir Letter sayfasını (612 × 792 point) ya da dikey bir kağıtta yatay sayfayı — bozar. Yazıcı tarafı, aygıt piksellerinde yazdırılabilir alandır (HORZRES ile VERTRES); sürücü marjları çoktan dışarıda. PDF tarafında TPdf.PageWidth ile PageHeight, sayfayı zaten görüntülendiği gibi raporlar: /Rotate 90 ile saklanmış bir A4 dikey sayfa 842 × 595 point olarak geri gelir. ISO 32000-1 §7.7.3.3 (Table 30), sayfanın görüntülenirken ya da yazdırılırken /Rotate ile döndürülmesi gerektiğini söyler; PDFium bunu render çağrısının içinde uygular ve Rotation argümanı — RenderPage çağrısında — yalnızca viewer'ın görünümü döndürdüğünde eklediği ek turdur. Oraya sayfanın kendi PageRotation'ını geçerseniz sayfa iki kez döner. Genişlik ile yüksekliği yalnızca o ek döndürme 90 ya da 270 dereceyken takas edin — döndürme altında thumbnail sığdırma ve zoom'u yöneten aynı kural çifti; aşağıdaki rutin, PDFium Component ile PDF belgelerini yazdırmak temel döngüsünü bir form-değeri dalı ve işi çöpe atan bir iptal yoluyla genişletir
uses
Winapi.Windows, System.SysUtils, Vcl.Graphics, Vcl.Forms, Vcl.Printers, PDFium;
procedure FitRect(PageW, PageH: Double; Swap: Boolean;
AreaW, AreaH: Integer; out X, Y, W, H: Integer);
var
T, Scale: Double;
begin
if Swap then
begin
T := PageW; PageW := PageH; PageH := T;
end;
if (PageW <= 0) or (PageH <= 0) then
begin
X := 0; Y := 0; W := AreaW; H := AreaH;
Exit;
end;
Scale := AreaW / PageW;
if AreaH / PageH < Scale then
Scale := AreaH / PageH;
W := Round(PageW * Scale);
H := Round(PageH * Scale);
X := (AreaW - W) div 2;
Y := (AreaH - H) div 2;
end;
procedure BlitBitmap(DC: HDC; Bitmap: TBitmap; X, Y: Integer);
var
InfoSize, ImageSize: DWORD;
Info: PBitmapInfo;
Bits: Pointer;
begin
GetDIBSizes(Bitmap.Handle, InfoSize, ImageSize);
Info := AllocMem(InfoSize);
try
Bits := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, Info^, Bits^);
StretchDIBits(DC, X, Y, Bitmap.Width, Bitmap.Height, 0, 0,
Bitmap.Width, Bitmap.Height, Bits, Info^, DIB_RGB_COLORS, SRCCOPY);
finally
FreeMem(Bits);
end;
finally
FreeMem(Info);
end;
end;
function PrintPages(Pdf: TPdf; FromPage, ToPage: Integer; FormLayer: Boolean;
ExtraRotation: TRotation; var Cancel: Boolean): Boolean;
const
Opts: TRenderOptions = [reAnnotations, rePrinting];
var
Page, X, Y, W, H: Integer;
Bitmap: TBitmap;
begin
Result := False;
Printer.BeginDoc;
try
for Page := FromPage to ToPage do
begin
if Page > FromPage then
Printer.NewPage;
Pdf.PageNumber := Page;
FitRect(Pdf.PageWidth, Pdf.PageHeight, ExtraRotation in [ro90, ro270],
Printer.PageWidth, Printer.PageHeight, X, Y, W, H);
if FormLayer then
begin
Bitmap := Pdf.RenderPage(0, 0, W, H, ExtraRotation, Opts);
if Bitmap <> nil then // bozuk sayfa boyutu için nil
try
BlitBitmap(Printer.Canvas.Handle, Bitmap, X, Y);
finally
Bitmap.Free;
end;
end
else
Pdf.RenderPage(Printer.Canvas.Handle, X, Y, W, H, ExtraRotation, Opts);
Application.ProcessMessages; // İptal düğmesinin bayrağı kurmasına izin verir
if Cancel then
Break;
end;
Result := not Cancel;
finally
if Result then
Printer.EndDoc // işi kuyruklayıcıya teslim eder
else
Printer.Abort; // iptal ya da istisna: hiçbir şey yazdırılmaz
end;
end;
Bir viewer bunu, Pdf.FormFill'i FormLayer olarak geçirerek çağırır; düzleştirilmiş kopya False geçirir. EndDoc bir temizlik adımı değil, taahhüdün kendisidir. BeginDoc'tan sonra Printer.EndDoc, o ana dek render edilen her şeyi geçerli bir iş olarak kuyruklayıcıya verir; dolayısıyla koşulsuz bir EndDoc, finally içinde, iptal edilmiş bir yazdırmayı yarım yazdırılmışa çevirir. Rutin, kararı finally içinde başarı bayrağından okur: son sayfadan sonra EndDoc, iptal ya da istisnadan sonra Abort. Döngü çıkışı da benzer bir özen ister; çünkü Delphi ile Free Pascal, bir for döngüsünün bitiş değerini girişte bir kez hesaplar. Bir İptal işleyicisinden ToPage'i ya da kopya sayısını sıfıra çekmek, hâlâ koşan bir döngüyü durdurmaz; bayrağı sınayın ve Break alın, iç içe kopya ve harmanlama döngülerinde her seviyeden çıkın. Application.ProcessMessages menü kısayollarının da tetiklenmesine izin verdiği için, iş koşarken Open, Save ve Print'i kapatın
Raster yolunu bellek içinde tutmak
TPdf.RenderPageBanded, bir sayfayı seçtiğiniz yükseklikteki yatay şeritler halinde render eder ve her şeridi bir callback'e verir. Her şerit, tam boyutlu sayfanın bir dilimidir — hiçbir şey yeniden ölçeklenmez — ve tam sayfalık bir bitmap'le aynı alan değerlerini taşır. 512 satırlık bantlarla, 4960 piksel genişliğinde bir sayfa bant başına kabaca 10 MB ister; sayfanın 139 MB'ına karşı. Kabaca belleğin on dörtte biri. TPdfBandCallback, bant indeksini, bandın sayfa pikselleri cinsinden üst ofsetini ve bitmap'i alan bir method pointer'dır ve devam etmek için True döndürür; RenderPageBanded bant sayısını, başarısızlıkta 0'ı döndürür
type
TBandPrinter = class
private
FDC: HDC;
FLeft, FTop: Integer;
public
constructor Create(DC: HDC; Left, Top: Integer);
function PrintBand(BandIndex, BandTopY: Integer; Bitmap: TBitmap): Boolean;
end;
constructor TBandPrinter.Create(DC: HDC; Left, Top: Integer);
begin
inherited Create;
FDC := DC;
FLeft := Left;
FTop := Top;
end;
function TBandPrinter.PrintBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean;
begin
BlitBitmap(FDC, Bitmap, FLeft, FTop + BandTopY);
Result := True; // False, kalan bantları durdurur
end;
// sayfa döngüsünden, tam sayfalık bitmap dalının yerine çağrılır
procedure PrintPageBanded(Pdf: TPdf; Page, X, Y, W, H: Integer;
ExtraRotation: TRotation);
var
Bands: TBandPrinter;
begin
Bands := TBandPrinter.Create(Printer.Canvas.Handle, X, Y);
try
if Pdf.RenderPageBanded(W, H, 512, Bands.PrintBand, ExtraRotation,
[reAnnotations, rePrinting]) = 0 then
raise Exception.CreateFmt('Page %d could not be rendered', [Page]);
finally
Bands.Free;
end;
end;
Konak, ViewerPreferences ile izin bitlerine ne yapmalı?
Kendi kodunuzda okuyun ve ona göre davranın; PDFium bunları raporlar ve sayfayı koşulsuz render eder. Bir üretici, kataloğun /ViewerPreferences sözlüğüne (ISO 32000-1 §12.2, Table 150) yazdırma ipuçları saklayabilir ve TPdf, yazdırma penceresini biçimlendirenleri ortaya çıkarır:
PrintScaling(Boolean), dosya/PrintScaling /Nonediyorsa — yani gerçek boyutta yazdırma demekse — False'tur;/AppDefaultiçin ya da girdi hiç yokken True'dur. Standart, yazdırma penceresi hiç gösterilmese bile buna uyulmasını isterPrintPaperHandling(TPrintPaperHandling),/Duplex'iphSimplex,phDuplexFlipShortEdge,phDuplexFlipLongEdgeya daphUndefinedolarak taşır.SetPdfPrintPaperHandlingDevMode, bunuDEVMODE.dmDuplexiçine yazar veDM_DUPLEX'i kurar;phUndefinediçin False döndürür ve kayda dokunmaz, böylece sürücü varsayılanı sağ kalırPrintCopies(Integer),/NumCopies'dir; pencereye yazılacak kopya sayısı
İzin bitleri aynı kuralı izler. Standart güvenlik işleyicisi için ISO 32000-1 §7.6.3.2 (Table 22), /P'nin 3. bitini yazdırmaya ve revision 3 ile sonraki işleyicilerde 12. biti tam sadakatle yazdırmaya ayırır; 3. bit kurulu, 12. bit temizken yazdırma, görünümün düşük düzeyli bir temsiliyle — muhtemelen kalite kaybıyla — sınırlıdır. TPdf.GrantedPermissions maskeyi permPrint ile permPrintHighQuality'ye çözer, HasPermission tek bir değeri sınar; motor ne istersek onu render eder, o yüzden zorunlu kılma konağın işidir — çoğunlukla kapatılmış bir Yazdır düğmesi. Yalnızca permPrint verilmişse, standartla uyuşan okuma, tam iznin açtığı vektör ya da PostScript çıktı değil, mütevazı bir çözünürlükte bitmap yoludur. Bitler belgeyi açıldığı haliyle anlatır: owner şifresiyle açılan bir dosya tam erişim raporlar ve UserHasPermission yalnızca kullanıcı düzeyi bitler için cevap verir
uses
Winapi.Windows, System.SysUtils, Vcl.Dialogs, Vcl.Printers, PDFium;
procedure PreparePrintJob(Pdf: TPdf; Dialog: TPrintDialog;
out ActualSize, HighQuality: Boolean);
var
Device, Driver, Port: array[0..MAX_PATH] of Char;
DevModeHandle: THandle;
DevMode: PDeviceMode;
begin
if not Pdf.HasPermission(permPrint) then
raise Exception.Create('This document does not permit printing');
HighQuality := Pdf.HasPermission(permPrintHighQuality);
ActualSize := not Pdf.PrintScaling; // /PrintScaling /None
Printer.GetPrinter(Device, Driver, Port, DevModeHandle);
if DevModeHandle <> 0 then
begin
DevMode := GlobalLock(DevModeHandle);
try
SetPdfPrintPaperHandlingDevMode(DevMode^, Pdf.PrintPaperHandling);
finally
GlobalUnlock(DevModeHandle);
end;
Printer.SetPrinter(Device, Driver, Port, DevModeHandle); // pencere buradan başlar
end;
Dialog.MinPage := 1;
Dialog.MaxPage := Pdf.PageCount;
Dialog.Options := Dialog.Options + [poPageNums];
if Pdf.PrintCopies > 1 then
Dialog.Copies := Pdf.PrintCopies;
end;
// ActualSize True iken FitRect yerine kullanılır
procedure ActualSizeRect(Pdf: TPdf; out X, Y, W, H: Integer);
begin
// PDF tarafında inç başına 72 point, yazıcıda inç başına aygıt pikseli
W := Round(Pdf.PageWidth / 72 * GetDeviceCaps(Printer.Handle, LOGPIXELSX));
H := Round(Pdf.PageHeight / 72 * GetDeviceCaps(Printer.Handle, LOGPIXELSY));
X := (Printer.PageWidth - W) div 2; // sayfa yazdırılabilir alandan büyükken
Y := (Printer.PageHeight - H) div 2; // negatif olur: kenarlar kırpılır
end;
Will-print ile did-print eylemlerini gerçek yazdırma sınırında koşturmak
Belge düzeyindeki yazdırma script'leri, konak onları koşturduğunda koşar. Kataloğun additional-actions sözlüğü (ISO 32000-1 §12.6.3, Table 197), yazdırmadan önce icra edilen bir JavaScript eylemi olan WP'yi ve yazdırmadan sonra icra edilen DP'yi tanımlar. PDFium Component ikisini de ExecuteDocumentLifecycleAction(dlaWillPrint, ErrorMessage) ile dlaDidPrint üzerinden açar ve render çağrılarının dışında tutar: thumbnail'ler, anlık görüntüler ve dönüştürmeler de render eder ve hiçbiri bir belge script'ine kullanıcının yazdırması gibi görünmemelidir. Sırayı doğru kurmak size düşer. Tarih damgası vuran ya da "printed by" alanını dolduran bir will-print script'i form değerlerini değiştirir; bu yüzden onu ilk sayfa render edilmeden önce, düzleştirme yolunda ise kopya kaydedilmeden önce koşturun; did-print'i yalnızca EndDoc işi taahhüt ettikten sonra ateşleyin. Çağrı, form ortamı yokken ya da LifecycleActionPolicy reddederken mesajla False döndürür; güvenilmeyen dosyalar için lapDisabled, belge eylemlerini kapatır. Daha geniş script'leme resmi, konak olaylarıyla AcroForm JavaScript koşturmak yazısındadır
Windows'ta form değerlerini taşımak zorunda olan bir iş, bitmap koluna aittir: sıradan çözünürlüklerde RenderPage, bir TBitmap'e; 600 dpi ve üzerinde RenderPageBanded. PDFium Component'in device-context overload'unu düzleştirilmiş kopyalar için ve alanı olmayan belgeler için saklayın; orada vektör çıktısı daha iyi sonuçtur