def set_name_to_target(self, font, target_font: str = None) -> None:
target_font = target_font or self.target_font_name
if self.is_protected_font(font):
return
for attr in ("name", "name_bi", "name_far_east"):
if hasattr(font, attr):
setattr(font, attr, target_font)
def is_protected_font(self, font) -> bool:
try:
nm = (getattr(font, "name", "") or "").strip()
except Exception:
return False
return nm in self.PROTECTED_FONTS
def set_style_font_size(self, doc: aw.Document, style_name: str, size_pt: float) -> bool:
try:
st = doc.styles.get_by_name(style_name)
if st is None:
return False
f = getattr(st, "font", None)
if f is None:
return False
changed = False
for attr in ("size", "size_bi"):
if hasattr(f, attr):
try:
setattr(f, attr, float(size_pt))
changed = True
except Exception:
logging.debug("[change style] style '%s' set %s failed", style_name, attr, exc_info=True)
return changed
except Exception:
logging.debug("[change style] set_style_font_size('%s') failed", style_name, exc_info=True)
return False
def force_runs_size_in_paragraphs_with_style(self, doc: aw.Document, style_name: str, size_pt: float) -> None:
try:
paras = doc.get_child_nodes(aw.NodeType.PARAGRAPH, True)
hits_p = hits_r = 0
target = style_name.casefold()
for i in range(paras.count):
p = paras[i].as_paragraph()
if p is None:
continue
sname = (getattr(p.paragraph_format, "style_name", "") or "").strip().casefold()
if sname != target:
continue
runs = p.get_child_nodes(aw.NodeType.RUN, True)
for j in range(runs.count):
rr = runs[j].as_run()
if rr is None:
continue
rf = rr.font
for attr in ("size", "size_bi"):
if hasattr(rf, attr):
try:
setattr(rf, attr, float(size_pt))
hits_r += 1
except Exception:
logging.debug("[change style] run size set failed (%s)", attr, exc_info=True)
hits_p += 1
logging.info("[change style] Forced sizes for style '%s' (paras=%d, runs≈%d)", style_name, hits_p, hits_r)
except Exception:
logging.debug("[change style] force_runs_size_in_paragraphs_with_style('%s') failed", style_name, exc_info=True)
# =============== 1a) Footer / Page Number precise tweaks ===============
try:
# Footer & Footer Right -> 10pt (style + existing content)
for style_name, to_sz in (("Footer", 10.0), ("Footer Right", 10.0)):
self.set_style_font_size(doc, style_name, to_sz)
self.force_runs_size_in_paragraphs_with_style(work, style_name, to_sz)
# Page Number style -> 12pt + target family
pn_style = work.styles.get_by_name("Page Number")
if pn_style is not None:
self.set_name_to_target(pn_style.font, target_font)
for attr in ("size", "size_bi"):
if hasattr(pn_style.font, attr):
try:
setattr(pn_style.font, attr, 12)
except Exception:
logging.debug("[change style] 'Page Number' style set %s failed", attr, exc_info=True)
# HARD ENFORCEMENT: field result runs inside footers
pn_runs = 0
for i in range(work.sections.count):
sect = work.sections[i]
hfs = getattr(sect, "headers_footers", None)
if hfs is None:
continue
for hft in (
aw.HeaderFooterType.FOOTER_PRIMARY,
aw.HeaderFooterType.FOOTER_FIRST,
aw.HeaderFooterType.FOOTER_EVEN,
):
hf = hfs.get_by_header_footer_type(hft)
if hf is None:
continue
rng = getattr(hf, "range", None)
fields = getattr(rng, "fields", None) if rng is not None else None
if not fields:
continue
for fi in range(fields.count):
try:
fld = fields[fi]
if getattr(fld, "type", None) not in (
aw.fields.FieldType.FIELD_PAGE,
aw.fields.FieldType.FIELD_NUM_PAGES,
aw.fields.FieldType.FIELD_SECTION_PAGES,
):
continue
sep = getattr(fld, "separator", None)
end = getattr(fld, "end", None)
start = getattr(fld, "start", None)
anchor = sep if sep is not None else start
if anchor is None or end is None:
continue
cur = anchor.next_sibling
while cur is not None and cur is not end:
if cur.node_type == aw.NodeType.RUN:
rn = cur.as_run()
rf = rn.font
if not self.is_protected_font(rf):
self.set_name_to_target(rf, target_font)
if hasattr(rf, "size"):
rf.size = 12
if hasattr(rf, "size_bi"):
rf.size_bi = 12
pn_runs += 1
# catch shallow nested runs (like in shapes/containers)
if hasattr(cur, "get_child_nodes"):
inner = cur.get_child_nodes(aw.NodeType.RUN, False)
for j in range(inner.count):
rr = inner[j].as_run()
if rr is None:
continue
rf = rr.font
if not self.is_protected_font(rf):
self.set_name_to_target(rf, target_font)
if hasattr(rf, "size"):
rf.size = 12
if hasattr(rf, "size_bi"):
rf.size_bi = 12
pn_runs += 1
cur = cur.next_sibling
except Exception:
logging.debug("[change style] page-number field formatting failed", exc_info=True)
logging.info("[change style] Footer 10pt + Page Number 12pt enforced (runs touched=%d)", pn_runs)
except Exception as e:
logging.debug("[change style] Footer/Page Number tweaks failure: %s", e, exc_info=True)
# =============== 1a-EXT) Style-size table (sizes only) =================
try:
# 1) Apply at STYLE level (unconditional set)
for style_name, to_sz in self.STYLE_SIZE_MAP.items():
self.set_style_font_size(work, style_name, float(to_sz))
# 2) Apply to EXISTING content (paragraphs/runs) using those styles
paras = work.get_child_nodes(aw.NodeType.PARAGRAPH, True)
_affected_paras = 0
_affected_runs = 0
style_names_cf = {n.casefold(): v for n, v in self.STYLE_SIZE_MAP.items()}
for i in range(paras.count):
try:
p = paras[i].as_paragraph()
if p is None:
continue
pf = p.paragraph_format
sname = (getattr(pf, "style_name", "") or "").strip().casefold()
if sname not in style_names_cf:
continue
to_sz = float(style_names_cf[sname])
pruns = p.get_child_nodes(aw.NodeType.RUN, True)
for r_i in range(pruns.count):
rr = pruns[r_i].as_run()
if rr is None:
continue
rf = rr.font
# Family always to target (unless protected)
if not self.is_protected_font(rf):
self.set_name_to_target(rf, target_font)
# Enforce size
for attr in ("size", "size_bi"):
if hasattr(rf, attr):
try:
setattr(rf, attr, to_sz)
_affected_runs += 1
except Exception:
logging.debug("[change style] Style-size map: run set failed", exc_info=True)
_affected_paras += 1
except Exception:
logging.debug("[change style] Style-size map: per-paragraph handling failed", exc_info=True)
logging.info("[change style] Style-size table enforced (paras=%d, runs≈%d)",
_affected_paras, _affected_runs)
except Exception as e:
logging.debug("[change style] Style-size table enforcement failure: %s", e, exc_info=True)
# =============== 1c) Comments ballons ==============================
try:
balloon_style = work.styles.get_by_style_identifier(aw.StyleIdentifier.BALLOON_TEXT)
balloon_style.font.size = 8
comment = work.get_child(aw.NodeType.COMMENT_RANGE_START, 0, True).as_comment_range_start()
comment_reference = comment.next_sibling.as_run()
comment_reference.font.size = 8.5
except Exception as e:
logging.debug("[change style] comments enforcement failure: %s", e, exc_info=True)