Skip to content

About

Browser-based constructor for Ukrainian road signs, built to the official design guide's exact typography, spacing, and layout rules. Compose pictograms, road numbers, distance markers, waves, and text blocks with live SVG preview, drag-to-position elements, JSON save/load, and SVG export. TypeScript + Vite.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Конструктор дорожніх знаків України

Демо: village-elder.github.io/road_sign_builder

Веб-застосунок для побудови знаків за путівником roadguide.a3.kyiv.ua.

Повністю клієнтський застосунок. Жодного бекенду чи бази даних — генерація SVG, метрика шрифту, конструктор з drag-and-drop і персистентність усе на боці клієнта. Збірка — TypeScript + Vite (до Фази H цього репозиторію проєкт був навмисно zero-build-step; повний контекст рішення — у git-історії).

Конструктор у режимі редагування


Архітектура

road_sign_builder/
│
├── index.html                   # UI-розмітка, вхідна точка для Vite
│
├── src/
│   ├── main.ts                  # bootstrap: DOMContentLoaded → Controller.init()
│   ├── css/main.css             # Стилі
│   ├── types.ts                 # Спільні типи pipeline (RenderContext/LayoutResult/…)
│   ├── types/                   # Ambient-типи (глобальний `opentype`, тестовий import .js)
│   ├── stores/                  # state.ts — тонкий фасад над цими 5 сторами
│   └── *.ts                     # Увесь застосунок — див. таблиці нижче
│
├── public/                      # Копіюється в dist/ як є (Vite-конвенція)
│   ├── fonts/                   # Road UA (Bold/Medium) + RoadUA-LICENSE.txt (CC BY-ND 4.0)
│   ├── vendor/opentype.min.js   # Парсинг TTF/контурів гліфів (MIT), глобальний <script>
│   ├── examples/                # Готові приклади для оверлея "Почніть з прикладу"
│   └── config-defaults.json     # Дефолти метрики (module/spacing/border/…)
│
├── tests/                       # Vitest — див. tests/README.md
│
├── .github/workflows/deploy.yml # Збірка + деплой на GitHub Pages
├── vite.config.ts / vitest.config.ts / tsconfig.json
├── package.json
└── .gitignore

opentype.min.js навмисно лишається вендорним <script>-тегом (не npm-пакетом opentype.js) — щоб не ризикувати розбіжністю зі збіркою, вже перевіреною побайтово проти Python-оригіналу. Типи для нього — власний ambient src/types/opentype.d.ts, лише сигнатури, що реально використовуються.

Measure → Layout → Render pipeline (src/)

Шар Модулі Відповідальність
Шрифтові метрики font-metrics.ts opentype.js-парсинг TTF, LSB/RSB/advance/контур гліфа
Вимір (measure) text-measure.ts Розбиття на блоки/рядки, ink-width через реальні метрики шрифту
Контекст рендеру render-context.ts RenderContext — розміри, кольори, шрифти за пресетом/темою
Компоновка (layout) layout-engine.ts Абсолютні координати всіх елементів знаку
Елементи знаку km-group.ts, road-number-generator.ts + road-num-group.ts, road-ua-symbols.ts + symbol-group.ts Вимір/рендер км-позначки, номерів доріг, символів/піктограм/стрілок (стрілки — category:"arrows" у symbol_configs, той самий рендер-шлях, що й решта символів). road_num/km — кілька незалежних груп (0..N екземплярів кожного типу), НЕ singleton; road_num має власну комо-множинність всередині (road_numbers), км — ОДНЕ значення на екземпляр, повністю незалежний від рядків тексту назви (layout.kmBaselineY — фіксований якір, не рухається при редагуванні/перетягуванні назви)
Текст render-text.ts Рендер <text> назви знаку
Конструктор bounds.ts Межі елементів, автосайз, drag-офсети, bounds/padding-оверлей. road_num/km — спільний layout-якір рахується лише з екземпляра [0]; кожен інстанс отримує власний drag-офсет через shifted-layout-копію (extraRoadNumOffsets/extraKmOffsets), без мутації спільного layout
Піктограми (SVG-контури) pictogram-icons-svg.ts Незалежні від Road UA font контури (bus/train/intersection/airplane_12/9/10/2/3 тощо) для symbol_configs, поряд із font-лігатурами
Дорожні знаки traffic-signs-svg.ts + traffic-sign-group.ts, traffic-sign-manager.ts/traffic-sign-picker.ts 7 стандартних дорожніх знаків (SVG-контур з гайду) як ОКРЕМА сутність від символів/піктограм — власний список traffic_sign_configs
Смуга «Кінець» / Роздільна лінія / Хвиля end-stripe-manager.ts, separator-line-manager.ts, wave-manager.ts + wave-group.ts Геометричні елементи без layout-якоря: діагональна смуга "Кінець населеного пункту", горизонтальна лінія між напрямками, хвиля (водойма/річка)
Контур знаку sign-outline.ts Форма знаку: закруглені кути, скошений правий край (exit_shape для знаку з'їзду з дороги)
Відступи indent.ts Спільний каркас IndentOffsets для 7 типів елементів — лише жовтий guide-прямокутник, не зсуває фактичний рендер
Анотації annotation-generator.ts Вимірювальна розмітка (pad/cap/border/km) поверх знаку
Оркестрація svg-generator.ts Збирає весь pipeline в один SVG-рядок
KiCad-експорт text-outliner.ts Конвертація <text> → <path> (шрифт-незалежний SVG)
Дефолти / валідація config-defaults.ts, norm-check.ts, legal-scale.ts Глобальні параметри метрики (JSON, аналог колишнього config.toml); перевірка введених значень проти легальної x-шкали
Утиліта html-escape.ts escapeHtml() — спільне екранування перед вставкою в <text> (раніше дублювалось у 6 файлах)

Адаптер стану та персистентність

Модуль Відповідальність
local-preview.ts State (snake_case) → generateSvg() конфіг (camelCase); локальний прев'ю/export/outlined/annotate
sign-files.ts Збереження/завантаження знаку у .json-файл (download/upload браузером) — єдиний спосіб персистентності, без localStorage
example-picker.ts Оверлей "Почніть з прикладу" — з'являється, коли знак порожній; готові приклади в public/examples/ (manifest.json + по одному .json на приклад, той самий формат, що sign-files.ts)
preview-trigger.ts Debounced запуск оновлення прев'ю

UI-шар

state.ts — тонкий фасад над stores/{sign,settings,grid,annotation,bounds}-store.ts (один store = один зріз стану, той самий публічний State-об'єкт для ~20 споживачів). constants.ts (таблиці пресетів), zoom.ts / grid.ts / drag-overlay.ts / preview.ts (канвас), symbol-picker.ts / symbol-element-picker.ts (піктограми), names-manager.ts / symbol-manager.ts / road-num-manager.ts / km-manager.ts / traffic-sign-manager.ts / end-stripe-manager.ts / separator-line-manager.ts / wave-manager.ts (data stores елементів-списків — спільне ядро list-manager.ts, toggle-групи — toggle-group.ts, одиночні числові поля — numeric-field.ts, drag — drag-group.ts

  • element-auto-position.ts; symbol-manager.ts покриває піктограми/райони/країни/стрілки одразу — колишній окремий arrow-manager.ts злито сюди як category:"arrows"; traffic-sign-manager.ts — ОКРЕМА від символів сутність (7 стандартних дорожніх знаків, не category); road-num-manager.ts/km-manager.ts — колишній singleton-реєстр element-types.ts/SINGLETON_ELEMENT_TYPES (видалено), тепер 0..N незалежних екземплярів кожного типу, той самий каркас, що symbol-manager.ts), tree-manager.ts / inspector.ts / element-list.ts (sidebar), main-tab.ts / settings-tab.ts / topbar-actions.ts (вкладки), preview-side-panel.ts (права панель "Чисте прев'ю" — специфікація знаку в мм, перевірка норми, експорт), controller.ts / main.ts (bootstrap).

Ці 8 менеджерів списків + tree-manager.ts/inspector.ts/element-list.ts не імпортують одне одного напряму — кожен отримує залежності через setDeps() з роль-специфічними інтерфейсами (ui-contracts.ts), звуженими до реально використовуваного зрізу API сусіда. wire-ui-modules.ts — єдиний композиційний корінь, де цих 11 модулів зв'язуються разом; викликається один раз з controller.ts:init().

Увесь застосунок — 100% клієнтський; жодного мережевого виклику до власного бекенду немає (транслітерація — локальна, transliteration.ts).


Функціонал

  • Назва знаку: укр./лат., кілька рядків, кілька населених пунктів через |, довільний відступ/центрування кожного блоку
  • Елементи знаку (кожен — 0..N незалежних екземплярів, вільний drag по канві): км-позначка, номери доріг (малі/великі бейджі, тип E/H/P), символи/піктограми й стрілки напрямку (лігатури Road UA + окремі SVG-контури — bus/train/ intersection/аеропорт-у-5-напрямках), дорожні знаки (7 стандартних, окрема від символів сутність), смуга «Кінець населеного пункту», роздільна лінія напрямків, хвиля (водойма/річка)
  • Кольорові теми (8 офіційних), режими облямівки, скошений правий край для знаку з'їзду з дороги, довільне обертання символу
  • Конструктор: drag-and-drop елементів, bounds/padding-оверлей, вимірювальні анотації, сітка з кроком/прозорістю/кольором
  • Оверлей "Почніть з прикладу" — готові приклади знаків при порожньому знаку
  • Експорт: кольоровий SVG і чорно-білий контурний SVG для KiCad/PCB (текст → <path>, без залежності від зовнішніх шрифтів)
  • Збереження знаку/налаштувань — лише файл (.json), без сервера й без localStorage; кожне перезавантаження сторінки стартує з чистих дефолтів

Локальний перегляд

Потрібен Node.js 20+.

npm install
npm run dev
# відкрити URL, який виведе Vite (типово http://localhost:5173/)

Vite dev-сервер дає HMR — зміни в .ts/CSS підхоплюються без ручного перезавантаження сторінки.

Збірка

npm run build     # tsc --noEmit + vite build → dist/
npm run preview   # локально підняти зібраний dist/ (перевірка production-білду)

Тестування

npm test          # vitest run — усі тести одноразово
npm run test:watch
npm run typecheck # tsc --noEmit окремо, без збірки

Vitest, strict: true в tsconfig.json — див. tests/README.md для розбивки по файлах і відомих прогалин покриття.


Гілки

  • develop — основна гілка розробки, усі поточні зміни йдуть сюди.
  • main — лише релізні версії сайту; оновлюється (merge/fast-forward з develop) свідомо, окремим кроком, коли поточний стан готовий до релізу. Саме push у main запускає деплой (нижче).

Деплой

.github/workflows/deploy.yml — при push у main: npm ci → typecheck → test → npm run build → публікація dist/ на GitHub Pages через actions/deploy-pages.

Потрібна одноразова ручна дія: у налаштуваннях репозиторію на GitHub — Settings → Pages → Source — перемкнути на "GitHub Actions" (замість "Deploy from a branch"). Без цього workflow відпрацює, але Pages не підхопить результат.

vite.config.ts's base: './' — відносний шлях, працює з будь-якого підкаталогу чи кастомного домену без додаткового налаштування.


Стандарти

  • Компонування: roadguide.a3.kyiv.ua
  • Транслітерація: Постанова КМУ №55 від 27.01.2010
  • Шрифт: Road UA (Андрій Константинов, minttype.com, CC BY-ND 4.0 — див. public/fonts/RoadUA-LICENSE.txt) / Overpass (Google Fonts fallback)
  • opentype.js: Frederik De Bleser (MIT — див. public/vendor/opentype-LICENSE.txt)

Ліцензія

Власний код проєкту — MIT. Шрифт Road UA та opentype.js поширюються на власних умовах (див. вище).

About

Browser-based constructor for Ukrainian road signs, built to the official design guide's exact typography, spacing, and layout rules. Compose pictograms, road numbers, distance markers, waves, and text blocks with live SVG preview, drag-to-position elements, JSON save/load, and SVG export. TypeScript + Vite.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages