diff --git a/resources/themes/gost.yaml b/resources/themes/gost.yaml index ce5adae..f6f9a9f 100644 --- a/resources/themes/gost.yaml +++ b/resources/themes/gost.yaml @@ -67,4 +67,4 @@ styles: line_spacing: 1.0 colors: - LinkColor: "0000FF" + LinkColor: "000" \ No newline at end of file diff --git a/src/core/converter.py b/src/core/converter.py index b9ac7ed..0c39dd1 100644 --- a/src/core/converter.py +++ b/src/core/converter.py @@ -14,6 +14,13 @@ def process_document( template_path: Optional[Path] = None, theme_name: Optional[str] = None, plugins_dir: Optional[Path] = None, dry_run: bool = False ): + if theme_name: + theme_path = Path("resources/themes") / f"{theme_name}.yaml" + if theme_path.exists(): + style_config_path = theme_path + else: + logger.warning(f"Тема {theme_name} не найдена, используем стандартный стиль") + if plugins_dir and plugins_dir.exists(): load_plugins_from_path(plugins_dir) @@ -42,6 +49,7 @@ def process_document( style_reg = StyleRegistry(style_config_path) style_reg.ensure_styles_in_doc(doc) + style_reg.apply_page_layout(doc) # ✅ применяем поля render_markdown_to_docx(doc, md_content, images_dir, style_reg) output_path = output_dir / f"{doc_name}.docx" diff --git a/src/core/style_registry.py b/src/core/style_registry.py index fd6d440..c0365f9 100644 --- a/src/core/style_registry.py +++ b/src/core/style_registry.py @@ -1,9 +1,8 @@ import yaml from docx import Document from docx.enum.style import WD_STYLE_TYPE -from docx.shared import Pt, RGBColor +from docx.shared import Pt, Cm, RGBColor from docx.enum.text import WD_LINE_SPACING, WD_ALIGN_PARAGRAPH -from docx.styles.style import ParagraphStyle class StyleRegistry: def __init__(self, config_path): @@ -20,6 +19,24 @@ class StyleRegistry: style = doc.styles.add_style(name, WD_STYLE_TYPE.PARAGRAPH) self.apply_style(style, name) + def apply_page_layout(self, doc: Document): + """Применяет настройки страницы (поля).""" + page_cfg = self.config.get("page", {}) + margins = page_cfg.get("margins", {}) + if not margins: + return + + sections = doc.sections + for section in sections: + if "top" in margins: + section.top_margin = Cm(margins["top"]) + if "bottom" in margins: + section.bottom_margin = Cm(margins["bottom"]) + if "left" in margins: + section.left_margin = Cm(margins["left"]) + if "right" in margins: + section.right_margin = Cm(margins["right"]) + def apply_style(self, style, style_name: str): cfg = self.config["styles"].get(style_name) if not cfg: @@ -50,6 +67,9 @@ class StyleRegistry: align = alignment_map.get(para_cfg["alignment"]) if align: pf.alignment = align + if "first_line_indent" in para_cfg: + from docx.shared import Mm + pf.first_line_indent = Mm(para_cfg["first_line_indent"]) def get_color(self, key: str): hex_color = self.config.get("colors", {}).get(key, "000000") diff --git a/tests/test_page_layout.py b/tests/test_page_layout.py new file mode 100644 index 0000000..65cca12 --- /dev/null +++ b/tests/test_page_layout.py @@ -0,0 +1,145 @@ +import pytest +from pathlib import Path +from docx import Document +from src.core.style_registry import StyleRegistry + +def test_apply_page_layout_all_margins(): + """Тест: все поля заданы.""" + yaml_content = """ +page: + margins: + top: 2.0 + bottom: 2.0 + left: 3.0 + right: 1.0 +styles: + Normal: + font: + name: "Arial" + size: 12 +""" + yaml_path = Path("tests/fixtures/test_layout_all.yaml") + yaml_path.parent.mkdir(exist_ok=True) + yaml_path.write_text(yaml_content, encoding="utf-8") + + doc = Document() + style_reg = StyleRegistry(yaml_path) + style_reg.apply_page_layout(doc) + + section = doc.sections[0] + assert abs(section.top_margin.cm - 2.0) < 0.01 + assert abs(section.bottom_margin.cm - 2.0) < 0.01 + assert abs(section.left_margin.cm - 3.0) < 0.01 + assert abs(section.right_margin.cm - 1.0) < 0.01 + + yaml_path.unlink(missing_ok=True) + +def test_apply_page_layout_partial_margins(): + """Тест: заданы не все поля.""" + yaml_content = """ +page: + margins: + top: 1.5 + left: 2.5 +styles: + Normal: + font: + name: "Arial" + size: 12 +""" + yaml_path = Path("tests/fixtures/test_layout_partial.yaml") + yaml_path.parent.mkdir(exist_ok=True) + yaml_path.write_text(yaml_content, encoding="utf-8") + + doc = Document() + style_reg = StyleRegistry(yaml_path) + style_reg.apply_page_layout(doc) + + section = doc.sections[0] + assert abs(section.top_margin.cm - 1.5) < 0.01 + assert abs(section.left_margin.cm - 2.5) < 0.01 + # Остальные поля не должны измениться (остаются по умолчанию) + # У docx по умолчанию: top=2.54, bottom=2.54, left=3.17, right=3.17 + # Но мы их не трогаем, если в YAML нет + + yaml_path.unlink(missing_ok=True) + +def test_apply_page_layout_no_page_section(): + """Тест: в YAML нет секции page.""" + yaml_content = """ +styles: + Normal: + font: + name: "Arial" + size: 12 +""" + yaml_path = Path("tests/fixtures/test_layout_no_page.yaml") + yaml_path.parent.mkdir(exist_ok=True) + yaml_path.write_text(yaml_content, encoding="utf-8") + + doc = Document() + style_reg = StyleRegistry(yaml_path) + style_reg.apply_page_layout(doc) + + section = doc.sections[0] + # Поля не должны измениться (остаются по умолчанию) + # top=2.54 cm по умолчанию + assert abs(section.top_margin.cm - 2.54) < 0.01 + + yaml_path.unlink(missing_ok=True) + +def test_apply_page_layout_no_margins(): + """Тест: в YAML есть page, но нет margins.""" + yaml_content = """ +page: + other_option: value +styles: + Normal: + font: + name: "Arial" + size: 12 +""" + yaml_path = Path("tests/fixtures/test_layout_no_margins.yaml") + yaml_path.parent.mkdir(exist_ok=True) + yaml_path.write_text(yaml_content, encoding="utf-8") + + doc = Document() + style_reg = StyleRegistry(yaml_path) + style_reg.apply_page_layout(doc) + + section = doc.sections[0] + # Поля не должны измениться + assert abs(section.top_margin.cm - 2.54) < 0.01 + + yaml_path.unlink(missing_ok=True) + +def test_apply_page_layout_zero_margins(): + """Тест: нулевые поля.""" + yaml_content = """ +page: + margins: + top: 0.0 + bottom: 0.0 + left: 0.0 + right: 0.0 +styles: + Normal: + font: + name: "Arial" + size: 12 +""" + yaml_path = Path("tests/fixtures/test_layout_zero.yaml") + yaml_path.parent.mkdir(exist_ok=True) + yaml_path.write_text(yaml_content, encoding="utf-8") + + doc = Document() + style_reg = StyleRegistry(yaml_path) + style_reg.apply_page_layout(doc) + + section = doc.sections[0] + assert abs(section.top_margin.cm - 0.0) < 0.01 + assert abs(section.bottom_margin.cm - 0.0) < 0.01 + assert abs(section.left_margin.cm - 0.0) < 0.01 + assert abs(section.right_margin.cm - 0.0) < 0.01 + + yaml_path.unlink(missing_ok=True) \ No newline at end of file