Files
md_to_gost/md2gost/styles.py
T
Igor20264 b38661f588
Python application / build (push) Has been cancelled
Update 0.4.0
- Add\Rework UI
- Add Split Table and Listing
- Add Support Customazeble schems
2026-09-04 22:28:39 +03:00

337 lines
12 KiB
Python

"""Apply MIREA TZ (GOST 7.32 / методичка 2022) paragraph styles to a Document."""
from docx.document import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING, WD_TAB_ALIGNMENT, WD_TAB_LEADER
from docx.oxml.ns import qn
from docx.shared import Cm, Mm, Pt
from docx.styles.style import _ParagraphStyle as ParagraphStyle
def _set_run_font(style: ParagraphStyle, name: str, size_pt: float, bold: bool = False, italic: bool = False):
"""Lock typeface/size/color so Word theme (Calibri + accent blue) cannot leak."""
from docx.oxml import OxmlElement
from docx.shared import RGBColor
font = style.font
font.name = name
font.size = Pt(size_pt)
font.bold = bold
font.italic = italic
font.color.rgb = RGBColor(0, 0, 0)
rPr = style.element.get_or_add_rPr()
for child in list(rPr):
tag = child.tag
if tag.endswith("}rFonts") or tag.endswith("}color") or tag.endswith("}sz") or tag.endswith("}szCs"):
rPr.remove(child)
rFonts = OxmlElement("w:rFonts")
rFonts.set(qn("w:ascii"), name)
rFonts.set(qn("w:hAnsi"), name)
rFonts.set(qn("w:cs"), name)
rFonts.set(qn("w:eastAsia"), name)
rPr.insert(0, rFonts)
half_points = str(int(size_pt * 2))
sz = OxmlElement("w:sz")
sz.set(qn("w:val"), half_points)
rPr.append(sz)
sz_cs = OxmlElement("w:szCs")
sz_cs.set(qn("w:val"), half_points)
rPr.append(sz_cs)
color = OxmlElement("w:color")
color.set(qn("w:val"), "000000")
rPr.append(color)
def _ensure_style(document: Document, name: str, base: str = "Normal") -> ParagraphStyle:
try:
return document.styles[name]
except KeyError:
style = document.styles.add_style(name, WD_STYLE_TYPE.PARAGRAPH)
style.base_style = document.styles[base]
return style
def _clear_tab_stops(style: ParagraphStyle) -> None:
pPr = style.element.get_or_add_pPr()
tabs = pPr.find(qn("w:tabs"))
if tabs is not None:
pPr.remove(tabs)
def _fix_toc_tab_stops(document: Document) -> None:
"""Align TOC page-number tabs with the text area (Template.docx used 175 mm for L=25 mm)."""
section = document.sections[0]
right_tab = section.page_width - section.left_margin - section.right_margin
left_tabs = {
"toc 1": Mm(5),
"toc 2": Mm(12.5),
"toc 3": Mm(20),
}
for style in document.styles:
if style.type != WD_STYLE_TYPE.PARAGRAPH:
continue
name = style.name.lower()
if not name.startswith("toc ") or name == "toc heading":
continue
_clear_tab_stops(style)
pf = style.paragraph_format
left = left_tabs.get(name)
if left is not None:
pf.tab_stops.add_tab_stop(left, WD_TAB_ALIGNMENT.LEFT)
pf.tab_stops.add_tab_stop(
right_tab, WD_TAB_ALIGNMENT.RIGHT, WD_TAB_LEADER.DOTS
)
def apply_mirea_styles(document: Document) -> None:
"""Mutate section margins and key paragraph styles to match the MIREA method guide."""
_apply_common_page_and_body(document)
# --- Headings (табл. 2.1): слева с отступом 1,25 см ---
heading_specs = [
(1, 18, Mm(0), Mm(10), True),
(2, 16, Mm(15), Mm(10), False),
(3, 14, Mm(15), Mm(10), False),
]
for level, size, before, after, page_break in heading_specs:
style: ParagraphStyle = document.styles[f"Heading {level}"]
_set_run_font(style, "Times New Roman", size, bold=True)
if level == 1:
style.font.all_caps = True
else:
style.font.all_caps = False
hpf = style.paragraph_format
hpf.alignment = WD_ALIGN_PARAGRAPH.LEFT
hpf.first_line_indent = Cm(0)
hpf.left_indent = Cm(1.25)
hpf.right_indent = Cm(0)
hpf.space_before = before
hpf.space_after = after
hpf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
hpf.page_break_before = page_break
hpf.keep_with_next = True
hpf.widow_control = True
_apply_common_captions_and_misc(document)
def apply_pis_custom_styles(document: Document) -> None:
"""Styles for PIS_custom: итоговый отчёт по практическим работам.
* H1 (разделы / практические работы): по центру, ПРОПИСНЫЕ, без точки.
* H2 (подразделы): с абзацного отступа 1,25 см, с прописной буквы.
* Поля / шрифт / интервал / красная строка — как в чек-листе ПИС (= ГОСТ поля).
"""
_apply_common_page_and_body(document)
# H1 — раздел: центр, caps, с новой страницы
h1: ParagraphStyle = document.styles["Heading 1"]
_set_run_font(h1, "Times New Roman", 14, bold=True)
h1.font.all_caps = True
h1pf = h1.paragraph_format
h1pf.alignment = WD_ALIGN_PARAGRAPH.CENTER
h1pf.first_line_indent = Cm(0)
h1pf.left_indent = Cm(0)
h1pf.right_indent = Cm(0)
h1pf.space_before = Mm(0)
h1pf.space_after = Mm(10)
h1pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
h1pf.page_break_before = True
h1pf.keep_with_next = True
h1pf.widow_control = True
# H2 / H3 — подразделы: абзацный отступ, не caps
for level, size, before in ((2, 14, Mm(15)), (3, 14, Mm(10))):
style = document.styles[f"Heading {level}"]
_set_run_font(style, "Times New Roman", size, bold=True)
style.font.all_caps = False
hpf = style.paragraph_format
hpf.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
hpf.first_line_indent = Cm(1.25)
hpf.left_indent = Cm(0)
hpf.right_indent = Cm(0)
hpf.space_before = before
hpf.space_after = Mm(10)
hpf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
hpf.page_break_before = False
hpf.keep_with_next = True
hpf.widow_control = True
_apply_common_captions_and_misc(document)
def apply_document_styles(document: Document, style_preset: str = "mirea") -> None:
if style_preset == "pis_custom":
apply_pis_custom_styles(document)
else:
apply_mirea_styles(document)
def _apply_common_page_and_body(document: Document) -> None:
from .page_geometry import apply_section_geometry, is_landscape_section
for section in document.sections:
# Do not wipe landscape sections created for +landscape figures/tables.
apply_section_geometry(section, landscape=is_landscape_section(section))
_fix_toc_tab_stops(document)
normal: ParagraphStyle = document.styles["Normal"]
_set_run_font(normal, "Times New Roman", 14)
pf = normal.paragraph_format
pf.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
pf.first_line_indent = Cm(1.25)
pf.left_indent = Cm(0)
pf.right_indent = Cm(0)
pf.space_before = Pt(0)
pf.space_after = Pt(0)
pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
pf.widow_control = True
def _apply_common_captions_and_misc(document: Document) -> None:
# --- Caption Figure: 12pt bold, center, under figure ---
caption_fig = _ensure_style(document, "Caption Figure", "Caption")
_set_run_font(caption_fig, "Times New Roman", 12, bold=True)
cpf = caption_fig.paragraph_format
cpf.alignment = WD_ALIGN_PARAGRAPH.CENTER
cpf.first_line_indent = Cm(0)
cpf.left_indent = Cm(0)
cpf.space_before = Mm(0)
cpf.space_after = Mm(6)
cpf.line_spacing_rule = WD_LINE_SPACING.SINGLE
cpf.widow_control = True
# --- Caption Table (legacy EN name) + «Название таблицы» (основной) ---
caption_tbl = _ensure_style(document, "Caption Table", "Caption")
_set_run_font(caption_tbl, "Times New Roman", 12, italic=True)
tpf = caption_tbl.paragraph_format
tpf.alignment = WD_ALIGN_PARAGRAPH.LEFT
tpf.first_line_indent = Cm(0)
tpf.left_indent = Cm(0)
tpf.space_before = Mm(6)
tpf.space_after = Mm(0)
tpf.line_spacing_rule = WD_LINE_SPACING.SINGLE
tpf.keep_with_next = True
tpf.widow_control = True
# ГОСТ-имя стиля подписи таблицы (те же параметры, что Caption Table)
caption_tbl_ru = _ensure_style(document, "Название таблицы", "Caption Table")
_set_run_font(caption_tbl_ru, "Times New Roman", 12, italic=True)
tpf_ru = caption_tbl_ru.paragraph_format
tpf_ru.alignment = WD_ALIGN_PARAGRAPH.LEFT
tpf_ru.first_line_indent = Cm(0)
tpf_ru.left_indent = Cm(0)
tpf_ru.space_before = Mm(6)
tpf_ru.space_after = Mm(0)
tpf_ru.line_spacing_rule = WD_LINE_SPACING.SINGLE
tpf_ru.keep_with_next = True
tpf_ru.widow_control = True
# --- Caption Listing (как таблицы) ---
caption_lst = _ensure_style(document, "Caption Listing", "Caption")
_set_run_font(caption_lst, "Times New Roman", 12, italic=True)
lpf = caption_lst.paragraph_format
lpf.alignment = WD_ALIGN_PARAGRAPH.LEFT
lpf.first_line_indent = Cm(0)
lpf.left_indent = Cm(0)
lpf.space_before = Mm(6)
lpf.space_after = Mm(0)
lpf.line_spacing_rule = WD_LINE_SPACING.SINGLE
lpf.keep_with_next = True
lpf.widow_control = True
caption = document.styles["Caption"]
_set_run_font(caption, "Times New Roman", 12, bold=True)
capf = caption.paragraph_format
capf.alignment = WD_ALIGN_PARAGRAPH.CENTER
capf.first_line_indent = Cm(0)
capf.space_before = Mm(0)
capf.space_after = Mm(6)
capf.line_spacing_rule = WD_LINE_SPACING.SINGLE
code = _ensure_style(document, "Code", "Normal")
_set_run_font(code, "Courier New", 10)
cdpf = code.paragraph_format
cdpf.alignment = WD_ALIGN_PARAGRAPH.LEFT
cdpf.first_line_indent = Cm(0)
cdpf.left_indent = Cm(0)
cdpf.space_before = Mm(0)
cdpf.space_after = Mm(0)
cdpf.line_spacing_rule = WD_LINE_SPACING.SINGLE
table_text = _ensure_style(document, "Table Text", "Normal")
_set_run_font(table_text, "Times New Roman", 12)
ttf = table_text.paragraph_format
ttf.alignment = WD_ALIGN_PARAGRAPH.LEFT
ttf.first_line_indent = Cm(0)
ttf.left_indent = Cm(0)
ttf.space_before = Mm(0)
ttf.space_after = Mm(0)
ttf.line_spacing_rule = WD_LINE_SPACING.SINGLE
biblio = _ensure_style(document, "Bibliography", "Normal")
_set_run_font(biblio, "Times New Roman", 14)
bpf = biblio.paragraph_format
bpf.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
bpf.first_line_indent = Cm(1.25)
bpf.space_before = Pt(0)
bpf.space_after = Pt(0)
bpf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
biblio_h = _ensure_style(document, "Bibliography Heading", "Normal")
_set_run_font(biblio_h, "Times New Roman", 14)
biblio_h.font.all_caps = True
bhpf = biblio_h.paragraph_format
bhpf.alignment = WD_ALIGN_PARAGRAPH.CENTER
bhpf.first_line_indent = Cm(0)
bhpf.left_indent = Cm(1.25) # табл. 5.1
bhpf.space_before = Mm(6)
bhpf.space_after = Mm(6)
bhpf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
bhpf.keep_with_next = True
# TOC styles: TNR 14, 1.5, no bold, no first-line indent; toc 1 = ALL CAPS
for toc_name, all_caps in (("toc 1", True), ("toc 2", False), ("toc 3", False)):
try:
toc_style = document.styles[toc_name]
except KeyError:
toc_style = _ensure_style(document, toc_name, "Normal")
_set_run_font(toc_style, "Times New Roman", 14, bold=False)
toc_style.font.all_caps = all_caps
toc_style.font.bold = False
tpf_toc = toc_style.paragraph_format
tpf_toc.alignment = WD_ALIGN_PARAGRAPH.LEFT
tpf_toc.first_line_indent = Cm(0)
tpf_toc.space_before = Pt(0)
tpf_toc.space_after = Pt(0)
tpf_toc.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
try:
footer_style = document.styles["Footer"]
_set_run_font(footer_style, "Times New Roman", 12)
footer_style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
footer_style.paragraph_format.first_line_indent = Cm(0)
except KeyError:
pass
for hyper_name in ("Hyperlink", "FollowedHyperlink"):
try:
hyper = document.styles[hyper_name]
except KeyError:
continue
_set_run_font(hyper, "Times New Roman", 14)
hyper.font.underline = False
after = _ensure_style(document, "Space After Table", "Normal")
apf = after.paragraph_format
apf.space_before = Mm(6)
apf.space_after = Pt(0)
apf.first_line_indent = Cm(1.25)
apf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE