From cc93e93da9277a5e20c677e35ea1a0be0af57791 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 7 Sep 2026 02:09:38 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=A2=D0=97=20#53=20=E2=80=94=20=D0=B2?= =?UTF-8?q?=D0=BD=D1=83=D1=82=D1=80=D0=B5=D0=BD=D0=BD=D0=B8=D0=B5=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BD?= =?UTF-8?q?=D0=B0=D1=82=20=D0=B3=D1=80=D0=B0=D0=BD=D1=8C-=D0=B2-=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D1=8C=20=D0=B8=20=D0=B2=D0=BD=D0=B5=D1=88?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D1=8B?= =?UTF-8?q?=20=D0=B3=D1=80=D0=B0=D0=BD=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #53 User-Visible: no --- docs/specs/052-view-dimensions.md | 10 +++-- docs/specs/053-pdf-export.md | 73 ++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/docs/specs/052-view-dimensions.md b/docs/specs/052-view-dimensions.md index 0da3f2d9..a6097d7e 100644 --- a/docs/specs/052-view-dimensions.md +++ b/docs/specs/052-view-dimensions.md @@ -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 diff --git a/docs/specs/053-pdf-export.md b/docs/specs/053-pdf-export.md index f8a3f196..c9bb65b6 100755 --- a/docs/specs/053-pdf-export.md +++ b/docs/specs/053-pdf-export.md @@ -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--<дата>.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 в новой вкладке, решение по факту |