Technisch artikel

Pagina-samenvoeging en watermerk-sjablonen in Delphi PDF's

Pagina-samenvoeging (page stitching) componeert de inhoud van bestaande pagina's op een gedeeld canvas, wat een andere bewerking is dan het samenvoegen van bestanden. losLab PDF Library implementeert Form XObject-pagina-samenvoeging en samenstelbare watermerksjablonen voor Delphi: StitchPageOverlay, StitchPagesSideBySide en StitchPagesVertically plaatsen vastgelegde pagina's op elke schaal met volledige vectorgetrouwheid, en TPDFlibCompositeWatermark stapelt tekst- en afbeeldingswatermerken over een paginabereik in één aanroep. Als u eigenlijk hele pagina's uit verschillende documenten aan één bestand wilt toevoegen, is dat de samenvoeg-familie, die wordt behandeld in het artikel over snelle PDF-samenvoeging op byte-niveau; dit artikel gaat over het combineren van paginainhoud binnen één coördinatenruimte

De verzoeken die hiernaartoe leiden zijn alledaags en hardnekkig. Een school wil twee examenpagina's op één liggend vel plaatsen om het papierverbruik te halveren. Een lokalisatie-beoordelaar wil de Engelse pagina en de vertaling ervan naast elkaar op één pagina, zodat ze samen scrollen. Een juridisch team wil dat elk concept tegelijkertijd van drie lagen wordt voorzien: een logo in de hoek, een diagonaal VERTROUWELIJK over het midden, en een kleine voettekstregel, alleen op pagina 1 tot en met 3. Alle drie zijn hetzelfde onderliggende probleem, namelijk het plaatsen van reeds opgemaakte paginainhoud ergens anders en op een ander formaat zonder dat deze degradeert tot een bitmap

Waarom mislukt het samenvoegen van PDF-inhoudsstromen?

Naïeve samenvoeging, waarbij de /Contents-stroom van de bronpagina naar de doelpagina wordt gekopieerd, mislukt om drie structurele redenen. Ten eerste botsen coördinaten: beide inhoudsstromen gaan uit van hun eigen oorsprong linksonder in hun eigen MediaBox, waardoor de geplakte inhoud bovenop de bestaande inhoud op de verkeerde plaats terechtkomt. Ten tweede botsen resourcenamen: elke pagina lost operatoren zoals /F1 Tf en /Im0 Do op tegen zijn eigen /Resources-woordenboek, en twee pagina's koppelen routinematig dezelfde naam aan verschillende lettertypen of afbeeldingen, waardoor een daarvan geruisloos met de verkeerde resource wordt gerenderd. Ten derde biedt het kopiëren van een ruwe stroom u helemaal geen schaling; het beste wat u kunt doen is transleren

Form XObjects lossen alle drie de problemen tegelijk op, en dat is waarom losLab PDF Library haar pagina-samenvoeging hierop baseert. ISO 32000-1 §8.10 definieert een Form XObject als een onafhankelijke inhoudsstroom met een eigen /Resources-woordenboek en een eigen coördinatenruimte, die op een pagina wordt geplaatst door de naam ervan aan te roepen onder de actieve transformatiematrix. CapturePage verpakt een volledige bestaande pagina, inclusief inhoud en bronnen, in een /Subtype /Form XObject, en de samenvoegfuncties positioneren dit vervolgens met een cm-matrix: schalen, transleren en roteren naar wens. Omdat de bron vectorgebaseerd blijft, blijft tekst tekst en blijven paden paden op elk zoomniveau, dezelfde eigenschap die vectorafbeeldingen en verloopsjablonen resolutie-onafhankelijk maakt. Er wordt niets gerasterd en het geïsoleerde bronnenwoordenboek betekent dat de vastgelegde pagina niet met de doelpagina kan strijden om de naam /F1

Hoe combineert u twee PDF pagina's op één pagina?

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) is het directe antwoord: het legt beide bronpagina's vast, maakt een nieuwe, bredere pagina aan en plaatst de twee vastgelegde pagina's links en rechts met een tussenruimte ertussen, elk geschaald om te passen bij TargetHeight. De functie retourneert het nieuwe paginanummer (1-gebaseerd) of 0 bij mislukking, en beide bronpagina's worden vastgelegd en verborgen als onderdeel van de bewerking. Het examenvoorbeeld met twee pagina's per vel is een klusje van drie regels

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;

Voor verticale compositie stapelt StitchPagesVertically(PageRanges, Gap, TargetWidth) een heel paginabereik op tot één lange pagina in posterstijl. PageRanges gebruikt de standaardbereik-syntaxis zoals '1-3,5'. Elke bronpagina wordt geschaald zodat de breedte overeenkomt met TargetWidth, zodat de hoogtebijdrage van elke pagina zijn eigen hoogte vermenigvuldigd met de eigen schaalfactor is, en de resulterende paginahoogte de som hiervan is, plus één Gap tussen elk paar. Dit is de vorm die u wilt voor documenten met doorlopend schuiven voor beoordeling of voor lange bonnen die zijn samengesteld uit bronnen van Letter-formaat

Hoe plaatst u de ene pagina over de andere?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) tekent een bestaande pagina op een andere bestaande pagina, in plaats van een nieuwe te maken. De bronpagina wordt vastgelegd als een Form XObject en op de doelpagina getekend in de door u opgegeven rechthoek, met een Opacity van 0 tot 1 (waarbij 1 ondoorzichtig is). Breedte en hoogte zijn vrije parameters, dus de plaatsing rekt uit als uw verhouding afwijkt van de eigen aspectverhouding van de bronpagina; geef een proportioneel paar op als u een onvervormde miniatuur van de bron wilt. Een typisch gebruik is een document waarvan het goedkeuringsblok als een afzonderlijke pagina wordt onderhouden en op de omslag wordt gestempeld

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;

Wat gebeurt er met paginanummers na een vastlegging?

CapturePage consumeert de bronpagina: de inhoud ervan wordt verplaatst naar het XObject en de nu lege pagina wordt verborgen, zodat elke pagina met een hoger nummer dan de bron onmiddellijk één positie omlaag verschuift. Dit is de allerbelangrijkste semantiek in de hele pagina-samenvoegings-API. losLab PDF Library handelt de berekening af binnen elke samenvoegingsaanroep, StitchPageOverlay past de doelindex aan wanneer het doel zich boven de bron bevindt, en StitchPagesSideBySide legt de pagina met het hoogste nummer eerst vast zodat de index van de tweede vastlegging nog steeds geldig is, maar uw eigen omliggende code moet rekening houden met de verschuiving tussen aanroepen. Twee regels houden code met meerdere samenvoegingen correct. Noteer alle paginaformaten of paginanummers die u nodig hebt vóór de eerste vastlegging, omdat ze daarna veranderen. En wanneer u zelf meerdere pagina's vastlegt in een lus, doorloop deze dan in aflopende paginavolgorde, zodat het verbergen van een latere pagina nooit de index verstoort van een eerdere pagina die u nog niet hebt vastgelegd

Hoe past u een meerlaags watermerk toe op een paginabereik?

De watermerksjabloonlaag in PDFlibWatermark.pas verandert "eenmaal definiëren, op veel pagina's toepassen" in objecten. TPDFlibWatermarkBase bevat wat elk watermerk deelt: een negenpunts Position (wpTopLeft tot en met wpBottomRight, plus wpCustom die gebruikmaakt van ruwe X/Y-coördinaten), een Angle in graden, een Opacity van 0 tot 1, en een PageRange-tekenreeks waarbij een lege waarde alle pagina's betekent. TPDFlibTextWatermark voegt tekst toe, evenals een lettertype-ID (standaard Helvetica), grootte en een RGB-kleur; TPDFlibImageWatermark voegt een afbeeldings-ID toe van een eerder toegeegde afbeelding plus een plaatsingsbreedte en -hoogte. TPDFlibCompositeWatermark bevat een lijst met subwatermerken en past deze recursief toe, en TPDFlib.ApplyWatermark is het aanroep-ingangspunt dat het eigen paginabereik van het sjabloon respecteert en het aantal gestempelde pagina's retourneert. De drielaagse juridische stempel ziet er als volgt uit

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;

Twee compositiedetails zijn het vermelden waard. Bereikovererving is eenrichtingsverkeer en niet-destructief: een kind-watermerk met een leeg PageRange erft tijdelijk het bereik van de composiet over tijdens Apply, terwijl een kind dat zijn eigen bereik instelt dit behoudt. Hierdoor kunt u de voettekst op elke pagina pinnen, terwijl de diagonale stempel alleen het conceptgedeelte dekt. En het eigendom volgt de composiet: TPDFlibCompositeWatermark.Destroy geeft zijn kinderen vrij, dus u geeft de composiet eenmalig vrij en niets anders, zoals in het bovenstaande voorbeeld

Grenzen die u moet kennen

Er gelden twee eerlijke limieten, en beide vloeien voort uit de werking van het mechanisme. Ten eerste omvat een vastlegging alleen de inhoudsstroom en de bronnen van de pagina. Annotaties, koppelingen, formuliervelden en opmerkingen bestaan als afzonderlijke objecten in het paginawaardenboek, niet in de inhoudsstroom, dus ze verhuizen niet naar het XObject; een samengevoegde lay-out bevat de zichtbare paginainhoud, maar niet de interactieve laag van de bronnen. Ten tweede is de negenpunts tekstpositionering een benadering: zonder een volledige lay-outengine kan de bibliotheek de exacte gerenderde breedte van een tekenreeks niet weten, dus schat deze het tekstvak op 40% van de paginabreedte bij het oplossen van posities zoals wpCenter. Dat is nauwkeurig genoeg voor het centreren van een kort label, maar voor een pixel-nauwkeurige plaatsing van lange tekenreeksen schakelt u over naar wpCustom en berekent u zelf de coördinaten

Schaal is hier geen praktische beperking, aangezien vastlegging en samenvoeging objectreferenties manipuleren in plaats van de paginainhoud opnieuw te coderen; als uw bronnen honderden megabytes groot zijn, is dezelfde discipline van toepassing die is beschreven in de handleiding voor het samenvoegen en splitsen van grote bestanden met directe toegang voordat u gaat samenvoegen. De samenvoegfuncties en de watermerksjabloonklassen die hier worden getoond, worden geleverd in de huidige losLab PDF Library voor Delphi, C# en VB.NET, samen met de lagere-niveau primitives CapturePage, DrawCapturedPage en DrawCapturedPageMatrix voor wanneer u een lay-out nodig hebt die de drie vooraf gebouwde samenvoegingsaanroepen niet dekken