e4d70f9fcb
证据源 = 主树 ci-artifacts/archify/architecture-dtu-overview-20261030-095132/ evidence.md §11。该目录不入库,故把每条的判据与实测源抄进正文,别让证据只活在 一台机器上。处置口径:能改的改成与代码同源,核实后确认文档没问题的明确写「不改」。 判停点与就绪屏障(条 1、条 3) - AGENTS 原写 `protect_task.c:98` 直接停走判据:`:98` 是 `s_prev_cal_lock` 变量, 判停点是 `App/task/protect_task.c:268 if (adc_cal_prot_blocked() != 0u)`, 判定本体在标定层 `adc_cal_prot_blocked()`、protectTask 只是消费者 → 改引用, 同步 `ui_task.c:240` 页面注释与 `docs/任务规划/数据可信.md` 的同一处行号。 - `SYS_EVT_FTP_READY` 有生产者(`Src/freertos.c:490`,注释自陈预留)无消费者; `SYS_EVT_TIMESYNC_READY` 全仓无生产者也无消费者(只有定义与测试桩)。 AGENTS、`sys_events.h` 位注释、屏障配图(生成器 + 产物)三处从「四个就绪位 并列」改成「bit0/bit1 在用 + bit2/bit3 预留」,并指向 `docs/架构设计/项目启动顺序管理.md` §3.2 的逐位对照表。ftpTask 实际等的是 `NET|STORAGE`(`Src/freertos.c:477-479`,WaitAll|NoClear,100 ms 分片喂狗)。 /log 不是「三本」(条 5) - `App/log/sys_log_mcu.c` 的 `s_paths[]` = run / selftest / accid / alarm / start / oper / yk 七本 64B 定长文本 + `soe.log`(64B 定长二进制)+ `wdev.log` (变长取证行,`App/fs/vol_lock` 侧投,不走定长格式)。 → AGENTS 两处、总览 §2.1 与 §4 的「三本」改成「七本 + SOE(+ wdev 取证行)」。 主机单测 44 套 → 47 套(条 2,两把机器尺互证) - `scripts/check_test_coverage.py --list` 打印 47 行;`ls -d test/host/*/` = 48 个 一级目录,唯一没有 `run_unit_test.py` 的是 `common/` ⇒ 47 套。 - 差额三名 = 屏板链路套件:`sb_proto`、`sb_link`(2026-10-29 入库)、 `sb_led_map`(2026-10-30 入库)。专属钩子仍是 22 根 ⇒ 无专属钩子的从 22 套变 25 套。 - 同步面:AGENTS、`docs/门禁说明/README.md`、`host-unit-tests.md` §6 台账(名单补 三名)、`docs/代码规范/pre-commit使用说明.md`、`主机单测覆盖率口径.md`(含耗时句)、 `scripts/README.md`、总览目录树、`.pre-commit-config.yaml` 三处注释、 `test/host/sys_log_mcu/sys_log_mcu_test.c` 一处注释。 - 带日期戳的历史测量值按纪律冻结不动:28 套、41 套 ~105 s、40 套/18 套那三处。 本仓 LVGL 版本(条 6) - vendored 版本 = `Lib/lvgl/lv_version.h:9-11` 的 9.3.0;总览两处「LVGL v9.2」与 `gen_arch_svg.py` 图内文字改 v9.3 并重跑产物(脚本与产物同一笔,`generated-fresh` 逐字节核)。 - **刻意不动**:`docs/内存管理/RAM使用分析与优化方案.md` 两处「LVGL v9.2 文档」+ GitHub `release/v9.2` 分支 URL ——那是对外游某分支文档的引用,不是本仓版本声明。 触发口径与栈值(条 4、7、9、10) - ADC 映射 §1「ADC1 为软件触发」是旧口径:`Src/adc.c:53` 实为 `ADC_EXTERNALTRIGCONV_T3_TRGO` → 改成 TIM3 TRGO,并补一句「TIM3 与 TPAFE 的 CONVST 之间没有硬件耦合,仍不构成共同采样触发」,缺陷结论本身不变。 - 同文档 adcTask 栈 512 → 640 words(`Src/freertos.c:159 .stack_size = 640 * 4`)。 - `docs/UI设计/UI_设计架构.md` 的 uiTask 栈「16384 words」是单位错: 实为 4096 words = 16384 B(`Src/freertos.c:218`),顺手补上行号。 - `App/app/app_main.c:197` 注释「DE = PD0」陈旧:`:198` 实传 `ST_DIR4_*` (V0.1 实板 PA15,V0.0 才是 PD0)→ 注释改指 V0.1 网表报告的引脚迁移表。 - 两处已退役编号 `N-18`(ADC 映射、V0.1 网表报告)换成登记簿条目名 「双转换器无共同采样触发」。 核实后确认「文档没问题」的三条(条 8、12、13) - 黄金参考「菜单 8 状态 + 内容页 66 张」:`simulator/tests/ref/` 实测 66 个 `shot_*.bin`,与 `ui-golden` 钩子显示名一致 → 不改。 - `App/task/rs485_task.c` 存在但 `Src/freertos.c` 不创建它(grep 无 rs485): AGENTS 与总览都已写明「尚未接线 / 暂缓」,图侧画成「实现待接线」→ 不改。 - 104 端口 `:2404` 是工厂默认、可经 `/config.ini` `[comm]` 改 → 总览索引表补 「默认」二字;「uiTask 10 ms 节拍」核实 = `ui_tokens.h:65 UI_TASK_TICK_MS 10` ✓。 并行勘查会话的同族发现(它扫「文档声明数 vs 真值」,逐处独立复验后采纳 3 处) - `App/protocol/iec104/iec104_link.h` 头注释写「端口:TCP 2404(§1.5 B0 定义)」, 同文件下方已写「服务端口真源已迁 [comm] 段」= 自相矛盾 → 补「工厂默认」并指回 [comm] 与「下次重 bind 即生效」。 - `scripts/gen_task_table.py` 的 iec104Task 描述「:2404 监听」→「工厂默认 :2404 监听」,`--write` 重生成 README 任务表(AGENTS 表无描述列,实测未被改写)。 - `App/log/README.md` 五行写入点行号全漂:事故 181→188、告警 184→191、启动 176→183(`App/task/protect_task.c`);操作 `iec104_pts.c` 574/602→582/610、 `default_task.c` 242→266、`sys_log_mcu.c` 646→682;遥控 573/576/582/586/596/ 598/609 → 581/584/590/594/604/606/617(宿主 `sim_syslog.c:245` 原值正确、未动)。 - **撤销一条**:我早先记的「`docs/算法/继电保护功能复现规划.md:225` 声称记录本已 有消费者」与原文不符——:225 实为规划表里「动作/SOE 记录 | 复用 `sys_log`」一行, 属能力复用口径、不是当前态断言 → 不改。教训:**改引用前逐处读现文,别照自己 上一轮的笔记盲替**(这条比改中的每一处都值钱)。 - 同一批勘查还报了约 12 处「`2404` 缺『工厂默认』限定词」(AGENTS、`App/protocol/ README.md`、`Src/freertos.c` 注释、5 篇 docs、`test/host` 工具,含一张配图生成器 要连产物同批重跑)与 `测试开发标准流程.md`「像素层 60 内容页」、 `.pre-commit-config.yaml` uinav 注释「快检档 18 张黄金参考」(后者写时为真、 描述的却是钩子现有行为):**本笔刻意不做**,另开任务空间处理——那批要同时动 AGENTS.md 与 `.pre-commit-config.yaml` 两处单写者面,与并行会话撞车的代价更高。 - 端口生效语义(用户裁定 2026-10-30):产品基线写「公共地址与端口可配…改后需重启 生效」,代码 `App/task/iec104_serv.c` 是「ca 改后要重启;port 下次重 bind 才生效」 ——裁定**按代码语义要改**处理(改了端口应立刻重 bind),属固件行为变更,另开任务 且需真机验证;`.serialtest/config.json` 写死 `i104: listening on 2404` 必检锚点 是同题(现场改端口会让串口回归假失败),一并留给那条任务。 自查(任务树内) - `check_doc_links.py --sections-strict`:12 个改动 .md 逐个 rc=0; `check_mermaid.py` 改动 .md 全 rc=0;`check_generated_fresh.py` PASS (25 个生成器 / 21 一致 / 4 存量基线);`check_nested_comment.py --all` PASS (449 文件,新发 0、存量 2);`check_bom_drift.py --all` 99 个名单文件无漂移。 - 编译:`.dsh/skills/keil-build` 退出码 0(0 错误 0 警告), Program Size Code=246720 RO=29836 RW=2052 ZI=149708 —— 与本树凭证同码, 证明这批改的确实只有注释与文档。 - 字节体检:23 个文件无 CR / U+FFFD / 行尾空格 / 缺末行换行;`ui_task.c` 被编辑后 丢的 3 字节 BOM 已补回(bom-guard 复验通过);5 处「三连空行」逐文件与 HEAD 比对确认是存量(Python 生成器的 PEP8 双空行与个别 C 文件原有空行)。 - 一次性工具 `ci-artifacts/fix_docs2.py`:断言式规则表,按文件读一次、在演化文本上 逐条替换、一次写盘。第一版写成「每条规则各自从启动时快照起算」,同一文件后一条 把前一条整体覆盖,只有每文件最后一条活下来——靠「改完再全仓复扫旧形态」当场抓到, 这条判据本身已值得写进流程。 - 待办:`git add` 后需重跑 `ram_baseline.py --update`(体积凭证按 git 索引内容生成, 未 staged 的文件不在凭证里,`check_size_gate.py --local` 现在因此报红)。 - 未 commit、未 push(AGENTS.md 边界:提交属 Ask first)。
248 lines
11 KiB
Python
248 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""生成 docs/images/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
|
||
|
||
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
|
||
本脚本手工排布为三栏:
|
||
左 = 生产者(set) 中 = 事件组 sys_evtHandle 右 = 消费者(wait)
|
||
任务卡与事件位各自对齐到同一水平行,箭头基本水平/正交;bit1 → ftpTask 的
|
||
"与"依赖用汇合点(All)表达,避免跨行连线。
|
||
|
||
用法(仓库根目录,任意 python3):
|
||
python tools/arch_diagram/gen_barrier_svg.py
|
||
输出:
|
||
docs/images/就绪屏障.svg
|
||
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
|
||
"""
|
||
import html
|
||
import math
|
||
import os
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
OUT = os.path.join(ROOT, "docs", "images", "就绪屏障.svg")
|
||
|
||
# ----------------------------- 画布与尺寸 -----------------------------
|
||
W = 1400
|
||
PAD = 40
|
||
TITLE_FONT = 20
|
||
SUB_FONT = 13
|
||
CARD_TITLE_FONT = 15
|
||
CARD_NOTE_FONT = 12
|
||
BIT_TITLE_FONT = 14
|
||
BIT_NOTE_FONT = 12
|
||
LABEL_FONT = 11.5
|
||
FOOT_FONT = 12
|
||
|
||
H_CARD = 84 # 任务卡 / 事件位行高
|
||
GAP = 26 # 行间距
|
||
MAIN_Y = 200 # 首行 y
|
||
CARD_W_L = 290 # 生产者卡宽
|
||
CARD_W_R = 450 # 消费者卡宽
|
||
PANEL_X, PANEL_W = 400, 420
|
||
PROD_X = 40
|
||
CONS_X = 910
|
||
TAP_X = 832 # bit1 → All 汇合点的下引线 x
|
||
BUS_X = 862 # bit0 分发总线 x
|
||
ARROW_W = 2.2
|
||
|
||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||
|
||
C_PROD, BG_PROD = "#2563eb", "#eff6ff"
|
||
C_CONS, BG_CONS = "#7c3aed", "#f5f3ff"
|
||
|
||
# ----------------------------- 数据(改这里) -----------------------------
|
||
# 事件位(中间栏),自上而下 = 时序先后
|
||
BITS = [
|
||
dict(bit="bit1", flag="STORAGE_READY", color="#16a34a", bg="#f0fdf4",
|
||
note="defaultTask 置位(挂载+配置)· netTask / ftpTask 等待"),
|
||
dict(bit="bit0", flag="NET_READY", color="#0d9488", bg="#f0fdfa",
|
||
note="netTask 置位 · 5 个任务等待"),
|
||
dict(bit="bit2", flag="FTP_READY", color="#d97706", bg="#fffbeb",
|
||
note="ftpTask 置位 · 预留消费者"),
|
||
dict(bit="bit3", flag="TIMESYNC_READY", color="#94a3b8", bg="#f8fafc",
|
||
note="预留(无生产者置位)"),
|
||
]
|
||
|
||
# 生产者(左栏):row = 其置位的 bit 行
|
||
PRODUCERS = [
|
||
dict(name="defaultTask", note="卷挂载 + net_cfg_load(f_mount)", row=0, label="set bit1"),
|
||
dict(name="netTask", note="net_stack_bring_up()", row=1, label="set bit0"),
|
||
dict(name="ftpTask", note='lftpd_start("/", 21)', row=2, label="set bit2"),
|
||
]
|
||
|
||
# 消费者(右栏):row = 卡片排布行(前两行与 bit1 / bit0 对齐)
|
||
CONSUMERS = [
|
||
dict(name="netTask", note="wait bit1 · ≤10s", row=0),
|
||
dict(name="ftpTask", note="wait bit0 + bit1 · All", row=1),
|
||
dict(name="uiTask", note="wait bit0 · Forever", row=2),
|
||
dict(name="timeSyncTask", note="wait bit0 · Forever", row=3),
|
||
dict(name="iec104Task", note="wait bit0 · Forever", row=4),
|
||
dict(name="testTask", note="wait bit0 · NET 模式", row=5),
|
||
]
|
||
|
||
FOOT = ("生产者与消费者互不知晓,只经事件组解耦;新增子系统 = 新 bit + 各自 wait/set,"
|
||
"不改已有任务。详见 docs/架构设计/项目启动顺序管理.md")
|
||
|
||
|
||
# ----------------------------- 工具函数 -----------------------------
|
||
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) >= 0x2E80 else 0.58)
|
||
return w
|
||
|
||
|
||
# ----------------------------- 生成 -----------------------------
|
||
def build():
|
||
out = []
|
||
add = out.append
|
||
|
||
def row_y(r):
|
||
return MAIN_Y + r * (H_CARD + GAP)
|
||
|
||
def row_cy(r):
|
||
return row_y(r) + H_CARD / 2.0
|
||
|
||
def poly(points, color, dash=False, head=True, width=ARROW_W):
|
||
pts = " ".join("%.1f,%.1f" % p for p in points)
|
||
d = ' stroke-dasharray="7 5"' if dash else ''
|
||
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
|
||
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
|
||
if head:
|
||
(x1, y1), (x2, y2) = points[-2], points[-1]
|
||
ang = math.atan2(y2 - y1, x2 - x1)
|
||
hl, hw = 11.0, 5.5
|
||
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
|
||
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
|
||
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
|
||
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
|
||
|
||
def card(x, y, w, title, note, color, bg):
|
||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
|
||
'stroke="%s" stroke-opacity="0.5"/>' % (x, y, w, H_CARD, bg, color))
|
||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||
% (x + 16, y + 34, CARD_TITLE_FONT, esc(title)))
|
||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||
% (x + 16, y + 58, CARD_NOTE_FONT, esc(note)))
|
||
|
||
# 行/画布尺寸
|
||
n_rows = max(len(CONSUMERS), len(BITS))
|
||
main_bottom = row_y(n_rows - 1) + H_CARD
|
||
total_h = main_bottom + 78
|
||
|
||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||
|
||
# ---- 标题 ----
|
||
add('<text x="%d" y="42" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||
% (PAD, TITLE_FONT, esc("就绪屏障(Readiness Barrier)· 启动依赖链")))
|
||
add('<text x="%d" y="68" font-size="%d" fill="#64748b">%s</text>'
|
||
% (PAD, SUB_FONT,
|
||
esc("CMSIS-RTOS2 Event Flags · 生产者置位声明「我好了」,消费者阻塞等待")))
|
||
|
||
# ---- 图例 ----
|
||
lx, ly = PAD, 108
|
||
for kind, color, label in [
|
||
("rect", C_PROD, "生产者(set)"),
|
||
("rect", C_CONS, "消费者(wait)"),
|
||
("rect", "#16a34a", "事件位(Event Flag)"),
|
||
("dot", "#0d9488", "All 汇合(等全部位)"),
|
||
("dash", "#94a3b8", "预留 / 未接入"),
|
||
]:
|
||
if kind == "rect":
|
||
add('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="#ffffff" '
|
||
'stroke="%s" stroke-width="2"/>' % (lx, ly - 12, color))
|
||
elif kind == "dot":
|
||
add('<circle cx="%d" cy="%d" r="6" fill="%s"/>' % (lx + 8, ly - 4, color))
|
||
else:
|
||
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2.4" '
|
||
'stroke-dasharray="7 5"/>' % (lx, ly - 4, lx + 16, ly - 4, color))
|
||
lx += 24
|
||
add('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>' % (lx, ly, esc(label)))
|
||
lx += text_w(label, 12) + 34
|
||
|
||
# ---- 事件组面板 ----
|
||
py0 = MAIN_Y - 34
|
||
py1 = row_y(len(BITS) - 1) + H_CARD + 14
|
||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
|
||
'stroke="#64748b" stroke-opacity="0.35"/>' % (PANEL_X, py0, PANEL_W, py1 - py0))
|
||
head = "sys_evtHandle(Event Flags 事件组)"
|
||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#334155">%s</text>'
|
||
% (PANEL_X + 16, py0 + 26, esc(head)))
|
||
for i, b in enumerate(BITS):
|
||
by = row_y(i)
|
||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" stroke="%s" '
|
||
'stroke-opacity="0.6"/>' % (PANEL_X + 14, by, PANEL_W - 28, H_CARD, b["bg"], b["color"]))
|
||
add('<rect x="%d" y="%d" width="30" height="30" rx="7" fill="%s"/>'
|
||
% (PANEL_X + 28, by + (H_CARD - 30) / 2, b["color"]))
|
||
add('<text x="%d" y="%d" font-size="14" font-weight="700" fill="#ffffff" '
|
||
'text-anchor="middle">%s</text>'
|
||
% (PANEL_X + 43, by + H_CARD / 2 + 5, esc(b["bit"][3])))
|
||
tx = PANEL_X + 70
|
||
add('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||
% (tx, by + 36, BIT_TITLE_FONT, b["color"], esc(b["bit"] + " · " + b["flag"])))
|
||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||
% (tx, by + 58, BIT_NOTE_FONT, esc(b["note"])))
|
||
|
||
# ---- 生产者卡 + set 箭头 ----
|
||
for p in PRODUCERS:
|
||
y, cy = row_y(p["row"]), row_cy(p["row"])
|
||
card(PROD_X, y, CARD_W_L, p["name"], p["note"], C_PROD, BG_PROD)
|
||
poly([(PROD_X + CARD_W_L, cy), (PANEL_X, cy)], BITS[p["row"]]["color"])
|
||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||
% ((PROD_X + CARD_W_L + PANEL_X) / 2.0, cy - 8, LABEL_FONT,
|
||
BITS[p["row"]]["color"], esc(p["label"])))
|
||
|
||
# ---- 消费者卡 ----
|
||
for c in CONSUMERS:
|
||
card(CONS_X, row_y(c["row"]), CARD_W_R, c["name"], c["note"], C_CONS, BG_CONS)
|
||
|
||
# ---- wait 连线:bit1 → netTask(水平) ----
|
||
c0, c1 = row_cy(0), row_cy(1)
|
||
col1, col0 = BITS[0]["color"], BITS[1]["color"]
|
||
poly([(PANEL_X + PANEL_W, c0), (CONS_X, c0)], col1)
|
||
add('<text x="%.0f" y="%.1f" font-size="%s" fill="%s" text-anchor="middle">%s</text>'
|
||
% ((PANEL_X + PANEL_W + CONS_X) / 2.0, c0 - 8, LABEL_FONT, col1, esc("wait")))
|
||
|
||
# ---- wait 连线:bit1 → All 汇合点(下引,与 bit0 相与) ----
|
||
poly([(TAP_X, c0), (TAP_X, c1)], col1, head=False)
|
||
add('<circle cx="%d" cy="%.1f" r="5.5" fill="%s"/>' % (TAP_X, c1, col0))
|
||
add('<text x="%d" y="%.1f" font-size="%s" font-weight="700" fill="%s">All</text>'
|
||
% (TAP_X + 9, c1 - 8, LABEL_FONT, col0))
|
||
|
||
# ---- wait 连线:bit0 → 总线 → 各 bit0 消费者(ftpTask/ui/timeSync/iec104/testTask) ----
|
||
c_last = row_cy(len(CONSUMERS) - 1)
|
||
poly([(PANEL_X + PANEL_W, c1), (BUS_X, c1)], col0)
|
||
poly([(BUS_X, c1), (BUS_X, c_last)], col0, head=False)
|
||
for c in CONSUMERS[1:]:
|
||
poly([(BUS_X, row_cy(c["row"])), (CONS_X, row_cy(c["row"]))], col0)
|
||
mid_y = (c1 + c_last) / 2.0
|
||
add('<text transform="rotate(-90 %.1f %.1f)" x="%.1f" y="%.1f" font-size="%s" '
|
||
'fill="%s" text-anchor="middle">%s</text>'
|
||
% (BUS_X - 12, mid_y, BUS_X - 12, mid_y, LABEL_FONT, col0, esc("wait bit0")))
|
||
|
||
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
|
||
% (PAD, total_h - 18, FOOT_FONT, 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()
|