@@ -0,0 +1,92 @@
|
||||
from copy import copy
|
||||
from typing import Generator
|
||||
|
||||
from docx.shared import Pt, Cm, Twips
|
||||
|
||||
from . import Paragraph
|
||||
from .renderable import Renderable
|
||||
from ..layout_tracker import LayoutState
|
||||
from ..rendered_info import RenderedInfo
|
||||
|
||||
|
||||
# ТЗ табл. 2.2: маркер 1,25 см; текст / табуляция 2,25 см
|
||||
MARKER_POS = Cm(1.25)
|
||||
TEXT_POS = Cm(2.25)
|
||||
BULLET = "–" # en dash, единый маркер на весь документ
|
||||
|
||||
|
||||
class List(Renderable):
|
||||
def __init__(self, parent, ordered: bool):
|
||||
self._parent = parent
|
||||
self._ordered = ordered
|
||||
self._paragraphs: list[Paragraph] = []
|
||||
self._last_paragraph_space_after = 0
|
||||
self._numbering = [0 for _ in range(10)]
|
||||
self._item_count = 0
|
||||
|
||||
def add_item(self, level: int) -> Paragraph:
|
||||
self._numbering[level - 1] += 1
|
||||
for i in range(level, len(self._numbering)):
|
||||
self._numbering[i] = 0
|
||||
|
||||
self._item_count += 1
|
||||
paragraph = Paragraph(self._parent)
|
||||
|
||||
if self._ordered:
|
||||
marker = f"{self._numbering[level - 1]}."
|
||||
else:
|
||||
marker = BULLET
|
||||
|
||||
paragraph.add_run(marker + "\t")
|
||||
|
||||
# hanging indent: left = TEXT_POS (+ nesting), first_line = MARKER_POS - TEXT_POS
|
||||
nest = TEXT_POS * (level - 1)
|
||||
paragraph._docx_paragraph.paragraph_format.tab_stops.add_tab_stop(TEXT_POS + nest)
|
||||
paragraph._docx_paragraph.paragraph_format.left_indent = TEXT_POS + nest
|
||||
paragraph._docx_paragraph.paragraph_format.first_line_indent = MARKER_POS - TEXT_POS
|
||||
|
||||
self._last_paragraph_space_after = paragraph._docx_paragraph.paragraph_format.space_after
|
||||
paragraph._docx_paragraph.paragraph_format.space_before = 0
|
||||
paragraph._docx_paragraph.paragraph_format.space_after = 0
|
||||
|
||||
self._paragraphs.append(paragraph)
|
||||
return paragraph
|
||||
|
||||
def finalize_punctuation(self) -> None:
|
||||
"""Apply TZ list punctuation: bullet → lowercase + ';' (last '.'); numbered → Capital + '.'."""
|
||||
for i, paragraph in enumerate(self._paragraphs):
|
||||
runs = paragraph._docx_paragraph.runs
|
||||
if len(runs) < 2:
|
||||
continue
|
||||
# Text after marker run
|
||||
text_runs = runs[1:]
|
||||
full = "".join(r.text or "" for r in text_runs).strip()
|
||||
if not full:
|
||||
continue
|
||||
is_last = i == len(self._paragraphs) - 1
|
||||
if self._ordered:
|
||||
fixed = full[0].upper() + full[1:] if full else full
|
||||
if not fixed.endswith("."):
|
||||
fixed = fixed.rstrip(";,. ") + "."
|
||||
else:
|
||||
fixed = full[0].lower() + full[1:] if full else full
|
||||
if is_last:
|
||||
fixed = fixed.rstrip(";,. ") + "."
|
||||
else:
|
||||
fixed = fixed.rstrip(";,. ") + ";"
|
||||
# Put all text into first content run, clear others
|
||||
text_runs[0].text = fixed
|
||||
for r in text_runs[1:]:
|
||||
r.text = ""
|
||||
|
||||
def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState) -> Generator[
|
||||
RenderedInfo | Renderable, None, None]:
|
||||
self.finalize_punctuation()
|
||||
if self._paragraphs:
|
||||
self._paragraphs[-1]._docx_paragraph.paragraph_format.space_after = self._last_paragraph_space_after
|
||||
|
||||
for paragraph in self._paragraphs:
|
||||
for x in paragraph.render(previous_rendered, copy(layout_state)):
|
||||
layout_state.add_height(x.height)
|
||||
previous_rendered = x
|
||||
yield x
|
||||
Reference in New Issue
Block a user