مقاله فنی

یادداشت‌گذاری‌های نشانه‌گذاری متن با PDFium QuadPoints در دلفی

کامپوننت PDFium یادداشت‌گذاری‌های نشانه‌گذاری متن (یعنی هایلایت، زیرخط، خط‌خوردگی و موج‌دار) را از طریق متد TPdf.CreateAnnotation ایجاد می‌کند: شما مقدار HasAttachmentPoints := True را در رکورد TPdfAnnotation تنظیم می‌کنید و چهارضلعی AttachmentPoints آن را پر می‌نمایید، و کامپوننت ورودی QuadPoints تعریف‌شده در استاندارد ISO 32000-1 §12.5.6.10 را می‌نویسد. این کل سطح API است. دلیل وجود این مقاله اتفاقاتی است که در زیر آن رخ می‌دهد، زیرا زنجیره فراخوانی خام PDFium دارای حالت شکستی است که کم‌فایده‌ترین علامت را در جعبه‌ابزار تولید می‌کند: متد FPDFAnnot_SetAttachmentPoints در یک یادداشت‌گذاری تازه ایجاد شده، هر بار مقدار نادرست را بدون هیچ کد خطا یا راهنمایی برمی‌گرداند. این مقاله مکمل بخش ایجاد برای مقاله ما در مورد خواندن و بازبینی یادداشت‌گذاری‌های موجود است، که در جهت دیگر از طریق همان ساختارها حرکت می‌کند

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

مفهوم QuadPoints در استاندارد ISO 32000-1

ویژگی QuadPoints آرایه‌ای از اعداد 8×n است که n چهارضلعی را توصیف می‌کند و استاندارد ISO 32000-1 §12.5.6.10 آن را در هر یادداشت‌گذاری نشانه‌گذاری متن الزامی می‌داند: هر چهارضلعی یک کلمه یا گروهی از کلمات متوالی را مشخص می‌کند که هایلایت، زیرخط یا خط‌خوردگی روی آن‌ها اعمال می‌شود. ورودی Rect یادداشت‌گذاری همچنان وجود دارد، اما برای ساب‌تایپ‌های نشانه‌گذاری، این ورودی فقط محدوده را مشخص می‌کند؛ چهارضلعی‌ها چیزی هستند که رندرکننده در واقع ترسیم می‌نماید. استفاده از چهارضلعی به جای مستطیل به این دلیل است که متن می‌تواند چرخیده یا کج شده باشد، بنابراین چهار گوشه به عنوان چهار نقطه مستقل ذخیره می‌شوند: x1 y1 x2 y2 x3 y3 x4 y4

ترتیب آن چهار نقطه جایی است که مشخصات استاندارد و نسخه‌های نصب‌شده در بازار از هم جدا می‌شوند. متن مشخصات نقاط را به عنوان رسم چهارضلعی در جهت پادساعتگرد توصیف می‌کند، اما رندرکننده خود شرکت ادوبی همیشه آن‌ها را به صورت الگوی Z تفسیر کرده است: ابتدا لبه بالایی از چپ به راست، سپس لبه پایینی از چپ به راست. از آنجا که هر نویسنده‌ای خروجی خود را در برابر آکروبات آزمایش می‌کرد، در عمل هر رندرکننده‌ای، از جمله PDFium، الگوی Z را دنبال می‌نماید و فایل‌هایی که از متن صریح مشخصات پیروی می‌کنند، در برخی از نمایشگرها به صورت هایلایت‌های درهم‌ریخته یا پیچ‌خورده رندر می‌شوند. ساختار FS_QUADPOINTSF در PDFium دقیقاً این قرارداد را کدگذاری می‌کند: (x1,y1) گوشه بالا سمت چپ، (x2,y2) بالا سمت راست، (x3,y3) پایین سمت چپ و (x4,y4) پایین سمت راست، در مختصات صفحه که در آن Y به سمت بالا رشد می‌کند. این ترتیب را دنبال کنید؛ رندرکننده‌ها در مورد خیلی چیزها ملایم رفتار می‌کنند، اما یک چهارضلعی درهم‌ریخته یکی از آن‌ها نیست

چرا FPDFAnnot_SetAttachmentPoints مقدار false را برمی‌گرداند؟

متد FPDFAnnot_SetAttachmentPoints در یک یادداشت‌گذاری جدید با شکست مواجه می‌شود زیرا پیمان آن جایگزینی چهارضلعی در یک اندیس داده شده است، و یک یادداشت‌گذاری تازه ایجاد شده دارای صفر چهارضلعی برای جایگزینی است. امضا یک هندل یادداشت‌گذاری، یک quad_index و نقاط را می‌پذیرد؛ اندیس 0 به معنای "اولین اسلات، ایجاد آن در صورت نیاز" نیست، بلکه به معنای "چهارضلعی موجود شماره 0" است، و هنگامی که FPDFAnnot_CountAttachmentPoints مقدار 0 را گزارش می‌کند، چنین چهارضلعی وجود ندارد و فراخوانی مقدار نادرست را برمی‌گرداند. تابعی که یک اسلات ایجاد می‌کند FPDFAnnot_AppendAttachmentPoints است. هر یادداشت‌گذاری ایجاد شده از طریق FPDFPage_CreateAnnot با تعداد صفر شروع می‌شود، بنابراین مسیر ایجاد ابتدا باید Append را فراخوانی کند و تنها به‌روزرسانی‌های بعدی می‌توانند Set را فراخوانی نمایند

این موضوع خود کامپوننت PDFium را تحت تاثیر قرار داد. تا نسخه v1.79.0، روال داخلی مشترک بین CreateAnnotation and SetAnnotation متد FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...) را به صورت هاردکد داشت، که برای به‌روزرسانی یک یادداشت‌گذاری موجود درست بود و تضمین می‌شد که برای یک یادداشت‌گذاری جدید با شکست مواجه شود و به عنوان یک EPdfException با پیام 'Cannot set attachment points' ظاهر گردد. اصلاحیه که در نسخه v1.79.1 ارائه شد، بر اساس تعداد شاخه‌بندی می‌کند

// در داخل نویسنده یادداشت‌گذاری کامپوننت (نسخه v1.79.1+):
// یک یادداشت‌گذاری جدید هنوز اسلات چهارضلعی ندارد، بنابراین متد Append
// اولین اسلات را می‌سازد؛ متد Set فقط اسلاتی را که از قبل وجود دارد جایگزین می‌کند
if FPDFAnnot_CountAttachmentPoints(Annotation) = 0 then
  Check(FPDFAnnot_AppendAttachmentPoints(Annotation, QuadPoints) <> 0,
    'Cannot set attachment points')
else
  Check(FPDFAnnot_SetAttachmentPoints(Annotation, 0, QuadPoints) <> 0,
    'Cannot set attachment points');

اگر توابع صادرشده C را مستقیماً فراخوانی کنید، همین الگو اعمال می‌شود، که کامپوننت به شما اجازه می‌دهد این کار را انجام دهید زیرا تمام نقاط ورود FPDFAnnot_* در PDFium.pas ارائه شده‌اند. هر زمان که یک هندل FPDF_ANNOTATION را نگه می‌دارید و می‌خواهید چهارضلعی‌ها را بنویسید، ابتدا از FPDFAnnot_CountAttachmentPoints بپرسید و بر این اساس مسیر را مشخص کنید. اگر عبارت "FPDFAnnot_SetAttachmentPoints returns false" را جستجو می‌کنید، این شاخه بررسی تعداد و سپس Append تقریباً قطعاً پاسخ شماست

ایجاد هایلایت با TPdf.CreateAnnotation

با وجود اینکه کامپوننت کار هدایت بین Append و Set را برای شما انجام می‌دهد، ایجاد هایلایت به پر کردن یک رکورد خلاصه می‌شود. مثال زیر یک صفحه A4 ایجاد می‌کند و یک هایلایت زرد نیمه‌شفاف را روی یک ناحیه 200×20 نقطه‌ای قرار می‌دهد؛ توجه داشته باشید که چهارضلعی از الگوی Z که در بالا توضیح داده شد پیروی می‌کند، و Rectangle طوری تنظیم شده است که چهارضلعی را در بر گیرد، که باعث می‌شود نمایشگرهایی که بر اساس Rect تست کلیک انجام می‌دهند، رفتار معقولی داشته باشند

var
  Pdf: TPdf;
  A: TPdfAnnotation;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;
    Pdf.AddPage(0, 595, 842);

    FillChar(A, SizeOf(A), 0);
    A.Subtype := anHighlight;
    A.HasColor := True;
    A.Color := clYellow;
    A.ColorAlpha := $80;                     // 50% شفافیت
    A.HasAttachmentPoints := True;
    A.AttachmentPoints[1].X := 50;  A.AttachmentPoints[1].Y := 700; // بالا چپ
    A.AttachmentPoints[2].X := 250; A.AttachmentPoints[2].Y := 700; // بالا راست
    A.AttachmentPoints[3].X := 50;  A.AttachmentPoints[3].Y := 680; // پایین چپ
    A.AttachmentPoints[4].X := 250; A.AttachmentPoints[4].Y := 680; // پایین راست
    A.Rectangle.Left := 50;  A.Rectangle.Top := 700;
    A.Rectangle.Right := 250; A.Rectangle.Bottom := 680;
    A.ContentsText := 'Highlighted region';
    Pdf.CreateAnnotation(A);

    Pdf.SaveAs('highlighted.pdf');
  finally
    Pdf.Free;
  end;
end;

تغییر ساب‌تایپ‌ها فقط یک خط هزینه دارد. مقادیر anUnderline، anStrikeout و anSquiggly شکل رکورد، چهارضلعی‌ها و همه موارد مشابه را به طور یکسان دریافت می‌کنند، زیرا استاندارد ISO 32000-1 با هر چهار مورد به عنوان یک خانواده یادداشت‌گذاری رفتار می‌کند که فقط در نحوه تزیین ناحیه چهارضلعی تفاوت دارند. ساب‌تایپ‌هایی که نشانه‌گذاری متن نیستند، مانند anSquare، anCircle و anText، موقعیت خود را تنها از روی Rectangle مشخص می‌کنند;مقدار HasAttachmentPoints را برای آن‌ها روی False بگذارید تا مکانیزم چهارضلعی هرگز اجرا نشود

چرا AttachmentPoints[0] در دلفی کامپایل می‌شود اما در FPC با شکست مواجه می‌گردد؟

ساختار TQuadrilateralPoint به صورت array [1..4] of TPdfPoint تعریف شده است که یک آرایه با مبنای 1 است، و این موضوع هر کسی را که انگشتانش به طور پیش‌فرض روی اندیس‌گذاری مبنای صفر کار می‌کنند غافلگیر می‌نماید. اگر بنویسید A.AttachmentPoints[0]، کامپایلر dcc32 دلفی بدون هیچ شکایتی آن را کامپایل می‌کند، زیرا بررسی محدوده (range checking) به طور پیش‌فرض خاموش است؛ در زمان اجرا این عبارت به آرامی حافظه قبل از آرایه را می‌خواند یا می‌نویسد، که در رکورد TPdfAnnotation یک فیلد مجاور است. هایلایت شما یک گوشه زباله دریافت می‌کند، یا یک فیلد مجاور خراب می‌شود، و هیچ خطایی صادر نخواهد شد. کامپایلر Free Pascal دقیقاً این باگ را در منابع دموهای خودمان در حین پورت برای Lazarus شناسایی کرد: fpc بررسی محدوده زمان کامپایل را روی اندیس‌های ثابت انجام می‌دهد و عبارت AttachmentPoints[0..3] را کاملاً رد کرد، که بدین ترتیب باگ اختلاف یک واحدی و باگ کتابخانه در مورد Set در برابر Append با هم کشف شدند

دو عادت حاصل می‌شود: اندیس چهارضلعی را از 1 تا 4 تنظیم کنید، متناسب با ترتیب گوشه‌ها در کد بالا، و کد یادداشت‌گذاری خود را حداقل یک بار با بررسی محدوده فعال، یا از طریق {$R+} در دلفی یا هر بیلد fpc کامپایل کنید، قبل از اینکه به آن اعتماد نمایید. اجرای موفقیت‌آمیز پیش‌فرض dcc32 دلیلی بر صحت اندیس‌ها نیست؛ بلکه فقط دلیلی است بر اینکه هیچ چیز روی حافظه‌ای که به طور تصادفی در آنجا قرار داشت کرش نکرده است

به دست آوردن مختصات چهارضلعی از متن واقعی

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

یک مرز صادقانه: رکورد TPdfAnnotation حاوی یک TQuadrilateralPoint واحد است، بنابراین یک فراخوانی CreateAnnotation یک چهارضلعی را می‌نویسد. یک انتخاب که سه خط را پوشش می‌دهد به سه چهارضلعی نیاز دارد، یک عدد برای هر خط، مطابق با استاندارد §12.5.6.10, و شما دو راه برای رسیدن به آن دارید. راه ساده، یک یادداشت‌گذاری برای هر خط است، که در همه‌جا به درستی رندر می‌شود و سطح API کامپوننت را حفظ می‌کند. راه فشرده، یک یادداشت‌گذاری حاوی سه چهارضلعی است، که به معنای ایجاد یادداشت‌گذاری از طریق کامپوننت و سپس فراخوانی FPDFAnnot_AppendAttachmentPoints صادرشده توسط خودتان برای چهارضلعی دوم و سوم است، که این کار دقیقاً به این دلیل انجام می‌شود که Append اسلات ایجاد می‌کند به جای جایگزینی آن‌ها. سعی نکنید از طریق فراخوانی‌های مکرر SetAttachmentPoints به حالت چند چهارضلعی برسید؛ هر اندیس فراتر از تعداد فعلی صرفاً مقدار نادرست را برمی‌گرداند، به همان دلیلی که اندیس 0 در یادداشت‌گذاری تازه انجام داد

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