مقال تقني

إصلاح النص المكرر الناتج عن Bold الوهمي في استخراج PDF

يزيل HotPDF Delphi Component، منذ الإصدار v2.768.15، المحارف المكررة التي يتركها Bold الوهمي والشعارات المطبوعة فوقها في نص PDF المستخرج. عندما يرسم المولّد المحرف نفسه مرتين بمسافة جزء من النقطة، تقرأ ExtractLoadedPageText و ExtractLoadedPageTextLayout و ExtractLoadedPageTextColumns ذلك المحرف الآن مرة واحدة، فلم تعد "كلمات Bold" تخرج على هيئة "كلمات Boldكلمات Bold"

هذا واحد من تلك الخلل التي تبدو فشلاً في الفك وليست كذلك. كل محرف يُفك بشكل صحيح؛ فالنص موجود على الصفحة مرتين فعلاً. وعلى الإصلاح أن يقرر أي محرفَين صالحَين تماماً هو النسخة، وأن يفعل ذلك دون أن يلتهم حروفاً متكررة مشروعة مثل l المزدوجة في "fill" أو خلايا الجدول المتطابقة. تشرح بقية هذه المقالة القاعدة التي يستخدمها HotPDF، والحدود خلفها، وأين يختلف عمداً عن أدوات الاستخراج الأخرى، وخللاً ثانياً غير ذي صلة من قطار الإصدارات نفسه كان يبتُّ النص من نهايات الأسطر أيضاً

لماذا ينتج Bold الوهمي نصاً مضاعفاً في استخراج PDF؟

يضاعف Bold الوهمي النص المستخرج لأن المولّد يرسم السلسلة مرتين وكلتا النسختين عمليتا عرض نص حقيقيّتان. عندما لا تمتلك عائلة خطوط وجه bold، أو حين لا يكلّف أداة التأليف نفسها بذلك، يحاكي كثير من المولّدات الـ bold بعرض السلسلة نفسها عبر Tj أو TJ، ثم يدفع موضع النص يميناً ببضعة أعشار النقطة، ثم يعرضها مرة أخرى. على الشاشة تندمج النسختان في ضربة أسمك. وفي تدفق المحتوى يوجد جريان كاملان من أكواد المحارف، ولا يعطي ISO 32000-1 §9.4 أداة الاستخراج أي علم يقول "هذه نسخة زخرفية"

الشعارات المطبوعة فوقها تصطدم بالمشكلة نفسها من زاوية أخرى. قد يُرسم الشعار مرة بوصفه تعبئة ومرة بوصفه تحديد أو ظلاً منزاحاً قليلاً، بالخط نفسه والمحارف نفسها. أداة استخراج ساذجة تسير في تدفق المحتوى بترتيب الرسم تُصدر الجريان الأول كاملاً ثم الثاني كاملاً، وهذا سبب قراءة النتيجة "كلمات Boldكلمات Bold" لا "BBoolldd". قبل v2.768.15 كان تجميع نص HotPDF يُبقي النسختين، وكان التكرار يكسر الشيء الذي يهمه معظم المستدعين فعلاً: البحث عن "كلمات Bold" عبر نص الصفحة قد يفشل لأن حد الكلمة بين الجريانين قد زال

وضع عرض النص الحقيقي 2 (تعبئة ثم تحديد، ISO 32000-1 §9.3.6) لا يعاني هذه المشكلة، لأنه عملية عرض نص واحدة تنتج مجموعة محارف واحدة. التكرارات لا تظهر إلا عندما يُصدر المولّد النص مرتين

كيف يقرر HotPDF أي محرف هو المكرر؟

يعامل HotPDF محرفَين بوصفهما محرفاً مرسوماً واحداً عندما يحملان قيمة Unicode نفسها، ولديهما اتجاه كتابة واحد، ويكونان ضمن 5% من بعضهما في المقاس، ويجلسان فوق بعضهما تقريباً: بفارق لا يتجاوز 0.1 من مقاس المحرف على طول خط الأساس ولا يتجاوز 0.2 منه عبره. ومن كل زوج كهذا، يُحفظ المحرف المرسوم أولاً ويُسقط الأحدث. تلك هي القيم في تنفيذ v2.768.15، ولكل منها سبب

  • Unicode نفسه، اتجاه واحد. المحارف المتطابقة وحدها يمكن أن تكون مكررة، والمحرف الأفقي لا يقارن أبداً برأسي (WMode 1)
  • مقاس ضمن 5%. حرف عنوان كبير وحرف تسمية توضيحية صغير يتزامن تداخلهما ليسا النص نفسه، فعدم تطابق المقاس يستبعد الزوج
  • 0.1 من المقاس على طول خط الأساس. إزاحات Bold الوهمي بضعة أعشار النقطة، أي بضعة بالمئة من مقاس النص العادي. حرفان حقيقيان متجاوران، حتى "ll" متراصة التشكيل، يفصلهما عرض تقدم كامل تقريباً، وهو أمر أكبر من هذا التسامح بمقدار رتبة
  • 0.2 من المقاس عبر خط الأساس. الظلال ومرات التحديد كثيراً ما تنزح لأسفل إضافة إلى اليمين، فالتسامح العمودي أوسع، لكنه ما يزال أدنى بكثير من المسافة إلى السطر التالي
  • الأول في الرسم يفوز. إبقاء المحرف الأسبق يحفظ ترتيب قراءة الجريان الأول، فيبقى النص الباقي يسير كما كتبه المولّد

تُقاس المسافات في إطار خط الأساس الخاص بالمحرف نفسه، لا في X وY الخامين للصفحة، فالنص المدوَّر والنص على خط أساس مائل يستخدمان التسامحات نفسها المستخدمة للأسطر الأفقية العادية. والمقاس المستخدم هو مقاس المحرف كما رُسم على الصفحة، مشتق من رباعيه (quad)، لا معامل المقاس المعطى إلى Tf؛ وهذا مهم للمستندات التي تضبط Tf على 1 وتكبّر كل شيء عبر مصفوفة النص، حيث كان مقاس الخط الخام سيلغي كل التسامحات

المسافات ليست مرشحة أبداً. نقاط الكود حتى U+0020، والمسافة غير الكاسرة U+00A0، والمسافة الطرية (ideographic) U+3000 تُتخطى، لأن محارف الفراغ لا تحمل حبراً مرئياً ليتكرر، وإسقاط أحدها سيدمج الكلمات

إبقاء الفحص سريعاً على الصفحات الكثيفة

فحص التكرار في HotPDF يعمل بزمن يقارب n log n، لا n تربيع، لأنه يرتّب أولاً ولا يقارن إلا المحارف داخل نافذة صغيرة. مقارنة كل محرف بكل محرف آخر صحيحة وغير صالحة للاستعمال: صفحة كثيفة بخط صغير تحمل بسهولة عشرات آلاف المحارف، والفحص يعمل مع كل استدعاء استخراج نص

يرتب الروتين الداخلي المحارف غير الفراغية بقيمة Unicode، ثم بأصل X، ثم بترتيب الرسم، مستخدماً فرز دمج مستقراً. بعد الترتيب، يجلس كل مكرر محتمل لمحرف بعده مباشرة في الترتيب، فيمشي المسح إلى الأمام فقط ما دامت قيمة Unicode متطابقة وفرق X يبقى ضمن 0.25 من مقاس المحرف. نافذة الـ 0.25 أوسع عمداً من تسامح الـ 0.1 على طول خط الأساس، حتى يهبط الزوج على خط أساس مائل قليلاً داخلها قبل تشغيل اختبار إطار خط الأساس الدقيق. حالة دقيقة واحدة: إذا قابل المسح انطلاقاً من المحرف A محرفاً مطابقاً رُسم قبل A، فA هو النسخة، فيُسقط ويتوقف المسح، تاركاً المحرف الأسبق يكمل المقارنة مع ما هو أبعد. تلك القاعدة هي ما يجعل "الأول في الرسم يفوز" صائباً مع الركلات الثلاثية كما مع الأزواج

المقارنة على قيمة Unicode، لا على الخط أو كود المحرف. محرفان يؤولان إلى Unicode نفسه عبر خطين مختلفين، كجريان تعبئة في مجموعة فرعية مضمّنة وجريان تحديد في مجموعة أخرى، يُعرفان بوصفهما محرفاً واحداً مع ذلك. والوجه الآخر أن محرفاً يؤول كوده إلى Unicode بقيمة 0 لا شيء موثوق فيه للمقارنة، وهو سبب إضافي لمتابعة عدد المحارف غير المحلولة المشمولة في استخراج النص من ملفات PDF المحمّلة عبر HotPDF

أي استدعاءات استخراج في HotPDF تزيل المحارف المكررة؟

كل الاستدعاءات المنتجة للنص في HotPDF تزيل التكرارات، والاستدعاء الخام للمحارف لا يفعل. يعيش إزالة التكرار في THotPDF.ExtractVisibleLoadedPageGlyphs، الخطوة التي تُسقط أيضاً المحارف خارج الصندوق المرئي للصفحة، وتقرأ منه استخراجات النص العادي والأعمدة والتخطيط والنص الدلالي محارفها جميعاً. أما THotPDF.ExtractLoadedPageGlyphs فيعيد كل محرف يرسمه تدفق المحتوى، الطبوع فوقها مشمولة، لأن المستدعين الذين يبنون أدوات التظليل والحجب الجنائي (redaction) والجنائية الرقمية يحتاجون السجل الكامل لما رُسم

لعمل النص العادي لا يتغير شيء في كود الاستدعاء. الحلقة نفسها التي كانت تستخرج نصاً مضاعفاً قبل v2.768.15 تستخرج الآن كل كلمة مرة واحدة

var
  Pdf: THotPDF;
  PageCount, I: Integer;
  PageText: UnicodeString;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('brochure.pdf');
    for I := 0 to PageCount - 1 do
      if Pdf.ExtractLoadedPageText(I, PageText) then
        if Pos('Bold words', PageText) > 0 then
          Writeln('Found on page ', I + 1);
  finally
    Pdf.Free;
  end;
end;

مخرجات التخطيط والأعمدة تستفيد بالقدر نفسه، لأن الجريان المكرر كان يدفع بقية السطر جانبياً على شبكة المحارف. يحتفظ ExtractLoadedPageTextColumns بمعامل الفجوة الاختياري الخاص به؛ ومرشح التكرار يعمل قبل تقسيم الأعمدة، فلا يتفاعل مع ذلك الضبط

var
  Grid, Columns: UnicodeString;
begin
  if Pdf.ExtractLoadedPageTextLayout(0, Grid) then
    TFile.WriteAllText('page1-layout.txt', Grid, TEncoding.UTF8);
  // الأعمدة تنقسم حيث تتجاوز الفجوة 3 أضعاف متوسط تقدم المحرف
  if Pdf.ExtractLoadedPageTextColumns(0, Columns, 3.0) then
    TFile.WriteAllText('page1-columns.txt', Columns, TEncoding.UTF8);
end;

وإذا أردت أن تعرف هل تحوي الصفحة نصاً مطبوعاً فوقها أصلاً، قارن بين استدعاءَي المحارف. انتبه أن الفرق يعدّ أيضاً المحارف الساقطة خارج صندوق القص (crop box)، فتعامل معه بوصفه حداً أعلى للتكرارات لا رقماً دقيقاً

var
  AllGlyphs, Visible: THPDFGlyphArray;
begin
  if Pdf.ExtractLoadedPageGlyphs(0, AllGlyphs) and
     Pdf.ExtractVisibleLoadedPageGlyphs(0, Visible) then
    Writeln(Format('%d painted, %d read as text (%d overprinted or off-page)',
      [Length(AllGlyphs), Length(Visible),
       Length(AllGlyphs) - Length(Visible)]));
end;

قواعد مسافات الكلمات وفواصل الأسطر التي تحوّل هذه المحارف إلى نص لم تغيّرها المرشحة؛ وهي مشروحة بالتفصيل في كيفية وضع HotPDF لمسافات الكلمات وفواصل الأسطر في نص الصفحة

أين يخالف هذا القاعد أدوات أخرى عمداً

يسقط HotPDF التكرارات التامة التي تُبقيها بعض أدوات الاستخراج الأخرى، وهذا الاختلاف مقصود. عندما تحقق تغيير v2.768.15 مقابل مدونة مرجعية، تذييل صفحة مرسوم مرتين في الموضع نفسه تماماً استُخرج مرة في HotPDF ومرتين في أداة استخراج مرجعية. المحرف المرسوم فوق محرف مطابق له غير مرئي بوصفه محرفاً منفصلاً، فقراءته مرة واحدة هي ما تعرضه الصفحة فعلاً

والحد الصادق يجري في الاتجاه الآخر. Bold الوهمي الذي يزيح المرّة الثانية بأكثر من 0.1 من مقاس المحرف على طول خط الأساس لا يُلتقط، ولا جريان الظل المرسوم بمقاس مختلف ملحوظاً. تلك التخطيطات نادرة عملياً، لأن التأثير البصري يتوقف عن مظهر الـ bold بمجرد أن تصل الإزاحة إلى ذلك الحد، لكن إذا رأيت نصاً مضاعفاً من عنوان زخرفي، قارن قيم X للمحارف من ExtractLoadedPageGlyphs قبل أن تفترض أن المرشحة معطوبة. والمرشحة أيضاً لا تفعل شيئاً للتكرارات غير المتجاورة هندسياً، كترويسة جارية مكررة في طبقة مخفية في مكان آخر من الصفحة؛ فتلك مسألة محتوى، لا طباعة فوقية

لماذا كانت نهايات الأسطر تُبتَع في نص خطوط CID؟

خلل منفصل أُصلح في HotPDF v2.768.12 كان يُسقط نهايات الأسطر في النص المضبوط بخطوط CID-keyed (من نوع Type 0) التي كانت مصفوفة عرض /W لديها غير مرتبة حسب الـ CID. تعرّف ISO 32000-1 §9.7.4.3 صيغة مصفوفة /W لكنه لا يشترط ترتيب مداخلها تصاعدياً حسب الـ CID، ويكتبها بعض المولّدات بترتيب أول استخدام للمحارف في المستند

كان مفكك استخراج النص يبحث عن العروض بالبحث الثنائي، وهو يفترض بصمت أن المدخل مرتب. على مصفوفة غير مرتبة كان معظم البحث يفشل ويرجع إلى العرض الافتراضي /DW، وهو 1000 وحدة، أي em كاملة. فكان النص اللاتيني في مثل ذلك الخط يُنسَّق وكأن كل حرف بعرض كامل، وجارت الأسطر بعيداً فما وراء الحافة اليمنى للصفحة، وتُسقط المحارف الساقطة خارج الصندوق المرئي، فتختفي نهايات الأسطر من النص المستخرج ومن البحث. لم ينهار شيء ولم يُبلَّغ عن شيء، وهذا ما جعله صعب الرصد

يرتب HotPDF الآن مداخل /W بفرز دمج مستقر فور قراءتها، فحيثما تداخل مدخلان يفوز المكتوب أولاً بنيّته، مطابقاً لكيفية قراءة المصفوفة بترتيب الملف. ولم يتأثر عارض الصفحة قط، لأنه يحتفظ بنسخته الخاصة من بيانات العروض ويمسحها خطياً، كما تُمسح خطياً مصفوفة العروض الرأسية /W2. يستحق ذلك الانقسام التذكر حين تبدو صفحة مثالية وتُستخرج بشكل سيئ: العرض والاستخراج في مكتبة PDF غالباً يتشاركان كوداً أقل بكثير مما تتوقع

الأسئلة الشائعة

هل يزيل HotPDF الحروف المتكررة مثل l المزدوجة في "fill"؟

لا. حرفان حقيقيان متجاوران يفصلهما نحو عرض تقدم واحد، بينما يدمج HotPDF المحارف المتطابقة فقط التي تبعد أقل من 0.1 من مقاس المحرف على طول خط الأساس و0.2 عبره، فالتكرارات المشروعة محفوظة

كيف أحصل على كل محرف، بما فيها المكررات المطبوعة فوقها؟

استدعِ THotPDF.ExtractLoadedPageGlyphs، الذي يعيد كل محرف ترسمه الصفحة بترتيب الرسم. أما THotPDF.ExtractVisibleLoadedPageGlyphs وكل استدعاءات استخراج النص فتعيد المجموعة المرئية منزوعة التكرار

أي إصدار HotPDF يصلح النص المضاعف من Bold الوهمي؟

أضاف HotPDF v2.768.15 إزالة المحارف المكررة إلى استخراج النص والتخطيط والأعمدة والنص الدلالي، وأصلح v2.768.12 نهايات الأسطر المفقودة في خطوط CID ذات مصفوفة /W غير مرتبة

يأتي كلا الإصلاحين ضمن مكون HotPDF PDF لـ Delphi الحالي، الذي يستخرج النص من ملفات PDF المحمّلة في Delphi و C++Builder إلى جانب ميزات إنشاء PDF وتحريره وعرضه