mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
491 lines
39 KiB
Markdown
491 lines
39 KiB
Markdown
# #482 — Доводка экспорта пространства в PDF
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/482
|
||
- **Тип / приоритет:** bug + polish / P1
|
||
- **Трек:** полный; меняются геометрия размеров, PDF-примитивы, компоновка
|
||
страницы, адаптивность View-диалога и golden-эталон
|
||
- **Оценка:** пользовательская ценность 8/10; ценность для разработки 7/10;
|
||
сложность 7/10; риск 7/10
|
||
- **Связано:** #52, #53; `docs/specs/052-view-dimensions.md`,
|
||
`docs/specs/053-pdf-export.md`, `docs/PDF-EXPORT.md`,
|
||
`docs/TOUCH-SUPPORT.md`
|
||
|
||
## 1. Сценарий
|
||
|
||
Персона — Home admin из `docs/SCOPE.md`. Поверхность — полная карточка House
|
||
Plan в режиме View на desktop либо touch-устройстве. Момент — пользователь
|
||
открывает «Сохранение в PDF», выбирает состав архитектурного чертежа и сохраняет
|
||
текущее пространство для печати, передачи строителю или обсуждения планировки.
|
||
|
||
В исходном плане могут одновременно присутствовать внешние и внутренние стены,
|
||
проёмы, перегородки, колонны, ступенчатые и вогнутые комнаты, декор, растровая
|
||
подложка и заданное направление севера.
|
||
|
||
## 2. Что человек увидит до и после
|
||
|
||
**До:** узкий диалог может получить горизонтальный scroll; противоположные
|
||
стороны дублируют одинаковые размеры; почти совпадающие вершины иногда создают
|
||
ложную диагональ; подписи прилипают к стенам; стены не похожи на стены плана;
|
||
чертёж плохо заполняет лист; север показан упрощённой стрелкой; внизу печатается
|
||
техническая легенда `wall · door · window`.
|
||
|
||
**После:** диалог помещается от 320 CSS px, размерные цепочки остаются только у
|
||
реальных осевых граней и не повторяют локальную противоположную пару, подписи
|
||
расположены равномерно и не пересекают стены, физические стены имеют фон
|
||
`#7f7f7f` и штриховку, полный чертёж оптимально вписан и центрирован, север
|
||
обозначен векторным компасом, а технической легенды нет.
|
||
|
||
## 3. Подтверждённые причины
|
||
|
||
1. `compactRing()` удаляет коллинеарные точки до схлопывания соседних точных и
|
||
почти точных дублей. В ступенчатом контуре это соединяет несмежные стороны
|
||
хордой. На приложенном примере ложные `3.39 m` и `3.93 m` совпадают с
|
||
диагоналями между реальными ортогональными участками; стены под углом 72° в
|
||
исходном плане нет.
|
||
2. Размеры формируются для каждой пригодной стороны независимо: понятия
|
||
локальной пары противоположных граней нет.
|
||
3. Внутренние подписи используют фиксированные 2 mm без учёта bbox текста и
|
||
тела стены; fallback сдвигает только текст вдоль стены. Внешняя shelf также
|
||
может двигаться отдельно от своей размерной линии.
|
||
4. Ориентация выбирается по сырому bbox архитектуры, до построения размеров и
|
||
выносок. Дополнительно всегда резервируются приближённые 30/48 mm, после чего
|
||
центрируется только архитектура. Поэтому масштаб и свободные поля не
|
||
соответствуют фактической печатной сцене.
|
||
5. PDF writer умеет заливать и обводить path, но не умеет even-odd clipping для
|
||
штриховки и заполненный составной vector path для нового компаса.
|
||
6. Общий двухзнаковый formatter PDF превратит `127 / 255` в `0.5`, то есть в
|
||
`#808080`. Для точного `#7f7f7f` цвету нужна отдельная точность.
|
||
7. В CSS диалога padding прибавляется к `min-width`, flex-подписи не могут
|
||
сжиматься, а footer запрещает перенос.
|
||
|
||
## 4. Изменяемый контракт
|
||
|
||
Это ТЗ заменяет только противоречащие ему части #53:
|
||
|
||
- §7.1: цвет и вид физических стен;
|
||
- §7.3: правило «все четыре стороны» и размещение размеров;
|
||
- §7.5: упрощённую стрелку севера и архитектурную легенду;
|
||
- §8.1–8.2: выбор ориентации, масштаба и центрирование;
|
||
- связанные AC4, AC6 и golden-ожидания.
|
||
|
||
Остальные гарантии #53 сохраняются: один лист A4, стандартный масштаб,
|
||
генерация на клиенте, Unicode-шрифт, детерминированные байты, отсутствие
|
||
маркеров и live-state, опциональные названия/размеры/декор/подложка и ленивый
|
||
PDF chunk.
|
||
|
||
## 5. Скоуп
|
||
|
||
1. Сделать диалог PDF безопасным на узкой View-поверхности.
|
||
2. Исправить нормализацию контуров до генерации размеров.
|
||
3. Ограничить размеры горизонтальными и вертикальными гранями с каноническим
|
||
near-axis допуском.
|
||
4. Убирать только одну из локальной пары геометрически противоположных равных
|
||
граней.
|
||
5. Перестроить размещение внутренних и внешних размеров в согласованные полосы.
|
||
6. Добавить физическим стенам, перегородкам и колоннам точный фон и штриховку с
|
||
clipping по их реальной геометрии и проёмам.
|
||
7. Выбирать ориентацию и стандартный масштаб по bbox фактически построенной
|
||
сцены и центрировать эту сцену в печатном поле.
|
||
8. Заменить стрелку севера на переданный векторный компас.
|
||
9. Удалить архитектурную легенду, сохранив дату, версию и масштабную линейку.
|
||
10. Обновить тесты, golden, документацию и лицензионное уведомление.
|
||
|
||
## 6. Не входит
|
||
|
||
- изменение модели пространства, wall segments, openings, decor или PDF
|
||
настроек в `localStorage`;
|
||
- новые настройки экспорта либо изменение значений существующих галочек;
|
||
- размеры диагональных стен, дуг, мебели, проёмов, перегородок или колонн;
|
||
- многостраничный PDF, другой формат бумаги или ручной выбор масштаба;
|
||
- ручное перетаскивание размеров и автоматические строительные размерные цепи;
|
||
- изменение вида стен на интерактивном плане;
|
||
- изменение семантики `north_deg` либо способа задания севера;
|
||
- печать устройств, их состояний или подключение внешних PDF-сервисов;
|
||
- общий рефакторинг writer за пределами нужных заполненных vector path,
|
||
clipping и точной сериализации цвета.
|
||
|
||
Если исправление потребует нового config-поля, изменения пользовательских
|
||
галочек или поддержки размеров настоящих диагональных стен, задача возвращается
|
||
в `S3-spec`.
|
||
|
||
## 7. Диалог на узкой поверхности
|
||
|
||
- Поддерживаемая минимальная ширина viewport — 320 CSS px.
|
||
- Surface, body и footer используют `box-sizing:border-box`, `min-width:0` и не
|
||
шире доступной области. Padding входит в вычисленную ширину.
|
||
- Текст внутри label может переноситься; checkbox/radio/input не сжимается и не
|
||
отделяется от начала своей подписи.
|
||
- При ширине, на которой две footer-кнопки с локализованными подписями не
|
||
помещаются, footer становится одной колонкой: «Отмена» сверху, основное
|
||
действие «Сохранить PDF» снизу; обе кнопки занимают доступную ширину.
|
||
- На большей ширине сохраняется действующий горизонтальный footer.
|
||
- Горизонтальный scroll запрещён для документа, dialog surface, body и footer.
|
||
Вертикальный scroll допустим; все элементы остаются доступны.
|
||
- Интерактивные цели сохраняют минимум 44×44 CSS px, focus ring и существующий
|
||
keyboard contract.
|
||
- Контракт проверяется во всех четырёх локалях EN/RU/DE/FR, а не только на самой
|
||
короткой строке.
|
||
|
||
## 8. Нормализация контуров и допустимые грани
|
||
|
||
### 8.1 Порядок очистки ring
|
||
|
||
Перед поиском коллинеарных вершин каждый конечный ring нормализуется так:
|
||
|
||
1. удалить повтор последней точки, если он замыкает первую;
|
||
2. схлопнуть соседние точные либо близкие точки, включая пару на seam
|
||
«последняя → первая»;
|
||
3. только после этого итеративно удалять промежуточную точку действительно
|
||
прямого участка;
|
||
4. повторять шаги 2–3 до fixed point либо пока проход ничего не изменил.
|
||
|
||
Близость точек — не более 1 mm в физических координатах плана, переведённых в
|
||
render coordinates через действующий `cell_cm`. Это только фильтр печатной
|
||
геометрии: исходный config не мутируется. Точки с нечисловыми координатами не
|
||
создают размер.
|
||
|
||
Точка считается промежуточной только когда оба соседних вектора ненулевые,
|
||
коллинеарны в пределах канонического допуска и их скалярное произведение
|
||
положительно. Поэтому разворот, настоящий угол и короткая грань между разными
|
||
точками не превращаются в хорду. Результат не содержит нулевых рёбер.
|
||
|
||
### 8.2 Только осевые размеры
|
||
|
||
Размер допускается только для:
|
||
|
||
- строго горизонтальной/вертикальной грани; либо
|
||
- near-axis грани, отклонённой не более чем на канонические 0.25° из
|
||
`src/near-axis.ts` (`NEAR_AXIS_MAX_DEGREES` / `NEAR_AXIS_MAX_SLOPE`).
|
||
|
||
Для near-axis шума печатная размерная грань выравнивается по major axis через
|
||
её midpoint и использует длину major projection. Исходная геометрия не
|
||
изменяется. Грань за пределом допуска полностью исключается: запрещено строить
|
||
проекцию, хорду или соединять её соседние концы.
|
||
|
||
Каждый кандидат сохраняет стабильную ссылку на исходные ring и edge index до
|
||
компактации. Стабильность нужна для дедупликации, layout и детерминированного
|
||
порядка, но не экспортируется в PDF.
|
||
|
||
## 9. Локальная дедупликация противоположных размеров
|
||
|
||
Равный отформатированный текст сам по себе никогда не является основанием для
|
||
удаления. Дедупликация выполняется отдельно:
|
||
|
||
- внутри одного room inner contour; либо
|
||
- внутри одного связного external outer ring.
|
||
|
||
Два кандидата образуют пару только если одновременно:
|
||
|
||
1. они имеют одну ось и противоположные нормали;
|
||
2. их tangent projection intervals совпадают с допуском 1 mm;
|
||
3. их projected lengths совпадают с допуском 1 mm;
|
||
4. midpoint соединяющего их перпендикуляра лежит внутри соответствующего
|
||
простого ring;
|
||
5. обе грани остаются допустимыми после §8.
|
||
|
||
Для external ring проверяется внутренняя область самого ring; внутренние holes
|
||
не связывают несвязанные здания. Один кандидат участвует максимум в одной паре.
|
||
Если возможны несколько пар, выбирается ближайшая по перпендикуляру, затем по
|
||
стабильному edge key.
|
||
|
||
Из пары остаётся сторона с лучшим placement score: меньше hard collisions с
|
||
телом архитектуры/границей печатного поля/уже размещёнными более приоритетными
|
||
аннотациями, затем больше свободного пространства по нормали. Полная ничья
|
||
разрешается минимальным стабильным ключом `(axis, tangentStart, normalSign,
|
||
ringIndex, sourceEdgeIndex)`.
|
||
|
||
Следствия:
|
||
|
||
- прямоугольная комната получает один горизонтальный и один вертикальный
|
||
внутренний размер, а не четыре;
|
||
- одинаковые значения у разных комнат, разных connected rings либо на разных
|
||
осях сохраняются;
|
||
- равные, но не противоположные участки L/ступенчатого контура сохраняются;
|
||
- общий горизонтальный и вертикальный габарит одинаковой длины не конфликтуют.
|
||
|
||
## 10. Размещение размеров
|
||
|
||
Все вычисления clearance выполняются в mm страницы после выбора масштаба и по
|
||
фактическому rotated bbox текста.
|
||
|
||
### 10.1 Общие правила
|
||
|
||
- Центр текста проецируется в midpoint измеряемой грани.
|
||
- Между bbox текста и ближайшей гранью тела стены остаётся минимум 1 mm.
|
||
- Текст, основная размерная линия и shelf не пересекают тело стены. Extension
|
||
внешнего размера может начинаться на самой внешней грани стены, но не входит
|
||
внутрь её тела.
|
||
- Для вогнутого ring внутренняя/внешняя нормаль определяется пробами
|
||
point-in-ring по обе стороны грани, а не только направлением к centroid.
|
||
- Нельзя исправлять collision индивидуальным tangent-jitter текста.
|
||
|
||
### 10.2 Внутренние размеры
|
||
|
||
Внутренний размер сохраняет действующий лаконичный text-only вид. Его базовая
|
||
полоса находится внутрь комнаты на расстоянии, равном половине normal extent
|
||
текста плюс 1 mm от реальной внутренней грани стены.
|
||
|
||
Размеры одного room contour с общей стороной/осью/нормалью относятся к одной
|
||
полосе. Если любой элемент полосы конфликтует с архитектурой либо уже принятым
|
||
более приоритетным элементом, вся полоса смещается внутрь шагами 3 mm до первого
|
||
валидного положения. Если валидного положения нет, сохраняется существующая
|
||
выноска для непрямоугольной комнаты; произвольный сдвиг текста вдоль стены не
|
||
допускается.
|
||
|
||
### 10.3 Внешние размеры
|
||
|
||
Базовая размерная линия проходит на 6 mm наружу от внешней грани тела стены.
|
||
Текст расположен снаружи линии с зазором 1 mm. Extension lines начинаются на
|
||
грани тела стены и заканчиваются на своей размерной линии.
|
||
|
||
Коллинеарные внешние размеры с общей осью и внешней нормалью образуют одну
|
||
полосу. При collision вся полоса — line, extensions, shelf и text — смещается
|
||
наружу шагами 4 mm. Shelf используется только когда текст шире доступного
|
||
участка и остаётся частью той же полосы.
|
||
|
||
Базовое расстояние одинаково для всех размеров своего класса. Дополнительные
|
||
полосы разрешены только как детерминированный ответ на collision; отдельная
|
||
подпись не может «прилипнуть» к стене или уехать независимо.
|
||
|
||
## 11. Вид физических стен
|
||
|
||
В PDF следующие объекты получают одинаковый material:
|
||
|
||
- физические room/external/shared стены ненулевой толщины;
|
||
- перегородки ненулевой толщины;
|
||
- квадратные и круглые колонны ненулевого размера.
|
||
|
||
Material состоит из:
|
||
|
||
1. фон RGB `127 127 127`, то есть точный `#7f7f7f`;
|
||
2. одна семья диагональных линий 45° к странице, шаг 3 mm, толщина 0.18 mm,
|
||
цвет основной печатной краски;
|
||
3. существующий контур толщиной 0.25 mm поверх fill и hatch.
|
||
|
||
Фаза штриховки привязана к началу страницы, поэтому соседние стены не начинают
|
||
узор заново и не дают заметных швов. Hatch строится в paper coordinates и
|
||
обрезается even-odd clip path (`W* n` или эквивалентом) фактического тела
|
||
каждого объекта. Проёмы остаются чистыми holes; hatch/fill не попадает в tunnel.
|
||
|
||
Нулевая стена сохраняет действующий line contract и не получает ни серый фон,
|
||
ни hatch. Decor и растровая подложка не меняются.
|
||
|
||
Цвет сериализуется отдельным formatter с достаточной точностью (минимум пять
|
||
знаков после точки для `127/255`). Координатный formatter writer не меняется:
|
||
это сохраняет детерминизм и размер файла.
|
||
|
||
## 12. Компоновка листа
|
||
|
||
### 12.1 Кандидаты
|
||
|
||
Для обеих ориентаций A4 — portrait и landscape — строится фактическая сцена с:
|
||
|
||
- архитектурой, включёнными decor/подложкой;
|
||
- реально оставшимися dimension lines, extensions, shelves и text;
|
||
- фактически понадобившимися выносками;
|
||
- line widths и rotated text bbox.
|
||
|
||
Приближённые безусловные резервы 30/48 mm удаляются. Header и footer не входят
|
||
в plan scene и заранее вычитаются из доступного поля.
|
||
|
||
Для каждой ориентации проверяется действующий стандартный ряд масштабов от
|
||
наиболее крупного к мелкому. Кандидат валиден, только если полный scene bbox с
|
||
допуском 0.5 mm находится внутри печатного поля.
|
||
|
||
Для экстремального плана после 1:500 denominator продолжается с шагом 50 через
|
||
конечный bracket/refinement. Заведомо невместимая фиксированная аннотация
|
||
завершает экспорт `pdf.failed`; обрезанный best-effort PDF не возвращается.
|
||
|
||
### 12.2 Выбор и центрирование
|
||
|
||
Побеждает:
|
||
|
||
1. минимальный denominator, то есть наиболее крупный стандартный масштаб;
|
||
2. при равном масштабе — большая доля площади печатного поля, занятая полным
|
||
scene bbox;
|
||
3. при полной ничьей — portrait.
|
||
|
||
После выбора переводится весь scene целиком: paths, raster, размеры, текст и
|
||
выноски. Центр полного bbox совпадает с центром доступного plan field с допуском
|
||
0.5 mm. Ни один элемент не пересекает header/footer или поля страницы.
|
||
|
||
Подпись масштаба и scale bar вычисляются из выбранного denominator; нельзя
|
||
визуально увеличить чертёж, оставив старое значение масштаба.
|
||
|
||
## 13. Векторный компас
|
||
|
||
Если у пространства задан `north_deg`, существующее место северного указателя
|
||
занимает `compass-line` из VMware Clarity Assets (`viewBox 0 0 36 36`) — обе
|
||
канонические filled path, без растрирования и без внешних ссылок.
|
||
|
||
Provenance проверен перед реализацией по неизменяемым первичным материалам:
|
||
|
||
- точный upstream commit —
|
||
https://github.com/vmware-archive/clarity-assets/commit/bf6bdd0dd3f247f1a320d44d13fecdeda18c071c;
|
||
- SVG в этом commit —
|
||
https://github.com/vmware-archive/clarity-assets/blob/bf6bdd0dd3f247f1a320d44d13fecdeda18c071c/icons/travel/compass-line.svg;
|
||
- MIT license и copyright VMware 2018 в том же commit —
|
||
https://github.com/vmware-archive/clarity-assets/blob/bf6bdd0dd3f247f1a320d44d13fecdeda18c071c/LICENSE.
|
||
|
||
Переданный владельцем файл имеет SHA-256
|
||
`461EBD20E65DF8F017CADCCFEF4F58E7A7E5CB3A8877B4797E6C81C011E41F0B`.
|
||
Он не совпадает с upstream побайтово: SVG Repo добавил XML/comment и заменил
|
||
display size `36×36` на `800×800`. При этом `viewBox` и значения обеих `d`
|
||
path совпадают с точным upstream blob. Реализация вендорит path из указанного
|
||
upstream commit, а не оболочку SVG Repo, и сохраняет полный MIT license и
|
||
copyright рядом с ассетом либо в third-party notices.
|
||
|
||
Компас:
|
||
|
||
- сохраняет квадратный aspect ratio и основной печатный цвет;
|
||
- остаётся vector content PDF;
|
||
- вращается по прежней семантике: `0°` — северная стрелка вверх страницы,
|
||
`90°` — вправо, `180°` — вниз, `270°` — влево;
|
||
- использует действующий resolver `north_deg` и подпись `pdf.north`;
|
||
- отсутствует, если направление севера не задано.
|
||
|
||
PDF writer получает минимально необходимый filled vector primitive с явно
|
||
заданными fill rule/fill/stroke. Он не становится универсальным SVG renderer.
|
||
|
||
## 14. Footer и i18n
|
||
|
||
Архитектурная легенда полностью удаляется: в PDF нет `wall · door · window`, её
|
||
переводов либо разделителей. Неиспользуемые `pdf.legend.*` удаляются из EN/RU/
|
||
DE/FR словарей.
|
||
|
||
В footer сохраняются локализованная дата, `House Plan v…`, действующие
|
||
разделители и масштабная линейка. Новых пользовательских строк нет. Изменённые
|
||
описания в документации обновляются на EN/RU; существующие строки диалога
|
||
проверяются во всех четырёх локалях.
|
||
|
||
## 15. Модель, совместимость и rollback
|
||
|
||
Config schema, `config_version`, модель пространства и ключи сохранённых PDF
|
||
опций не меняются. Миграции и downgrade converter не нужны. Экспорт читает
|
||
исходную модель, создаёт нормализованную временную геометрию и не записывает её
|
||
обратно.
|
||
|
||
Rollback — возврат frontend/PDF lazy chunk к предыдущей версии. Сохранённые
|
||
планы и пользовательские настройки остаются совместимы в обе стороны.
|
||
|
||
## 16. Touch, доступность, безопасность и производительность
|
||
|
||
- PDF-диалог относится к View, поэтому полностью поддерживается на touch, а не
|
||
по best-effort правилу редакторов. На 320 px он остаётся читаемым и usable.
|
||
- Клавиатура, Escape, focus trap, focus return и screen-reader names действуют
|
||
как в #53; перестройка footer не меняет DOM-порядок действий.
|
||
- Генерация остаётся полностью локальной. Новый ассет вендорится, runtime
|
||
network request, пользовательский HTML и новые данные HA не появляются.
|
||
- Lazy boundary сохраняется: до открытия экспорта PDF writer, compass и шрифт
|
||
не загружаются в initial bundle.
|
||
- Детерминированность байтов при одинаковых `now`, config и options сохраняется.
|
||
- Бюджет построения сцены из теста #53 остаётся `<200 ms` для его large-house
|
||
fixture на референсной Node-среде. Проверка двух ориентаций не должна
|
||
выполнять повторный разбор модели или декодирование растра.
|
||
|
||
## 17. Acceptance criteria и доказательства
|
||
|
||
| AC | Критерий | Обязательное доказательство |
|
||
|---|---|---|
|
||
| AC1 | При viewport 320×760 в EN/RU/DE/FR ни document, ни surface/body/footer диалога не имеют горизонтального overflow; кнопки полностью доступны и не меньше 44×44 px | targeted browser smoke с `scrollWidth <= clientWidth`, bbox кнопок и фактическим export |
|
||
| AC2 | Точные/почти точные соседние вершины схлопываются до удаления коллинеарных точек; каждый реальный поворот сохраняется; нулевых рёбер и ложных `3.39/3.93` нет | unit fixture регрессии + mutation witness, отключающий duplicate normalization |
|
||
| AC3 | Размер получают только H/V и near-axis ≤0.25°; грань за порогом не создаёт проекцию/хорду | boundary unit на обе стороны допуска + scene test + mutation witness без axis filter |
|
||
| AC4 | Удаляется одна локальная противоположная пара, но сохраняются одинаковые значения разных комнат/rings/осей и непарные равные грани L-контура | unit topology matrix + scene counts + mutation witness глобальной дедупликации |
|
||
| AC5 | Подпись центрирована, clearance ≥1 mm, базовая полоса едина; collision перемещает целиком полосу, включая line/extensions/shelf | unit bbox/concave tests + focused golden + mutation witness старого tangent-jitter |
|
||
| AC6 | Физические стены/перегородки/обе колонны имеют точный `#7f7f7f` и page-anchored hatch; openings чисты; zero wall не заштрихована | writer operators/color unit + PDF raster semantic probes/golden + mutation witness без even-odd clip |
|
||
| AC7 | Обе A4 orientation оцениваются по полному фактическому bbox; выбран максимальный стандартный масштаб, сцена целиком внутри поля и центрирована ≤0.5 mm | scene unit с portrait/landscape и реальными callouts + focused golden + mutation witness старого raw-aspect выбора |
|
||
| AC8 | Компас содержит обе канонические path, остаётся vector и верно ориентирован при 0/90/180/270°; без north отсутствует; license сохранена | parser/unit + writer PDF parse + raster probes + license check + mutation witness старой стрелки |
|
||
| AC9 | Архитектурная легенда и `pdf.legend.*` отсутствуют, дата/версия/масштаб сохраняются | extracted-text unit/smoke и i18n consistency + mutation witness возврата легенды |
|
||
| AC10 | Сохраняются один A4, стандартный scale, все четыре content-toggle, Unicode, отсутствие devices/live-state, deterministic bytes и lazy loading | актуализированные существующие unit/smoke тесты #53 |
|
||
| AC11 | Построение large-house scene укладывается в `<200 ms`; initial bundle не получает PDF-код | perf unit + bundle/lazy manifest check |
|
||
| AC12 | Пользовательская и техническая документация соответствует новому результату, а golden/docs fingerprints актуальны | `check-docs`, Linux golden verify и полный Linux docs artifact review |
|
||
|
||
## 18. Тест-план
|
||
|
||
### Unit / component
|
||
|
||
- `pdf-dimensions`: exact/near duplicate, seam duplicate, turn retention,
|
||
fixed-point compact, zero edge, 0.25° boundary, true diagonal, rectangle pair,
|
||
L-contour false pair, equal dimensions across rooms/rings/axes.
|
||
- `pdf-scene`: retained counts, midpoint, clearance, whole-lane shift, concave
|
||
normal, exact full bbox, both orientations, scale bar, wall/partition/column
|
||
material, opening holes, zero wall, compass cardinals, footer text and perf.
|
||
- `pdf-writer`: high-precision color, filled compound vector, even-odd clip,
|
||
parseability and deterministic bytes after new operators.
|
||
- отдельный compass unit: обе path разбираются в конечные координаты, имеют
|
||
квадратный bbox и детерминированно трансформируются.
|
||
|
||
### Browser smoke
|
||
|
||
- текущий полный экспорт и отказ lazy chunk;
|
||
- 320×760 и четыре локали с измерением overflow;
|
||
- stacked footer сохраняет cancel/save behavior и download;
|
||
- извлечённый текст не содержит легенду.
|
||
|
||
### Visual / golden
|
||
|
||
Обновить `pdf-export-geometry-light` и добавить focused scenario с почти
|
||
квадратным планом, near-duplicate углом, локальной парой, одинаковыми размерами
|
||
двух комнат, проёмом, zero wall, перегородкой, обеими колоннами и `north_deg`.
|
||
|
||
Semantic guard проверяет:
|
||
|
||
- ненулевой architectural ink и минимальное заполнение plan field;
|
||
- серый участок стены с контрастными hatch-полосами;
|
||
- чистую область проёма;
|
||
- vector/raster ink в области компаса;
|
||
- отсутствие содержимого за полями и приблизительное центрирование.
|
||
|
||
На Windows разрешены unit/typecheck/build/browser smoke и диагностический
|
||
`golden:verify`. Канонические golden и десять docs screenshots переснимаются в
|
||
Linux CI/закреплённом контейнере: DirectWrite не является принимаемой заменой
|
||
Linux FreeType.
|
||
|
||
Backend и HA API не меняются, поэтому backend pytest не является целевым
|
||
доказательством этой задачи.
|
||
|
||
## 19. Документация и release artifacts
|
||
|
||
Обновить:
|
||
|
||
- `docs/PDF-EXPORT.md` — размеры, hatch, layout, compass, отсутствие легенды;
|
||
- `docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md` — вид результата;
|
||
- `docs/TESTING.md` — структурные и visual PDF gates;
|
||
- `docs/specs/053-pdf-export.md` — явные ссылки на заменённый контракт #482;
|
||
- `docs/specs/README.md`, `docs/STATUS.md`, `docs/ARCHITECTURE.md` при изменении
|
||
перечисленных модулей;
|
||
- screenshot диалога PDF и оба changelog.
|
||
|
||
Коммиты реализации получают `User-Visible: yes`. Changelog EN/RU коротко
|
||
описывает исправленный экспорт и ссылается на #482. Release body по общему
|
||
процессу содержит только значимое пользовательское изменение и ссылки на оба
|
||
changelog.
|
||
|
||
## 20. Ожидаемая карта изменений
|
||
|
||
- `src/pdf/pdf-dimensions.ts` — нормализация, axis filter, кандидаты и пары;
|
||
- `src/pdf/pdf-collision.ts` — точные box/segment collision-предикаты для полос;
|
||
- `src/pdf/pdf-scene.ts` — dimension lanes, material, full-scene candidates;
|
||
- `src/pdf/pdf-writer.ts` — clipping, filled vector и точный цвет;
|
||
- `src/pdf/pdf-compass.ts` и лицензионный notice — канонический compass asset;
|
||
- `src/pdf/hp-pdf-dialog.ts` — narrow responsive contract;
|
||
- `src/i18n/{en,ru,de,fr}.json` — удаление мёртвых legend keys;
|
||
- targeted unit/smoke, golden matrix/guards/fixtures и документы §19.
|
||
|
||
Карта не является разрешением на сопутствующий refactoring. Любой новый
|
||
runtime dependency, config migration или выход за PDF/View dialog требует
|
||
возврата в `S3-spec`.
|
||
|
||
## 21. Риски
|
||
|
||
| Риск | Снижение |
|
||
|---|---|
|
||
| Локальная дедупликация удалит два разных размера с одинаковым числом | topology pair требует общий ring, ось, interval, противоположные normals и inside probe; negative matrix AC4 |
|
||
| Очистка дублей срежет настоящий короткий участок | физический epsilon 1 mm, forward-dot и сохранение каждого turn; boundary tests AC2 |
|
||
| Штриховка закроет проём либо даст швы | page-anchored pattern + even-odd clip + raster probes AC6 |
|
||
| `#7f7f7f` округлится до `#808080` | отдельный high-precision color formatter и operator/raster test |
|
||
| Более точный layout станет медленным | model parse/raster decode один раз, perf budget AC11 |
|
||
| Полный bbox всё ещё пропустит rotated text/line width | bbox строится после фактических annotations и включает stroke/text extents; adversarial tests AC7 |
|
||
| Compass нарушит лицензию или раздует runtime | две вендоренные path, MIT notice, без общего SVG runtime и в lazy chunk |
|
||
| Narrow fix сломает focus либо действия | DOM-order неизменен, browser keyboard/download smoke AC1/AC10 |
|