Om de PDF-bestandsgrootte in Delphi te verkleinen, biedt losLab PDF Library drie API's die de drie grootste bronnen van overtollige ballast aanpakken: SubsetEmbeddedFonts herschrijft elk ingesloten TrueType-lettertypeprogramma tot de glyphs die het document daadwerkelijk rendert, DownsampleImages resamplet rasterafbeeldingen die een doel-DPI overschrijden, en NormalizeLZWStreams vervangt verouderde LZWDecode-compressie door FlateDecode. Elk retourneert het aantal objecten dat het heeft gewijzigd, dus een nul vertelt u dat de bewerking een no-op was in plaats van een stille mislukking
Een samengevoegde of programmatisch gegenereerde PDF is meestal te groot om een van de volgende drie redenen: volledig ingesloten lettertypen, afbeeldingen die ver boven hun weergavesolving zijn gesampled en streams die nog steeds zijn gecomprimeerd met het verouderde LZW-filter. ISO 32000-1 §9.9 staat een maker toe het volledige lettertypeprogramma in te sluiten, en de meeste makers doen precies dat omdat het de veilige standaard is. Een compleet Arial-lettertypebestand FontFile2 loopt op to honderden kilobytes; sluit het in twaalf bronbestanden in, voeg ze samen en u draagt twaalf kopieën van glyph-contouren mee voor tekens die niemand heeft getypt. Het samenvoegen zelf creëert de verspilling niet, het concentreert het alleen in een enkel bestand waar het totaal uiteindelijk zichtbaar wordt
Afbeeldingen zijn de tweede boosdoener. Een 4800 pixels brede scan geplaatst in een frame van een kwart pagina verzendt ongeveer 40 keer meer pixelgegevens dan een 300 DPI-afdrukpijplijn kan gebruiken. De derde is stiller: streams gefilterd met LZWDecode. ISO 32000-1 §7.4.4 specificeert zowel LZWDecode als FlateDecode en merkt op dat Flate meestal minstens zo goed comprimeert; in de praktijk is de Flate-uitvoer consistent kleiner bij dezelfde gegevens, en LZW overleeft meestal in bestanden die op een bepaald moment in hun geschiedenis door tools uit de jaren 90 zijn gegaan. De rest van dit artikel doorloopt de drie bewerkingen van losLab PDF Library die elk probleem oplossen en combineert ze vervolgens in één pijplijn
Lettertype-subsetting met SubsetEmbeddedFonts
SubsetEmbeddedFonts verkleint elk ingesloten TrueType-lettertype in een geladen document tot de tekens die het document daadwerkelijk gebruikt, en het heeft geen argumenten nodig omdat het de keep-lijst afleidt uit de inhoudsstreams zelf. Intern doorloopt de bewerking de inhoudsstream van elke pagina met GetTextRuns, verzamelt de tekencodes waarnaar wordt verwezen onder elke lettertypebron, bouwt een keep-lijst op en overhandigt het originele lettertypeprogramma aan de Windows FontSub-engine (CreateFontPackage) om een subset te produceren. Het herschreven programma vervangt de stream FontFile2 ter plaatse, en de naam BaseFont krijgt een LOSABC+-tag, the conventie van zes hoofdletters plus een plusteken die ISO 32000-1 §9.6.4 definieert voor subset-lettertypen. Dat voorvoegsel is ook wat de aanroep idempotent maakt: voer de bewerking twee keer uit en reeds gesubsette lettertypen worden herkend en overgeslagen, zodat het veilig is om dit op te nemen in een batchtaak die bestanden mogelijk opnieuw bezoekt
Twee implementatiedetails zijn de moeite waard om te weten omdat ze de grenzen van de API verklaren. Ten eerste richt de bewerking zich op FontFile2, dus het dekt ingesloten TrueType-programma's; lettertypen ingesloten als Type 1 of kale CFF worden onaangeroerd gelaten in plaats van het risico te nemen. Ten tweede vertrouwt het op FontSub, wat SubsetEmbeddedFonts Windows-only maakt. Een subtieler punt uit de implementatie: of een lettertype in aanmerking komt, wordt beslist door daadwerkelijk de verwijzingsketen FontDescriptor → FontFile2 op te lossen, niet door te vertrouwen op een heuristiek met ingesloten vlaggen (embedded-flag), omdat lettertypen in een geladen document nooit de boekhouding aan de creatiezijde hebben doorlopen die dergelijke vlaggen instelt. Als de opgeloste stream bestaat, is het lettertype een kandidaat; als niet, dan wordt het zonder fout overgeslagen
De eerlijke afweging: een subset-lettertype bevat alleen de glyphs die aanwezig waren op het moment van subsetting. Als een downstream-tool, of uw eigen code, later tekst toevoegt in datzelfde lettertype, heeft elk teken buiten de subset geen contour en zal het worden weergegeven als een ontbrekende glyph. Subset als de laatste inhoudswijzigende stap, nooit voor een bewerkingsfase. Dezelfde voorzichtigheid is geboden als u van plan bent het lettertype later weer te extraheren voor hergebruik; het artikel over het extraheren van tekst, afbeeldingen en lettertypen met PDFlibPas covers wat een geëxtraheerd subsetprogramma wel en niet kan bieden
Hoe beslist DownsampleImages welke afbeeldingen moeten worden verkleind?
DownsampleImages(MaxDPI, Quality, Filter) resamplet alleen de afbeeldingen die het met een gerust hart oversampled kan noemen, met behulp van een bewust conservatieve DPI-schatting. Een PDF-afbeelding XObject slaat pixeldimensies op, maar geen betrouwbare fysieke resolutie, en een DPI-tag van de bronafbeelding overleeft zelden een laad-bewerk-bewaar-cyclus. Dus schat de bewerking SrcDPI = PixelWidth / 8.5, en vraagt in feite: als deze afbeelding de volledige breedte van een Letter-pagina zou beslaan, wat zou de resolutie dan zijn? Alleen afbeeldingen waarvan de schatting MaxDPI overschrijdt, worden aangeraakt. De afwijking is opzettelijk: een afbeelding die klein op de pagina is geplaatst, heeft een werkelijke DPI die hoger is dan de schatting, dus de bewerking triggert liever te weinig dan dat een printkwaliteit-asset die het niet kan meten wordt aangetast
Quality van 1 tot 100 selecteert de JPEG-herscoderingskwaliteit, terwijl 0 de uitvoer behoudt als verliesvrije PNG-stijl Flate; Filter kiest de resampling-kernel, 0 voor een boxgemiddelde en 1 voor bilineair. Voor gescand kantoorpapier is DownsampleImages(150, 75, 1) een verstandig startpunt; voor alles wat mogelijk opnieuw wordt afgedrukt, verhoogt u MaxDPI naar 300 of slaat u de bewerking volledig over. Downsampling is de enige verliesgevende stap van de drie, dus hoort deze thuis achter een instelling die uw gebruikers kunnen uitschakelen
Verouderde LZW-streams converteren met NormalizeLZWStreams
NormalizeLZWStreams is de gratis winst: het decomprimeert verliesvrij elke LZWDecode-stream en comprimeert deze opnieuw met FlateDecode, ter plaatse, waarbij het aantal geconverteerde streams wordt geretourneerd. Het verwerkt zowel een enkele vermelding /Filter /LZWDecode als LZW die voorkomt in een filterketen-array, waar alleen de LZW-link wordt vervangen en de rest van de keten behouden blijft. Predictor-parameters (Predictor, Columns, Colors, BitsPerComponent) worden gelezen uit de DecodeParms van de stream en doorgegeven aan de decompressor, zodat predictor-gecodeerde afbeeldingsgegevens correct heen en terug worden geleid. Omdat beide filters bit-exacte codecs zijn, zijn de gedecodeerde bytes voor en na identiek; alleen de containercompressie verandert, en daarom is deze bewerking veilig om onvoorwaardelijk op elk bestand uit te voeren
Op een document zonder LZW-streams retourneert de aanroep simpelweg 0 en raakt niets aan, wat de regressietestsuite van de bibliotheek expliciet test: een vers gemaakt bestand dat alleen Flate gebruikt moet nul conversies rapporteren. Die no-op garantie is belangrijk wanneer de bewerking deel uitmaakt van een pijplijn die duizenden heterogene bestanden verwerkt, sommige uit 2024 en sommige uit 1998
De volledige pijplijn voor bestandsgrootte-optimalisatie in Delphi
De drie bewerkingen worden gecombineerd in een enkele laad-optimaliseer-bewaar-functie, en de volgorde is minder belangrijk dan u zou verwachten omdat ze werken op onafhankelijke objecttypen: lettertypen, afbeeldingen (XObjects) en streamfilters. Het eerst uitvoeren van subsetting is nog steeds de nette keuze, aangezien dat de bewerking is met een bewerkingsvolgorde-beperking
function OptimizePDF(const Src, Dst: string): Boolean;
var
Lib: TPDFlib;
Fonts, Images, Streams: Integer;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
Exit;
Fonts := Lib.SubsetEmbeddedFonts; // TrueType FontFile2 -> subset
Images := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, bilinear
Streams := Lib.NormalizeLZWStreams; // LZWDecode -> FlateDecode
Result := Lib.SaveToFile(Dst) = 1;
// Log Fonts/Images/Streams: three zeros mean the file was already lean
finally
Lib.Free;
end;
end;
Verifieer de pijplijn zoals de bibliotheek zichzelf verifieert: een round-trip. De v3.130 regressietests maken een document aan, slaan het op, laden het opnieuw, voeren de optimalisatie uit, slaan het opnieuw op en controleren vervolgens drie dingen: de uitvoer is kleiner, de geretourneerde aantallen voldoen aan de verwachtingen en het opnieuw laden van het geoptimaliseerde bestand parseert en rendert nog steeds. Het reproduceren van die maak-optimaliseer-herlaad-lus tegen een steekproef van uw eigen productiebestanden, en het vergelijken van de geëxtraheerde tekst voor en na, is een investering van een uur die integratiefouten signaleert lang voordat een klant een beschadigde factuur opent
// Round-trip check: the optimized file must still load cleanly
Lib := TPDFlib.Create;
try
Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
Assert(Lib.GetPageCount > 0);
finally
Lib.Free;
end;
Waar past de pijplijn in een samenvoeg-workflow? Na de samenvoeging, niet tijdens. Door eerst samen te voegen en het enkele resultaat te optimaliseren, wordt elk ingesloten lettertype eenmaal gesubset tegen de unie van alle gebruikte tekens, in plaats van per bronbestand. Als de doorvoersnelheid van het samenvoegen de bottleneck is, biedt PDFlibPas een snel byte-niveau pad dat volledige objectparsing vermijdt, beschreven in het artikel over snelle PDF-samenvoeging met verschuiving van byte-referenties; en voor invoer die te groot is om volledig in het geheugen te houden, behandelt directe-toegang samenvoegen en splitsen voor grote PDF's de streamingroute. Beide koppelen natuurlijk met een definitieve optimalisatieslag op de samengevoegde uitvoer
Wat de drie bewerkingen niet zullen doen
Het optimalisatietrio van losLab PDF Library sluit bewust alles uit wat de semantiek van het document verandert. SubsetEmbeddedFonts voegt dubbele lettertypen uit samengevoegde bronnen niet samen tot één programma, het verkleint elk lettertype afzonderlijk; deduplicatie is een andere, risicovollere transformatie. DownsampleImages slaat een afbeelding over waarvan de conservatieve DPI-schatting onder de drempel blijft, zelfs wanneer een mens kan zien dat deze te groot is voor zijn frame. En geen van de bewerkingen raakt de documentstructuur aan, dus een bestand dat is opgeblazen door duizenden verweesde objecten heeft een bewerking nodig die lijkt op opnieuw opslaan, in plaats van deze streamspecifieke bewerkingen. Binnen die grenzen verwijdert de combinatie van lettertype-subsetting, downsampling van afbeeldingen en LZW-naar-Flate-normalisatie de drie klassieke bronnen van PDF-ballast met elk één voorspelbare API-aanroep. De drie functies worden geleverd als onderdeel van losLab PDF Library voor Delphi, C# en VB.NET, samen met de samenvoeg-, extractie- en rendering-API's die hierboven zijn besproken