مقاله فنی

خواندن و نوشتن محتوای نشانه‌گذاری‌شده PDF در Delphi

محتوای نشانه‌گذاری‌شده مکانیزمی است که ISO 32000-1 §14.6 برای نشانه‌گذاری محتوای صفحه تعریف می‌کند، و PDF تگ‌دار و PDF/UA هر دو روی آن ساخته شده‌اند. جزء PDFium آن را مستقیماً افشا می‌کند: PageObjectMarks هر تگ BDC و لیست ویژگی‌اش را از یک شیء صفحه می‌خواند، AddPageObjectMark یکی می‌نویسد، RemovePageObjectMark یکی را حذف می‌کند، و PageObjectMarkedContentID MCID را که محتوا را به درخت ساختار پیوند می‌دهد گزارش می‌دهد

تا زمانی که درخت ساختار بتواند به محتوایی که توصیف می‌کند پیوند داده شود، ابزار دسترس‌پذیری حدس است. درخت ساختار می‌گوید «این یک عنوان است»؛ MCID می‌گوید آن عنوان کدام نشانه‌ها روی کدام صفحه واقعاً هست. هر دو نیمه باید قبل از آنکه یک کاربرد بتواند نشانه‌گذاری را بررسی، تعمیر یا گزارش کند قابل‌خواندن باشند

یک نشانه در بایت چیست؟

یک عملگر BDC با یک نام تگ و یک لیست ویژگی اختیاری، بسته با EMC. در content stream مانند /P <</MCID 3>> BDC ... EMC به‌نظر می‌رسد: تگ /P نقش را نام‌گذاری می‌کند، دیکشنری ویژگی‌ها را حمل می‌کند، و هر چیزی میان عملگرها محتوای نشانه‌گذاری‌شده است. یک شیء صفحه درون آن بازه نشانه را حمل می‌کند، که همان چیزی است PDFium برمی‌گرداند و جزء PDFium آن را به یک رکورد تبدیل می‌کند

TPdfContentMark یک handle، تگ Name، و یک آرایه از TPdfContentMarkParam نگه می‌دارد. هر پارامتر یک Key، یک Kind و یک فیلد مقدار معنادار دارد که توسط آن kind انتخاب شده: pmpInt، pmpFloat، pmpString یا pmpBlob. kind از گزارش نوع خود PDFium می‌آید نه از هر getterی که اتفاقاً موفق شد، که تفاوت میان خواندن یک لیست ویژگی و حدس زدن یکی است

var
  Marks: TPdfContentMarks;
  M: TPdfContentMark;
  P: TPdfContentMarkParam;
  I: Integer;
begin
  Pdf.PageNumber := 1;                    // PageNumber is 1-based
  for I := 0 to Pdf.ObjectCount - 1 do    // page object indexes are 0-based
  begin
    Marks := Pdf.PageObjectMarks(I);
    for M in Marks do
    begin
      Memo1.Lines.Add('mark ' + M.Name +
        ' (MCID ' + IntToStr(Pdf.PageObjectMarkedContentID(I)) + ')');
      for P in M.Params do
        case P.Kind of
          pmpInt:    Memo1.Lines.Add('  ' + P.Key + ' = ' + IntToStr(P.IntValue));
          pmpString: Memo1.Lines.Add('  ' + P.Key + ' = ' + P.StringValue);
          pmpFloat:  Memo1.Lines.Add('  ' + P.Key + ' = ' + FloatToStr(P.FloatValue));
          pmpBlob:   Memo1.Lines.Add('  ' + P.Key + ' = ' +
                       IntToStr(Length(P.BlobValue)) + ' bytes');
        end;
    end;
  end;
end;

چرا pmpUnknown دو معنای متفاوت دارد

pmpUnknown برگردانده می‌شود وقتی PDFium FPDF_OBJECT_UNKNOWN گزارش می‌دهد، و PDFium همچنین آن را برای کلیدی که وجود ندارد برمی‌گرداند. دو مورد را در این لایه نمی‌توان از هم تمایز داد، و وانمود کردن خلافش بدتر از گفتن آن بود

پیامد عملی برای کد شما: pmpUnknown را به‌عنوان «هیچ مقدار قابل‌استفاده‌ای اینجا نیست» در نظر بگیرید نه به‌عنوان نوعی که شاید باز هم decode کنید. اگر یک ویژگی برای workflow شما مهم است، با kindی که می‌شناسید تأیید کنید که موجود است، و عدم‌حضور را از یک unknown استنتاج نکنید — نشانه‌ای که لیست ویژگی‌اش را نمی‌توانید بخوانید نشانه‌ای است که باید روی آن گزارش دهید، نه نشانه‌ای که خاموش بپذیرید

یک رکورد نشانه یک snapshot است، نه handleی که مالک آن هستید

فیلد Handle متعلق به کتابخانه است. لحظه‌ای که نشانه حذف می‌شود، شیء صفحه نابود می‌شود یا صفحه unload می‌شود stale می‌شود، پس رکورد یک snapshot فقط‌خواندنی با کوتاه‌عمر است. آن را سراسر یک تعویض صفحه cache کنید و شما اشارهگری به درون حافظه‌ای نگه داشته‌اید که موتور بازپس‌گرفته

این همان انضباطی است که به‌طور کلی به handleهای شیء صفحه در PDFium اعمال می‌شود، و مردم را در همان مکان می‌گیرد: یک کنترل لیست populated با رکوردهای نشانه، یک کاربر که به صفحه دیگری پیمایش می‌کند، و یک crash که نامرتبط به پیمایش به‌نظر می‌رسد. مقادیری که نیاز دارید را بیرون کپی کنید — نام، کلیدها، اعداد — و handle را رها کنید. یادداشت‌های stale شدن handleهای شیء صفحه پس از یک transform قاعده کلی و نحوه گاز گرفتن در جاهای دیگر را پوشش می‌دهند

افزودن یک نشانه، و مرحله ذخیره که آسان است فراموش شود

AddPageObjectMark اندیس شیء صفحه، یک نام تگ و یک مجموعه پارامتر کامل می‌گیرد. پارامترها به‌عنوان یک مجموعه نوشته می‌شوند نه کلید به کلید وصله‌شده، که چرا TPdfContentMarkParam هیچ sentinel Has* ندارد — مورد «به‌روزرسانی یک فیلد از یک رکورد موجود» که آن‌ها محافظت می‌کردند پیش نمی‌آید

بخش ارزش بیان صریح: افزودن یک نشانه content stream صفحه را بازسازی می‌کند تا تگ از یک ذخیره دوام بیاورد. این باید صریح می‌بود چون SaveAs محتوا را به‌تنهایی بازتولید نمی‌کند — تغییری که فقط در مدل شیء زندگی می‌کرد دور ریخته می‌شد، و فایل ذخیره‌شده دقیقاً مانند آنچه با آن شروع کرده بودید به‌نظر می‌رسید. اگر تا به حال چیزی به یک صفحه PDFium اضافه کرده باشید و در خروجی مفقود یافته‌اید، این معمولاً چرایی است

var
  Params: TPdfContentMarkParams;
begin
  SetLength(Params, 1);
  Params[0].Key := 'MCID';
  Params[0].Kind := pmpInt;
  Params[0].IntValue := NextMcid;
  Pdf.AddPageObjectMark(ObjectIndex, 'P', Params);   // rebuilds the content stream
  Pdf.UpdatePage;
  Pdf.SaveAs('tagged-out.pdf');
end;

این چه چیزی را از یک سند می‌سازد و نمی‌سازد

نشانه‌ها به‌تنهایی یک PDF تگ‌دار نمی‌سازند. یک سند تگ‌دار سازگار به یک درخت ساختار نیاز دارد که عناصرش به این MCIDها ارجاع دهند، یک entry /MarkInfo که سند را نشانه‌گذاری‌شده اعلام کند، و نام نقش که معنای آنچه استاندارد می‌گوید داشته باشد. نوشتن یک نشانه /P با یک MCID که هیچ عنصر ساختاری به آن اشاره نمی‌کند به شما محتوایی می‌دهد که ادعا می‌کند تگ‌دار است و یک درخت ساختار که هرگز آن را ذکر نمی‌کند

جایی که محتوای نشانه‌گذاری‌شده واقعاً در این سطح ارزش خود را نشان می‌دهد بازرسی و تعمیر است: ممیزی کدام اشیاء صفحه تگ‌دار هستند، یافتن artifactهایی که باید به‌عنوان چنین علامت‌گذاری می‌شدند، یا منطبق‌کردن MCIDها در برابر یک درخت ساختار برای یافتن orphanها. برای نیمه درخت ساختار آن کار، مرور اعتبارسنجی درخت ساختار PDF/UA را ببینید، و برای تجربه خواندنی که تگ‌ها در نهایت برای آن هستند، یادداشت‌های ساخت یک خواننده PDF دسترس‌پذیر در Delphi

جزء PDFium به کاربردهای Delphi، C++Builder و Lazarus یک API VCL سطح‌بالا روی موتور PDFium می‌دهد، با محتوای نشانه‌گذاری‌شده، درختان ساختار و اعتبارسنجی دسترس‌پذیری که از کد Pascal معمولی قابل‌دسترس هستند — صفحه محصول جزء PDFium را برای سطح API کامل ببینید