arabic.css:95 · Zain 166 · Cairo 291
لماذا يرسم خطّي العربي نصّي الإنجليزي؟
لأنّ ملف الخط ليس ما يوحي به اسمه. افتح أي خط عربي للويب بـ fontTools وعُدّ نقاط الترميز التي يصرّح بها بين U+0041 وU+024F: فـ Zain، وهو الوجه الذي يشحنه هذا الموقع اليوم، فيه 166؛ وCairo، الوجه الذي حلّ محلّه، فيه 291؛ وNoto Kufi Arabic فيه 248. وثلاثتها تحمل فوق ذلك مجموعة ASCII القابلة للطباعة كاملةً، وهي 95 نقطة ترميز. والخط العربي الخالي من اللاتينية استثناء لا قاعدة.
ثم تتكفّل مطابقة الخطوط بالباقي، وتفعل ذلك لكل محرف لا لكل مقطع. فلكل نقطة ترميز يمشي المتصفح على قائمة font-family ويتوقّف عند أوّل عائلة تملك رسمًا لها — وليس في الخوارزمية كلّها خطوة تسأل إلى أي كتابة ينتمي المحرف. ضع عائلة عربية في المقدّمة داخل قاعدة شاملة تفُز باللاتينية أيضًا، بصمت، لأنّ النتيجة مقروءة وخاطئة قليلًا لا أكثر.
وهذا المستودع قال غير ذلك سنوات. فترويسة src/styles/locale/arabic.css كانت تعلّل تصدير Cairo في كل قائمة بأنّ الملفات «تحمل رسومًا عربية فقط — واللاتينية تسقط إلى Quicksand، فتبقى هوية الموقع نفسها للأسماء والشيفرة». وقد قيس ذلك فتبيّن خطؤه. فكل اسم مشروع، وكل شريحة HTML وCSS، وعبارة Google AI Essentials على الصفحات العربية، كانت تُرسم بـ Cairo — 381 مقطعًا لاتينيًا — طوال عمر البناء.
| الخط | ASCII القابل للطباعة، U+0020 إلى U+007E | العربية، U+0600 إلى U+06FF | اللاتينية، U+0041 إلى U+024F |
|---|---|---|---|
| Zain Regular, Bold, ExtraBold | 95 | 109 | 166 |
| Cairo Regular, SemiBold | 95 | 102 | 291 |
| Noto Kufi Arabic Bold | 95 | 256 | 248 |
| Quicksand Regular | 95 | 0 | 357 |
الصفّ الأخير من الجدول هو المهمّ مرّتين. فـ Quicksand لا يحمل ولا نقطة ترميز عربية، ولذلك فإنّ مقطعًا عربيًا لا تسمّي قائمته أي عائلة عربية لا يجد إلى أين يذهب — وذلك هو النصف الثاني من هذا المقال، وقد شحنه الموقع نفسه في الوقت نفسه، لكن في الاتجاه المعاكس.
CSS Fonts 4 §4.5 · initial U+0-10FFFF
unicode-range ليس الملف، ولا العائلة، ولا المستند
الواصف يجيب عن سؤال واحد بالضبط — هل يجوز استعمال هذا الوجه لنقطة الترميز هذه — وتُسند إليه CSS Fonts 4 مهمّتين في فقرة واحدة. فاتّحاد النطاقات المصرّح بها «تلميح لوكلاء المستخدم عند تقرير تنزيل مورد خط من عدمه لمقطع نصّي معيّن»، ثم، وهو الأشدّ: «يجب على وكلاء المستخدم ألّا ينزّلوا الخط أو يستعملوه لنقاط ترميز خارج هذه المجموعة». والقيمة الابتدائية U+0-10FFFF، فالوجه الذي لا نطاق مصرّحًا به له يجوز له أن يجيب عن يونيكود كلّه، وهو ما فعله كل وجه في هذا الموقع حتى أغسطس.
وهو ليس محتوى الملف، والمواصفة تتكلّف لتقول ذلك: «خريطة المحارف الفعّالة هي تقاطع نقاط الترميز التي يحدّدها unicode-range مع خريطة محارف الخط». ووجوه Zain الأربعة هنا تصرّح بـ 1,264 نقطة ترميز وتغطّي 252 منها، أي 19.9 في المئة. وثلاث من الكتل المصرّح بها فارغة تمامًا في الملف، وكذلك U+200F، علامة اليمين إلى اليسار. ولا شيء ينكسر، لأنّ التقاطع هو القاعدة، ولأنّ المواصفة تبارك التصريح بنطاقات واسعة دون فحص الخط.
وهو ليس تجزئةً أيضًا، وهذا الخلط يكلّف أكثر من نقطة مصطلحية. فالتجزئة خطوة بناء تعيد كتابة الخط، وقد قيس في هذا المشروع أنّها تدمّر الكتابة: فنقاط الحروف العربية رسوم منفصلة تُركَّب وقت العرض، وأداة التجزئة التي تُسقط ما تحسبه غير قابل للوصول تُسقطها. اتُّخذ القرار لأجل Cairo وانتقل إلى Zain كما هو. هذه الملفات خطوط كاملة عن قصد، وأمامها نطاق مقيَّد.
| النطاق المصرّح به | نقاط ترميز مصرّح بها | نقاط ترميز مغطّاة |
|---|---|---|
| U+0600-06FF، العربية | 256 | 109 |
| U+0750-077F، ملحق العربية | 48 | 0 |
| U+0870-088E، العربية الموسّعة-B | 31 | 0 |
| U+08A0-08FF، العربية الموسّعة-A | 96 | 0 |
| U+200F، علامة اليمين إلى اليسار | 1 | 0 |
| U+FB50-FDFF، أشكال العرض-A | 688 | 54 |
| U+FE70-FEFF، أشكال العرض-B | 144 | 89 |
| السبعة كلّها، كما تُصرَّح على كل وجه | 1,264 | 252 |
نقطتا ترميز تسيران في الاتجاه الآخر — موجودتان في الملف، غائبتان عن النطاق. فـ U+0020 وU+0030 كلتاهما في خريطة محارف Zain ولا تقع أيّ منهما داخل الاتّحاد المصرّح به، فيُمنع المتصفح من استعمال Zain لمسافة أو لصفر. وهذا متعمَّد، وهو سبب سقوط الفراغ بين كلمتين عربيتين إلى Quicksand، وله أثر في القسم الأخير لم يقسه أحد هنا بعد.
nav-lang.css · 5 faces · 70 routes
لماذا فشلت التقنية نفسها في الموقع الذي حلّ هذا محلّه
الموقع السابق لهذا جرّب unicode-range أوّلًا وخلص إلى أنّه لا يعمل. فقد علّق وجوهًا ذات نطاق عربي تحت أسماء عائلات الموقع نفسه — Cairo تحت Quicksand، وCairo مرة أخرى تحت Quicksand Label، وNoto Kufi Arabic تحت Quicksand Display — تفاديًا لمعركة تتالٍ مع ورقة أنماط مشتركة حجمها 183 كيلوبايت. خمسة وجوه، على المسارات السبعين كلّها. وتعليقه هو نفسه يسجّل النتيجة: «ظلّ Chromium يحلّ المقاطع العربية عبر وجوه Quicksand كاملة النطاق ثم يسقط إلى خط النسخ في النظام».
كان القياس صحيحًا والاستنتاج المبنيّ عليه خاطئًا. فـ unicode-range يُستشار بعد أن يكون الوجه قد اختير بالنمط، لا قبله، والوجوه لا تنطوي في وجه مركّب واحد لتلك الخطوة إلا إذا تطابقت واصفاتها الأخرى: «مجموعة الوجوه المعرَّفة بقواعد @font-face بقيم واصفات متطابقة وقيم unicode-range مختلفة تُعدّ وجه خط مركّبًا واحدًا في هذه الخطوة». وهذه لم تكن متطابقة. فوجوه الموقع اللاتينية صرّحت بنطاقات وزن 200 300 و400 و500 900، والعربية بـ 100 500 و501 900.
فترك تضييق الوزن أكثر من وجه قائمًا، وعندها تسلّم المواصفة القرار لغيرها. فوكيل المستخدم «يجب أن يختار خطًّا واحدًا من المجموعة المطابِقة»، و«اختيار أيّ خط يُنتقى قد يختلف بين وكلاء مستخدم متعدّدين ومنصّات تشغيل متعدّدة». وحين اختار وجهًا لاتينيًا لم يكن الرسم العربي موجودًا، والجملة التالية تغلق الباب: «لا تُؤخذ رسوم الوجوه الأخرى في العائلة بالحسبان». فتُهجَر العائلة بكاملها، ويُجرَّب اسم العائلة التالي، وفي آخر القائمة يشغّل المتصفح التراجع إلى الخطوط المثبّتة، ونتيجته «قد تتباين بين وكلاء المستخدم». وذلك هو خط النسخ في النظام.
/* الوجه العربي، معلَّقًا تحت اسم عائلة الموقع نفسه */
@font-face {
font-family: 'Quicksand';
src: url('Cairo-SemiBold.ar.v2.woff2') format('woff2');
font-weight: 501 900;
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF,
U+FB50-FDFF, U+FE70-FEFF, U+200C-200F;
}
/* وفي موضع آخر من الورقة نفسها ذات 183 كيلوبايت، Quicksand الحقيقي */
@font-face {
font-family: 'Quicksand';
src: url('Quicksand-SemiBold.woff2') format('woff2');
font-weight: 500 900;
/* لا unicode-range: القيمة الابتدائية هي يونيكود كلّه */
}
/* الواصفات تختلف، فهذان وجهان لا وجه خط */
/* مركّب واحد. تضييق النمط يجري أوّلًا وقد يُبقي الاثنين. */
المستودع نفسه يشحن المثال المضادّ، ويستحقّ أن يوضع بجانب الفشل. فعشرة أدلّة خطوط تجريبية تحمل تقسيم latin وlatin-ext الخاص بـ Google Fonts، مولّدًا بأداة tools/localize-demo-fonts.mjs: 109 وجوه أُبقيت، و100 وجه لكتابات أخرى حُذفت. وتلك الوجوه واصفاتها متطابقة فعلًا عدا النطاق، فتؤلّف وجهًا مركّبًا واحدًا وتصير النتيجة حتمية في كل محرّك. Google يقسّم تصميمًا واحدًا على نطاقات. أمّا Golden Master فعلّق تصميمًا مختلفًا تحت اسم قائم وبوزن مختلف، وهذا هو الترتيب الوحيد الذي لا تغطّيه قاعدة الوجه المركّب.
legacy 11-route probe · comment said 2.5 KB
كلمة عربية واحدة كلّفت 50,096 بايت في كل صفحة إنجليزية
الفاتورة الثانية لذلك الترتيب قيست بمسبار fetch عبر أحد عشر مسارًا من الموقع القديم. فالملف Cairo-SemiBold.ar.v2.woff2، وحجمه 50,096 بايت، لم يُحمَّل مسبقًا في أي مكان وجُلب في المسارات الأحد عشر كلّها — بما فيها كل مسار إنجليزي وألماني. ووصل بـ initiatorType css بين 610 و640 مللي ثانية في خمسة مسارات عادية، وعند 1,118.4 مللي ثانية في صفحة 404، وعند 1,464.2 مللي ثانية في الصفحة الرئيسية.
والمشغّل كان سلسلة واحدة: عنوان اللغة العربية في مبدّل اللغة، أربعة محارف داخل قائمة تنقّل لم يفتحها أحد. والتعليق الجالس بجانب الشيفرة كان يزعم أنّ الصفحات الإنجليزية والألمانية تنزّل نحو 2.5 كيلوبايت مرة واحدة لأجلها. وسجلّ العمارة يذكر الفارق صراحةً — أكبر بنحو عشرين ضعفًا — ويضيف ما يجعله عيبًا لا طرفة: من يحسّن اعتمادًا على التعليق لا على القياس لن يعثر على الـ 50 كيلوبايت.
| الملف | محمَّل مسبقًا في | مجلوب في | بايت |
|---|---|---|---|
| Quicksand-Regular.woff2 | en, de, ar | 11 من 11 | 30,844 |
| Quicksand-Medium.woff2 | en, de | 10 من 11 | 30,784 |
| Quicksand-SemiBold.woff2 | en, de | 11 من 11 | 31,084 |
| Quicksand-Light.woff2 | أبدًا | 0 من 11 | 29,000 |
| Cairo-Regular.ar.v2.woff2 | ar | 1 من 11 | 49,084 |
| Cairo-SemiBold.ar.v2.woff2 | أبدًا | 11 من 11 | 50,096 |
| NotoKufiArabic-Bold.ar.v2.woff2 | ar | 1 من 11 | 60,720 |
| الملفات السبعة كلّها | — | — | 281,612 |
هل تكلّف سلسلة مخفيّة شيئًا؟ ذلك سؤال عن الصناديق لا عن الظهور. فالقاعدة الأساسية للوحة القائمة القديمة في الورقة المشحونة كانت تحمل clip-path المفتوح؛ أمّا الحالة المغلقة، display none وقطع ناقص مسطَّح، فكانت تُكتب نمطًا سطريًا من زمن التشغيل عند الإقلاع. فكانت اللوحة تُخطَّط عند أوّل رسم ثم تُخفى بعده، والمقطع النصّي المخطَّط مقطع تنطبق عليه قاعدة التنزيل.
وهذا البناء يصرّح بالحالة المغلقة استاتيكيًا — فاللوحة تحمل display none في الورقة المشحونة، موصوفةً في تعليقها بأنّها الحالة المستقرّة والحالة المنبعثة. ومن المفترض أن يعني ذلك لا صناديق ولا جلب حتى تُفتح القائمة. لكنّه لم يُقَس على هذا البناء، والبناء السابق قيس فيه الادّعاء المكافئ فتبيّن خطؤه، فعامله آليةً معلّقًا بها فحص لا نتيجةً. افتح لوحة Network، ورشّح على Font، وحمّل صفحة إنجليزية، ثم افتح القائمة وراقب.
arabic.css:104-146 · 4 faces, one range
النطاق كما شُحن، والثغرة التي فتحها في اليوم نفسه
دخلت القاعدة مع تبديل الخط الطباعي، والسبب كان الانحراف. فلاتينية Zain تصميم مختلف عن لاتينية Cairo: ارتفاع الحرف الكبير 69.5 في المئة من الـ em مقابل 69.1 عند Cairo، لكن x-height عند 46.0 مقابل 50.0 — أصغر بـ 8 في المئة عند ارتفاع كبير متطابق تقريبًا. فالتقاط غير مكتشَف كان سيزداد سوءًا ظاهرًا تحت الوجه الجديد، ولذلك فإنّ التغيير الذي أدخل Zain أدخل معه التصريح الذي يمنع Zain من الإجابة عن اللاتينية أصلًا.
أربعة وجوه، وعائلتان مركّبتان، ونطاق واحد متطابق عليها جميعًا، مقسومًا عند الوزن 650 لتختار ترقيات 600 و700 القائمة القطعة الصحيحة دون تحرير قاعدة واحدة. والحمولة محايدة بالبايت في حدود كيلوبايت: 161,096 بايت لـ Zain مقابل 159,900 كلّفتها العائلتان اللتان حلّ محلّهما، بفارق 1,196 بايت.
@font-face {
font-family: 'Zain AB';
src: url('../assets/fonts/zain/regular.woff2') format('woff2');
font-weight: 100 500;
font-display: swap;
size-adjust: 112%;
unicode-range:
U+0600-06FF, U+0750-077F, U+0870-088E, U+08A0-08FF,
U+200F, U+FB50-FDFF, U+FE70-FEFF;
}
html[lang='ar'] body,
html[lang='ar'] body * {
font-family: 'Zain AB', Quicksand, Arial, sans-serif;
}
/* Zain في الآخر هنا عن قصد: الأرقام والمعرّفات تحتفظ بأحادي العرض */
html[lang='ar'] :is(code, pre, .cdx-code) {
font-family: ui-monospace, menlo, 'Zain AB', 'Courier New', monospace;
}
إضافة نطاق تُنشئ فورًا صنفًا من نقاط الترميز لم يعد يجوز للعائلة رسمه، وأوّله المسافة بين كلمتين عربيتين. فكل قائمة تسمّي Zain صارت تحتاج عائلة خلفها تغطّي كل ما يستثنيه النطاق — وواحدة منها لم تكن لديها. فقائمة العرض أغفلت Quicksand، فسقطت المسافة داخل عنوان عربي من كلمتين، وعلامة العطف في عنوان آخر، وأيّ رقم، على Arial: رسمان من Arial داخل أكبر حرف على الصفحة، عُثر عليهما بـ CSS.getPlatformFontsForNode.
والقياس الذي يثبت أنّ النطاق نجح هو نفسه القياس الذي وجد الثغرة التي فتحها. فبعد الإصلاح، صار كل رسم لاتيني على الصفحات العربية يُحلّ إلى Quicksand أو Menlo، ولا واحد منها إلى Zain.
قيمتا size-adjust في تلك القائمة، 112 في المئة للنص و103 في المئة للعرض، تخرجان من قياس منفصل — نسبة السنّة إلى الألف.
P11-03 · found and fixed 2026-08-26
الخلل المعاكس: كلمة عربية بلا وجه عربي
مبدّل اللغة يطبع أسماء اللغات الثلاث بلغاتها على المستندات الثلاثة، وذلك هو مغزاه: القارئ الذي لا يقرأ الصفحة الحالية يظلّ يتعرّف على لغته هو. والوسوم كانت تقول الحقيقة أصلًا. فالمرساة العربية تحمل hreflang وlang وdir، والسلسلة مصرَّح بها مرة واحدة ومدرجة كمشتركة في حزم المحتوى الثلاث، فلا تترجمها أي لغة.
وما لم يكن لديها هو وجه. فكل قاعدة عربية في ورقة الأنماط كانت مربوطة بعنصر المستند، وهو واقعة عن الصفحة لا عن المقطع النصّي. وعلى المستندين الإنجليزي والألماني ورث العنوان رمز العنوان اللاتيني بدلًا من ذلك — Quicksand Label، ثم Arial، ثم sans-serif — وQuicksand يحوي صفرًا من نقاط الترميز بين U+0600 وU+06FF. فاستنفد المتصفح القائمة وشغّل التراجع إلى الخطوط المثبّتة.
قيس ذلك على macOS في WebKit وChromium معًا: العنوان يُرسم بخط النسخ في المنصّة بجانب أربعة عناوين بـ Quicksand، بوزن مختلف وكثافة لون مختلفة وحجم بصري مختلف. وعلى جهاز بلا وجه عربي في نظامه يُرسم مربّعات فارغة. ولم يكن في المقطع شيء خاطئ سوى CSS، وCSS سأل سؤالًا عن المستند بينما كان السؤال عن أربعة محارف.
- العربية
- السلسلة نفسها، مصرَّح بها مرة واحدة ومدرجة كمشتركة في الحزم الإنجليزية والألمانية والعربية، فتطبعها المستندات الثلاثة بالعربية بدل ترجمتها. صحيحة من البداية.
- الوسوم
- الملف src/components/site-nav.html يضع hreflang وlang وdir على المرساة، ويطلب من المقطِّع أسطرًا لا كلمات ومحارف. صحيح من البداية.
- ورقة الأنماط
- كل قاعدة كان يمكن أن تزوّد وجهًا عربيًا كانت مربوطة بعنصر المستند. وعلى المستندين اللاتينيين لم تطابق ولا واحدة منها، فسقط المقطع من القائمة كلّها.
نجا من بوّابة RTL مبنيّة لهذا الغرض، ومن سجلّ محتوى ثلاثي اللغة، ومن بناء يفرض التكافؤ عبر ثلاث لغات، لأنّ أيًّا منها لا يسأل الصفحة الإنجليزية سؤالًا عن العربية. وقد عُثر عليه بقراءة ترويسة الملف نفسها وعدم تصديقها.
المرساة العربية في المبدّل تتّكئ على ورقة أنماط وكيل المستخدم في معالجة الاتجاه على الصفحات اللاتينية، وهي شكل الفجوة التي لا تسدّها الخصائص المنطقية.
utilities beats components, no !important
اربط القاعدة باللغة، لا بالمستند
الإصلاح قاعدتان، والنصف الثاني من الأولى حاملٌ لا زيادةَ احتياط. فالعائلة توضع على خاصية اللغة وعلى ما ينحدر منها، لأنّ components/button.css يضبط font-family من رمز على العنصر span داخل المرساة — والوراثة لا تغلب تصريحًا على العنصر نفسه أبدًا. وقاعدة على المرساة وحدها كانت ستخسر على العنصر الوحيد الذي يحمل الحبر.
وهي تفوز بلا !important بفضل ترتيب الطبقات. فالملف src/styles/order.json يصرّح بـ reset وtokens وbase وlayout وcomponents وsections وutilities وoverrides؛ وlocale/arabic.css يجلس في utilities وbutton.css في components، أي بطبقتين قبله، والطبقة اللاحقة تغلب أيّ أولوية تستطيع طبقة سابقة كتابتها. والقاعدة العربية الشاملة بجانبها تحمل !important واحدًا، لأجل letter-spacing، وهي القاعدة الشاملة الوحيدة المبرَّرة من نوعها في المشروع. أمّا هذه فلا تحتاج شيئًا منه.
- سَمِّ اللغة، لا الصفحة استبدل بالمحدّد المربوط بالمستند محدّدًا على خاصية اللغة، لكل ما يجب أن ينطبق على مقطع عربي أينما ظهر. ويبقى ربط المستند لكل ما هو عن المستند حقًّا، كالاتجاه.
- صِل إلى العنصر الحامل للحبر أضف نصف المحدّد الخاصّ بالمنحدرين. فالابن الذي يصرّح بـ font-family خاصّ به لن يرث خاصّتك، فعلى القاعدة أن تطابق ذلك الابن أيضًا.
- دع الطبقة تحسمها ضع القاعدة في طبقة تلي الطبقة التي صرّحت بالرمز. لا معركة أولوية ولا !important، والآلية نفسها تمنع ورقة قسم من تجاوز تصحيح لغوي.
- صفّر التباعد الجسم يضبط letter-spacing عند سالب 0.005em ولا يصرّح span العنوان بشيء خاصّ به، فورثته الكلمة العربية على الصفحات اللاتينية. والعربية لا تُباعَد حروفها أبدًا: التباعد يكسر الخط المتّصل، فتكفّ الحروف عن التلامس وتكفّ الكلمة عن كونها كلمة.
- افتح صندوق السطر ونافذة القصّ معًا العنوان مضبوط عند line-height يساوي 1 من مكوّن الزرّ ومقصوص عند 94 في المئة من صندوقه. وصندوق محتوى Zain عند size-adjust 112 في المئة هو 1.859em، فعند line-height يساوي 1 يصير نصف الفراغ السطري سالبًا ويتجاوز الحبر القصّ. والقاعدة تضبط 1.55 وتحرّك النافذة إلى 114 في المئة.
- انتقل إلى صنف اللغة الزائف في قاعدة المقاييس قاعدة المقاييس تستعمل :lang(ar) بدل محدّد الخاصية. فلغة المحتوى يرثها المنحدرون وتُطابَق بالترشيح الموسّع، فتغطّي أيضًا ar-EG وar-Arab-EG، وهو ما لا يفعله تطابق خاصية حرفيّ.
حساب القصّ يُستنسخ بالضبط، وهكذا اختيرت أرقام تلك القاعدة لا تخمينًا. فجدول hhea في Zain يعطي صعودًا 869 وهبوطًا سالب 459 على 800 وحدة لكل em؛ وعند size-adjust بنسبة 112 في المئة يصير ذلك 1.2166em و0.6426em، أي صندوق محتوى 1.8592em. وعند line-height يساوي 1 يكون نصف الفراغ السطري سالب 0.4296em ويجلس خط القاعدة على بعد 0.7870em تحت أعلى صندوق السطر، وهو ما يضع أعمق حبر في العنوان عند 1.2505em مقابل قصّ عند 0.9400em. وعند 1.55 مع النافذة عند 114 في المئة يهبط الحبر عند 1.5716em، والنافذة عند 1.7670em، والتوأم عند التحويم عند 1.8454em، بهذا الترتيب. وأعماق الرسوم مقيسة على أشكال معزولة.
والملف يذكر الثابت هكذا: التوأم تحت النافذة، والنافذة تحت الحبر، ويسجّل ما يحدث حين ينكسر. ففي رابط تذييل الموقع القديم تجاوز الحبر القصّ بخمسة بكسلات، ففقدت ياء أخيرة نقطتيها وصارت الكلمة كلمة أخرى — خطأ إملائي في كتابة صاحب الموقع نفسه. أُصلح على الصفحات العربية وبقي منشورًا على الإنجليزية والألمانية منذ ذلك الحين، على الكلمة العربية الوحيدة التي يحتويها هذان المستندان.
الرقم 1.55 في تلك القاعدة رقم نافذة قصّ لا رقم تصادم، والفرق بين الاثنين هو الحدّ الأدنى المقيس لارتفاع السطر العربي.
split-registry.js:121-190 · read once
الخطأ نفسه، بطبقة أدنى، في JavaScript
سجلّ التقطيع يقرأ لغة المستند مرة واحدة بالضبط، عند الإقلاع، وتعليقه يعطي سببًا معقولًا: فـ lang وdir يكتبهما البناء على عنصر المستند ولا يتغيّران بعدها، فالسؤال لكل عنصر سيكون 55 قراءة لواقعة واحدة و55 فرصة لقراءتها على نحو مختلف. ثم يستعمل تلك القراءة الواحدة ليقرّر هل يجوز تقطيع مقطع إلى محارف.
على الصفحات العربية هذا صحيح. وعلى الإنجليزية والألمانية هو خطأ ورقة الأنماط بلغة أخرى: فالمستند en أو de، فيُقطَّع العنوان العربي إلى محارف. وتقطيع المحارف ليس تجميليًا في خط متّصل. فمكوّن الكشف يجعل كل محرف inline-block، ولا بدّ له من ذلك، لأنّ transform لا ينطبق على صندوق سطري غير مستبدَل — والصندوق الذرّي لكل حرف صندوق لا يستطيع محرّك التشكيل الوصل عبره. فتخرج الكلمة بأشكال معزولة، أي مكتوبةً خطأً.
والوسوم تتفادى ذلك يدويًا. فذلك الـ span الواحد يطلب من المقطِّع أسطرًا بينما يطلب العنوانان الإنجليزي والألماني كلمات ومحارف، وتعليق الوحدة نفسه يسلّم بالأمر: ملف المكوّن كان قد بلغ هذا الاستنتاج يدويًا للعنوان الوحيد الذي لاحظه أحد.
| العطل | أين | ماذا فعل المتصفح | ماذا كلّف |
|---|---|---|---|
| التقاط زائد | الصفحات العربية، Cairo أوّلًا بلا نطاق | أجاب عن كل نقطة ترميز لاتينية من الملف العربي | 381 مقطعًا لاتينيًا بالخط الخطأ |
| التقاط زائد، عند التنزيل | الصفحات الإنجليزية والألمانية القديمة | أبقى وجهين قائمين واختار أحدهما اعتباطًا | 50,096 بايت في 11 من 11 مسارًا |
| التقاط ناقص | الصفحات الإنجليزية والألمانية، كل قاعدة على المستند | استنفد القائمة وشغّل التراجع إلى الخطوط المثبّتة | كلمة واحدة بخط النسخ في النظام |
| التقاط ناقص، في الشيفرة | الصفحات الإنجليزية والألمانية، سجلّ التقطيع | كان سيقطّع خطًّا متّصلًا إلى صناديق سطرية ذرّية | كلمة واحدة مكتوبة خطأً على الشاشة |
البوّابة الآلية لا ترى ثلاثة من الصفوف الأربعة أدناه. فالأداة tools/golden/rtl.mjs تمشي على كل عقدة نصّية وتؤكّد أنّ كل مقطع عربي يطلب Zain وأنّ كل مقطع لاتيني يحتفظ باحتياطي لاتيني — على المسارات العربية وحدها. وهي تحمّل الصفحة الرئيسية الإنجليزية شاهدًا للمقطِّع وللترويسة وللثنائية وللقضيب، ولا تطرح عليها سؤال الخط أبدًا. وبحث grep عبر الاختبارات وفحوص العقود لا يعيد شيئًا للقاعدة الجديدة المربوطة باللغة. بوّابة RTL مبنيّة لهذا الغرض جرت خضراء بينما كان العيب يعيش على أكثر مستندين زيارةً في المنتج.
أمّا لماذا يكسر inline-block لكل حرف كلمةً عربية، وما الذي يجب أن يفعله المقطِّع بدلًا من ذلك، فتلك هي مشكلة تقطيع الخطوط المتّصلة.
glyphCount, not fontFamily
كيف تثبت أيّ وجه فاز فعلًا؟
النمط المحسوب لن يخبرك. فهو يبلّغ بالقائمة التي كتبتها، وبوّابة هذا المستودع نفسها تقول ذلك في تعليق على الفرع الذي تستسلم عنده: المقطع اللاتيني الخالص داخل مستند عربي يجوز له بحقّ أن يطلب العائلة العربية أوّلًا، وunicode-range هو ما يوجّهه بعدها إلى Quicksand، والنمط المحسوب لا يرى ذلك. وما تستطيع البوّابة تأكيده هو الخاصّية الأضعف والنافعة مع ذلك — ألّا يطلب أيّ مقطع عائلةً بلا احتياطي لاتيني خلفها.
والدالة CSS.getPlatformFontsForNode عبر بروتوكول أدوات مطوّري Chrome هي ما يحسم الأمر. فهي تعيد، لكل عقدة، عائلات المنصّة التي رسمت النص فعلًا، وهل نُزّلت كلٌّ منها أم حُلّت محليًا، وكم رسمًا أسهمت به. وهي ما وجدت رسمَي Arial داخل عنوان عرض، وما أكّد بعدها أنّ لا رسم لاتيني على الصفحات العربية من Zain. ويدويًا هي كتلة Rendered Fonts في أسفل لوحة Computed.
- CSS.getPlatformFontsForNode
- تعيد familyName وisCustomFont وglyphCount لكل خط منصّة استُعمل لرسم نصّ عقدة. وهي الوحيدة من الأربع التي تجيب عن سؤال أيّ وجه فاز.
- Rendered Fonts، في لوحة Computed
- البيانات نفسها يدويًا، في أسفل اللوحة. وإدراج عدّة عائلات مقابل عنصر واحد يعني أنّ تراجعًا حدث داخله.
- لوحة Network، مرشَّحة على Font
- سؤال آخر: هل جُلب الملف أصلًا. والملف المجلوب قد يكون مع ذلك خسر كل نقطة ترميز لعائلة أمامه.
- document.fonts.check()
- فخّ. تقول MDN إنّها ليست مصمَّمة للتحقّق من إمكان رسم نمط خط بعينه، ووسيطها النصّي يفترض مسافة واحدة افتراضيًا — وأمام وجه يستثني نطاقه U+0020 تكون الإجابة جوفاء. مرّر السلسلة الحقيقية.
أثر واحد لهذا النطاق لم يُقَس هنا، ويستحقّ فحصًا في أي موقع يستعمل ch للقياس. فوحدة ch هي تقدّم U+0030 في الخط المستعمل لرسمها، وU+0030 خارج تصريح Zain، فيجب على المتصفح ألّا يستعمل Zain لها ويزوّدها Quicksand بدلًا منه. والانتقال من Cairo إلى Quicksand حرّك 1ch بمقدار 0.09 في المئة. ولو تُرك Zain كامل النطاق لكان 1ch قد كبر بمقدار 25.4 في المئة، عبر أكثر من عشرين قياسًا مبنيًا على ch في ورقة الأنماط هذه.
وللبند نفسه أثر أهدأ. فأوّل خط متاح — وهو ما تُحلّ إليه ex وcap وlh — معرَّف بأنّه أوّل خط لا يستثني unicode-range فيه U+0020، وU+0020 خارج نطاق Zain أيضًا، فلا يمكن أن يكون Zain أوّل خط متاح في أي مستند هنا. والتقدّمات أعلاه مقيسة؛ أمّا عروض الأعمدة المرسومة على الصفحات العربية قبل وبعد فليست كذلك. قارنها بنفسك قبل أن تفترض أيّ اتجاه.
والبناء الثلاثي اللغة الذي قُرئ منه كل رقم من هذه الأرقام هو واحد من المواقع المفاهيمية العشرة.