Tekninen artikkeli

Sivujen yhdistäminen ja vesileimamallit Delphin PDF-tiedostoissa

Sivujen yhdistäminen (page stitching) koostaa olemassa olevien sivujen sisällön yhteiselle pohjalle, mikä on eri toimenpide kuin tiedostojen yhdistäminen. losLab PDF Library toteuttaa Form XObject -pohjaisen sivujen yhdistämisen ja yhdisteltävät vesileimamalleja Delphille: StitchPageOverlay, StitchPagesSideBySide ja StitchPagesVertically asettavat kaapatut sivut mihin tahansa mittakaavaan täydellisellä vektoritarkkuudella, ja TPDFlibCompositeWatermark pinoaa teksti- ja kuvavesileimat sivualueelle yhdellä kutsulla. Jos tarvitset kokonaisten sivujen liittämistä useista asiakirjoista yhdeksi tiedostoksi, kyseessä on tiedostojen yhdistäminen (merge), jota käsitellään tavutason nopeaa PDF-yhdistämistä käsittelevässä artikkelissa; tämä artikkeli käsittelee sivujen sisällön yhdistämistä samassa koordinaattiavaruudessa

Tähän ratkaisuun johtavat pyynnöt ovat arkipäiväisiä ja yleisiä. Koulu haluaa kaksi koesivua yhdelle vaakasuuntaiselle arkille puolittaakseen paperinkulutuksen. Lokalisoinnin tarkastaja haluaa englanninkielisen sivun ja sen käännöksen vierekkäin yhdelle sivulle, jotta niitä voidaan vierittää yhdessä. Lakiasiainosasto haluaa jokaiseen luonnokseen kolme kerrosta kerralla: logon kulmaan, vinottainen CONFIDENTIAL-tekstin keskelle ja pienen alatunnisterivin vain sivuille 1–3. Kaikki kolme tapausta jakavat saman perustavanlaatuisen ongelman: jo valmiiksi taitetun sivusisällön sijoittamisen muualle eri koossa heikentämättä sitä bittikartaksi

Miksi PDF-sisältövirtojen ketjuttaminen epäonnistuu?

Naiivi yhdistäminen, jossa lähdesivun /Contents-virta kopioidaan kohdesivulle, epäonnistuu kolmesta rakenteellisesta syystä. Ensinnäkin koordinaatit törmäävät: molemmat sisältövirrat olettavat oman alkupisteensä oman MediaBoxinsa vasempaan alakulmaan, joten liitetty sisältö päätyy olemassa olevan sisällön päälle väärään paikkaan. Toiseksi resurssien nimet törmäävät: kukin sivu ratkaisee operaattorit kuten /F1 Tf ja /Im0 Do oman /Resources-sanakirjansa perusteella, ja kaksi sivua sitoo rutiininomaisesti saman nimen eri fontteihin tai kuviin, jolloin toinen niistä piirtyy hiljaa väärällä resurssilla. Kolmanneksi raaka virran kopiointi ei tarjoa lainkaan skaalausta; parhaimmillaankin voit vain siirtää (translate) sisältöä

Form XObject -objektit ratkaisevat kaikki kolme ongelmaa kerralla, ja siksi losLab PDF Library perustaa sivujen yhdistämisensä niihin. ISO 32000-1 §8.10 määrittelee Form XObjectin itsenäiseksi sisältövirraksi, jolla on oma /Resources-sanakirja ja oma koordinaattiavaruus, ja joka sijoitetaan sivulle kutsumalla sen nimeä minkä tahansa aktiivisen muunnosmatriisin alaisuudessa. CapturePage käärii kokonaisen olemassa olevan sivun sisältöineen ja resursseineen /Subtype /Form -tyyppiseksi XObjectiksi, ja yhdistämisfunktiot sijoittavat sen sitten cm-matriisin avulla: skaalaten, siirtäen ja kiertäen vapaasti. Koska lähde säilyy vektorimuodossa, teksti pysyy tekstinä ja polut polkuina kaikilla zoomaustasoilla. Tämä on sama ominaisuus, joka tekee vektorigrafiikasta ja varjostuskuvioista resoluutiosta riippumattomia. Mitään ei rasteroida, ja eristetty resurssisanakirja tarkoittaa, ettei kaapattu sivu voi kiistellä kohdesivun kanssa nimestä /F1

Miten yhdistetään kaksi PDF-sivua yhdelle sivulle?

StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) on sura vastaus: se kaappaa molemmat lähdesivut, luo uuden leveämmän sivun ja asettaa kaksi kaappausta vasemmalle ja oikealle jättäen niiden väliin marginaalin (gutter), kummankin skaalattuna sopimaan TargetHeight-korkeuteen. Funktio palauttaa uuden sivunumeron (1-perustaisen) tai 0 epäonnistuessaan, ja molemmat lähdesivut kaapataan ja piilotetaan osana toimenpidettä. Kahden koesivun sovittaminen yhdelle arkille hoituu kolmella koodirivillä

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;

Pystysuuntaista koostamista varten StitchPagesVertically(PageRanges, Gap, TargetWidth) pinoaa kokonaisen sivualueen yhdeksi korkeaksi, julistemaiseksi sivuksi. PageRanges ottaa vastaan tavallisen sivualuesyntaksin, kuten '1-3,5'. Jokainen lähdesivu skaalataan siten, että sen leveys vastaa TargetWidth-leveyttä, jolloin kunkin sivun korkeusvaikutus on sen oma korkeus kerrottuna sen omalla skaalauskertoimella, ja tulossivun korkeus on näiden summa plus yksi Gap kunkin parin välillä. Tämä muoto sopii jatkuvasti vieritettäviin tarkasteluasiakirjoihin tai pitkiin kuitteihin, jotka kootaan Letter-kokoisista lähteistä

Miten sijoitetaan yksi sivu toisen päälle?

StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) piirtää olemassa olevan sivun toisen olemassa olevan sivun päälle uuden luomisen sijaan. Lähdesivu kaapataan Form XObjectina ja piirretään kohteeseen annettuun suorakulmioon Opacity-peittävyydellä väliltä 0–1 (1 on täysin peittävä). Leveys ja korkeus ovat vapaita parametreja, joten sijoitus venyy, jos kuvasuhde poikkeaa lähdesivun omasta kuvasuhteesta; välitä suhteellinen pari, kun haluat vääristymättömän pikkukuvan lähteestä. Tyypillinen käyttötapaus on asiakirja, jonka hyväksymislohko ylläpidetään omana sivunaan ja leimataan kansilehdelle

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;

Mitä sivunumeroille tapahtuu kaappauksen jälkeen?

CapturePage kuluttaa lähdesivun: sen sisältö siirretään XObjectiin ja nyt tyhjä sivu piilotetaan, joten jokainen lähdesivua suurempi sivunumero siirtyy välittömästi alaspäin yhdellä. Tämä on koko sivujen yhdistämisrajapinnan tärkein semanttinen ominaisuus. losLab PDF Library käsittelee laskennan jokaisen yhdistämiskutsun sisällä: StitchPageOverlay säätää kohdeindeksiä, kun kohde on lähteen yläpuolella, ja StitchPagesSideBySide kaappaa korkeampinumeroisen sivun ensin, jotta toisen kaappauksen indeksi säilyy voimassa. Oman ympäröivän koodisi on kuitenkin otettava huomioon tämä siirtymä kutsujen välillä. Kaksi sääntöä pitää monivaiheisen yhdistämiskoodin oikeana: Tallenna kaikki tarvitsemasi sivukoot tai sivunumerot ennen ensimmäistä kaappausta, koska ne muuttuvat sen jälkeen. Ja kun kaappaat useita sivuja itse silmukassa, käsittele ne laskevassa sivujärjestyksessä, jotta myöhemmän sivun piilottaminen ei koskaan häiritse sellaisen aiemman sivun indeksiä, jota et ole vielä kaapannut

Miten monikerroksinen vesileima levitetään sivualueelle?

PDFlibWatermark.pas-tiedoston vesileimamallikerros muuttaa "määritä kerran, käytä useille sivuille" -ajatuksen olioiksi. TPDFlibWatermarkBase sisältää sen, mikä on yhteistä kaikille vesileimoille: yhdeksänpisteinen Position (wpTopLeftwpBottomRight, plus wpCustom, joka käyttää raakoja X/Y-koordinaatteja), Angle asteina, Opacity väliltä 0–1 ja PageRange-merkkijono, jossa tyhjä tarkoittaa kaikkia sivuja. TPDFlibTextWatermark lisää tekstin, fonttitunnuksen (oletuksena Helvetica), koon ja RGB-värin; TPDFlibImageWatermark lisää kuvan tunnuksen aiemmin lisätystä kuvasta sekä sijoituksen leveyden ja korkeuden. TPDFlibCompositeWatermark sisältää luettelon alivesileimoista ja soveltaa niitä rekursiivisesti, ja TPDFlib.ApplyWatermark on yhdellä kutsulla toimiva aloituspiste, joka noudattaa mallin omaa sivualuetta ja palauttaa leimattujen sivujen määrän. Kolmikerroksinen lakisääteinen leima näyttää tältä

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;

Kaksi koostamisen yksityiskohtaa on syytä täsmentää. Alueen periytyvyys on yksisuuntaista ja rikkoutumatonta: lapsi, jonka PageRange on tyhjä, perii tilapäisesti yhdistelmän (composite) alueen Apply-kutsun aikana, kun taas omaa aluettaan käyttävä lapsi säilyttää sen. Voit siis kiinnittää alatunnisteen jokaiselle sivulle samalla, kun vinottainen leima kattaa vain luonnososion. Lisäksi omistajuus seuraa yhdistelmää: TPDFlibCompositeWatermark.Destroy vapauttaa lapsensa, joten vapautat yhdistelmän kerran etkä mitään muuta, kuten yllä olevassa esimerkissä

Rajoitukset, jotka on hyvä tietää

Asialla on kaksi rehellistä rajoitusta, ja molemmat johtuvat mekanismin toimintatavasta. Ensinnäkin kaappaus käärii vain sivun sisältövirran ja resurssit. Annotaatiot, linkit, lomakekentät ja kommentit ovat erillisiä objekteja sivun sanakirjassa, eivät sisältövirrassa, joten ne eivät siirry XObjectiin. Yhdistetty asettelu sisältää näkyvän sivusisällön, mutta ei sen lähteiden interaktiivista kerrosta. Toiseksi yhdeksänpisteinen tekstin sijoittelu on likimääräinen arvio: ilman täydellistä asettelumoottoria kirjasto ei voi tietää merkkijonon tarkkaa piirrettyä leveyttä, joten se arvioi tekstilaatikon kooksi 40 % sivun leveydestä ratkaistessaan sijainteja kuten wpCenter. Tämä on riittävän tarkka lyhyen leiman keskittämiseen, mutta pitkien merkkijonojen pikselintarkkaan sijoittamiseen on syytä siirtyä käyttämään wpCustom-vaihtoehtoa ja laskea koordinaatit itse

Mittakaava ei ole tässä käytännön rajoite, koska kaappaus ja yhdistäminen muokkaavat olioviittauksia sen sijaan, että sivun sisältö koodattaisiin uudelleen; jos lähdetiedostosi ovat satojen megatavujen kokoisia, sovelletaan samaa kurinalaisuutta, jota kuvataan suurten tiedostojen yhdistämistä ja jakamista suoralla pääsyllä käsittelevässä oppaassa ennen yhdistämistä. Tässä esitetyt yhdistämistoiminnot ja vesileimamalliluokat toimitetaan nykyisessä losLab PDF Library -kirjastossa Delphille, C#:lle ja VB.NETille yhdessä alemman tason primitivien CapturePage, DrawCapturedPage ja DrawCapturedPageMatrix kanssa silloin, kun tarvitset asettelun, jota kolme valmista yhdistämiskutsua eivät kata