Teknisk artikkel

Fler-motors PDF-rendring i Delphi: Innebygd, Cairo og PDFium med PDF Library for Delphi

Tre rasteriserere kan lese den samme PDF-en og være uenige om hva den sier. Den innebygde motoren i PDF Library for Delphi er den som følger med uten ekstra filer og rendrer alt kompetent, og det er derfor den fortjener standardplassen. Cairo bringer med seg en annen pipeline for gjennomsiktighet og kantutjevning, og pleier å være den folk griper til når myke masker eller blandingsmoduser kommer galt ut andre steder. PDFium bærer Chromes rendringskode, så en side som ser riktig ut i en nettleser, ser vanligvis riktig ut under PDFium også, til prisen av en anselig DLL og en bitbredde den insisterer på å matche. Ingen av de tre er riktig i det abstrakte. Riktighet avgjøres per dokument, og den eneste ærlige måten å finne ut hvilken motor som takler et gitt korpus, er å kjøre det korpuset gjennom hver av dem

Det er argumentet for å behandle motoren som et valg ved kjøretid framfor ved byggetid. PDF Library for Delphi, PDF-biblioteket for Delphi og C++Builder fra losLab, plasserer alle tre bak én rendringsflate, slik at beslutningen koster ett heltall i stedet for en kodeforgrening. Resten koker ned til å velge trygt mellom dem, bekrefte hvilke motorer en utrullet binærfil faktisk bærer, og hindre at rendringstilstanden stilltiende forgifter neste jobb

Tre rasteriserere bak én kallflate

Biblioteket nummererer motorene sine. Motor 1 er den innebygde rendreren, standardvalget, med GDI+-utjevningsvalg på Windows. Motor 2 er Cairo og motor 3 er PDFium, begge valgt ved kjøretid gjennom SelectRenderer. De to eksterne motorene lastes fra DLL-er hvis stier du oppgir med SetCairoFileName og SetPDFiumFileName før du velger dem. Uansett hvilken motor som er aktiv, går arbeidet gjennom de samme kallene: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Å bytte motor flytter ett tall; resten av rendringskoden din merker det aldri

Målmodellen strekker seg langt forbi punktgrafikk. Rendrerklassen sikter også mot metafiler (WMF, EMF, EMF+), EPS, direkte enhetskontekster, skrivere og HTML5, der Cairo og PDFium bare dukker opp som ekstra mål når de ble kompilert inn. Rasterutdata er der de tre motorene skiller lag mest synlig, så det er det eksemplene her bruker

Tre PDF-renderingsmotorer bak ett kallgrensesnitt: SelectRenderer bytter mellom den innebygde motoren, Cairo og PDFium, mens applikasjonskoden fortsetter å kalle de samme renderfunksjonene
SelectRenderer bytter ett heltall for å flytte arbeid mellom den innebygde, Cairo- og PDFium-motorene. Applikasjonskode fortsetter å kalle RenderPageToFile og venner uansett hvilken motor som produserte pikslene

Anta aldri at en motor finnes: sonder ved oppstart

Cairo og PDFium er funksjoner styrt av betinget kompilering, noe som betyr at en binærfil kan bygges helt uten dem. Skjer det, utløser det ingenting å be om motor 2 eller 3. SelectRenderer returnerer bare en annen verdi enn ID-en du ba om, og kode som overser returverdien, fortsetter å rendre med den motoren som allerede var aktiv. Forsvaret er en oppstartssonde som ber hver motor identifisere seg og noterer svaret:

function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // motor 1 er alltid til stede
  if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
    Result := Result + ', cairo';
  if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
    Result := Result + ', pdfium';
  PDF.SelectRenderer(1);                       // gjenopprett standarden før det virkelige arbeidet
end;

Kjør den sonden én gang ved oppstart og skriv resultatet inn i loggen ved siden av hver rendringsjobb. Det aller vanligste spørsmålet når en kunde melder om en rendringsforskjell, er hvilke motorer installasjonen deres faktisk har, og et svar på én linje i loggen avgjør det uten en fjernskrivebordsøkt. En nyttig bivirkning: returnerer SetPDFiumFileName selv 0, vet du allerede at DLL-en er problemet (feil sti, feil bitbredde, en manglende avhengighet) og ikke en binærfil kompilert uten PDFium-støtte, fordi stikallet ikke fant noe før SelectRenderer i det hele tatt kjørte

Ti utdataformater bak ett Options-heltall

Parameteren Options på rendringskallene velger utdatakodingen: 0 er BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ og 9 HTML5. PNG (5) er det fornuftige standardvalget for forhåndsvisninger og arkivbilder av sider. JPEG (1), sammen med SetJPEGQuality, er det bedre valget for fotografiske skanninger der filstørrelse betyr mer enn skarpe kanter

Ett format skjuler et krav til målstrømmen. BMP-veien skriver bildedataene først, og søker så tilbake til offset 0x26 for å lappe oppløsningsfeltene i filhodet. Rett den mot en strøm som bare går framover, en komprimeringsinnpakning eller en nettverkssokkel, og kallet feiler på en måte som leses som en motorfeil, men ikke er det. Når et mål uten søkemulighet ikke er til å unngå, rendr PNG i stedet, eller mellomlagre BMP-en gjennom en minnestrøm og kopier den videre når den er ferdig

DPI-en du sender inn, er ikke DPI-en du får

Hvert rendringskall tar et DPI-argument, men oppløsningen du faktisk får, er den verdien ganget med den globale rendringsskalaen. SetRenderScale starter på 1,0, og når du først endrer den, gjelder den nye faktoren stilltiende for hver senere rendring på den instansen:

PDF.SetRenderScale(2.0);                    // hver senere rendring dobles
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // i praksis 300 DPI
PDF.SetRenderScale(1.0);                    // nullstill, ellers blir miniatyrbildene enorme

Den samme klebrigheten gjelder SetRenderCropType og innstillingen for JPEG-kvalitet. I en tjeneste som produserer miniatyrbilder, forhåndsvisninger og bilder i utskriftsoppløsning fra én delt instans, er disse etterlatte innstillingene det som egentlig ligger bak den sporadiske saken om at «miniatyrbildene er plutselig 40 MB». To rene utveier: nullstill den relevante tilstanden øverst i hver operasjon, eller vie en egen instans til hver utdataprofil så ingenting lekker mellom dem

PDF Library for Delphi: Flytskjema for oppstartsmotorsjekk: hver renderer bekrefter sin DLL-sti og sitt SelectRenderer-svar før en tilgjengelighetssammendrag loggføres ved hvert renderjobb
Et feilende path-kall dømmer DLL-en, mens et umatchet SelectRenderer-resultat betyr at binærfilen aldri kompilerte motoren inn. Proben kjører én gang, og dens éninjes oppsummering avgjør de fleste kunders rendringsspørsmål

Å justere standardmotoren før du griper etter en annen

En overraskende andel av forespørslene om at «vi trenger en annen motor» viser seg å være innstillingsproblemer i forkledning. Den innebygde rendreren eksponerer utjevningsoppførselen sin gjennom SetGDIPlusOptions og den bredere SetRenderOptions-familien, og SetGDIPlusFileName lar deg peke den mot en bestemt GDI+-kjøretid når et utrullingsmiljø leverer en uvanlig en. Hakkete strektegninger ved lav DPI, uskarp tekst i miniatyrbilder, båndlegging i fargeoverganger: alt dette svarer på de rattene, og å vri på dem koster ingenting i installasjonspakken. Å legge til Cairo eller PDFium betyr derimot å levere flere DLL-er, følge en andre eller tredje bitbreddevariant og påta seg plikten til å oppdatere dem

En kvalitetsklage har altså en naturlig rekkefølge. Reproduser den først ved kundens nøyaktige DPI og skala, siden forskjellen halvparten av gangene fordamper når de stemmer overens. Prøv den innebygde motorens utjevningsvalg deretter. Først da setter du siden side om side på tvers av motorer med hver andre variabel holdt konstant: rendr den til PNG gjennom motor 1, 2 og 3 ved identisk DPI, og legg ved alle tre. Vanligvis er to av tre enige, og det flertallet forteller deg om avvikeren er dokumentet som tolkes forskjellig, eller din egen forventning som er på jordet. Tre konkrete bilder avgjør en tvist om «feil rendring» langt raskere enn et avsnitt med adjektiver

En reservekjede som forklarer seg selv

Når sondering og tilstandsdisiplin først er på plass, er selve reservekjeden kort. Å oppdage en feil hviler på LastRenderError, som holder motorens egen meldingstekst for den siste rendringen, og er tom når rendringen lyktes:

procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // den innebygde først
  PDF.RenderPageToFile(200, Page, 5, OutFile);      // 5 = PNG
  if PDF.LastRenderError = '' then Exit;
  LogEngineFailure('built-in', Page, PDF.LastRenderError);
  if PDF.SelectRenderer(3) = 3 then                 // PDFium som den tunge reserven
  begin
    PDF.RenderPageToFile(200, Page, 5, OutFile);
    if PDF.LastRenderError = '' then Exit;
    LogEngineFailure('pdfium', Page, PDF.LastRenderError);
  end;
  raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;

To designpoenger veier tungt her. Kjeden noterer hvorfor hvert bytte skjedde, for en loggline som lyder «denne siden falt tilbake til PDFium siden utgivelse 3.7», er et regresjonssignal du vil ha som trend i overvåkingen framfor tapt. Selve rekkefølgen i reservekjeden er en policy verdt å velge per arbeidsmengde. Den innebygde motoren rulles ut uten ekstra DLL-er, noe som gjør den til det rette første forsøket i de fleste installasjoner, mens dokumenter tunge på gjennomsiktighetsgrupper eller uvanlig skyggelegging er den vanlige grunnen til at et team i det hele tatt kobler inn en alternativ motor. Ingen motor er raskest generelt, og det er hele poenget med å velge per kall: mål hver av dem mot et utvalg av dine virkelige dokumenter ved din virkelige DPI, og gjenta den målingen hver gang motor-DLL-ene eller dokumentmiksen endrer seg. Korpuset vinner diskusjonen hver gang

PDF-render fallback-kjede: den innebygde motoren prøver først, feil loggføres, PDFium prøver igjen, og et hevet unntak rapporterer når alle tilgjengelige motorer feiler en side
Hvert forsøk sjekker LastRenderError og logger årsaken før det bytter motor. Først når hver installerte motor har feilet, utløser kjeden et unntak, med de samlede årsakene allerede liggende i loggen

Forbi enkeltsider: TIFF-batcher og levende enhetskontekster

To naboer til kallene per side runder av verktøykassen. RenderAsMultipageTIFFToFile rendrer et uttrykk for et sideområde rett inn i en flersidig TIFF, den naturlige formen for arkivoverleveringer til dokumenthåndteringssystemer som er eldre enn PDF. RenderPageToDC maler direkte på en Windows-enhetskontekst for forhåndsvisningskontroller, styrt av sin egen trio av klebrige innstillinger (SetRenderDCOffset, SetRenderDCErasePage, pluss beskjæringstypen) som trenger den samme nullstillingsdisiplinen som skalafaktoren. Forhåndsvisning på skjerm og rendring i utskriftsveien bærer nok av egne feller til å fortjene en egen artikkel, lenket nedenfor

Hvor du går videre

Én vane verdt å ta med seg: fordi SelectRenderer trer i kraft for hvert senere kall på instansen, kan én enkelt gjenstridig side prøves på nytt på en annen motor mens resten av dokumentet blir på standardvalget. For maling av forhåndsvisning, skrivervalg og DevMode-håndtering, fortsett med artikkelen om forhåndsvisning ved utskrift og enhetskontekst. Når rendringer mater en pipeline med høyt volum over svært store filer, passer den håndtaksbaserte tilnærmingen i guiden til direkte tilgang naturlig sammen med rendring per side gjennom DARenderPageToFile

Pakking av motorer, støttede formater og prøvebygg er beskrevet på produktsiden for PDF Library for Delphi