diff --git a/docs/specs/053-pdf-export.md b/docs/specs/053-pdf-export.md new file mode 100755 index 00000000..f8a3f196 --- /dev/null +++ b/docs/specs/053-pdf-export.md @@ -0,0 +1,356 @@ +# ТЗ #53 — Экспорт пространства в PDF: чистый архитектурный план + +- **Issue:** https://github.com/Matysh/houseplan-card/issues/53 +- **Приоритет:** P3 · **Тип:** feature · **Трек:** полный (новый UX, новый ленивый чанк, новый формат вывода) +- **Оценка:** пользовательская ценность 7/10 («распечатал план для электрика/страховой»), уникальность против конкурентов 9/10 (у них нет геометрии для печати); сложность 6/10; риск 4/10 (шрифты и растр в PDF, детерминизм) +- **Решения владельца:** 2026-08-15 (исключение из `docs/SCOPE.md`, задача остаётся в очереди); 2026-09-07 (#52 объединён сюда, слой размеров во View не делается; кнопка-принтер, маленький диалог, только текущее пространство, без устройств, подложка — если есть у пространства) +- **Связанные контракты:** `docs/specs/052-view-dimensions.md` (§ Площади, § Длины — контракт измерений), `docs/WALL-THICKNESS.md`, `docs/BACKDROP.md`, `docs/FURNITURE.md`, `docs/SCOPE.md`, #456 (образец диалога у пространства), #476 (диалог с кнопкой) + +## 1. Сценарий + +Персона — администратор дома, desktop-браузер (touch — best effort по +`docs/TOUCH-SUPPORT.md`, но диалог обязан работать пальцем). Он открывает +нужное пространство, нажимает иконку принтера в панели карточки, в +маленьком диалоге оставляет или снимает три-четыре галочки и нажимает +«Сохранить». Через секунду браузер скачивает `houseplan-<пространство>-<дата>.pdf` +— один лист A4 с планом этого пространства: стены с толщиной, перегородки, +виртуальные стены, проёмы, размеры и площади, названия комнат — без +устройств, состояний, подсветок и цветов. Лист можно отдать электрику, +страховой, подрядчику. + +## 2. Что человек увидит до и после + +До: план существует только на экране; «бумажный» вариант — скриншот с +маркерами и свечением. + +После: в панели карточки между шестерёнкой и знаком вопроса — иконка +принтера. Диалог «Сохранение в PDF» с галочками и одной кнопкой. Файл +скачивается без диалога печати браузера, одинаково на десктопе и в +мобильном приложении HA. На листе — векторная штриховая графика, читаемые +подписи на языке плана, масштаб и масштабная линейка в подвале. + +## 3. Проблема + +Экспорт JSON (#50) переносит план между установками, но не годится как +документ для человека без Home Assistant. SVG на экране рисует всё сразу — +состояния, свечение, маркеры — и не имеет ни масштаба, ни листа. Размеры +стен и площади уже считаются (ресайз, карточка комнаты), но нигде не +печатаются. Контракт измерений принят в #52 и не реализован; печатный лист +— первое место, где он нужен целиком. + +## 4. Решения владельца (2026-09-07) + +1. Точка входа — иконка принтера в основной панели между настройками и + знаком вопроса. +2. Диалог маленький: заголовок «Сохранение в PDF», галочки и кнопка + «Сохранить». +3. Экспортируется только текущее пространство; «все пространства» — не + делаем. +4. На листе только архитектура: стены, перегородки, виртуальные стены, + проёмы, размеры. Устройства не нужны совсем — ни галочки, ни маркеров. +5. Галочки: «Отображать размеры» (по умолчанию включена), «Отображать мебель + и другой декор», «Отображать названия комнат», «Отображать подложку» — + последняя есть только если у пространства задана подложка. +6. Слой размеров на экране (View) не делается — #52 закрыт. + +## 5. Скоуп + +- Кнопка в панели и диалог (§6). +- Печатная сцена пространства из канонической модели (§7): геометрия, + подписи, размеры по контракту #52, подложка и декор по галочкам. +- Лист A4, автоориентация, вписывание, подвал с масштабом (§7.5). +- Генерация PDF целиком на клиенте, ленивым чанком, без внешних сервисов + (§8); скачивание файла. +- Шрифт с кириллицей и латиницей, встроенный в PDF (§8.3). +- i18n четырёх языков (§9), документация, тесты (§12–§13). + +## 6. Не-скоуп + +- Устройства, маркеры, состояния, свечение, солнце, следы робота, топология + Zigbee — не печатаются никогда. +- Экспорт всех пространств одним файлом; титульный лист; выбор формата + листа (A3, Letter) и масштаба вручную — возможные следующие задачи. +- Слой размеров на экране; изометрический вид (печатается только плоский + план независимо от текущей проекции). +- Печать через `window.print()`/диалог печати браузера — отвергнуто в §8.1. +- Изменение модели данных, конфига, схемы бэкенда — нет. +- `houseplan-space-card` — кнопки нет (статичная карточка без панели); + отдельная задача при необходимости. + +## 7. Контракт: что на листе + +### 7.1. Геометрия (всегда) + +Источник — `spaceModels(cfg)` для текущего пространства, та же +нормализованная модель, что у View: комнаты, `wall_segments` с толщиной, +`partitions`, `wall_columns`, проёмы (`door`, `window`, `gate`, `passage`). + +| элемент | изображение | +|---|---| +| стена с толщиной | замкнутый контур по физическим граням, заливка серым (#555), обводка чёрная 0,25 мм; общая стена — один раз | +| стена нулевой толщины (виртуальная / открытая граница) | штриховая линия 0,35 мм, штрих 3 мм / пробел 2 мм, без заливки | +| перегородка | как стена, своей толщиной; тоньше 4 см на бумаге — сплошная линия 0,5 мм | +| колонна | контур по `cm` (квадрат/круг), заливка серым | +| дверь | разрыв в стене + створка и дуга открывания в сторону и с петлями, как на экране (`flip_v`/петли уважаются) | +| окно | разрыв в стене с двойной линией по граням | +| ворота | как дверь, без дуги, с диагональной штриховкой створки | +| проход (`passage`) | разрыв без линий | + +Никаких заливок комнат, свечения, цветов стен из настроек: лист +монохромный. Толщина линий — в миллиметрах бумаги, не зависит от масштаба +(аналог `vector-effect: non-scaling-stroke`). + +### 7.2. Названия комнат (галочка, по умолчанию — см. §18 Q1) + +Название — как хранится в плане (язык пользователя), в сохранённой позиции +подписи либо во внутренней точке комнаты (тот же резолвер, что у View). +Кегль 9 pt; если название не помещается в комнату при масштабе листа — +уменьшается до 7 pt, ниже — скрывается (tooltip'а на бумаге нет, поэтому +комната остаётся без подписи, а не с обрезанной). + +### 7.3. Размеры и площади (галочка, по умолчанию включена) + +Правила измерения — **дословно из `052-view-dimensions.md`**, § Площади и +§ Длины: площадь чистого пола через `geometryArea`; уникальные физические +осевые пролёты стен из нормализованной модели, общая стена один раз, +виртуальные границы без длины, проёмы не вычитаются, колонны без линейного +размера, коллинеарный мусор через существующую компакцию. Значения — +`formatArea`/`formatLength`, единицы из HA (metric/imperial), масштаб сетки +пространства (`cell_cm`). + +Раскладка на бумаге (в отличие от экрана — без зума и без указателя): + +- **площадь** — под названием комнаты тем же кеглем минус 1 pt; если + названия нет или оно скрыто — в той же точке одна строка площади; +- **внешние стены** — размерные линии *снаружи* внешнего контура: + выносные линии перпендикулярно грани, размерная линия с засечками + (архитектурная нотация), текст над линией, читаемый слева направо или + снизу вверх, никогда вверх ногами; пролёты одной прямой грани — цепочкой + на одной размерной линии; +- **внутренние стены и перегородки** — подпись вдоль пролёта с той стороны, + где больше свободного пола; кегль 7 pt; +- **коллизии** — детерминированно: приоритет внешний контур → длинный + пролёт → короткий; подпись, чей прямоугольник пересекает уже размещённый + или выходит за поле листа, скрывается; пролёты короче 40 см на бумаге не + подписываются. + +### 7.4. Мебель и другой декор (галочка, по умолчанию — Q1) + +Всё содержимое `decor[]`: мебель (арт из `furniture-art-runtime`, штрих +чёрный), линии/прямоугольники/эллипсы (цвет игнорируется — чёрный штрих, +без заливки), текстовые подписи (кегль по `width_cm`, минимум 6 pt), +изображения (`kind: 'image'`) — растром по правилу §7.6. Порядок слоёв — +как в модели. + +### 7.5. Лист, подвал, легенда (всегда) + +- A4 (210 × 297 мм), поля 12 мм; ориентация — по пропорциям bounding box + контента (ширина > высоты → landscape). +- Контент — геометрия ± внешние размерные линии (при включённой галочке) + ± подложка/декор (при включённых) — вписывается в поле листа с сохранением + пропорций; масштаб округляется **вниз** до стандартного ряда 1:20, 1:25, + 1:50, 1:75, 1:100, 1:150, 1:200, 1:250, 1:500, чтобы линейкой на бумаге + можно было мерить; при экстремальных планах — ближайший больший. +- Шапка: название пространства (кегль 14 pt), справа — название дома + (`title` карточки), если задано. +- Подвал: «Масштаб 1:N», масштабная линейка 1 м (metric) / 5 ft (imperial) + с делениями, стрелка севера при заданном компасе пространства или общем + `north_deg` (тот же резолвер, что у солнца), дата (локальная, `YYYY-MM-DD`), + «House Plan vX.Y.Z», легенда: стена · перегородка · виртуальная стена · + дверь · окно · ворота (только присутствующие на плане элементы). +- Текст шапки/подвала — на языке карточки (`_t`). + +### 7.6. Подложка (галочка, есть только при заданной подложке) + +Растровая подложка (PNG/JPEG/WebP) встраивается как XObject с исходными +байтами (JPEG — DCTDecode без перекодирования; PNG/WebP — через canvas в +JPEG q=0.85, чтобы не тащить декодер PNG в writer), в сохранённой позиции +(`plan_x/plan_y/plan_scale*`, поворот) под геометрией, с прозрачностью 60 % +— иначе штриховая геометрия тонет в скане. SVG-подложка растеризуется через +canvas при 150 dpi по размеру на листе. Изображения декора — по тем же +правилам. Суммарный размер встроенных растров ограничен 25 МБ: превышение — +отказ с тостом `pdf.too_large` до генерации, галочки остаются. + +Внешние URL подложки идут через тот же подписанный доступ, что и на +экране; недоступная картинка — отказ с тостом `pdf.asset_failed`, файл не +пишется (лист без обещанной подложки молча — хуже, чем честный отказ). + +## 8. Контракт: как делается PDF + +### 8.1. Выбор способа + +| вариант | плюсы | минусы | решение | +|---|---|---|---| +| `window.print()` + print-CSS | нет зависимостей, любой шрифт | диалог печати вместо скачивания; в мобильном приложении HA (WebView) печать недоступна или калечит лист; пагинация и масштаб не под контролем | **отвергнут** | +| jsPDF + svg2pdf | готовые примитивы | ~200 КБ gzip ленивого кода **плюс** шрифт всё равно нужен свой (стандартные 14 шрифтов PDF без кириллицы); конвертация SVG → PDF с потерями (маски, `non-scaling-stroke`) | отвергнут | +| **собственный минимальный PDF-writer** | вектор, прямое скачивание, ~15 КБ gzip кода, одинаково на десктопе и в приложении; рисуем из модели, а не из экрана — без потерь | писать самим: страницы, контент-поток (`m l c h f S`), встраивание TrueType (CIDFontType2, Identity-H, ToUnicode), XObject Image, xref | **принят** | + +### 8.2. Ленивый чанк `pdf-export` + +`src/pdf/` — новый модуль, динамически импортируемый из ядра через +`EditorRuntimeLoader` (отпечаток сборки, повтор с нонсом, терминальный отказ +для чужой сборки — образец `iso-scene-render`). В стартовый граф не входит +ничего, кроме кнопки, обработчика и вызова загрузчика; манифест получает +роль `pdf` и `lazyPdfFiles`, `bundle:budget` требует непустой список и +отсутствие пересечения с initial (как у `lazyIsometricFiles`). + +Состав: `pdf-writer.ts` (объекты, потоки, xref, шрифт, изображения), +`pdf-scene.ts` (модель → примитивы листа: геометрия, подписи, размеры, +подвал), `pdf-dimensions.ts` (контракт #52: пролёты, площади, раскладка), +`pdf-export.ts` (оркестрация: опции → сцена → байты → `Blob` → скачивание), +`hp-pdf-dialog.ts` (диалог). + +### 8.3. Шрифт + +Один шрифт, встроенный в PDF как FontFile2: **Roboto Regular**, лицензия +Apache 2.0 (файл лицензии в `assets/fonts/`), сабсет, собираемый на этапе +генерации (`scripts/generate-pdf-font.mjs`, devDependency `subset-font`): +Basic Latin, Latin-1 Supplement, Latin Extended-A, Cyrillic, Cyrillic +Supplement, General Punctuation, знаки ° ² ′ ″ × ≈. Результат — +`src/pdf/pdf-font.generated.ts` (base64, ожидаемо 60–80 КБ raw), только в +ленивом чанке; `--check`-режим генератора, как у мебели. Глиф вне сабсета +→ `.notdef` (пустой прямоугольник) — документировано; язык плана +ограничен четырьмя поддерживаемыми, поэтому в практике не встречается. + +Ширины глифов берутся из `hmtx` сабсета в момент генерации и кладутся в +тот же файл — writer не парсит TrueType в браузере. + +### 8.4. Скачивание и детерминизм + +`Blob` → `URL.createObjectURL` → `` → `revokeObjectURL`. Имя: +`houseplan--.pdf`. В мобильном +приложении HA скачивание идёт штатным путём WebView — проверяется вручную +на Android-приложении (AC12). + +При одинаковых входах (модель, опции, единицы, язык, версия, дата) байты +PDF идентичны: даты `CreationDate`/`ModDate` берутся из переданного +`now`, объекты нумеруются детерминированно, растры — исходные байты. Это +основа golden-проверки (§13). + +### 8.5. Диалог и состояния + +`hp-pdf-dialog` на базе `hp-dialog` (модальность и центрирование — #463). +Заголовок `pdf.title` («Сохранение в PDF»), галочки в порядке: размеры, +мебель и другой декор, названия комнат, подложка (только если +`space.bg`); кнопка `pdf.save` («Сохранить»), закрытие крестиком/Esc/кликом +вне — как у остальных диалогов. Состояние галочек запоминается в +`localStorage` (`hp.pdf.options`, схема `{v:1,...}`); при отсутствии — +значения по умолчанию §18 Q1. + +Нажатие «Сохранить»: кнопка блокируется, текст `pdf.saving` +(«Формируем…»), загружается чанк (первый раз — до 1 с), формируется файл, +диалог закрывается после начала скачивания. Отказ (чанк не загрузился, +растр не удалось получить, лимит) — тост с причиной, диалог остаётся +открытым с теми же галочками. Повторное нажатие во время формирования +игнорируется. + +Кнопка в панели: `mdi:printer-outline`, `title`/`aria-label` = +`title.export_pdf`, стоит между «Общие настройки» и «Помощь и обратная +связь» в том же блоке (`_canEdit`), в киоске не показывается, в +изометрии — показывается (печатается плоский план). Минимальная зона +нажатия 44×44 px как у соседей. + +## 9. i18n + +Новые ключи (en/ru/de/fr): `title.export_pdf`, `pdf.title`, +`pdf.dimensions`, `pdf.decor`, `pdf.room_names`, `pdf.backdrop`, +`pdf.save`, `pdf.saving`, `pdf.failed`, `pdf.too_large`, `pdf.asset_failed`, +`pdf.scale` («Масштаб 1:{n}»), `pdf.north`, `pdf.legend.wall`, +`pdf.legend.partition`, `pdf.legend.virtual`, `pdf.legend.door`, +`pdf.legend.window`, `pdf.legend.gate`. Тест `i18n-dead-keys` требует +потребителя у каждого — все используются диалогом или подвалом. + +## 10. Модель данных, миграция, совместимость + +Конфиг, схема, бэкенд — без изменений; экспорт только читает. Планы любой +поддерживаемой версии модели печатаются через ту же нормализацию, что и +View. `localStorage` — единственное новое состояние, per-браузер. + +## 11. Затронутые файлы и модули + +- `src/pdf/pdf-writer.ts`, `pdf-scene.ts`, `pdf-dimensions.ts`, + `pdf-export.ts`, `hp-pdf-dialog.ts`, `pdf-font.generated.ts` — новые; +- `scripts/generate-pdf-font.mjs`, `assets/fonts/Roboto-Regular.ttf` + + `LICENSE` — новые; `package.json` (devDependency `subset-font`, скрипты + `pdf-font:generate`/`pdf-font:check`); +- `src/houseplan-card.ts` — кнопка, загрузчик, открытие диалога: ядро на + потолке 13 659 (факт 13 563 после #478 — запас 96 строк); добавление + укладывается в запас, но правило «хочешь добавить — вынеси» действует: + число в дифе; +- `scripts/bundle-manifest.mjs`, `scripts/bundle-budget.mjs` — роль `pdf`, + `lazyPdfFiles`, токен retry-URL; +- `src/i18n/*.json`; `docs/PDF-EXPORT.md` (новый), `docs/USER-GUIDE*.md`, + `docs/ARCHITECTURE.md` (ленивая граница), `docs/SCOPE.md` (исключение + 2026-08-15 — уже записано, проверить формулировку), `docs/CHANGELOG*`; +- тесты §13; golden-сцена; смок `demo/smoke_pdf_export.mjs`; + `scripts/mutation-gate.mjs` — свидетели §13. + +## 12. Критерии приёмки + +| AC | Критерий | Доказательство | +|---|---|---| +| AC1 | Кнопка-принтер между шестерёнкой и «?» у `_canEdit`, отсутствует в киоске; открывает диалог с заголовком, галочками §8.5 и кнопкой | smoke | +| AC2 | Галочка «подложка» присутствует только у пространства с `bg`; состояние галочек переживает перезагрузку страницы | smoke | +| AC3 | «Сохранить» скачивает файл `houseplan--<дата>.pdf`; в файле ровно одна страница A4 (MediaBox 595.28×841.89 или наоборот) с ориентацией по пропорциям | smoke (перехват download) + unit на writer | +| AC4 | Геометрия §7.1: стены с толщиной залиты, нулевые — штрих, проёмы с разрывами, общая стена один раз, ни одного маркера/состояния/цвета комнаты | golden по растру PDF (§13) + unit по операторам контент-потока | +| AC5 | Размеры: значения площадей и длин побайтно совпадают с `formatArea`/`formatLength` на тех же данных, что карточка комнаты и живая линейка (контракт #52) | unit (общие фикстуры с тестами #52-контракта: прямоугольник, L, общая толстая стена, виртуальная граница, партиция) | +| AC6 | Внешние размерные линии снаружи контура, текст не вверх ногами, коллизии детерминированы; пролёты < 40 см без подписи | unit на раскладку + golden | +| AC7 | Галочки реально управляют содержимым: без «размеров» в PDF нет ни одной размерной строки; без «названий» — ни одного названия; без «декора» — ни одного декор-объекта; без «подложки» — ни одного XObject Image | unit по извлечённому тексту/операторам | +| AC8 | Кириллица и латиница в названиях печатаются встроенным сабсетом; текст извлекается из PDF (`pdfjs-dist` в тесте) как Unicode — работает ToUnicode | unit | +| AC9 | Подвал: масштаб из ряда §7.5, линейка соответствует масштабу (1 м на бумаге = 1000/N мм ± 0,1 мм), стрелка севера при компасе, легенда только присутствующих элементов | unit | +| AC10 | Детерминизм: два вызова с тем же `now` дают идентичные байты; изменение любой галочки — другие | unit | +| AC11 | Ленивость: чанк `pdf` не в initial, initial View не растёт больше чем на кнопку/обработчик (≤ 400 Б gzip); отказ загрузки чанка → тост, карточка жива; чужая сборка → терминальный отказ без повтора | `bundle:budget` + smoke с перехватом чанка (образец #474) | +| AC12 | Скачивание работает в мобильном приложении HA (Android) — файл появляется в загрузках | вручную владельцем на даче до закрытия, запись в issue | +| AC13 | Растровая подложка встроена под геометрией с прозрачностью, лимит 25 МБ даёт `pdf.too_large` без файла | unit + smoke | +| AC14 | Диалог модален и центрирован после переподключения HA (контракт #463) | smoke `smoke_dialog_modal_recovery` расширяется на `hp-pdf-dialog` | +| AC15 | Свидетели §13 в реестре, каждый «поймано 1 из 1» | отрицательные прогоны | + +## 13. План автотестов + +- `test/pdf-writer.test.mjs` — объекты/xref валидны (парсится `pdfjs-dist`), шрифт CIDFontType2 + ToUnicode, изображение DCTDecode, детерминизм. +- `test/pdf-dimensions.test.mjs` — пролёты и площади на общих фикстурах, раскладка, коллизии, порог 40 см, «не вверх ногами». +- `test/pdf-scene.test.mjs` — таблица §7.1 по операторам, галочки (AC7), подвал (AC9), ориентация и ряд масштабов. +- `demo/smoke_pdf_export.mjs` — кнопка, диалог, `localStorage`, перехват `download`, MediaBox, отказ чанка, лимит растра. +- golden: сцена `pdf-export-geometry-light` — PDF рендерится `pdfjs-dist` в canvas в браузере golden-harness и сравнивается как PNG (детерминизм §8.4 делает это устойчивым); при отклонении — обычная приёмка эталона. +- Свидетели: `pdf-shared-wall-twice` (общая стена дважды), `pdf-dimensions-ignore-toggle` (галочка размеров не влияет), `pdf-virtual-wall-solid` (нулевая стена сплошная), `pdf-font-no-tounicode` (текст без ToUnicode — не извлекается), `pdf-backdrop-over-geometry` (подложка поверх геометрии), `pdf-devices-leak` (маркеры попадают на лист), `pdf-scale-not-standard` (масштаб не из ряда). + +## 14. Release-артефакты + +`User-Visible: yes`. Changelog en/ru: «Экспорт текущего пространства в PDF: чистый архитектурный план с размерами, площадями и названиями комнат, по желанию — мебель и подложка (#53)». `docs/PDF-EXPORT.md` (что печатается, правила измерений — ссылка на 052, ограничения растров, шрифт и лицензия), раздел в `USER-GUIDE`/`USER-GUIDE.ru`, скриншот диалога в документации (пересъёмка по правилу §8 PROCESS). + +## 15. Производительность и безопасность + +Генерация — синхронный расчёт сцены (< 200 мс на `large-house` фикстуре, 60 комнат; проверяется в unit как порог) плюс асинхронная растеризация подложки. Ничего не уходит в сеть, кроме загрузки чанка и получения подложки тем же путём, что и на экране. PDF не содержит ссылок, скриптов, метаданных HA (только название пространства, дома, дата, версия). Шрифт — лицензия Apache 2.0, файл лицензии в репозитории. + +## 16. Риски и меры + +| Риск | Мера | +|---|---| +| Собственный writer даёт PDF, который открывается не везде | тест парсером `pdfjs-dist` + ручная проверка в Acrobat Reader, Chrome, macOS Preview, Android (AC12); PDF 1.4 без потоков-объектов и без сжатия текста (только FlateDecode контента при наличии `CompressionStream`, иначе без сжатия) | +| Размерные подписи налезают друг на друга на плотных планах | детерминированная коллизия §7.3 с приоритетом внешнего контура; проверено на `large-house` фикстуре в golden | +| Ядро на потолке | логика целиком в `src/pdf/**`, ядро — кнопка и вызов; счёт строк в дифе | +| Растры делают файл огромным | лимит 25 МБ, JPEG q=0.85, тост вместо молчаливого 100-МБ файла | +| Мобильное приложение не скачивает Blob | AC12 вручную до закрытия; при отказе — запасной путь `data:`-URL в новой вкладке, решение по факту | +| Детерминизм ломается плавающей точкой раскладки | координаты округляются до 0,01 pt перед записью | + +## 17. Откат + +Revert одного коммита: кнопка и чанк исчезают, конфиг и данные не +затрагивались, `localStorage`-ключ становится бесхозным (безвреден). + +## 18. Принятые предположения и вопросы к ревью ТЗ + +- **Q1 — значения галочек по умолчанию.** Владелец задал только «размеры — + включена». Принято: названия комнат — **включена** (архитектурный план + без названий комнат нечитаем), мебель и другой декор — **выключена** + («чистый план»), подложка — **включена, если есть** (формулировка + владельца «подложка — да, если она включена на этом пространстве»). +- **Q2 — кому видна кнопка.** Принято: тем же, кому видны шестерёнка и «?» + (`_canEdit`), поскольку владелец поместил её между ними; не-админы + экспорт не получают. Альтернатива — всем, кто видит карточку. +- **Q3 — формат листа и масштаб.** Принято автоматическое решение §7.5 + (A4, автоориентация, стандартный ряд масштабов); выбор в диалоге — + не-скоуп. +- Шрифт Roboto Regular как единственный (без жирного): заголовок — тот же + шрифт большим кеглем. +- Изометрическая проекция на печать не влияет: всегда плоский план. diff --git a/docs/specs/README.md b/docs/specs/README.md index 78c8de3b..57a45f9c 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -114,6 +114,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#44](https://github.com/Matysh/houseplan-card/issues/44) Filtering/grouping policy | [044-filter-grouping-policy.md](044-filter-grouping-policy.md) | | [#51](https://github.com/Matysh/houseplan-card/issues/51) Custom decor images | [051-custom-decor-images.md](051-custom-decor-images.md) | | [#52](https://github.com/Matysh/houseplan-card/issues/52) Dimensions in View — закрыт, объединён с [#53](https://github.com/Matysh/houseplan-card/issues/53); файл остаётся контрактом измерений для экспорта PDF | [052-view-dimensions.md](052-view-dimensions.md) | +| [#53](https://github.com/Matysh/houseplan-card/issues/53) Экспорт пространства в PDF: чистый архитектурный план | [053-pdf-export.md](053-pdf-export.md) | | [#54](https://github.com/Matysh/houseplan-card/issues/54) Zigbee topology overlay | [054-zigbee-topology-overlay.md](054-zigbee-topology-overlay.md) | | [#55](https://github.com/Matysh/houseplan-card/issues/55) Independent Glow overlay | [055-independent-glow-overlay.md](055-independent-glow-overlay.md) | | [#56](https://github.com/Matysh/houseplan-card/issues/56) Static room color | [056-static-room-color.md](056-static-room-color.md) |