كيف تعلمنا رسم النصوص على القماش

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



تتحرك مع فلاش


أنشأنا المنتج في عام 2015 على Flash. يوجد داخل Flash محرر نصوص يمكنه العمل بشكل جيد مع النصوص ، لذلك لم نكن بحاجة إلى القيام بأي شيء إضافي للعمل مع النصوص. ولكن في ذلك الوقت كان فلاش يموت بالفعل ، لذلك انتقلنا منه إلى HTML / Canvas. وقبلنا كانت المهمة هي عرض النص على لوحة الرسم القماشية كما هو الحال في محرر html ، مع عدم كسر النصوص التي تم إنشاؤها في إصدار Flash عند النقل.

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

كمحرر ، استخدمنا مكتبة مفتوحة ، لكن المكتبات الجاهزة للعرض من html إلى Canvas لم تتناسب مع سرعة العمل والوظائف غير الكافية.

درسنا عدة حلول:

  • Canvas.fillText القياسية. قادرة على رسم النص كما هو الحال في لغة تأشير النص الفائق ، ويمكن تصميمها ، ويعمل في جميع المتصفحات. لكنه لا يعرف كيفية رسم الروابط كما هو الحال في النصوص متعددة الأسطر لمحرر HTML بتنسيقات مختلفة. يمكن حل هذه الصعوبات ، ولكنها تتطلب الكثير من الوقت ؛
  • رسم DOM على رأس قماش. الخيار لم يناسبنا ، لأنه في منتجنا ، كل كائن تم إنشاؤه به فهرس z الخاص به على القماش. وخلطه مع مؤشر DOM z لن ينجح.
  • تحويل أتش تي أم أل إلى SVG. إنه قادر على تحويل html إلى صورة بفضل عنصر foreignObject. هذا يتيح لك خبز html داخل svg والعمل معها كصورة. لقد اخترنا هذا الخيار.

ميزات SVG foreignObject


كيف يعمل SVG foreignObject: لدينا HTML من المحرر → نضع HTML في foreignObject ← بعض السحر ← نحصل على الصورة ← أضف الصورة إلى قماش



عن السحر. على الرغم من أن معظم المتصفحات تدعم علامة foreignObject ، فإن لكل منها خصائصه الخاصة لاستخدام النتيجة مع قماش. يعمل FireFox مع كائن Blob ، في Edge تحتاج إلى القيام Base64 للصورة وإرجاع عنوان url للبيانات ، وفي IE11 لا تعمل العلامة على الإطلاق.

getImageUrl(svg: string, browser: string): string { let dataUrl = '' switch (browser) { case browsers.FIREFOX: let domUrl = window.URL || window.webkitURL || window let blob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'}) dataUrl = domUrl.createObjectURL(blob) break case browsers.EDGE: let encodedSvg = encodeURIComponent(svg) dataUrl = 'data:image/svg+xml;base64,' + btoa(window.unescape(encodedSvg)) break default: dataUrl = 'data:image/svg+xml,' + encodeURIComponent(svg) return dataUrl } 

بعد العمل مع SVG ، حصلنا على أخطاء مثيرة للاهتمام لم نلاحظها على Flash. تم عرض النص بنفس الحجم والخط في المتصفحات المختلفة بشكل مختلف. على سبيل المثال ، يمكن التفاف الكلمة الأخيرة في سطر وتشغيلها في النص أدناه. كان من المهم بالنسبة لنا أن يحصل المستخدمون على نفس النوع من الأدوات ، بغض النظر عن المتصفحات التي يعملون بها. لم يكن هناك مشكلة مع فلاش على هذا ، كما هو نفسه في كل مكان.



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

ثانياً ، تجريبياً توصلنا إلى استنتاج مفاده أنه من الضروري إضافة بعض أنماط css غير العادية للمحرر و svg من أجل تقليل الفرق في العرض بين المتصفحات:


ما حصلنا عليه في النهاية بفضل SVG <foreignObject>:

  • يمكننا رسم أي لغة تأشير النص الفائق: النص والجداول والرسومات
  • تقوم العلامة بإرجاع صورة متجهة.
  • تعمل العلامة في جميع المتصفحات الحديثة باستثناء IE11

لماذا تخلينا عن ForeignObject


كان كل شيء يعمل بشكل جيد ، ولكن بمجرد وصول المصممين إلينا وطلب إضافة دعم خط لإنشاء نماذج بالحجم الطبيعي.



تساءلنا عما إذا كان يمكننا القيام بذلك مع foreignObject. اتضح أن لديه ميزة ، عند حل هذه المشكلة ، يصبح عيبًا قاتلًا. يمكنه عرض HTML داخل نفسه ، لكن لا يمكنه الوصول إلى الموارد الخارجية ، لذلك يجب تحويل جميع الموارد التي يعمل معها إلى base64 وإضافتها داخل svg.



هذا يعني أنه إذا كان لديك أربعة نصوص كتبها OpenSans ، فعليك تنزيل هذا الخط للمستخدم أربع مرات. هذا الخيار لم يناسبنا.

قررنا أن نكتب نصنا القماشي مع ... الأداء الجيد ، ودعم الصور المتجهة ، ونحن لن ننسى IE 11

ما أهمية صورة المتجه لنا؟ في منتجنا ، يمكن تكبير أي كائن على اللوحة ، وبصورة متجهة يمكننا إنشاءه مرة واحدة فقط وإعادة استخدامه بغض النظر عن التكبير / التصغير. يرسم Canvas.fillText صورة نقطية: في هذه الحالة ، نحتاج إلى إعادة رسم الصورة مع كل تكبير ، والتي ، كما اعتقدنا ، تؤثر بشكل كبير على الأداء.

إنشاء نموذج أولي


بادئ ذي بدء ، أنشأنا نموذجًا أوليًا بسيطًا لاختبار أدائه.



مبدأ تشغيل النموذج الأولي:

  • نعطي وظيفة "النص" ؛
  • منه نحصل على كائن فيه كل كلمة من النص ، مع إحداثيات وأنماط للعرض ؛
  • إعطاء الكائن إلى Canvas؛
  • قماش يرسم النص.

كان للنموذج الأولي العديد من المهام: للتحقق من أن عملية إعادة الرسم على القماش باستخدام التحجيم ستتم دون تأخير وأن وقت تحويل html إلى كائن لن يكون أكثر من إنشاء صورة svg.

تعامل النموذج الأولي مع المهمة الأولى ، ولم يؤثر القياس تقريبًا على الأداء عند رسم النصوص. كانت هناك مشاكل في المهمة الثانية: معالجة كميات كبيرة من النص تستغرق وقتًا كافيًا وأظهرت قياسات الأداء الأولى نتائج سيئة. لرسم نص من 1K حرف ، استغرق النهج الجديد ما يقرب من 2 مرات أكثر من svg.


قررنا استخدام الطريقة الأكثر موثوقية لتحسين الشفرة - "استبدل الاختبار بالطريقة التي نحتاجها" ؛-). ولكن على محمل الجد ، ذهبنا إلى المحللين وسألنا عن المدة التي يتم فيها إنشاء النصوص في أغلب الأحيان بواسطة مستخدمينا. اتضح أن متوسط ​​حجم النص هو 14 حرفًا. لمثل هذه النصوص القصيرة ، أظهر نموذجنا الأولي نتائج أداء أفضل بكثير ، مثل اعتماد السرعة على حجم النص خطي ، ويتم التقريب دائمًا في svg في نفس الوقت ، بغض النظر عن طول النص. إنها مناسبة لنا: يمكننا أن نخسر الأداء في النصوص الطويلة ، ولكن في معظم الحالات ستكون سرعتنا أفضل من svg.


بعد عدة تكرارات للعمل على تحديث Canvas Text ، حصلنا على الخوارزمية التالية:

المرحلة 1. نحن اقتحام كتل المنطقية

  1. نقسم النص إلى كتل: فقرات ، قوائم ؛
  2. نقوم بتقسيم الكتل إلى كتل أصغر وفقًا للأنماط ؛
  3. نقسم الكتل إلى كلمات.

المرحلة 2. نجمعها في كائن واحد مع الإحداثيات والأنماط

  1. حساب عرض وارتفاع كل كلمة في بكسل.
  2. نقوم بتوصيل الكلمات المقسمة ، حيث تم تقسيم بعض الكلمات في النقطة 2 إلى عدة كلمات ؛
  3. من الكلمات التي نجمعها السطور ، إذا كانت الكلمة لا تتناسب مع الخط ، فإننا نقطعها حتى تلائمها ؛
  4. نحن نجمع الفقرات والقوائم.
  5. نحسب س ، ص لكل كلمة.
  6. نحصل على كائن جاهز للعرض.

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

نتيجةً لذلك ، قدمنا ​​الدعم للخطوط و IE 11 ، وقمنا بتغطية كل شيء باختبارات الوحدة ، وأصبحت سرعة العرض أعلى في معظم الحالات من سرعة foreignObject. فحص في مستخدمي بيتا وأفرج عنه. يبدو أن النجاح!

استمر النجاح 30 دقيقة


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



لحسن الحظ ، لم يكن إضافة دعم لنظام الكتابة باليد أمرًا صعبًا ، نظرًا لأن Canvas.fillText القياسي يدعمه بالفعل.

لكن بينما كنا نتعامل مع هذا ، فقد صادفنا حالات أكثر إثارة للاهتمام لم يعد بإمكان fillText دعمها. لقد صادفنا نصوص ثنائية الاتجاه يتم فيها كتابة جزء من النص من اليمين إلى اليسار ، ثم من اليسار إلى اليمين ومرة ​​أخرى من اليمين إلى اليسار.



كان الحل الوحيد الذي عرفناه هو الدخول في مواصفات W3C للمتصفحات ومحاولة تكرار ذلك داخل Canvas Text. كان الأمر صعباً ومؤلماً ، لكننا تمكنا من إضافة الدعم الأساسي. المزيد عن ثنائي الاتجاه: واحد واثنين .

استنتاجات موجزة التي قطعناها على أنفسنا


  1. لعرض HTML في صورة ، استخدم SVG foreignObject؛
  2. دائما تحليل المنتج الخاص بك لاتخاذ القرارات ؛
  3. جعل النماذج. يمكنهم إظهار أن القرارات المعقدة يمكن أن تبدو فقط للوهلة الأولى ؛
  4. اكتب الكود على الفور حتى يمكن تغطيته بالاختبارات ؛
  5. في منتج دولي ، من المهم ألا ننسى أن هناك العديد من اللغات المختلفة ، بما في ذلك biderectional.

إذا كان لديك خبرة في حل مثل هذه المشاكل - شاركها في التعليقات.

Source: https://habr.com/ru/post/ar458624/


All Articles