Technikai cikk

Oldal-összefűzés és vízjel-sablonok Delphi PDF-ekben

A lap-összefűzés (page stitching) a meglévő oldalak tartalmát komponálja egy közös vászonra, ami különbözik a fájlok összefűzésétől (merging). A losLab PDF Library megvalósítja a Form XObject alapú oldal-összefűzést és a komponálható vízjel-sablonokat Delphihez: a StitchPageOverlay, a StitchPagesSideBySide és a StitchPagesVertically tetszőleges méretben, teljes vektoros hűséggel helyezi el a rögzített oldalakat, míg a TPDFlibCompositeWatermark egyetlen hívással rétegezi a szöveges és képi vízjeleket egy oldaltartományon keresztül. Ha Önnek valójában arra van szüksége, hogy több dokumentum teljes oldalait fűzze össze egyetlen fájlba, akkor ez a merge funkciócsalád, amelyet a gyors, bájtszintű PDF-összefűzésről szóló cikk tárgyal; ez a cikk a tartalom egyetlen koordináta-térben történő egyesítéséről szól

Az idevezető kérések hétköznapiak és makacsok. Egy iskola két vizsgaoldalt szeretne egyetlen fekvő lapra helyezni, hogy a felére csökkentse a papírhasználatot. Egy fordító a meglévő angol oldalt és annak fordítását szeretné egymás mellett látni egyetlen oldalon, együtt görgetve. Egy jogi csapat azt szeretné, ha minden vázlat egyszerre három réteggel lenne ellátva: a logóval a sarokban, egy átlós BIZALMAS felirattal a közepén, és egy kis lábléccel, kizárólag az 1–3. oldalakon. Mindhárom ugyanarra a mögöttes problémára vezethető vissza, nevezetesen a már kiszedett oldaltartalom máshova történő elhelyezésére más méretben, anélkül, hogy azt bitképpé rontanánk le

Miért hibásodik meg a PDF tartalomfolyamok egyszerű összefűzése?

A naiv összefűzés — a forrásoldal /Contents folyamának másolása a céloldalra — három strukturális okból is meghiúsul. Először is, a koordináták ütköznek: mindkét tartalomfolyam a saját MediaBox-ának bal alsó sarkában feltételezi a saját origóját, így a beillesztett tartalom a meglévő tartalom tetejére, rossz helyre kerül. Másodszor, az erőforrásnevek ütköznek: minden oldal a saját /Resources szótárához viszonyítva oldja fel az olyan operátorokat, mint a /F1 Tf és a /Im0 Do, és két oldal gyakran ugyanazt a nevet különböző betűtípusokhoz vagy képekhez rendeli, így az egyikük csendben a rossz erőforrással jelenik meg. Harmadszor, a nyers folyam másolása egyáltalán nem teszi lehetővé a nagyítást; a legtöbb, amit tehet, az az eltolás

Form XObject-ek mindhárom problémát egyszerre oldják meg, és a losLab PDF Library ezért építi rájuk az összefűzést. Az ISO 32000-1 §8.10 szabvány a form XObject-et önálló tartalomfolyamként definiálja, amely saját /Resources szótárral és saját koordináta-térrel rendelkezik, és amelyet az éppen aktív transzformációs mátrix alatt a nevének hívásával helyeznek el egy oldalon. A CapturePage egy teljes meglévő oldalt — a tartalmat és az erőforrásokat is beleértve — egy /Subtype /Form XObject-be csomagol, és az összefűző függvények ezután egy cm mátrix segítségével pozicionálják azt: tetszés szerint méretezik, eltolják vagy elforgatják. Mivel a forrás vektoros marad, a szöveg szöveg, az útvonalak pedig útvonalak maradnak bármilyen nagyítási szinten, ami megegyezik azzal a tulajdonsággal, amely a vektorgrafikát és az árnyékolási mintákat felbontásfüggetlenné teszi. Semmi sem lesz raszterizálva, és az izolált erőforrás-szótár azt jelenti, hogy a rögzített oldal nem ütközhet a céloldallal a /F1 név miatt

Hogyan lehet két PDF oldalt egyetlen oldalon egyesíteni?

A StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) a közvetlen válasz: rögzíti mindkét forrásoldalt, létrehoz egy új, szélesebb oldalt, és a két rögzített oldalt balra és jobbra helyezi el egy margóval közöttük, mindegyiket a TargetHeight magassághoz igazítva. A függvény az új oldalszámmal (1-alapú) tér vissza, vagy sikertelenség esetén 0-t ad, és mindkét forrásoldal rögzítésre és elrejtésre kerül a művelet részeként. A laponként két vizsgaoldalas eset egy háromsoros feladat

var
  Lib: TPDFlib;
  NewPage: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('exam.pdf', '');
    // Az 1. és 2. oldal egy új, szélesebb oldalon, 18pt margóval,
    // mindkettő 595pt kimeneti magasságra méretezve (A4 fekvő)
    NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
    if NewPage > 0 then
      Lib.SaveToFile('exam-2up.pdf');
  finally
    Lib.Free;
  end;
end;

Függőleges elrendezéshez a StitchPagesVertically(PageRanges, Gap, TargetWidth) egy teljes oldaltartományt egymásra helyez egyetlen hosszú, poszter-stílusú oldallá. A PageRanges elfogadja a szabványos tartomány-szintaxist, például az '1-3,5' formátumot. Minden forrásoldal úgy van méretezve, hogy a szélessége megegyezzen a TargetWidth értékkel, így az egyes oldalak magassági hozzájárulása a saját magasságuk szorozva a saját skálatényezőjükkel, a kimeneti oldal magassága pedig ezek összege, plusz egy-egy Gap (rés) az egyes párok között. Ez az elrendezés ideális a folyamatos görgetésű felülvizsgálati dokumentumokhoz vagy a letter méretű forrásokból összeállított hosszú bizonylatokhoz

Hogyan helyezhetünk átfedésként egy oldalt egy másikra?

A StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) egy meglévő oldalt rajzol egy másik meglévő oldalra, ahelyett, hogy újat hozna létre. A forrásoldalt form XObject-ként rögzíti, és a megadott téglalapban rajzolja rá a céloldalra, 0 és 1 közötti Opacity (átlátszatlanság) értékkel (az 1 teljesen átlátszatlan). A szélesség és a magasság szabad paraméterek, így az elhelyezés megnyúlik, ha a megadott arány eltér a forrásoldal saját méretarányától; adjon meg arányos párt, ha a forrás torzításmentes indexképét szeretné kapni. Tipikus felhasználása az a dokumentum, amelynek jóváhagyási blokkját külön oldalként tartják karban, és rányomják a borítóra

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('report.pdf', '');
    // Az 5. oldal tartalmazza a jóváhagyási blokkot; helyezze el az 1. oldalon
    // egy 260 x 180-as dobozban 45%-os átlátszatlansággal. Az 5. oldal elfogy:
    // rögzítésre és elrejtésre kerül, így a 6..n oldalak 5..n-1 oldalak lesznek
    if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
      Lib.SaveToFile('report-approved.pdf');
  finally
    Lib.Free;
  end;
end;

Mi történik az oldalszámokkal a rögzítés után?

A CapturePage felemészti a forrásoldalt: annak tartalma átkerül az XObject-be, és a most már üres oldal elrejtésre kerül, így a forrás feletti oldalszámok azonnal eggyel lejjebb csúsznak. Ez a legfontosabb szemantikai szabály az egész összefűző API-ban. A losLab PDF Library kezeli az aritmetikát az egyes összefűző hívásokon belül: a StitchPageOverlay módosítja a célindexet, ha a cél a forrás felett helyezkedik el, a StitchPagesSideBySide pedig a magasabb sorszámú oldalt rögzíti először, így a második rögzítés indexe továbbra is érvényes marad, de a saját környező kódjának számolnia kell a hívások közötti eltolódással. Két szabály tartja helyesen a több összefűzést tartalmazó kódot: rögzítsen minden szükséges oldalméretet vagy oldalszámot az első rögzítés *előtt*, mert utána megváltoznak; valamint amikor maga rögzít több oldalt egy ciklusban, iteráljon csökkenő oldalsorrendben, hogy a későbbi oldalak elrejtése soha ne zavarja meg a még nem rögzített korábbiak indexét

Hogyan lehet többrétegű vízjelet alkalmazni egy oldaltartományra?

A PDFlibWatermark.pas fájlban található vízjel-sablonréteg objektumokká alakítja az „egyszer definiáld, sok oldalon alkalmazd” elvet. A TPDFlibWatermarkBase hordozza azt, ami minden vízjelben közös: egy kilencpontos Position-t (wpTopLeft-től wpBottomRight-ig, plusz a wpCustom-ot, amely nyers X/Y koordinátákat használ), egy Angle értéket fokban, egy 0 és 1 közötti Opacity értéket, valamint egy PageRange karakterláncot, ahol az üres érték az összes oldalt jelenti. A TPDFlibTextWatermark hozzáadja a szöveget, a betűtípus ID-t (alapértelmezés szerint Helvetica), a méretet és az RGB színt; a TPDFlibImageWatermark hozzáadja a korábban hozzáaddott kép ID-jét, valamint az elhelyezési szélességet és magasságot. A TPDFlibCompositeWatermark alvízjelek listáját tárolja, és rekurzívan alkalmazza őket, a TPDFlib.ApplyWatermark pedig az egyetlen hívással elérhető belépési pont, amely tiszteletben tartja a sablon saját oldaltartományát, és visszaadja a lebélyegzett oldalak számát. A háromrétegű jogi bélyegző így néz ki

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';   // a gyermekek öröklik ezt a tartományt
      Lib.ApplyWatermark(Stack);
      Lib.SaveToFile('draft-marked.pdf');
    finally
      Stack.Free;                 // a kompozit felszabadítja a gyermekeit
    end;
  finally
    Lib.Free;
  end;
end;

Két kompozíciós részletet érdemes részletezni. A tartományöröklés egyirányú és nem destruktív: az üres PageRange-dzsel rendelkező gyermek ideiglenesen örökli a kompozit tartományát az Apply során, míg az a gyermek, amely saját tartományt állít be, megtartja azt, így a láblécet minden oldalhoz rögzítheti, miközben az átlós bélyegző csak a vázlatrészt fedi le. A tulajdonjog pedig követi a kompozitot: a TPDFlibCompositeWatermark.Destroy felszabadítja a gyermekeit, így Ön egyszer szabadítja fel a kompozitot és semmi mást, a fenti példához hasonlóan

Ismerendő korlátok

Két valós korlát érvényes, és mindkettő a működési mechanizmusból adódik. Először is, a rögzítés csak az oldal tartalomfolyamát és erőforrásait csomagolja be. A megjegyzések (annotations), linkek, űrlapmezők és kommentek különálló objektumként élnek az oldalszótárban, nem pedig a tartalomfolyamban, így nem kerülnek át az XObject-be; az összefűzött elrendezés hordozza a látható oldaltartalmat, de a források interaktív rétegét nem. Másodszor, a kilencpontos szövegpozicionálás csak közelítés: teljes elrendezési motor nélkül a könyvtár nem tudja a karakterlánc pontos leképezett szélességét, ezért a szövegdobozt az oldalszélesség 40%-ára becsüli az olyan pozíciók feloldásakor, mint a wpCenter. Ez elég pontos egy rövid címke középre igazításához, de a hosszú karakterláncok képpont-pontos elhelyezéséhez váltson a wpCustom-ra, és számítsa ki magának a koordinátákat

A méret itt nem jelent gyakorlati korlátot, mivel a rögzítés és az összefűzés objektumhivatkozásokat manipulál ahelyett, hogy újra kódolná az oldaltartalmat; ha a források több száz megabájtosak, akkor az összefűzés előtt a nagy fájlok közvetlen hozzáféréssel történő összefűzéséről és felosztásáról szóló útmutatóban leírt módszert kell alkalmazni. Az itt bemutatott összefűző függvényer és vízjel-sablon osztályok a Delphi, C# és VB.NET rendszerekhez készült aktuális losLab PDF Library-ben találhatók, az alacsonyabb szintű CapturePage, DrawCapturedPage és DrawCapturedPageMatrix primitívek mellett, ha olyan elrendezésre van szüksége, amelyet a három kész összefűző hívás nem fed le