Files
bid-doctor/docs/design/logo/scripts/wordmark_compare.py
YANG JIANKUAN 5753b51131 docs(logo): 新增 logo v5.1 正式资产、使用规范与构建脚本
- 白文印「标」(AI 生成原图经脚本抠图、统一为 #D9291C、矢量化)
- 字标:思源宋体 SemiBold + Libre Caslon;比例 H2,英文视觉内缩 C2
- svg/、png/、favicon:横版、竖版、反白、单色、印章、各尺寸图标
- scripts/:logo_lib.py 统一排版参数,build_assets.sh 一键生成
- logo-spec.md、logo-sheet.html、字体许可证(OFL)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:28:18 +08:00

161 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""生成字标字体对比与印章高度对比(docs/design/logo/compare/)。
用法:.work/venv/bin/python scripts/wordmark_compare.py
字体文件在 .work/fonts/(不进 git),许可证见 compare/index.html 与 logo-spec.md。
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from logo_lib import CN_H, GAP, LOGO, NAVY, PY_CAP, WHITE, lockup, wordmark # noqa: E402
OUT = LOGO / "compare"
OUT.mkdir(exist_ok=True)
# (编号, 中文字体, 中文字重, 中文字距(em), 拼音字体, 拼音字重, 名称, 许可证, 说明)
PAIRS = [
("F1", "NotoSerifSC[wght].ttf", 900, 0.00, "Cinzel[wght].ttf", 600, "思源宋体 Black + Cinzel", "OFL", "现用。最粗的宋体,庄重、有分量"),
("F2", "NotoSerifSC[wght].ttf", 600, 0.04, "CormorantGaramond[wght].ttf", 600, "思源宋体 SemiBold + Cormorant Garamond", "OFL", "同一家族的细一档,更清秀,和印章的轻重对比更明显"),
("F3", "ZhuqueFangsong.ttf", None, 0.06, "CormorantSC-SemiBold.ttf", None, "朱雀仿宋 + Cormorant SC", "OFL", "仿宋,招标文件和公文的正文字体,文书感最强"),
("F4", "HuiwenMincho.ttf", None, 0.04, "LibreCaslonText[wght].ttf", 700, "汇文明朝体 + Libre Caslon", "授权需复核", "老铅字印刷的宋体,带油墨感,和印章最搭;授权说明不规范"),
("F5", "LXGWWenKai-Medium.ttf", None, 0.06, "EBGaramond[wght].ttf", 600, "霞鹜文楷 + EB Garamond", "OFL", "楷书,书卷气,亲切但不够硬朗"),
("F6", "ZCOOLXiaoWei-Regular.ttf", None, 0.04, "MarcellusSC-Regular.ttf", None, "站酷小薇 LOGO 体 + Marcellus SC", "OFL", "为标志设计的宋体,笔画更有个性;但「标」的木字旁写成带钩的连笔,容易被看成「扌」,不建议"),
("F7", "NotoSansSC[wght].ttf", 900, 0.02, "Montserrat[wght].ttf", 600, "思源黑体 Black + Montserrat", "OFL", "黑体,现代、稳重,和传统印章形成反差"),
("F8", "SmileySans-Oblique.ttf", None, 0.02, "SmileySans-Oblique.ttf", None, "得意黑(中英同一字体)", "OFL", "带倾斜的现代黑体,年轻、有速度感"),
]
# 1. 印章高度对比(字体 F1)
body, w, h = wordmark(PAIRS[0], NAVY)
cn_h = CN_H
variants = [
("H0", "现状:96%,上下没对齐", lockup(body, w, h, h * 0.961, (h - h * 0.961) / 2 + 0.6)),
("H1", "等高对齐:印章上下边对齐中文顶端和拼音底部", lockup(body, w, h, h, 0)),
("H2", "等高 + 光学补偿 2%:抵消印章颗粒边缘显虚", lockup(body, w, h, h * 1.02, -h * 0.01)),
("H3", "放大到 115%:图形主导", lockup(body, w, h, h * 1.15, -h * 0.075)),
("H4", "书法落款式:印章只与中文同高", lockup(body, w, h, cn_h, 0)),
]
for vid, _, svg in variants:
(OUT / f"height-{vid}.svg").write_text(svg)
# 2. 字体对比(统一用 H2)
for p in PAIRS:
for color, dark, suffix in ((NAVY, False, ""), (WHITE, True, "-reverse")):
body, w, h = wordmark(p, color)
(OUT / f"font-{p[0]}{suffix}.svg").write_text(lockup(body, w, h, h * 1.02, -h * 0.01, on_dark=dark))
# 3. 对比页
rows_h = "".join(f'<figure class="tile"><img src="height-{vid}.svg" alt="{t}"><figcaption><b>{vid}</b> {t}</figcaption></figure>'
for vid, t, _ in variants)
rows_f = "".join(
f'<section class="font"><h3>{p[0]} · {p[6]}</h3><p>{p[8]}<span class="lic{" warn" if p[7] != "OFL" else ""}">{p[7]}</span></p>'
f'<div class="pair"><div class="tile"><img src="font-{p[0]}.svg" alt="{p[6]} 浅色底"></div>'
f'<div class="tile dark"><img src="font-{p[0]}-reverse.svg" alt="{p[6]} 深色底"></div></div>'
f'<div class="small"><img src="font-{p[0]}.svg" width="200" alt=""><img src="font-{p[0]}.svg" width="120" alt=""></div></section>'
for p in PAIRS)
html = f"""<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>字标与比例对比</title><style>
:root{{--ink:#1D2733;--sub:#5C6670;--line:#D9DDD8}}
*{{box-sizing:border-box}}body{{margin:0;background:#F5F6F3;color:var(--ink);font:15px/1.7 "PingFang SC","Microsoft YaHei",system-ui,sans-serif}}
main{{max-width:1120px;margin:0 auto;padding:40px 24px 80px}}h1{{font-size:26px;margin:0 0 4px}}h2{{font-size:19px;margin:44px 0 6px}}h3{{font-size:16px;margin:0}}
p.note{{color:var(--sub);margin:0 0 18px;max-width:48em}}
.grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px}}
.tile{{background:#fff;border:1px solid var(--line);padding:28px;margin:0}}.tile.dark{{background:#1D2733;border-color:#1D2733}}
.tile img{{display:block;width:100%;height:auto}}figcaption{{font-size:13px;color:var(--sub);margin-top:12px}}
.font{{background:#fff;border:1px solid var(--line);padding:20px;margin:0 0 16px}}.font p{{margin:2px 0 12px;color:var(--sub);font-size:14px}}
.pair{{display:grid;grid-template-columns:1fr 1fr;gap:12px}}.pair .tile{{padding:20px}}
.small{{display:flex;gap:24px;align-items:center;margin-top:12px}}
.lic{{display:inline-block;margin-left:8px;font-size:12px;padding:0 6px;border:1px solid #2E6B4F;color:#2E6B4F;border-radius:2px}}.lic.warn{{border-color:#A66300;color:#A66300}}
@media (max-width:700px){{.pair{{grid-template-columns:1fr}}}}
</style></head><body><main>
<h1>字标与比例对比</h1><p class="note">两个问题:一、印章和右边文字的高度关系;二、字标字体。所有版本都用同一枚印章,文字已转成路径。</p>
<h2>一、印章高度</h2><p class="note">字体统一用 F1。H0 是现在的版本,H2 是建议的版本。</p>
<div class="grid">{rows_h}</div>
<h2>二、字标字体</h2><p class="note">比例统一用 H2。每组下面一行是 200px 和 120px 宽度的实际效果。</p>
{rows_f}
</main></body></html>"""
(OUT / "index.html").write_text(html)
print("ok", len(PAIRS), "fonts,", len(variants), "height variants")
# 4. 英文字体对比:中文固定为 F2(思源宋体 SemiBold),比例 H2
# (编号, 英文字体, 字重, 名称, 对应的经典字体 / 类型, 说明)
EN_OPTIONS = [
("E0", "CormorantGaramond[wght].ttf", 600, "Cormorant Garamond", "Garamond 类 · 古典衬线", "现选(F2)。纤细优雅,笔画对比高"),
("E1", "Cinzel[wght].ttf", 500, "Cinzel", "Trajan 类 · 罗马碑刻大写", "庄重、权威,金融和教育机构常用的气质"),
("E2", "MarcellusSC-Regular.ttf", None, "Marcellus SC", "Optima / Albertus 类 · 半衬线", "笔画末端微微张开,介于衬线和无衬线之间,和宋体很合拍"),
("E3", "TenorSans-Regular.ttf", None, "Tenor Sans", "Optima 类 · 人文无衬线", "无衬线但有书法韵味,现代而不冷"),
("E4", "EBGaramond[wght].ttf", 600, "EB Garamond", "Garamond · 古典衬线", "比 E0 更结实、更正统的 Garamond"),
("E5", "LibreCaslonText[wght].ttf", 600, "Libre Caslon", "Caslon · 老派衬线", "英国老字号和出版社的经典选择,沉稳"),
("E6", "SourceSerif4[opsz,wght].ttf", 600, "Source Serif 4", "与思源宋体同属 Adobe Source 家族", "和中文出自同一设计体系,最协调、最「工整」"),
("E7", "BodoniModa[opsz,wght].ttf", 600, "Bodoni Moda", "Didot / Bodoni 类 · 现代高对比", "时尚、精致,奢侈品和杂志常用"),
("E8", "Jost[wght].ttf", 500, "Jost", "Futura 类 · 几何无衬线", "现代、干净,和传统印章形成反差"),
]
cn = PAIRS[1] # F2
for eid, ef, ew, *_ in EN_OPTIONS:
pair = (eid, cn[1], cn[2], cn[3], ef, ew)
for color, dark, suffix in ((NAVY, False, ""), (WHITE, True, "-reverse")):
body, w, h = wordmark(pair, color)
(OUT / f"en-{eid}{suffix}.svg").write_text(lockup(body, w, h, h * 1.02, -h * 0.01, on_dark=dark))
rows_e = "".join(
f'<section class="font"><h3>{e[0]} · {e[3]}</h3><p>{e[4]}。{e[5]}<span class="lic">OFL</span></p>'
f'<div class="pair"><div class="tile"><img src="en-{e[0]}.svg" alt="{e[3]} 浅色底"></div>'
f'<div class="tile dark"><img src="en-{e[0]}-reverse.svg" alt="{e[3]} 深色底"></div></div>'
f'<div class="small"><img src="en-{e[0]}.svg" width="200" alt=""><img src="en-{e[0]}.svg" width="120" alt=""></div></section>'
for e in EN_OPTIONS)
(OUT / "english.html").write_text(html.split("<h1>")[0] + f"""<h1>英文字体对比(H2 + F2)</h1>
<p class="note">中文固定为思源宋体 SemiBold,比例固定为 H2,只换下方英文。全部是 Google Fonts 上的 SIL OFL 1.1 字体,可以免费商用、可以用在 logo 里。</p>
{rows_e}
</main></body></html>""")
print("ok", len(EN_OPTIONS), "english options")
# 5. 英文特写:只显示拼音这一行,放大比较字形
py_top, py_h = CN_H * (1 + GAP), CN_H * PY_CAP
for eid, ef, ew, *_ in EN_OPTIONS:
body, w, h = wordmark((eid, cn[1], cn[2], cn[3], ef, ew), NAVY)
(OUT / f"en-{eid}-detail.svg").write_text(
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 {py_top - 3:.1f} {w + 4:.1f} {py_h + 6:.1f}">{body}</svg>')
detail = "".join(f'<figure class="tile"><img src="en-{e[0]}-detail.svg" alt="{e[3]} 特写"><figcaption><b>{e[0]}</b> {e[3]}</figcaption></figure>'
for e in EN_OPTIONS)
page = (OUT / "english.html").read_text()
page = page.replace("</main>", f'<h2>英文特写</h2><p class="note">只看拼音这一行,放大比较字形细节。</p><div class="grid">{detail}</div></main>')
(OUT / "english.html").write_text(page)
print("ok detail")
# 6. 英文视觉对齐补偿(H2 + F2 + E5)
# 实测:中文两端只有细横画尖,英文两端是 B、I 的粗竖画;按墨迹浓度算英文左超 6.5、右超 7.7(中文字高 = 100)
E5 = next(e for e in EN_OPTIONS if e[0] == "E5")
ALIGN = [
("C0", (0.0, 0.0), "不补偿:几何两端对齐(现状)"),
("C1", (3.5, 4.0), "补偿一半:左 3.5、右 4.0"),
("C2", (5.0, 5.8), "补偿约 3/4:左 5.0、右 5.8"),
("C3", (6.5, 7.7), "完全补偿:按墨迹浓度边缘对齐,左 6.5、右 7.7"),
]
CN_VISUAL = (8.6, 315.4) # 中文「浓度 ≥ 15%」的视觉边缘(实测)
pair_e5 = ("E5", cn[1], cn[2], cn[3], E5[1], E5[2])
for cid, ins, _ in ALIGN:
for color, dark, suffix in ((NAVY, False, ""), (WHITE, True, "-reverse")):
body, w, h = wordmark(pair_e5, color, inset=ins)
(OUT / f"align-{cid}{suffix}.svg").write_text(lockup(body, w, h, h * 1.02, -h * 0.01, on_dark=dark))
# 带辅助线的字标特写:红线 = 中文视觉边缘,灰线 = 中文几何边界
body, w, h = wordmark(pair_e5, NAVY, inset=ins)
guides = (f'<line x1="0" y1="-4" x2="0" y2="{h + 4:.1f}" stroke="#999" stroke-width="0.6" stroke-dasharray="2 2"/>'
f'<line x1="{w:.1f}" y1="-4" x2="{w:.1f}" y2="{h + 4:.1f}" stroke="#999" stroke-width="0.6" stroke-dasharray="2 2"/>'
f'<line x1="{CN_VISUAL[0]}" y1="-4" x2="{CN_VISUAL[0]}" y2="{h + 4:.1f}" stroke="#D9291C" stroke-width="0.6"/>'
f'<line x1="{CN_VISUAL[1]}" y1="-4" x2="{CN_VISUAL[1]}" y2="{h + 4:.1f}" stroke="#D9291C" stroke-width="0.6"/>')
(OUT / f"align-{cid}-guides.svg").write_text(
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8 -8 {w + 16:.1f} {h + 16:.1f}"><rect x="-8" y="-8" width="{w + 16:.1f}" height="{h + 16:.1f}" fill="#fff"/>{body}{guides}</svg>')
rows_a = "".join(
f'<section class="font"><h3>{c[0]} · {c[2]}</h3>'
f'<div class="pair"><div class="tile"><img src="align-{c[0]}.svg" alt="{c[0]} 浅色底"></div>'
f'<div class="tile"><img src="align-{c[0]}-guides.svg" alt="{c[0]} 辅助线"></div></div>'
f'<div class="small"><img src="align-{c[0]}.svg" width="240" alt=""><img src="align-{c[0]}.svg" width="160" alt=""></div></section>'
for c in ALIGN)
(OUT / "alignment.html").write_text(html.split("<h1>")[0] + f"""<h1>英文视觉对齐补偿(H2 + F2 + E5)</h1>
<p class="note">右侧特写的辅助线:灰色虚线是中文的几何边界(最外的墨迹),红线是中文的视觉边缘(墨迹浓度达到行高 15% 的位置)。
补偿越多,英文越往红线靠。建议不看辅助线、只看左侧的整体效果做判断。</p>
{rows_a}
</main></body></html>""")
print("ok alignment")