- Add\Rework UI - Add Split Table and Listing - Add Support Customazeble schems
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""A4 page geometry helpers for portrait / landscape sections."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from docx.enum.section import WD_ORIENT
|
||||
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Mm, Pt
|
||||
|
||||
from .util import create_element
|
||||
|
||||
# GOST-like A4 margins (same as styles._apply_common_page_and_body)
|
||||
MARGIN_LEFT = Mm(30)
|
||||
MARGIN_RIGHT = Mm(10)
|
||||
MARGIN_TOP = Mm(20)
|
||||
MARGIN_BOTTOM = Mm(20)
|
||||
|
||||
A4_SHORT = Mm(210)
|
||||
A4_LONG = Mm(297)
|
||||
|
||||
|
||||
def is_landscape_section(section) -> bool:
|
||||
"""True if section is (or should be treated as) landscape A4."""
|
||||
try:
|
||||
if section.orientation == WD_ORIENT.LANDSCAPE:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return int(section.page_width) > int(section.page_height)
|
||||
|
||||
|
||||
def apply_section_geometry(section, *, landscape: bool) -> None:
|
||||
"""
|
||||
Set orientation and page size.
|
||||
|
||||
Set orientation first (python-docx may swap w/h on change), then force A4 dims.
|
||||
"""
|
||||
target = WD_ORIENT.LANDSCAPE if landscape else WD_ORIENT.PORTRAIT
|
||||
try:
|
||||
section.orientation = target
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if landscape:
|
||||
section.page_width = A4_LONG
|
||||
section.page_height = A4_SHORT
|
||||
else:
|
||||
section.page_width = A4_SHORT
|
||||
section.page_height = A4_LONG
|
||||
|
||||
section.left_margin = MARGIN_LEFT
|
||||
section.right_margin = MARGIN_RIGHT
|
||||
section.top_margin = MARGIN_TOP
|
||||
section.bottom_margin = MARGIN_BOTTOM
|
||||
|
||||
# Explicit orient on pgSz for Word.
|
||||
pg_sz = section._sectPr.find(qn("w:pgSz"))
|
||||
if pg_sz is None:
|
||||
pg_sz = section._sectPr._add_pgSz()
|
||||
if landscape:
|
||||
pg_sz.set(qn("w:orient"), "landscape")
|
||||
# Re-assert after XML tweak (some builds reshuffle).
|
||||
section.page_width = A4_LONG
|
||||
section.page_height = A4_SHORT
|
||||
else:
|
||||
if pg_sz.get(qn("w:orient")) is not None:
|
||||
del pg_sz.attrib[qn("w:orient")]
|
||||
section.page_width = A4_SHORT
|
||||
section.page_height = A4_LONG
|
||||
|
||||
# Vertical align: center on landscape (figures/tables only); top on portrait
|
||||
sect_pr = section._sectPr
|
||||
for el in list(sect_pr.findall(qn("w:vAlign"))):
|
||||
sect_pr.remove(el)
|
||||
if landscape:
|
||||
sect_pr.append(create_element("w:vAlign", {"w:val": "center"}))
|
||||
|
||||
|
||||
def content_size(*, landscape: bool) -> tuple:
|
||||
"""Return (max_height, max_width) usable content area for LayoutTracker."""
|
||||
if landscape:
|
||||
page_w, page_h = A4_LONG, A4_SHORT
|
||||
else:
|
||||
page_w, page_h = A4_SHORT, A4_LONG
|
||||
max_height = page_h - MARGIN_TOP - MARGIN_BOTTOM
|
||||
max_width = page_w - MARGIN_LEFT - MARGIN_RIGHT
|
||||
return max_height, max_width
|
||||
|
||||
|
||||
def apply_centered_page_footer(section) -> None:
|
||||
"""Centered PAGE field, Times New Roman 12 (body section style)."""
|
||||
footer = section.footer
|
||||
footer.is_linked_to_previous = False
|
||||
if not footer.paragraphs:
|
||||
footer.add_paragraph()
|
||||
paragraph = footer.paragraphs[0]
|
||||
paragraph.clear()
|
||||
paragraph.paragraph_format.first_line_indent = 0
|
||||
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
run = paragraph.add_run()
|
||||
run.font.name = "Times New Roman"
|
||||
run.font.size = Pt(12)
|
||||
paragraph._p.append(create_element("w:fldSimple", {
|
||||
"w:instr": "PAGE \\* MERGEFORMAT",
|
||||
}))
|
||||
|
||||
|
||||
def clear_section_footer(section) -> None:
|
||||
"""Empty footer (no PAGE) — for title / assignment / TOC sections."""
|
||||
footer = section.footer
|
||||
footer.is_linked_to_previous = False
|
||||
for p in footer.paragraphs:
|
||||
p.clear()
|
||||
if not footer.paragraphs:
|
||||
footer.add_paragraph()
|
||||
|
||||
|
||||
def ensure_continuous_page_numbers(document) -> None:
|
||||
"""
|
||||
After docxcompose of title/assignment: no PAGE on early sections,
|
||||
continuous numbering (do not restart at 1 on body section).
|
||||
"""
|
||||
from docx.oxml.ns import qn
|
||||
|
||||
sections = list(document.sections)
|
||||
if not sections:
|
||||
return
|
||||
|
||||
# Heuristic: sections before the first that already has a PAGE field /
|
||||
# or first N-1 if title was prepended — clear footer on all but keep
|
||||
# continuous pgNumType. Body renderer already puts PAGE on body section;
|
||||
# after compose, early sections may inherit footers — clear empty ones
|
||||
# that belong to front matter (no Heading-like body content is hard to
|
||||
# detect), so: clear footer on every section that has no PAGE field, and
|
||||
# strip w:pgNumType start=1 everywhere.
|
||||
for section in sections:
|
||||
sect_pr = section._sectPr
|
||||
for child in list(sect_pr):
|
||||
if child.tag == qn("w:pgNumType"):
|
||||
# Keep continuous: remove start attribute if present
|
||||
if child.get(qn("w:start")) is not None:
|
||||
del child.attrib[qn("w:start")]
|
||||
Reference in New Issue
Block a user