From f426e865b5f8f28042373f4f418a539347155c02 Mon Sep 17 00:00:00 2001 From: Arseny <20096ninja@gmail.com> Date: Wed, 2 Sep 2026 12:53:35 +0300 Subject: [PATCH] =?UTF-8?q?[FIX]=20=D0=B4=D0=BB=D0=B8=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=D1=85=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D1=81=D1=8F=D1=82=D1=81=D1=8F,=20=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BD=D0=B0=D0=BB=D0=B5=D0=B7=D0=B0=D1=8E=D1=82?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D1=81=D0=BE=D1=81=D0=B5=D0=B4=D0=BD=D1=8E?= =?UTF-8?q?=D1=8E=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md2gost/handlers/table.py | 29 ++++++++++++++++++++++++++++- md2gost/latex/preamble.py | 1 + tests/test_render.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/md2gost/handlers/table.py b/md2gost/handlers/table.py index 86272e1..a46e12e 100644 --- a/md2gost/handlers/table.py +++ b/md2gost/handlers/table.py @@ -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: diff --git a/md2gost/latex/preamble.py b/md2gost/latex/preamble.py index c8c78c7..10af761 100644 --- a/md2gost/latex/preamble.py +++ b/md2gost/latex/preamble.py @@ -16,6 +16,7 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle} \usepackage{float} \usepackage{wrapfig} \sloppy +\setlength{\emergencystretch}{2em} \clubpenalty=10000 \widowpenalty=10000 \usepackage{enumitem} diff --git a/tests/test_render.py b/tests/test_render.py index 37bf014..4aa911f 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -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