[FIX] длинные токены в таблицах переносятся, а не налезают на соседнюю колонку

This commit is contained in:
Arseny
2026-09-02 12:53:35 +03:00
parent 6b1f508825
commit f426e865b5
3 changed files with 59 additions and 1 deletions
+28 -1
View File
@@ -33,6 +33,13 @@ from md2gost.registry import block, pattern
_CAPTION_RE = re.compile(r"^\s*Таблица\s+\d+\s*[—–-]+\s*(.+?)\s*$")
# Длина слова, начиная с которой ему нужны точки разрыва. Идентификаторы вроде
# HackRFTestingServer:идентификатор не содержат пробелов и дефисов, поэтому в
# узкой колонке LaTeX не может их перенести: строка уезжает за границу колонки
# и печатается поверх соседней.
_LONG_WORD = 18
_BREAK_AFTER = ":/_.-"
_ALIGN_BY_STYLE = {
"text-align:left": "l",
"text-align:center": "c",
@@ -40,6 +47,26 @@ _ALIGN_BY_STYLE = {
}
def _breakable(cell: str) -> str:
"""Расставить точки переноса внутри длинных слов ячейки.
Ячейка уже отрендерена в LaTeX, поэтому слова с обратной косой чертой или
фигурными скобками пропускаются: вставка внутрь имени команды сломала бы её.
"""
words = []
for word in cell.split(" "):
if len(word) < _LONG_WORD or any(ch in word for ch in "\\{}"):
words.append(word)
continue
out = []
for ch in word:
out.append(ch)
if ch in _BREAK_AFTER:
out.append("\\allowbreak ")
words.append("".join(out))
return " ".join(words)
def _cell_align(tok: Token) -> str:
style = str(tok.attrGet("style") or "").replace(" ", "")
return _ALIGN_BY_STYLE.get(style, "l")
@@ -69,7 +96,7 @@ def _collect_rows(
pos += 1
break
if ctok.type in ("th_open", "td_open"):
row.append(render_inline(tokens[pos + 1]))
row.append(_breakable(render_inline(tokens[pos + 1])))
row_aligns.append(_cell_align(ctok))
pos += 3
else:
+1
View File
@@ -16,6 +16,7 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle}
\usepackage{float}
\usepackage{wrapfig}
\sloppy
\setlength{\emergencystretch}{2em}
\clubpenalty=10000
\widowpenalty=10000
\usepackage{enumitem}
+30
View File
@@ -410,3 +410,33 @@ def test_table_without_left_columns_stays_plain_tabular() -> None:
tex = body(render("| a | b |\n| :---: | :---: |\n| 1 | 2 |\n"))
assert "\\begin{tabular}{|c|c|}" in tex
assert "\\begin{tabularx}" not in tex
LONG_TOKEN_TABLE_MD = (
"| Направление | Формат | Назначение |\n"
"| :--- | :--- | :--- |\n"
"| сервер | HackRFTestingServer:идентификатор | Приветствие, подтверждает |\n"
)
def test_long_token_in_cell_gets_break_points() -> None:
"""Иначе токен не переносится и печатается поверх соседней колонки."""
tex = body(render(LONG_TOKEN_TABLE_MD))
assert "HackRFTestingServer:\\allowbreak" in tex
def test_short_cells_are_left_alone() -> None:
tex = body(render("| a | b |\n| :--- | :--- |\n| ping | сервер |\n"))
assert "\\allowbreak" not in tex
def test_break_points_do_not_touch_latex_commands() -> None:
"""В ячейке с разметкой есть команды; ломать их вставками нельзя."""
tex = body(
render(
"| Поле | Значение |\n| :--- | :--- |\n"
"| **оченьдлинноежирноеслово_подчёркивание** | x |\n"
)
)
assert "\\textbf{" in tex
assert "\\textbf\\allowbreak" not in tex