v0.6.0 BigUpdate
Example document / build (push) Has been cancelled
Python application / build (push) Has been cancelled

This commit is contained in:
Igor20264
2026-10-20 11:38:06 +03:00
parent daa84f8bce
commit 8223e0ac14
77 changed files with 12158 additions and 117 deletions
+10 -3
View File
@@ -142,9 +142,13 @@ class Image(Renderable, RequiresNumbering):
need = Length(int(height) + int(caption_reserve))
remaining = layout_state.remaining_page_height
if remaining < need:
if self.landscape:
# Already on a fresh landscape section — shrink instead of soft page-break
# (soft break + section break = empty landscape page before the figure).
at_top_of_landscape = (
self.landscape and layout_state.current_page_height == 0
)
if at_top_of_landscape:
# Too tall for a whole landscape page. Shrink here: a soft break
# on this fresh section used to stack with the section break and
# leave an empty page before the figure.
avail = Length(max(0, int(remaining) - int(caption_reserve)))
if avail > 0 and self._image.height > avail:
ratio = int(avail) / int(self._image.height)
@@ -152,6 +156,9 @@ class Image(Renderable, RequiresNumbering):
self._image.width = Length(int(self._image.width * ratio))
height = self._image.height
else:
# Does not fit the leftover of the current page (including a
# second landscape figure after the first). Keep the fitted size
# and let Word move it; do not squash it into the gap.
height = Length(int(height) + int(remaining))
yield (rendered_image := RenderedInfo(self._docx_paragraph, Length(height)))
+52 -9
View File
@@ -52,32 +52,75 @@ class List(Renderable):
self._paragraphs.append(paragraph)
return paragraph
@staticmethod
def _is_hyphen_run(run) -> bool:
"""True for a run that carries only w:noBreakHyphen (text is empty)."""
if run.text:
return False
return bool(run._element.xpath("./w:noBreakHyphen"))
@classmethod
def _logical_text(cls, runs) -> str:
parts = []
for run in runs:
if cls._is_hyphen_run(run):
parts.append("-")
else:
parts.append(run.text or "")
return "".join(parts)
def finalize_punctuation(self) -> None:
"""Apply TZ list punctuation: bullet → lowercase + ';' (last '.'); numbered → Capital + '.'."""
"""Apply TZ list punctuation: bullet → lowercase + ';' (last '.'); numbered → Capital + '.'.
Case and the final mark are written into existing runs. Runs are not merged:
merging drops w:noBreakHyphen and inline code formatting, so «REST-интерфейс»
became «RESTинтерфейс».
"""
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()
logical = self._logical_text(text_runs)
full = logical.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
fixed = full[0].upper() + full[1:]
if not fixed.endswith("."):
fixed = fixed.rstrip(";,. ") + "."
else:
fixed = full[0].lower() + full[1:] if full else full
fixed = full[0].lower() + full[1:]
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 = ""
if logical == fixed:
continue
self._apply_list_punctuation(text_runs, full, fixed)
@classmethod
def _apply_list_punctuation(cls, text_runs, full: str, fixed: str) -> None:
if full[0] != fixed[0]:
for run in text_runs:
if cls._is_hyphen_run(run) or not run.text:
continue
run.text = fixed[0] + run.text[1:]
break
desired_end = fixed[-1]
last = None
for run in text_runs:
if cls._is_hyphen_run(run) or not run.text:
continue
last = run
if last is None:
return
trimmed = last.text.rstrip(";,. ")
if trimmed.endswith(desired_end):
last.text = trimmed
else:
last.text = trimmed + desired_end
def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState) -> Generator[
RenderedInfo | Renderable, None, None]: