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>
This commit is contained in:
YANG JIANKUAN
2026-11-02 20:28:18 +08:00
parent f4ef7af542
commit 5753b51131
51 changed files with 1160 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
"""生成 VI 手册里的标志构成与安全区标注图(docs/design/vi/diagrams/)。
标注数值直接取自 logo_lib 的实际几何,保证和正式资产一致。
用法:.work/venv/bin/python scripts/vi_diagrams.py
"""
from logo_lib import (FINAL, FINAL_INSET, LOGO, NAVY, SB, SEAL_GAP, SEAL_SCALE, SL, SR, ST, CN_H, GAP, PY_CAP,
seal_group, wordmark)
OUT = LOGO.parent / "vi/diagrams"
OUT.mkdir(parents=True, exist_ok=True)
BLUE = "#2F5BD3"
FONT = 'font-family="PingFang SC, Noto Sans SC, sans-serif"'
body, W, H = wordmark(FINAL, NAVY, inset=FINAL_INSET)
SEAL_H = H * SEAL_SCALE
SEAL_TOP = -(SEAL_H - H) / 2
SEAL_W = (SR - SL) * SEAL_H / (SB - ST)
GAP_X = SEAL_H * SEAL_GAP
TX = SEAL_W + GAP_X
def label(x, y, text, anchor="middle", size=7.5):
return f'<text x="{x:.1f}" y="{y:.1f}" fill="{BLUE}" font-size="{size}" text-anchor="{anchor}" {FONT}>{text}</text>'
def hline(x1, x2, y, dash=True):
d = ' stroke-dasharray="2 2"' if dash else ""
return f'<line x1="{x1:.1f}" y1="{y:.1f}" x2="{x2:.1f}" y2="{y:.1f}" stroke="{BLUE}" stroke-width="0.5"{d}/>'
def vline(x, y1, y2, dash=True):
d = ' stroke-dasharray="2 2"' if dash else ""
return f'<line x1="{x:.1f}" y1="{y1:.1f}" x2="{x:.1f}" y2="{y2:.1f}" stroke="{BLUE}" stroke-width="0.5"{d}/>'
def dim_v(x, y1, y2, text, side="left"):
"""竖向尺寸线,带两端短横。"""
t = 2.5
lx = x - 4 if side == "left" else x + 4
return (vline(x, y1, y2, dash=False) + hline(x - t, x + t, y1, dash=False) + hline(x - t, x + t, y2, dash=False)
+ label(lx, (y1 + y2) / 2 + 2.6, text, "end" if side == "left" else "start"))
def dim_h(x1, x2, y, text, below=False):
t = 2.5
ly = y + 9 if below else y - 3.5
return (hline(x1, x2, y, dash=False) + vline(x1, y - t, y + t, dash=False) + vline(x2, y - t, y + t, dash=False)
+ label((x1 + x2) / 2, ly, text))
def construction_h():
seal, _ = seal_group("clean", 0, SEAL_TOP, SEAL_H)
py_top = CN_H * (1 + GAP)
x_end = TX + W
parts = [seal, f'<g transform="translate({TX:.2f} 0)">{body}</g>']
# 文字块上下边界与印章边界
parts += [hline(-30, x_end + 12, 0), hline(-30, x_end + 12, H),
hline(-30, SEAL_W + 4, SEAL_TOP), hline(-30, SEAL_W + 4, SEAL_TOP + SEAL_H)]
parts.append(dim_v(-12, 0, H, "H"))
parts.append(dim_v(-26, SEAL_TOP, SEAL_TOP + SEAL_H, "1.02H"))
# 印章与文字间距
parts.append(dim_h(SEAL_W, TX, H + 18, "0.3 × 印章高", below=True))
parts += [vline(SEAL_W, SEAL_TOP + SEAL_H, H + 22), vline(TX, H, H + 22)]
# 字标内部:中文字高、间距、拼音高
rx = x_end + 10
parts.append(dim_v(rx, 0, CN_H, "中文字高 100", side="right"))
parts.append(dim_v(rx, CN_H, py_top, "间距 14", side="right"))
parts.append(dim_v(rx, py_top, H, "拼音 20", side="right"))
# 英文视觉内缩
parts += [vline(TX, py_top - 6, H + 4), vline(x_end, py_top - 6, H + 4)]
parts.append(vline(TX + FINAL_INSET[0], py_top - 6, H + 4))
parts.append(vline(x_end - FINAL_INSET[1], py_top - 6, H + 4))
parts.append(label(TX + 2.5, H + 12, f"内缩 {FINAL_INSET[0]:g}", "start", 6.5))
parts.append(label(x_end - 2.5, H + 12, f"内缩 {FINAL_INSET[1]:g}", "end", 6.5))
vb = (-60, SEAL_TOP - 14, x_end + 150, SEAL_H + 56)
return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{" ".join(f"{v:.1f}" for v in vb)}">{"".join(parts)}</svg>'
def construction_v():
seal_h = W * 0.5
seal, _ = seal_group("clean", (W - seal_h) / 2, 0, seal_h)
gap = seal_h * 0.18
y_wm = seal_h + gap
parts = [seal, f'<g transform="translate(0 {y_wm:.2f})">{body}</g>']
parts += [vline(0, -8, y_wm + H + 6), vline(W, -8, y_wm + H + 6)]
parts.append(dim_h(0, W, -14, "字标宽 W"))
parts.append(dim_h((W - seal_h) / 2, (W + seal_h) / 2, seal_h + 5, "0.5W", below=True))
parts.append(dim_v(W + 12, seal_h, y_wm, "0.18 × 印章边长", side="right"))
parts += [hline((W + seal_h) / 2, W + 14, seal_h), hline(W, W + 14, y_wm)]
vb = (-20, -30, W + 130, y_wm + H + 40)
return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{" ".join(f"{v:.1f}" for v in vb)}">{"".join(parts)}</svg>'
def clearspace():
seal, _ = seal_group("clean", 0, SEAL_TOP, SEAL_H)
m = H / 4
x0, y0 = -m, SEAL_TOP - m
x1, y1 = TX + W + m, SEAL_TOP + SEAL_H + m
parts = [f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" fill="#E7EDFB" stroke="{BLUE}" stroke-width="0.6" stroke-dasharray="3 2"/>',
f'<rect x="0" y="{SEAL_TOP:.1f}" width="{TX + W:.1f}" height="{SEAL_H:.1f}" fill="#fff"/>',
seal, f'<g transform="translate({TX:.2f} 0)">{body}</g>']
# 四边标注 H/4
parts.append(dim_h(x0, 0, SEAL_TOP + SEAL_H / 2, "H/4"))
parts.append(dim_h(TX + W, x1, SEAL_TOP + SEAL_H / 2, "H/4"))
parts.append(dim_v(TX + W / 2, y0, SEAL_TOP, "H/4", side="right"))
parts.append(dim_v(TX + W / 2, SEAL_TOP + SEAL_H, y1, "H/4", side="right"))
vb = (x0 - 10, y0 - 10, x1 - x0 + 20, y1 - y0 + 20)
return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{" ".join(f"{v:.1f}" for v in vb)}">{"".join(parts)}</svg>'
(OUT / "construction-horizontal.svg").write_text(construction_h())
(OUT / "construction-vertical.svg").write_text(construction_v())
(OUT / "clearspace.svg").write_text(clearspace())
print("ok", sorted(p.name for p in OUT.iterdir()))