طباعة إيصال على طابعة حرارية تعني أن ترسل لها أوامر ESC/POS: سلسلة من البايتات (bytes) تقول «فعّل الخط العريض»، «وسّط هذا السطر»، «اطبع هذا الباركود»، «اقطع الورق». مع النص اللاتيني الأمر سهل نسبيًا. مع العربية تظهر المشاكل بسرعة: الطابعة لا تشكّل الحروف، فيخرج كل حرف بشكله المنفصل، وتطبع البايتات بالترتيب الذي تستقبلها به، فيخرج السطر المكتوب من اليمين إلى اليسار معكوسًا. وفوق ذلك، الطابعة تريد النص بترميز code page تعرفه، لا بـ UTF-8.
escpos_arabic حزمة (package) مكتوبة بـ Dart فقط، تبني هذه البايتات وتتعامل مع العربية كحالة أساسية. تشكّل الحروف، تعيد ترتيب السطر، ترمّزه إلى CP864 أو Windows-1256، وتغطي بقية الإيصال أيضًا: التخطيط، الصور، الباركود ورموز QR. يصفها الـ README بأنها بديل مُصان لحزمة esc_pos_utils.
نقطة يجب توضيحها من البداية: الحزمة تبني البايتات فقط. إرسالها عبر Bluetooth أو USB أو الشبكة خارج نطاقها، وأنت تختار حزمة النقل التي تناسبك.
ماذا تقدّم
مولّد مرتبط بملف تعريف الطابعة. تنشئ Generator لـ PrinterProfile معيّن: ورق 58 مم أو 80 مم، عدد الحروف في السطر للخط A والخط B، أرقام الـ code pages ونوع الجرس. كل دالة تُرجع List<int>، فالإيصال قائمة تبنيها بعامل النشر (spread operator).
المولّد يغطي الأوامر المعتادة: التهيئة ESC @، الخط العريض، التسطير (نقطة أو نقطتان)، double-strike، العكس (reverse)، الخط A/B، مضاعفات العرض والارتفاع من 1 إلى 8، المحاذاة، تباعد الأسطر، تغذية الورق، القطع الكامل أو الجزئي، نبضة درج النقود، والجرس إن كان الملف يحتوي عليه. ويتتبّع تغيّرات النمط والمحاذاة والـ code page، فلا يرسل إلا الأوامر التي تغيّرت فعلًا.
التخطيط. الدالة row([PosColumn(text, flex/width, align)]) تبني الأعمدة، مع التفاف النص داخل كل خانة حسب عرض العرض: حروف شرق آسيا العريضة تأخذ خانتين وعلامات التشكيل لا تأخذ شيئًا. hr() ترسم خطًا فاصلًا، وkeyValue تطبع أسطرًا مثل Total ........ 1 250,00 DA.
الباركود. UPC-A و UPC-E و EAN-13 و EAN-8 و CODE39 و ITF و CODABAR و CODE128 (المجموعات A و B و C)، مع موضع وخط النص المقروء (HRI)، الارتفاع وعرض الوحدة. والمدخلات تُفحص: الأطوال، مجموعات الحروف، أرقام التحقق، وضغط الأصفار في UPC-E.
رموز QR. تستعمل أمر الطابعة الأصلي GS ( k، مع النموذج، حجم الوحدة ومستوى تصحيح الخطأ.
الصور. تحويل إلى تدرّج الرمادي، ثم dithering بطريقة threshold أو Floyd–Steinberg أو Atkinson، تحجيم إلى العرض القابل للطباعة، إرسال raster بأمر GS v 0 على دفعات، مع بديل ESC * بنمط 24 نقطة.
العربية بطريقتين. وهذا هو الجزء الذي سُمّيت الحزمة باسمه.
- وضع code page. الدالة
arabicTextتختار أشكال الحروف حسب موقعها (منفصل، أول، وسط، آخر) وتركّب «لا» (lam-alef)، ثم تمرّر السطر على خوارزمية bidi مبسّطة حتى تبقى الأرقام والنص اللاتيني من اليسار إلى اليمين، ثم ترمّز إلى CP864 (بحروف مشكّلة) أو Windows-1256. ورقمESC t nقابل للضبط. - وضع raster (الموصى به). ترسم السطر بمحرك النصوص في Flutter وتطبعه كصورة. في الـ README دالة مساعدة جاهزة للنسخ اسمها
renderTextLineمبنية علىParagraphBuilderمنdart:ui.
يمكن استعمال الطريقتين في نفس الإيصال. وهناك code pages أخرى: CP437 و CP850 و CP858 (فيها رمز اليورو) و CP1252، مع حرف بديل لكل ما لا تستطيع الصفحة ترميزه.
طريقة الاستعمال
أضِفها من GitHub:
dependencies: escpos_arabic: git: https://github.com/AbdeldjalilChougui/escpos_arabic ثم ابنِ الإيصال:
import 'package:escpos_arabic/escpos_arabic.dart'; List<int> buildReceipt() { final g = Generator(PrinterProfile.mm80, codePage: CodePage.cp858); return [ ...g.reset(), ...g.text('CAFE DU PORT', styles: const PosStyles( align: PosAlign.center, bold: true, width: 2, height: 2)), ...g.arabicText('مقهى الميناء', styles: const PosStyles(align: PosAlign.center)), ...g.hr(), ...g.row([ PosColumn('Café crème', flex: 3), PosColumn('x2', width: 4, align: PosAlign.right), PosColumn('400,00', width: 10, align: PosAlign.right), ]), ...g.keyValue('Total', '1 250,00 DA', styles: const PosStyles(bold: true)), ...g.arabicText('المجموع: 1 250,00 دج'), ...g.barcode(Barcode.ean13('5901234123457'), height: 60), ...g.qrCode('https://abdeldjalilchougui.biz/', size: 5), ...g.feed(2), ...g.cut(), ]; } النتيجة قائمة بايتات عادية. لطابعة على الشبكة، يرسلها الـ README عبر TCP خام على المنفذ 9100:
Future<void> printOverTcp(List<int> bytes, String host) async { final socket = await Socket.connect(host, 9100, timeout: const Duration(seconds: 5)); socket.add(bytes); await socket.flush(); await socket.close(); } وفي الـ README أمثلة أخرى مع print_bluetooth_thermal 1.2 و flutter_thermal_printer 2.x، وهما الحزمتان اللتان تم التحقق من الأمثلة معهما.
أرقام code page تختلف من طابعة لأخرى
الرقم الذي ترسله مع ESC t لاختيار code page ليس موحّدًا. القيم الافتراضية هي أرقام Epson (PC437 = 0، PC850 = 2، WPC1252 = 16، PC858 = 19، PC864 = 37، WPC1256 = 50). العلامات الأخرى تستعمل أرقامًا مختلفة، فاطبع صفحة الاختبار الذاتي (self-test) للطابعة وضع أرقامها في الملف:
final profile = PrinterProfile.mm58.copyWith( codePages: { CodePage.cp437: 0, CodePage.cp858: 19, CodePage.cp864: 22, CodePage.cp1256: 50, }, beeper: BeeperType.epsonPattern, ); final g = Generator(profile, arabicCodePage: CodePage.cp864); الانتقال من esc_pos_utils
في الـ README جدول للانتقال. أغلب النداءات تحتفظ بشكلها: text مع PosStyles كما هي، PosStyles(height: PosTextSize.size2) تصبح PosStyles(height: 2)، qrcode تصبح qrCode، drawer() تصبح drawerKick()، والصفوف تأخذ flex أو width بعدد الحروف بدل شبكة الـ 12 عمودًا. وملفات التعريف كائنات Dart عادية، فلا حاجة لتحميل أي assets.
أشياء يجب معرفتها
- لم تُطبع على طابعة حقيقية بعد. الـ README يقولها صراحة: تم اختبار البايتات الناتجة، لكنها لم تُطبع على جهاز أثناء كتابة الحزمة. أرقام الأوامر تتبع مرجع Epson، والطابعات المقلّدة تختلف: بعضها يتجاهل
GS v 0أوGS ( kأو UPC-E بـ 6 إلى 8 أرقام أو الجرس. - استعمل مسافة غير منقسمة داخل الأرقام. في السطر العربي، افصل مجموعات الأرقام بـ
لا بمسافة عادية. حسب UAX #9، المسافة العادية تقسم1 250إلى رقمين يظهران بترتيب من اليمين إلى اليسار (250 1). - CP864 لا تحتوي على كل الأشكال. الشكل المفقود يُعوَّض بأقرب شكل (الوسط يصبح أولًا، والآخر يصبح منفصلًا)، والألف بهمزة تحتها تُطبع ألفًا. وعلامات التشكيل تُحذف افتراضيًا.
- CP1256 تُطبع غالبًا بحروف غير متصلة. الطابعة تستقبل الحروف الأساسية غير مشكّلة بالترتيب المرئي، وأغلب الطابعات لا تصلها. الأفضل CP864 أو وضع raster.
- خوارزمية bidi مبسّطة. لا embeddings ولا overrides ولا isolates، ولا مطابقة للأقواس، وتعمل على سطر واحد في كل مرة. والتشكيل يغطي الأشكال الموجودة في Unicode فقط، بلا خصائص OpenType غير «لا». لأفضل جودة استعمل وضع raster.
- لا يوجد وضع multi-byte للغات CJK. الحروف العريضة تُقاس للتخطيط فقط.
- بايتات فقط. لا اكتشاف للطابعات ولا اتصال ولا قراءة للحالة.
وللنص المرسوم كصورة، Dithering.threshold يحافظ على حواف واضحة. اترك Floyd–Steinberg و Atkinson للصور والشعارات.
لماذا بنيتها
esc_pos_utils لم تكن تدعم العربية إطلاقًا، والعربية هي الجزء الصعب في الإيصال: الطابعة لا تشكّل الحروف ولا تعيد ترتيب السطر، فإرسال النص كما هو يعطي نتيجة غير مقروءة. حلّ ذلك يحتاج ثلاثة أشياء في مكان واحد: جداول التشكيل، خوارزمية bidi، ومرمّزات الـ code pages. أردتها بجانب أوامر الإيصال، بـ Dart فقط، بلا كود native وبلا assets، مع مسار ثانٍ (raster) حين لا تكفي code pages الطابعة.
والجداول ليست مكتوبة باليد: تُولَّد من Unicode Character Database 18.0.0 ومن جداول code pages التي تنشرها Unicode، عبر dart run tool/generate_tables.dart، وصيغ الأوامر تتبع مرجع ESC/POS من Epson.
احصل على الكود
المصدر، والـ README مع دالة raster وأمثلة النقل، ومثال قابل للتشغيل، كلها على GitHub: https://github.com/AbdeldjalilChougui/escpos_arabic. الرخصة MIT. وإن جرّبتها على طابعة تتصرف بشكل مختلف، فمرحبًا بـ issue فيه الموديل وصفحة الاختبار الذاتي.
نُشر في الأصل على abdeldjalilchougui.biz.
