وقتی یک صفحهٔ PDF هیچ TrimBox ای ندارد، TrimBox مؤثرش همان CropBox صفحه است و وقتی CropBox هم غایب است، MediaBox است. BleedBox و ArtBox هم همین قاعده را دنبال میکنند. PDFlibPas، کتابخانهٔ PDF برای Delphi، این زنجیرهٔ پیشفرض را از v3.539.44 به بعد در GetPageBox و HasPageBox و CapturePageEx یکدست اعمال میکند و جعبههای تولیدی را که روی یک گرهٔ /Pages گذاشته شدهاند نادیده میگیرد، چون ISO 32000-1 اجازهٔ ارثبردن به آنها نمیدهد
تا وقتی یک کار را impose نکنی این مثل پانویس خوانده میشود. تصور کن داخل یک کتاب با MediaBox برابر 6.25 × 9.25 اینچ، یک CropBox روی برش 6 × 9 اینچ، و بدون هیچ TrimBox ای، چون کسی که export کرده هرگز فکر نکرده بود یکی بنویسد. جعبهٔ برش را میخواهی، جعبهٔ رسانه میگیری، و هر سلول روی فرم چاپ یکهشتم اینچ bleed و slug را به همسایهاش میکشد. PDFlibPas دقیقاً در همین ناحیه عیب داشت که در v3.539.42 و v3.539.44 فیکس شدند، و نحوهٔ فیکس شدنشان چیزی میگوید دربارهٔ اینکه معناشناسی page box باید در هر کتابخانهٔ PDF چطور پیاده شود
وقتی صفحهای TrimBox ندارد کدام جعبه اعمال میشود؟
جواب یک زنجیرهٔ پیشفرض ثابت از ISO 32000-1 §14.11.2 است: CropBox به MediaBox برمیگردد و BleedBox و TrimBox و ArtBox هر کدام به CropBox. هیچ چیز غیر از CropBox مستقیم به MediaBox برنمیگردد. پس صفحهای که فقط MediaBox تعریف کرده پنج جعبهٔ یکسان دارد و صفحهای که MediaBox بهعلاوهٔ CropBox تعریف کرده چهار جعبهٔ برابر با CropBox دارد
| جعبه | BoxType در PDFlibPas | پیشفرض وقتی غایب است | ارثبردنی از /Pages |
|---|---|---|---|
| MediaBox | 1 | هیچ، درایه الزامی است | بله |
| CropBox | 2 | MediaBox | بله |
| BleedBox | 3 | CropBox | خیر |
| TrimBox | 4 | CropBox | خیر |
| ArtBox | 5 | CropBox | خیر |
دوگامی بودن زنجیره مهم است چون CropBox خودش ممکن است ارثی باشد. TrimBox مؤثر صفحهای که نه TrimBox دارد و نه CropBox خودش را، همان CropBox نزدیکترین جدی است که یکی دارد، و در نبود آن، MediaBox ارثی. مشخصه یک قاعدهٔ دیگر هم اضافه میکند که فراموش کردنش راحت است: جعبههای crop و bleed و trim و art نباید از media box بیرون بزنند، و اگر بزنند عملاً به اشتراکشان با آن کاهش مییابند. PDFlibPas هر جعبه را همانطور که در فایل ذخیره شده گزارش میکند، پس یک validator که ورودی غیرقابلاعتماد میگیرد باید خودش نسبت به MediaBox ببَرد
یک گرهٔ /Pages کدام attributeهای صفحه را پایین میفرستد؟
دقیقاً چهار تا: Resources و MediaBox و CropBox و Rotate. ISO 32000-1 §7.7.3.4 ارثبری attribute را تعریف میکند و جدول 30 فقط همین چهار درایهٔ شیء صفحه را ارثبردنی علامت میزند. BleedBox و TrimBox و ArtBox متعلق به برگ صفحهاند. یک TrimBox نوشتهشده در گرهٔ /Pages مقدار ارثی نیست؛ یک کلید غیراستاندارد است که یک خوانندهٔ منطبق نادیدهاش میگیرد
فایلهای غیراستانداردی مثل این وجود دارند، معمولاً با یک TrimBox تکی روی گرهٔ ریشهٔ درخت صفحه بهعنوان کوتاهنویسیِ «هر صفحه این برش را دارد». آن کوتاهنویسی در هر ابزاری که برای هر کلیدی /Parent را میپیماید درست به نظر میرسد، و همان مشکل است: فایل حالا دو معنا دارد بسته به اینکه چه کسی میخواند. خوانندهای که از مشخصه پیروی میکند هیچ TrimBox ای نمیبیند و CropBox را استفاده میکند، در حالی که خوانندهای که همهچیز را ارث میبرد مقدار پدر را میبیند. در یک خط لولهٔ prepress آن ابهام روی فرم چاپ فرود میآید
گردشکارهای PDF/X (ISO 15930) برای اندازهٔ تمامشده به TrimBox وابستهاند و پروفایلهای PDF/X میخواهند هر صفحه یک TrimBox یا ArtBox اعلام کند. جعبهای که روی گرهٔ /Pages پارک شده آن الزام را برآورده نمیکند، چون کلید هرگز به شیء صفحه نمیرسد. Preflight باید چنین فایلهایی را علامت بزند نه اینکه بیسروصدا به یک شکل یا شکل دیگر بخواندشان
PDFlibPas قبل از v3.539.44 چه اشتباهی کرده بود؟
PDFlibPas سه عیب جدا داشت، همه در فاصلهٔ بین آنچه مشخصه میگوید و کاری که دو مسیر کد مستقل میکردند. اولی در v3.539.42 فیکس شد، دوتای دیگر در v3.539.44
جعبههای تولیدی موقع capture به MediaBox برمیگشتند
قبل از v3.539.42، رویهٔ داخلی که صفحه را برای capture آماده میکرد (درایههای ارثی را روی صفحه کپی میکند و جعبههای غایب را پر میکند) به BleedBox و TrimBox و ArtBox وقتی غایب بودند مقادیر MediaBox را میداد. CapturePageEx با آپشنهای 2 تا 4 مستطیل محصورکنندهاش را از دقیقاً همان درایههای پرشده میخواند، پس روی صفحهای که فقط CropBox تعریف کرده، خواستن جعبهٔ برش کل media box را capture میکرد. GetPageBox از قبل پیشفرض CropBox را اعمال میکرد و مرجع CapturePageEx هم همیشه میگفت وقتی جعبهٔ درخواستی غایب است از crop box استفاده میشود؛ کد capture با هر دو ناسازگار بود. از v3.539.42 سه جعبهٔ تولیدی به CropBox صفحه برمیگردند که در آن نقطه از قبل روی صفحه است (خودش، کپیشده از جد، یا پرشده از MediaBox)، و فقط خود CropBox به MediaBox برمیگردد
دو مسیر ارثبری، یک قاعدهٔ معنایی
عیب دوم خود ارثبری غیراستاندارد بود و ظریفش این بود که PDFlibPas جعبهها را در دو مسیر مستقل حل میکرد. پرسوجوهای جعبه یعنی GetPageBox و HasPageBox زنجیرهٔ /Parent را از طریق یک هلپر میپیمودند و capture از طریق یک هلپر محلی جدا. هر دو هر کلیدی را ارث میبردند، جعبههای تولیدی هم شامل. فیکس کردن فقط یکی از آنها درون یک سند تکی تناقض تولید میکرد: با یک TrimBox به عرض 180 point روی گرهٔ /Pages و یک CropBox به عرض 380 point روی صفحه، GetPageBox همچنان عرض برش 180 را گزارش میکرد در حالی که CapturePageEx فرمی به عرض 380 میساخت. در v3.539.44 هر دو مسیر پیمایش /Parent را به چهار کلید ارثبردنی محدود میکنند، جعبههای تولیدی فقط از برگ خوانده میشوند، و درایهٔ سرگردان پدر بدون دست خورده در فایل میماند، نه حذفشده نه بازنویسیشده
HasPageBox آرایههای مستقیم پدر را از دست میداد
HasPageBox وقتی صفحه هیچ جعبهای از نوع درخواستی ندارد 0 برمیگرداند، وقتی جعبهٔ خودش را دارد (ذخیرهشده مستقیم یا از طریق ارجاع غیرمستقیم) 1، و وقتی یک MediaBox یا CropBox از جد ارث شده 2. کد قدیمی فقط وقتی مقدار ارثی یک ارجاع غیرمستقیم بود 2 برمیگرداند، پس یک آرایهٔ مستقیم ارثی مقدار 0 برمیگرداند. فیکس dereference کردن را از تست آرایه جدا میکند و هر دو نمایش حالا 2 برمیگردانند. از v3.539.44 HasPageBox برای یک BleedBox یا TrimBox یا ArtBox فقط میتواند 0 یا 1 برگرداند
این درس خیلی فراتر از جعبههای صفحه تعمیم مییابد. وقتی یک بخش از معناشناسی مشخصه دو نقطهٔ ورود پیادهسازی در یک کتابخانه دارد، آنها را با هم فیکس کن و بهشکل ماتریس تست کن نه با یک فایل خوشمسیر. مجموعهٔ regression در PDFlibPas دو نمایش جعبهٔ پدر (آرایهٔ مستقیم و غیرمستقیم) را با سه حالت برگ (غایب، آرایهٔ مستقیم، آرایهٔ غیرمستقیم) و سه آپشن capture (bleed و trim و art) ضربدر میکند که 18 سناریو میشود، و هر کدام نتیجهٔ پرسوجو و محدودهٔ captureشده و ارثبری مشروع MediaBox و CropBox و درایهٔ دستنخوردهٔ پدر را چک میکند
TrimBox مؤثر را در Delphi چطور میخوانی؟
روی صفحهٔ انتخابشده GetPageBox(4, Dimension) را صدا بزن. PDFlibPas زنجیرهٔ پیشفرض را برایت اعمال میکند، پس نتیجه TrimBox مؤثر است چه صفحه یکی داشته باشد چه نه. هر وقت لازم شد بدانی مقدار از کجا آمده با HasPageBox جفتش کن، که یک گزارش prepress معمولاً میخواهد
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 = خودش، 2 = ارثی
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 و SetPageBox در تنظیمات مختصات فعلی سند کار میکنند. مثالهای اینجا با پیشفرضها اجرا میشوند: مبدأ 0 (پایین-چپ، مطابق user space در PDF) و point بهعنوان واحد اندازهگیری، پس بعد Top لبهٔ بالایی اندازهگیریشده از پایین صفحه است. بعد از SetOrigin(1) بعداهای Top و Bottom بهجایش از بالای صفحه به پایین اندازه گرفته میشوند و بعد از SetMeasurementUnits(1) هر مقداری به میلیمتر برمیگردد. عرض و ارتفاع به مبدأ وابسته نیستند
پیدا کردن جعبههای تولیدی سرگردان روی گرههای /Pages
از v3.539.44 API جعبه دیگر یک TrimBox روی گرهٔ /Pages را نمیبیند، که درست است، اما یک ابزار prepress معمولاً میخواهد چنین فایلی را گزارش کند نه اینکه بیسروصدا به شیوهٔ مشخصه بخواندش. گرههای درخت صفحه اشیای عادیاند، پس API شیء سطح پایین میتواند پیدایشان کند: شمارههای شیء را تا GetMaxObjectNumber بپیمای، هر کدام را با GetObjectToString بخوان و دنبال یک dictionary به نام /Pages بگرد که کلید جعبهٔ تولیدی حمل میکند. نیمهٔ دوم چک، تست بهازای هر صفحه است که PDF/X به آن اهمیت میدهد و HasPageBox حالا همانطور که یک validator در PDF/X جوابش میدهد جواب میدهد، چون TrimBox پدری دیگر حساب نمیشود
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. جعبههای تولیدی روی گرههای درخت صفحه: غیراستاندارد و نادیدهگرفتهشده
for ObjNum := 1 to Lib.GetMaxObjectNumber do
begin
Src := ''; // شمارههای آزاد متنی برنمیگردانند
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: هر صفحه به TrimBox یا 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. ترمیم اختیاری: یک برش 6 در 9 اینچ داخل یک media box 6.25 در 9.25 اینچ
// (point، مبدأ پایین-چپ: Left، Top، Width، Height)
if Missing > 0 then
Log.Add(Format('TrimBox written on %d pages',
[Lib.SetPageBoxRange('', 4, 9, 657, 432, 648)]));
end;
تطبیق متن یک چک عملگرایانه است نه یک parser. به این تکیه دارد که PDFlibPas هر درایهٔ dictionary را بهشکل یک کلید و یک فاصله و یک مقدار سریالایز میکند، که برای اشیای خواندهشده از طریق GetObjectToString برقرار است. مرحلهٔ ترمیم لایق یک تصمیم است نه یک رفلکس: مقدار سرگردان پدر ممکن است خیلی خوب همان چیزی باشد که نویسنده قصدش را داشته، اما قبل از رسمی کردنش با برگهٔ کار تطبیقش بده. SetPageBoxRange با یک بازهٔ خالی جعبه را روی هر صفحه اعمال میکند و تعداد صفحات بهروزرسانیشده را برمیگرداند. وقتی جعبهٔ موجود صفحه یک آرایهٔ غیرمستقیم است — که صفحهٔ دیگری یا یک گرهٔ /Pages ممکن است شریکش باشد — SetPageBox به آن صفحه یک آرایهٔ مستقیم جدید میدهد بهجای بازنویسی شیء مشترک. گذاشتن یک BleedBox یا TrimBox یا ArtBox سند قفلنشده را هم به PDF 1.3 میبرد، نسخهای که آن درایهها را معرفی کرد
impose کردن صفحات روی TrimBox با CapturePageEx
CapturePageEx(Page, 3) یک صفحه را به یک Form XObject تبدیل میکند که جعبهٔ محصورکنندهاش TrimBox مؤثر صفحه است، و DrawCapturedPage آن فرم را با هر اندازهای روی صفحهٔ دیگری میگذارد. از v3.539.42، آپشن 3 روی صفحهای بدون TrimBox به تو CropBox را میدهد، همانطور که مرجع توضیح میدهد، بهجای MediaBox با تمام slug اش
دو خاصیت capture کد را شکل میدهند. capture مخرب است: صفحهٔ captureشده از سند حذف میشود و سند هرگز نمیتواند به صفر صفحه برسد، پس اولین فرم خروجی را قبل از capture کردن هر چیزی اضافه کن. capture همچنین فقط داخل یک سند کار میکند، پس همهٔ ورودیها را اول در یک سند تکی جمع کن؛ تکنیکهای دور هم چیدن و درهمبافی منابع PDF در یک گذر مستقیماً کاربرد دارند
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;
// اندازهٔ برش مؤثر صفحهٔ 1 (این layout یک برش یکنواخت فرض میکند)
Lib.SelectPage(1);
TrimW := Lib.GetPageBox(4, 2);
TrimH := Lib.GetPageBox(4, 3);
// اولین فرم را اضافه و اندازه کن؛ NewPage صفحهٔ جدید را انتخاب میکند
Lib.NewPage;
Lib.SetPageDimensions(2 * TrimW, TrimH);
// هر capture صفحهٔ 1 را حذف میکند، پس صفحهٔ منبع بعدی بالا میآید
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;
// فقط فرم باقی مانده: دو صفحهٔ بریده بهازای هر فرم، کنار هم
Lib.SelectPage(1);
for I := 0 to SourceCount - 1 do
begin
if (I > 0) and (I mod 2 = 0) then
Lib.NewPage; // هماندازهٔ فرم فعلی
// مبدأ پیشفرض: Top لبهٔ بالایی است، اندازهگیریشده از پایین
Lib.DrawCapturedPage(Captures[I], (I mod 2) * TrimW, TrimH, TrimW, TrimH);
end;
Lib.SaveToFile(OutFile);
finally
Lib.Free;
end;
end;
یک capture مبتنی بر برش هر چیزی بیرون TrimBox را میبُرد، که برای یک پرووف دیجیتال یا چیدمان برش-و-انباشته همان چیزی است که میخواهی. برای فرم چاپی که بعد از چاپ بریده میشود، با آپشن 2 capture کن تا bleed جان سالم به در ببرد و سلولها را با عرض bleed فاصله بده. چون capture صفحات مبدأ را حذف میکند، بوکمارکها و لینکهایی که به آنها اشاره میکردند مقصدهایشان را از دست میدهند، پس بهجای ویرایش سندی که ناوبریاش هنوز لازم است، در یک فایل خروجی جدا impose کن؛ جایگزین کردن صفحات بدون شکستن بوکمارکها آن سمت جراحی صفحه را پوشش میدهد
وقتی مبدأ باید دستنخورده بماند، ImportPageAsFormXObject(SourceDocumentID, SourcePage, Options) همان مقادیر آپشن 0 تا 4 را میگیرد (برای سند فعلی Lib.SelectedDocument را بده)، درخت صفحهٔ مبدأ را بدون تغییر میگذارد، چرخش ارثی صفحه را نرمال به ماتریس فرم میکند، و یک handle برمیگرداند که DrawCapturedPage میپذیرد. CapturePageEx خودش /Rotate را خنثی نمیکند، پس ورودی چرخیده اول آن گام را لازم دارد و تخت کردن چرخش صفحه بدون شکستن جعبههای صفحه نشان میدهد با هر جعبه چه میشود وقتی انجامش میدهی. یک هشدار برای ورودیهایی که ممکن است جعبههای تولیدی روی گرههای /Pages داشته باشند: مسیر import جعبهاش را از طریق جستوجوی جد خودش حل میکند، جدا از دو مسیری که در v3.539.44 همراستا شدند، پس اول HasPageBox(4) را روی صفحهٔ مبدأ چک کن و وقتی 0 برگرداند آپشن 1 یعنی CropBox را بده. این نتیجه را به مشخصه گره میزند نه به اینکه فایل اتفاقاً چطور نوشته شده بود
مرجع سریع page box
- CropBox مؤثر: CropBox خود صفحه، وگرنه نزدیکترین CropBox ارثی، وگرنه MediaBox مؤثر (ISO 32000-1 §14.11.2)
- BleedBox و TrimBox و ArtBox مؤثر: درایهٔ خود برگ صفحه، وگرنه CropBox مؤثر
- فقط
ResourcesوMediaBoxوCropBoxوRotateاز گرههای/Pagesارث میبرند (§7.7.3.4، جدول 30)؛ جعبههای تولیدی روی گرههای/Pagesنادیده گرفته میشوند 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 یعنی بدون جعبه، 1 یعنی جعبهٔ خود صفحه (مستقیم یا غیرمستقیم)، 2 یعنی یک MediaBox یا CropBox ارثی (مستقیم یا غیرمستقیم)CapturePageEx(Page, Options): 0 یعنی MediaBox، 1 یعنی CropBox با بازگشت به MediaBox، و 2 تا 4 یعنی BleedBox یا TrimBox یا ArtBox با بازگشت به CropBox- برای پیشفرضها و ارثبری یکدست در پرسوجوهای جعبه و capture به v3.539.44 یا بعدتر ارتقا بده
جعبههای صفحه جایی هستند که پیشفرضهای آرام PDF با تلورانسهای prepress اندازهگیریشده به کسرهای میلیمتر ملاقات میکنند، و یک کتابخانه یا این پیشفرضها را همهجا به یک شکل اعمال میکند یا دو جواب برای یک سؤال به تو میدهد. API کامل جعبه و capture و Form XObject در صفحهٔ محصول PDFlibPas PDF Library for Delphi مستند شده است