Odborný článok

Spájanie stránok a šablóny vodotlače v PDF v Delphi

Spájanie stránok (page stitching) spája obsah existujúcich stránok na spoločné plátno, čo je iná operácia ako zlučovanie súborov. losLab PDF Library implementuje spájanie stránok pomocou Form XObject a kombinovateľné šablóny vodotlače pre Delphi: metódy StitchPageOverlay, StitchPagesSideBySide a StitchPagesVertically umiestňujú zachytené stránky v akejkoľvek mierke s plnou vektorovou vernosťou a TPDFlibCompositeWatermark spája textové a obrázkové vodotlače na celom rozsahu stránok v jednom volaní. Ak v skutočnosti potrebujete spojiť celé stránky z viacerých dokumentov do jedného súboru, ide o rodinu zlučovania (merge), ktorá je popísaná v článku o rýchlom zlučovaní PDF na úrovni bajtov; tento článok je o kombinovaní obsahu stránok v rámci jedného súradnicového priestoru

Požiadavky, ktoré sem vedú, sú obyčajné a neústupné. Škola chce umiestniť dve stránky skúšky na jeden hárok na šírku, aby o polovicu znížila spotrebu papiera. Korektor lokalizácie chce vidieť anglickú stránku a jej preklad vedľa seba na jedinej stránke, aby sa rolovali spoločne. Právny tím chce mať každý koncept označený tromi vrstvami naraz: logom v rohu, diagonálnym nápisom CONFIDENTIAL (DÔVERNÉ) v strede a malým riadkom päty, a to len na stranách 1 až 3. Všetky tri scenáre predstavujú rovnaký základný problém, a to umiestnenie už vysádzaného obsahu stránky inam a v inej veľkosti bez toho, aby sa degradoval na bitmapu

Prečo zlyháva spájanie prúdov obsahu PDF?

Naivné spájanie, teda skopírovanie prúdu /Contents zo zdrojovej stránky do cieľovej, zlyháva z troch štrukturálnych dôvodov. Po prvé, kolidujú súradnice: oba prúdy obsahu predpokladajú svoj vlastný počiatok v ľavom dolnom rohu vlastného boxu MediaBox, takže vložený obsah skončí na vrchu existujúceho obsahu na nesprávnom mieste. Po druhé, kolidujú názvy prostriedkov (resources): každá stránka vyhodnocuje operátory ako /F1 Tf a /Im0 Do voči svojmu vlastnému slovníku /Resources, a dve stránky bežne viažu rovnaký názov na rôzne písma alebo obrázky, takže jedna z nich sa potichu vykreslí s nesprávnym prostriedkom. Po tretie, kopírovanie čistého prúdu vám neposkytuje žiadnu možnosť zmeny mierky; najlepšie, čo môžete urobiť, je posun (translate)

Form XObjects riešia všetky tri problémy naraz, a preto na nich losLab PDF Library stavia svoje spájanie. Norma ISO 32000-1 §8.10 definuje form XObject ako samostatný prúd obsahu s vlastným slovníkom /Resources a vlastným súradnicovým priestorom, ktorý sa umiestni na stránku vyvolaním jeho názvu pod akoukoľvek aktívnou maticou transformácie. Metóda CapturePage zabalí celú existujúcu stránku, obsah aj prostriedky, do objektu XObject s vlastnosťami /Subtype /Form, a spojovacie funkcie ju potom umiestnia pomocou matice cm: zmena mierky (scale), posun (translate), otočenie (rotate) podľa ľubovôle. Keďže zdroj zostáva vektorový, text zostáva textom a cesty cestami pri akejkoľvek úrovni priblíženia, čo je rovnaká vlastnosť, vďaka ktorej sú vektorová grafika a vzory tieňovania nezávislé od rozlíšenia. Nič sa nerasterizuje a izolovaný slovník prostriedkov znamená, že zachytená stránka nemôže bojovať s cieľovou stránkou o názov /F1

Ako skombinovať dve stránky PDF na jednu stránku?

Priamou odpoveďou je metóda StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight): zachytí obe zdrojové stránky, vytvorí novú širšiu stránku a umiestni tieto dve zachytenia vľavo a vpravo s medzerou (gutter) medzi nimi, pričom každá je prispôsobená mierke tak, aby sa zmestila do výšky TargetHeight. Funkcia vráti číslo novej stránky (od 1) alebo 0 pri zlyhaní, pričom obe zdrojové stránky sú v rámci operácie zachytené a skryté. Prípad skúšky s dvoma stránkami na hárok je prácou na tri riadky

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;

Pre vertikálne skladanie spája metóda StitchPagesVertically(PageRanges, Gap, TargetWidth) celý rozsah stránok do jednej vysokej stránky v štýle plagátu. Parameter PageRanges preberá štandardnú syntax rozsahu, napríklad '1-3,5'. Každá zdrojová stránka sa zmení tak, aby jej šírka zodpovedala TargetWidth, takže výška, ktorou každá stránka prispeje, je jej vlastná výška vynásobená jej vlastným koeficientom mierky, a výška výslednej stránky je súčtom týchto výšok plus jedna medzera Gap medzi každým párom. Toto je formát, ktorý chcete pre dokumenty s nepretržitým rolovaním alebo dlhé účtenky zostavené zo zdrojov veľkosti Letter

Ako prekryť jednu stránku druhou?

Metóda StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) vykreslí jednu existujúcu stránku na inú existujúcu stránku, namiesto vytvárania novej. Zdrojová stránka sa zachytí ako form XObject a vykreslí sa na cieľovú stránku v zadanom obdĺžniku s nepriehľadnosťou Opacity od 0 do 1 (1 znamená nepriehľadný). Šírka a výška sú voľné parametre, takže umiestnenie sa roztiahne, ak sa váš pomer líši od vlastného pomeru strán zdrojovej stránky; ak chcete nezdeformovaný miniatúrny náhľad zdroja, odovzdajte proporčnú dvojicu rozmerov. Typickým použitím je dokument, ktorého schvaľovací blok sa udržiava ako samostatná stránka a otlačí sa 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;

Čo sa stane s číslami stránok po zachytení?

Metóda CapturePage skonzumuje zdrojovú stránku: jej obsah sa presunie do objektu XObject a teraz prázdna stránka sa skryje, takže každá stránka s vyšším číslom ako zdrojová sa okamžite posunie o jedno číslo nadol. Toto je najdôležitejšie sémantické pravidlo v celom rozhraní API na spájanie stránok. losLab PDF Library rieši túto aritmetiku vo vnútri každého volania spájania, metóda StitchPageOverlay upraví cieľový index, keď je cieľ nad zdrojom, a StitchPagesSideBySide zachytí najprv stránku s vyšším číslom, takže index druhého zachytenia je stále platný, ale váš vlastný kód musí s týmto posunom medzi volaniami počítať. Správnosť kódu s viacerými spojeniami zabezpečujú dve pravidlá. Zaznamenajte si všetky veľkosti stránok alebo čísla stránok, ktoré budete potrebovať, pred prvým zachytením, pretože potom sa zmenia. A keď sami zachytávate viacero stránok v slučke, postupujte v zostupnom poradí stránok, aby skrytie neskoršej stránky nikdy nenarušilo index skoršej stránky, ktorú ste ešte nezachytili

Ako aplikovať viacvrstvovú vodotlač na rozsah stránok?

Vrstva šablóny vodotlače v PDFlibWatermark.pas mení pravidlo „definuj raz, aplikuj na veľa stránok“ na objekty. Trieda TPDFlibWatermarkBase nesie to, čo zdieľa každá vodotlač: deväťbodovú pozíciu Position (od wpTopLeft po wpBottomRight plus wpCustom, ktorá používa surové súradnice X/Y), uhol Angle v stupňoch, nepriehľadnosť Opacity od 0 do 1 a reťazec PageRange, kde prázdna hodnota znamená všetky stránky. Trieda TPDFlibTextWatermark pridáva text, ID písma (predvolene Helvetica), veľkosť a farbu RGB; TPDFlibImageWatermark pridáva ID obrázka z predtým pridaného obrázka plus šírku a výšku umiestnenia. TPDFlibCompositeWatermark obsahuje zoznam podvodotlačí a aplikuje ich rekurzívne a TPDFlib.ApplyWatermark je vstupný bod na jedno volanie, ktorý rešpektuje vlastný rozsah stránok šablóny a vracia počet opečiatkovaných stránok. Trojvrstvová právna pečiatka vyzerá takto

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;

Za zmienku stoja dva detaily zloženia. Dedičnosť rozsahu je jednosmerná a nedeštruktívna: potomok s prázdnym PageRange dočasne zdedí rozsah kompozitu počas vykonávania Apply, zatiaľ čo potomok, ktorý definuje svoj vlastný rozsah, si ho ponechá, takže môžete pätičku pripnúť na každú stránku, zatiaľ čo diagonálna pečiatka pokrýva iba sekciu konceptu. A vlastníctvo nasleduje kompozit: metóda TPDFlibCompositeWatermark.Destroy uvoľní svojich potomkov, takže uvoľníte kompozit raz a nič iné, ako v príklade vyššie

Obmedzenia, ktoré sa oplatí poznať

Platia dve úprimné obmedzenia a obe vyplývajú zo spôsobu, akým mechanizmus funguje. Po prvé, zachytenie zabaľuje iba prúd obsahu a prostriedky stránky. Anotácie, odkazy, formulárové polia a komentáre žijú ako samostatné objekty v slovníku stránky, nie v prúde obsahu, takže sa do objektu XObject neprenášajú; spojené rozloženie nesie viditeľný obsah stránky, ale nie interaktívnu vrstvu jej zdrojov. Po druhé, deväťbodové umiestnenie textu je len približné: bez plnohodnotného rozvrhového enginu (layout engine) knižnica nemôže poznať presnú vykreslenú šírku reťazca, takže pri vyhodnocovaní pozícií ako wpCenter odhaduje textový rámec na 40 % šírky stránky. To je dostatočne presné na vycentrovanie krátkeho štítku, ale pre pixelovo presné umiestnenie dlhých reťazcov prepnite na wpCustom a súradnice si vypočítajte sami

Mierka tu nepredstavuje praktické obmedzenie, keďže zachytenie a spojenie manipulujú s odkazmi na objekty a nie s opätovným kódovaním obsahu stránky; ak majú vaše zdroje stovky megabajtov, pred spájaním platí rovnaká disciplína popísaná v príručke pre zlučovanie a rozdeľovanie veľkých súborov s priamym prístupom. Spojovacie funkcie a triedy šablón vodotlače zobrazené v tomto článku sa dodávajú v aktuálnej knižnici losLab PDF Library pre Delphi, C# a VB.NET spolu s nízkoúrovňovými primitívmi CapturePage, DrawCapturedPage a DrawCapturedPageMatrix pre prípady, keď potrebujete rozloženie, ktoré tri predpripravené spojovacie volania nepokrývajú