Teknisk artikkel

Sammenslåing av sider og vannmerkemaler i Delphi PDF-er

Sammenslåing av sider setter sammen innholdet av eksisterende sider på et felles lerret, noe som er en annen operasjon enn å slå sammen filer. losLab PDF Library implementerer Form XObject-sidesammenslåing og sammensettbare vannmerkemaler for Delphi: StitchPageOverlay, StitchPagesSideBySide og StitchPagesVertically plasserer fangede sider i enhver skala med full vektor-nøyaktighet, og TPDFlibCompositeWatermark stabler tekst- og bildevannmerker over et sideområde i ett enkelt kall. Hvis det du faktisk trenger er å legge til hele sider fra flere dokumenter inn i én fil, er det merge-familien, som dekkes i artikkelen om rask PDF-sammenslåing på bytenivå; denne artikkelen handler om å kombinere sideinnhold innenfor ett enkelt koordinatrom

Forespørslene som fører hit er dagdagse og gjenstridige. En skole ønsker to eksamenssider plassert på ett enkelt ark i liggende format for å halvere papirforbruket. En lokaliserings-kontrollør ønsker den engelske siden og oversettelsen side om side på en enkelt side, slik at de rulles sammen. Et juridisk team ønsker hvert utkast stemplet med tre lag samtidig: en logo in den corner, en diagonal KONFIDENSIELT over midten, og en liten bunntekstlinje, kun på side 1 til 3. Alle tre er det samme underliggende problemet, nemlig å plassere sideinnhold som allerede er satt opp, et annet sted i en annen størrelse uten å forringe det til et bitmap

Hvorfor feiler sammensetting av PDF-innholdsstrømmer?

Enkel sammenslåing, ved å kopiere kildesidens /Contents-strøm inn i målsiden, feiler av tre strukturelle grunner. For det første kolliderer koordinater: begge innholdsstrømmene antar sin egen opprinnelse i nedre venstre hjørne av sin egen MediaBox, så det limte innholdet havner oppå det eksisterende innholdet på feil sted. For det andre kolliderer resursnavn: hver side slår opp operatorer som /F1 Tf og /Im0 Do mot sin egen /Resources-ordbok, og to sider binder rutinemessig samme navn to forskjellige fonter eller bilder, slik at en av dem lydløst tegnes med feil resurs. For det tredje gir en ren strømkopi deg ingen skalering i det hele tatt; det beste du kan gjøre er å translatere

Form XObjects løser alle tre på en gang, og det er derfor losLab PDF Library bygger sin sammenslåing på dem. ISO 32000-1 §8.10 definerer et form XObject som en frittstående innholdsstrøm med sin egen /Resources-ordbok og sitt eget koordinatrom, plassert på en side ved å kalle navnet under den transformasjonsmatrisen som til enhver tid er aktiv. CapturePage pakker en hel eksisterende side, innhold pluss resurser, inn i et /Subtype /Form XObject, og sammenslåingsfunksjonene posisjonerer det deretter med en cm-matrise: skaler, translater, roter etter eget ønske. Fordi kilden forblir vektor, forblir tekst tekst og baner baner på ethvert zoomnivå, den samme egenskapen som gjør vektorgrafikk og skyggelegger-mønstre uavhengig av oppløsning. Ingenting blir rasterisert, og den isolerte resursordboken betyr at den fangede siden ikke kan kjempe med målsiden om navnet /F1

Hvordan kombinerer du to PDF-sider på én enkelt side?

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) er det direkte svaret: det fanger begge kildesidene, oppretter en ny bredere side, og plasserer de to fangede sidene til venstre og høyre med en renne mellom dem, hver skalert for å passe til TargetHeight. Funksjonen returnerer det nye sidenummeret (1-basert) eller 0 ved feil, og begge kildesidene fanges og skjules som en del av operasjonen. Tilfellet med to eksamenssider per ark er en jobb på tre linjer

var
  Lib: TPDFlib;
  NewPage: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('exam.pdf', '');
    // Pages 1 and 2 on one new wider page with an 18pt gutter,
    // both scaled to a 595pt output height (A4 landscape)
    NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
    if NewPage > 0 then
      Lib.SaveToFile('exam-2up.pdf');
  finally
    Lib.Free;
  end;
end;

For vertikal sammensetning stabler StitchPagesVertically(PageRanges, Gap, TargetWidth) et helt sideområde på en høy side i poster-stil. PageRanges tar standard syntaks for områder, som for eksempel '1-3,5'. Hver kildeside skaleres slik at bredden samsvarer med TargetWidth, så høydebidraget fra hver side er dens egen høyde multiplisert med dens egen skaleringsfaktor, og høyden på utdatasiden er summen av disse pluss en Gap mellom hvert par. Dette er formen du vil ha for revisjonsdokumenter med kontinuerlig rulling eller lange kvitteringer satt sammen fra kilder i brevstørrelse

Hvordan legger du én side over en annen?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) tegner en eksisterende side oppå en annen eksisterende side, i stedet for å opprette en ny side. Kildesiden fanges som et form XObject og tegnes på målet i rektangelet du oppgitt, med en Opacity fra 0 til 1 (1 er ugjennomsiktig). Bredde og høyde er frie parametere, så plasseringen strekkes hvis forholdet ditt avviker fra kildesidens eget størrelsesforhold; send inn et proporsjonalt par når du ønsker et uforvrengt miniatyrbilde av kilden. En typisk bruk er et dokument der godkjenningsblokken vedlikeholdes som en egen side og stemples på forsiden

Hva skjer med sidenumre etter en fangst?

CapturePage forbruker kildesiden: innholdet flyttes inn i XObject-et, og den nå tomme siden skjules, slik at alle sider med høyere nummer enn kilden umiddelbart forskyves ned med én. Dette er den viktigste semantikken i hele API-et for sammenslåing. losLab PDF Library håndterer beregningene internt i hvert sammenslåingskall, StitchPageOverlay justerer målindeksen når målet ligger over kilden, og StitchPagesSideBySide fanger siden med høyest nummer først slik at indeksen til den andre fangsten forblir gyldig, som betyr at din egen omkransende kode må ta høyde for forskyvningen mellom kallene. To regler holder kode med flere sammenslåinger korrekt: Registrer eventuelle sidestørrelser eller sidenumre du vil trenge før den første fangsten, fordi de endrer seg etterpå. Og når du selv fanger flere sider i en løkke, må du iterere i synkende siderekkefølge, slik at skjuling av en senere side aldri forstyrrer indeksen til en tidligere side du ennå ikke har fanget

Hvordan bruker du et vannmerke med flere lag på et sideområde?

Vannmerkemal-laget i PDFlibWatermark.pas gjør om "definer én gang, bruk på mange sider" til objekter. TPDFlibWatermarkBase bærer det alle vannmerker deler: en nipunkts Position (wpTopLeft til wpBottomRight, pluss wpCustom som bruker rå X/Y-koordinater), en Angle i grader, en Opacity fra 0 til 1, og en PageRange-streng der tomt betyr alle sider. TPDFlibTextWatermark legger til tekst, en font-ID (standard til Helvetica), størrelse og en RGB-farge; TPDFlibImageWatermark legger til en bilde-ID fra et tidligere lagt til bilde pluss en plasseringsbredde og -høyde. TPDFlibCompositeWatermark inneholder en liste over under-vannmerker og bruker dem rekursivt, og TPDFlib.ApplyWatermark er inngangspunktet for ett enkelt kall som respekterer malens eget sideområde og returnerer antall stemplede sider. Det juridiske stempelet med tre lag ser slik ut

var
  Lib: TPDFlib;
  Logo: TPDFlibImageWatermark;
  Diagonal, Footer: TPDFlibTextWatermark;
  Stack: TPDFlibCompositeWatermark;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('draft.pdf', '');
    Stack := TPDFlibCompositeWatermark.Create;
    try
      Logo := TPDFlibImageWatermark.Create;
      Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
      Logo.Position := wpTopRight;
      Logo.Width := 90;
      Logo.Height := 32;
      Stack.Add(Logo);

      Diagonal := TPDFlibTextWatermark.Create;
      Diagonal.Text := 'CONFIDENTIAL';
      Diagonal.Position := wpCenter;
      Diagonal.Angle := 45;
      Diagonal.FontSize := 60;
      Diagonal.Opacity := 0.15;
      Stack.Add(Diagonal);

      Footer := TPDFlibTextWatermark.Create;
      Footer.Text := 'Internal review copy';
      Footer.Position := wpBottomCenter;
      Footer.FontSize := 9;
      Footer.Opacity := 0.6;
      Stack.Add(Footer);

      Stack.PageRange := '1-3';   // children inherit this range
      Lib.ApplyWatermark(Stack);
      Lib.SaveToFile('draft-marked.pdf');
    finally
      Stack.Free;                 // composite frees its children
    end;
  finally
    Lib.Free;
  end;
end;

To sammensetningsdetaljer er verdt å utdype. Områdearv er enveis og ikke-destruktiv: et barn med et tomt PageRange arver midlertidig sammensetningens område under Apply, mens et barn som setter sitt eget område beholder det, slik at du kan feste bunnteksten til hver side mens det diagonale stempelet bare dekker utkastseksjonen. Og eierskapet følger sammensetningen: TPDFlibCompositeWatermark.Destroy frigjør sine barn, så du frigjør sammensetningen én gang og ingenting annet, som i eksempelet over

Grenser verdt å kjenne til

To ærlige begrensninger gjelder, og begge følger av hvordan mekanismen fungerer. For det første pakker en fangst kun inn sidens innholdsstrøm og resurser. Annotasjoner, lenker, skjemafelt og kommentarer lever som separate objekter i sideordboken, ikke i innholdsstrømmen, så de overføres ikke til XObject-et; et sammenslått oppsett bærer det synlige sideinnholdet, men ikke det interaktive laget fra kildene. For det andre er den nipunkts tekstposisjoneringen en tilnærming: uten en full layoutmotor kan ikke biblioteket vite den nøyaktige tegnede bredden på en streng, så det anslår tekstboksen til 40 % av sidebredden når det løser posisjoner som wpCenter. Det er nøyaktig nok for å sentrere en kort etikett, men for piksel-nøyaktig plassering av lange strenger bør du bytte til wpCustom og beregne koordinatene selv

Skala er ikke en praktisk begrensning her, siden fangst og sammenslåing manipulerer objektreferanser fremfor å kode sideinnhold på nytt; hvis kildene dine er på hundrevis av megabyte, gjelder den samme disiplinen som er beskrevet i veiledningen for sammenslåing og oppsplitting av store filer med direkte tilgang før du slår dem sammen. Sammenslåingsfunksjonene og vannmerkemalklassene som vises her, leveres i den nåværende losLab PDF Library for Delphi, C# og VB.NET, sammen med de laverenivå primitivene CapturePage, DrawCapturedPage og DrawCapturedPageMatrix når du trenger et oppsett de tre forhåndsbygde sammenslåingskallene ikke dekker