"""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