Teknisk artikel

Sideombrydning og vandmærkeskabeloner i Delphi PDF'er

Sammenføjning af sider sammensætter indholdet af eksisterende sider på et delt lærred, hvilket er en anden handling end at flette filer. losLab PDF Library implementerer Form XObject-sideombrydning og sammensættelige vandmærkeskabeloner til Delphi: StitchPageOverlay, StitchPagesSideBySide og StitchPagesVertically placerer fangede sider i enhver skala med fuld vektorpræcision, og TPDFlibCompositeWatermark stabler tekst- og billedvandmærker på tværs af et sideområde i ét kald. Hvis det, du rent faktisk har brug for, er at tilføje hele sider fra flere dokumenter til én fil, er det flettefamilien, der er dækket i artiklen om hurtig PDF-fletning på byteniveau; denne artikel handler om at kombinere sideindhold inden for ét koordinatområde

De forespørgsler, der fører hertil, er hverdagsagtige og stædige. En skole ønsker to eksamenssider placeret på ét liggende ark for at halvere papirforbruget. En lokaliseringskorrekturlæser ønsker den engelske side og dens oversættelse side om side på en enkelt side, rullet sammen. Et juridisk team ønsker, at hvert udkast stemples med tre lag på én gang: Et logo i hjørnet, et diagonalt CONFIDENTIAL hen over midten og en lille bundtekstlinje, kun på side 1 til 3. Alle tre er det samme underliggende problem, nemlig at placere allerede sat sideindhold et andet sted i en anden størrelse uden at forringe det til et bitmap

Hvorfor går det galt med at sammenkæde PDF-indholdsstrømme?

Naiv sammenføjning, hvor kildesidens /Contents-strøm kopieres til målsiden, mislykkes af tre strukturelle årsager. For det første kolliderer koordinater: Begge indholdsstrømme antager deres egen oprindelse i nederste venstre hjørne af deres egen MediaBox, så det indsatte indhold lander oven på det eksisterende indhold på det forkerte sted. For det andet kolliderer ressourcenavne: Hver side løser operatorer som /F1 Tf og /Im0 Do mod sin egen /Resources-ordbog, og to sider binder rutinemæssigt det samme navn to forskellige skrifttyper eller billeder, så den ene af dem renderes lydløst med den forkerte ressource. For det tredje giver en rå strømkopi dig slet ingen skalering; det bedste du kan gøre er at flytte

Form XObjects løser alle tre på én gang, og det er grunden til, at losLab PDF Library bygger sin sammenføjning på dem. ISO 32000-1 §8.10 definerer et form XObject som en selvstændig indholdsstrøm med sin egen /Resources-ordbog og sit eget koordinatområde, placeret på en side ved at kalde dens navn under den aktuelle transformationsmatrix, der er aktiv. CapturePage pakker en hel eksisterende side, indhold plus ressourcer, ind i et /Subtype /Form XObject, og sammenføjningsfunktionerne placerer det derefter med en cm-matrix: Skaler, flyt, roter efter behag. Fordi kilden forbliver vektor, forbliver tekst tekst og stier forbliver stier på ethvert zoomniveau, den samme egenskab, som gør vektorgrafik og skyggemønstre uafhængige af opløsningen. Intet rusteriseres, og den isolerede ressourceordbog betyder, at den fangede side ikke kan kæmpe med målsiden om navnet /F1

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

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) er det direkte svar: Den fanger begge kildesider, opretter en ny bredere side og placerer de to fangede sider til venstre og højre med en rende imellem dem, hver skaleret til at passe til TargetHeight. Funktionen returnerer det nye sidenummer (1-baseret) eller 0 ved fejl, og begge kildesider fanges og skjules som en del af handlingen. Eksamenssagen med to sider pr. ark er en opgave 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;

Til lodret komposition stabler StitchPagesVertically(PageRanges, Gap, TargetWidth) et helt sideområde på én høj side i plakatformat. PageRanges tager den standardiserede områdesyntaks som f.eks. '1-3,5'. Hver kildeside skaleres, så dens bredde matcher TargetWidth, så højdebiddraget fra hver side er dens egen højde ganget med dens egen skaleringsfaktor, og højden på output-siden er summen af disse plus en Gap mellem hvert par. Dette er den form, du ønsker til dokumenter til løbende rulning eller lange kvitteringer samlet fra kilder i brevstørrelse

Hvordan overlejrer du en side på en anden?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) tegner en eksisterende side oven på en anden eksisterende side i stedet for at oprette en ny. Kildesiden fanges som et form XObject og tegnes på målet ved det rektangel, du angiver, med en Opacity fra 0 til 1 (1 er uigennemsigtig). Bredde og højde er frie parametre, så placeringen strækkes, hvis dit forhold afviger fra kildesidens eget højde-bredde-forhold; send et proportionelt par, hvis du ønsker et uforvrænget miniaturebillede af kilden. En typisk anvendelse er et dokument, hvis godkendelsesblok vedligeholdes som sin egen side og stemples på forsiden

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('report.pdf', '');
    // Page 5 holds the approval block; place it on page 1
    // in a 260 x 180 box at 45% opacity. Page 5 is consumed:
    // it is captured and hidden, so pages 6..n become 5..n-1
    if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
      Lib.SaveToFile('report-approved.pdf');
  finally
    Lib.Free;
  end;
end;

Hvad sker der med sidenumre efter en fangst?

CapturePage forbruger kildesiden: Dens indhold flyttes ind i XObject'et, og den nu tomme side skjules, så alle sider med et højere nummer end kilden straks rykker ét nummer ned. Dette er den absolut vigtigste semantik i hele sammenføjnings-API'en. losLab PDF Library håndterer beregningerne inde i hvert stitch-kald, StitchPageOverlay justerer målindekset, når målet ligger over kilden, og StitchPagesSideBySide fanger siden med det højeste nummer først, så det andet fangede indeks stadig er gyldigt, men din egen omgivende kode skal tage højde for skiftet mellem kald. To regler holder kode med flere sammenføjninger korrekt. Registrer eventuelle sidestørrelser eller sidenumre, du får brug for, før den første fangst, fordi de ændrer sig bagefter. Og når du selv fanger flere sider i en løkke, skal du iterere i faldende siderækkefølge, så skjulning af en senere side aldrig forstyrrer indekset for en tidligere side, du endne ikke har fanget

Hvordan anvender du et vandmærke i flere lag på et sideområde?

Vandmærkeskabelonlaget i PDFlibWatermark.pas forvandler "definer én gang, anvend på mange sider" til objekter. TPDFlibWatermarkBase indeholder det, som alle vandmærker har til fælles: En nipunkts Position (wpTopLeft til wpBottomRight, plus wpCustom som bruger rå X/Y-koordinater), en Angle i grader, en Opacity fra 0 til 1 og en PageRange-streng, hvor tom betyder alle sider. TPDFlibTextWatermark tilføjer tekst, et skrifttype-id (standard er Helvetica), størrelse og en RGB-farve; TPDFlibImageWatermark tilføjer et billed-id fra et tidligere tilføjet billede plus en placeringsbredde og -højde. TPDFlibCompositeWatermark indeholder en liste over under-vandmærker og anvender dem rekursivt, og TPDFlib.ApplyWatermark er indgangspunktet med ét kald, der respekterer skabelonens eget sideområde og returnerer antallet af stemplede sider. Det juridiske stempel i tre lag ser således ud

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 kompositionsdetaljer er værd at uddybe. Områdearv er ensidig og ikke-destruktiv: Et barn med et tomt PageRange arver midlertidigt kompositens område under Apply, dens eget område, beholder det, så du kan fastgøre bundteksten til hver side, mens det diagonale stempel kun dækker udkastsafsnittet. Og ejerskabet følger kompositen: TPDFlibCompositeWatermark.Destroy frigør sine børn, så du frigør kompositen én gang og intet andet, som i eksemplet ovenfor

Grænser, der er værd at kende

To ærlige grænser gælder, og begge følger af, hvordan mekanismen fungerer. For det første omslutter en fangst kun sidens indholdsstrøm og ressourcer. Annoteringer, links, formularfelter og kommentarer lever som separate objekter i sideordbogen, ikke i indholdsstrømmen, så de rejser ikke med ind i XObject'et; et sammenføjet layout bærer det synlige sideindhold, men ikke det interaktive lag fra dets kilder. For det andet er tekstpositioneringen i ni punkter en tilnærmelse: Uden en komplet layoutmotor kan bibliotekets ikke kende den nøjagtige renderede bredde af en streng, så det estimerer tekstboksen til 40% af sidebredden, når det løser positioner som wpCenter. Det er præcist nok til at centrere en kort etiket, men for pixelpræcis placering af lange strenge skal du skifte til wpCustom og selv beregne koordinaterne

Skala er ikke en praktisk begrænsning her, da fangst og sammenføjning manipulerer objektreferencer i stedet for at omkode sideindhold; hvis dine kilder er på hundredvis af megabytes, gælder den samme disciplin, som er beskrevet i vejledningen til fletning og opdeling af store filer med direkte adgang, før du foretager sammenføjningen. Sammenføjningsfunktionerne og vandmærkeskabelonklasserne, der vises her, leveres i den aktuelle losLab PDF Library til Delphi, C# og VB.NET, sammen med de lavere niveauer af primitive CapturePage, DrawCapturedPage og DrawCapturedPageMatrix, når du har brug for et layout, som de tre prædefinerede sammenføjningskald ikke dækker