v0.6.0 BigUpdate
This commit is contained in:
@@ -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,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]:
|
||||
|
||||
Reference in New Issue
Block a user