Tehnični članak

PDFlibPas okvirji strani: TrimBox, BleedBox in CropBox

Ko stran PDF nima TrimBox, je njen učinkoviti TrimBox CropBox strani, kadar pa manjka tudi CropBox, je to MediaBox. BleedBox in ArtBox sledita istemu pravilu. PDFlibPas, knjižnica PDF za Delphi, uveljavi to privzeto verigo dosledno v GetPageBox, HasPageBox in CapturePageEx od v3.539.44, okvirje za produkcijo, postavljene na vozel /Pages, pa ignorira, ker jih ISO 32000-1 ne pusti podedovati

To zveni kot opomba pod črto, dokler ne sestavite posla. Predstavljajte si notranjost knjige z MediaBox 6,25 × 9,25 palca, CropBox nastavljenim na obrez 6 × 9 palcev in brez TrimBox, ker tisti, ki jo je izvozil, ni pomislil, da bi ga zapisal. Zahtevate obrezni okvir, dobite medijskega, in vsaka celica na vašem tiskovnem listu vleče osmino palca prelive in odpada v sosedo. PDFlibPas je imel napake točno na tem območju, popravljene v v3.539.42 in v3.539.44, način njihovega popravila pa nekaj pove o tem, kako bi semantiko okvirjev strani morala implementirati katera koli knjižnica PDF

Kateri okvir velja, kadar stran nima TrimBox?

Odgovor je fiksna privzeta veriga iz ISO 32000-1 §14.11.2: CropBox se privzame na MediaBox, BleedBox, TrimBox in ArtBox pa se vsak privzame na CropBox. Ničesar, razen CropBox, se ne privzame naravnost na MediaBox. Stran, ki definira samo MediaBox, ima torej pet identičnih okvirjev, stran, ki definira MediaBox plus CropBox, pa štiri okvirje, enake CropBox

OkvirBoxType v PDFlibPasPrivzeto, kadar manjkaPodedovalno s /Pages
MediaBox1Brez, vnos je obvezenDa
CropBox2MediaBoxDa
BleedBox3CropBoxNe
TrimBox4CropBoxNe
ArtBox5CropBoxNe

Dvostopenjska veriga šteje, ker je lahko CropBox sam podedovan. Učinkoviti TrimBox strani, ki nima ne TrimBox ne lastnega CropBox, je CropBox najbližjega prednika, ki ga ima, če pa tega ni, podedovani MediaBox. Specifikacija doda še eno pravilo, ki ga je lahko pozabiti: okvirji obreza, prelive, trim in umetnosti ne smejo segati čez medijski okvir, če pa segajo, so učinkovito zmanjšani na svoje presečišče z njim. PDFlibPas poroča vsak okvir, kakor je shranjen v datoteki, zato naj validator, ki obravnava nezaupljiv vnos, sam stisne proti MediaBox

PDFlibPas privzeta veriga okvirjev strani, kjer se CropBox privzame na MediaBox, BleedBox, TrimBox in ArtBox pa vsak na CropBox, narisana ob notranjosti knjige z MediaBox 450 krat 666 točk in CropBox 432 krat 648 točk, ki postane učinkovit obrez, ko TrimBox ne obstaja
Ničesar razen CropBox se ne privzame naravnost na MediaBox, zato stran z samo MediaBox ima pet identičnih okvirjev

Katere atribute strani lahko vozel /Pages prenese naprej?

Točno štiri: Resources, MediaBox, CropBox in Rotate. ISO 32000-1 §7.7.3.4 definira podedovanje atributov, tabela 30 pa označuje kot podedovalne samo tiste štiri vnose objekta strani. BleedBox, TrimBox in ArtBox pripadajo listni strani. TrimBox, zapisan vozlu /Pages, ni podedovana vrednost; to je nestandardni ključ, ki ga skladen bralec ignorira

Nestandardne datoteke, take kot ta, obstajajo, običajno z enim samim TrimBox na korenskem vozlu stranskega drevesa kot okrajšavo za "vsaka stran ima ta obrez". Okrajšava je videti pravilna v vsakem orodju, ki slede /Parent za vsak ključ, in to je težava: datoteka zdaj pomeni dve stvari, odvisno od tega, kdo jo bere. Bralec, ki sledi specifikaciji, ne vidi TrimBox in uporabi CropBox, bralec, ki podeduje vse, pa vidi starševsko vrednost. V predtiskovnem cevovodu ta dvoumnost pristane na tiskovnem listu

PDFlibPas podedovanje stranskega drevesa, kjer samo Resources, MediaBox, CropBox in Rotate preidejo naprej skozi vozel Pages, tako da je TrimBox, parkiran na korenu, nestandardni ključ, ki ga skladni bralci ignorirajo; pred v3.539.44 sta dva neodvisna kodna predora podedovala in poročala različni velikosti obreza za isti dokument
Datoteka pomeni dve stvari, odvisno od tega, kdo jo bere, v predtiskovnem cevovodu pa ta dvoumnost pristane na tiskovnem listu

Delovni tokovi PDF/X (ISO 15930) so od TrimBox odvisni za končno velikost, profili PDF/X pa zahtevajo, da vsaka stran deklarira TrimBox ali ArtBox. Okvir, parkiran na vozlu /Pages, tiste zahteve ne izpolnjuje, ker ključ nikoli ne doseže objekta strani. Preflight naj take datoteke označi, namesto da bi jih tiho bral na en ali drug način

Kaj je PDFlibPas naredil narobe pred v3.539.44?

PDFlibPas je imel tri ločene napake, vse v reki med tem, kar pravi specifikacija, in tem, kar sta počela dva neodvisna kodna predora. Prva je bila popravljena v v3.539.42, drugi dve pa v v3.539.44

Produktni okvirji so se med zajemom privzeli na MediaBox

Pred v3.539.42 je notranja rutina, ki pripravi stran za zajem (kopira podedovane vnose na stran in dopolni manjkajoče okvirje), BleedBox, TrimBox in ArtBox dala vrednosti MediaBox, kadar so manjkali. CapturePageEx z opcijami 2 do 4 bere svoj obdajajoč pravokotnik točno iz teh dopolnjenih vnosov, tako da na strani, ki definira samo CropBox, zahteva po obreznem okvirju zajela celoten medijski okvir. GetPageBox je privzeti CropBox že uveljavljal in sklic CapturePageEx je ves čas pravil, da se uporabi obrezni okvir, kadar zahtevani okvir manjka; koda zajema se je z obema skorala. Od v3.539.42 se trije produktni okvirji privzamejo na CropBox strani, ki je do takrat že na strani (njen lasten, kopiran od prednika ali dopolnjen iz MediaBox), samo CropBox sam pa pade nazaj na MediaBox

Dve poti podedovanja, eno semantično pravilo

Druga napaka je bilo samo nestandardno podedovanje, tanko pa je bilo to, da je PDFlibPas razreševal okvirje po dveh neodvisnih predorih. Poizvedbe okvirjev (GetPageBox in HasPageBox) so slede verigi /Parent skozi enega pomočnika, zajem pa skozi ločenega lokalnega pomočnika. Oba sta podedovala vsak ključ, produktni okvirji niso bili izvzeti. Popravek samo enega od njiju bi znotraj enega dokumenta izdelal protislovje: s TrimBox, širokim 180 točk, na vozlu /Pages in CropBox, širokim 380 točk, na strani bi GetPageBox še naprej poročal širino obreza 180, medtem ko bi CapturePageEx zgradil formo, široko 380. V v3.539.44 obe poti omejita hojo po /Parent na štiri podedovalne ključe, produktni okvirji se berejo samo z lista, tuj starševski vnos pa ostane v datoteki nedotaknjen — ne izbrisan in ne prepisan

PDFlibPas kode vrnitve HasPageBox nič, ena in dva, kjer od v3.539.44 neposredni in indirektni seznami oba štejeta kot podedovana, ob opcijah CapturePageEx od nič do štiri, kjer BleedBox, TrimBox in ArtBox od v3.539.42 padejo nazaj na CropBox namesto na MediaBox
Dva implementacijska vstopni točki za eno pravilo specifikacije sta popravljena skupaj in preskušena kot matrika 18 scenarijev, s poizvedbo in zajemom, ki se strinjata pri vsaki datoteki

HasPageBox je spregledal neposredne starševske sezname

HasPageBox vrne 0, kadar stran nima okvirja zahtevanega tipa, 1, kadar ima stran lastnega (shranjenega neposredno ali skozi indirekten sklic), in 2, kadar je MediaBox ali CropBox podedovan od prednika. Stara koda je vrnila 2 samo, kadar je bila podedovana vrednost indirekten sklic, tako da je podedovani neposreden seznam vrnil 0. Popravek loči razreševanje sklica od preskusa seznama in obe representaciji zdaj vrneta 2. Od v3.539.44 HasPageBox za BleedBox, TrimBox ali ArtBox lahko vrne samo 0 ali 1

Lekcija se posploši daleč prek okvirjev strani. Ko en kos semantike specifikacije ima v knjižnici dve implementacijski vstopni točki, ju popravite skupaj in ju preskusite kot matriko, ne z eno samo srečno-potno datoteko. Regresijska množica PDFlibPas prekriža dve representaciji starševskega okvirja (neposredni in indirektni seznam) s tremi stanji lista (manjka, neposredni seznam, indirektni seznam) in tremi opcijami zajema (bleed, trim, art), kar da 18 scenarijev, vsak pa preveri rezultat poizvedbe, zajete meje, legitemirno podedovanje MediaBox in CropBox ter nedotaknjen starševski vnos

Kako v Delphiju preberem učinkoviti TrimBox?

Kličite GetPageBox(4, Dimension) na izbrani strani. PDFlibPas vam uveljavi privzeto verigo, tako da je rezultat učinkoviti TrimBox, ne glede na to, ali ga stran ima. Parirajte ga s HasPageBox, kadar morate vedeti, od kod vrednost prihaja, kar poročilo preflighta ponavadi hoče

uses
  System.SysUtils, PDFlibrary;

const
  BOX_CROP   = 2;
  BOX_TRIM   = 4;
  DIM_LEFT   = 0;
  DIM_WIDTH  = 2;
  DIM_HEIGHT = 3;
  DIM_BOTTOM = 5;

function DescribeTrim(Lib: TPDFlib; Page: Integer): string;
var
  Source: string;
begin
  Lib.SelectPage(Page);
  if Lib.HasPageBox(BOX_TRIM) = 1 then
    Source := 'own TrimBox'
  else if Lib.HasPageBox(BOX_CROP) <> 0 then   // 1 = lastna, 2 = podedovana
    Source := 'defaulted to the CropBox'
  else
    Source := 'defaulted to the MediaBox';
  Result := Format('page %d: trim %.2f x %.2f pt at (%.2f, %.2f), %s',
    [Page,
     Lib.GetPageBox(BOX_TRIM, DIM_WIDTH),
     Lib.GetPageBox(BOX_TRIM, DIM_HEIGHT),
     Lib.GetPageBox(BOX_TRIM, DIM_LEFT),
     Lib.GetPageBox(BOX_TRIM, DIM_BOTTOM),
     Source]);
end;

var
  Lib: TPDFlib;
  Page: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('interior.pdf', '') = 1 then
      for Page := 1 to Lib.PageCount do
        Writeln(DescribeTrim(Lib, Page));
  finally
    Lib.Free;
  end;
end.

GetPageBox in SetPageBox delujeta v trenutnih koordinatnih nastavitvah dokumenta. Primeri tukaj tečejo s privzetimi: izvor 0 (spodaj levo, skladen s uporabniškim prostorom PDF) in točke kot merska enota, tako da je dimenzija Top zgornji rob, merjen navzgor od spodnjega roba strani. Po SetOrigin(1) sta dimenziji Top in Bottom merjeni navzdol od zgornjega roba strani, po SetMeasurementUnits(1) pa vsaka vrednost pride nazaj v milimetrih. Širina in višina nista odvisna od izvora

Iskanje produktnih okvirjev, nasedlih na vozlih /Pages

Od v3.539.44 API okvirjev TrimBox na vozlu /Pages ne vidi več, kar je prav, orodje preflighta pa običajno hoče tako datoteko poročati, namesto da bi jo tiho bralo po specifikaciji. Vozli stranskega drevesa so navadni objekti, zato jih lahko najde nizkonivojski objektni API: hodite po številkah objektov do GetMaxObjectNumber, berite vsakega z GetObjectToString in iščite slovar /Pages, ki nosi produktni okvirni ključ. Druga polovica preizkusa je preizkus po posameznih straneh, ki zanima PDF/X, HasPageBox pa nanj zdaj odgovarja, kakor bi validator PDF/X, ker starševski TrimBox ne šteje več

procedure PreflightTrim(Lib: TPDFlib; Log: TStrings);
const
  ProductionKeys: array[0..2] of string = ('/BleedBox', '/TrimBox', '/ArtBox');
var
  ObjNum, K, Page, Missing: Integer;
  Src: string;
begin
  // 1. Produktni okvirji na vozlih stranskega drevesa: nestandardni in ignorirani
  for ObjNum := 1 to Lib.GetMaxObjectNumber do
  begin
    Src := '';                                // prosta številka ne vrne besedila
    Src := string(Lib.GetObjectToString(ObjNum));
    if Pos('/Type /Pages', Src) = 0 then
      Continue;
    for K := Low(ProductionKeys) to High(ProductionKeys) do
      if Pos(ProductionKeys[K] + ' ', Src) > 0 then
        Log.Add(Format('object %d: %s on a /Pages node is not inheritable',
          [ObjNum, ProductionKeys[K]]));
  end;

  // 2. PDF/X: vsaka stran potrebuje svoj TrimBox ali ArtBox
  Missing := 0;
  for Page := 1 to Lib.PageCount do
  begin
    Lib.SelectPage(Page);
    if (Lib.HasPageBox(4) = 0) and (Lib.HasPageBox(5) = 0) then
    begin
      Inc(Missing);
      Log.Add(Format('page %d: no TrimBox or ArtBox', [Page]));
    end;
  end;

  // 3. Neobvezen popravek: obrez 6 x 9 palcev znotraj medijskega okvirja 6.25 x 9.25 palca
  //    (točke, izhodišče spodaj levo: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

Tekstovno ujemanje je pragmatičen preizkus, ne razčlenjevalnik. Zanaša se na to, da PDFlibPas serializira vsak vnos slovarja kot ključ, en presledek in vrednost, kar velja za objekte, prebrane nazaj skozi GetObjectToString. Korak popravila zasluži odločitev, ne refleksa: tuj starševski vnos je lahko kar to, kar je nameraval avtor, a ga potrdite proti delovnemu nalogu, preden ga povzdignete v uradnega. SetPageBoxRange s praznim razponom uveljavi okvir na vsako stran in vrne število posodobljenih strani. Ko je obstoječi okvir strani indirekten seznam, ki si ga lahko deli druga stran ali vozel /Pages, SetPageBox da tej strani nov neposreden seznam, namesto da bi prepisal deljeni objekt. Nastavitev BleedBox, TrimBox ali ArtBox tudi dvigne odklenjen dokument na PDF 1.3, različico, ki je uvedla te vnose

Impozicija strani na TrimBox s CapturePageEx

CapturePageEx(Page, 3) spremeni stran v Form XObject, katerega obdajajoči okvir je učinkoviti TrimBox strani, DrawCapturedPage pa to formo postavi na drugo stran v kateri koli velikosti. Od v3.539.42 vam opcija 3 na strani brez TrimBox da CropBox, kakor pravi sklic, namesto MediaBox z vsem odpadom

Dve lastnosti zajema oblikujeta kodo. Zajem je destruktiven: zajeta stran je odstranjena iz dokumenta in dokument ne more nikoli pasti na nič strani, zato dodajte prvi izhodni list, preden zajamete karkoli. Zajem dela tudi samo znotraj enega dokumenta, zato povlecite vsak vhod najprej v en sam dokument; tehnike za sestavljanje in prepletanje virov PDF v enem prehodu veljajo neposredno

procedure ImposeTwoUp(const InFile, OutFile: string);
var
  Lib: TPDFlib;
  Captures: array of Integer;
  SourceCount, I: Integer;
  TrimW, TrimH: Double;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(InFile, '') <> 1 then
      raise Exception.Create('Cannot open ' + InFile);
    SourceCount := Lib.PageCount;

    // Učinkovita velikost obreza strani 1 (ta postavitev predpostavlja enoten obrez)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Dodajte in dimenzionirajte prvi list; NewPage izbere novo stran
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Vsak zajem odstrani stran 1, zato se naslednja izvorna stran premakne gor
    SetLength(Captures, SourceCount);
    for I := 0 to SourceCount - 1 do
    begin
      Captures[I] := Lib.CapturePageEx(1, 3);   // 3 = TrimBox
      if Captures[I] = 0 then
        raise Exception.CreateFmt('Capture of source page %d failed', [I + 1]);
    end;

    // Ostane samo list: dve obrezani strani na list, druga ob drugi
    Lib.SelectPage(1);
    for I := 0 to SourceCount - 1 do
    begin
      if (I > 0) and (I mod 2 = 0) then
        Lib.NewPage;                            // iste velikosti kot trenutni list
      // Privzet izvor: Top je zgornji rob, merjen od spodaj
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

Zajem na osnovi obreza odreže vse izven TrimBox, kar je to, kar hočete za digitalni izvod ali postavitev reži-in-sklada. Za tiskovni list, obrezan po tiskanju, zajemite z opcijo 2, da preživi preliv, in razmaknite celice za širino prelive. Ker zajem odstrani izvorne strani, zaznamki in povezave, ki so kazale nanje, izgubijo svoje cilje, zato sestavite v ločeno izhodno datoteko, namesto da bi urejali dokument, katerega navigacijo še potrebujete; zamenjava strani brez lomljenja zaznamkov pokriva to stran stranske kirurgije

Kadar mora izvirnik ostati nedotaknjen, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) vzame iste vrednosti opcij od 0 do 4 (podajte Lib.SelectedDocument za trenutni dokument), pusti stransko drevo izvirnika nespremenjeno, normalizira podedovani zasuk strani v matriko forme in vrne ročaj, ki ga DrawCapturedPage sprejme. CapturePageEx ne razveljavi /Rotate, zato zasukan vhod potrebuje ta korak najprej, izravnava zasuka strani brez lomljenja okvirjev strani pa pokaže, kaj se zgodi z vsakim okvirjem, ko to storite. Ena previdnost za vhode, ki lahko nosijo produktni okvirje na vozlih /Pages: pot uvoza razreši svoj okvir skozi svoje lastno iskanje prednikov, ločeno od dveh predorov, usklajenih v v3.539.44, zato najprej preverite HasPageBox(4) na izvorni strani in podajte opcijo 1 (CropBox), kadar vrne 0. To obdrži rezultat vezanega na specifikacijo, ne na to, kako je bila datoteka po naključju zapisana

Hiter pregled okvirjev strani

  • Učinkoviti CropBox: lasten CropBox strani, sicer najbližji podedovani CropBox, sicer učinkoviti MediaBox (ISO 32000-1 §14.11.2)
  • Učinkoviti BleedBox, TrimBox in ArtBox: lasten vnos listne strani, sicer učinkoviti CropBox
  • Samo Resources, MediaBox, CropBox in Rotate se podedujejo z vozlov /Pages (§7.7.3.4, tabela 30); produktni okvirji na vozlih /Pages so ignorirani
  • GetPageBox(BoxType, Dimension): BoxType 1 MediaBox, 2 CropBox, 3 BleedBox, 4 TrimBox, 5 ArtBox; Dimension 0 Left, 1 Top, 2 Width, 3 Height, 4 Right, 5 Bottom
  • HasPageBox(BoxType): 0 brez okvirja, 1 lasten okvir strani (neposreden ali indirekten), 2 podedovani MediaBox ali CropBox (neposreden ali indirekten)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox z rezervo na MediaBox, 2 do 4 BleedBox, TrimBox ali ArtBox z rezervo na CropBox
  • Nadgradite na v3.539.44 ali novejši za dosledne privzete vrednosti in podedovanje čez poizvedbe okvirjev in zajem

Okvirji strani so mesto, kjer se tihe privzete vrednosti PDF srečajo s predtiskovnimi tolerancami, merjenimi v delih milimetra, in knjižnica bodisi uveljavi te privzete vrednosti povsod enako bodisi vam da dva odgovora na eno vprašanje. Celoten API okvirjev, zajema in Form XObject je dokumentiran na strani izdelka PDFlibPas PDF Library for Delphi