تخطَّ إلى المحتوى
getnextpdf.com

لماذا النص في ملف PDF ليس نصًّا حقيقةً

حين تقرأ ملف ⁨PDF⁩، ترى كلمات. لكن الملف لا يحتوي على كلمات. إنه يحتوي على تعليمات لرسم أشكال عند إحداثيات، والأشكال يصادف أنها تبدو مثل أحرف. الفجوة بين ما يرسمه ملف ⁨PDF⁩ وما يعنيه هي السبب في أنّ صفحة قد تبدو خالية من العيوب ومع ذلك تُنسَخ هراءً. تتناول هذه الصفحة تلك الفجوة، والخريطة الصغيرة المنفصلة التي تسدّها.

كل ما تفعله بملف ⁨PDF⁩ بوصفه نصًّا — تحديده، ونسخه، والبحث فيه، وفهرسته للاسترجاع، وقراءته بصوت عالٍ عبر قارئ شاشة — يعتمد على استرجاع أحرف لم يخزّنها الملف مباشرةً قطّ. فإذا فشل ذلك الاسترجاع، كان الفشل غير مرئي. الصفحة لا تزال تُعرَض. لا يلاحظ أحد شيئًا حتى ينسخ أحدهم فقرة في بريد إلكتروني فيحصل على □□□□، أو يبحث في عقد من 400 صفحة عن بند موجود فيه بوضوح فلا يجد شيئًا.

هذا صنف باهظ من العلل تحديدًا لأنه ينجو من كل مراجعة بصرية. ليس هناك ما يُرى. يبدو المستند موثوقًا، وهو، لأغراض الآلة، صامت.

  • يرسم ملف ⁨PDF⁩ محارف — أشكالًا مرئية تُختار برموز عددية إلى داخل خط — لا أحرف ⁨Unicode.⁩
  • الشكل نفسه قد يعني أحرفًا مختلفة، والحرف نفسه قد يُرسَم بمحارف مختلفة. المظهر والمعنى منفصلان بحكم التصميم.
  • لاسترجاع النص، يشغّل القارئ مسار العرض بالعكس: من الرمز إلى الحرف. خريطة ⁨CMap⁩ في /ToUnicode الخاصة بالخط هي جدول البحث لتلك الخطوة العكسية (Spec: ISO 32000-2, §9.10).
  • غياب /ToUnicode، أو وجود خريطة خاطئة، يعني نسخًا ولصقًا مشوَّهين وبحثًا فاشلًا — مع بقاء الصفحة مثالية البكسل.
  • هذه مشكلة المعنى. أمّا ما إذا كان المحرف يبدو صحيحًا فهي مشكلة المظهر المنفصلة التي تتناولها صفحة الخطوط: موضع الصعوبة. يُصدر ⁨NextPDF⁩ خريطة /ToUnicode صحيحة كي تكون الرحلة ذهابًا وإيابًا أمينة.

كيف يتعامل ⁨NextPDF⁩ مع الأمر

قسم بعنوان «كيف يتعامل ⁨NextPDF⁩ مع الأمر»

ابدأ بكيفية وصول النص إلى الصفحة أصلًا. دفق المحتوى لا يقول “اكتب الكلمة file.” إنه يختار خطًّا، ثم يسلّم سلسلة من الرموز إلى مُعامِل عرض النص (Spec: ISO 32000-2, §9). كل رمز فهرس — موضع في برنامج خط — ويحوّل الخط ذلك الفهرس إلى مُخطَّط محرف ويرسمه. الرمز 70 ليس الحرف F. إنه “الخانة 70 في هذا الخط”، ويصادف أنّ الخانة 70 تحمل منحنى على شكل F. اختر خطًّا مختلفًا، وقد تكون الخانة 70 ندفة ثلج.

إذًا الرمز لا يعني شيئًا إلا نسبةً إلى ترميز خطّه. بالنسبة إلى خط بسيط، يكون ذلك الترميز نحو بايت واحد لكل محرف تقريبًا. أمّا بالنسبة إلى الأنظمة الكتابية التي تحتاج آلاف المحارف — ⁨CJK⁩، أو أي مستند ⁨Unicode⁩ كامل — فهذا متّسع غير كافٍ، فيلجأ ملف ⁨PDF⁩ إلى خط مركَّب (⁨Type 0⁩) (Spec: ISO 32000-2, §9.7). يقرأ الخط المركَّب رموزًا متعدّدة البايتات عبر خريطة ⁨CMap⁩، ويحوّلها إلى معرّفات محارف (⁨CIDs⁩)، ويخطّط المعرّفات إلى محارف. إنها مواربة أنيقة تتيح لخط واحد معالجة مجموعة محارف هائلة. وهي أيضًا موضع إضافي قد يبرد فيه الأثر من “ما يُعرَض” إلى “ما يعنيه”.

الآن شغّله بالعكس. لاستخراج النص، يأخذ القارئ الرموز التي يجدها في دفق المحتوى وعليه أن يسترجع الأحرف (Spec: ISO 32000-2, §9.10). الترميز الذي رسم المحرف ذهب من الحرف إلى المحرف؛ ويحتاج الاستخراج من رمز المحرف إلى الحرف، وذلك الاتجاه ليس مضمونًا أن يكون قابلًا للعكس. قد يكون خط مجزَّأ قد أعاد ترقيم محارفه. وقد تكون معرّفات محارف خط مركَّب خاصة. الشكل على الصفحة لا يحمل أي معنى ⁨Unicode⁩ متأصّل.

الحل هو شحن الخريطة العكسية إلى جانب الخط. تلك الخريطة هي خريطة ⁨CMap⁩ في /ToUnicode: فلكل رمز يستخدمه المستند، تسجّل قيمة (أو قيم) ⁨Unicode⁩ التي يمثّلها ذلك الرمز. بوجودها، يكون الاستخراج بحثًا نظيفًا. وبغيابها، يُترَك القارئ ليخمّن من ترميزات الخطوط والاستدلالات الإرشادية — والتخمين هو بالضبط حيث يصير fi علامة استفهام.

  1. Your charactersThe Unicode text you set, for example the word 'file'.
  2. Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
  3. Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
  4. Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
  5. Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
The two directions of PDF text. Rendering goes character → glyph and is what you see. Extraction goes code → character and is what you can copy, search, and read aloud — and it only works when the /ToUnicode map closes the loop.

يكتب ⁨NextPDF⁩ الخريطة العكسية بحكم العادة. عندما يضمّن خطًّا مركَّبًا، يُصدر السليل CIDFontType2، والأصل Type0، وخريطة ⁨CMap⁩ في /ToUnicode كي تُحلّ الرموز عودةً إلى ⁨Unicode⁩ — وهو عمل الترميز الموصوف في صفحة الخطوط: موضع الصعوبة. لكنّ النقطة الجديرة بالفصل هنا هي لماذا: دفق /ToUnicode ليس بصدد جعل الصفحة تبدو صحيحة. المحارف تبدو صحيحة فعلًا من دونه. إنه القطعة الوحيدة التي تُبقي النص قابلًا للاستخراج.

أشهر موضع يظهر فيه هذا هو رابطة fi. كثير من الخطوط ترسم f وi محرفًا مدمجًا واحدًا، لأنّ نقطة i تصطدم بخطّاف f. على الصفحة، إنه شكل واحد، يرسمه رمز واحد. والسؤال هو ما الذي يحدث عند نسخه.

% A /ToUnicode entry that maps the single ligature code to TWO characters,
% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.
1 beginbfchar
<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'
endbfchar

ذلك المُدخَل الواحد هو الفرق بين بحث عن “file” يطابق، وبحث عن “file” يفوّت بصمت كل ورود مُصيَّر بالرابطة. خطّط رمز الرابطة إلى متتالية الحرفين f + i، فتصبح الكلمة قابلة للبحث والنسخ. اتركه غير مخطَّط، فتظل الصفحة تُظهر file على نحو مثالي بينما يقول النص بهدوء شيئًا لا تستطيع آلة قراءته.

لهذا قد تُستخرَج “fi” محرفًا واحدًا أو محرفين، ولهذا لا يكون الفرق عشوائيًا — إنه أيًّا كان ما طُلِب من خريطة /ToUnicode أن تقوله. الخريطة الصحيحة تفكّك الرابطة عودةً إلى أحرفها. يُصدر ⁨NextPDF⁩ بالضبط هذا النوع من المُدخَلات، كي تكون الرابطة على الصفحة حرفين عاديين على الحافظة.

الفخّ، بصريح العبارة: “النص يُعرَض، إذًا النص بخير.” العرض والاستخراج آليتان مختلفتان تقرآن بيانات مختلفة. يتبع العرض مسار الرمز إلى المحرف وهو ما تتحقّق منه عيناك. ويتبع الاستخراج مسار الرمز إلى الحرف عبر /ToUnicode وهو ما يتحقّق منه كل مستهلِك آليّ. يمكن لمستند أن يتفوّق في الأول ويفشل في الثاني فشلًا تامًّا، لأنه بحكم البناء لا يوجد شيء مرئي يكشفه.

فخّ ثانٍ أدقّ: افتراض أنّ المحرف “بداهةً” يعرف أيّ حرف هو. إنه لا يعرف. المحرف شكل له فهرس. والتخطيط عودةً إلى حرف هو معلومة إضافية على المُنتِج أن يوفّرها. فإذا لم يكتبها المُنتِج قطّ، لا يستطيع أي قارئ استرجاعها بأمانة — لا يستطيع سوى التخمين.

Faithful text extraction from documents NextPDF writes — edition availability
EditionAvailability
CoreNextPDF emits a correct /ToUnicode CMap for the fonts it embeds, including the ligature and composite-font cases, so the documents it produces are searchable and copyable.
ProNot in this edition
EnterpriseNot in this edition

خريطة /ToUnicode لا تستطيع حمل سوى المعلومات التي يكشفها الخط المصدري فعلًا. وحيث لا يكون للمحرف حرف يمكن تحديده — علامة زخرفية بحتة، أو رمز ذو استخدام خاص بلا تعيين ⁨Unicode⁩ — لا تستطيع أي خريطة اختراع معنى لم يكن موجودًا قطّ. يُصدر ⁨NextPDF⁩ التخطيط الصادق الذي يستطيع اشتقاقه؛ ولا يفبرك أحرفًا لسدّ فجوة.

تتناول هذه الصفحة المستندات التي يكتبها ⁨NextPDF.⁩ وهي ليست أداة إصلاح لملفات ⁨PDF⁩ واردة اعتباطية يكون /ToUnicode فيها غائبًا أو خاطئًا أصلًا؛ فاسترجاع النص من تلك مشكلة منفصلة استدلالية. كما أنّ الاستخراج الأمين ضروري للوصول لكنه ليس كله — فترتيب القراءة، والوسوم، والنص البديل تتناولها صفحة ما الذي يجعل ملف ⁨PDF⁩ متاحًا للوصول.

لماذا يُنسَخ ملف ⁨PDF⁩ نفسه على نحو جيّد في قارئ وسيّئ في آخر؟ تتراجع القارئات بطرق مختلفة عند غياب /ToUnicode. بعضها يخمّن من الترميز المدمج في الخط فيحالفه الحظ في النص اللاتيني الشائع؛ وبعضها لا. خريطة /ToUnicode الصحيحة تزيل اليانصيب — فيحصل كل قارئ مطابق على الأحرف نفسها.

هل هذا الشيء نفسه كون الخط غير مضمَّن؟ لا. التضمين بصدد المظهر — ما إذا كان المحرف يُرسَم دون تثبيت الخط. أمّا /ToUnicode فبصدد المعنى — ما إذا كان الرمز يُخطَّط عودةً إلى حرف. يمكن لخط أن يكون مضمَّنًا على نحو مثالي ومع ذلك لا يملك /ToUnicode صالحًا، وهو إخفاق “غير قابل للبحث لكنه جميل”.

هل يحتاج الاستخراج يومًا إلى أكثر من حرف واحد لكل رمز؟ نعم — حالة الرابطة هي ذلك بالضبط: رمز واحد يُخطَّط إلى حرفين. يستطيع مُدخَل /ToUnicode تخطيط رمز واحد إلى متتالية قصيرة، وهكذا تعود الروابط، وبعض الأشكال المركَّبة، أحرفها المكوِّنة لها.

  • المحرف — شكل مرئي في خط (مُخطَّط). ما يرسمه ملف ⁨PDF⁩ فعلًا. للمحرف فهرس لكن لا معنى حرفيّ متأصّل.
  • الحرف — وحدة من اللغة المكتوبة، تُعرَّف بنقطة ترميز ⁨Unicode.⁩ ما تقصده، وما يحاول الاستخراج استرجاعه.
  • الرمز — القيمة العددية في دفق المحتوى التي تختار محرفًا من الخط الحالي. ليس حرف ⁨Unicode⁩؛ وله معنى فقط نسبةً إلى الخط.
  • معرّف المحرف (⁨CID⁩) — معرّف يستخدمه خط مركَّب لمعالجة محرف ضمن مجموعة كبيرة؛ خطوة وسيطة بين الرمز والمحرف.
  • الخط المركَّب (⁨Type 0⁩) — خط يقرأ رموزًا متعدّدة البايتات عبر خريطة ⁨CMap⁩ للوصول إلى معرّفات المحارف والمحارف، يُستخدَم حين يجب على خط واحد معالجة آلاف المحارف.
  • /ToUnicode — دفق خريطة ⁨CMap⁩ يخطّط الرموز التي يستخدمها مستند عودةً إلى قيم ⁨Unicode⁩؛ وهو ما يجعل نص ⁨PDF⁩ قابلًا للبحث والنسخ والوصول.
  • الرابطة — محرف واحد يرسم حرفين أو أكثر شكلًا واحدًا (على سبيل المثال fi)؛ ويفكّك مُدخَل /ToUnicode الخاص بها عودةً إلى أحرفها.