From 24d3cd072805a99e7e93c101c616bd0cb6b8084d Mon Sep 17 00:00:00 2001 From: Arseny <20096ninja@gmail.com> Date: Mon, 7 Sep 2026 17:24:57 +0300 Subject: [PATCH] =?UTF-8?q?[FIX]=20=D0=BF=D0=B5=D1=80=D0=B5=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D1=8C=20=D1=81=D0=BE=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9:=20=D0=B4=D0=BE=20=D1=82=D0=B8=D1=80=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8B=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B1=D0=B5=D0=BB,=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D1=82=D0=BE=D0=BA=20=D0=B2=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=B8=20=D1=80=D0=B8=D1=81=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Колонка аббревиатур больше не считается на глаз: обычная колонка l берёт ширину по самой длинной аббревиатуре, тире и пробелы вокруг него ушли в разделитель колонок. Расшифровка целиком лежит в колонке X, поэтому её перенос по-прежнему встаёт под первым словом расшифровки. Подпись рисунка не проходит через инлайн-рендер, и пометка ==текст== в ней печаталась как есть. Разбор пометок вынесен в render_marked и вызывается из обработчика рисунка. --- md2gost/handlers/image.py | 4 ++-- md2gost/handlers/inline.py | 20 +++++++++++++++----- md2gost/handlers/text.py | 29 +++++++++++------------------ tests/test_render.py | 16 ++++++++++++++-- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/md2gost/handlers/image.py b/md2gost/handlers/image.py index 44b8c16..090cff8 100644 --- a/md2gost/handlers/image.py +++ b/md2gost/handlers/image.py @@ -30,7 +30,7 @@ from urllib.parse import unquote from markdown_it.token import Token -from md2gost.handlers.inline import escape_latex +from md2gost.handlers.inline import escape_latex, render_marked from md2gost.registry import pattern DEFAULT_WIDTH = 0.8 @@ -93,7 +93,7 @@ def figure(tokens: list[Token]) -> str: if len(tokens) >= 5: m = _CAPTION_RE.match(tokens[4].content) if m: - caption = escape_latex(m.group(1)) + caption = render_marked(m.group(1), escape_latex) lines = [ "\\begin{figure}[htb]", diff --git a/md2gost/handlers/inline.py b/md2gost/handlers/inline.py index a32988d..70c9729 100644 --- a/md2gost/handlers/inline.py +++ b/md2gost/handlers/inline.py @@ -8,6 +8,7 @@ from __future__ import annotations import re import sys +from collections.abc import Callable from markdown_it.token import Token @@ -114,18 +115,27 @@ def highlight(text: str) -> str: return " ".join(highlight_word(word) for word in escaped.split(" ") if word) -@inline("text") -def _text(tok: Token) -> str: +def render_marked(text: str, plain: Callable[[str], str] = render_text) -> str: + """Разобрать пометки правок, остальное отдать ``plain``. + + Подпись рисунка не проходит через инлайн-рендер, но выделять правки + нужно и в ней, поэтому разбор вынесен из обработчика текстового узла. + """ parts: list[str] = [] end = 0 - for match in _MARK_RE.finditer(tok.content): - parts.append(render_text(tok.content[end : match.start()])) + for match in _MARK_RE.finditer(text): + parts.append(plain(text[end : match.start()])) parts.append(highlight(match.group(1))) end = match.end() - parts.append(render_text(tok.content[end:])) + parts.append(plain(text[end:])) return "".join(parts) +@inline("text") +def _text(tok: Token) -> str: + return render_marked(tok.content) + + @inline("strong_open") def _strong_open(tok: Token) -> str: return r"\textbf{" diff --git a/md2gost/handlers/text.py b/md2gost/handlers/text.py index 12110c4..932a556 100644 --- a/md2gost/handlers/text.py +++ b/md2gost/handlers/text.py @@ -33,23 +33,16 @@ def abbreviations(text: str) -> str: if not rows: return text - max_abbr = max(len(r[0]) for r in rows) - # На прописную букву в Times уходит около 0,75em; плюс отбивка перед тире. - # Прежняя оценка в 1em на символ отодвигала тире от аббревиатуры почти на - # треть строки. - abbr_col = round(max_abbr * 0.75 + 0.8, 2) - tabular_rows = [] - for abbr, desc in rows: - pad = max_abbr - len(abbr) + 2 - tabular_rows.append( - f"{abbr} {' ' * pad}& \\hangafter=1\\hangindent=2em --- {desc} \\\\" - ) + # Колонка l сама берёт ширину самой длинной аббревиатуры, поэтому тире + # выстраиваются в столбец без подсчёта ширин на глаз, а отбивка до тире + # остаётся обычным пробелом. Расшифровка целиком лежит в колонке X, и её + # перенос встаёт под первым словом расшифровки, а не под тире. + body = "\n".join(f"{abbr} & {desc} \\\\" for abbr, desc in rows) - # Отбивки колонок убраны с обеих сторон и между ними: иначе сумма ширин - # превышает \linewidth на 2\tabcolsep и перечень выходит за правое поле. - col_spec = ( - f"@{{\\hspace{{0pt}}}}p{{{abbr_col}em}}" - f"@{{}}p{{\\dimexpr\\linewidth-{abbr_col}em\\relax}}@{{\\hspace{{0pt}}}}" + # Отбивки колонок убраны с обоих краёв: иначе сумма ширин превышает + # \linewidth на 2\tabcolsep и перечень выходит за правое поле. + return ( + "\\noindent\\begin{tabularx}{\\linewidth}{@{}l@{\\ ---\\ }X@{}}\n" + f"{body}\n" + "\\end{tabularx}" ) - body = "\n".join(tabular_rows) - return f"\\noindent\\begin{{tabular}}{{{col_spec}}}\n{body}\n\\end{{tabular}}" diff --git a/tests/test_render.py b/tests/test_render.py index e63a1ed..02fc5d0 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -50,6 +50,11 @@ def test_mark_splits_compound_word_into_separate_boxes() -> None: assert "\\hlw{аппаратно}\\hlw{-}\\allowbreak \\hlw{программный}" in tex +def test_mark_in_figure_caption() -> None: + tex = render("![[images/a.png]]\n\nРисунок 1 — ==Схема== комплекса\n") + assert "\\caption{\\hlw{Схема} комплекса}" in tex + + def test_mark_in_heading_reaches_the_contents() -> None: tex = render("## ==Контейнеризация серверного приложения==\n") assert "\\subsection{\\hlw{Контейнеризация}" in tex @@ -497,13 +502,20 @@ def test_abbreviation_dash_is_single() -> None: """Шесть дефисов дают в LaTeX два длинных тире подряд вместо одного.""" tex = body(render(ABBR_MD)) assert "------" not in tex - assert "--- Software-Defined Radio" in tex + assert "SDR & Software-Defined Radio" in tex def test_abbreviation_table_has_no_extra_column_padding() -> None: """Отбивка между колонками добавляется к \\linewidth и роняет строку за поле.""" tex = body(render(ABBR_MD)) - assert "@{}p{\\dimexpr\\linewidth" in tex + assert "{@{}l@{\\ ---\\ }X@{}}" in tex + + +def test_abbreviation_column_is_as_wide_as_the_abbreviation() -> None: + """Колонка l берёт ширину по содержимому: до тире остаётся пробел.""" + tex = body(render(ABBR_MD)) + assert "p{" not in tex + assert "\\begin{tabularx}{\\linewidth}" in tex def test_cpp_uses_extended_dialect() -> None: