[FIX] широкие таблицы переносятся по словам и не вылезают за поле

This commit is contained in:
Arseny
2026-09-02 03:47:44 +03:00
parent f616dc0cd5
commit 4f0c2ce16d
2 changed files with 63 additions and 5 deletions
+21 -3
View File
@@ -80,18 +80,36 @@ def _collect_rows(
return pos, rows, aligns return pos, rows, aligns
_STRETCH = ">{\\raggedright\\arraybackslash}X"
def _tabular(rows: list[list[str]], aligns: list[str]) -> str: def _tabular(rows: list[list[str]], aligns: list[str]) -> str:
"""Табличное окружение для строк с заданным выравниванием колонок.
Колонка, выровненная по левому краю, считается текстовой: её содержимое
должно переноситься по словам, иначе широкая таблица уезжает за правое
поле страницы. Такие колонки верстаются растягиваемым типом ``X``, а вся
таблица — окружением ``tabularx`` шириной в текстовое поле. Колонки по
центру и по правому краю (обычно числовые) остаются узкими.
Если текстовых колонок нет, растягивать нечего и используется обычный
``tabular``: ``tabularx`` без единой ``X``-колонки не собирается.
"""
cols = max(len(row) for row in rows) cols = max(len(row) for row in rows)
for row in rows: for row in rows:
row.extend([""] * (cols - len(row))) row.extend([""] * (cols - len(row)))
spec = list(aligns[:cols]) + ["l"] * max(0, cols - len(aligns)) spec = list(aligns[:cols]) + ["l"] * max(0, cols - len(aligns))
col_spec = "|".join(spec) stretchable = "l" in spec
lines = [f"\\begin{{tabular}}{{|{col_spec}|}}", "\\hline"] 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: for row in rows:
lines.append(" & ".join(row) + " \\\\") lines.append(" & ".join(row) + " \\\\")
lines.append("\\hline") lines.append("\\hline")
lines.append("\\end{tabular}") lines.append(f"\\end{{{env}}}")
return "\n".join(lines) return "\n".join(lines)
+42 -2
View File
@@ -1,5 +1,6 @@
import pytest import pytest
from md2gost.handlers.table import _STRETCH
from md2gost.latex.titlepages import TITLEPAGES from md2gost.latex.titlepages import TITLEPAGES
from md2gost.parser import Parser from md2gost.parser import Parser
from md2gost.render import Renderer from md2gost.render import Renderer
@@ -12,6 +13,11 @@ def render(md: str) -> str:
return Renderer(parser.tokens, parser.front_matter).render() 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: def render_fm(extra: str, body: str = "Текст.\n") -> str:
head = f"---\nназвание: Тестовая работа\nтип: Отчёт\n{extra}---\n\n" head = f"---\nназвание: Тестовая работа\nтип: Отчёт\n{extra}---\n\n"
parser = Parser(head + body) parser = Parser(head + body)
@@ -123,7 +129,7 @@ def test_table_with_caption() -> None:
tex = render(TABLE_MD) tex = render(TABLE_MD)
assert "\\begin{table}[H]" in tex assert "\\begin{table}[H]" in tex
assert "\\caption{Сравнение скорости}" 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 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: def test_table_without_caption_stays_bare() -> None:
tex = render("| a | b |\n| --- | --- |\n| 1 | 2 |\n") tex = render("| a | b |\n| --- | --- |\n| 1 | 2 |\n")
assert "\\begin{table}" not in tex 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: 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") tex = render("```yaml\nkey: value\n```\n")
assert "language=" not in tex assert "language=" not in tex
assert "key: value" 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