بخیه زدن صفحه (Page stitching)، محتوای صفحات موجود را روی یک بوم مشترک ترکیب میکند که این کار عملیاتی متفاوت از ادغام فایلها است. کتابخانه PDF losLab قابلیت بخیه زدن صفحه با Form XObject و الگوهای واترمارک ترکیبی را برای دلفی پیادهسازی میکند: توابع StitchPageOverlay، StitchPagesSideBySide و StitchPagesVertically صفحات کپچر شده را در هر مقیاسی با کیفیت برداری کامل قرار میدهند، و TPDFlibCompositeWatermark واترمارکهای متنی و تصویری را در یک محدوده صفحه با یک فراخوانی روی هم قرار میدهد. اگر آنچه واقعاً نیاز دارید، پیوست کردن صفحات کامل از چند سند در یک فایل است، این موضوع به خانواده ادغام مربوط میشود که در مقاله ادغام سریع PDF در سطح بایت پوشش داده شده است؛ این مقاله درباره ترکیب محتوای صفحه در یک فضای مختصات واحد است
درخواستهایی که به این مسیر ختم میشوند، معمول و تکراری هستند. یک مدرسه میخواهد دو صفحه امتحان را روی یک ورق افقی (landscape) چاپ کند تا مصرف کاغذ را به نصف کاهش دهد. یک بازبین بومیسازی میخواهد صفحه انگلیسی و ترجمه آن را در کنار هم روی یک صفحه داشته باشد تا با هم اسکرول شوند. یک تیم حقوقی میخواهد روی هر پیشنویس به طور همزمان سه لایه مهر زده شود: یک لوگو در گوشه، یک عبارت مورب CONFIDENTIAL در وسط، و یک خط پاورقی کوچک، آن هم فقط در صفحات ۱ تا ۳. هر سه مورد، یک مسئله اساسی یکسان دارند؛ یعنی قرار دادن محتوای صفحهای که قبلاً صفحهآرایی شده است در جایی دیگر و با اندازهای دیگر بدون افت کیفیت آن به یک بیتمپ
چرا الحاق مستقیم استریمهای محتوای PDF با شکست مواجه میشود؟
بخیه زدن ساده، یعنی کپی کردن استریم /Contents صفحه مبدأ به صفحه مقصد، به سه دلیل ساختاری با شکست مواجه میشود. اول اینکه مختصات با هم تداخل پیدا میکنند: هر دو استریم محتوا مبدأ خود را در پایین سمت چپ MediaBox خود فرض میکنند، بنابراین محتوای چسبانده شده روی محتوای موجود در مکان نادرست قرار میگیرد. دوم اینکه نام منابع تداخل پیدا میکند: هر صفحه اپراتورهایی مانند /F1 Tf و /Im0 Do را در برابر دیکشنری /Resources خود ارزیابی میکند، و دو صفحه به طور معمول یک نام را به فونتها یا تصاویر متفاوتی متصل میکنند، بنابراین یکی از آنها بدون هیچ هشداری با منبع اشتباه رندر میشود. سوم اینکه کپی استریم خام هیچ مقیاسگذاری به شما نمیدهد؛ بهترین کاری که میتوانید انجام دهید انتقال دادن است
اشیای Form XObject هر سه مشکل را به طور همزمان حل میکنند و به همین دلیل است که کتابخانه PDF losLab عملیات بخیه زدن خود را بر روی آنها بنا میکند. استاندارد ISO 32000-1 §8.10 یک Form XObject را به عنوان یک استریم محتوای مستقل با دیکشنری /Resources و فضای مختصات خود تعریف میکند که با فراخوانی نام آن تحت هر ماتریس تبدیل فعلی که فعال است، در یک صفحه قرار میگیرد. متد CapturePage کل یک صفحه موجود، شامل محتوا و منابع را در یک شیء /Subtype /Form XObject بستهبندی میکند، و توابع بخیه زدن سپس آن را با یک ماتریس cm موقعیتدهی میکنند: مقیاسگذاری، انتقال، چرخش، به دلخواه. از آنجا که مبدأ به صورت برداری باقی میماند، متن به عنوان متن و مسیرها به عنوان مسیر در هر سطح زوم باقی میمانند؛ همان ویژگی که گرافیکهای برداری و الگوهای سایهزنی را مستقل از رزولوشن میکند. هیچ چیز رسترایز نمیشود و دیکشنری منابع ایزوله به این معنی است که صفحه کپچر شده نمیتواند با صفحه مقصد بر سر نام /F1 رقابت کند
چگونه دو صفحه PDF را در یک صفحه ترکیب کنیم؟
تابع StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) پاسخ مستقیم است: هر دو صفحه مبدأ را کپچر میکند، یک صفحه جدید عریضتر ایجاد میکند و دو کپچر را در سمت چپ و راست با یک فاصله (gutter) بین آنها قرار میدهد که هر کدام برای قرار گرفتن در TargetHeight مقیاسگذاری شدهاند. این تابع شماره صفحه جدید (بر پایه ۱) یا ۰ را در صورت شکست برمیگرداند، و هر دو صفحه مبدأ به عنوان بخشی از عملیات کپچر و پنهان میشوند. مورد آزمون دو صفحه در یک ورق، یک کار سه خطی است
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;
برای ترکیب عمودی، StitchPagesVertically(PageRanges, Gap, TargetWidth) یک محدوده کامل از صفحات را در یک صفحه بلند و به سبک پوستر روی هم قرار میدهد. PageRanges ساختار محدوده استاندارد مانند '1-3,5' را میپذیرد. هر صفحه مبدأ مقیاسگذاری میشود تا عرض آن با TargetWidth مطابقت داشته باشد، بنابراین سهم ارتفاع هر صفحه، ارتفاع خودش ضرب در فاکتور مقیاس خودش است، و ارتفاع صفحه خروجی مجموع آنها به علاوه یک Gap بین هر جفت است. این همان شکلی است که برای اسناد بازبینی با اسکرول مداوم یا رسیدهای طولانی که از منابعی با اندازه نامه (letter) جمعآوری شدهاند، میخواهید
چگونه یک صفحه را روی صفحه دیگری قرار دهیم؟
تابع StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) به جای ایجاد یک صفحه جدید، یک صفحه موجود را روی صفحه موجود دیگری ترسیم میکند. صفحه مبدأ به عنوان یک Form XObject کپچر شده و روی صفحه مقصد در مستطیلی که مشخص کردهاید با Opacity از ۰ تا ۱ (۱ کاملاً کدر است) ترسیم میشود. عرض و ارتفاع پارامترهای آزاد هستند، بنابراین اگر نسبت شما با نسبت تصویر خود صفحه مبدأ متفاوت باشد، قرارگیری کشیده میشود؛ زمانی که میخواهید یک تصویر بندانگشتی بدون تغییر شکل از مبدأ داشته باشید، یک جفت متناسب را پاس دهید. یک کاربرد رایج، سندی است که بلوک تأیید آن به عنوان صفحه خود نگهداری میشود و روی جلد مهر میشود
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;
بعد از کپچر کردن چه اتفاقی برای شماره صفحات میافتد؟
متد CapturePage صفحه مبدأ را مصرف میکند: محتوای آن به XObject منتقل شده و صفحه خالی شده پنهان میشود، بنابراین هر صفحهای که شماره آن بالاتر از مبدأ است، بلافاصله یک واحد به پایین منتقل میشود. این مهمترین مفهوم در کل API بخیه زدن است. کتابخانه PDF losLab محاسبات را در داخل هر فراخوانی بخیه زدن انجام میدهد، StitchPageOverlay شاخص مقصد را زمانی که مقصد بالاتر از مبدأ قرار دارد تنظیم میکند، و StitchPagesSideBySide ابتدا صفحه با شماره بالاتر را کپچر میکند تا شاخص کپچر دوم همچنان معتبر باشد، اما کد شما باید تغییرات شاخص را در بین فراخوانیها در نظر بگیرد. دو قانون کد مولتی-بخیه را صحیح نگه میدارد. هر اندازه صفحه یا شماره صفحهای را که نیاز دارید قبل از اولین کپچر ثبت کنید، زیرا بعد از آن تغییر میکنند. و هنگامی که خودتان چندین صفحه را در یک حلقه کپچر میکنید، ترتیب صفحات را به صورت نزولی پیمایش کنید، به طوری که پنهان کردن یک صفحه بعدی هرگز شاخص صفحه قبلی را که هنوز کپچر نکردهاید به هم نزند
چگونه یک واترمارک چند لایه را روی یک محدوده صفحه اعمال کنیم؟
لایه الگوی واترمارک در PDFlibWatermark.pas مفهوم "یکبار تعریف کن، روی صفحات متعدد اعمال کن" را به شیء تبدیل میکند. کلاس TPDFlibWatermarkBase ویژگیهای مشترک هر واترمارک را حمل میکند: یک Position نه نقطهای (wpTopLeft تا wpBottomRight، به علاوه wpCustom که از مختصات X/Y خام استفاده میکند)، یک Angle به درجه، یک Opacity از ۰ تا ۱، و یک رشته PageRange که خالی بودن آن به معنی تمام صفحات است. کلاس TPDFlibTextWatermark متن، یک شناسه فونت (پیشفرض Helvetica)، اندازه و یک رنگ RGB را اضافه میکند؛ TPDFlibImageWatermark یک شناسه تصویر از تصویری که قبلاً اضافه شده را به همراه عرض و ارتفاع قرارگیری اضافه میکند. TPDFlibCompositeWatermark لیستی از زیرواترمارکها را نگه میدارد و آنها را به صورت بازگشتی اعمال میکند، و TPDFlib.ApplyWatermark نقطه ورود تک فراخوانی است که به محدوده صفحه خود الگو احترام میگذارد و تعداد صفحات مهر شده را برمیگرداند. مهر حقوقی سه لایه به این شکل است
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;
دو جزئیات ترکیب ارزش توضیح دادن دارند. ارثبری محدوده یکطرفه و غیرمخرب است: یک فرزند با PageRange خالی به طور موقت محدوده کامپوزیت را در طول Apply به ارث میبرد، در حالی که فرزندی که محدوده خود را تنظیم میکند آن را حفظ میکند، بنابراین میتوانید پاورقی را به تمام صفحات پین کنید در حالی که مهر مورب فقط بخش پیشنویس را پوشش میدهد. و مالکیت از کامپوزیت پیروی میکند: TPDFlibCompositeWatermark.Destroy فرزندان خود را آزاد میکند، بنابراین شما کامپوزیت را یک بار آزاد میکنید و نه چیز دیگری، همانطور که در مثال بالا نشان داده شده است
محدودیتهایی که ارزش دانستن دارند
دو محدودیت واقعی وجود دارد و هر دو از نحوه کارکرد مکانیسم ناشی میشوند. اول اینکه کپچر فقط استریم محتوا و منابع صفحه را بستهبندی میکند. یادداشتها، لینکها، فیلدهای فرم و کامنتها به عنوان اشیاء مجزا در دیکشنری صفحه زندگی میکنند، نه در استریم محتوا، بنابراین به XObject منتقل نمیشوند؛ یک چیدمان بخیه زده شده محتوای قابل مشاهده صفحه را منتقل میکند اما لایه تعاملی منابع خود را به همراه ندارد. دوم اینکه موقعیتدهی متن نه نقطهای یک تقریب است: بدون یک موتور کامل چیدمان، کتابخانه نمیتواند عرض دقیق رندر شده یک رشته را بداند، بنابراین جعبه متن را هنگام حل موقعیتهایی مانند wpCenter در ۴۰ درصد عرض صفحه تخمین میزند. این برای تراز وسط قرار دادن یک برچسب کوتاه به اندازه کافی دقیق است، اما برای قرارگیری دقیق پیکسلی رشتههای طولانی، به wpCustom تغییر وضعیت داده و مختصات را خودتان محاسبه کنید
مقیاس در اینجا یک محدودیت عملی نیست، زیرا کپچر و بخیه زدن به جای کدگذاری مجدد محتوای صفحه، مراجع شیء را دستکاری میکنند؛ اگر منابع شما صدها مگابایت هستند، همان دیسیپلین توضیح داده شده در راهنمای ادغام و تقسیم فایلهای بزرگ با دسترسی مستقیم قبل از بخیه زدن اعمال میشود. توابع بخیه زدن و کلاسهای الگوی واترمارک نشان داده شده در اینجا در نسخه فعلی losLab PDF Library برای دلفی، سیشارپ و VB.NET، در کنار توابع سطح پایینتر CapturePage، DrawCapturedPage و DrawCapturedPageMatrix ارائه میشوند تا زمانی که به چیدمانی نیاز دارید که سه فراخوانی از پیش ساخته شده بخیه زدن پوشش نمیدهند، از آنها استفاده کنید