Když stránka PDF nemá TrimBox, její efektivní TrimBox je CropBox stránky a když chybí i CropBox, je to MediaBox. BleedBox a ArtBox drží stejné pravidlo. PDFlibPas, PDF Library for Delphi, aplikuje tenhle výchozí řetěz konzistentně v GetPageBox, HasPageBox a CapturePageEx od v3.539.44 a ignoruje produkční boxy položené na uzlu /Pages, protože ISO 32000-1 jim dědit nedovoluje
To zní jako poznámka pod čarou, dokud nezaimponujete zakázku. Představte si vnitřek knihy s MediaBoxem 6,25 × 9,25 palce, CropBoxem nastaveným na ořez 6 × 9 palců a bez TrimBoxu, protože ten, kdo ho exportoval, na napsání TrimBoxu ani nepomyslel. Zeptáte se na trim box, dostanete media box a každá buňka na vašem tiskovém archu vláčí osminu palce bleedu a slug do svého souseda. PDFlibPas měl závady přesně v téhle oblasti, opravené ve v3.539.42 a v3.539.44, a způsob, jakým byly opraveny, něco říká o tom, jak by se sémantika page boxů měla implementovat v jakékoli PDF knihovně
Který box platí, když stránka nemá TrimBox?
Odpovědí je pevný výchozí řetěz z ISO 32000-1 §14.11.2: CropBox se defaultně bere z MediaBoxu a BleedBox, TrimBox a ArtBox se každý bere z CropBoxu. Přímo z MediaBoxu se defaultně bere nic jiného než CropBox. Stránka definující jen MediaBox má proto pět identických boxů a stránka definující MediaBox plus CropBox má čtyři boxy rovné CropBoxu
| Box | PDFlibPas BoxType | Výchozí hodnota při absenci | Děditelné z /Pages |
|---|---|---|---|
| MediaBox | 1 | Nic, položka je povinná | Ano |
| CropBox | 2 | MediaBox | Ano |
| BleedBox | 3 | CropBox | Ne |
| TrimBox | 4 | CropBox | Ne |
| ArtBox | 5 | CropBox | Ne |
Dvoustupňový řetěz má význam, protože i samotný CropBox může být zděděný. Efektivní TrimBox stránky, která nemá ani vlastní TrimBox, ani vlastní CropBox, je CropBox nejbližšího předka, který nějaký má, a když ani ten, pak zděděný MediaBox. Specifikace přidává ještě jedno pravidlo, které se snadno zapomíná: crop, bleed, trim a art boxy nemají přesahovat za media box a pokud přesáhnou, efektivně se redukují na svůj průnik s ním. PDFlibPas hlásí každý box tak, jak je uložený v souboru, takže validátor pracující s nedůvěryhodným vstupem by se měl sám seříznout proti MediaBoxu
Které atributy stránky může uzel /Pages předat dál?
Přesně čtyři: Resources, MediaBox, CropBox a Rotate. ISO 32000-1 §7.7.3.4 definuje dědičnost atributů a Table 30 označuje jako děditelné jen ty čtyři položky page objektu. BleedBox, TrimBox a ArtBox patří listové stránce. TrimBox zapsaný do uzlu /Pages není zděděná hodnota; je to nestandardní klíč, který korektní čtečka ignoruje
Takove nestandardní soubory existují, typicky s jediným TrimBoxem na kořenovém uzlu page tree jako zkratkou za „každá stránka má tento ořez“. Zkratka vypadá správně v jakémkoli nástroji, který prochází /Parent pro každý klíč, a v tom je problém: soubor teď znamená dvě věci podle toho, kdo ho čte. Čtečka jdoucí podle specifikace nevidí žádný TrimBox a použije CropBox, zatímco čtečka, která dědí všechno, vidí hodnotu rodiče. V prepress pipeline ta nejednoznačnost skončí na tiskovém archu
Workflow PDF/X (ISO 15930) spoléhají na TrimBox jako na hotovou velikost a profily PDF/X vyžadují, aby každá stránka deklarovala TrimBox nebo ArtBox. Box zaparkovaný na uzlu /Pages ten požadavek nesplňuje, protože klíč se do page objektu nikdy nedostane. Preflight by měl takové soubory označit, místo aby je potichu četl jedním nebo druhým způsobem
Co dělal PDFlibPas před v3.539.44 špatně?
PDFlibPas měl tři oddělené závady, všechny v mezeře mezi tím, co říká specifikace, a tím, co dělaly dvě nezávislé kódové cesty. První se opravila ve v3.539.42, ty další dvě ve v3.539.44
Produkční boxy se při capture defaultně braly z MediaBoxu
Před v3.539.42 dávala interní rutina připravující stránku na capture (kopíruje zděděné položky na stránku a doplňuje chybějící boxy) BleedBoxu, TrimBoxu a ArtBoxu hodnoty MediaBoxu, když chyběly. CapturePageEx s options 2 až 4 čte svůj bounding rectangle přesně z těch doplněných položek, takže na stránce definující jen CropBox se dotaz na trim box chytil celý media box. GetPageBox už default CropBoxu aplikovala a reference CapturePageEx odjakživa říkala, že se při chybějícím boxu použije crop box; capture kód odporoval oběma. Od v3.539.42 se tři produkční boxy defaultně berou z CropBoxu stránky, který je v ten moment už na stránce (vlastní, zkopírovaný od předka, nebo doplněný z MediaBoxu) a jen samotný CropBox spadne zpátky na MediaBox
Dvě dědické cesty, jedno sémantické pravidlo
Druhou závadou byla ta nestandardní dědičnost sama a jemné na tom bylo, že PDFlibPas rozlišoval boxy po dvou nezávislých cestách. Dotazy na boxy (GetPageBox a HasPageBox) procházely řetěz /Parent přes jednoho pomocníka a capture jiným, místním pomocníkem. Obě dědily každý klíč, produkční boxy nevyjímaje. Oprava jen jedné by vytvořila rozpor uvnitř jediného dokumentu: s TrimBoxem širokým 180 bodů na uzlu /Pages a CropBoxem širokým 380 bodů na stránce by GetPageBox pořád hlásila šířku ořezu 180, zatímco CapturePageEx postavila form široký 380. Ve v3.539.44 obě cesty omezují procházení /Parent na čtyři děditelné klíče, produkční boxy se čtou jen z listu a zbloudilá rodičovská položka zůstává v souboru nedotčená, ani smazaná, ani přepsaná
HasPageBox přehlížela přímé rodičovské arrayy
HasPageBox vrací 0, když stránka nemá box požadovaného typu, 1, když má vlastní box (uložený přímo nebo přes nepřímou referenci) a 2, když je MediaBox nebo CropBox zděděný od předka. Starý kód vracel 2 jen tehdy, když byla zděděná hodnota nepřímá reference, takže zděděný přímý array vrátil 0. Oprava odděluje dereferenci od testu array a obě podoby teď vracejí 2. Od v3.539.44 může HasPageBox pro BleedBox, TrimBox nebo ArtBox vrátit jen 0 nebo 1
Lekce se zobecňuje hodně dál než na page boxy. Když má jedna část sémantiky specifikace v knihovně dva implementační vstupní body, opravte je společně a testujte je jako matici, ne jedním happy-path souborem. Regresní sada PDFlibPas kříží dvě podoby rodičovského boxu (přímý a nepřímý array) se třemi stavy listu (absentní, přímý array, nepřímý array) a třemi capture options (bleed, trim, art), což dává 18 scénářů, a každá kontroluje výsledek dotazu, zachycené meze, legitimní dědičnost MediaBoxu a CropBoxu a nedotčenou rodičovskou položku
Jak v Delphi přečtu efektivní TrimBox?
Zavolejte GetPageBox(4, Dimension) na vybrané stránce. PDFlibPas vám výchozí řetěz aplikuje sám, takže výsledkem je efektivní TrimBox, ať stránka nějaký má nebo ne. Spárujte to s HasPageBox, když potřebujete vědět, odkud hodnota přišla — což obvykle chce preflight report
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 = vlastní, 2 = zděděné
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.
Jak GetPageBox, tak SetPageBox pracují v aktuálním nastavení souřadnic dokumentu. Příklady tady běží s defaulty: počátek 0 (vlevo dole, odpovídající PDF user space) a body jako měrná jednotka, takže dimenze Top je horní hrana měřená odspodu stránky. Po SetOrigin(1) se dimenze Top a Bottom měří odshora dolů a po SetMeasurementUnits(1) přichází každá hodnota v milimetrech. Šířka a výška na počátku nezávisí
Hledání produkčních boxů uvízlých na uzlech /Pages
Od v3.539.44 box API už TrimBox na uzlu /Pages nevidí, což je správně, ale preflight nástroj obvykle takový soubor chce nahlásit, místo aby ho potichu četl specovým způsobem. Uzly page tree jsou obyčejné objekty, takže je nízkoúrovňové objektové API najde: projděte čísla objektů až do GetMaxObjectNumber, čtěte každý přes GetObjectToString a hledejte slovník /Pages nesoucí klíč produkčního boxu. Druhá půlka kontroly je test na stránku, na kterém záleží PDF/X, a HasPageBox na něj teď odpovídá tak, jak by odpovídal validátor PDF/X, protože rodičovský TrimBox se už nepočí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. Produkční boxy na uzlech page tree: nestandardní a ignorované
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // volná čísla nevrací žádný text
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: každá stránka potřebuje vlastní TrimBox nebo 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. Volitelná oprava: ořez 6 x 9 palců uvnitř media boxu 6.25 x 9.25 palce
// (body, počátek vlevo dole: Left, Top, Width, Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
Text match je pragmatická kontrola, ne parser. Spoléhá na to, že PDFlibPas serializuje každou položku slovníku jako klíč, jednu mezeru a hodnotu, což platí pro objekty čtené zpátky přes GetObjectToString. Krok opravy si zaslouží rozhodnutí, ne reflex: zbloudilá rodičovská hodnota může být klidně to, co autor zamýšlel, ale potvrďte ji proti zakázkovému listu, než z ní uděláte oficiální skutečnost. SetPageBoxRange s prázdným rozsahem aplikuje box na každou stránku a vrací počet aktualizovaných stránek. Když je stávající box stránky nepřímý array, který může sdílet jiná stránka nebo uzel /Pages, dá SetPageBox té stránce nový přímý array místo přepsání sdíleného objektu. Nastavení BleedBoxu, TrimBoxu nebo ArtBoxu navíc zvedne odemčený dokument na PDF 1.3, verzi, která ty položky zavedla
Impozice stránek na TrimBox přes CapturePageEx
CapturePageEx(Page, 3) změní stránku na Form XObject, jehož bounding box je efektivní TrimBox stránky, a DrawCapturedPage ten form umístí na jinou stránku v jakékoli velikosti. Od v3.539.42 dá option 3 na stránce bez TrimBoxu CropBox, jak popisuje reference, místo MediaBoxu se vším jeho slugem
Kód formují dvě vlastnosti capture. Capture je destruktivní: zachycená stránka se z dokumentu odstraní a dokument nikdy nesmí klesnout na nula stránek, takže připojte první výstupní arch, než začnete něco zachycovat. Capture navíc funguje jen v jednom dokumentu, takže nejdřív stáhněte každý vstup do jediného dokumentu; techniky z koládování a prokládání PDF zdrojů na jeden průchod se aplikují přímo
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;
// Efektivní velikost ořezu stránky 1 (toto rozložení předpokládá jednotný ořez)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// Připojte a rozměřte první arch; NewPage vybere novou stránku
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// Každé zachycení odstraní stránku 1, takže další zdrojová stránka poskočí výš
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;
// Zbyl jen arch: dva ořezané stránky na arch, vedle sebe
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // stejná velikost jako aktuální arch
// Výchozí počátek: Top je horní hrana, měřená odspodu
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
Capture podle ořezu stříhá všechno mimo TrimBox, což přesně chcete pro digitální korektur nebo rozložení cut-and-stack. Pro tiskový arch ořezávaný po tisku zachytávejte s option 2, aby bleed přežil, a rozestupte buňky po šířce bleedu. Protože capture odstraňuje zdrojové stránky, ztrácejí záložky a odkazy ukazující na ně své cíle, takže impozujte do odděleného výstupního souboru místo editace dokumentu, jehož navigaci ještě potřebujete; výměna stránek bez rozbitých záložek pokrývá tuhle stranu operací se stránkami
Když musí zdroj zůstat nedotčený, ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) bere stejné hodnoty options 0 až 4 (pro aktuální dokument pošlete Lib.SelectedDocument), nechává zdrojový page tree beze změny, normalizuje zděděnou rotaci stránky do matice formu a vrací handle, který DrawCapturedPage přijme. CapturePageEx nezruší /Rotate, takže otočený vstup tenhle krok potřebuje nejdřív a zploštění rotace stránky bez rozbitých page boxů ukazuje, co se stane s každým boxem, když to uděláte. Jedna opatrnost pro vstupy, které mohou nést produkční boxy na uzlech /Pages: importní cesta rozliší svůj box vlastním vyhledáním předka, odděleně od dvou cest srovnávaných ve v3.539.44, takže nejdřív zkontrolujte HasPageBox(4) na zdrojové stránce a pošlete option 1 (CropBox), když vrátí 0. Tím zůstane výsledek svázaný se specifikací, ne s tím, jak se náhodou soubor zapsal
Rychlá reference page boxů
- Efektivní CropBox: vlastní CropBox stránky, jinak nejbližší zděděný CropBox, jinak efektivní MediaBox (ISO 32000-1 §14.11.2)
- Efektivní BleedBox, TrimBox a ArtBox: vlastní položka listové stránky, jinak efektivní CropBox
- Z uzlů
/Pagesdědí jenResources,MediaBox,CropBoxaRotate(§7.7.3.4, Table 30); produkční boxy na uzlech/Pagesse ignorují 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 BottomHasPageBox(BoxType): 0 žádný box, 1 vlastní box stránky (přímý nebo nepřímý), 2 zděděný MediaBox nebo CropBox (přímý nebo nepřímý)CapturePageEx(Page, Options): 0 MediaBox, 1 CropBox s oporou v MediaBoxu, 2 až 4 BleedBox, TrimBox nebo ArtBox s oporou v CropBoxu- Přejděte na v3.539.44 a novější pro konzistentní defaulty a dědičnost napříč dotazy na boxy a capture
Page boxy jsou místo, kde se tiché defaulty PDF potkávají s prepress tolerancemi měřenými na zlomky milimetru, a knihovna buď aplikuje ty defaulty stejně všude, nebo vám podá dvě odpovědi na jednu otázku. Kompletní API boxů, capture a Form XObject je zdokumentované na produktové stránce PDFlibPas PDF Library for Delphi