PDF koordinatės nurodomos taškais (points), spausdintuvo koordinatės – įrenginio vienetais, ir jos neturi nieko bendro, kol jų tyčia nekonvertuojate. Šis neatitikimas yra dažniausia prasto spausdinimo rezultato priežastis „Delphi“ programose: kodas siunčia tinkamą failą, bet puslapis atspausdinamas nukirptas, ištemptas arba tuščias. „PDFium Component“ švariai tvarko atvaizdavimo dalį; spausdintuvo mechanizmas yra standartinis VCL. Šios dvi dalys suderinamos naudojant nedidelį kodo kiekį, kai suprantate, ko tikimasi iš abiejų pusių
Kaip veikia atvaizdavimo ir spausdinimo konvejeris
„PDFium Component“ tiesiogiai su spausdintuvais nebendrauja. Modelis toks: atvaizduojate puslapį į TBitmap norima skiriamąja geba, tada tą taškų schemą perkeliate į spausdintuvo drobę (canvas) naudodami StretchDIBits. TPdf.RenderPage grąžina iškvietėjui priklausančią taškų schemą, todėl jūs valdote pikselių matmenis. Parinkčių rinkinyje perduokite [rePrinting], ir „PDFium“ perjungia savo atvaizdavimo kelią į tokį, kuris praleidžia tik ekranui skirtus efektus, pvz., LCD subpikselių derinimą (hinting), ir teisingai apdoroja puslapio „MediaBox“ spausdinimo išvesčiai. Nepridėkite rePrinting, ir tai, ką nusiųsite į spausdintuvą, bus ekrano atvaizdas, kuris ekrane atrodo puikiai, bet didelio DPI spausdintuvuose linkęs duoti ne tokį ryškų rezultatą, nes 96 DPI ekranams priimti derinimo sprendimai netinka 300 ar 600 DPI spausdinimui
TPdf.Active yra vieninteliai vartai, kuriuos reikia patikrinti prieš liečiant bet kokią puslapio savybę. Komponentas tyliai nuryja įkėlimo klaidas: nustačius Active := True pažeistam ar slaptažodžiu apsaugotam failui, išimtis nesukeliama; jis tiesiog palieka Active kaip False. Visada patikrinkite tai po priskyrimo. Skaitant PageCount ar PageWidth neaktyviame dokumente grąžinamas nulis, o tai sukuria tylias neveikimo klaidas (no-ops), kurias labai sunku diagnozuoti, kai jos pasiekia kaupo eilę (spooler)
Minimalus spausdinimo ciklas
Paprasčiausias veikiantis atvejis įkelia failą, atidaro spausdinimo užduotį, pereina per puslapius ir užsidaro. Vienintelė sudėtinga detalė yra ta, kad Printer.NewPage neturi būti iškviesta prieš pirmąjį puslapį, todėl naudojama FirstPage žymė (flag). StretchDIBits perdavimas pereina per GetDIBSizes ir GetDIB, kad ištrauktų nuo įrenginio nepriklausomus bitus iš taškų schemos rodyklės (handle), o tada nupiešia juos ant spausdintuvo drobės visu puslapio dydžiu:
procedure PrintPdfFile(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Bitmap: TBitmap;
InfoHeaderSize, ImageSize: DWORD;
InfoHeader: PBitmapInfo;
Image: Pointer;
FirstPage: Boolean;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
Exit; // load failed silently; bail out
Printer.Title := Pdf.Title;
Printer.BeginDoc;
try
FirstPage := True;
for I := 1 to Pdf.PageCount do
begin
if FirstPage then
FirstPage := False
else
Printer.NewPage;
Pdf.PageNumber := I;
// Render at printer resolution; rePrinting adjusts the render path
Bitmap := Pdf.RenderPage(
0, 0,
Printer.PageWidth,
Printer.PageHeight,
ro0,
[rePrinting]
);
try
GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
InfoHeader := AllocMem(InfoHeaderSize);
try
Image := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
0, 0, Printer.PageWidth, Printer.PageHeight,
0, 0, Bitmap.Width, Bitmap.Height,
Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(InfoHeader);
end;
finally
Bitmap.Free;
end;
end;
finally
Printer.EndDoc;
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Perduodant Printer.PageWidth ir Printer.PageHeight kaip taškų schemos matmenis reiškia, kad atvaizduojate natūraliu spausdintuvo pikselių dydžiu, kuris jau atsižvelgia į įrenginio DPI. Tada iškvietimas StretchDIBits atvaizduoja tuos pikselius 1:1 puslapyje. Tai suteikia geriausią įmanomą tikslumą be jokios aiškios DPI aritmetikos, tačiau tai veikia tik tada, kai PDF puslapio ir fizinio popieriaus dydis sutampa. Kai jie skiriasi, reikia aiškaus mastelio keitimo
Mastelio keitimas, kai puslapio ir popieriaus dydžiai skiriasi
A4 portretinio formato PDF puslapis automatiškai netilps į „US Letter“ spausdintuvą, o gulsčio formato puslapis, pateiktas portretinės orientacijos spausdintuvui, bus nukirptas. Standartinis metodas yra apskaičiuoti vienodą mastelio koeficientą iš spausdintuvo pikselių ir PDF taškų (points) santykio, tada pritaikyti jį abiem matmenims, kad būtų išlaikytas kraštinių santykis. Pdf.PageWidth ir Pdf.PageHeight atskleidžia dabartinius puslapio matmenis taškais, kur vienas taškas yra 1/72 colio. Padauginus iš tikslinio DPI ir padalijus iš 72, konvertuojama į tos skiriamosios gebos pikselius. Paimkite X ir Y santykių funkciją Min, kad gautumėte didžiausią mastelį, kuris vis dar telpa spausdinamoje srityje:
// Fit PDF page to printable area, preserving aspect ratio
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // target render resolution
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Clamp to 1.0 for shrink-to-fit only (no enlargement)
if Scale > 1.0 then Scale := 1.0;
DestWidth := Round(Pdf.PageWidth * Dpi / 72 * Scale);
DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);
Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
[rePrinting, reAnnotations]);
// ... transfer with StretchDIBits as above
end;
Atvaizdavimas esant Dpi = 300 tinka daugumai biuro spausdintuvų. Esant 600 DPI, vieno A4 puslapio taškų schema siekia maždaug 34 megapikselius, o tai yra apie 100 MB kaip 32 bitų taškų schema; paprastų tekstinių dokumentų kokybės padidėjimas yra minimalus, o atminties kaina vienam puslapiui – reikšminga. Palikite 600 DPI spaustuvėms ar vektoriniams techniniams brėžiniams, kur tai tikrai svarbu
Antrame kodo bloke esanti žymė reAnnotations nepriklauso nuo rePrinting. Įtraukite ją, kai vartotojas tikisi popieriuje matyti antspaudus, paryškinimus ir komentarų laukelius. Praleiskite ją, jei norite spausdinti tik turinį. Abi žymes galima laisvai derinti
Puslapio pasukimas
„PDFium“ saugo puslapio pasukimą PDF faile kaip /Rotate įrašą, pasiekiamą per Pdf.PageRotation, kuris grąžina TRotation reikšmę (ro0, ro90, ro180, ro270). Spausdintuvo koordinačių sistema apverčia 90 ir 270 laipsnių pasukimus ekrano atžvilgiu. Jei tiesiogiai perduosite neapdorotą PageRotation reikšmę į RenderPage be jokio koregavimo, gulsčio formato puslapiai, įterpti į portretinį dokumentą, daugumoje „Windows“ spausdintuvų tvarkyklių bus atspausdinti aukštyn kojomis. Pataisymas yra paprastas sukeitimas prieš atvaizdavimo iškvietimą: susiekite ro90 su ro270, o ro270 atgal su ro90, palikdami ro0 ir ro180 nepakeistus
Prieš išleisdami, patikrinkite šį elgesį savo konkrečiame tiksliniame spausdintuve. Tvarkyklių elgesys su pasukimu nėra vienodas skirtingų gamintojų atveju, o kai kurios tvarkyklės taiko savo pasukimo korekciją GDI lygmenyje. Jei matote dvigubą pasukimą, pašalinkite sukeitimą; jei apskritai nematote jokios korekcijos, pridėkite ją. Mišrios orientacijos dokumentas, kuriame kaitaliojasi portretiniai ir gulsti puslapiai, yra greičiausias būdas aptikti bet kurį gedimo režimą testuojant
Atminties valdymas atliekant ilgą spausdinimo užduotį
Kiekvienas RenderPage iškvietimas priskiria naują TBitmap, kuris priklauso iškvietėjui ir kurį privalu atlaisvinti. Aukščiau esančiame cikle try/finally Bitmap.Free blokas tai teisingai apdoroja po vieną puslapį. Nekaupkite taškų schemų iš skirtingų puslapių: 300 DPI atvaizduotas 200 puslapių dokumentas sunaudotų gigabaitus atminties, kol pirmasis puslapis pasiektų kaupo eilę. Atlaisvinkite kiekvieną taškų schemą prieš pereidami prie kito puslapio
Perdavimo bloko viduje esanti AllocMem / FreeMem pora laikosi tos pačios taisyklės. GetDIBSizes parodo, kiek atminties reikia DIB antraštei ir pikselių duomenims; jūs išskiriate, užpildote, nupiešiate ir atlaisvinate viską vieno puslapio ribose. Leidus bet kuriam blokui nutekėti, spausdinimo užduotis išnaudos proceso krūvą (heap) dokumentuose, ilgesniuose nei kelios dešimtys puslapių
Jei spausdinimo užduotis reikia paleisti foninėje gijoje, palikite TPdf ir visus VCL spausdintuvo iškvietimus toje pačioje gijoje. Pats TPdf nėra saugus gijoms (thread-safe) tarp egzempliorių, besidalijančių pasauline „PDFium“ DLL būsena; saugiausias modelis yra vienas TPdf gijai, kur kiekvienas įkelia savo failo kopiją
Čia parodyta atvaizdavimo ir dokumentų API yra „Delphi“ ir „C++Builder“ skirtos „PDFium Component“ dalis