# ADR-068: Use sharp density 72 for pixel-perfect 1024×1024 PNG output ## Статус Accepted (2026-07-30) ## Контекст `render.mjs` рендерил SVG-шаблон cover (1024×1024 по `viewBox`) в PNG 1365×1365 вместо ожидаемых 1024×1024. Root cause: `sharp(Buffer.from(svg), { density: 96 })` — density в DPI, sharp интерпретирует `viewBox` 1024 (в user units, по умолчанию 1 unit = 1pt = 1/72in) и рендерит 1024pt × 96 DPI / 72 = 1365px. Тесты `assertPng1024` проверяли только PNG magic bytes (`0x89 0x50 0x4e 0x47`), не размеры — баг маскировался. `meta.size: 1024` в `cli.ts:78` был контрактом, но фактически нарушался. Рассматривались варианты: - density: 72 → 1024 × 72/72 = 1024px (точное совпадение). - density: 96 + resize до 1024 → лишний resample, потеря качества. - Явный `.resize(1024, 1024)` после density → double-pass, медленнее. ## Решение Установить `density: 72` в `sharp()` вызове (`render.mjs:36`). 72 DPI = canonical SVG default (1pt = 1/72in), даёт pixel-perfect 1:1 маппинг `viewBox` → px. Дополнительно: assertions в тестах декодируют PNG IHDR chunk (width bytes 16-19, height bytes 20-23, big-endian uint32) и проверяют `=== 1024` — регрессия на размер теперь детектируется. ## Альтернативы - **density 96 + explicit resize(1024)**: отвергнут — лишний resample снижает качество текста (subpixel), double-pass рендер. - **density 96 + viewBox в px вместо pt**: потребовало бы правки всех SVG-шаблонов (`cover.svg` etc.), больший scope, риск regressions в layout. - **assertions через `sharp().metadata()` вместо IHDR decode**: добавил бы зависимость от sharp в test-слой (сейчас тесты читают bytes напрямую, без dep). IHDR decode — zero-dependency, быстрее.