docs: ТЗ #53 — внутренние размеры комнат грань-в-грань и внешние размеры граней

Issue: #53
User-Visible: no
This commit is contained in:
Codex
2026-09-07 02:09:38 +03:00
parent 48a17f5dc9
commit cc93e93da9
2 changed files with 53 additions and 30 deletions
+6 -4
View File
@@ -1,10 +1,12 @@
# ТЗ #52 — Размеры стен и площади в View
> **Закрыто 2026-09-07 решением владельца, объединено с #53.** Слой размеров
> в режиме просмотра не делается. Разделы «Площади» и «Длины» ниже остаются
> действующим контрактом измерений и переиспользуются опцией «Показывать
> размеры» экспорта PDF (#53); разделы «Настройка» и «Collision/zoom» к
> экрану больше не относятся — раскладку на печатном листе задаёт ТЗ #53.
> в режиме просмотра не делается. Раздел «Площади» остаётся действующим
> контрактом и переиспользуется опцией «Отображать размеры» экспорта PDF
> (#53). Раздел «Длины» (осевые пролёты стен) на бумаге НЕ применяется:
> ТЗ #53 §7.3 печатает внутренние размеры каждой комнаты грань-в-грань и
> внешние размеры наружных граней. «Настройка» и «Collision/zoom» к экрану
> больше не относятся.
- Issue: https://github.com/Matysh/houseplan-card/issues/52 (закрыт, см. #53)
- Приоритет: P3
+47 -26
View File
@@ -4,7 +4,7 @@
- **Приоритет:** 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 (диалог с кнопкой)
- **Связанные контракты:** `docs/specs/052-view-dimensions.md` (§ Площади — контракт площадей; § Длины на бумаге не применяется, см. §7.3), `docs/WALL-THICKNESS.md`, `docs/BACKDROP.md`, `docs/FURNITURE.md`, `docs/SCOPE.md`, #456 (образец диалога у пространства), #476 (диалог с кнопкой)
## 1. Сценарий
@@ -110,29 +110,50 @@
### 7.3. Размеры и площади (галочка, по умолчанию включена)
Правила измерения — **дословно из `052-view-dimensions.md`**, § Площади и
§ Длины: площадь чистого пола через `geometryArea`; уникальные физические
осевые пролёты стен из нормализованной модели, общая стена один раз,
виртуальные границы без длины, проёмы не вычитаются, колонны без линейного
размера, коллинеарный мусор через существующую компакцию. Значения —
`formatArea`/`formatLength`, единицы из HA (metric/imperial), масштаб сетки
пространства (`cell_cm`).
Решение владельца 2026-09-07: **внутренние размеры каждой комнаты,
достаточные, чтобы начертить её независимо от остальной планировки, и
внешние размеры каждой наружной грани планировки.** Осевые пролёты стен из
`052-view-dimensions.md` § Длины на бумаге не используются; из #52
переиспользуется только § Площади.
Раскладка на бумаге (в отличие от экрана — без зума и без указателя):
**Площадь** — чистый пол через `geometryArea` (тот же резолвер, что у
карточки комнаты), `formatArea`, единицы из HA. Печатается под названием
комнаты кеглем на 1 pt меньше; без названия — одна строка площади в той же
точке.
- **площадь** — под названием комнаты тем же кеглем минус 1 pt; если
названия нет или оно скрыто — в той же точке одна строка площади;
- **внешние стены** — размерные линии *снаружи* внешнего контура:
выносные линии перпендикулярно грани, размерная линия с засечками
(архитектурная нотация), текст над линией, читаемый слева направо или
снизу вверх, никогда вверх ногами; пролёты одной прямой грани — цепочкой
на одной размерной линии;
- **внутренние стены и перегородки** — подпись вдоль пролёта с той стороны,
где больше свободного пола; кегль 7 pt;
- **коллизии** — детерминированно: приоритет внешний контур → длинный
пролёт → короткий; подпись, чей прямоугольник пересекает уже размещённый
или выходит за поле листа, скрывается; пролёты короче 40 см на бумаге не
подписываются.
**Внутренние размеры комнаты.** Контур — внутренние грани стен комнаты
(чистый пол, тот же полигон, что у площади), после компакции коллинеарных
рёбер. Каждое ребро контура получает длину `formatLength(segmentCm)` —
расстояние от грани до грани, а не по оси стены. Подпись — внутри комнаты,
параллельно ребру, отступ 2 мм от грани, читается слева направо или снизу
вверх. Для комнаты-прямоугольника печатаются все четыре ребра; если при
коллизии места не хватает, второе ребро той же длины и того же направления
может быть опущено — комната остаётся вычерчиваемой (по одной длине на
направление). Для непрямоугольных контуров опускать нельзя: каждое ребро —
свой размер. Рёбра короче 30 см на бумаге не подписываются, но их длину
можно восстановить из соседних — такие рёбра помечаются короткой засечкой
без числа. Проёмы на длину ребра не влияют (ребро — грань стены целиком).
**Внешние размеры планировки.** Внешний контур — объединение физических
стен пространства по наружным граням (тот же силуэт, что у контура
«вписать всё»), после компакции. Каждая прямая наружная грань получает
размерную линию *снаружи* контура на расстоянии 6 мм: выносные линии
перпендикулярно грани, засечки, число над линией. Грани одной прямой
(сдвиг по наружной стене из-за выступа) — отдельные размеры, каждая своя;
суммарной цепочки нет (её даёт сумма). Несколько несвязных наружных
контуров (пристройка, отдельное строение в пространстве) — каждый со
своими внешними размерами. Виртуальные (нулевые) границы наружного
контура длину не получают.
**Единицы и точность** — `formatLength`/`formatArea`, metric/imperial из
HA, масштаб сетки `cell_cm`; числа совпадают с живой линейкой ресайза на
том же ребре и с площадью в карточке комнаты.
**Коллизии** — детерминированно: приоритет внешние размеры → площади →
внутренние рёбра длинные → короткие; подпись, чей прямоугольник
пересекает уже размещённый или выходит за поле листа, скрывается (кроме
случая «непрямоугольный контур» — там уменьшается кегль до 6 pt прежде
чем скрыться). Никакая подпись не печатается вверх ногами.
### 7.4. Мебель и другой декор (галочка, по умолчанию — Q1)
@@ -293,8 +314,8 @@ View. `localStorage` — единственное новое состояние,
| AC2 | Галочка «подложка» присутствует только у пространства с `bg`; состояние галочек переживает перезагрузку страницы | smoke |
| AC3 | «Сохранить» скачивает файл `houseplan-<slug>-<дата>.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 |
| AC5 | Внутренние размеры: у каждой комнаты подписано каждое ребро чистого контура (грань-в-грань), значения совпадают с `formatLength` живой линейки на том же ребре, площадь — с карточкой комнаты; по этим числам комната вычерчивается автономно (тест восстанавливает контур из напечатанных длин и сверяет с моделью) | unit (фикстуры: прямоугольник, L, комната с выступом, общая толстая стена, виртуальная граница, партиция внутри комнаты) |
| AC6 | Внешние размеры: у каждой прямой наружной грани планировки своя размерная линия снаружи контура с выносными линиями; несвязные контуры — каждый свои; виртуальные границы без размера; текст не вверх ногами; коллизии детерминированы; рёбра < 30 см — засечка без числа | unit на раскладку + golden |
| AC7 | Галочки реально управляют содержимым: без «размеров» в PDF нет ни одной размерной строки; без «названий» — ни одного названия; без «декора» — ни одного декор-объекта; без «подложки» — ни одного XObject Image | unit по извлечённому тексту/операторам |
| AC8 | Кириллица и латиница в названиях печатаются встроенным сабсетом; текст извлекается из PDF (`pdfjs-dist` в тесте) как Unicode — работает ToUnicode | unit |
| AC9 | Подвал: масштаб из ряда §7.5, линейка соответствует масштабу (1 м на бумаге = 1000/N мм ± 0,1 мм), стрелка севера при компасе, легенда только присутствующих элементов | unit |
@@ -312,7 +333,7 @@ View. `localStorage` — единственное новое состояние,
- `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` (масштаб не из ряда).
- Свидетели: `pdf-shared-wall-twice` (общая стена дважды), `pdf-dimensions-ignore-toggle` (галочка размеров не влияет), `pdf-room-edge-dropped` (у непрямоугольной комнаты пропущено ребро — контур невосстановим), `pdf-outer-face-inside` (внешний размер нарисован внутри контура), `pdf-virtual-wall-solid` (нулевая стена сплошная), `pdf-font-no-tounicode` (текст без ToUnicode — не извлекается), `pdf-backdrop-over-geometry` (подложка поверх геометрии), `pdf-devices-leak` (маркеры попадают на лист), `pdf-scale-not-standard` (масштаб не из ряда).
## 14. Release-артефакты
@@ -327,7 +348,7 @@ View. `localStorage` — единственное новое состояние,
| Риск | Мера |
|---|---|
| Собственный writer даёт PDF, который открывается не везде | тест парсером `pdfjs-dist` + ручная проверка в Acrobat Reader, Chrome, macOS Preview, Android (AC12); PDF 1.4 без потоков-объектов и без сжатия текста (только FlateDecode контента при наличии `CompressionStream`, иначе без сжатия) |
| Размерные подписи налезают друг на друга на плотных планах | детерминированная коллизия §7.3 с приоритетом внешнего контура; проверено на `large-house` фикстуре в golden |
| Размерные подписи налезают друг на друга на плотных планах (по 4+ числа на комнату) | детерминированная коллизия §7.3: внешние → площади → длинные рёбра; у прямоугольников дубликаты опускаются первыми; проверено на `large-house` фикстуре в golden |
| Ядро на потолке | логика целиком в `src/pdf/**`, ядро — кнопка и вызов; счёт строк в дифе |
| Растры делают файл огромным | лимит 25 МБ, JPEG q=0.85, тост вместо молчаливого 100-МБ файла |
| Мобильное приложение не скачивает Blob | AC12 вручную до закрытия; при отказе — запасной путь `data:`-URL в новой вкладке, решение по факту |