From 0f9afc978f993a48d3d5993436065a0399c2c59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E7=90=A6?= <1702282943@qq.com> Date: Sat, 28 Feb 2026 10:12:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=AE=BA=E6=96=87?= =?UTF-8?q?=E6=8E=92=E7=89=88=E8=84=9A=E6=9C=AC=E5=B9=B6=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E6=A0=87=E5=87=86=E6=A0=BC=E5=BC=8F=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/format_thesis_docx.py | 197 ++++++++++++++++++ .../萌贝母婴商城毕业论文初稿-2026版-排版.docx | Bin 0 -> 62421 bytes 2 files changed, 197 insertions(+) create mode 100644 example/format_thesis_docx.py create mode 100644 example/萌贝母婴商城毕业论文初稿-2026版-排版.docx diff --git a/example/format_thesis_docx.py b/example/format_thesis_docx.py new file mode 100644 index 0000000..14ec1d9 --- /dev/null +++ b/example/format_thesis_docx.py @@ -0,0 +1,197 @@ +from docx import Document +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Cm, Pt, RGBColor + + +SRC = r"/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版.docx" +DST = r"/Users/apple/code/bs/mying/example/萌贝母婴商城毕业论文初稿-2026版-排版.docx" + + +def set_style_font( + style, + east_asia_font: str, + size_pt: float, + bold: bool | None = None, + west_font: str = "Times New Roman", +): + font = style.font + font.name = west_font + font.size = Pt(size_pt) + if bold is not None: + font.bold = bold + font.color.rgb = RGBColor(0, 0, 0) + rfonts = style.element.get_or_add_rPr().get_or_add_rFonts() + rfonts.set(qn("w:ascii"), west_font) + rfonts.set(qn("w:hAnsi"), west_font) + rfonts.set(qn("w:eastAsia"), east_asia_font) + + +def set_runs_font( + paragraph, + east_asia_font: str, + size_pt: float, + bold: bool | None = None, + west_font: str = "Times New Roman", +): + for run in paragraph.runs: + run.font.name = west_font + run.font.size = Pt(size_pt) + if bold is not None: + run.font.bold = bold + run.font.color.rgb = RGBColor(0, 0, 0) + rpr = run._element.get_or_add_rPr() + rfonts = rpr.get_or_add_rFonts() + rfonts.set(qn("w:ascii"), west_font) + rfonts.set(qn("w:hAnsi"), west_font) + rfonts.set(qn("w:eastAsia"), east_asia_font) + + +def set_runs_common(paragraph, italic: bool | None = None, color_black: bool = True): + for run in paragraph.runs: + if italic is not None: + run.font.italic = italic + if color_black: + run.font.color.rgb = RGBColor(0, 0, 0) + + +def is_numbered_paragraph(paragraph) -> bool: + ppr = paragraph._p.pPr + if ppr is None: + return False + return ppr.numPr is not None + + +def set_table_all_borders_black(table): + for row in table.rows: + for cell in row.cells: + tc = cell._tc + tc_pr = tc.get_or_add_tcPr() + tc_borders = tc_pr.find(qn("w:tcBorders")) + if tc_borders is None: + tc_borders = OxmlElement("w:tcBorders") + tc_pr.append(tc_borders) + for edge in ("top", "left", "bottom", "right", "insideH", "insideV"): + edge_tag = qn(f"w:{edge}") + elem = tc_borders.find(edge_tag) + if elem is None: + elem = OxmlElement(f"w:{edge}") + tc_borders.append(elem) + elem.set(qn("w:val"), "single") + elem.set(qn("w:sz"), "4") + elem.set(qn("w:color"), "000000") + elem.set(qn("w:space"), "0") +def iter_table_paragraphs(table): + for row in table.rows: + for cell in row.cells: + for p in cell.paragraphs: + yield p + for t in cell.tables: + yield from iter_table_paragraphs(t) + +def set_first_line_two_chars(paragraph, twips: int = 420, chars: int = 200): + ppr = paragraph._p.get_or_add_pPr() + ind = ppr.find(qn("w:ind")) + if ind is None: + ind = OxmlElement("w:ind") + ppr.append(ind) + ind.set(qn("w:firstLine"), str(twips)) + ind.set(qn("w:firstLineChars"), str(chars)) + +def format_paragraph(p): + style_name = p.style.name if p.style is not None else "" + if style_name == "Heading 1": + apply_para_format(p, 1.5, 0, WD_ALIGN_PARAGRAPH.CENTER) + set_runs_font(p, "黑体", 22, True) + elif style_name == "Heading 2": + apply_para_format(p, 1.5, 32) + set_runs_font(p, "黑体", 16, True) + elif style_name == "Heading 3": + apply_para_format(p, 1.5, 28) + set_runs_font(p, "宋体", 14, True) + elif style_name == "Heading 4": + apply_para_format(p, 1.5, 24) + set_runs_font(p, "宋体", 12, True) + set_runs_common(p, italic=False, color_black=True) + elif is_numbered_paragraph(p) or style_name.startswith("List Number"): + p.paragraph_format.line_spacing = 1.5 + set_runs_font(p, "宋体", 12) + set_runs_common(p, color_black=True) + else: + apply_para_format(p, 1.5, 24) + set_runs_font(p, "宋体", 10.5) + set_runs_common(p, color_black=True) + + +def apply_para_format(paragraph, line_spacing: float, first_line_pt: float | None = None, align=None): + fmt = paragraph.paragraph_format + fmt.line_spacing = line_spacing + fmt.space_before = Pt(0) + fmt.space_after = Pt(0) + if first_line_pt is not None: + fmt.first_line_indent = Pt(first_line_pt) + set_first_line_two_chars(paragraph) + + if align is not None: + paragraph.alignment = align +def set_page_layout(doc): + for section in doc.sections: + section.page_width = Cm(21.0) + section.page_height = Cm(29.7) + section.top_margin = Cm(2.5) + section.bottom_margin = Cm(2.5) + section.left_margin = Cm(2.5) + section.right_margin = Cm(2.5) + section.header_distance = Cm(1.5) + section.footer_distance = Cm(1.75) + +def main(): + doc = Document(SRC) + + normal = doc.styles["Normal"] + h1 = doc.styles["Heading 1"] + h2 = doc.styles["Heading 2"] + h3 = doc.styles["Heading 3"] + h4 = doc.styles["Heading 4"] + + # 正文:宋体小四,首行缩进2字符(约24pt),1.5倍行距 + set_style_font(normal, "宋体", 10.5) + normal.paragraph_format.line_spacing = 1.5 + normal.paragraph_format.first_line_indent = Pt(21) + + # 标题1:黑体二号,加粗,居中,1.5倍行距 + set_style_font(h1, "黑体", 22, True) + h1.paragraph_format.line_spacing = 1.5 + h1.paragraph_format.first_line_indent = Pt(0) + + # 标题2:黑体三号,加粗,首行缩进2字符,1.5倍行距 + set_style_font(h2, "黑体", 16, True) + h2.paragraph_format.line_spacing = 1.5 + h2.paragraph_format.first_line_indent = Pt(32) + + # 标题3:宋体四号,加粗,首行缩进2字符,1.5倍行距 + set_style_font(h3, "黑体", 14, True) + h3.paragraph_format.line_spacing = 1.5 + h3.paragraph_format.first_line_indent = Pt(28) + + # 标题4:加粗,取消斜体,黑色,1.5倍行距 + set_style_font(h4, "黑体", 14, True) + h4.font.italic = False + h4.paragraph_format.line_spacing = 1.5 + h4.paragraph_format.first_line_indent = Pt(24) + set_page_layout(doc) + for p in doc.paragraphs: + format_paragraph(p) + + for t in doc.tables: + set_table_all_borders_black(t) + for p in iter_table_paragraphs(t): + format_paragraph(p) + + doc.save(DST) + print(DST) + + +if __name__ == "__main__": + main() diff --git a/example/萌贝母婴商城毕业论文初稿-2026版-排版.docx b/example/萌贝母婴商城毕业论文初稿-2026版-排版.docx new file mode 100644 index 0000000000000000000000000000000000000000..35970ccaa053f178008c877f83d9116051ba2a9a GIT binary patch literal 62421 zcmY(qQ+O?1(*+vawr$(CZD+@}?PSNcZQHhO?%2-B`<-)f{+sSz-MyY!vuf0uvqp`j zAPo$H0ssI20f4Efqf@O^9G?sb05Aat0D$~^S6kT5*4fn7SzpD&-qcB#&fUhkIayYI zQvf0K<_k52o{zvw7#_9az@EaMCLT+qHuH%?bB+E=2I%c&iYr2OULg#OE
%Nui+4UplmZjehK
zDda{dPb)B4;|(B|ygE(|34bI6LWy?*G`zNOo;|;d*j`2HVvPKd`p@x%g6r3mpBvGe
zu)k=|*BTv}TDDatR7U_uT7n32kxd_+
z+oU@41z1CQ^rN!Y4w7Za(UqN5Fj?K=8E}sJk1coJzfCpjKV4dyu_`_m7eNwOo-e|1
zS^xS1DajiNb(6p{n7ZPsy@Ou{UtTDS<4E}4*248(mKJ4grd0jM;6qte`RkQB1>;i2
znK{{i94>^3e4g@xwbwaC Ge1A=C|o^%Q-kXgIS~hTL`z ?Vn?ao}
z)u!$2d6>T|?G%l|H}I6PwXFScFgopmjh7Zam$3b{Da9ba&uM%ui}lyK;I2~ 4Z>LFSon4gl@yxnmheZqpy^6
zdwd*~tRLI>)-p$wx=#+Ty_<+0yqoj@U`ae2Mw{`|wYsosd&5tuu*`}wM^(e-&z&uHyl-#xJE{rb@V8fEwEy6@Dt=>J>V
zJ-m2f_wxO%{?gU2|5c0qv-{KaAsE%$-Z;I9w_p(Rya(>ao#FR0ldVv87!(?6@PKQV
zN$%b}y^3=S{?#g^p7~`1`>oyTJ-rj!`7IzVeAv0Vqw41Z@;SX@fMEMlU-~63e~atY
z&i3=Kdga8T=6n71y0bTEwa3*w20eFdci$!q-tG<9CI_JlW-HVV#P{s74amyXU8pCs
zM@|0>ujZ+zaVoCYuatT2JD__6^_26L8@~_O`_@?7>dEM5vZffk`KR2M_?8P^QvYpe
z$MMM>7SN8Gr>>XReRS2K&8}1zmaj`$_o$eE?aBDYRj1RJt-ufIb}PgW4&6I@)GnXC
zxp^V6*L&FNrUy*a9`Y2s_5nitM$-q0{b8GTd!>#2<$3-2c^Bw=CU$Vy+g$sq@fkm>
zSpcDHT#GGci=WoLCG71zwiFm(Y2~xuymj*naCTbFooLMgmh(Ovn;_fvUHf{!7x#kS
z53v!;SCBQ(;2as}ztQ+SNT{AJu#9Vla30@w@Y8jlSn%Xqh*g
zkiW-i5!xG|c6l^Mo6rv6N6qSWLwm1VyGMZGv0dxPv#?{ro0DlImoio#E(!?lynKV1ShU`be;mU-Ucn{};
zEeRM6stkYN|B~7sPUwIFA`f>`H%4X*;HnwJlMKB>
oyXzTaXIktL%W^Z&@Z8J8`UHYr0^vNXTXmgo5AKQl>tJJ))a*rF+I
KYw;^V1~$Zr5CM(iOG$yrFZ)V0Xt
zxe=|7FoXB#;`hu>-4?Y%k!&N-DnbzaPO6RWA4xvhM+%3oiE~*br{>IJuGC^BeATV?
zx=;`)NV7%5j6QJ>ws(M2S{4;LK!&QJc&W{z0w@8@E2DxThEfkg*bVX_u}f!veSs<|
z2#DARmR^jRNyHtR=EdQK@-d=Q1r`vPSy5-aJr?zV*0D|yVLPEg*Kim**+?@n2n@=P
zsAPkBVe#L?5;0644x15Rn22a1({=7q$f|VFtBOi=Eh9QJYuvOU``t>I$iyeNF(l^n
zMh!w>P{=?Cs~i~*)gnMTClL@>0ql`tTqf8qL&T>F5&vq50{Rcj<3)3zrR&KbOWxbC
z*=qB@Ei<7|$b=(0k>oqGRfd0%f;n5!T4Um@&;p`SM50tvnZ*qzF!~Yf457%y4KB%p
zkrrE$UFgI?3@o6+a=nfumAQemB}H~;7Ve@D!ns)KGc@7VV7s