恢复 BSD Socket API 使用指南(4e9ac0d 误删):同步当前 API,补分层架构图/状态机/时序图与 §2.3 模拟器端说明,README 索引补登
This commit is contained in:
@@ -379,6 +379,7 @@ pre-commit run --all-files
|
||||
| `docs/项目启动顺序管理.md` | Event Flags 就绪屏障设计 |
|
||||
| `docs/系统墙钟时间维护说明.md` | RTC + 墙钟时间子系统 |
|
||||
| `docs/网络配置说明.md` | 网口设置页配置(UI 可改的 IP/掩码/网关/MAC:持久化 + 立即生效 + 页面操作) |
|
||||
| `docs/BSD_Socket_API_使用指南.md` | BSD Socket API 使用指南(`net_socket` / `net_select`:线程安全 / 快速开始 / TCP / UDP / I/O 多路复用 / 事件回调 / API 参考 / 常见问题) |
|
||||
| `docs/UI 设计/UI_设计架构.md` | LVGL UI 系统架构(菜单 / 内容页 / 内存预算) |
|
||||
| `docs/UI 设计/菜单页面设计.md` | 菜单导航行为、数据流与内容页设计 |
|
||||
| `docs/UI 设计/UI_界面制作流程.md` | 参考图 → 设计规格 → LVGL 代码管线(P0~P4 门禁) |
|
||||
@@ -390,7 +391,7 @@ pre-commit run --all-files
|
||||
| `docs/代码规范/cppcheck使用说明.md` | cppcheck 规则、配置与误报抑制 |
|
||||
| `docs/测试说明/自动化测试流程.md` | 构建 → 烧录 → 串口校验闭环 |
|
||||
| `docs/测试说明/` | 各模块测试指南(CH395F / NAND / FTP / RTC / 串口调试) |
|
||||
| `docs/测试开发标准流程.md` / `docs/测试文档模板规范.md` | 测试开发流程与文档模板 |
|
||||
| `docs/测试说明/测试开发标准流程.md` / `docs/测试说明/测试文档模板规范.md` | 测试开发流程与文档模板 |
|
||||
| `docs/问题记录/` | 已知陷阱与解决方案(CH395F / NAND / RTC) |
|
||||
| `docs/芯片手册/` | 硬件数据手册 |
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1096" viewBox="0 0 1200 1096" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1200" height="1096" fill="#ffffff"/>
|
||||
<text x="26" y="40" font-size="20" font-weight="700" fill="#0f172a">BSD Socket API 分层架构(NET 网络栈)</text>
|
||||
<text x="26" y="62" font-size="13" fill="#64748b">CH395F 以太网协议栈芯片 · 消息队列串行化全部网络操作至 netTask · FreeRTOS V10.3.1</text>
|
||||
<rect x="26" y="78" width="1148" height="140" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.45"/>
|
||||
<text x="44" y="104" font-size="17" font-weight="700" fill="#2563eb">应用层任务</text>
|
||||
<text x="141" y="104" font-size="12" fill="#64748b">任意 FreeRTOS 任务可调用公共 API(线程安全)</text>
|
||||
<rect x="44" y="116" width="414" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
|
||||
<text x="251" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_send() / net_recv() / net_close()(线程安全封装)</text>
|
||||
<rect x="468" y="116" width="431" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
|
||||
<text x="684" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_select() / net_poll_events() / net_set_event_cb()</text>
|
||||
<rect x="44" y="164" width="601" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
|
||||
<text x="344" y="187" font-size="13" fill="#1e293b" text-anchor="middle">消费方:lftpd(FTP :21)· time_sync(对时 :8888)· ui_net_sink(远程显示 :7003)</text>
|
||||
<rect x="26" y="262" width="1148" height="140" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
|
||||
<text x="44" y="288" font-size="17" font-weight="700" fill="#7c3aed">消息队列</text>
|
||||
<text x="124" y="288" font-size="12" fill="#64748b">osMessageQueue · CubeMX 生成,在 MX_FREERTOS_Init() 中创建</text>
|
||||
<rect x="44" y="300" width="383" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="235" y="323" font-size="13" fill="#1e293b" text-anchor="middle">net_msg_t:send / recv / close / listen 四类请求</text>
|
||||
<rect x="437" y="300" width="383" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="629" y="323" font-size="13" fill="#1e293b" text-anchor="middle">调用方:入队请求 → 阻塞等待结果(ulTaskNotifyTake)</text>
|
||||
<rect x="44" y="348" width="423" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
|
||||
<text x="255" y="371" font-size="13" fill="#1e293b" text-anchor="middle">netTask 侧:出队 → 执行 → 写结果 → xTaskNotifyGive 通知</text>
|
||||
<rect x="26" y="446" width="1148" height="140" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
|
||||
<text x="44" y="472" font-size="17" font-weight="700" fill="#0d9488">netTask(网络任务)</text>
|
||||
<text x="227" y="472" font-size="12" fill="#64748b">1ms 周期 · 所有 CH395F 操作在此串行执行</text>
|
||||
<rect x="44" y="484" width="423" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
|
||||
<text x="255" y="507" font-size="13" fill="#1e293b" text-anchor="middle">net_poll() — CH395F 中断轮询 + Socket 状态机 + 事件回调</text>
|
||||
<rect x="477" y="484" width="324" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
|
||||
<text x="640" y="507" font-size="13" fill="#1e293b" text-anchor="middle">net_process_messages() — 消费消息队列请求</text>
|
||||
<rect x="44" y="532" width="667" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
|
||||
<text x="377" y="555" font-size="13" fill="#1e293b" text-anchor="middle">net_send_sock() / net_recv_sock() / net_close_sock() / net_listen_locked()(内部版本)</text>
|
||||
<rect x="26" y="630" width="1148" height="140" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
|
||||
<text x="44" y="656" font-size="17" font-weight="700" fill="#16a34a">net_socket.c(BSD Socket API 核心)</text>
|
||||
<text x="390" y="656" font-size="12" fill="#64748b">Socket 状态机 + CH395F 命令封装</text>
|
||||
<rect x="44" y="668" width="368" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<text x="228" y="691" font-size="13" fill="#1e293b" text-anchor="middle">8 个 Socket 控制块 s_net_socks[](状态机,无锁)</text>
|
||||
<rect x="422" y="668" width="475" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<text x="660" y="691" font-size="13" fill="#1e293b" text-anchor="middle">CH395F 命令序列封装(OPEN / TCP_LISTEN / CONNECT / SEND ...)</text>
|
||||
<rect x="44" y="716" width="482" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
|
||||
<text x="285" y="739" font-size="13" fill="#1e293b" text-anchor="middle">网络代次号(net_get_generation / net_generation_inc,服务自愈)</text>
|
||||
<rect x="26" y="814" width="1148" height="92" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.45"/>
|
||||
<text x="44" y="840" font-size="17" font-weight="700" fill="#d97706">ch395f.c/h(底层 SPI 驱动)</text>
|
||||
<text x="306" y="840" font-size="12" fill="#64748b">SPI2 · 不可重入</text>
|
||||
<rect x="44" y="852" width="449" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="268" y="875" font-size="13" fill="#1e293b" text-anchor="middle">ch395f_spi_begin() / ch395f_spi_end() — 包裹每次 SPI 事务</text>
|
||||
<rect x="503" y="852" width="358" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
|
||||
<text x="682" y="875" font-size="13" fill="#1e293b" text-anchor="middle">所有 SPI 事务仅能由 netTask 发起(独占,无重入)</text>
|
||||
<rect x="26" y="950" width="1148" height="92" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
|
||||
<text x="44" y="976" font-size="17" font-weight="700" fill="#e11d48">CH395F 硬件</text>
|
||||
<text x="159" y="976" font-size="12" fill="#64748b">以太网协议栈芯片</text>
|
||||
<rect x="44" y="988" width="448" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="268" y="1011" font-size="13" fill="#1e293b" text-anchor="middle">CH395F(SPI2:PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI)</text>
|
||||
<rect x="502" y="988" width="362" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
|
||||
<text x="683" y="1011" font-size="13" fill="#1e293b" text-anchor="middle">INT# 中断输出(低电平有效 · GPIO 轮询,无 EXTI)</text>
|
||||
<line x1="600.0" y1="224.0" x2="600.0" y2="247.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 600.0 254.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="600.0" y1="408.0" x2="600.0" y2="431.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 600.0 438.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="600.0" y1="592.0" x2="600.0" y2="615.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 600.0 622.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="600.0" y1="776.0" x2="600.0" y2="799.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 600.0 806.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<line x1="600.0" y1="912.0" x2="600.0" y2="935.0" stroke="#94a3b8" stroke-width="2.5"/>
|
||||
<path d="M 600.0 942.0 l -6 -9 h 12 z" fill="#94a3b8"/>
|
||||
<text x="26" y="1078" font-size="12" fill="#94a3b8">上层只依赖相邻下层(单向);消息队列 + netTask 串行化全部 CH395F 操作;模拟器端定义 NET_NO_OS 裁掉队列/回调(Winsock 后端)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/BSD网络栈分层.svg —— NET 网络栈分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
由 gen_arch_svg.py 复制改数据区:BSD Socket API 六层结构
|
||||
(docs/BSD_Socket_API_使用指南.md §2.1):
|
||||
应用层任务 → 消息队列 → netTask → net_socket 核心 → ch395f 驱动 → CH395F 硬件。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_net_stack_svg.py
|
||||
输出:
|
||||
docs/images/BSD网络栈分层.svg
|
||||
改架构时:编辑本文件顶部的 LAYERS / ROWS 数据后重跑即可。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images", "BSD网络栈分层.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1200
|
||||
PAD = 26
|
||||
TITLE_FONT = 20
|
||||
SUB_FONT = 13
|
||||
BAND_TITLE_FONT = 17
|
||||
BAND_SUB_FONT = 12
|
||||
CHIP_FONT = 13
|
||||
|
||||
CHIP_H = 38 # 芯片盒高
|
||||
CHIP_GAP = 10 # 芯片水平间距
|
||||
CHIP_ROW_GAP = 10 # 芯片行间距
|
||||
CHIP_PAD = 16 # 芯片内左右留白
|
||||
BAND_PAD_X = 18 # 框内左右留白
|
||||
BAND_TITLE_H = 38 # 框内标题区高
|
||||
BAND_PAD_BOTTOM = 16
|
||||
BAND_H_GAP = 26 # 同一行内并列框的间距
|
||||
BAND_V_GAP = 44 # 行间距(放箭头)
|
||||
TITLE_H = 78 # 顶部标题区高
|
||||
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
# ----------------------------- 架构数据(改这里) -----------------------------
|
||||
L_APP = dict(
|
||||
title="应用层任务", sub="任意 FreeRTOS 任务可调用公共 API(线程安全)", color="#2563eb", bg="#eff6ff",
|
||||
chips=["net_send() / net_recv() / net_close()(线程安全封装)",
|
||||
"net_select() / net_poll_events() / net_set_event_cb()",
|
||||
"消费方:lftpd(FTP :21)· time_sync(对时 :8888)· ui_net_sink(远程显示 :7003)"],
|
||||
)
|
||||
L_QUEUE = dict(
|
||||
title="消息队列", sub="osMessageQueue · CubeMX 生成,在 MX_FREERTOS_Init() 中创建", color="#7c3aed", bg="#f5f3ff",
|
||||
chips=["net_msg_t:send / recv / close / listen 四类请求",
|
||||
"调用方:入队请求 → 阻塞等待结果(ulTaskNotifyTake)",
|
||||
"netTask 侧:出队 → 执行 → 写结果 → xTaskNotifyGive 通知"],
|
||||
)
|
||||
L_NETTASK = dict(
|
||||
title="netTask(网络任务)", sub="1ms 周期 · 所有 CH395F 操作在此串行执行", color="#0d9488", bg="#f0fdfa",
|
||||
chips=["net_poll() — CH395F 中断轮询 + Socket 状态机 + 事件回调",
|
||||
"net_process_messages() — 消费消息队列请求",
|
||||
"net_send_sock() / net_recv_sock() / net_close_sock() / net_listen_locked()(内部版本)"],
|
||||
)
|
||||
L_CORE = dict(
|
||||
title="net_socket.c(BSD Socket API 核心)", sub="Socket 状态机 + CH395F 命令封装", color="#16a34a", bg="#f0fdf4",
|
||||
chips=["8 个 Socket 控制块 s_net_socks[](状态机,无锁)",
|
||||
"CH395F 命令序列封装(OPEN / TCP_LISTEN / CONNECT / SEND ...)",
|
||||
"网络代次号(net_get_generation / net_generation_inc,服务自愈)"],
|
||||
)
|
||||
L_DRV = dict(
|
||||
title="ch395f.c/h(底层 SPI 驱动)", sub="SPI2 · 不可重入", color="#d97706", bg="#fffbeb",
|
||||
chips=["ch395f_spi_begin() / ch395f_spi_end() — 包裹每次 SPI 事务",
|
||||
"所有 SPI 事务仅能由 netTask 发起(独占,无重入)"],
|
||||
)
|
||||
L_HW = dict(
|
||||
title="CH395F 硬件", sub="以太网协议栈芯片", color="#e11d48", bg="#fff1f2",
|
||||
chips=["CH395F(SPI2:PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI)",
|
||||
"INT# 中断输出(低电平有效 · GPIO 轮询,无 EXTI)"],
|
||||
)
|
||||
|
||||
# 每一行可放多个"同级并列"的层(上层只依赖下方各层)
|
||||
ROWS = [
|
||||
[L_APP],
|
||||
[L_QUEUE],
|
||||
[L_NETTASK],
|
||||
[L_CORE],
|
||||
[L_DRV],
|
||||
[L_HW],
|
||||
]
|
||||
|
||||
FOOT = "上层只依赖相邻下层(单向);消息队列 + netTask 串行化全部 CH395F 操作;模拟器端定义 NET_NO_OS 裁掉队列/回调(Winsock 后端)"
|
||||
|
||||
|
||||
# ----------------------------- 工具函数 -----------------------------
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角/圈码/箭头 ≈ 1.0em,其余 ≈ 0.58em。
|
||||
|
||||
取偏大估计(宁可芯片略宽,不可文字溢出盒外)。
|
||||
"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2460 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
def chip_w(label):
|
||||
return text_w(label, CHIP_FONT) + 2 * CHIP_PAD
|
||||
|
||||
|
||||
def wrap_chips(chips, avail):
|
||||
rows, cur, cur_w = [], [], 0.0
|
||||
for c in chips:
|
||||
cw = chip_w(c)
|
||||
need = cw if not cur else cur_w + CHIP_GAP + cw
|
||||
if cur and need > avail:
|
||||
rows.append(cur)
|
||||
cur, cur_w = [c], cw
|
||||
else:
|
||||
cur.append(c)
|
||||
cur_w = need
|
||||
if cur:
|
||||
rows.append(cur)
|
||||
return rows
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
# 布局:逐行排布(行内可并列多个框,行高取该行最高框)
|
||||
placements = [] # (layer, chip_rows, x, y, w, h)
|
||||
y = TITLE_H
|
||||
for row in ROWS:
|
||||
n = len(row)
|
||||
bw = (W - 2 * PAD - BAND_H_GAP * (n - 1)) // n
|
||||
bands = []
|
||||
for i, layer in enumerate(row):
|
||||
bx = PAD + i * (bw + BAND_H_GAP)
|
||||
avail = bw - 2 * BAND_PAD_X
|
||||
rows = wrap_chips(layer["chips"], avail)
|
||||
bh = BAND_TITLE_H + len(rows) * CHIP_H + (len(rows) - 1) * CHIP_ROW_GAP + BAND_PAD_BOTTOM
|
||||
bands.append([layer, rows, bx, y, bw, bh])
|
||||
row_h = max(b[5] for b in bands)
|
||||
for b in bands:
|
||||
b[5] = row_h # 同行等高(视觉"同级")
|
||||
placements.extend(bands)
|
||||
y += row_h + BAND_V_GAP
|
||||
total_h = y - BAND_V_GAP + 54
|
||||
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, 40, TITLE_FONT, esc("BSD Socket API 分层架构(NET 网络栈)")))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, 62, SUB_FONT,
|
||||
esc("CH395F 以太网协议栈芯片 · 消息队列串行化全部网络操作至 netTask · FreeRTOS V10.3.1")))
|
||||
|
||||
for (layer, rows, bx, by, bw, bh) in placements:
|
||||
# 框背景 + 边框
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" stroke="%s" '
|
||||
'stroke-opacity="0.45"/>' % (bx, by, bw, bh, layer["bg"], layer["color"]))
|
||||
# 标题(层名 + 目录/职责说明)
|
||||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||||
% (bx + BAND_PAD_X, by + 26, BAND_TITLE_FONT, layer["color"], esc(layer["title"])))
|
||||
tw = text_w(layer["title"], BAND_TITLE_FONT)
|
||||
add('<text x="%.0f" y="%d" font-size="%s" fill="#64748b">%s</text>'
|
||||
% (bx + BAND_PAD_X + tw + 12, by + 26, BAND_SUB_FONT, esc(layer["sub"])))
|
||||
# 芯片
|
||||
cy = by + BAND_TITLE_H
|
||||
for row in rows:
|
||||
cx = bx + BAND_PAD_X
|
||||
for c in row:
|
||||
cw = chip_w(c)
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.55"/>' % (cx, cy, cw, CHIP_H, layer["color"]))
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#1e293b" text-anchor="middle">%s</text>'
|
||||
% (cx + cw / 2, cy + CHIP_H / 2 + CHIP_FONT * 0.35, CHIP_FONT, esc(c)))
|
||||
cx += cw + CHIP_GAP
|
||||
cy += CHIP_H + CHIP_ROW_GAP
|
||||
|
||||
# 行间箭头:每个上层框向下引一条(顶层两框 → 两条,体现"都依赖下层")
|
||||
row_first = [] # 每行首个 placement 的下标
|
||||
idx = 0
|
||||
for row in ROWS:
|
||||
row_first.append(idx)
|
||||
idx += len(row)
|
||||
for r in range(len(ROWS) - 1):
|
||||
y0 = placements[row_first[r]][3] + placements[row_first[r]][5] + 6
|
||||
y1 = placements[row_first[r + 1]][3] - 8
|
||||
for i in range(row_first[r], row_first[r] + len(ROWS[r])):
|
||||
(_, _, bx, _, bw, _) = placements[i]
|
||||
ax = bx + bw / 2
|
||||
add('<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#94a3b8" '
|
||||
'stroke-width="2.5"/>' % (ax, y0, ax, y1 - 7))
|
||||
add('<path d="M %.1f %.1f l -6 -9 h 12 z" fill="#94a3b8"/>' % (ax, y1))
|
||||
|
||||
add('<text x="%d" y="%d" font-size="12" fill="#94a3b8">%s</text>'
|
||||
% (PAD, total_h - 18, esc(FOOT)))
|
||||
|
||||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
|
||||
% (W, total_h, W, total_h, FONT, "\n".join(out)))
|
||||
return svg, total_h
|
||||
|
||||
|
||||
def main():
|
||||
svg, h = build()
|
||||
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(svg)
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(svg)
|
||||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user