لماذا استبدلنا ثلاثة محركات هندسية لضربات قلم التمييز بالطوابع
كشفت بيانات Apple Pencil الحقيقية عن عيوب لم ترها الاختبارات الاصطناعية، فنقلت Lulucat Notes من ثلاثة محركات للمخططات إلى نموذج الطوابع في MaLiang.
بدأت Lulucat Notes بمصيّر تقليدي لقلم التمييز: إنشاء مخطط هندسي حول المسار المأخوذ منه عينات، ثم ملء المخطط مرة واحدة بلون شبه شفاف. تمنع التعبئة الواحدة ظهور الأشرطة الأغمق حيث تتراكب المقاطع شبه الشفافة. كما تجعل كل وصلة وتقاطع ذاتي ونهاية وضربة قصيرة جزءًا من المشكلة الهندسية نفسها.
أزالت ثلاثة أجيال من الهندسة المخصصة كلٌّ منها فئة من العيوب، لكنها كشفت فئة أخرى. يستخدم المصيّر الذي نشحنه الآن طوابع دائرية ناعمة مبنية على MaLiang، وهو إطار رسم مفتوح المصدر بترخيص MIT طوّره Harley-xk. وفي مقابل إزالة فئة كاملة من إخفاقات المخطط، قبلنا بتقاطعات ذاتية أغمق ونهايات تتدرج بحسب الضغط.

لقطة الشاشة الأصلية من الجهاز عند تكبير 228%. تظهر خطافات صغيرة على طرفي عدة ضربات.
أنتجت الكتابة اليدوية الحقيقية خطافات لم تُنتجها المشاهد الاصطناعية
غطّت مشاهدنا الاصطناعية الخطوط المستقيمة والأقواس والنقرات القصيرة وحركات الرجوع وتغيرات الضغط والانعطافات الحادة، وقد صُيّرت كلها بنظافة. لكن الكتابة الصينية الحقيقية كانت لا تزال تنتج خطافات بطول يقارب 1–3 نقاط عند بداية كثير من الضربات ونهايتها.
لذلك أضفنا زر حفظ إلى التطبيق وسجّلنا 29 ضربة كاملة. احتفظت كل عينة بموضعها ونصف قطرها وضغطها وسمتها وارتفاعها وطابعها الزمني وأعلام التقدير في UIKit. فكّ برنامج يعمل دون اتصال ذلك JSON وجمّع الملفات نفسها Stroke.swift وStrokeRenderer.swift وHighlighterStrokeBuilder.swift التي يستخدمها التطبيق. تطابق خرجه مع لقطة شاشة الجهاز ضربةً بضربة، بما في ذلك الخطافات.
وبما أن المصيّر غير المتصل أعاد إنتاج العيب من المدخلات المسجلة وحدها، فقد كان السبب في العينات والهندسة، لا في سلوك العرض. وأصبح بإمكاننا قراءة العينات الدقيقة التي دخلت إلى المصيّر.
ألقت فرضيتنا الأولى اللوم على اتجاه القلم المقدّر عند ملامسة السطح ورفع القلم. لكن البيانات رفضت ذلك. عبر الضربات الـ29 كلها، تغيّر السمت بحد أقصى 0.09 راديان والارتفاع بحد أقصى 0.02 راديان ضمن العينات الثماني الأولى والأخيرة، وكانت كل أعلام التقدير false على ذلك iPad. ظل توجيه القلم ثابتًا بينما تغيّر الضغط واتجاه الحركة بحدة.
جاء حد الضغط الأدنى من فجوة واضحة في البيانات
كانت العينة الأخيرة من كل ضربة مسجلة تحمل ضغطًا 0.000. وكانت عينات الكتابة عند 0.01 أو فوقه، بينما كانت العينات المسجلة بعد مغادرة Pencil للسطح عند 0.002 أو دونه. وغالبًا ما استمرت تلك العينات الهوائية 2–7 نقاط وقطعت مسافة 0.8–9.0 نقاط، كما كان اتجاهها قد ينحرف نحو 90 درجة تقريبًا بعيدًا عن الضربة المكتوبة.
توضح الضربة 2 الآلية. انتهى جسمها بين 143 و180 درجة. ثم انحرف الذيل منخفض الضغط من −90 إلى −67 درجة وقطع 8.5 نقاط أخرى. عامل مصيّر المخطط النقطة الهوائية الأخيرة على أنها نقطة النهاية المعتمدة، فمدّ قطعة هندسية ضيقة نحوها.
تترك الضغوط المقاسة فارقًا يقارب مرتبة عشرية كاملة بين حركة الكتابة والحركة الهوائية، لذلك وضعنا حد القطع داخل تلك الفجوة:
private static let pressureFloor: CGFloat = 0.005
تُزال عينات الضغط المنخفض عند الطرفين فقط. أما عينة الضغط المنخفض في منتصف الضربة فتبقى جزءًا منها. تتبع القاعدة الحد الفاصل المقاس للحدث، وتترك المسار المكتوب دون تعديل.

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

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

يقطع قيد النهاية العام جسم ضربة منحنية.

يؤدي قصر قصّ النهايات على البدائيات الواقعة ضمن نصف عرض واحد من النهاية إلى استعادة هذه الضربة.
جعلت بوابة طول القوس قصّ النهايات محليًا وأصلحت كل حالة مسجلة. ومع ذلك، أظهر خطاف اصطناعي شديد التطرف شقًا أبيض صغيرًا عند التقاء طرف منخفض الضغط بالنهاية المقصوصة. وهكذا استبدل المحرك الثالث مرة أخرى فشلًا هندسيًا بفشل آخر.
يزيل MaLiang الحد الصلب
يبني MaLiang الضربة من طوابع نسيجية متكررة بدلًا من مخطط مغلق واحد. يستخدم مولّد مساره مقاطع Bézier تربيعية تمر عبر منتصفات العينات المتجاورة، ويضع مصيّر الخط الطوابع بخطوة منتظمة على طول ذلك المسار. ويمكن أن يتغير حجم الطابع ودورانه ولونه وعتامته على طول الضربة.
لا يحمل الطابع الناعم نهاية أو وصلة أو قاعدة التفاف منفصلة. ويظل التغير الحاد في الاتجاه سلسلة من العلامات المتداخلة. ولا يمكن لحركات الرجوع أن تنشئ جيبًا فارغًا في مضلع، كما لا يمكن لنصف مستوى عند النهاية أن يقطع منتصف الضربة، لأن النموذج لا يحتوي على قصّ عند النهاية.
اعتمدنا النموذج لا الشفرة. ينفذ HighlighterStrokeBuilder الحالي أربع عمليات:
- إزالة عينات النهايتين فقط التي يكون ضغطها أقل من
0.005. - تنعيم المواضع باستخدام مقاطع Bézier تربيعية تمر عبر منتصفات العينات.
- وضع طابع دائري واحد لكل نقطة من طول القوس.
- ضبط قطر الطابع انطلاقًا من الضغط المطبّع، وتركيب الطوابع باستخدام ألفا source-over.
انخفض عدد أسطر المصيّر من 686 إلى 190. وغادرت السمت والارتفاع وبناء المخطط وأقنعة الاتحاد ونوافذ اتجاه النهايات وتمديد النهاية وقصّ النهايات مسار قلم التمييز بالكامل. وما زلنا نسجل اتجاه القلم، لأن نموذج الإدخال يدعمه، لكن قلم التمييز نفسه لم يعد يقرأ تلك الحقول.
احتاجت صيغة ضغط MaLiang إلى معايرة الجهاز
يحدد MaLiang حجم الطابع بصيغة مكافئة للصيغة التالية:
وتفترض هذه الصيغة أن force يستخدم جزءًا مفيدًا من المجال الممتد من 0 إلى 1. لكن بيانات جهازنا لم تفعل ذلك. فعبر نحو 3,400 عينة من Apple Pencil، كان وسيط الضغط 0.047، بينما بلغ الحد الأقصى 0.178. وأدى إدخال تلك القيم مباشرة في الصيغة إلى جعل الكتابة العادية أرق بكثير من حجم الفرشاة الاسمي.
لذلك طبّعنا حول الوسيط المرصود، ثم طبقنا الأسّ وحصرنا النتيجة:
يرتبط ضغط الكتابة الوسيط الآن بقطر اسمي قدره 12 نقطة. وتظل الملامسة الخفيفة جدًا مرئية عند 35% من الحجم الاسمي، بينما يتوقف الضغط الشديد عن زيادة الحجم عند 120%.
تنتمي هذه المعايرة إلى جهاز الإدخال وسلوك الفرشاة، لا إلى MaLiang. فنسخ الصيغة الأصلية دون قياس توزيع الضغط الخاص بنا كان سيحافظ على البنية، لكنه سينتج فرشاة خاطئة.
جاءت ألفا كل طابع من عدد التداخل المعروف
يعوّض MaLiang عن تراكب الطوابع شبه الشفافة بالتعبير التجريبي alpha ÷ overlapping × 2.5. ولأن فرشاتنا ذات قطر اسمي ثابت قدره 12 نقطة وخطوة طابع قدرها 1 نقطة، فإن خط المركز يتلقى نحو 12 طبقة. وعدد التداخل معروف في حالتنا، لذلك يمكن حل التعويض بدلًا من تقديره.
من أجل عتامة ضربة مستهدفة
ولذلك يحتاج الطابع الواحد إلى
مع

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