#!/usr/bin/env python3 """Общая сборка XHTML для pdf2html.py и epub2html.py. Формат намеренно жёсткий: один блок — одна строка (кроме
, где переносы
значимы). На этом держится мост translate.py — он правит блоки по номеру
строки, а всё, чего не узнал, доносит до результата нетронутым.
"""
import html
import re

# Брак OCR-а внутри слова: цифра вплотную к букве («30HWod1Q»), смесь алфавитов
# в одном слове («aoHegoduenue»), заглавная посреди слова.
DIGIT_WORD = re.compile(r"[^\W\d_][\d]|[\d][^\W\d_]")
WORD = re.compile(r"\w+")
LAT, CYR = re.compile(r"[A-Za-z]"), re.compile(r"[А-Яа-яЁё]")
MIDCAP = re.compile(r"[a-zа-яё][A-ZА-ЯЁ]")

CSS = """
body { margin: 0 1em; }
h1 { text-align: center; margin: 2em 0 0.2em; page-break-before: always; }
h1.title { page-break-before: avoid; }
h2 { text-align: center; font-style: italic; font-weight: normal;
     font-size: 1.1em; margin: 0.2em 0 1.5em; }
p { text-indent: 1.2em; margin: 0; text-align: justify; }
p.note { text-indent: 0; margin: 1em 2em; font-size: 0.9em;
         font-family: sans-serif; }
p.li { text-indent: 0; margin: 0.3em 0 0.3em 1.5em; text-align: left; }
p.row { text-indent: 0; margin: 0.3em 0; text-align: left;
        font-size: 0.9em; }
p.figure { text-indent: 0; text-align: center; margin: 1em 0; }
img { max-width: 100%; }
pre { font-family: monospace; font-size: 0.75em; margin: 1em 0;
      white-space: pre-wrap; word-wrap: break-word; text-align: left;
      text-indent: 0; }
code { font-family: monospace; font-size: 0.9em; }
"""


def looks_garbled(t):
    """Заголовок ли это вообще, или подпись из схемы, распознанная по буквам.

    Основную часть брака снимает фильтр поворота в главном цикле — здесь остаётся
    то, что набрано горизонтально: подписи внутри иллюстраций и обрывки таблиц
    («LF. FF. FT. TIT», «0|91239123», «В»).

    Безусловный брак — три случая, каждый сам по себе: в заголовке нет ни одной
    буквы; заголовок короче трёх знаков; в нём есть палка (в наборе её не бывает,
    в распознанном скане она попадается постоянно).
    Дальше шесть признаков структуры, любых двух хватает. Одного мало: «C++11 и
    лямбды» даёт «не-букв больше трети», «Python3» — цифру вплотную к букве,
    и оба заголовка настоящие.
    """
    t = t.strip()
    words = WORD.findall(t)
    if not words or not any(c.isalpha() for c in t):
        return True
    if "|" in t:
        return True
    # Заголовок короче четырёх знаков — обрывок («В», «лов», «юн»). Аббревиатуры
    # целиком заглавными («API», «AI») настоящими заголовками бывают, их щадим —
    # но только если букв в них не одна и та же: «TT» это обрывок таблицы.
    if len(t) < 4 and not (t.isupper() and len(set(t)) >= 2):
        return True
    # Слова из двух букв по кругу: «TIT. ITITIT», «LF. FF. FT». Нужны минимум два
    # таких слова подряд и ни одного нормального, иначе под нож попадёт «Часть III».
    long_words = [w for w in words if len(w) >= 3]
    if len(long_words) >= 2 and all(len(set(w.lower())) <= 2 for w in long_words):
        return True
    letters = sum(c.isalpha() for c in t)
    toks = t.split()
    # Обрывок — короткое слово без цифр внутри: «LF.», «оо». Цифры исключены
    # намеренно, иначе «C++11» и «Qt 6» считались бы обрывками.
    frags = [w for w in toks if not any(c.isdigit() for c in w)
             and sum(c.isalpha() for c in w) < 3]
    signals = (
        len(frags) * 2 > len(toks),                            # обрывки слов
        letters * 3 < len(t) * 2,                              # не-букв больше трети
        bool(DIGIT_WORD.search(t)),
        any(LAT.search(w) and CYR.search(w) for w in words),   # смесь алфавитов в слове
        any(MIDCAP.search(w) for w in words),                  # заглавная посреди слова
        next((c.islower() for c in t if c.isalpha()), False),  # начинается со строчной
    )
    return sum(signals) >= 2


def document(title, parts):
    """parts: [(tag, cls, inner_html)]; tag 'figure' — псевдотег для картинки."""
    buf = ['',
           '',
           "%s" % html.escape(title),
           "" % CSS]
    for tag, cls, txt in parts:
        if tag == "figure":
            buf.append('

%s

' % txt) else: c = ' class="%s"' % cls if cls else "" buf.append("<%s%s>%s" % (tag, c, txt, tag)) buf.append("") return "\n".join(buf)