ckb/.agents/skills/clouddrift-docx/scripts/embed_fonts.py
2026-07-15 09:54:12 +02:00

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()