Change styles for complete document

@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

@Prakruth Could you please elaborate what exactly does not work?

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}-]", "")