Technický článek

Skládání stránek a šablony vodoznaků v PDF v Delphi

Skládání stránek komponuje obsah existujících stránek na společné plátno, což je jiná operace než slučování souborů. losLab PDF Library implementuje skládání stránek pomocí Form XObject a skládatelné šablony vodoznaků pro Delphi: metody StitchPageOverlay, StitchPagesSideBySide a StitchPagesVertically umísťují zachycené stránky v jakémkoli měřítku s plnou vektorovou přesností a třída TPDFlibCompositeWatermark vrství textové a obrázkové vodoznaky napříč rozsahem stránek v jediném volání. Pokud ve skutečnosti potřebujete připojit celé stránky z několika dokumentů do jednoho souboru, jedná se o rodinu slučování, která je popsána v článku o rychlém slučování PDF na úrovni bajtů; tento článek se zabývá kombinováním obsahu stránek v rámci jednoho souřadnicového prostoru

Požadavky, které vedou k tomuto řešení, jsou běžné a naléhavé. Škola chce umístit dvě zkušební stránky na jeden list na šířku, aby ušetřila polovinu papíru. Korektor lokalizace chce vidět anglickou stránku a její překlad vedle sebe na jediné stránce, aby jimi mohl posouvat současně. Právní tým požaduje, aby byl každý koncept označen třemi vrstvami najednou: logem v rohu, diagonálním nápisem DŮVĚRNÉ (CONFIDENTIAL) uprostřed a malým řádkem v zápatí, a to pouze na stránkách 1 až 3. Všechny tři případy představují stejný základní problém, konkrétně umístění již vysázeného obsahu stránky na jiné místo a v jiné velikosti, aniž by došlo k jeho znehodnocení na bitmapu

Proč selhává zřetězení proudů obsahu PDF?

Naivní skládání, tedy kopírování proudu /Contents zdrojové stránky do cílové stránky, selhává ze tří strukturálních důvodů. Za prvé dochází ke kolizi souřadnic: oba proudy obsahu předpokládají svůj vlastní počátek v levém dolním rohu svého vlastního MediaBoxu, takže vložený obsah skončí na nesprávném místě přes stávající obsah. Za druhé dochází ke kolizi názvů prostředků: každá stránka vyhodnocuje operátory jako /F1 Tf a /Im0 Do vůči svému vlastnímu slovníku /Resources a dvě stránky běžně přiřazují stejný název různým písmům nebo obrázkům, takže jedna z nich se tiše vykreslí s nesprávným prostředkem. Za třetí, kopírování surového proudu neposkytuje vůbec žádné měřítko; nejlepší, co můžete udělat, je posunutí (translace)

How do you combine two PDF pages onto one page?

Přímou odpovědí je StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight): zachytí obě zdrojové stránky, vytvoří novou širší stránku a umístí oba zachycené obrazy vlevo a vpravo s mezerou mezi nimi, přičemž každý z nich je změněn tak, aby odpovídal TargetHeight. Funkce vrací číslo nové stránky (od 1) nebo 0 při selhání, a obě zdrojové stránky jsou v rámci operace zachyceny a skryty. Případ zkoušky se dvěma stránkami na list je záležitostí tří řádků kódu

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;

Pro svislé skládání skládá metoda StitchPagesVertically(PageRanges, Gap, TargetWidth) celý rozsah stránek do jedné vysoké stránky ve stylu plakátu. Parametr PageRanges přijímá standardní syntaxi rozsahu, jako je například '1-3,5'. Každá zdrojová stránka je změněna tak, aby její šířka odpovídala TargetWidth, takže výška, kterou přispívá každá stránka, je její vlastní výška vynásobená jejím vlastním faktorem měřítka, a výsledná výška stránky je součtem těchto výšek plus jedna mezera Gap mezi každou dvojicí. To je formát vhodný pro dokumenty k revizi s plynulým posouváním nebo dlouhé účtenky sestavené ze zdrojů o velikosti Letter

Jak překrýt jednu stránku druhou?

Metoda StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) vykreslí jednu existující stránku na druhou existující stránku, namísto vytváření nové. Zdrojová stránka je zachycena jako objekt form XObject a vykreslena na cílovou stránku v zadaném obdélníku, s krytím Opacity od 0 do 1 (1 znamená neprůhledné). Šířka a výška jsou volné parametry, takže umístění se roztáhne, pokud se váš poměr liší od vlastního poměru stran zdrojové stránky; chcete-li nezdeformovaný náhled zdroje, předejte proporcionální dvojici hodnot. Typickým použitím je dokument, jehož schvalovací blok je udržován jako samostatná stránka a orazítkován na obálku

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;

Co se stane s čísly stránek po zachycení?

Metoda CapturePage spotřebuje zdrojovou stránku: její obsah se přesune do objektu XObject a nyní prázdná stránka se skryje, takže každá stránka s číslem vyšším než zdrojová se okamžitě posune o jedno číslo dolů. To je nejdůležitější sémantika v celém rozhraní API pro skládání stránek. losLab PDF Library řeší výpočty uvnitř každého volání skládání, metoda StitchPageOverlay upravuje cílový index, když se cíl nachází nad zdrojem, a StitchPagesSideBySide zachytí nejprve stránku s vyšším číslem, aby index druhého zachycení byl stále platný, avšak váš vlastní kód musí s tímto posunem mezi voláními počítat. Správnost kódu s vícenásobným skládáním zajišťují dvě pravidla. Zaznamenejte si všechny velikosti stránek nebo čísla stránek, které budete potřebovat, před prvním zachycením, protože po něm se změní. A když sami zachycujete několik stránek v cyklu, postupujte v sestupném pořadí stránek, aby skrytí pozdější stránky nikdy nenarušilo index dřívější stránky, kterou jste ještě nezachytili

Jak aplikovat vícevrstvý vodoznak na rozsah stránek?

Vrstva šablon vodoznaků v PDFlibWatermark.pas mění princip „definovat jednou, aplikovat na mnoho stránek“ na objekty. TPDFlibWatermarkBase nese to, co sdílí každý vodoznak: devítibodovou pozici Position (od wpTopLeft po wpBottomRight, plus wpCustom, která používá surové souřadnice X/Y), úhel Angle ve stupních, krytí Opacity od 0 do 1 a řetězec PageRange pro rozsah stránek, kde prázdná hodnota znamená všechny stránky. TPDFlibTextWatermark přidává text, ID písma (výchozí je Helvetica), velikost a barvu RGB; TPDFlibImageWatermark přidává ID obrázku z dříve přidaného obrázku plus šířku a výšku umístění. TPDFlibCompositeWatermark uchovává seznam podvodoznaků a aplikuje je rekurzivně a TPDFlib.ApplyWatermark je přístupový bod pro jedno volání, který respektuje vlastní rozsah stránek šablony a vrací počet orazítkovaných stránek. Třívrstvé právní razítko vypadá následovně

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;

Omezení, o kterých je dobré vědět

Platí dvě upřímná omezení, a obě vyplývají z toho, jak mechanismus funguje. Za prvé, zachycení zahrnuje pouze proud obsahu stránky a prostředky. Anotace, odkazy, pole formulářů a komentáře žijí jako samostatné objekty ve slovníku stránky, nikoli v proudu obsahu, takže se do objektu XObject nepřenášejí; poskládané rozvržení nese viditelný obsah stránky, nikoli však interaktivní vrstvu svých zdrojů. Za druhé, devítibodové umístění textu je pouze přibližné: bez plnohodnotného vykreslovacího jádra knihovna nemůže znát přesnou vykreslenou šířku řetězce, takže při vyhodnocování pozic jako wpCenter odhaduje textový rámec na 40 % šířky stránky. To je dostatečně přesné pro vystředění krátkého štítku, ale pro na pixel přesné umístění dlouhých řetězců přejděte na wpCustom a souřadnice si vypočítejte sami

Měřítko zde nepředstavuje praktické omezení, protože zachycení a skládání manipuluje s odkazy na objekty, místo aby znovu kódovalo obsah stránky; pokud mají vaše zdroje stovky megabajtů, platí před skládáním stejný postup popsaný v průvodci slučováním a rozdělováním velkých souborů s přímým přístupem. Zde zobrazené funkce pro skládání a třídy šablon vodoznaků se dodávají v aktuální verzi losLab PDF Library pro Delphi, C# a VB.NET, spolu s nízkoúrovňovými primitivy CapturePage, DrawCapturedPage a DrawCapturedPageMatrix pro případ, kdy potřebujete rozvržení, které tři předpřipravená volání skládání nepokrývají