Teknisk artikkel

Reduser PDF-filstørrelse i Delphi: Fonter, bilder, LZW

For å redusere PDF-filstørrelsen i Delphi tilbyr losLab PDF Library tre API-er som angriper de tre største kildene til unødvendig størrelse: SubsetEmbeddedFonts skriver om hvert innebygde TrueType-fontprogram ned til glyfene dokumentet faktisk viser, DownsampleImages resampler rasterbilder som overskrider en mål-DPI, og NormalizeLZWStreams erstatter eldre LZWDecode-komprimering med FlateDecode. Hver av dem returnerer antallet objekter den endret, så null forteller deg at operasjonen var en no-op i stedet for en stille feil

Hvorfor er min sammenslåtte PDF større enn kildefilene?

En sammenslått eller programmatisk generert PDF is og for stor av en av tre grunner: fullt innebygde fonter, bilder som er samplet langt over visningsoppløsningen, og strømmer som fremdeles er komprimert med det eldre LZW-filteret. ISO 32000-1 §9.9 lar en produsent bygge inn hele fontprogrammet, og de fleste produsenter gjør akkurat det fordi det er det trygge standardvalget. En komplett Arial FontFile2 er på hundrevis av kilobyte; bygger du den inn i et dusin kildefiler og slår dem sammen, bærer du på et dusin kopier av glyf-omriss for tegn ingen har skrevet. Sammenslåingen i seg selv skaper ikke sløsingen, den bare konsentrerer den i en enkelt fil der totalen til slutt blir synlig

Bilder er den andre synderen. En 4800 piksler bred skanning plassert i en ramme på en kvart side sender omtrent 40 ganger mer pikseldata enn en 300 DPI utskriftspipeline kan bruke. Den tredje er mer lydløs: strømmer filtrert med LZWDecode. ISO 32000-1 §7.4.4 spesifiserer både LZWDecode og FlateDecode, og bemerker at Flate vanligvis komprimerer minst like bra; i praksis er Flate-utdata konsekvent mindre på de samme dataene, og LZW overlever stort sett i filer som har passert gjennom verktøy fra 1990-tallet på et tidspunkt i historien. Resten av denne artikkelen går gjennom de tre losLab PDF Library-operasjonene som fikser hvert problem, og kombinerer dem deretter inn i én pipeline

Fontdelsett-innbygging med SubsetEmbeddedFonts

SubsetEmbeddedFonts krymper hver innebygde TrueType-font i et lastet dokument til tegnene dokumentet faktisk bruker, og den trenger ingen argumenter fordi den henter behold-listen fra selve innholdsstrømmene. Internt går operasjonen gjennom innholdsstrømmen til hver side med GetTextRuns, samler tegnkodene som refereres under hver fontressurs, bygger en behold-liste, og overleverer det opprinnelige fontprogrammet til Windows FontSub-motoren (CreateFontPackage) for å produsere et delsett. Det omskrevne programmet erstatter FontFile2-strømmen på stedet, og BaseFont-navnet får en LOSABC+-tagg, konvensjonen med seks store bokstaver pluss plusstegn som ISO 32000-1 §9.6.4 definerer for delsett-fonter. Dette prefikset er også det som gjør kallet idempotent: kjører du operasjonen to ganger, blir allerede delsett-innbygde fonter gjenkjent og hoppet over, så det er trygt å koble det inn i en batchjobb som kan gå over filer på nytt

To implementasjonsdetaljer er verdt å kjenne til fordi de forklarer grensene for API-et. For det første retter operasjonen seg mot FontFile2, så den dekker innebygde TrueType-programmer; fonter bygd inn som Type 1 eller ren CFF forblir urørt i stedet for å ta risikoen. For det andre er den avhengig av FontSub, noe som gjør SubsetEmbeddedFonts kun tilgjengelig for Windows. Et mer subtilt poeng fra implementasjonen: om en font kvalifiserer, avgjøres ved faktisk å løse opp referansekjeden FontDescriptorFontFile2, ikke ved å stole på en heuristikk for innebygde flagg, fordi fonter i et lastet dokument never went through the creation-side bookkeeping that sets such flags. If the resolved stream exists, the font is a candidate; if not, it is skipped without error

Det ærlige kompromisset: en delsett-font inneholder bare glyfene som var til stede da delsettet ble opprettet. Hvis et etterfølgende verktøy, eller din egen kode, senere legger til tekst med samme font, alle tegn utenfor delsettet vil mangle omriss og vises som en manglende glyf. Gjør delsett-innbygging som det siste innholdsendrende trinnet, aldri før et redigeringstrinn. Den samme forsiktigheten gjelder hvis du planlegger å hente ut fonten igjen senere for gjenbruk; artikkelen om uthenting av tekst, bilder og fonter med PDFlibPas dekker hva et uthentet delsettprogram kan og ikke kan gi deg

Hvordan DownsampleImages avgjør hvilke bilder som skal krympes?

DownsampleImages(MaxDPI, Quality, Filter) resampler bare bildene den trygt kan kalle oversamplet, ved hjelp av et bevisst konservativt DPI-estimat. Et PDF-bilde XObject lagrer pikseldimensjoner, men ingen pålitelig fysisk oppløsning, og en eventuell DPI-tagg fra kildebildet overlever sjelden en last-rediger-lagre-syklus. Så operasjonen estimerer SrcDPI = PixelWidth / 8.5, og spør i praksis: hvis dette bildet spente over hele bredden av en Letter-side, hva ville oppløsningen vært? Bare bilder der estimatet overskrider MaxDPI blir berørt. Skjevheten er tilsiktet: et bilde plassert lite på siden har en reell DPI som er høyere enn estimatet, så operasjonen underutløser heller enn å forringe en utskriftskvalitet-ressurs den ikke kan måle

Quality fra 1 til 100 velger JPEG-re-kodingskvaliteten, mens 0 beholder utdataene som tapsfri PNG-lignende Flate; Filter velger resampling-kjernen, 0 for et boksgjennomsnitt og 1 for bilineær. For skannede kontordokumenter er DownsampleImages(150, 75, 1) et fornuftig utgangspunkt; for alt som kan bli trykket på nytt, øk MaxDPI til 300 eller hopp over operasjonen helt. Nedskalering er det eneste tapssensitive trinnet av de tre, så det bør ligge bak en innstilling som brukerne dine kan slå av

Konvertere eldre LZW-strømmer med NormalizeLZWStreams

NormalizeLZWStreams er den gratis gevinsten: den dekomprimerer tapsfritt hver LZWDecode-strøm og rekomprimerer den med FlateDecode på stedet, og returnerer antallet konverterte strømmer. Den håndterer både en enkelt /Filter /LZWDecode-oppføring og LZW som vises i en filterkjedearray, der bare LZW-koblingen erstattes og resten av kjeden bevares. Predikator-parametere (Predictor, Columns, Colors, BitsPerComponent) leses fra strømmens DecodeParms og sendes videre til dekomprimereren, slik at predikator-kodet bildedata går riktig frem og tilbake. Fordi begge filtrene er bit-nøyaktige kodeker, er de dekomprimerte bytene identiske før og etter; bare beholderkomprimeringen endres, noe som gjør at denne operasjonen er trygg å kjøre ubetinget på alle filer

På et dokument uten LZW-strømmer returnerer kallet ganske enkelt 0 og rører ingenting, noe bibliotekets regresjonssuite tester eksplisitt: en nyopprettet fil med bare Flate må rapportere null konverteringer. Denne no-op-garantien betyr mye når operasjonen ligger i en pipeline som behandler tusenvis av heterogene filer, noen fra 2024 og noen fra 1998

Den komplette størrelsesoptimaliserings-pipelinen i Delphi

De tre operasjonene kombineres til en enkelt last-optimaliser-lagre-funksjon, og rekkefølgen betyr mindre enn du kanskje forventer fordi de opererer på adskilte objekttyper: fonter, bilde-XObjects og strømfiltre. Å kjøre delsett-innbygging først er likevel det ryddige valget, siden det er operasjonen med en redigeringsrekkefølge-begrensning

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: tre nuller betyr at filen allerede var slank
  finally
    Lib.Free;
  end;
end;

Verifiser pipelinen slik biblioteket verifiserer seg selv: tur-retur. Regresjonstestene i v3.130 oppretter et dokument, lagrer det, laster det inn på nytt, kjører optimaliseringen, lagrer igjen, og bekrefter deretter tre ting: utdataene er mindre, de returnerte antallene samsvarer med forventningene, og en ny innlasting av den optimaliserte filen kan fortsatt analyseres og rendres. Å reprodusere denne opprett-optimaliser-reinnlast-løkken mot et utvalg av dine egne produksjonsfiler, og sammenligne uthentet tekst før og etter, er en investering på en time som fanger opp integrasjonsfeil lenge før en kunde åpner en ødelagt faktura

// Round-trip check: den optimaliserte filen må fortsatt kunne lastes inn uten feil
Lib := TPDFlib.Create;
try
  Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
  Assert(Lib.GetPageCount > 0);
finally
  Lib.Free;
end;

Hvor passer pipelinen inn i en sammenslåingsarbeidsflyt? Etter sammenslåingen, ikke under den. Å slå sammen først og optimalisere det enkelte resultatet betyr at hver innebygd font delsett-innbygges én gang mot unionen av alle brukte tegn, i stedet for per kildefil. Hvis sammenslåingskapasiteten er flaskehalsen, tilbyr PDFlibPas en rask bane på bytenivå som unngår full objektanalyse, beskrevet i artikkelen om rask PDF-sammenslåing med byte-referanseskifte; og for inndata som er for store til å holdes helt i minnet, dekker direkte filtilgang for sammenslåing og oppsplitting av store PDF-er den strømmende ruten. Begge parer seg naturlig med en avsluttende optimaliseringsoperasjon på de sammenslåtte utdataene

Hva de tre operasjonene ikke vil gjøre

losLab PDF Librarys optimaliseringstrio ekskluderer bevisst alt som endrer dokumentsemantikk. SubsetEmbeddedFonts forener ikke dupliserte fonter på tvers av sammenslåtte kilder til ett program, den krymper hver uavhengig; deduplisering er en annen og mer risikabel transformasjon. DownsampleImages vil hoppe over et bilde der det konservative DPI-estimatet forblir under terskelen selv om et menneske kunne se at det er overdimensjonert for rammen sin. Og ingen av operasjonene berører dokumentstrukturen, så en fil som er blåst opp av tusenvis av foreldreløse objekter trenger en lagring med omskriving i stedet for disse operasjonene på strømnivå. Innenfor disse grensene fjerner kombinasjonen av fontdelsett-innbygging, bildenikerskalering og LZW-til-Flate-normalisering de tre klassiske kildene til PDF-oppblåsing med ett forutsigbart API-kall hver. De tre funksjonene leveres som en del av losLab PDF Library for Delphi, C# og VB.NET, sammen med sammenslåings-, uthentings- og rendrings-API-ene diskutert ovenfor