Műszaki cikk

PDFlibPas: TrimBox, BleedBox és CropBox alapértelmezések

Amikor egy PDF oldalnak nincs TrimBoxja, a tényleges TrimBoxja az oldal CropBoxja, és ha a CropBox is hiányzik, akkor a MediaBox. A BleedBox és az ArtBox ugyanezt a szabályt követi. A PDFlibPas, a PDF Library for Delphi, ezt az alapértelmezési láncot v3.539.44 óta következetesen alkalmazza a GetPageBox-ban, a HasPageBox-ban és a CapturePageEx-ben, és ignorálja a /Pages csomópontra tett gyártódobozokat, mert az ISO 32000-1 nem engedi, hogy öröklődjenek

Ez lábjegyzetnek hangzik, amíg nem kényszerítesz nyomdai megrendelést. Képzelj el egy könyvbelst, aminek 6,25 × 9,25 inch a MediaBoxja, a CropBoxja a 6 × 9 inch vágóméretre van állítva, és nincs TrimBoxja, mert aki exportálta, eszébe sem jutott egyet írni. Kérsz a vágódobozt, a médiadobozt kapod helyette, és minden cella a nyomtatási íven egy nyolcad inch vérzést és szennyszélt húz a szomszédjára. A PDFlibPasnak pontosan ezen a területen voltak hibái, amiket a v3.539.42 és a v3.539.44 javított, és a javítás módja elárul valamit arról, hogyan kellene megvalósítani az oldaldoboz-szemantikát bármely PDF libraryben

Melyik doboz érvényes, ha az oldalnak nincs TrimBoxja?

A válasz az ISO 32000-1 §14.11.2-e szerinti rögzített alapértelmezési lánc: a CropBox a MediaBoxra esik vissza, a BleedBox, TrimBox és ArtBox pedig egyaránt a CropBoxra. Semmi, csak a CropBox esik vissza közvetlenül a MediaBoxra. Egy oldal, ami csak MediaBoxot definiál, ezért öt azonos dobozzal bír, egy pedig, ami MediaBoxot meg CropBoxot, négy, a CropBoxszal egyenlő dobozzal

DobozPDFlibPas BoxTypeAlapérték hiányábanÖrökölhető /Pages-ből
MediaBox1Nincs, a bejegyzés kötelezőIgen
CropBox2MediaBoxIgen
BleedBox3CropBoxNem
TrimBox4CropBoxNem
ArtBox5CropBoxNem

A két lépcsős lánc azért számít, mert a CropBox maga is örökölt lehet. Egy olyan oldal tényleges TrimBoxja, aminek se TrimBoxja, se saját CropBoxja nincs, annak a legközelebbi ősnek a CropBoxja, akinek van, ennek hiányában az örökölt MediaBox. A spec még egy, könnyen elfelejtett szabályt ad: a crop, bleed, trim és art dobozok nem nyúljhatnak túl a media boxon, és ha mégis, gyakorlatilag az átfedésükre zsugorodnak. A PDFlibPas minden dobozt úgy jelent, ahogy a fájlban tárolják, így egy megbízhatatlan bemenetet kezelő validátornak magának kell a MediaBoxra szorítania

PDFlibPas oldaldoboz alapértelmezési lánc, ahol a CropBox a MediaBoxra, a BleedBox, TrimBox és ArtBox egyaránt a CropBoxra esik vissza, lerajzolva egy 450 × 666 pontos MediaBoxú és 432 × 648 pontos CropBoxú könyvbelst mellett, aminek a CropBoxja lesz a tényleges vágóméret, ha nincs TrimBox
Semmi, csak a CropBox esik vissza közvetlenül a MediaBoxra, így egy csak MediaBoxos oldalon öt azonos doboz van

Mely oldalattribútumokat adhat át le egy /Pages csomópont?

Pontosan négyet: Resources, MediaBox, CropBox és Rotate. Az ISO 32000-1 §7.7.3.4-e definiálja az attribútumöröklést, és a 30. táblázat csak ezt a négy oldalobjektum-bejegyzést jelöli örökölhetőként. A BleedBox, TrimBox és ArtBox a levéloldalé. Egy /Pages csomópontba írt TrimBox nem örökölt érték; szabványidegen kulcs, amit egy szabványkövető olvasó ignorál

Ilyen nem szabványos fájlok léteznek, jellemzően egyetlen TrimBoxszal a gyökér oldalfa csomóponton, mint rövidítés arra, hogy „minden oldalnak ez a vágója". A rövidítés bármely eszközben jól néz ki, ami minden kulcsra rájár a /Parent-re, és ez a gond: a fájl most két dolgot jelent attól függően, ki olvassa. A szabványt követő olvasó nem lát TrimBoxot, és a CropBoxot használja, míg a mindent öröklő olvasó a szülői értéket látja. Egy prepress pipeline-ban ez a kétértelműség a nyomtatási íven landol

PDFlibPas oldalfa öröklés, ahol csak a Resources, MediaBox, CropBox és Rotate megy át egy Pages csomóponton, így a gyökérre parkolt TrimBox szabványidegen kulcs, amit a szabványkövető olvasók ignorálnak; v3.539.44 előtt két független kódút örökölte, és egy dokumentumra eltérő vágóméretet jelentett
A fájl két dolgot jelent attól függően, ki olvassa, és egy prepress pipeline-ban ez a kétértelműség a nyomtatási íven landol

A PDF/X (ISO 15930) munkafolyamatok a TrimBoxra építenek a kész méret szempontjából, és a PDF/X profilok megkövetelik, hogy minden oldal TrimBoxot vagy ArtBoxot deklaráljon. Egy /Pages csomópontra parkolt doboz nem felel meg ennek a követelménynek, mert a kulcs sosem ér el az oldalobjektumig. A preflight inkább jelezze az ilyen fájlokat, minthogy csendben egyik vagy másik módon olvassa őket

Mit rontott el a PDFlibPas v3.539.44 előtt?

A PDFlibPasnak három külön hibája volt, mind azok közt a résben, ami azért van, amit a spec mond és amit két független kódút tett. Az elsőt a v3.539.42, a másik kettőt a v3.539.44 javította

A gyártódobozok capture közben a MediaBoxra estek vissza

v3.539.42 előtt a belső rutin, ami az oldalt capture-re készíti (felteszi az örökölt bejegyzéseket az oldalra és pótolja a hiányzó dobozokat), hiányban a BleedBoxnak, TrimBoxnak és ArtBoxnak a MediaBox értékeit adta. A CapturePageEx 2-től 4-ig terjedő opciókkal pontosan ezekből a bepótölt bejegyzésekből olvassa a határoló téglalapját, így egy csak CropBoxot definiáló oldalon a vágódoboz kérése a teljes media boxot kapta el. A GetPageBox már alkalmazta a CropBox-alapértéket, és a CapturePageEx referencia is mindig azt írta, hogy hiányzó kért doboznál a crop box szolgál; a capture kód mindkettővel szembement. v3.539.42 óta a három gyártódoboz az oldal CropBoxjára esik vissza, ami arra a pontra már ott van az oldalon (saját, ősől másolt vagy MediaBoxból pótolt), és csak maga a CropBox esik vissza a MediaBoxra

Két öröklési út, egy szemantikai szabály

A második hiba maga a nem szabványos öröklés volt, és az volt a trükkös benne, hogy a PDFlibPas két független úton oldotta fel a dobozokat. A dobozlekérdezések (GetPageBox és HasPageBox) egy helperen át járták a /Parent láncot, a capture pedig egy külön helyi helperen át. Mindkettő minden kulcsot örökölt, a gyártódobozokat is beleértve. Ha csak az egyiket javították volna meg, ellentmondás született volna egyetlen dokumentumon belül: a /Pages csomóponton ülő 180 pontos széles TrimBox és az oldalon ülő 380 pontos széles CropBox mellett a GetPageBox továbbra is 180 széles trimet jelentett volna, miközben a CapturePageEx 380 széles formot épített volna. A v3.539.44-ben mindkét út a négy örökölhető kulcsra szűkíti a /Parent bejárást, a gyártódobozok kizárólag a levélről olvasódnak, és a csavarodott szülői bejegyzés a fájlban marad, érintetlenül, se törölve, se átírva

PDFlibPas HasPageBox visszatérési kódok nulla, egy és kettő, közvetlen és indirekt tömbökkel egyaránt örököltként számolva v3.539.44 óta, mellette a CapturePageEx nulla-négy opciói, ahol a BleedBox, TrimBox és ArtBox v3.539.42 óta a CropBoxra esik vissza a MediaBox helyett
Egy specifikációs szabály két implementációs belépőjét együtt javítják, és 18 forgatókönyves mátrixként tesztelik, lekérdezés és capture minden fájlon egyetért

A HasPageBox kihagyta a közvetlen szülői tömböket

A HasPageBox 0-t ad, ha az oldalnak nincs a kért típusú doboza, 1-et, ha saját doboza van (közvetlenül vagy indirekt hivatkozáson át tárolva), 2-t pedig, ha MediaBoxot vagy CropBoxot örökölt egy ősétől. A régi kód csak akkor adott 2-t, ha az örökölt érték indirekt hivatkozás volt, így egy örökölt közvetlen tömb 0-t adott. A javítás szétválasztja a dereferálást a tömbteszttől, és most már mindkét reprezentáció 2-t ad. v3.539.44 óta a HasPageBox egy BleedBoxra, TrimBoxra vagy ArtBoxra csak 0-t vagy 1-et adhat

A tanulság jóval túlmutat az oldaldobozokon. Amikor egy specifikációs szemantikának két implementációs belépője van egy libraryben, javítsd őket együtt, és mátrixként teszteld, ne egy boldog-út fájllal. A PDFlibPas regressziós készlete két szülői dobozreprezentációt (közvetlen és indirekt tömb) keresztez három levélállapottal (hiányzik, közvetlen tömb, indirekt tömb) és három capture opcióval (bleed, trim, art), ami 18 forgatókönyvet ad, és mindegyik ellenőrzi a lekérdezés eredményét, a kapcsolt határokat, a legális MediaBox- és CropBox-öröklést, meg az érintetlen szülői bejegyzést

Hogyan olvasom ki Delphiben a tényleges TrimBoxot?

Hívd meg a GetPageBox(4, Dimension)-ot a kiválasztott oldalon. A PDFlibPas helyetted alkalmazza az alapértelmezési láncot, így az eredmény a tényleges TrimBox, akkor is, ha az oldalnak nincs egyje. Párosítsd a HasPageBox-szal, amikor tudni kell, honnan jött az érték, amit egy preflight riport általában akar

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 = saját, 2 = örökölt
    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.

Mind a GetPageBox, mind a SetPageBox a dokumentum aktuális koordinátabeállításaiban dolgozik. A példák itt az alapértékekkel futnak: 0-s hipuspont (bal-alsó, a PDF user space-szel egyező) és pont mértékegység, így a Top dimenzió a felső él, alulról mérve. SetOrigin(1) után a Top és Bottom dimenziók felülről lefelé mérődnek, SetMeasurementUnits(1) után pedig minden érték milliméterben jön. A szélesség és magasság nem függ a hipusponttól

A /Pages csomópontokon elcsatangolt gyártódobozok felkutatása

v3.539.44 óta a doboz API már nem lát TrimBoxot egy /Pages csomóponton, ami helyes, de egy preflight eszköz általában jelenteni akarja az ilyen fájlt, nem pedig csendben spec szerint olvasni. Az oldalfa csomópontok rendes objektumok, így az alacsonyszintű objektum API megtalálhatja őket: járjad végig az objektumszámokat GetMaxObjectNumber-ig, olvasd mindegyiket GetObjectToString-gel, és keress egy gyártódoboz kulcsot hordozó /Pages szótárt. Az ellenőrzés második fele az oldalankénti teszt, ami a PDF/X-et érdekli, és a HasPageBox most úgy válaszol rá, ahogy egy PDF/X validátor tenné, mert a szülői TrimBox már nem számít

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. Gyártódobozok oldalfa csomópontokon: nem szabványosak és ignoráltak
  for ObjNum := 1 to Lib.GetMaxObjectNumber do
  begin
    Src := '';                                // a szabad számok nem adnak szöveget
    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: minden oldalnak kell saját TrimBox vagy 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. Opcionális javítás: 6 x 9 inch trim egy 6.25 x 9.25 inch media boxban
  //    (pontok, bal-alsó hipuspont: Left, Top, Width, Height)
  if Missing > 0 then
    Log.Add(Format('TrimBox written on %d pages',
      [Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;

A szövegillesztés pragmatikus ellenőrzés, nem parser. Arra épít, hogy a PDFlibPas minden szótárbejegyzést kulcsként, egy szóközként és értékként sorosít, ami az GetObjectToString-gel visszaolvasott objektumokra igaz. A javítási lépés döntést érdemel, nem reflexet: a csavarodott szülői érték jól lehet az, amit a szerző szánt, de erősítsd meg a nyomdai munkalappal, mielőtt hivatalossá teszed. Az üres tartományú SetPageBoxRange minden oldalra ráírja a dobozt, és a frissített oldalak számát adja. Ha egy oldal meglévő doboza indirekt tömb, amit másik oldal vagy /Pages csomópont osztozhat, a SetPageBox annak az oldalnak új közvetlen tömböt ad a megosztott objektum átírása helyett. BleedBox, TrimBox vagy ArtBox állítása ráadásul a nyitott dokumentumot PDF 1.3-ra emeli, arra a verzióra, ami ezeket a bejegyzéseket bevezette

Oldalak kényszerítése a TrimBoxra CapturePageExszel

A CapturePageEx(Page, 3) az oldalt Form XObjectté alakítja, aminek a határolódobozát az oldal tényleges TrimBoxja adja, a DrawCapturedPage pedig bármekkora méretben másik oldalra teszi azt a formot. v3.539.42 óta a 3-as opció TrimBox nélküli oldalon a CropBoxot adja, ahogy a referencia írja, nem pedig a MediaBoxot az összes szennyszélével

A capture két tulajdonsága formálja a kódot. A capture destruktív: a kapcsolt oldal kikerül a dokumentumból, és a dokumentum sosem mehet nullára, ezért az első kimeneti ívet fűzd hozzá, mielőtt bármit kapcsolnál. A capture egyetlen dokumentumon belül működik csak, ezért húzd egyetlen dokumentumba az összes bemenetet előbb; a PDF források egy menetben való fésülése és közbeszúrása technikái közvetlenül alkalmazhatók

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;

    // Az 1. oldal tényleges trim mérete (ez a layout egységes trimet feltételez)
    Lib.SelectPage(1);
    TrimW := Lib.GetPageBox(4, 2);
    TrimH := Lib.GetPageBox(4, 3);

    // Hozzáfűzöd és méretezed az első ívet; a NewPage kiválasztja az új oldalt
    Lib.NewPage;
    Lib.SetPageDimensions(2 * TrimW, TrimH);

    // Minden capture elveszi az 1. oldalt, így a következő forrásoldal előrelép
    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;

    // Csak az ív maradt: ívenként két levágott oldal, egymás mellett
    Lib.SelectPage(1);
    for I := 0 to SourceCount - 1 do
    begin
      if (I > 0) and (I mod 2 = 0) then
        Lib.NewPage;                            // ugyanakkora, mint az aktuális ív
      // Alapértelmezett hipuspont: a Top a felső él, alulról mérve
      Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
    end;
    Lib.SaveToFile(OutFile);
  finally
    Lib.Free;
  end;
end;

A trim alapú capture mindent levág, ami a TrimBoxon kívül esik, ami pont az, amit egy digitális korrektúránál vagy cut-and-stack layoutnál akarsz. Nyomdai ívnél, amit nyomtatás után vágnak, kapcsolj 2-es opcióval, hogy a vérzés megmaradjon, és hézagold a cellákat a vérzés szélességével. Mivel a capture elveszi a forrásoldalakat, a rájuk mutató bookmarkok és linkek célt veszítenek, ezért külön kimeneti fájlba kényszeríts, ne pedig olyan dokumentumot szerkessz, aminek a navigációjára még szükséged van; a oldalak cseréje bookmarkok törése nélkül az oldalműtét ezen oldalát fedi le

Amikor a forrásnak érintetlennek kell maradnia, az ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) ugyanazokat a 0-tól 4-ig terjedő opciókat veszi (a jelenlegi dokumentumhoz add Lib.SelectedDocument-ot), érintetlenül hagyja a forrás oldalfáját, az örökölt oldalforgatást a form mátrixába normalizálja, és olyan handlet ad vissza, amit a DrawCapturedPage elfogad. A CapturePageEx nem vonja vissza a /Rotate-ot, így a forgatott bemenetnek előbb arra a lépésre van szüksége, és a oldalforgatás kilapítása oldaldobozok törése nélkül megmutatja, mi történik mindegyik dobozzal, ha megteszed. Egy figyelmeztetés azokhoz a bemenetekhez, amik /Pages csomópontokon hordozhatnak gyártódobozokat: az import út a saját őskeresésén át oldja fel a dobozát, a v3.539.44-ben összeigazított két úttól elkülönülten, ezért nézd meg előbb a forrásoldalon a HasPageBox(4)-et, és adj 1-es opciót (CropBox), ha az 0-t ad. Így az eredmény a spechez kötődik, nem ahhoz, ahogy a fájlt véletlenül megírták

Oldaldoboz gyorsreferencia

  • Tényleges CropBox: az oldal saját CropBoxja, ennek hiányában a legközelebbi örökölt CropBox, végül a tényleges MediaBox (ISO 32000-1 §14.11.2)
  • Tényleges BleedBox, TrimBox és ArtBox: a levéloldal saját bejegyzése, ennek hiányában a tényleges CropBox
  • Csak a Resources, MediaBox, CropBox és Rotate öröklődik /Pages csomópontokról (§7.7.3.4, 30. táblázat); a /Pages csomópontokon lévő gyártódobozok ignorálódnak
  • 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 nincs doboz, 1 az oldal saját doboza (közvetlen vagy indirekt), 2 örökölt MediaBox vagy CropBox (közvetlen vagy indirekt)
  • CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox MediaBox-esessel, 2-től 4-ig BleedBox, TrimBox vagy ArtBox CropBox-esessel
  • Frissíts v3.539.44-re vagy újabbra következetes alapértékekért és öröklésért dobozlekérdezés és capture között

Az oldaldobozok az a hely, ahol a PDF csendes alapértékei millimétertörtekben mért prepress tűrésekkel találkoznak, és egy library vagy mindenhol ugyanúgy alkalmazza azokat az alapértékeket, vagy két választ ad egy kérdésre. A teljes doboz-, capture- és Form XObject API a PDFlibPas PDF Library for Delphi termékoldalon van dokumentálva