Satori لا يستطيع تشكيل العربية، وما الذي نشرناه بدلاً من ذلك

صور OG المولّدة تنكسر مع العربية بطريقة محددة وموثّقة. إليك السبب، والحلول الأربعة التي لا تعمل، والحل الذي يعمل، والحالة التي عجزنا عن حلها.

نولّد صور Open Graph وقت البناء عبر next/og، الذي يشغّل Satori تحته. وبالإنجليزية تأتي النتيجة كما تريد تماماً: مكوّن حقيقي وخطوط حقيقية وبلا أداة تصميم في الدورة. أما بالعربية فأنتج بطاقة خاطئة بطريقة استغرق مجرد تسميتها وقتاً.

خرج العنوان بفجوات نحو 40 بكسل بين كل كلمتين، أشبه بصفّ جريدة سيئ الضبط. كل شيء مقروء، ولا شيء ناقص، وبدا الأمر كعطل تباعد في مكوّننا نحن. ولم يكن كذلك.

السبب: قياس بلا تشكيل

العربية خط متصل. للحرف أشكال مختلفة بحسب موقعه في أول الكلمة أو وسطها أو آخرها، والأشكال المتصلة أضيق من المعزولة. وتحويل السلسلة إلى تلك الأشكال يُسمى التشكيل، وهو خطوة منفصلة عن القياس.

يقيس Satori أولاً ويجمع عرض التقدم المعزول لكل حرف. والمحارف التي تُطلى في النهاية أضيق مما قيس، فتنتهي كل كلمة في صندوق أوسع من حبرها، بمقدار يتفاوت من كلمة لأخرى تبعاً للحروف المتصلة. هذا هو العطل كله، وهو يفسّر الفجوات وكل ما يليها.

كتابات

تريد هذا على موقعك؟

احجز مكالمة

أربعة حلول لا تعمل

  • خاصية word-spacing غير منفَّذة في Satori إطلاقاً. يُقبل التصريح ويُتجاهل.
  • قيمة display: block مُتجاهَلة، فلا يمكنك العودة إلى تخطيط الكتل العادي للإفلات من قياس flex.
  • التقسيم إلى كلمات وتباعدها بـ gap في flex يبدو صحيحاً وليس كذلك. فكل صندوق كلمة يحمل تقدمه اللاحق، فتخرج الفجوات متفاوتة وتتصادم بعض الكلمات.
  • تقسيم السطر إلى مقاطع نصية وترتيبها بـ row-reverse يكسره فعلياً. فهو يعيد ترتيب الكلمات: عنوان يقرأ Webflow وFramer وكود وضع مقطع Framer في أقصى طرف السطر.

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

ما الذي يعمل، والتفصيل الذي يكلّف بعد ظهيرة كاملة

المسافة غير الفاصلة U+00A0 تتشكّل بإحكام وصحة حيث تفشل المسافة العادية. لكن سلوك الفاصل عند حدود النصوص هو حيث يذهب الوقت، والنتائج ليست ما قد تخمّنه.

// Separator behaviour at an Arabic-then-Latin boundary, measured
// against rendered output rather than assumed.

"\u00A0"          // ONE nbsp   -> space vanishes: "تطويرWebflow"
"\u2009"          // thin space -> dropped the same way
"\u2066…\u2069"   // LRI / PDI isolates -> dropped the same way
"\u00A0\u00A0"    // TWO nbsp   -> survives
" "                // plain space -> survives, but re-splits the bidi
                   //    run and reverses word order, which is worse
لا شيء من هذا موثّق. كل سطر قياس مأخوذ من ناتج مرسوم فعلياً، وهي الطريقة الوحيدة لإثبات أي منه.

أنت الآن مسؤول عن كسر الأسطر

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

/* Measured off rendered output: IBM Plex Sans Arabic sets at about
   0.41em per character. 0.45 leaves headroom without breaking early.
   It was 0.54, which put a 34-character headline onto three ragged
   lines with one word alone on the last. */
const CHAR_EM = 0.45;

/** Greedy packer. Whole words, never overflowing the box, because
 *  satori cannot measure for us and CHAR_EM stands in for it. */
function rtlLines(text: string, fontSize: number, boxWidth: number) {
  const maxChars = Math.max(8, Math.floor(boxWidth / (fontSize * CHAR_EM)));
  const lines: string[][] = [];
  let cur: string[] = [];

  for (const word of text.split(/\s+/).filter(Boolean)) {
    const next = [...cur, word];
    if (cur.length && next.join(" ").length > maxChars) {
      lines.push(cur);
      cur = [word];
    } else {
      cur = next;
    }
  }
  if (cur.length) lines.push(cur);
  return lines;
}
عدّ المحارف بدل قياس المحارف المرسومة أسلوب فجّ، وهو الخيار الوحيد المتاح داخل Satori. اضبط الثابت على عناوين حقيقية لا على معادلة.

الحالة التي عجزنا عن إصلاحها

حُلّ التباعد. أما خطأ القياس الكامن فلا، ولا يمكن حلّه من داخل Satori. فلأن صندوق كل كلمة أوسع من حبرها بمقدار متفاوت، تجلس المحارف داخل صناديقها بإزاحات مختلفة، والنتيجة البصرية عنوان لا يحاذي هامشه.

جُرِّب وقيس كل ما يبدو أنه يحرّك الحبر: alignItems، والاتجاه على السطر، والاتجاه على الكلمة المفردة، وrow-reverse، وmarginLeft بقيمة auto، وtextAlign. ولا شيء منها يحرّكه، لأن لا شيء منها يعالج الفجوة بين الصندوق والحبر. إنها تحرّك الصناديق. وإثبات ذلك يقيناً لا بالعين يحتاج فحصاً لكل محرف بدل لقطة الشاشة.

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

الاحتفاظ بمسار كود صحيح غير مستخدم، مع تدوين السبب، أرخص من إعادة اكتشاف هذا كله يوم تتعلم المكتبة الأصلية التشكيل.

إن كنت تولّد صوراً بنص لا تستطيع تشكيله

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

لنبنِ الموقع الذي يستحقه عملك.

أرسل ما لديك الآن وإلى أين تريد الوصول. ستحصل على إجابة صريحة حول النطاق والجدول الزمني والتكلفة، عادة خلال ساعة.

نعمل عبر أمريكا الشمالية وأوروبا والشرق الأوسط.