مقاله فنی

جایگزینی صفحات PDF در Delphi بدون شکستن نشانک‌ها

جایگزین‌کردن صفحه ۳ یک قرارداد امضاشده نباید فهرست مطالب را جابه‌جا کند. صفحه قدیمی را حذف کنید، جدیدی را درج کنید، و هر نشانکی که قبلاً به آنجا اشاره می‌کرد اکنون جایی دیگر فرود می‌آید. کتابخانه PDF Library for Delphi Delphi PDF از آن با نگه‌داشتن خود آبجکت صفحه هدف و انتقال فقط مدخل‌هایی که محتوای بصری حمل می‌کنند اجتناب می‌کند

چرا نشانک‌ها پس از جایگزینی یک صفحه PDF می‌شکنند؟

نشانک‌ها می‌شکنند چون یک مقصد PDF یک صفحه را با ارجاع غیرمستقیم آبجکت نام می‌برد، نه با شماره صفحه. ISO 32000-1 §12.3.2.2 یک مقصد صریح را به‌عنوان آرایه‌ای تعریف می‌کند که اولین عنصرش یک ارجاع غیرمستقیم به آبجکت صفحه است. آن آبجکت را حذف کنید و جایگزینی را ضمیمه کنید، و ارجاع آویزان می‌شود: بیشتر نمایشگرها با فرودآوردن خواننده روی صفحه ۱ پاسخ می‌دهند، که دقیقاً همان علامتی است که مردم پس از یک جایگزینی حذف-سپس-درج گزارش می‌کنند. درخت صفحه کاملاً درست به‌نظر می‌رسد، شمارش صفحه درست است، رندرینگ درست است، و کل لایه ناوبری در سکوت اشتباه است

مقصدهای نام‌دار هم شما را نجات نمی‌دهند. §12.3.2.3 یک نام را از راه درخت نام /Dests در فهرست سند مسیریابی می‌کند، اما برگی که آن نام به آن حل می‌شود همچنان یک آرایه مقصد صریح است که همان ارجاع صفحه را نگه می‌دارد. نام‌گذاری یک لایه واسط بالای ارجاع صفحه اضافه می‌کند، نه پیرامونش. همین استدلال بقیه لایه تعاملی توصیف‌شده در §12.5 را می‌پوشاند: یک حاشیه‌نویسی لینک یک /Dest یا یک عمل GoTo /A حمل می‌کند که /Dاش همان آرایه است، هر حاشیه‌نویسی ممکن است یک مدخل /P حمل کند که یک ارجاع غیرمستقیم به صفحه‌اش است، و یک ویجت فیلد فرم دقیقاً روی همان بنیان یک حاشیه‌نویسی است. یک تعویض ساده‌لوح صفحه چهار زیرسیستم را یکجا جدا می‌کند، و اگر می‌خواهید آن‌ها را روی یک فایل واقعی شمارش‌شده ببینید، همان گراف آبجکت چیزی است که درون‌نگری outline، حاشیه‌نویسی و عمل می‌پیماید

نمودار مقایسه PDF Library for Delphi: مقصد نشانک نام‌دار با ارجاع غیرمستقیم که جایگزینی صفحه درجا را تاب می‌آورد اما پس از جابه‌جایی حذف-و-الحاق بی‌مرجع می‌شود
مقاصد، طرح‌ها و لینک‌ها و ویجت‌ها را به شماره شیء صفحه می‌بندند، پس تغییر همان شیء در جا ناوبری را زنده نگه می‌دارد، جایی که حذف-سپس-درج خواننده‌ها را به صفحه 1 می‌اندازد

کدام مدخل‌های صفحه هویت حمل می‌کنند و کدام ظاهر

یک دیکشنری صفحه دو نوع مدخل را مخلوط می‌کند، و یک جایگزینی درجا دقیقاً وقتی موفق می‌شود که آن‌ها را جدا کنید. سمت ظاهر محدود و شمارش‌پذیر است: /Contents، /Resources، پنج جعبه صفحه /MediaBox، /CropBox، /BleedBox، /TrimBox و /ArtBox، به‌علاوه /Rotate، /Group، /UserUnit و /BoxColorInfo. آن یازده مدخل هرچیزی را که یک رستریزکننده برای صفحه تولید می‌کند تعیین می‌کنند، و هیچ‌چیز دیگری در فایل با نام به آن‌ها اشاره نمی‌کند

سمت هویت چیزی است که بقیه سند خودش را به آن مقید کرده: شماره آبجکت و نسل صفحه، پیوند بازگشتی /Parent به درخت صفحه، و /Annots. PDF Library for Delphi هرکدام از آن‌ها را دست‌نخورده نگه می‌دارد. ReplacePageRanges یازده مدخل بصری را از دیکشنری صفحه هدف پاک می‌کند و دوباره از صفحه منبع وارد‌شده اضافه می‌کند، پس آبجکت صفحه هدف درجا تغییر می‌یابد نه جایگزین. ساختار درخت صفحه الزام‌شده توسط §7.7.3 نیز بایت‌به‌بایت هم‌شکل می‌ماند: ترتیب /Kids، /Count، و هر /Parent باقی‌مانده پیش و پس یکسان است، چون هیچ گره هرگز قطع نشده

PDF Library for Delphi چگونه یک صفحه را بدون شماره‌گذاری‌مجدد آبجکت‌ها جایگزین می‌کند؟

این فراخوانی یک سند منبع، یک صفحه شروع هدف ۱-پایه، یک عبارت بازه منبع، و یک پرچم گزینه می‌گیرد. هر دو سند باید در همان نمونه باز باشند، و سند هدف آنی است که انتخاب شده. چون شمارش صفحه هدف هرگز تغییر نمی‌کند، بازه‌ای که درخواست می‌کنید باید درون سند شروع‌شده در TargetStartPage جا بگیرد، و آن پیش از ساختن هرچیز بررسی می‌شود

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    // سندی که نشانک‌ها و لینک‌هایش باید سالم بمانند
    if Lib.LoadFromFile('contract-final.pdf', '') <> 1 then
      Exit;
    TargetDoc := Lib.SelectedDocument;

    // صفحه بند بازنگری‌شده، رندرشده توسط هر چیزی که آن را تولید کرده
    if Lib.LoadFromFile('clause-7-revised.pdf', '') <> 1 then
      Exit;
    SourceDoc := Lib.SelectedDocument;

    Lib.SelectDocument(TargetDoc);
    // صفحه 1 منبع ظاهر صفحه 3 هدف را رونویسی می‌کند.
    // شمارش صفحه، شماره آبجکت صفحه 3، نشانک‌ها و حاشیه‌نویسی‌ها حفظ می‌شوند.
    if Lib.ReplacePageRanges(SourceDoc, 3, '1', 0) = 1 then
      Lib.SaveToFile('contract-final.pdf');
  finally
    Lib.Free;
  end;
end;

در سطح داخلی، صفحات منبع نمی‌توانند به‌سادگی در سراسر مرز سند خوانده شوند، چون هر ارجاع غیرمستقیم درونشان به شماره‌گذاری آبجکت منبع تعلق دارد. پس بازه منبع ابتدا به روش معمول وارد می‌شود، به‌عنوان صفحات موقت ضمیمه‌شده پس از آخرین صفحه واقعی، که کل نگاشت‌مجدد گراف آبجکت را اجرا می‌کند: جریان‌های محتوا، فونت‌ها، XObjectها، شیدینگ‌ها و فضاهای رنگ همه به سند هدف شماره‌گذاری‌مجدد می‌شوند. فقط پس از آن یازده مدخل بصری از هر صفحه موقت روی صفحه هدفش کپی می‌شوند، و فقط پس از آن صفحات موقت از درخت صفحه جدا می‌شوند. کار نگاشت‌مجدد جایی اتفاق می‌افتد که ارزان و امن است، و ویرایش مخرب به یک تعویض سطح-دیکشنری روی صفحاتی که از قبل وجود دارند کاهش می‌یابد

مسیر حذفی که آنچه تازه منتقل کردید را نابود می‌کرد

حذف آن صفحات موقت گامی است که ساده به‌نظر می‌رسد و نیست. مسیر حذف صفحه معمولی در کتابخانه بیش از قطع یک گره انجام می‌دهد: لایه‌های هر صفحه در‌حال‌حذف را ترکیب می‌کند، اولین جریان محتوا را خالی می‌کند، و منابعی که هیچ صفحه دیگری به اشتراک نگذاشته را بازپس می‌گیرد. آن رفتار درستی برای یک حذف واقعی است، و اینجا فاجعه‌بار، چون در زمانی که صفحات موقت حذف می‌شوند، صفحات هدف از قبل دقیقاً همان جریان‌های محتوا و آبجکت‌های منبع را ارجاع می‌دهند. خالی‌کردنشان صفحه‌ای را که تازه جایگزین کردید سفید می‌کند، و جاروب منابع فونت‌ها و تصاویری را جمع‌آوری می‌کند که اکنون یک مالک زنده دارند

رفع مشکل یک حالت حفظ-آبجکت‌های-ارجاع‌شده روی مسیر حذف داخلی است. وقتی ست شده باشد، حذف هم جاروب منابع غیرمشترک و هم پاک‌سازی جریان محتوا را رد می‌کند، و هیچ کاری جز جداکردن صفحات از درخت صفحه و تعمیر دفترداری درخت انجام نمی‌دهد. آبجکت‌های منتقل‌شده با مالک جدید جان سالم به‌در می‌برند، و مالکیت آبجکت پس از عملیات چیزی است که روی یک تخته سفید می‌کشید: یک جریان محتوا، یک صفحه مالک، یک شماره آبجکت که هرگز حرکت نکرده. قواعد چرخه حیات مرتبط برای ساخت، حذف و بازترتیب صفحات در یادداشت‌های عملیات چرخه حیات سند و صفحه جداگانه پوشش داده شده‌اند

PDF Library for Delphi: کالبدشکافی دیکشنری صفحه با جداسازی ورودی‌های هویتی که فایل به آن‌ها وابسته است از یازده ورودی بصری که ReplacePageRanges از صفحه مبدأ واردشده جابه‌جا می‌کند
ReplacePageRanges یازده کلید بصری را پاک و از ایمپورت دوباره می‌افزاید، در حالی که شماره شیء و generation و /Parent و /Annots دقیقاً همان‌طور که بودند می‌مانند

ترتیب، تکرارها، و شکست همه-یا-هیچ

پرچم گزینه انتخاب می‌کند بازه منبع چگونه تفسیر شود. 0 شماره صفحات تجزیه‌شده را مرتب می‌کند و تکرارها را حذف می‌کند، که پیش‌فرض عاقلانه است وقتی فراخوان‌کننده چیزی مانند '4-6,2' را می‌گذرد و به‌سادگی یعنی آن چهار صفحه. 1 ترتیبی که نوشته‌اید را حفظ می‌کند و اجازه می‌دهد یک صفحه تکرار شود، پس '2,1,2' واقعاً یعنی سه جایگزینی گرفته‌شده از دو صفحه منبع. اعتبارسنجی ابتدا اجرا می‌شود و کاملاً اجرا می‌شود: نحو بازه، هر شماره صفحه علیه شمارش صفحه منبع، خود مقدار گزینه، و ظرفیت هدف همه پیش از ساخته‌شدن یک آبجکت منفرد بررسی می‌شوند. یک فراخوانی ردشده LastErrorCode را به ۴۱۲ ست می‌کند، صفحه پیش‌تر انتخاب‌شده را بازمی‌گرداند، و سند را دقیقاً همان‌طور که بود رها می‌کند

PDF Library for Delphi: جریان سه‌مرحله‌ای ReplacePageRanges: وارد کردن موقت با بازنگاشت شیءها، کپی ورودی‌های بصری، و جدا کردن با حفظ ارجاع‌شده‌ها که منابع منتقل‌شده را دست‌نخورده می‌گذارد
ایمپورت منبع به‌عنوان صفحات موقت اجازه می‌دهد نگاشت مجدد عادی اول اجرا شود، پس ویرایش مخرب به کپی کلیدهای بصری و جداکردن گره‌ها کوچک می‌شود بدون بازپس‌گیری منابع زنده
var
  Replaced: Integer;
begin
  Lib.SelectDocument(TargetDoc);
  // Options = 1: ترتیب منبع حفظ می‌شود و تکرارها مجازند، پس
  // صفحات هدف 5، 6 و 7 به‌ترتیب صفحات منبع 2، 1 و 2 را دریافت می‌کنند
  Replaced := Lib.ReplacePageRanges(SourceDoc, 5, '2,1,2', 1);
  if Replaced = 0 then
    raise Exception.CreateFmt('Replacement rejected, LastErrorCode = %d',
      [Lib.LastErrorCode]);
  // در صورت موفقیت، انتخاب اولین صفحه جایگزین‌شده است
  Assert(Lib.SelectedPage = 5);
end;

اتمی‌بودن فراتر از اعتبارسنجی تا خود انتقال گسترش می‌یابد. پیش از آنکه اولین صفحه منبع وارد شود، یازده مدخل بصری هر صفحه هدف در بازه به‌عنوان مقادیر رمزگذاری‌شده اسنپ‌شات می‌شوند. اگر واردکردن شکست بخورد، یا شمارش صفحه وارد‌شده با آنچه درخواست شده مطابقت نداشته باشد، اسنپ‌شات‌ها به‌عقب روی صفحات هدف رمزگشایی می‌شوند و صفحات موقت حذف می‌شوند، پس یک شکست میان‌راه همچنان ظاهرهای اصلی را روی آبجکت‌های اصلی‌شان جا می‌گذارد. این بیشتر از آنچه به‌نظر می‌رسد اهمیت دارد: یک بازه صفحه نیمه‌جایگزین‌شده در یک قرارداد بدتر از یک فراخوانی شکست‌خورده است، چون هیچ‌چیز در فایل آن را به‌عنوان نیمه‌انجام‌شده علامت نمی‌زند

// پساشرط‌هایی که ارزش assert کردن در یک تست رگرسیون را دارند
Lib.SelectPage(3);
// هندسه اکنون از صفحه منبع می‌آید
WriteLn(Format('%.2f x %.2f', [Lib.PageWidth, Lib.PageHeight]));
// حاشیه‌نویسی‌هایی که قبلاً روی صفحه 3 هدف بودند همچنان متصل‌اند
WriteLn(Lib.AnnotationCount);
// نشانکی که پیش از جایگزینی ساخته شده هنوز به صفحه 3 حل می‌شود
WriteLn(Lib.GetOutlinePage(OutlineID));
// و سند همچنان همان طول را دارد
WriteLn(Lib.PageCount);

جایگزینی درجا هنوز چه چیزی برایتان انجام نمی‌دهد؟

حاشیه‌نویسی‌های منبع، فیلدهای فرم منبع و outline های منبع عمداً وارد نمی‌شوند. آوردن یک ویجت بدون مدخل فیلد /AcroForm اش، یا یک حاشیه‌نویسی حامل محتوای علامت‌گذاری‌شده بدون مالکیت درخت ساختارش، یک آبجکت تعاملی نیمه‌وارد‌شده تولید می‌کند که هیچ نمایشگری نمی‌تواند درباره‌اش استدلال کند، پس عملیات فقط ظاهر را منتقل می‌کند. پیامد عملی این است که اگر صفحه جایگزین قرار است فیلدهای فرم جدید یا لینک‌های جدید حمل کند، آن‌ها را بعداً به صفحه هدف اضافه می‌کنید، علیه آبجکت صفحه هدفی که همچنان آنجا نشسته منتظرشان است

دو مرز دیگر ارزش بررسی روی فایل‌های خودتان را دارند. اول، /Annots حفظ می‌شود اما هندسه صفحه نه، پس جایگزینی یک صفحه ۲۲۰میلی‌متری با یک صفحه ۳۲۰میلی‌متری مستطیل‌های حاشیه‌نویسی را در مختصات قدیمی‌شان درون یک /MediaBox با اندازه متفاوت نگه می‌دارد؛ اگر هندسه تغییر می‌کند، حاشیه‌نویسی‌هایی که نگه داشتید را جای‌گذاری‌مجدد کنید. دوم، مدخل‌های بیرون از یازده کلید بصری با طراحی با صفحه هدف می‌مانند، که برای /Trans یا /AA درست است و برای /Thumb کهنه، پس پس از یک جایگزینی تصاویر کوچک را بازتولید کنید. اسناد برچسب‌گذاری‌شده یک فکر اضافی نیاز دارند: عناصر ساختار همچنان از راه /Pg به آبجکت صفحه درست اشاره می‌کنند، اما شناسه‌های محتوای علامت‌گذاری‌شده‌شان محتوایی را توصیف می‌کنند که دیگر آنجا نیست، پس یک تعویض صفحه درون یک گردش‌کار PDF/UA یک ویرایش درخت ساختار است هم به‌اندازه یک ویرایش محتوا. اگر کار شما واقعاً ترکیب است نه تعویض، لایه‌گذاری اثر هنری روی صفحاتی که نگه می‌دارید، رویکرد دوخت صفحه و الگو ابزار ارزان‌تری است

هرچیز شرح‌داده‌شده در اینجا، شامل نحو عبارت بازه، مقادیر گزینه و API دستکاری صفحه پیرامون، در کتابخانه PDF Library for Delphi Delphi PDF استاندارد برای Delphi و C++Builder ارائه می‌شود، که مستندات مرجعش مدخل کامل برای فراخوانی جایگزینی صفحه و کدهای خطایش را در بر دارد