@Prakruth Could you please provide your code that will allow us to reproduce the problem?
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)
@Prakruth Thank you for additional information. But could you please provide a simple runnable code that will allow us to reproduce the problem on our side?
As I can see in the provided code you do not set table border, so it is expected it is not set in the output document.
target_font = "Aptos"
target_size = 10.5
tol = 0.05
# ---- Load document and changing styles and fonts ----
fp = "headings.docx"
doc = aw.Document(fp)
builder = aw.DocumentBuilder(doc=doc)
# --- 1. Universal style fonts and sizes ---
styles = doc.styles
for i in range(styles.count):
style = styles[i]
if style.type in (aw.StyleType.PARAGRAPH, aw.StyleType.CHARACTER, aw.StyleType.TABLE):
style.font.name = target_font
style.font.size = target_size
paras = doc.get_child_nodes(aw.NodeType.PARAGRAPH, True)
for i in range(paras.count):
para = paras[i]
p = para.as_paragraph()
for node in p.get_child_nodes(aw.NodeType.RUN, False):
run = node.as_run()
run.font.name = target_font
run.font.size = target_size
# --- 2. Built-in property: "Blank" ---
properties = doc.built_in_document_properties
for i in range(properties.count):
prop = properties[i]
if prop.name == "BlankDocx":
print(f"the property name is {prop.name}")
# --- 3. Table borders in footers only ---
all_tables = doc.get_child_nodes(aw.NodeType.TABLE, True)
for ti in range(all_tables.count):
tbl = all_tables[ti].as_table()
anc = tbl.get_ancestor(aw.NodeType.HEADER_FOOTER)
if anc is None:
continue
hf = anc.as_header_footer()
if hf.is_header:
continue
tbl.set_border(aw.BorderType.TOP, aw.LineStyle.SINGLE, 1.0, Color.blue, False)
sections = doc.sections
for i in range(sections.count):
section = sections[i]
headers_footers = section.headers_footers
for hf_node in headers_footers:
hf = hf_node.as_header_footer()
if hf.header_footer_type in (
aw.HeaderFooterType.FOOTER_PRIMARY,
aw.HeaderFooterType.FOOTER_FIRST,
aw.HeaderFooterType.FOOTER_EVEN,
):
# Set all footer runs (not page numbers or footnotes) to 8 pt
for para in hf.get_child_nodes(aw.NodeType.PARAGRAPH, True):
p = para.as_paragraph()
for node in p.get_child_nodes(aw.NodeType.RUN, False):
run = node.as_run()
run.font.name = target_font
run.font.size = 8
# --- Remove "- " around page numbers in footer paragraphs ---
paras = hf.get_child_nodes(aw.NodeType.PARAGRAPH, True)
for p_i in range(paras.count):
p = paras[p_i].as_paragraph()
text = p.get_text().strip()
if text.startswith("- ") and text.endswith(" -"):
new_text = text[2:-2].strip()
p.runs.clear()
p.append_child(aw.Run(doc, new_text))
# Set page number fields to 12 pt
fields = hf.range.fields
for i in range(fields.count):
fld = fields[i]
if fld.type in (
aw.fields.FieldType.FIELD_PAGE,
aw.fields.FieldType.FIELD_NUM_PAGES,
aw.fields.FieldType.FIELD_SECTION_PAGES,
):
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()
rn.font.name = target_font
rn.font.size = 12
cur = cur.next_sibling
# Set footer footnotes to 8 pt (rare, but for robustness)
footnotes = hf.get_child_nodes(aw.NodeType.FOOTNOTE, True)
for i in range(footnotes.count):
f = footnotes[i].as_footnote()
f.font.name = target_font
f.font.size = 8
for para in f.get_child_nodes(aw.NodeType.PARAGRAPH, True):
for run in para.as_paragraph().runs:
run.font.name = target_font
run.font.size = 8
# ---- Save updated document ----
out_path = r"styles_output.docx"
doc.save(out_path)
Here using this code we are able setup border but hyphen removal and the page number font size is not changing as expected`
Preformatted text
@Prakruth Regarding removing hyphens, the code is not quite correct. Page numbers in MS Word document are not represented with simple text. They are represented with PAGE
field. Please try modifying the code like this:
# --- Remove "- " around page numbers in footer paragraphs ---
paras = hf.get_child_nodes(aw.NodeType.PARAGRAPH, True)
for p_i in range(paras.count):
p = paras[p_i].as_paragraph()
if p.runs[0] != None and p.runs[0].as_run().text == "- ":
p.runs[0].remove()
if p.runs[p.runs.count-1] != None and p.runs[p.runs.count-1].as_run().text == " -":
p.runs[p.runs.count-1].remove()
As I can see page number size is changed as expected after modifying the code like suggested above.
footer test.docx (89.8 KB)
thanks for the code, same code is not working for this file where we have same structure here
in the file uploaded hyphen are not getting removed from page numbers
@Prakruth The problem occurs because non breaking hyphen is used in this document instead of regular hyphen. So the conditions used in the code does not pass. Please try modifying your code like this:
# --- Remove "- " around page numbers in footer paragraphs ---
paras = hf.get_child_nodes(aw.NodeType.PARAGRAPH, True)
for p_i in range(paras.count):
p = paras[p_i].as_paragraph()
p_text = p.to_string(aw.SaveFormat.TEXT).strip("\r\n")
if (p_text.startswith("-") or p_text.startswith(aw.ControlChar.NON_BREAKING_HYPHEN_CHAR)) and (p_text.endswith("-") or p_text.endswith(aw.ControlChar.NON_BREAKING_HYPHEN_CHAR)):
p.range.replace_regex(f"[{aw.ControlChar.NON_BREAKING_HYPHEN_CHAR}-]\\s+", "")
p.range.replace_regex(f"\\s+[{aw.ControlChar.NON_BREAKING_HYPHEN_CHAR}-]", "")