127 lines
4.3 KiB
Python
127 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Embed Open Sans / Open Sans Light TTFs into a docx so it renders correctly
|
|
even on machines that don't have the fonts installed. Mirrors the (unobfuscated)
|
|
embedding scheme found in the Cloud Drift case-study docx: fontKey all-zeros,
|
|
plain .ttf parts referenced directly."""
|
|
import shutil
|
|
import zipfile
|
|
import re
|
|
import sys
|
|
|
|
DOCX = sys.argv[1] if len(sys.argv) > 1 else "clouddrift-reference.docx"
|
|
FONT_DIR = "fonts"
|
|
|
|
FONTS = {
|
|
"Open Sans Light": {
|
|
"regular": "OpenSansLight-regular.ttf",
|
|
"bold": "OpenSansLight-bold.ttf",
|
|
"italic": "OpenSansLight-italic.ttf",
|
|
"boldItalic": "OpenSansLight-boldItalic.ttf",
|
|
},
|
|
"Open Sans": {
|
|
"regular": "OpenSans-regular.ttf",
|
|
"bold": "OpenSans-bold.ttf",
|
|
"italic": "OpenSans-italic.ttf",
|
|
"boldItalic": "OpenSans-boldItalic.ttf",
|
|
},
|
|
}
|
|
|
|
NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
FONT_REL_TYPE = f"{NS_R}/font".replace(NS_R, "http://schemas.openxmlformats.org/officeDocument/2006/relationships") + "/font"
|
|
FONT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font"
|
|
|
|
|
|
def build_font_table_and_rels():
|
|
rid = 1
|
|
font_entries = []
|
|
rels = []
|
|
file_map = {}
|
|
for name, variants in FONTS.items():
|
|
embeds = []
|
|
for kind, fname in variants.items():
|
|
tag = {
|
|
"regular": "w:embedRegular",
|
|
"bold": "w:embedBold",
|
|
"italic": "w:embedItalic",
|
|
"boldItalic": "w:embedBoldItalic",
|
|
}[kind]
|
|
rId = f"rIdFont{rid}"
|
|
embeds.append(
|
|
f'<{tag} r:id="{rId}" w:fontKey="{{00000000-0000-0000-0000-000000000000}}" w:subsetted="0"/>'
|
|
)
|
|
rels.append(
|
|
f'<Relationship Id="{rId}" Type="{FONT_REL_TYPE}" Target="fonts/{fname}"/>'
|
|
)
|
|
file_map[rId] = fname
|
|
rid += 1
|
|
font_entries.append(f'<w:font w:name="{name}">{"".join(embeds)}</w:font>')
|
|
|
|
font_table_xml = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
'<w:fonts xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" '
|
|
'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
|
|
+ "".join(font_entries)
|
|
+ "</w:fonts>"
|
|
)
|
|
rels_xml = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
|
+ "".join(rels)
|
|
+ "</Relationships>"
|
|
)
|
|
return font_table_xml, rels_xml, file_map
|
|
|
|
|
|
def patch_settings(xml_text):
|
|
if "embedTrueTypeFonts" in xml_text:
|
|
return xml_text
|
|
return re.sub(
|
|
r"(<w:settings[^>]*>)",
|
|
r'\1<w:embedTrueTypeFonts w:val="1"/>',
|
|
xml_text,
|
|
count=1,
|
|
)
|
|
|
|
|
|
def patch_content_types(xml_text):
|
|
if 'Extension="ttf"' in xml_text:
|
|
return xml_text
|
|
return xml_text.replace(
|
|
"<Types ",
|
|
"<Types ",
|
|
).replace(
|
|
'<Default Extension="rels"',
|
|
'<Default Extension="ttf" ContentType="application/x-font-ttf"/><Default Extension="rels"',
|
|
)
|
|
|
|
|
|
def main():
|
|
font_table_xml, rels_xml, file_map = build_font_table_and_rels()
|
|
|
|
tmp = DOCX + ".tmp"
|
|
with zipfile.ZipFile(DOCX, "r") as zin:
|
|
names = zin.namelist()
|
|
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
for item in zin.infolist():
|
|
data = zin.read(item.filename)
|
|
if item.filename == "word/fontTable.xml":
|
|
data = font_table_xml.encode("utf-8")
|
|
elif item.filename == "word/settings.xml":
|
|
data = patch_settings(data.decode("utf-8")).encode("utf-8")
|
|
elif item.filename == "[Content_Types].xml":
|
|
data = patch_content_types(data.decode("utf-8")).encode("utf-8")
|
|
zout.writestr(item, data)
|
|
|
|
if "word/_rels/fontTable.xml.rels" not in names:
|
|
zout.writestr("word/_rels/fontTable.xml.rels", rels_xml)
|
|
|
|
for rId, fname in file_map.items():
|
|
with open(f"{FONT_DIR}/{fname}", "rb") as f:
|
|
zout.writestr(f"word/fonts/{fname}", f.read())
|
|
|
|
shutil.move(tmp, DOCX)
|
|
print("Embedded fonts into", DOCX)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|