from copy import copy from dataclasses import dataclass from typing import Generator from docx.shared import Parented from docx.text.paragraph import Paragraph as DocxParagraph from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from md2gost.layout_tracker import LayoutState from md2gost.renderable import Renderable from md2gost.rendered_info import RenderedInfo from .paragraph_sizer import ParagraphSizer from ..util import create_element # Map category → Word style name CAPTION_STYLES = { "Рисунок": "Caption Figure", "Таблица": "Название таблицы", "Листинг": "Caption Listing", } @dataclass class CaptionInfo: unique_name: str | None text: str | None with_listing: bool = False landscape: bool = False class Caption(Renderable): def __init__(self, parent: Parented, category: str, caption_info: CaptionInfo | None, number: str | None = None, before: bool = True): self._parent = parent self._before = before self._category = category self._docx_paragraph = DocxParagraph(create_element("w:p"), parent) style_name = CAPTION_STYLES.get(category, "Caption") try: self._docx_paragraph.style = style_name except KeyError: try: self._docx_paragraph.style = "Caption Table" if category == "Таблица" else "Caption" except KeyError: self._docx_paragraph.style = "Caption" # Format: «Рисунок 1.1 — Название» (or « - » if --emdash-to-hyphen) from ..profiles import dash_separator self._docx_paragraph.add_run(f"{category} ") self._numbering_run = self._docx_paragraph.add_run(str(number) if number else "?") if caption_info and caption_info.text: text = caption_info.text.strip().rstrip(".") if text: self._docx_paragraph.add_run(f"{dash_separator()}{text}") def center(self): self._docx_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState) -> Generator[ "RenderedInfo | Renderable", None, None]: height_data = ParagraphSizer( self._docx_paragraph, previous_rendered.docx_element if previous_rendered and isinstance(previous_rendered.docx_element, DocxParagraph) else None, layout_state.max_width ).calculate_height() # Table/listing captions use keep_with_next. pageBreakBefore + keepWithNext # on a paragraph that Word already wraps to a new page produces a blank # page that the layout tracker / debug overlay never sees. leftover = 0 need_new_page = ( self._before and layout_state.current_page_height > 0 and ((height_data.lines + 2 - 1) * height_data.line_spacing + 1) * height_data.line_height > layout_state.remaining_page_height ) if need_new_page: leftover = layout_state.remaining_page_height height_data = ParagraphSizer( self._docx_paragraph, None, layout_state.max_width ).calculate_height() self._docx_paragraph.paragraph_format.page_break_before = False yield RenderedInfo(self._docx_paragraph, height_data.full + leftover)