diff --git a/md2gost/handlers/table.py b/md2gost/handlers/table.py index b519cf3..86272e1 100644 --- a/md2gost/handlers/table.py +++ b/md2gost/handlers/table.py @@ -80,18 +80,36 @@ def _collect_rows( return pos, rows, aligns +_STRETCH = ">{\\raggedright\\arraybackslash}X" + + def _tabular(rows: list[list[str]], aligns: list[str]) -> str: + """Табличное окружение для строк с заданным выравниванием колонок. + + Колонка, выровненная по левому краю, считается текстовой: её содержимое + должно переноситься по словам, иначе широкая таблица уезжает за правое + поле страницы. Такие колонки верстаются растягиваемым типом ``X``, а вся + таблица — окружением ``tabularx`` шириной в текстовое поле. Колонки по + центру и по правому краю (обычно числовые) остаются узкими. + + Если текстовых колонок нет, растягивать нечего и используется обычный + ``tabular``: ``tabularx`` без единой ``X``-колонки не собирается. + """ cols = max(len(row) for row in rows) for row in rows: row.extend([""] * (cols - len(row))) spec = list(aligns[:cols]) + ["l"] * max(0, cols - len(aligns)) - col_spec = "|".join(spec) - lines = [f"\\begin{{tabular}}{{|{col_spec}|}}", "\\hline"] + stretchable = "l" in spec + col_spec = "|".join(_STRETCH if col == "l" else col for col in spec) + + env = "tabularx" if stretchable else "tabular" + width = "{\\textwidth}" if stretchable else "" + lines = [f"\\begin{{{env}}}{width}{{|{col_spec}|}}", "\\hline"] for row in rows: lines.append(" & ".join(row) + " \\\\") lines.append("\\hline") - lines.append("\\end{tabular}") + lines.append(f"\\end{{{env}}}") return "\n".join(lines) diff --git a/tests/test_render.py b/tests/test_render.py index 16587b7..62cf41e 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -1,5 +1,6 @@ import pytest +from md2gost.handlers.table import _STRETCH from md2gost.latex.titlepages import TITLEPAGES from md2gost.parser import Parser from md2gost.render import Renderer @@ -12,6 +13,11 @@ def render(md: str) -> str: return Renderer(parser.tokens, parser.front_matter).render() +def body(tex: str) -> str: + """Тело документа без преамбулы: в преамбуле свои tabularx-макросы.""" + return tex.split("\\setcounter{page}{2}", 1)[1] + + def render_fm(extra: str, body: str = "Текст.\n") -> str: head = f"---\nназвание: Тестовая работа\nтип: Отчёт\n{extra}---\n\n" parser = Parser(head + body) @@ -123,7 +129,7 @@ def test_table_with_caption() -> None: tex = render(TABLE_MD) assert "\\begin{table}[H]" in tex assert "\\caption{Сравнение скорости}" in tex - assert "\\begin{tabular}{|l|c|r|}" in tex + assert "\\begin{tabularx}{\\textwidth}" in tex assert "\\textbf{1} & 0.027852 & 0.000285 \\\\" in tex @@ -136,7 +142,7 @@ def test_table_caption_number_is_discarded() -> None: def test_table_without_caption_stays_bare() -> None: tex = render("| a | b |\n| --- | --- |\n| 1 | 2 |\n") assert "\\begin{table}" not in tex - assert "\\begin{tabular}{|l|l|}" in tex + assert "\\begin{tabularx}{\\textwidth}" in tex def test_table_caption_paragraph_is_not_duplicated() -> None: @@ -365,3 +371,37 @@ def test_listing_unsupported_language_falls_back_to_plain() -> None: tex = render("```yaml\nkey: value\n```\n") assert "language=" not in tex assert "key: value" in tex + + +WIDE_TABLE_MD = ( + "Таблица 1 —— Сценарии интеграции\n\n" + "| Сценарий | Событие | Что делает |\n" + "| :--- | :--- | :--- |\n" + "| build.yml | отправка изменений | Собирает образ Docker целиком |\n" +) + + +def test_wide_table_uses_tabularx_so_it_fits_the_page() -> None: + """Колонки с текстом должны переноситься, а не вылезать за поле.""" + tex = render(WIDE_TABLE_MD) + assert "\\begin{tabularx}{\\textwidth}" in tex + assert "\\begin{tabular}{" not in tex + + +def test_table_left_columns_become_stretchable() -> None: + tex = render(WIDE_TABLE_MD) + assert tex.count(">{\\raggedright\\arraybackslash}X") == 3 + + +def test_table_keeps_center_columns_narrow() -> None: + """Числовые колонки не должны растягиваться — растягивается только текст.""" + tex = render(TABLE_MD) + assert "\\begin{tabularx}{\\textwidth}" in tex + assert tex.count(">{\\raggedright\\arraybackslash}X") == 1 + assert f"{{|{_STRETCH}|c|r|}}" in tex + + +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