# reportlab / platypus — грабли вёрстки (добыто кровью)

Справочник к скиллу pdf-visual-qa. Всё проверено на реальных документах (КП, AI-отчёты).

## Кириллица
- Встроенные шрифты reportlab кириллицу НЕ умеют. Регистрируй TTF:
  ```python
  pdfmetrics.registerFont(TTFont("AR", "/System/Library/Fonts/Supplemental/Arial.ttf"))
  pdfmetrics.registerFont(TTFont("ARB", "/System/Library/Fonts/Supplemental/Arial Bold.ttf"))
  pdfmetrics.registerFontFamily("AR", normal="AR", bold="ARB", italic="AR", boldItalic="ARB")
  ```
  registerFontFamily обязателен, иначе `<b>` внутри Paragraph не работает.

## Отсутствующие глифы (главная причина «чёрных квадратов»)
- **₽ (U+20BD) в Arial = чёрный квадрат.** Писать `руб.`. (sed 's/₽/руб./g' по тексту.)
- **Emoji не рендерятся вообще** (нет в TTF). Замены:
  - цветной кружок: `<font color='#2E9E5B'>●</font>` для светофора/маркеров;
  - «галочка/крест» статуса: цветная ячейка Table или `✓`/`⚠` (есть в Arial), но эмодзи-версии (✅🚫) — нет.
- Юникодные нижние/верхние индексы (₀₁², ⁰¹²) тоже квадраты → используй `<sub>`/`<super>` в Paragraph.

## Переносы текста
- В `Table` простая строка НЕ переносится и вылезает за ячейку. Длинный текст в ячейке
  ОБЯЗАТЕЛЬНО оборачивай в `Paragraph(text, style)`. Заголовки-шапки можно оставлять
  строками (чтобы TableStyle FONT/TEXTCOLOR на них применялся), но если шапка длинная —
  тоже Paragraph или укороти текст.

## Разрывы страниц (то, что пользователь зовёт «разрывы»)
- **Осиротевший заголовок** (заголовок внизу, текст на след. странице): добавь
  `keepWithNext=1` в ParagraphStyle заголовков (H2/H3). Держит заголовок со следующим
  flowable.
- **Порванный блок** (карточку/таблицу/мокап разрезало): оберни в
  `KeepTogether([flow1, flow2, ...])`.
- **ВНИМАНИЕ:** `KeepTogether` блока, который крупнее свободного места, перекидывает его
  целиком на новую страницу → большой провал на текущей. Если блок ~целая страница —
  убедись, что он реально влезает; иначе разбей.
- **Пустая / почти пустая последняя страница:** хвост (дисклеймер+подпись) свесился один.
  Лечится сокращением контента ВЫШЕ (ужать списки, отступы, объединить пункты), чтобы
  хвост подтянулся на предыдущую страницу. Спейсеры-флоу внутри Table добавляют padding
  с обеих сторон — дорогие, лучше управлять отступами через `spaceAfter` стиля.
- **Лишняя пустая страница в середине:** обычно ручной `PageBreak()` там, где контент и
  так почти заполнил страницу. Убери принудительные PageBreak, пусть течёт само.

## Зазоры внутри блоков
- Высокий элемент в строке Table (напр. бейдж 18 мм) растягивает всю строку; короткий
  текст рядом с `VALIGN='MIDDLE'` повисает с зазорами. Лечение: `VALIGN='TOP'` + уменьшить
  высоту высокого элемента (rowHeights) до высоты текста.
- Каждый flowable-блок как отдельная строка Table получает TOP+BOTTOM padding с обеих
  сторон — между блоками копится 2×padding. Уменьшай `TOPPADDING/BOTTOMPADDING` или не
  плоди строки-спейсеры.

## Полезное оформление
- Скруглённые углы: `("ROUNDEDCORNERS",[r,r,r,r])` в TableStyle (работает).
- Чередование фона строк: `("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white, LIGHT])`.
- Цветная плашка-шапка таблицы: `("BACKGROUND",(0,0),(-1,0),BRAND)` + `("TEXTCOLOR",...,white)`.
- Footer/номера страниц: `BaseDocTemplate` + `PageTemplate(onPage=footer)`; в footer
  пропусти первую страницу (`if doc.page == 1: return`), если стр.1 — обложка.

## Рабочий цикл
1. собрать PDF → 2. `render_pdf.sh` → 3. Read каждую страницу → 4. править → 5. пересобрать
и СНОВА посмотреть. Итерировать до чистоты. Структурой кода вёрстку не проверяют.
