Посібник з програмування FGL для термопринтерів

Практичний довідник для розробників, що працюють з Friendly Graphics Language | English

1. Що таке FGL

FGL (Friendly Graphics Language), також відома як Ghostwriter Printer Language, — це командна мова для керування термопринтерами. Вона широко використовується у продажу квитків, організації подій, друку посадкових талонів та етикеток, де стандартом є термопринтери (прямого або трансферного друку).

На відміну від мов опису сторінок, таких як PostScript або PCL, FGL — це легковісний потоковий протокол. Ви надсилаєте послідовність команд у кутових дужках, за якими йдуть дані, і принтер безпосередньо відтворює результат. Моделі документа немає — ви позиціонуєте кожен елемент (текст, штрихкод, лінію, зображення) за допомогою абсолютних координат у точках (dots).

FGL підтримує:

Типові розміри квитків — 2.125" × 5.5" (ширина кредитної картки) або 2.125" × 3.375" з роздільною здатністю 200 або 300 DPI. Цей посібник базується на офіційному керівництві з програмування BOCA Systems FGL46 у поєднанні з практичним досвідом впровадження.

Примітка щодо сумісності: FGL пройшла кілька ревізій (FGL26, FGL42, FGL44, FGL46). Деякі команди залежать від версії — наприклад, 2D штрихкоди (PDF-417, Data Matrix, Aztec) потребують FGL46, а назви окремих команд (наприклад, режим перезапису) відрізняються між ревізіями прошивки. Використовуйте команду <PROM>, щоб перевірити версію прошивки вашого принтера, і завжди тестуйте на цільовому обладнанні.

2. Структура команд

Кожна команда FGL обрамлена кутовими дужками. Команди з'єднуються одна за одною без розділювачів, а дані для друку йдуть безпосередньо після послідовності команд.

<COMMAND><COMMAND>data

Наприклад, щоб надрукувати текст певним шрифтом у заданій позиції:

<F3><HW1,1><NR><RC100,50>Hello World<WH1,1>

Читаючи зліва направо:

  1. <F3> — вибрати шрифт 3 (OCR-B, 17×31)
  2. <HW1,1> — встановити масштабування висоти та ширини 1×
  3. <NR> — без обертання
  4. <RC100,50> — позиція: рядок 100, стовпець 50 (у точках)
  5. Hello World — текст для друку
  6. <WH1,1> — скинути масштабування ширини/висоти після тексту

Завершення завдання друку здійснюється командою друку. Найпоширеніший термінатор — <q>. У деяких ревізіях прошивки <q> друкує без відрізання, тоді як <p> друкує та відрізає. В інших <q> є стандартною командою завершення квитка. Завжди перевіряйте поведінку для вашої конкретної прошивки. Команда <FF> (переведення сторінки, 0CH) є універсальною альтернативою друку з відрізанням:

<F3><HW1,1><NR><RC100,50>Hello World<WH1,1>
<q>

3. Система координат та DPI

FGL використовує систему координат на основі точок (dots). Команда <RCrow,column> позиціонує елементи, де:

Початок координат <RC0,0> — це верхній лівий кут квитка.

Точки та дюйми: Щоб перевести дюйми в точки, помножте на DPI принтера. При 300 DPI, 1 дюйм = 300 точок. Позиція 0.5" від верху та 1" від лівого краю стає <RC150,300>.

Типові розміри квитків

Розмір (дюйми)При 200 DPIПри 300 DPI
2.0" × 5.5"400 × 1100600 × 1650
2.125" × 5.5"425 × 1100637 × 1650
2.5" × 5.5"500 × 1100750 × 1650
2.7" × 5.5"540 × 1100810 × 1650

При роботі в альбомній орієнтації ширина та висота міняються місцями. Квиток 2.125" × 5.5" в альбомному режимі має довшу сторону як ширину (1650 точок при 300 DPI).

4. Текст та шрифти

FGL надає до 16 вбудованих шрифтів (F1–F13 є найпоширенішими, плюс F14–F16 на окремих моделях), які вибираються командою <Fn>. Кожен шрифт має фіксований розмір комірки символу (ширина × висота в точках). Зверніть увагу, що F5 — це спеціальний шрифт, доступний не на всіх принтерах:

КомандаНазваКомірка (200 DPI)Комірка (300 DPI)Стиль
<F1>Font 15×77×8Базовий ASCII
<F2>Font 28×1610×18Базовий ASCII
<F3>OCR-B17×3120×33OCR-B стандартний
<F4>OCR-A5×97×11OCR-A стандартний
<F6>Large OCR-B30×5234×56OCR-B великий
<F7>OCR-A Full15×2920×31Повний набір OCR-A
<F8>Courier20×4020×33Courier
<F9>Small OCR-B13×2013×22OCR-B компактний
<F10>Prestige25×4128×41Prestige жирний
<F11>Script25×4926×49Script
<F12>Orator46×9147×91Високий, жирний
<F13>Courier Intl20×4020×42Courier + міжнародний
<F16>Cyrillic18×3120×33Набір кириличних символів

Масштабування шрифтів

Команда <HWheight,width> масштабує вибраний шрифт. Значення — цілочисельні множники:

# Подвійна висота, нормальна ширина тексту
<F3><HW2,1><NR><RC100,50>Tall Text<WH1,1>

# Потрійна ширина, подвійна висота
<F3><HW2,3><NR><RC200,50>Wide & Tall<WH1,1>

Завжди скидайте масштабування за допомогою <WH1,1> після тексту, щоб запобігти впливу на наступні елементи.

Повний шаблон текстової команди

<Fn><HWh,w><ROTATION><RCrow,col>text<WH1,1>

5. Обертання

FGL підтримує чотири стани обертання з кроком 90 градусів:

КомандаОбертанняОпис
<NR>Без обертання (за замовчуванням, зліва направо)
<RR>90°Обертання вправо (зверху вниз)
<RU>180°Обертання догори ногами (справа наліво)
<RL>270°Обертання вліво (знизу вгору)

Команда обертання ставиться перед командою позиціонування. Координати <RC> завжди вказують на точку прив'язки елемента, яка зміщується залежно від кута обертання.

# Звичайний текст
<F3><HW1,1><NR><RC100,50>Normal<WH1,1>

# Той самий текст, обернений на 90 градусів за годинниковою стрілкою
<F3><HW1,1><RR><RC100,50>Rotated<WH1,1>

Порада: При обертанні елементів на 180° або 270° може знадобитися коригування координат <RC>, щоб врахувати зміщення точки прив'язки. Верхній лівий початок координат переміщується у протилежний кут після обертання.

6. 1D штрихкоди

FGL підтримує кілька символік 1D штрихкодів. Кожен тип має унікальну літеру-тег, що використовується в команді штрихкоду.

СимволікаТегРоздільникПриклад даних
Code 39N**CODE39*
Code 128O^^CODE128^
EAN-13E5901234123457
EAN-8U12345670
UPCU401234567893
CodabarCA123456B
Interleaved 2of5F1234567890

Також існують застарілі команди вибору штрихкоду: <A=N> (Code39), <A=O> (Code128), <A=E> (EAN-13), <A=U> (UPC/EAN-8), <A=C> (Codabar), <A=F> (Interleaved 2of5). Вони не залежать від обертання і можуть зустрічатися в старіших реалізаціях. Перевага надається сучасному підходу на основі тегів, описаному нижче.

Структура команди штрихкоду

<ROTATION><RCrow,col><Xn><BI><TAGorientationwidth>data

Розбір команд:

КомандаПризначенняЗначення
<Xn>Коефіцієнт розширення штрихів1–9 (2 рекомендовано для читабельності)
<BI>Інтерпретація штрихкодуДрукує людиночитабельний текст під штрихкодом
Літера-тегТип символікиN, O, E, U, C (див. таблицю вище)
ОрієнтаціяНапрямок друкуP = портретний (fence), L = альбомний (ladder)
ШиринаМножник ширини штриха1–8

Орієнтація штрихкоду

Суфікс орієнтації (P або L) визначає, чи друкуються штрихи як паркан (вертикальні штрихи, зчитуються зліва направо) чи як драбина (горизонтальні штрихи, зчитуються зверху вниз):

Для обертання на 180° та 270° літера-тег стає малою:

# Штрихкод Code39, без обертання, портретний, ширина 5
<NR><RC200,50><X2><BI><NP5>*TICKET-001*

# Штрихкод Code128, з інтерпретацією, портретний, ширина 4
<NR><RC300,50><X2><BI><OP4>^ABC-12345^

# Той самий штрихкод, обернений на 180 градусів (мала літера тегу)
<RU><RC300,50><X2><BI><oP4>^ABC-12345^

7. 2D штрихкоди (QR, PDF-417, Data Matrix, Aztec)

FGL46 підтримує чотири символіки 2D штрихкодів. Усі використовують формат даних у фігурних дужках {data} з необов'язковими параметрами конфігурації.

QR-коди

QR-коди — це найпоширеніший 2D штрихкод у FGL. Структура команди використовує параметри версії та розміру модуля:

<NR><RCrow,col><QRVversion><QRsize>data

Команда <QRVn> встановлює версію QR (складність), а <QRn> встановлює розмір модуля в точках:

КомандаМодуліМакс. символів
<QRV2>25 × 25~20
<QRV7>45 × 45~122
<QRV11>61 × 61~251
<QRV15>77 × 77~412

Розміри модулів: <QR4> = 4 точки/модуль (компактний), <QR6> = 6 точок/модуль (легше сканувати). Діапазон розмірів точок — 3–16.

Версія + розмірШирина (300 DPI)Варіант використання
<QRV2><QR6>~0.47"Короткі URL, малі ідентифікатори
<QRV7><QR4>~0.59"Середні дані, компактний
<QRV7><QR6>~0.87"Середні дані, зручний для сканування
<QRV11><QR4>~1.18"Довгі URL, висока щільність
# Малий QR-код для короткого URL
<NR><RC50,400><QRV2><QR6>https://example.com

# Більший QR-код для даних валідації квитка
<NR><RC50,400><QRV7><QR4>EVT-2024-ABCD-1234-WXYZ

QR-коди також підтримують рівні корекції помилок 0–3 (L, M, Q, H) та режими кодування 0–2 при використанні розширеного синтаксису.

PDF-417

PDF-417 — це стековий 2D штрихкод, популярний у транспортній галузі та державних документах. Він може кодувати до ~1 800 символів ASCII. Команда FGL використовує позиційні параметри для стовпців, рядків, корекції помилок та прапорця обрізання:

# PDF-417: стовпці=5, рядки=20, корекція помилок=3, не обрізаний
<NR><RC100,50><p4175,20,3,0>Ticket: EVT-2024-001234

# PDF-417 з тильдою для керуючих кодів
<NR><RC300,50><p4174,15,2,0>~029Data with GS separator

Символ тильди (~) використовується для вбудовування керуючих кодів. Параметри: <p417cols,rows,errLevel,truncated>.

Data Matrix

Data Matrix кодує дані у компактний квадратний або прямокутний візерунок. Корисний, коли простір обмежений. FGL підтримує режими кодування 0–3 та вибір бажаного формату (0–29 попередньо визначених розмірів):

# Data Matrix: режим кодування 0 (авто), бажаний формат 0 (авто розмір)
<NR><RC100,50><dm0,0>https://example.com/verify/12345

# Data Matrix: режим кодування 1 (ASCII), фіксований формат розміру 5
<NR><RC100,300><dm1,5>COMPACT-ID-789

Параметри: <dmencodeMode,preferredFormat>. Формат 0 = автоматичний розмір на основі довжини даних.

Aztec

Aztec-коди використовуються на посадкових талонах (стандарт IATA BCBP) і не потребують зони тиші, що робить їх ефективними за площею. FGL підтримує налаштовувані рівні корекції помилок (5–95%) та кількість шарів:

# Aztec: корекція помилок 23%, авто шари
<NR><RC100,50><az23,0>M1SMITH/JOHN  EABC123 JFKLHRBA 0742 231Y

# Aztec: корекція помилок 50%, фіксовано 8 шарів
<NR><RC100,300><az50,8>HIGH-RELIABILITY-DATA

Параметри: <azerrorPercent,layers>. Використовуйте 0 для шарів, щоб принтер автоматично визначив їх кількість на основі розміру даних.

Примітка: Підтримка обертання 2D штрихкодів залежить від версії прошивки. Старіші прошивки не підтримують команди обертання (<RR>, <RU>, <RL>) для 2D штрихкодів — вони друкуються лише в орієнтації <NR>. Деякі новіші ревізії FGL46 додають часткову підтримку обертання. Завжди тестуйте обертання на вашому конкретному обладнанні. Якщо обертання не працює, обертайте весь макет квитка.

8. Лінії та прямокутники

FGL малює горизонтальні лінії, вертикальні лінії та заповнені прямокутники з налаштовуваною товщиною:

<NR><RCrow,col><LTthickness><HXlength>   # горизонтальна
<NR><RCrow,col><LTthickness><VXlength>   # вертикальна
КомандаПризначення
<LTn>Товщина лінії в точках (наприклад, <LT1> = 1 точка, <LT3> = 3 точки)
<HXn>Малює горизонтальну лінію довжиною n точок
<VXn>Малює вертикальну лінію довжиною n точок
# Тонкий горизонтальний роздільник (1px, 400 точок завширшки)
<NR><RC250,30><LT1><HX400>

# Товста вертикальна межа (3px, 490 точок заввишки)
<NR><RC30,95><LT3><VX490>

# Контур прямокутника (чотири лінії)
<NR><RC100,100><LT1><HX200>  # верх
<NR><RC300,100><LT1><HX200>  # низ
<NR><RC100,100><LT1><VX200>  # ліво
<NR><RC100,300><LT1><VX200>  # право

Лінії завжди використовують <NR> (без обертання). Щоб змінити напрямок лінії, перемикайтеся між <HX> та <VX>. При програмному малюванні ліній під кутами 180° або 270° коригуйте початкові координати, віднімаючи довжину лінії від відповідної осі.

Малювання прямокутників

FGL також надає спеціальну команду прямокутника, яка малює заповнений прямокутник без потреби у чотирьох окремих лініях:

<NR><RCrow,col><LTthickness><DBheight,width>

Команда <DBr,c> (Draw Box) малює прямокутник висотою r точок та шириною c точок. Товщина лінії, встановлена <LT>, застосовується до межі прямокутника:

# Прямокутник з контуром 200x400 та межею в 2 точки
<NR><RC100,50><LT2><DB200,400>

9. Графіка (зображення)

Графічний режим FGL дозволяє керувати окремими точками на квитку. Він використовується для логотипів, іконок та будь-якого графічного контенту. Принтер отримує дані стовпець за стовпцем, де кожен байт представляє 8 вертикальних точок.

Графічні команди

FGL має два графічних режими:

КомандаРежимФормат даних
<Gn>Бінарна графікаСирі байти (значення 0–255)
<gn>ASCII-графікаHex-рядок (2 символи на байт)

ASCII-режим (<g>) частіше використовується в програмних реалізаціях, оскільки уникає проблем з кодуванням бінарних даних. Параметр n у <gn> — це загальна кількість hex-символів (не кількість байтів). Оскільки кожен байт потребує двох hex-символів, n завжди має бути парним. Наприклад, 60 стовпців точкових даних = 60 байтів = 120 hex-символів, тому використовуємо <g120>.

Як працюють точкові дані

Кожен байт представляє стовпець з 8 точок. MSB — верхня точка, а LSB — нижня точка. Біт 1 друкує чорну точку; 0 — порожньо.

Значення байта: 0xA5 = 10100101 у двійковій системі Біт 7 (MSB) █ ← верхня точка (чорна) Біт 6 ░ ← порожньо Біт 5 █ ← чорна Біт 4 ░ ← порожньо Біт 3 ░ ← порожньо Біт 2 █ ← чорна Біт 1 ░ ← порожньо Біт 0 (LSB) █ ← нижня точка (чорна)

В режимі ASCII-графіки цей байт надсилається як два символи A5.

Багаторядкові зображення

Оскільки кожна графічна команда охоплює лише 8 вертикальних точок, вищі зображення розбиваються на горизонтальні смуги. Кожна смуга — це окрема команда FGL, позиціонована на 8 точок нижче попередньої:

# Зображення: 60px завширшки, 24px заввишки = 3 смуги по 8px кожна
# Кожна смуга має 60 стовпців × 2 hex-символи = 120 hex-символів
<NR><RC100,50><g120>FF00FF00...hex data for row 0-7...
<NR><RC108,50><g120>AA55AA55...hex data for row 8-15...
<NR><RC116,50><g120>0F0F0F0F...hex data for row 16-23...

Алгоритм конвертації зображень

Щоб перетворити стандартне зображення (PNG, BMP) у графічні дані FGL:

  1. Конвертувати у монохромне. Кожен піксель стає чорним (1) або білим (0). Поширений підхід — порогова обробка альфа-каналу: непрозорість > 120 = чорна точка.
  2. Розбити на смуги по 8 пікселів. Розділіть висоту зображення на блоки по 8 рядків. Якщо висота не кратна 8, доповніть останню смугу нулями.
  3. Сканувати стовпець за стовпцем. Для кожної смуги пройдіть по кожному стовпцю (позиція x). Зберіть 8 вертикальних пікселів у один байт, де верхній піксель є MSB.
  4. Закодувати в hex. Перетворіть кожен байт у 2-символьний hex-рядок (з доповненням нулями). З'єднайте всі стовпці в один hex-рядок для кожної смуги.
  5. Згенерувати команди FGL. Для кожної смуги створіть <NR><RCy,x><gn>hexdata, де y збільшується на 8 для кожної смуги.

Псевдокод:

// Конвертація зображення в ASCII-графічні команди FGL
function imageToFGL(imageData, width, height, startRow, startCol) {
  const strips = Math.ceil(height / 8)
  const commands = []

  for (let strip = 0; strip < strips; strip++) {
    let hex = ""

    for (let x = 0; x < width; x++) {
      let byte = 0

      for (let bit = 0; bit < 8; bit++) {
        let y = strip * 8 + bit
        if (isBlackPixel(imageData, x, y))
          byte |= 1 << (7 - bit)  // MSB = верхня точка
      }

      hex += byte.toString(16).padStart(2, "0")
    }

    const row = startRow + strip * 8
    commands.push(
      `<NR><RC${row},${startCol}><g${hex.length}>${hex}`
    )
  }

  return commands.join("\n")
}

10. Інверсний друк та візуальні ефекти

FGL підтримує інверсний друк (білим по чорному) для візуального акценту. Він зазвичай використовується для заголовків секцій, смуг виділення та індикаторів статусу на квитках.

КомандаОпис
<EI>Увімкнути інверсний режим — весь наступний текст друкується білим на чорному фоні
<DI>Вимкнути інверсний режим — повернутися до звичайного чорного на білому
# Чорна смуга з білим текстовим заголовком
<EI><F6><HW1,1><NR><RC40,50> VIP ACCESS <WH1,1><DI>

# Звичайний текст нижче
<F3><HW1,1><NR><RC100,50>Section A - Row 5<WH1,1>

Порада: Додавайте пробіли навколо інверсного тексту (наприклад, VIP ACCESS ), щоб створити відступи в межах чорного фону.

Шаблони затінення

На принтерах FGL42/44/46 можна заповнювати області попередньо визначеними шаблонами затінення:

КомандаОпис
<ES>Увімкнути затінення
<DS>Вимкнути затінення
<SPn>Вибрати номер шаблону
<SPBn>Затінення фону шаблону
<SPFn>Затінення переднього плану шаблону

Режим перезапису

За замовчуванням елементи, що перекриваються, друкуються один поверх іншого. Режим перезапису замінює раніше надруковані дані в буфері:

FGL надає кілька команд для контролю того, як і коли квитки друкуються, відрізаються та підраховуються.

Команди друку та відрізання

КомандаОпис
<q>Друк та відрізання квитка (стандартне завершення)
<FF> / 0CHПереведення сторінки — друк та відрізання (альтернатива <q>)
1DHДрук без відрізання — корисно для багатосекційних квитків
<NOCM>Режим без відрізання (команда драйвера)
<CM>Режим відрізання — повторне ввімкнення відрізання (за замовчуванням)

Повторення та утримання

КомандаОпис
<REPn>Друк n додаткових копій (всього = n+1)
<PH>Друк та утримання зображення в буфері для заміни полів
<PNH>Утримання зображення без відрізання до надходження звичайної команди друку

Команда <PH> (Print & Hold) є потужною для високошвидкісного друку: спроєктуйте шаблон один раз, а потім замінюйте лише змінні поля (ім'я, місце, штрихкод) між друками.

Підрахунок квитків

КомандаОпис
<PTC>Друк поточного 7-значного лічильника квитків на квитку
<LTCnnnnnnn>Завантаження/встановлення лічильника квитків (усі 7 цифр обов'язкові)
<RTCn>Скидання лічильника квитків для шляху n

Буфер та стан

КомандаОпис
<CB>Очистити буфер квитка та відновити налаштування шрифтів за замовчуванням
<CR>Повернення каретки — перехід до наступного рядка в поточному обертанні
<MTM>Режим кількох квитків (за замовчуванням)
<STM>Режим одного квитка
<MBM>Режим кількох буферів (за замовчуванням)
<SBM>Режим одного буфера (сумісність з FGL2)

Статус та діагностика

Команди статусу дозволяють вашому додатку опитувати стан принтера для обробки помилок та моніторингу:

КомандаОпис
<SR>Запит однобайтового статусу (папір, застрягання, температура)
<PROM>Запит версії прошивки та лічильника квитків
<DSA>Звіт про доступну пам'ять для завантаження (8-значний hex)
<DIAG>Увійти в режим діагностики
<CME>Увімкнути повідомлення CRT (закінчення квитків, застрягання тощо)
<CMD>Вимкнути повідомлення CRT

Інтенсивність друку

Команда <LVn> налаштовує зміщення напруги друкувальної головки від -5 до +5 (за замовчуванням 0). Збільшуйте для темнішого друку на товстому носії, зменшуйте для тоншого носія, щоб запобігти розмазуванню.

Подвійний шлях / мультипринтер

Принтери BOCA з подвійним шляхом можуть направляти друк на конкретні шляхи:

12. Збереження та виклик логотипів

Принтери FGL можуть зберігати логотипи у флеш-пам'яті для швидкого виклику, уникаючи необхідності передавати дані зображення з кожним квитком.

Команди логотипів

КомандаОпис
<SPr,c>Встановити початкову точку (позицію) для наступного логотипу
<RLn>Друк заводського логотипу з ID n
<PLn>Друк завантаженого (користувацького) логотипу з ID n

Примітка щодо прошивки: Старіші ревізії прошивки BOCA можуть використовувати <LDn> / <LOn> для завантаження та виклику логотипів замість системи <FI> / <PL>. Перевірте керівництво з програмування вашої прошивки для правильного синтаксису.

Завантаження логотипів

Користувацькі логотипи завантажуються у флеш-пам'ять принтера за допомогою файлових операцій:

КомандаОпис
<FIn>Призначити ID файлу n наступному завантаженню
<PF>Встановити режим постійного файлу (зберігається між циклами живлення)
<TF>Встановити режим тимчасового файлу (очищується при вимкненні)
<DFn>Видалити файл з ID n з пам'яті
ESC cОчистити всю область завантаження та скинути покажчики

Файли зображень PCX та BMP

На принтерах FGL42/44/46 можна надсилати стандартні файли зображень безпосередньо, замість конвертації в точкові дані:

# Надсилання файлу зображення PCX (FGL42/44/46)
<SP100,50><pcx><G3500>...3500 bytes of PCX data...

# Надсилання файлу зображення BMP (FGL26/46)
<SP100,50><bmp><G4200>...4200 bytes of BMP data...

Команда <SPr,c> позиціонує зображення, а <Gn> вказує точну кількість байтів файлових даних, що слідують. Між командою та байтами файлу не повинно бути зайвих символів.

Збережені логотипи проти вбудованої графіки: Для логотипів, що з'являються на кожному квитку, завантажте їх один раз у флеш та викликайте через <PLn>. Це значно швидше, ніж надсилати дані зображення з кожним квитком. Використовуйте вбудовану графіку <g> лише для динамічних або одноразових зображень.

13. Створення генератора FGL (практичний досвід)

Якщо ви створюєте програмне забезпечення, що генерує FGL програмно (редактор макетів, сервер друку або систему квитків), наступні патерни базуються на реальному досвіді продакшену. Це проблеми, про які офіційна документація не попереджає.

Проблема початку координат при обертанні

Це найбільш заплутаний аспект програмування FGL. При обертанні елемента його початкова точка зміщується в інший кут. Команда <RC> завжди посилається на початкову точку, і якщо ви не врахуєте зміщення, ваші елементи «стрибатимуть» у неочікувані позиції після обертання.

Кут початкової точки для кожного кута обертання:

0° (NR) 90° (RR) 180° (RU) 270° (RL) [ORIGIN]─────┐ ┌─────────────┐ ┌─────────────┐ ┌─────[ORIGIN] │ text ──> │ │ │ │ │ │ │ │ │ │ text │ │ <── text │ │ text │ └─────────────┘ │ │ │ │ │ │ │ │ │ v │ └─────[ORIGIN] │ ^ │ [ORIGIN]──────┘ └─────────────┘ originX: left originX: left originX: right originX: right originY: top originY: bottom originY: bottom originY: top

Коли команда <RC> розміщує елемент, вона використовує кут початку координат, відповідний куту обертання. Тому при генерації FGL необхідно обчислювати правильну точку прив'язки на основі поточного обертання.

Нормалізація кута

Взаємодія користувача (перетягування-обертання в редакторі, введення через API) може створювати довільні кути: від'ємні значення, значення понад 360 або десяткові дроби на кшталт 89.7°. FGL підтримує лише 0/90/180/270. Завжди нормалізуйте:

function normalizeAngle(angle) {
  return (Math.round(angle) + 360) % 360
}

// -90  → 270
// 450  → 90
// 89.7 → 90

Техніка обертання навколо центру

Якщо ваш редактор дозволяє візуальне перетягування-обертання, обертання навколо кутової початкової точки призводить до «стрибків» елементів. Рішення — обертати навколо центру елемента, а потім перерахувати верхню ліву позицію для виводу FGL:

function rotateElement(el, newAngle) {
  // 1. Знаходимо центр елемента
  const cx = el.x + el.width / 2
  const cy = el.y + el.height / 2

  // 2. Обчислюємо нову верхню ліву точку після обертання навколо центру
  const origin = getOriginCorner(newAngle)
  const hw = el.width / 2,  hh = el.height / 2
  const dx = origin.x === "left"  ? -hw : hw
  const dy = origin.y === "top"   ? -hh : hh

  el.x = Math.round(cx + dx)
  el.y = Math.round(cy + dy)
  el.angle = newAngle
}

function getOriginCorner(angle) {
  const map = {
    0:   { x: "left",  y: "top"    },
    90:  { x: "left",  y: "bottom" },
    180: { x: "right", y: "bottom" },
    270: { x: "right", y: "top"    },
  }
  return map[angle]
}

Це забезпечує візуальну прив'язку елемента до його центру під час обертання, після чого координати перераховуються для виводу FGL.

Округлення координат — завжди округлюйте до цілих чисел

Координати FGL є цілими числами (позиції в точках). Дробові значення спричиняють непередбачувану поведінку. Після будь-якого обчислення округлюйте та обмежуйте:

function sanitizeCoords(props) {
  return {
    top:  Math.max(0, Math.round(props.top)),
    left: Math.max(0, Math.round(props.left)),
    strokeWidth: Math.round(props.strokeWidth),
    length: Math.round(props.length),
  }
}

Від'ємні координати мовчки приймаються деякими принтерами, але не дають результату. Завжди обмежуйте мінімумом 0. Якщо елемент частково виходить за межі квитка, принтер обріже його — але від'ємні значення RC можуть спричинити поведінку, специфічну для прошивки.

Фіксація альбомної орієнтації

Більшість принтерів BOCA очікують дані FGL в альбомній орієнтації незалежно від того, як квиток фізично подається через принтер. В альбомному режимі ширина та висота міняються місцями:

function toTicketDots(widthInch, heightInch, dpi, landscape) {
  // В альбомному режимі міняємо місцями, щоб довша сторона завжди була шириною
  if (landscape) {
    return { w: Math.round(heightInch * dpi), h: Math.round(widthInch * dpi) }
  }
  return { w: Math.round(widthInch * dpi), h: Math.round(heightInch * dpi) }
}

// Квиток 2.125" x 5.5" при 300 DPI, альбомний:
// w = 5.5 * 300   = 1650 точок
// h = 2.125 * 300 = 637 точок

Якщо ваша система дозволяє редагування в портретному режимі, ви повинні конвертувати в альбомні координати перед генерацією FGL:

function buildFGL(elements, ticket) {
  // Забезпечуємо альбомні координати для FGL
  const size = toTicketDots(ticket.width, ticket.height, ticket.dpi, true)

  const commands = elements.map(el => {
    const coords = toLandscapeCoords(el, size)
    return elementToFGL(el.type, coords, el.props)
  })

  return commands.join("\n") + "\n<q>"
}

Коригування координат ліній при обертанні

Лінії в FGL завжди малюються з <NR> (без обертання). Замість обертання лінії ви перемикаєтеся між <HX> (горизонтальна) та <VX> (вертикальна). Але при 180° та 270° початкову координату потрібно скоригувати, віднявши довжину лінії:

function lineFGL(x, y, angle, thickness, length) {
  const dir = (angle === 0 || angle === 180) ? "HX" : "VX"

  // Зміщуємо початкову точку для 180/270, щоб лінія продовжувалась правильно
  if (angle === 180) x -= length
  if (angle === 270) y -= length

  return `<NR><RC${y},${x}><LT${thickness}><${dir}${length}>`
}

Без цього коригування лінії при 180° та 270° починатимуться з неправильного кінця і виходитимуть за межі квитка.

Обертання зображень без обертання FGL

Графічний режим FGL (<g>) не підтримує команди обертання. Щоб надрукувати обернене зображення, ви повинні попередньо обернути вихідне зображення на стороні канвасу/сервера перед конвертацією в точкові дані:

function rotateImage(img, angle) {
  const c = document.createElement("canvas")
  const ctx = c.getContext("2d")
  const swap = angle === 90 || angle === 270

  c.width  = swap ? img.height : img.width
  c.height = swap ? img.width  : img.height

  ctx.translate(c.width / 2, c.height / 2)
  ctx.rotate(angle * Math.PI / 180)
  ctx.drawImage(img, -img.width / 2, -img.height / 2)

  return ctx.getImageData(0, 0, c.width, c.height)
}

Потім подайте обернені піксельні дані в алгоритм 1-бітного стиснення з розділу 9.

Серіалізація схеми — збереження/завантаження макетів

Якщо ви створюєте редактор, вам знадобиться формат серіалізації для збереження та перезавантаження макетів. Практична JSON-схема:

{
  "version": 1,
  "ticket": {
    "width": 2.125, "height": 5.5,
    "dpi": 300,
    "landscape": true
  },
  "elements": [
    {
      "type": "text",
      "x": 50, "y": 100, "angle": 0,
      "font": 3, "scaleH": 1, "scaleW": 1,
      "content": "Hello World",
      "bind": null
    },
    {
      "type": "barcode",
      "x": 50, "y": 200, "angle": 0,
      "symbology": "code128",
      "expand": 2, "barWidth": 5,
      "interpretation": true
    }
  ]
}

Ключові практики:

Динамічні поля даних (заповнювачі)

У продакшені більшість квитків мають поєднання статичного макету (логотипи, межі, підписи) та динамічних даних (ім'я, місце, штрихкод). Використовуйте поле bind, щоб позначити елементи, вміст яких замінюється під час друку:

const ROTATIONS = { 0: "NR", 90: "RR", 180: "RU", 270: "RL" }

function textToFGL(el, data) {
  // Використовуємо прив'язані дані, якщо доступні, інакше статичний вміст
  const text = el.bind && data[el.bind] ? data[el.bind] : el.content
  const rot = ROTATIONS[el.angle]

  return `<F${el.font}><HW${el.scaleH},${el.scaleW}>`
       + `<${rot}><RC${el.y},${el.x}>${text}<WH1,1>`
}

// Використання: заповнення динамічних полів під час друку
const fgl = textToFGL(
  { font: 3, scaleH: 1, scaleW: 1, angle: 0, x: 50, y: 100, bind: "guest_name" },
  { guest_name: "John Smith", seat: "A-12" }
)

Редактор зберігає значення-заповнювачі, такі як "Guest Name", у content, тоді як ключ bind вказує на поле даних. Під час друку ваш сервер підставляє реальні значення через об'єкт data.

Паралельна генерація FGL

Конвертація зображення в FGL може бути повільною (рендеринг канвасу, сканування пікселів). Оскільки порядок елементів у FGL не має значення — лише координати визначають розміщення — ви можете генерувати всі елементи паралельно:

// Повільно: кожне зображення блокує наступне
let fgl = ""
for (const el of elements) {
  fgl += await toFGL(el, data)
}

// Швидко: всі конвертації виконуються паралельно
const parts = await Promise.all(elements.map(el => toFGL(el, data)))
const fgl = parts.join("\n") + "\n<q>"

Це безпечно, оскільки кожен елемент генерує незалежну команду FGL — між ними немає спільного стану.

14. Компонування повного макету

Повний макет FGL — це просто конкатенація команд окремих елементів, завершена <q>. Кожен елемент займає один або більше рядків, і принтер обробляє їх послідовно.

Приклад: квиток на подію

# ── Текстові елементи ──
<F12><HW1,1><NR><RC40,50>SUMMER FEST 2024<WH1,1>
<F3><HW1,1><NR><RC140,50>Main Stage - Section A<WH1,1>
<F9><HW1,1><NR><RC180,50>June 15, 2024 - 7:00 PM<WH1,1>
<F9><HW1,1><NR><RC210,50>Gate: North  Row: 12  Seat: 34<WH1,1>

# ── Лінія-роздільник ──
<NR><RC250,40><LT1><HX560>

# ── Штрихкод ──
<NR><RC280,50><X2><BI><OP4>^EVT-2024-001234^

# ── QR-код (права сторона) ──
<NR><RC270,480><QRV7><QR4>https://verify.example.com/EVT-2024-001234

# ── Обернений бічний текст ──
<F1><HW1,1><RR><RC30,620>ADMIT ONE<WH1,1>

# ── Кінець квитка ──
<q>

Ключові моменти:

15. Практичні поради

Врахування DPI

Завжди знайте DPI вашого принтера перед розрахунком координат. Макет, спроєктований для 300 DPI, друкуватиметься вдвічі меншим фізичним розміром на принтері 200 DPI. Тримайте координати як відносні до DPI розрахунки:

// Обчислення позиції з дюймів
const DPI = 300
const row = Math.round(0.5 * DPI)  // 150 точок = 0.5 дюйма від верху
const col = Math.round(1.0 * DPI)  // 300 точок = 1.0 дюйм від лівого краю

Фіксація орієнтації

Багато принтерів FGL очікують дані квитка в альбомній орієнтації незалежно від того, як фізичний квиток орієнтований у принтері. Якщо ваш принтер використовує альбомне відображення координат, переконайтеся, що всі координати обчислені з довшою стороною як ширина. Міняйте місцями значення ширини/висоти відповідно перед генерацією FGL.

Шаблони заповнювачів

При створенні динамічних систем, де дані підставляються під час друку, використовуйте токени-заповнювачі у ваших FGL-шаблонах:

<F3><HW1,1><NR><RC100,50>{{event_name}}<WH1,1>
<NR><RC280,50><X2><BI><OP4>^{{ticket_code}}^
<NR><RC280,480><QRV7><QR4>{{validation_url}}
<q>

Ваш сервер друку замінює токени {{...}} реальними даними перед надсиланням потоку FGL на принтер.

Високошвидкісний друк

Для сценаріїв з високою пропускною здатністю (прохідні на подіях, турнікети транспорту) мінімізуйте обсяг даних на кожен квиток:

  1. Використовуйте збережені логотипи — завантажте один раз через <PL>, викликайте на кожному квитку
  2. Використовуйте Print & Hold<PH> утримує шаблон у буфері, надсилайте лише змінні дані для кожного квитка
  3. Мінімізуйте графіку — ASCII-графіка (<g>) передає вдвічі більше даних, ніж бінарна (<G>). Використовуйте бінарний режим, якщо ваш транспорт підтримує 8-бітні дані
  4. Використовуйте <REPn> для ідентичних копій замість повторного надсилання квитка

Типові помилки

Робочий процес налагодження

  1. Почніть з <DIAG>, щоб перевірити, що принтер обробляє команди.
  2. Протестуйте позиціонування простим текстом на відомих координатах перед додаванням складних елементів.
  3. Використовуйте <PROM> для перевірки версії прошивки — деякі команди потребують конкретних версій FGL (наприклад, 2D штрихкоди потребують FGL46).
  4. Надсилайте <CB> між тестовими завданнями для очищення буфера та скидання стану.
  5. Перевіряйте цілісність даних штрихкоду: скануйте надруковані штрихкоди та порівнюйте з вхідними даними.

Налаштування довжини квитка

Якщо ваш рулон квитків відрізняється від заводських налаштувань, налаштуйте довжину друку:


Довідник команд

Текст та позиціонування

КомандаОпис
<Fn>Вибір шрифту (n = 1–13, 16)
<HWh,w>Масштабування висоти/ширини шрифту
<WHw,h>Скидання ширини/висоти шрифту (використовуйте <WH1,1>)
<BSw,h>Зміна розміру комірки символу (ширина, висота)
<SDn>Зменшення шрифту в n разів
<RCr,c>Позиціонування в рядку, стовпці (у точках)
<CR>Повернення каретки / наступний рядок
<NR>Без обертання (0°)
<RR>Обертання вправо (90°)
<RU>Обертання догори ногами (180°)
<RL>Обертання вліво (270°)
<EI>Увімкнути інверсний друк (білий на чорному)
<DI>Вимкнути інверсний друк
<ECM>Увімкнути розширений режим символів (>127)
<ECMD>Вимкнути розширений режим символів

Штрихкоди

КомандаОпис
<Xn>Коефіцієнт розширення штрихкоду (1–9)
<Yn>Коригування співвідношення штрихкоду (3:1 або 5:2)
<BI>Інтерпретація штрихкоду (людиночитабельний текст)
<QRVn>Версія QR-коду (2–15)
<QRn>Розмір модуля QR (3–16)

Лінії, прямокутники та графіка

КомандаОпис
<LTn>Товщина лінії в точках
<HXn>Горизонтальна лінія довжиною n точок
<VXn>Вертикальна лінія довжиною n точок
<DBr,c>Прямокутник висотою r точок, шириною c точок
<Gn>Бінарний графічний режим (n байтів)
<gn>ASCII графічний режим (n hex-символів)

Логотипи та файлові операції

КомандаОпис
<SPr,c>Встановити початкову точку для логотипу (рядок, стовпець)
<RLn>Друк заводського логотипу з ID n
<PLn>Друк завантаженого логотипу з ID n
<FIn>Призначити ID файлу для завантаження
<PF>Режим постійного файлу
<TF>Режим тимчасового файлу
<DFn>Видалити файл з ID n

Керування друком

КомандаОпис
<q>Кінець квитка (поведінка друку/відрізання залежить від прошивки)
<FF>Переведення сторінки (друк та відрізання)
<REPn>Друк n додаткових копій
<PH>Друк та утримання зображення для повторного використання
<CB>Очищення буфера та скидання шрифтів
<LVn>Інтенсивність друку (від -5 до +5)
<PTC>Друк лічильника квитків на квитку
<SR>Запит статусу принтера
<PROM>Запит інформації про прошивку/лічильник
<DSA>Звіт про доступну пам'ять
<DIAG>Увійти в режим діагностики

Додаткові матеріали