| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- # -*- coding: utf-8 -*-
- """Shared DOCX helpers for the reference document."""
- import docx.opc.constants
- from docx import Document
- from docx.shared import Pt, Cm, RGBColor
- from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
- from docx.enum.table import WD_TABLE_ALIGNMENT
- from docx.oxml.ns import qn
- from docx.oxml import OxmlElement
- BLACK = RGBColor(0, 0, 0)
- GREY = RGBColor(0x40, 0x40, 0x40)
- def set_cn_font(target, name_cn, name_ascii=None, size=None, bold=None, color=None):
- f = target.font
- f.name = name_ascii or name_cn
- if size is not None:
- f.size = Pt(size)
- if bold is not None:
- f.bold = bold
- if color is not None:
- f.color.rgb = color
- el = target.element
- rPr = el.get_or_add_rPr()
- rFonts = rPr.find(qn("w:rFonts"))
- if rFonts is None:
- rFonts = OxmlElement("w:rFonts")
- rPr.append(rFonts)
- rFonts.set(qn("w:ascii"), name_ascii or name_cn)
- rFonts.set(qn("w:hAnsi"), name_ascii or name_cn)
- rFonts.set(qn("w:eastAsia"), name_cn)
- def init_doc():
- doc = Document()
- sec = doc.sections[0]
- sec.page_width = Cm(21.0)
- sec.page_height = Cm(29.7)
- sec.top_margin = Cm(2.54)
- sec.bottom_margin = Cm(2.54)
- sec.left_margin = Cm(2.8)
- sec.right_margin = Cm(2.8)
- normal = doc.styles["Normal"]
- set_cn_font(normal, "宋体", "Times New Roman", 10.5)
- pf = normal.paragraph_format
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- pf.space_before = Pt(0)
- pf.space_after = Pt(4)
- for name, size, before, after in (("Heading 1", 16, 18, 8),
- ("Heading 2", 13, 14, 6),
- ("Heading 3", 11, 10, 4)):
- st = doc.styles[name]
- set_cn_font(st, "黑体", "Times New Roman", size, True, BLACK)
- st.paragraph_format.space_before = Pt(before)
- st.paragraph_format.space_after = Pt(after)
- st.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- # let Word refresh the TOC field when the document is opened
- settings = doc.settings.element
- uf = OxmlElement("w:updateFields")
- uf.set(qn("w:val"), "true")
- settings.append(uf)
- footer = sec.footer
- fp = footer.paragraphs[0]
- fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
- run = fp.add_run()
- f1 = OxmlElement("w:fldChar"); f1.set(qn("w:fldCharType"), "begin")
- it = OxmlElement("w:instrText"); it.set(qn("xml:space"), "preserve"); it.text = " PAGE "
- f2 = OxmlElement("w:fldChar"); f2.set(qn("w:fldCharType"), "end")
- run._r.append(f1); run._r.append(it); run._r.append(f2)
- set_cn_font(run, "宋体", "Times New Roman", 9)
- return doc
- def add_hyperlink(paragraph, url, text, size=8.5, bold=False):
- part = paragraph.part
- r_id = part.relate_to(url, docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK, is_external=True)
- link = OxmlElement("w:hyperlink")
- link.set(qn("r:id"), r_id)
- r = OxmlElement("w:r")
- rPr = OxmlElement("w:rPr")
- rFonts = OxmlElement("w:rFonts")
- rFonts.set(qn("w:ascii"), "Consolas")
- rFonts.set(qn("w:hAnsi"), "Consolas")
- rFonts.set(qn("w:eastAsia"), "宋体")
- rPr.append(rFonts)
- if bold:
- rPr.append(OxmlElement("w:b"))
- color = OxmlElement("w:color"); color.set(qn("w:val"), "1F4E79"); rPr.append(color)
- u = OxmlElement("w:u"); u.set(qn("w:val"), "single"); rPr.append(u)
- sz = OxmlElement("w:sz"); sz.set(qn("w:val"), str(int(size * 2))); rPr.append(sz)
- r.append(rPr)
- t = OxmlElement("w:t"); t.text = text; t.set(qn("xml:space"), "preserve")
- r.append(t)
- link.append(r)
- paragraph._p.append(link)
- def shade(cell, hexcolor):
- tcPr = cell._tc.get_or_add_tcPr()
- shd = OxmlElement("w:shd")
- shd.set(qn("w:val"), "clear")
- shd.set(qn("w:color"), "auto")
- shd.set(qn("w:fill"), hexcolor)
- tcPr.append(shd)
- def cell_margins(table, top=55, bottom=55, left=85, right=85):
- tblPr = table._tbl.tblPr
- mar = OxmlElement("w:tblCellMar")
- for tag, val in (("top", top), ("left", left), ("bottom", bottom), ("right", right)):
- el = OxmlElement("w:" + tag)
- el.set(qn("w:w"), str(val))
- el.set(qn("w:type"), "dxa")
- mar.append(el)
- tblPr.append(mar)
- def repeat_header(row):
- trPr = row._tr.get_or_add_trPr()
- el = OxmlElement("w:tblHeader"); el.set(qn("w:val"), "true")
- trPr.append(el)
- def cant_split(row):
- trPr = row._tr.get_or_add_trPr()
- el = OxmlElement("w:cantSplit"); el.set(qn("w:val"), "true")
- trPr.append(el)
- def set_cell_text(cell, text, size=8.5, bold=False, align=None, color=None):
- cell.text = ""
- p = cell.paragraphs[0]
- p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE
- p.paragraph_format.space_before = Pt(1)
- p.paragraph_format.space_after = Pt(1)
- if align is not None:
- p.alignment = align
- run = p.add_run(text)
- set_cn_font(run, "宋体", "Times New Roman", size, bold, color)
- def make_table(doc, headers, rows, widths, link_col=None, font=8.5, head_font=8.5):
- table = doc.add_table(rows=1, cols=len(headers))
- table.style = "Table Grid"
- table.alignment = WD_TABLE_ALIGNMENT.CENTER
- table.autofit = False
- cell_margins(table)
- hdr = table.rows[0]
- repeat_header(hdr)
- cant_split(hdr)
- for i, h in enumerate(headers):
- c = hdr.cells[i]
- c.width = Cm(widths[i])
- shade(c, "D9E2F3")
- set_cell_text(c, h, head_font, True, WD_ALIGN_PARAGRAPH.CENTER)
- for r in rows:
- row = table.add_row()
- cant_split(row)
- for i, val in enumerate(r):
- c = row.cells[i]
- c.width = Cm(widths[i])
- if i == 0:
- cell_no_shade = c
- if link_col is not None and i == link_col:
- c.text = ""
- p = c.paragraphs[0]
- p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE
- p.paragraph_format.space_before = Pt(1)
- p.paragraph_format.space_after = Pt(1)
- add_hyperlink(p, val[1], val[0], size=font)
- else:
- set_cell_text(c, val, font)
- for row in table.rows:
- for i, c in enumerate(row.cells):
- c.width = Cm(widths[i])
- return table
- def para(doc, text, size=10.5, bold=False, first_indent=True, space_after=4,
- align=None, color=None, left=0.0, line=1.5, font_cn="宋体"):
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.space_after = Pt(space_after)
- if left:
- pf.left_indent = Cm(left)
- if line == 1.5:
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- else:
- pf.line_spacing = line
- if align is not None:
- p.alignment = align
- if first_indent:
- pf.first_line_indent = Pt(21)
- run = p.add_run(text)
- set_cn_font(run, font_cn, "Times New Roman", size, bold, color)
- return p
- def rich(doc, chunks, size=10.5, first_indent=True, space_after=4, left=0.0):
- """chunks: list of (text, bold) tuples, all one paragraph."""
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.space_after = Pt(space_after)
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- if left:
- pf.left_indent = Cm(left)
- if first_indent:
- pf.first_line_indent = Pt(21)
- for text, bold in chunks:
- run = p.add_run(text)
- set_cn_font(run, "宋体" if not bold else "黑体", "Times New Roman", size, bold, None)
- return p
- def bullet(doc, text, size=10.5, level=0, space_after=2):
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.left_indent = Cm(0.75 + 0.6 * level)
- pf.first_line_indent = Cm(-0.45)
- pf.space_after = Pt(space_after)
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- run = p.add_run("· " if level == 0 else "– ")
- set_cn_font(run, "宋体", "Times New Roman", size)
- run2 = p.add_run(text)
- set_cn_font(run2, "宋体", "Times New Roman", size)
- return p
- def bullet_rich(doc, chunks, size=10.5, level=0, space_after=2):
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.left_indent = Cm(0.75 + 0.6 * level)
- pf.first_line_indent = Cm(-0.45)
- pf.space_after = Pt(space_after)
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- run = p.add_run("· " if level == 0 else "– ")
- set_cn_font(run, "宋体", "Times New Roman", size)
- for text, bold in chunks:
- r = p.add_run(text)
- set_cn_font(r, "宋体" if not bold else "黑体", "Times New Roman", size, bold, None)
- return p
- def heading(doc, text, level=1):
- h = doc.add_heading(level=level)
- h.paragraph_format.space_before = Pt({1: 18, 2: 14, 3: 10}[level])
- h.paragraph_format.space_after = Pt({1: 8, 2: 6, 3: 4}[level])
- run = h.add_run(text)
- set_cn_font(run, "黑体", "Times New Roman", {1: 16, 2: 13, 3: 11}[level], True, BLACK)
- return h
- def caption(doc, text):
- p = doc.add_paragraph()
- p.alignment = WD_ALIGN_PARAGRAPH.CENTER
- p.paragraph_format.space_before = Pt(8)
- p.paragraph_format.space_after = Pt(3)
- p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE
- run = p.add_run(text)
- set_cn_font(run, "黑体", "Times New Roman", 9, True)
- return p
- def note(doc, text):
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.space_before = Pt(4)
- pf.space_after = Pt(10)
- pf.left_indent = Cm(0.2)
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- run = p.add_run(text)
- set_cn_font(run, "楷体", "Times New Roman", 9.5, False, GREY)
- return p
- def quote(doc, lines):
- for ln in lines:
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.left_indent = Cm(0.9)
- pf.right_indent = Cm(0.4)
- pf.space_after = Pt(3)
- pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
- run = p.add_run(ln)
- set_cn_font(run, "楷体", "Times New Roman", 10)
- return
- def link_para(doc, pairs, size=9.5, left=0.75, space_after=2):
- """pairs: list of (label, url) rendered as 'label url' with url clickable."""
- p = doc.add_paragraph()
- pf = p.paragraph_format
- pf.left_indent = Cm(left)
- pf.first_line_indent = Cm(-0.45)
- pf.space_after = Pt(space_after)
- pf.line_spacing_rule = WD_LINE_SPACING.SINGLE
- r = p.add_run("· ")
- set_cn_font(r, "宋体", "Times New Roman", size)
- for label, url in pairs:
- rl = p.add_run(label + " ")
- set_cn_font(rl, "宋体", "Times New Roman", size)
- add_hyperlink(p, url, url.replace("https://", "").replace("http://", ""), size=size - 0.5)
- return p
- def toc(doc):
- hint = doc.add_paragraph()
- hint.paragraph_format.space_after = Pt(6)
- hr = hint.add_run("(本页为自动目录域。用 Word 打开时若提示更新域,选择「是」即可生成带页码的目录;也可按 Ctrl+A 后按 F9 手动更新。)")
- set_cn_font(hr, "楷体", "Times New Roman", 9, False, GREY)
- p = doc.add_paragraph()
- run = p.add_run()
- f1 = OxmlElement("w:fldChar"); f1.set(qn("w:fldCharType"), "begin"); f1.set(qn("w:dirty"), "true")
- it = OxmlElement("w:instrText"); it.set(qn("xml:space"), "preserve")
- it.text = r' TOC \o "1-2" \h \z \u '
- f2 = OxmlElement("w:fldChar"); f2.set(qn("w:fldCharType"), "separate")
- f3 = OxmlElement("w:fldChar"); f3.set(qn("w:fldCharType"), "end")
- run._r.append(f1); run._r.append(it); run._r.append(f2)
- ph = OxmlElement("w:r")
- pt = OxmlElement("w:t")
- pt.text = "目录将在此处生成"
- pt.set(qn("xml:space"), "preserve")
- ph.append(pt)
- run._r.append(ph)
- run._r.append(f3)
- return p
|