Technisch artikel

PDF Library for Delphi: multi-engine PDF rendering in Delphi

Drie rasterizers kunnen hetzelfde PDF-bestand lezen en het oneens zijn over wat erin staat. De ingebouwde engine in PDF Library for Delphi wordt zonder extra bestanden geleverd en rendert alles degelijk, waardoor hij de standaardpositie verdient. Cairo brengt een andere transparantie- en anti-aliasing-pijplijn mee en is vaak de keuze wanneer zachte maskers of overvloeimodi elders verkeerd uitkomen. PDFium bevat de rendercode van Chrome, dus een pagina die er in een browser goed uitziet, ziet er doorgaans ook onder PDFium goed uit, tegen de prijs van een flinke DLL en een bitness die exact moet overeenkomen. Geen van de drie is in het abstracte correct. Correctheid is documentgebonden en de enige eerlijke manier om te leren welke engine een bepaald corpus aankan, is dat corpus door elk van hen te voeren

Daarom moet u de engine als een runtimekeuze behandelen in plaats van als een keuze bij het bouwen. PDF Library for Delphi, de PDF-bibliotheek voor Delphi en C++Builder van losLab, plaatst alle drie achter één renderoppervlak, zodat de beslissing één geheel getal kost in plaats van een codevertakking. De rest komt neer op veilig tussen de engines kiezen, bevestigen welke engines een geïmplementeerd binair bestand werkelijk bevat en voorkomen dat de renderstatus stilletjes de volgende taak vergiftigt

Drie rasterizers achter één aanroepoppervlak

De bibliotheek nummert haar engines. Engine 1 is de ingebouwde renderer, de standaard, met GDI+-afvlakkingsopties op Windows. Engine 2 is Cairo en engine 3 is PDFium; beide worden tijdens runtime geselecteerd via SelectRenderer. De twee externe engines laden uit DLL's waarvan u de paden opgeeft met SetCairoFileName en SetPDFiumFileName voordat u ze selecteert. Welke engine ook actief is, het werk verloopt via dezelfde aanroepen: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Het wisselen van engine verandert één getal; de rest van uw rendercode merkt daar niets van

Het bestemmingsmodel reikt veel verder dan bitmaps. De rendererklasse richt zich ook op metabestanden (WMF, EMF, EMF+), EPS, directe device contexts, printers en HTML5, waarbij Cairo en PDFium alleen als extra bestemmingen verschijnen wanneer ze zijn meegecompileerd. Rasteruitvoer is waar de drie engines het zichtbaarst uiteenlopen, dus dat gebruiken de voorbeelden hier

Drie PDF-renderingengines achter één aanroeppvlak: SelectRenderer schakelt tussen de ingebouwde engine, Cairo en PDFium terwijl applicatiecode dezelfde renderfuncties blijft aanroepen
SelectRenderer ruilt één geheel getal in om werk te verplaatsen tussen de ingebouwde engine, Cairo en PDFium. Applicatiecode blijft RenderPageToFile en soortgenoten aanroepen, ongeacht welke engine de pixels produceerde

Ga nooit uit van het bestaan van een engine: onderzoek bij opstarten

Cairo en PDFium zijn functies voor conditionele compilatie, wat betekent dat een binair bestand volledig zonder hen kan zijn gebouwd. Wanneer dat gebeurt, veroorzaakt het vragen om engine 2 of 3 niets. SelectRenderer retourneert eenvoudig een andere waarde dan de gevraagde ID, en code die de retourwaarde negeert, blijft renderen met de al actieve engine. De verdediging is een opstartonderzoek dat elke engine vraagt zichzelf te identificeren en het antwoord vastlegt:

function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // engine 1 is altijd aanwezig
  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);                       // herstel de standaard voordat het echte werk begint
end;

Voer dat onderzoek één keer bij het opstarten uit en schrijf het resultaat naast elke rendertaak in het logboek. De meest voorkomende vraag wanneer een klant een renderverschil meldt, is welke engines hun installatie werkelijk heeft, en een antwoord van één regel in het logboek beslecht die vraag zonder remote-desktopsessie. Een nuttig neveneffect: als SetPDFiumFileName zelf 0 retourneert, weet u al dat de DLL het probleem is (verkeerd pad, verkeerde bitness, een ontbrekende afhankelijkheid) in plaats van een binair bestand dat zonder PDFium-ondersteuning is gecompileerd, omdat de padaanroep niets kon oplossen voordat SelectRenderer ooit draaide

Tien uitvoerformaten achter één Options-geheel getal

De parameter Options bij de renderaanroepen kiest de uitvoercodering: 0 is BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ en 9 HTML5. PNG (5) is de verstandige standaard voor voorvertoningen en gearchiveerde pagina-afbeeldingen. JPEG (1), gecombineerd met SetJPEGQuality, is beter voor fotografische scans waarbij bestandsgrootte belangrijker is dan scherpe randen

Eén formaat verbergt een vereiste voor de doelstream. Het BMP-pad schrijft eerst de afbeeldingsgegevens en zoekt daarna terug naar offset 0x26 om de resolutievelden in de header bij te werken. Richt u dit op een alleen-vooruit-stream, een compressiewrapper of een netwerksocket, dan mislukt de aanroep op een manier die op een enginefout lijkt maar dat niet is. Wanneer een niet-zoekbaar doel onvermijdelijk is, render dan PNG, of laat de BMP eerst via een geheugenstream lopen en kopieer hem vooruit zodra hij voltooid is

De DPI die u doorgeeft is niet de DPI die u krijgt

Elke renderaanroep neemt een DPI-argument, maar de resolutie die u werkelijk krijgt is die waarde vermenigvuldigd met de globale renderschaal. SetRenderScale begint op 1.0, en zodra u deze verandert, geldt de nieuwe factor stilletjes voor elke latere render op die instantie:

PDF.SetRenderScale(2.0);                    // elke latere render wordt verdubbeld
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // effectief 300 DPI
PDF.SetRenderScale(1.0);                    // reset, anders komen uw thumbnails enorm binnen

Dezelfde hardnekkigheid geldt voor SetRenderCropType en de JPEG-kwaliteitsinstelling. In een service die thumbnails, voorvertoningen en afbeeldingen met printresolutie uit één gedeelde instantie produceert, zijn deze achtergebleven instellingen de werkelijke oorzaak achter het incidentele ticket "thumbnails zijn plotseling 40 MB". Er zijn twee nette uitwegen: reset de relevante status aan het begin van elke bewerking, of reserveer een afzonderlijke instantie voor elk uitvoerprofiel zodat niets tussen taken door lekt

PDF Library for Delphi: Stroomdiagram van de opstart-engineprobe: elke renderer bevestigt zijn DLL-pad en zijn SelectRenderer-antwoord voordat een beschikbaarheidssamenvatting wordt gelogd naast elke rendertaak
Een mislukte path-aanroep wijst de DLL aan, terwijl een afwijkend SelectRenderer-resultaat betekent dat de binary de engine nooit heeft ingecompileerd. De probe draait één keer en zijn samenvatting van één regel beslecht de meeste rendering-vragen van klanten

De standaardengine afstellen voordat u naar een andere grijpt

Een verrassend groot deel van de verzoeken "we hebben een andere engine nodig" blijken instellingenproblemen met een vermomming. De ingebouwde renderer maakt zijn afvlakkingsgedrag toegankelijk via SetGDIPlusOptions en de bredere SetRenderOptions-familie, en met SetGDIPlusFileName kunt u hem richten op een specifieke GDI+-runtime wanneer een implementatieomgeving een ongebruikelijke versie levert. Kartelige lijntekeningen bij lage DPI, wazige tekst in thumbnails, strepen over verlopen: ze reageren allemaal op die knoppen, en eraan draaien kost niets in het installatieprogramma. Cairo of PDFium toevoegen betekent daarentegen meer DLL's leveren, een tweede of derde bitness-variant bijhouden en de verplichting op u nemen om die bij te werken

Een kwaliteitsklacht heeft dus een natuurlijke volgorde van handelingen. Reproduceer deze eerst op de exacte DPI en schaal van de klant, want de helft van de tijd verdwijnt het verschil zodra die overeenkomen. Probeer daarna de afvlakkingsopties van de ingebouwde engine. Zet pas dan de pagina naast elkaar in engines 1, 2 en 3 met elke andere variabele constant: render deze via elke engine naar PNG op identieke DPI en voeg alle drie toe. Gewoonlijk stemmen twee van de drie overeen, en die meerderheid vertelt u of de uitschieter het document anders interpreteert of dat uw eigen basisverwachting niet klopt. Drie concrete afbeeldingen beslechten een geschil over "rendert verkeerd" veel sneller dan een alinea bijvoeglijke naamwoorden

Een fallbackketen die zichzelf verklaart

Zodra onderzoek en statusdiscipline aanwezig zijn, is de fallbackketen zelf kort. Het detecteren van een fout steunt op LastRenderError, dat de eigen berichttekst van de engine voor de meest recente render bevat en leeg is wanneer de render slaagde:

procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // eerst de ingebouwde
  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 als de zware fallback
  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;

Twee ontwerppunten wegen hier zwaar. De keten legt vast waarom elke wissel plaatsvond, omdat een logregel met "deze pagina viel sinds release 3.7 terug op PDFium" een regressiesignaal is dat u in monitoring wilt volgen in plaats van verliezen. De fallbackvolgorde zelf is beleid dat u per werklast moet kiezen. De ingebouwde engine wordt zonder extra DLL's geïmplementeerd, wat hem in de meeste installaties de juiste eerste poging maakt, terwijl documenten met veel transparantiegroepen of ongebruikelijke schaduwen de gebruikelijke reden zijn waarom een team überhaupt een alternatieve engine aansluit. Geen enkele engine is in het algemeen het snelst; dat is precies het punt van per aanroep kiezen: benchmark elk ervan op een steekproef van uw echte documenten op uw echte DPI, en herhaal die meting wanneer de engine-DLL's of de documentmix veranderen. Het corpus wint de discussie, elke keer weer

Fallback-keten voor PDF-rendering: de ingebouwde engine probeert eerst, mislukkingen worden gelogd, PDFium probeert opnieuw, en een opgeworpen exceptie meldt wanneer alle beschikbare engines een pagina laten mislukken
Elke poging controleert LastRenderError en logt de reden voordat van engine wordt gewisseld. Pas wanneer elke geïnstalleerde engine heeft gefaald, geeft de keten een exception, met de verzamelde oorzaken al in het log

Verder dan losse pagina's: TIFF-batches en live device contexts

Twee buren van de aanroepen per pagina maken de gereedschapskist compleet. RenderAsMultipageTIFFToFile rendert een paginabereikexpressie rechtstreeks naar een meerpagina-TIFF, de natuurlijke vorm voor archiefoverdrachten aan documentbeheersystemen die ouder zijn dan PDF. RenderPageToDC tekent rechtstreeks op een Windows-device context voor voorvertoningscontrols, bestuurd door zijn eigen drietal hardnekkige instellingen (SetRenderDCOffset, SetRenderDCErasePage, plus het croptype) die dezelfde resetdiscipline nodig hebben als de schaalfactor. Renderen voor schermvoorvertoning en het printpad hebben elk genoeg eigen valkuilen om een afzonderlijk artikel te rechtvaardigen, waarnaar hieronder wordt gelinkt

Waar u hierna naartoe gaat

Eén gewoonte die het waard is om mee te nemen: omdat SelectRenderer voor elke latere aanroep op de instantie effect heeft, kan één hardnekkige pagina op een andere engine worden herprobeerd terwijl de rest van het document op de standaard blijft. Ga voor voorvertoningstekenen, printerselectie en DevMode-afhandeling verder met het artikel over afdrukvoorbeeld en device context. Wanneer renders een hoogvolume-pijplijn voor zeer grote bestanden voeden, past de handlegebaseerde aanpak in de direct-access-handleiding vanzelfsprekend bij rendering per pagina via DARenderPageToFile

Engineverpakking, ondersteunde formaten en proefbuilds staan beschreven op de PDF Library for Delphi-productpagina