mirror of
https://github.com/jacobinwwey/obsidian-NotEMD.git
synced 2026-07-22 05:48:27 +00:00
feat: honor png trns render smoke
This commit is contained in:
parent
bdb6675629
commit
67dd505e1d
10 changed files with 225 additions and 32 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -126,7 +126,7 @@ node scripts/export-circuitikz.js \
|
|||
--expected-svg-text v_{out}
|
||||
```
|
||||
|
||||
For `.png` screenshot artifacts, the smoke check decodes non-interlaced 1/2/4/8-bit indexed-color PNG output, 1/2/4/8/16-bit grayscale PNG output, and 8/16-bit RGB, grayscale-alpha, or RGBA PNG output. Indexed-color and sub-byte grayscale images support packed samples; indexed-color images also support PLTE palette entries and optional tRNS alpha. 16-bit direct samples are normalized into the same 8-bit RGBA comparison space used by the smoke checks. It verifies positive dimensions, records the foreground pixel bounding box as `foregroundBounds`, records foreground density inside that box as `foregroundDensity`, and requires at least one pixel that differs from the top-left background color. Blank screenshots fail with `render-png-blank`; foreground content that touches the image boundary fails with `render-png-content-clipped`; foreground pixels that are unusually dense inside a non-trivial bounding box fail with `render-png-foreground-dense`; malformed or unsupported PNGs fail with `render-png-invalid` or `render-png-unsupported`.
|
||||
For `.png` screenshot artifacts, the smoke check decodes non-interlaced 1/2/4/8-bit indexed-color PNG output, 1/2/4/8/16-bit grayscale PNG output, and 8/16-bit RGB, grayscale-alpha, or RGBA PNG output. Indexed-color and sub-byte grayscale images support packed samples; indexed-color images also support PLTE palette entries and optional tRNS alpha; grayscale/RGB images support tRNS transparent samples. 16-bit direct samples are normalized into the same 8-bit RGBA comparison space used by the smoke checks. It verifies positive dimensions, records the foreground pixel bounding box as `foregroundBounds`, records foreground density inside that box as `foregroundDensity`, and requires at least one pixel that differs from the top-left background color. Blank screenshots fail with `render-png-blank`; foreground content that touches the image boundary fails with `render-png-content-clipped`; foreground pixels that are unusually dense inside a non-trivial bounding box fail with `render-png-foreground-dense`; malformed or unsupported PNGs fail with `render-png-invalid` or `render-png-unsupported`.
|
||||
|
||||
The result is recorded as `compileExecution.renderSmoke`. Missing or empty artifacts add `render-artifact-missing` or `render-artifact-empty`; SVG structure failures add diagnostics such as `render-svg-invalid`, `render-svg-dimension-missing`, `render-svg-no-visible-elements`, `render-svg-text-missing`, `render-svg-text-path-only`, `render-svg-out-of-bounds`, `render-svg-text-overlap`, `render-svg-label-overlap`, or `render-svg-path-glyph-overlap`; PNG screenshot failures add diagnostics such as `render-png-blank`, `render-png-content-clipped`, or `render-png-foreground-dense`.
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ node scripts/export-circuitikz.js \
|
|||
--expected-svg-text v_{out}
|
||||
```
|
||||
|
||||
对于 `.png` screenshot artifact,smoke check 会解码非交错的 1/2/4/8-bit indexed-color PNG 输出、1/2/4/8/16-bit grayscale PNG 输出,以及 8/16-bit RGB、grayscale-alpha 或 RGBA PNG 输出。indexed-color 与 sub-byte grayscale 图像支持 packed samples;indexed-color 图像还支持 PLTE palette entries 与可选 tRNS alpha。16-bit direct samples 会被归一化到 smoke check 使用的同一个 8-bit RGBA 比较空间。它会检查正的尺寸,把前景像素包围盒记录为 `foregroundBounds`,把包围盒内前景密度记录为 `foregroundDensity`,并要求至少一个不同于左上角背景色的像素。空白截图会以 `render-png-blank` 失败;前景内容贴到图像边界会以 `render-png-content-clipped` 失败;非平凡包围盒内前景像素异常密集会以 `render-png-foreground-dense` 失败;格式损坏或不支持的 PNG 会以 `render-png-invalid` 或 `render-png-unsupported` 失败。
|
||||
对于 `.png` screenshot artifact,smoke check 会解码非交错的 1/2/4/8-bit indexed-color PNG 输出、1/2/4/8/16-bit grayscale PNG 输出,以及 8/16-bit RGB、grayscale-alpha 或 RGBA PNG 输出。indexed-color 与 sub-byte grayscale 图像支持 packed samples;indexed-color 图像还支持 PLTE palette entries 与可选 tRNS alpha;grayscale/RGB 图像支持 tRNS transparent samples。16-bit direct samples 会被归一化到 smoke check 使用的同一个 8-bit RGBA 比较空间。它会检查正的尺寸,把前景像素包围盒记录为 `foregroundBounds`,把包围盒内前景密度记录为 `foregroundDensity`,并要求至少一个不同于左上角背景色的像素。空白截图会以 `render-png-blank` 失败;前景内容贴到图像边界会以 `render-png-content-clipped` 失败;非平凡包围盒内前景像素异常密集会以 `render-png-foreground-dense` 失败;格式损坏或不支持的 PNG 会以 `render-png-invalid` 或 `render-png-unsupported` 失败。
|
||||
|
||||
检查结果会记录为 `compileExecution.renderSmoke`。缺失或空 artifact 会追加 `render-artifact-missing` 或 `render-artifact-empty`;SVG 结构失败会追加 `render-svg-invalid`、`render-svg-dimension-missing`、`render-svg-no-visible-elements`、`render-svg-text-missing`、`render-svg-text-path-only`、`render-svg-out-of-bounds`、`render-svg-text-overlap`、`render-svg-label-overlap` 或 `render-svg-path-glyph-overlap` 等 diagnostic;PNG screenshot 失败会追加 `render-png-blank`、`render-png-content-clipped` 或 `render-png-foreground-dense` 等 diagnostic。
|
||||
|
||||
|
|
|
|||
|
|
@ -146,12 +146,12 @@ Phase A is documented. Phase B/C now have a constrained repository prototype:
|
|||
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`, `--repair-brief-output`, and `--repair-brief` produce and consume schema `notemd.circuitikz.repair-brief.v1`, a topology-preserving repair handoff that carries the source `CircuitSpec`, topology signature, compile/render diagnostics, allowed changes, prohibited topology changes, and next verification steps. Candidate specs can be checked against the brief signature before output is written.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzDiagnostics.ts` parses existing LaTeX/TikZJax compile logs into actionable diagnostics without spawning a compiler or depending on shell command resolution.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzCompileRunner.ts` can run an explicitly configured local renderer with `shell: false`, placeholder-expanded arguments, structured executable diagnostics for empty, missing, or invalid renderer commands, generated-log diagnostics, and optional `--expected-artifact` render-smoke checks.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRenderSmoke.ts` inspects expected render artifacts. PDF and other opaque artifacts are checked for existence/non-empty output; SVG artifacts are additionally checked for an `<svg>` root, positive dimensions or `viewBox`, visible drawing elements, optional repeated `--expected-svg-text` tokens, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement through resolved `<use href="#...">` geometry, definition-local transforms on reusable glyph paths, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry, positioned `tspan` label geometry, bounded-canvas/text-overlap diagnostics, label-vs-drawing overlap diagnostics through `render-svg-label-overlap`, and transform-aware geometry for common group and element transforms; PNG screenshot artifacts are decoded for positive dimensions, non-background pixels, 1/2/4/8-bit indexed-color packed samples with PLTE/tRNS palette data, 1/2/4/8/16-bit grayscale samples, 8/16-bit grayscale-alpha/RGB/RGBA direct samples, foreground bounds, `foregroundDensity`, edge-touching clipped content through `render-png-content-clipped`, and unusually dense foreground blocks through `render-png-foreground-dense`.
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRenderSmoke.ts` inspects expected render artifacts. PDF and other opaque artifacts are checked for existence/non-empty output; SVG artifacts are additionally checked for an `<svg>` root, positive dimensions or `viewBox`, visible drawing elements, optional repeated `--expected-svg-text` tokens, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement through resolved `<use href="#...">` geometry, definition-local transforms on reusable glyph paths, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry, positioned `tspan` label geometry, bounded-canvas/text-overlap diagnostics, label-vs-drawing overlap diagnostics through `render-svg-label-overlap`, and transform-aware geometry for common group and element transforms; PNG screenshot artifacts are decoded for positive dimensions, non-background pixels, 1/2/4/8-bit indexed-color packed samples with PLTE/tRNS palette data, grayscale/RGB tRNS transparent samples, 1/2/4/8/16-bit grayscale samples, 8/16-bit grayscale-alpha/RGB/RGBA direct samples, foreground bounds, `foregroundDensity`, edge-touching clipped content through `render-png-content-clipped`, and unusually dense foreground blocks through `render-png-foreground-dense`.
|
||||
- `src/rendering/diagnostics.ts` summarizes `RenderArtifact.diagnostics` into error/warning/info counts, and `src/ui/DiagramPreviewModal.ts` uses that summary in both the artifact diagnostics panel and preview history entries. Any render target can attach diagnostics to `RenderArtifact.diagnostics`; the preview UI shows the summary, severity, kind, message, and advice without requiring TikZJax or LaTeX in the plugin runtime.
|
||||
- `src/tests/circuitikzExporter.test.ts`, `src/tests/circuitikzRepairBrief.test.ts`, `src/tests/circuitikzCompileDiagnostics.test.ts`, `src/tests/circuitikzRenderSmoke.test.ts`, `src/tests/circuitikzCompileRunner.test.ts`, and `src/tests/circuitikzExportCli.test.ts` verify deterministic output, topology rejection, topology-preserving repair brief generation and candidate validation, package-script exposure, UTF-8 BOM handling, diagnostic parsing, shell-free compile execution, structured `compile-executable-invalid` / `compile-executable-not-found` diagnostics, diagnostics JSON output, render artifact existence/non-empty smoke checks, SVG structure checks, path-only SVG label classification, path-only glyph placement checks, definition-local glyph transform checks, path-only glyph overlap diagnostics, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry checks, transform-aware SVG geometry checks, SVG label-vs-drawing overlap checks, PNG blank screenshot checks, indexed-color and grayscale PNG packed sample decoding, indexed-color PNG palette decoding, 16-bit direct PNG sample normalization, PNG foreground-bound and foreground-density reporting, PNG clipped-content diagnostics, dense-foreground diagnostics, and nonzero CLI exit for logs or smoke reports with errors.
|
||||
- `src/tests/circuitikzExporter.test.ts`, `src/tests/circuitikzRepairBrief.test.ts`, `src/tests/circuitikzCompileDiagnostics.test.ts`, `src/tests/circuitikzRenderSmoke.test.ts`, `src/tests/circuitikzCompileRunner.test.ts`, and `src/tests/circuitikzExportCli.test.ts` verify deterministic output, topology rejection, topology-preserving repair brief generation and candidate validation, package-script exposure, UTF-8 BOM handling, diagnostic parsing, shell-free compile execution, structured `compile-executable-invalid` / `compile-executable-not-found` diagnostics, diagnostics JSON output, render artifact existence/non-empty smoke checks, SVG structure checks, path-only SVG label classification, path-only glyph placement checks, definition-local glyph transform checks, path-only glyph overlap diagnostics, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry checks, transform-aware SVG geometry checks, SVG label-vs-drawing overlap checks, PNG blank screenshot checks, indexed-color and grayscale PNG packed sample decoding, indexed-color PNG palette decoding, grayscale/RGB PNG tRNS transparent sample handling, 16-bit direct PNG sample normalization, PNG foreground-bound and foreground-density reporting, PNG clipped-content diagnostics, dense-foreground diagnostics, and nonzero CLI exit for logs or smoke reports with errors.
|
||||
- `src/tests/diagramPreviewModal.test.ts` verifies that artifact diagnostics are visible in the diagram preview modal and that preview history does not collapse entries with different diagnostics.
|
||||
|
||||
Phase D now has log parsing, opt-in local renderer execution, artifact-level smoke checks, SVG structure smoke checks with transform-aware geometry, path-only label classification, path-only glyph placement checks, path-only glyph overlap diagnostics, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` geometry coverage, positioned `tspan` label geometry, text/text and label/drawing overlap diagnostics, first PNG screenshot nonblank, indexed-color and grayscale packed-sample decoding, 16-bit direct sample normalization, edge-clipping, and dense-foreground checks, a topology-preserving repair guard, a topology-preserving repair brief handoff with candidate validation, and a front-end diagnostics surface, but the implementation still deliberately stops before required renderer availability, OCR recognition for path-only glyph text, precise pixel-level overlap detection, full SVG path coverage, and automated Phase E repair execution. It does not bundle LaTeX, make TikZJax a plugin runtime dependency, or run a visual repair loop.
|
||||
Phase D now has log parsing, opt-in local renderer execution, artifact-level smoke checks, SVG structure smoke checks with transform-aware geometry, path-only label classification, path-only glyph placement checks, path-only glyph overlap diagnostics, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` geometry coverage, positioned `tspan` label geometry, text/text and label/drawing overlap diagnostics, first PNG screenshot nonblank, indexed-color and grayscale packed-sample decoding, grayscale/RGB tRNS transparent sample handling, 16-bit direct sample normalization, edge-clipping, and dense-foreground checks, a topology-preserving repair guard, a topology-preserving repair brief handoff with candidate validation, and a front-end diagnostics surface, but the implementation still deliberately stops before required renderer availability, OCR recognition for path-only glyph text, precise pixel-level overlap detection, full SVG path coverage, and automated Phase E repair execution. It does not bundle LaTeX, make TikZJax a plugin runtime dependency, or run a visual repair loop.
|
||||
|
||||
The SVG geometry smoke also covers SVG number grammar for leading-dot decimals and explicit plus signs. This is a small but important renderer-compatibility gate because dvisvgm can emit compact decimals that should remain fractional during bounds checks.
|
||||
|
||||
|
|
|
|||
|
|
@ -146,12 +146,12 @@ Phase A 已经文档化。Phase B/C 现在有了受约束的仓库内原型:
|
|||
- `src/diagram/adapters/circuitikz/circuitikzRepairBrief.ts`、`--repair-brief-output` 与 `--repair-brief` 会生成并消费 schema `notemd.circuitikz.repair-brief.v1`,作为 topology-preserving repair handoff,携带 source `CircuitSpec`、topology signature、compile/render diagnostics、允许修改项、禁止拓扑修改项和下一步验证步骤。candidate spec 可以在写出 output 前按 brief signature 执行校验。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzDiagnostics.ts` 会把已有 LaTeX/TikZJax compile logs 解析为 actionable diagnostics,不 spawn 编译器,也不依赖 shell command resolution。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzCompileRunner.ts` 可以用 `shell: false`、placeholder-expanded arguments、针对空、缺失或无效 renderer command 的结构化 executable diagnostics、生成 log diagnostics 和可选 `--expected-artifact` render-smoke checks 运行显式配置的本地 renderer。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRenderSmoke.ts` 会检查预期 render artifact。PDF 和其他 opaque artifacts 检查存在与非空;SVG artifacts 还会检查 `<svg>` root、正的尺寸或 `viewBox`、可见绘图元素、可选重复传入的 `--expected-svg-text` tokens、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、通过解析 `<use href="#...">` 几何执行的 path-only glyph placement checks、可复用 glyph path 上的 definition-local transforms、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry、positioned `tspan` label geometry、bounded-canvas/text-overlap diagnostics、通过 `render-svg-label-overlap` 报告的 label-vs-drawing overlap diagnostics,以及常见 group 与 element transforms 的 transform-aware geometry;PNG screenshot artifacts 会被解码并检查正尺寸、非背景像素、1/2/4/8-bit indexed-color packed samples 与 PLTE/tRNS palette data、1/2/4/8/16-bit grayscale samples、8/16-bit grayscale-alpha/RGB/RGBA direct samples、前景包围盒、`foregroundDensity`、通过 `render-png-content-clipped` 报告的贴边裁剪内容,以及通过 `render-png-foreground-dense` 报告的异常密集前景块。
|
||||
- `src/diagram/adapters/circuitikz/circuitikzRenderSmoke.ts` 会检查预期 render artifact。PDF 和其他 opaque artifacts 检查存在与非空;SVG artifacts 还会检查 `<svg>` root、正的尺寸或 `viewBox`、可见绘图元素、可选重复传入的 `--expected-svg-text` tokens、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、通过解析 `<use href="#...">` 几何执行的 path-only glyph placement checks、可复用 glyph path 上的 definition-local transforms、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry、positioned `tspan` label geometry、bounded-canvas/text-overlap diagnostics、通过 `render-svg-label-overlap` 报告的 label-vs-drawing overlap diagnostics,以及常见 group 与 element transforms 的 transform-aware geometry;PNG screenshot artifacts 会被解码并检查正尺寸、非背景像素、1/2/4/8-bit indexed-color packed samples 与 PLTE/tRNS palette data、grayscale/RGB tRNS transparent samples、1/2/4/8/16-bit grayscale samples、8/16-bit grayscale-alpha/RGB/RGBA direct samples、前景包围盒、`foregroundDensity`、通过 `render-png-content-clipped` 报告的贴边裁剪内容,以及通过 `render-png-foreground-dense` 报告的异常密集前景块。
|
||||
- `src/rendering/diagnostics.ts` 会把 `RenderArtifact.diagnostics` 汇总为 error/warning/info 计数,`src/ui/DiagramPreviewModal.ts` 会在 artifact diagnostics panel 和 preview history entries 中使用同一份 summary。任意 render target 都可以把 diagnostics 挂到 `RenderArtifact.diagnostics`;preview UI 会显示 summary、severity、kind、message 和 advice,而不要求插件 runtime 内置 TikZJax 或 LaTeX。
|
||||
- `src/tests/circuitikzExporter.test.ts`、`src/tests/circuitikzRepairBrief.test.ts`、`src/tests/circuitikzCompileDiagnostics.test.ts`、`src/tests/circuitikzRenderSmoke.test.ts`、`src/tests/circuitikzCompileRunner.test.ts` 与 `src/tests/circuitikzExportCli.test.ts` 验证确定性输出、拓扑拒绝、topology-preserving repair brief generation 与 candidate validation、package-script 暴露、UTF-8 BOM 处理、diagnostic parsing、shell-free compile execution、结构化 `compile-executable-invalid` / `compile-executable-not-found` diagnostics、diagnostics JSON 输出、render artifact 存在/非空 smoke checks、SVG 结构检查、path-only SVG label classification、path-only glyph placement checks、definition-local glyph transform checks、path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry checks、transform-aware SVG geometry checks、SVG label-vs-drawing overlap checks、PNG 空白截图检查、indexed-color 与 grayscale PNG packed sample decoding、indexed-color PNG palette decoding、16-bit direct PNG sample normalization、PNG 前景包围盒与前景密度报告、PNG 裁剪内容诊断、异常密集前景诊断,以及 compile log 或 smoke report 含错误时 CLI 非零退出。
|
||||
- `src/tests/circuitikzExporter.test.ts`、`src/tests/circuitikzRepairBrief.test.ts`、`src/tests/circuitikzCompileDiagnostics.test.ts`、`src/tests/circuitikzRenderSmoke.test.ts`、`src/tests/circuitikzCompileRunner.test.ts` 与 `src/tests/circuitikzExportCli.test.ts` 验证确定性输出、拓扑拒绝、topology-preserving repair brief generation 与 candidate validation、package-script 暴露、UTF-8 BOM 处理、diagnostic parsing、shell-free compile execution、结构化 `compile-executable-invalid` / `compile-executable-not-found` diagnostics、diagnostics JSON 输出、render artifact 存在/非空 smoke checks、SVG 结构检查、path-only SVG label classification、path-only glyph placement checks、definition-local glyph transform checks、path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry checks、transform-aware SVG geometry checks、SVG label-vs-drawing overlap checks、PNG 空白截图检查、indexed-color 与 grayscale PNG packed sample decoding、indexed-color PNG palette decoding、grayscale/RGB PNG tRNS transparent sample handling、16-bit direct PNG sample normalization、PNG 前景包围盒与前景密度报告、PNG 裁剪内容诊断、异常密集前景诊断,以及 compile log 或 smoke report 含错误时 CLI 非零退出。
|
||||
- `src/tests/diagramPreviewModal.test.ts` 验证 artifact diagnostics 会显示在 diagram preview modal 中,并且 preview history 不会把 diagnostics 不同的条目错误去重。
|
||||
|
||||
Phase D 现在已经具备 log parsing、opt-in local renderer execution、artifact-level smoke checks、带 transform-aware geometry、path-only label classification、path-only glyph placement checks、path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` geometry coverage、positioned `tspan` label geometry、text/text 与 label/drawing overlap diagnostics 的 SVG structure smoke checks、第一层 PNG screenshot nonblank、indexed-color 与 grayscale packed-sample decoding、16-bit direct sample normalization、edge-clipping 与 dense-foreground checks、topology-preserving repair guard、带 candidate validation 的 topology-preserving repair brief handoff,以及前端 diagnostics surface,但实现仍有意停在 required renderer availability、path-only glyph text 的 OCR recognition、精确 pixel-level overlap detection、完整 SVG path coverage 和自动化 Phase E repair execution 之前。它不会捆绑 LaTeX、不会把 TikZJax 变成插件 runtime 依赖,也不会运行视觉修复闭环。
|
||||
Phase D 现在已经具备 log parsing、opt-in local renderer execution、artifact-level smoke checks、带 transform-aware geometry、path-only label classification、path-only glyph placement checks、path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` geometry coverage、positioned `tspan` label geometry、text/text 与 label/drawing overlap diagnostics 的 SVG structure smoke checks、第一层 PNG screenshot nonblank、indexed-color 与 grayscale packed-sample decoding、grayscale/RGB tRNS transparent sample handling、16-bit direct sample normalization、edge-clipping 与 dense-foreground checks、topology-preserving repair guard、带 candidate validation 的 topology-preserving repair brief handoff,以及前端 diagnostics surface,但实现仍有意停在 required renderer availability、path-only glyph text 的 OCR recognition、精确 pixel-level overlap detection、完整 SVG path coverage 和自动化 Phase E repair execution 之前。它不会捆绑 LaTeX、不会把 TikZJax 变成插件 runtime 依赖,也不会运行视觉修复闭环。
|
||||
|
||||
SVG geometry smoke 也覆盖 leading-dot decimals 与 explicit plus signs 的 SVG number grammar。这是一个很小但关键的 renderer-compatibility gate,因为 dvisvgm 可能输出紧凑小数,bounds checks 中必须保留其小数语义。
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@ interface PngPalette {
|
|||
alpha: number[];
|
||||
}
|
||||
|
||||
interface PngTransparency {
|
||||
grayscale?: number;
|
||||
rgb?: [number, number, number];
|
||||
}
|
||||
|
||||
type PngPixelLayout = {
|
||||
kind: 'direct';
|
||||
colorType: number;
|
||||
|
|
@ -1491,6 +1496,37 @@ function parsePngPalette(chunks: PngChunk[], colorType: number): PngPalette | un
|
|||
};
|
||||
}
|
||||
|
||||
function parsePngTransparency(chunks: PngChunk[], colorType: number): PngTransparency | undefined {
|
||||
const transparency = chunks.find(chunk => chunk.type === 'tRNS');
|
||||
if (!transparency || colorType === 3) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (colorType === 0) {
|
||||
if (transparency.data.length !== 2) {
|
||||
throw new Error('PNG grayscale transparency chunk is malformed.');
|
||||
}
|
||||
return {
|
||||
grayscale: transparency.data.readUInt16BE(0)
|
||||
};
|
||||
}
|
||||
|
||||
if (colorType === 2) {
|
||||
if (transparency.data.length !== 6) {
|
||||
throw new Error('PNG RGB transparency chunk is malformed.');
|
||||
}
|
||||
return {
|
||||
rgb: [
|
||||
transparency.data.readUInt16BE(0),
|
||||
transparency.data.readUInt16BE(2),
|
||||
transparency.data.readUInt16BE(4)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readPackedSample(decoded: Buffer, pixelIndex: number, width: number, bitDepth: number, scanlineByteLength: number): number {
|
||||
if (bitDepth === 8) {
|
||||
return decoded[pixelIndex];
|
||||
|
|
@ -1515,24 +1551,29 @@ function normalizePngSample(sample: number, bitDepth: number): number {
|
|||
return Math.round(sample * 255 / ((1 << bitDepth) - 1));
|
||||
}
|
||||
|
||||
function readDirectSample(decoded: Buffer, offset: number, bitDepth: number): number {
|
||||
function readRawDirectSample(decoded: Buffer, offset: number, bitDepth: number): number {
|
||||
if (bitDepth === 16) {
|
||||
return normalizePngSample(decoded.readUInt16BE(offset), bitDepth);
|
||||
return decoded.readUInt16BE(offset);
|
||||
}
|
||||
return decoded[offset];
|
||||
}
|
||||
|
||||
function readDirectSample(decoded: Buffer, offset: number, bitDepth: number): number {
|
||||
return normalizePngSample(readRawDirectSample(decoded, offset, bitDepth), bitDepth);
|
||||
}
|
||||
|
||||
function readPixel(
|
||||
decoded: Buffer,
|
||||
pixelIndex: number,
|
||||
width: number,
|
||||
layout: PngPixelLayout,
|
||||
palette?: PngPalette
|
||||
palette?: PngPalette,
|
||||
transparency?: PngTransparency
|
||||
): [number, number, number, number] {
|
||||
if (layout.kind === 'grayscale') {
|
||||
const sample = readPackedSample(decoded, pixelIndex, width, layout.bitDepth, layout.scanlineByteLength);
|
||||
const gray = normalizePngSample(sample, layout.bitDepth);
|
||||
return [gray, gray, gray, 255];
|
||||
return [gray, gray, gray, transparency?.grayscale === sample ? 0 : 255];
|
||||
}
|
||||
|
||||
if (layout.kind === 'indexed') {
|
||||
|
|
@ -1547,16 +1588,25 @@ function readPixel(
|
|||
const offset = pixelIndex * layout.bytesPerPixel;
|
||||
switch (layout.colorType) {
|
||||
case 0: {
|
||||
const gray = readDirectSample(decoded, offset, layout.bitDepth);
|
||||
return [gray, gray, gray, 255];
|
||||
const sample = readRawDirectSample(decoded, offset, layout.bitDepth);
|
||||
const gray = normalizePngSample(sample, layout.bitDepth);
|
||||
return [gray, gray, gray, transparency?.grayscale === sample ? 0 : 255];
|
||||
}
|
||||
case 2:
|
||||
case 2: {
|
||||
const red = readRawDirectSample(decoded, offset, layout.bitDepth);
|
||||
const green = readRawDirectSample(decoded, offset + layout.bytesPerSample, layout.bitDepth);
|
||||
const blue = readRawDirectSample(decoded, offset + layout.bytesPerSample * 2, layout.bitDepth);
|
||||
const transparent = transparency?.rgb
|
||||
&& transparency.rgb[0] === red
|
||||
&& transparency.rgb[1] === green
|
||||
&& transparency.rgb[2] === blue;
|
||||
return [
|
||||
readDirectSample(decoded, offset, layout.bitDepth),
|
||||
readDirectSample(decoded, offset + layout.bytesPerSample, layout.bitDepth),
|
||||
readDirectSample(decoded, offset + layout.bytesPerSample * 2, layout.bitDepth),
|
||||
255
|
||||
normalizePngSample(red, layout.bitDepth),
|
||||
normalizePngSample(green, layout.bitDepth),
|
||||
normalizePngSample(blue, layout.bitDepth),
|
||||
transparent ? 0 : 255
|
||||
];
|
||||
}
|
||||
case 4: {
|
||||
const gray = readDirectSample(decoded, offset, layout.bitDepth);
|
||||
return [gray, gray, gray, readDirectSample(decoded, offset + layout.bytesPerSample, layout.bitDepth)];
|
||||
|
|
@ -1591,6 +1641,7 @@ function extractPngSmoke(pngBytes: Buffer): CircuitikzPngSmokeReport {
|
|||
throw new Error('PNG dimensions must be positive.');
|
||||
}
|
||||
const palette = parsePngPalette(chunks, header.colorType);
|
||||
const transparency = parsePngTransparency(chunks, header.colorType);
|
||||
|
||||
const idatChunks = chunks.filter(chunk => chunk.type === 'IDAT');
|
||||
if (idatChunks.length === 0) {
|
||||
|
|
@ -1600,7 +1651,7 @@ function extractPngSmoke(pngBytes: Buffer): CircuitikzPngSmokeReport {
|
|||
const inflated = zlib.inflateSync(Buffer.concat(idatChunks.map(chunk => chunk.data)));
|
||||
const decoded = unfilterPngScanlines(inflated, header.height, pixelLayout.scanlineByteLength, pixelLayout.filterByteStride);
|
||||
const decodedPixelCount = header.width * header.height;
|
||||
const background = readPixel(decoded, 0, header.width, pixelLayout, palette);
|
||||
const background = readPixel(decoded, 0, header.width, pixelLayout, palette, transparency);
|
||||
let nonBackgroundPixelCount = 0;
|
||||
let minX = header.width;
|
||||
let minY = header.height;
|
||||
|
|
@ -1608,7 +1659,7 @@ function extractPngSmoke(pngBytes: Buffer): CircuitikzPngSmokeReport {
|
|||
let maxY = -1;
|
||||
|
||||
for (let pixelIndex = 0; pixelIndex < decodedPixelCount; pixelIndex += 1) {
|
||||
const pixel = readPixel(decoded, pixelIndex, header.width, pixelLayout, palette);
|
||||
const pixel = readPixel(decoded, pixelIndex, header.width, pixelLayout, palette, transparency);
|
||||
if (pixel[3] > 0 && colorDistance(pixel, background) > 8) {
|
||||
nonBackgroundPixelCount += 1;
|
||||
const x = pixelIndex % header.width;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ function createGrayscalePng(
|
|||
width: number,
|
||||
height: number,
|
||||
bitDepth: number,
|
||||
samples: number[]
|
||||
samples: number[],
|
||||
transparentSample?: number
|
||||
): Buffer {
|
||||
const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
const ihdr = Buffer.alloc(13);
|
||||
|
|
@ -134,12 +135,62 @@ function createGrayscalePng(
|
|||
scanlines.push(...packPngSamples(samples.slice(row * width, row * width + width), bitDepth));
|
||||
}
|
||||
|
||||
return Buffer.concat([
|
||||
const chunks = [
|
||||
signature,
|
||||
pngChunk('IHDR', ihdr),
|
||||
pngChunk('IHDR', ihdr)
|
||||
];
|
||||
if (transparentSample !== undefined) {
|
||||
const transparency = Buffer.alloc(2);
|
||||
transparency.writeUInt16BE(transparentSample, 0);
|
||||
chunks.push(pngChunk('tRNS', transparency));
|
||||
}
|
||||
chunks.push(
|
||||
pngChunk('IDAT', zlib.deflateSync(Buffer.from(scanlines))),
|
||||
pngChunk('IEND', Buffer.alloc(0))
|
||||
]);
|
||||
);
|
||||
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
|
||||
function createRgbPng(
|
||||
width: number,
|
||||
height: number,
|
||||
pixels: Array<[number, number, number]>,
|
||||
transparentColor?: [number, number, number]
|
||||
): Buffer {
|
||||
const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0);
|
||||
ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8;
|
||||
ihdr[9] = 2;
|
||||
ihdr[10] = 0;
|
||||
ihdr[11] = 0;
|
||||
ihdr[12] = 0;
|
||||
|
||||
const scanlines: number[] = [];
|
||||
for (let row = 0; row < height; row += 1) {
|
||||
scanlines.push(0);
|
||||
for (let column = 0; column < width; column += 1) {
|
||||
scanlines.push(...pixels[row * width + column]);
|
||||
}
|
||||
}
|
||||
|
||||
const chunks = [
|
||||
signature,
|
||||
pngChunk('IHDR', ihdr)
|
||||
];
|
||||
if (transparentColor) {
|
||||
const transparency = Buffer.alloc(6);
|
||||
transparentColor.forEach((sample, index) => transparency.writeUInt16BE(sample, index * 2));
|
||||
chunks.push(pngChunk('tRNS', transparency));
|
||||
}
|
||||
chunks.push(
|
||||
pngChunk('IDAT', zlib.deflateSync(Buffer.from(scanlines))),
|
||||
pngChunk('IEND', Buffer.alloc(0))
|
||||
);
|
||||
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
|
||||
function createIndexedPng(
|
||||
|
|
@ -1265,6 +1316,95 @@ describe('circuitikz render smoke inspection', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('ignores grayscale PNG tRNS transparent samples when checking foreground', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'notemd-circuitikz-png-gray-trns-smoke-'));
|
||||
const pngPath = path.join(tempRoot, 'gray-trns-render.png');
|
||||
fs.writeFileSync(
|
||||
pngPath,
|
||||
createGrayscalePng(
|
||||
3,
|
||||
3,
|
||||
8,
|
||||
[
|
||||
255, 255, 255,
|
||||
255, 0, 255,
|
||||
255, 255, 255
|
||||
],
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
const report = inspectCircuitikzRenderArtifact({
|
||||
expectedArtifactPath: pngPath
|
||||
});
|
||||
|
||||
expect(report.artifactKind).toBe('png');
|
||||
expect(report.png).toEqual(expect.objectContaining({
|
||||
bitDepth: 8,
|
||||
colorType: 0,
|
||||
decodedPixelCount: 9,
|
||||
nonBackgroundPixelCount: 0,
|
||||
foregroundBounds: undefined
|
||||
}));
|
||||
expect(report.diagnostics).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: 'render-png-blank',
|
||||
message: expect.stringContaining('visually blank')
|
||||
})
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('ignores RGB PNG tRNS transparent colors when checking foreground', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'notemd-circuitikz-png-rgb-trns-smoke-'));
|
||||
const pngPath = path.join(tempRoot, 'rgb-trns-render.png');
|
||||
fs.writeFileSync(
|
||||
pngPath,
|
||||
createRgbPng(
|
||||
3,
|
||||
3,
|
||||
[
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 0, 0],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255]
|
||||
],
|
||||
[255, 0, 0]
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
const report = inspectCircuitikzRenderArtifact({
|
||||
expectedArtifactPath: pngPath
|
||||
});
|
||||
|
||||
expect(report.artifactKind).toBe('png');
|
||||
expect(report.png).toEqual(expect.objectContaining({
|
||||
bitDepth: 8,
|
||||
colorType: 2,
|
||||
decodedPixelCount: 9,
|
||||
nonBackgroundPixelCount: 0,
|
||||
foregroundBounds: undefined
|
||||
}));
|
||||
expect(report.diagnostics).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: 'render-png-blank',
|
||||
message: expect.stringContaining('visually blank')
|
||||
})
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
label: 'grayscale',
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ SVG smoke verifies the `<svg>` root, positive dimensions or `viewBox`, at least
|
|||
|
||||
The same geometry pass follows SVG number grammar for leading-dot decimals and explicit plus signs, so compact dvisvgm coordinates such as `.5`, `-.5`, or `+.5` stay fractional during bounds checks instead of becoming false out-of-bounds geometry or being skipped.
|
||||
|
||||
If the renderer emits `.png`, the same expected-artifact path becomes a first screenshot smoke: Notemd decodes non-interlaced 1/2/4/8-bit indexed-color PNG files, 1/2/4/8/16-bit grayscale PNG files, and 8/16-bit grayscale-alpha/RGB/RGBA PNG files. Indexed-color and sub-byte grayscale images support packed samples; indexed-color images also support PLTE and optional tRNS data. 16-bit direct samples are normalized into the same 8-bit RGBA comparison space used by the smoke checks. The smoke check verifies positive dimensions, records foreground bounds as `foregroundBounds`, records foreground density inside that box as `foregroundDensity`, fails with `render-png-blank` when every visible pixel matches the top-left background color, fails with `render-png-content-clipped` when foreground content touches the image boundary, and fails with `render-png-foreground-dense` when foreground pixels are unusually dense inside a non-trivial bounding box. This catches blank screenshots, obvious canvas clipping, and first pixel-level crowding failures without adding a platform-specific shell dependency. It is not yet OCR-level label recognition, precise text-overlap detection, or topology-preserving image repair.
|
||||
If the renderer emits `.png`, the same expected-artifact path becomes a first screenshot smoke: Notemd decodes non-interlaced 1/2/4/8-bit indexed-color PNG files, 1/2/4/8/16-bit grayscale PNG files, and 8/16-bit grayscale-alpha/RGB/RGBA PNG files. Indexed-color and sub-byte grayscale images support packed samples; indexed-color images also support PLTE and optional tRNS data; grayscale/RGB images support tRNS transparent samples. 16-bit direct samples are normalized into the same 8-bit RGBA comparison space used by the smoke checks. The smoke check verifies positive dimensions, records foreground bounds as `foregroundBounds`, records foreground density inside that box as `foregroundDensity`, fails with `render-png-blank` when every visible pixel matches the top-left background color, fails with `render-png-content-clipped` when foreground content touches the image boundary, and fails with `render-png-foreground-dense` when foreground pixels are unusually dense inside a non-trivial bounding box. This catches blank screenshots, obvious canvas clipping, and first pixel-level crowding failures without adding a platform-specific shell dependency. It is not yet OCR-level label recognition, precise text-overlap detection, or topology-preserving image repair.
|
||||
|
||||
When diagnostics show a failed compile or render-smoke run, the CLI can also write a topology-preserving repair brief:
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ For a CMOS inverter, the prompt should request an explicit topology plus layout
|
|||
| General diagrams | Spec-first pipeline implemented for Mermaid, JSON Canvas, Vega-Lite, HTML | Keep expanding semantic verification coverage |
|
||||
| Editable figures | `editable-html-svg`, Draw.io XML, and Drawnix JSON artifact boundaries implemented | Add richer primitives only after tests prove editability |
|
||||
| CLI support | `npm run diagram:export-artifact` exports editable HTML/SVG, Draw.io, and Drawnix from one `DiagramSpec` | Add target-specific smoke fixtures when new targets ship |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source and CMOS inverter golden templates, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, validates repair candidates through `--repair-brief`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition, precise pixel-level overlap checks, broader SVG path coverage where needed, and automated topology-preserving repair execution |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` prototype exports common-source and CMOS inverter golden templates, rejects repair topology drift through `--topology-reference`, emits topology-preserving repair briefs through `--repair-brief-output` and schema `notemd.circuitikz.repair-brief.v1`, validates repair candidates through `--repair-brief`, parses compile logs, can run explicit local renderers plus `--expected-artifact`, SVG `--expected-svg-text`, `render-svg-text-path-only` / `pathOnlyGlyphUseCount` classification for path-only labels, path-only glyph placement checks for `<use href="#...">`, path-only glyph overlap diagnostics through `render-svg-path-glyph-overlap`, exact arc bounds for A/a arc extrema, exact Bezier curve bounds for C/S/Q/T curve extrema, stroke-width-aware SVG bounds and label overlap checks, `polyline` / `polygon` drawing geometry checks, positioned `tspan` label geometry, transform-aware geometry for SVG bounded-canvas/text-overlap and label-vs-drawing smoke through `render-svg-label-overlap`, and PNG nonblank / clipped / dense-foreground screenshot smoke checks, including indexed-color palette alpha and grayscale/RGB tRNS transparent samples, through `foregroundBounds`, `foregroundDensity`, `render-png-content-clipped`, and `render-png-foreground-dense` without shell parsing, includes aggregate maintainer smoke fixtures through `npm run diagram:smoke-circuitikz`, and has generic preview diagnostics, diagnostic summary counts, diagnostics-aware history entries, and source-only fallback through `RenderArtifact.diagnostics` and the preview modal | Add OCR-level label recognition, precise pixel-level overlap checks, broader SVG path coverage where needed, and automated topology-preserving repair execution |
|
||||
| TikZJax integration | Candidate render host for Obsidian-side display | Keep it optional; do not make TikZJax a hard plugin runtime dependency |
|
||||
|
||||
## Configuration
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ SVG smoke 会检查 `<svg>` root、正的尺寸或 `viewBox`、至少一个可
|
|||
|
||||
同一层 geometry pass 也按 SVG number grammar 处理 leading-dot decimals 与 explicit plus signs,因此 dvisvgm 输出的 `.5`、`-.5` 或 `+.5` 这类紧凑坐标会在 bounds checks 中保留小数语义,而不会变成 false out-of-bounds geometry 或被静默跳过。
|
||||
|
||||
如果 renderer 输出 `.png`,同一个 expected-artifact 路径会变成第一层 screenshot smoke:Notemd 会解码非交错的 1/2/4/8-bit indexed-color PNG 文件、1/2/4/8/16-bit grayscale PNG 文件,以及 8/16-bit grayscale-alpha/RGB/RGBA PNG 文件。indexed-color 与 sub-byte grayscale 图像支持 packed samples;indexed-color 图像还支持 PLTE 与可选 tRNS 数据。16-bit direct samples 会被归一化到 smoke check 使用的同一个 8-bit RGBA 比较空间。smoke check 会检查正尺寸,把前景包围盒记录为 `foregroundBounds`,把包围盒内前景密度记录为 `foregroundDensity`,在所有可见像素都与左上角背景色一致时以 `render-png-blank` 失败,在前景内容贴到图像边界时以 `render-png-content-clipped` 失败,并在非平凡包围盒内前景像素异常密集时以 `render-png-foreground-dense` 失败。这能在不引入平台特定 shell 依赖的情况下捕获空白截图、明显画布裁剪和第一层 pixel-level crowding failure,但它还不是 OCR-level label recognition、精确 text-overlap detection 或 topology-preserving image repair。
|
||||
如果 renderer 输出 `.png`,同一个 expected-artifact 路径会变成第一层 screenshot smoke:Notemd 会解码非交错的 1/2/4/8-bit indexed-color PNG 文件、1/2/4/8/16-bit grayscale PNG 文件,以及 8/16-bit grayscale-alpha/RGB/RGBA PNG 文件。indexed-color 与 sub-byte grayscale 图像支持 packed samples;indexed-color 图像还支持 PLTE 与可选 tRNS 数据;grayscale/RGB 图像支持 tRNS transparent samples。16-bit direct samples 会被归一化到 smoke check 使用的同一个 8-bit RGBA 比较空间。smoke check 会检查正尺寸,把前景包围盒记录为 `foregroundBounds`,把包围盒内前景密度记录为 `foregroundDensity`,在所有可见像素都与左上角背景色一致时以 `render-png-blank` 失败,在前景内容贴到图像边界时以 `render-png-content-clipped` 失败,并在非平凡包围盒内前景像素异常密集时以 `render-png-foreground-dense` 失败。这能在不引入平台特定 shell 依赖的情况下捕获空白截图、明显画布裁剪和第一层 pixel-level crowding failure,但它还不是 OCR-level label recognition、精确 text-overlap detection 或 topology-preserving image repair。
|
||||
|
||||
当 diagnostics 显示 compile 或 render-smoke 失败时,CLI 也可以写出 topology-preserving repair brief:
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ runner 使用 `docs/maintainer/fixtures/circuitikz/common-source-nmos-v1.json`
|
|||
| General diagrams | Mermaid、JSON Canvas、Vega-Lite、HTML 的 spec-first pipeline 已实现 | 继续扩展 semantic verification coverage |
|
||||
| Editable figures | `editable-html-svg`、Draw.io XML、Drawnix JSON artifact boundaries 已实现 | 只有在测试证明 editability 后再加入更丰富 primitives |
|
||||
| CLI support | `npm run diagram:export-artifact` 可从同一个 `DiagramSpec` 导出 editable HTML/SVG、Draw.io、Drawnix | 新 target 发布时增加 target-specific smoke fixtures |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source 与 CMOS inverter golden templates、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、通过 `--repair-brief` 校验 repair candidates、解析 compile logs,并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke,以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks;现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage,并推进自动化 topology-preserving repair execution |
|
||||
| circuitikz | `CircuitSpec -> circuitikz` 原型已经可通过 `npm run diagram:export-circuitikz` 导出 common-source 与 CMOS inverter golden templates、通过 `--topology-reference` 拒绝 repair topology drift、通过 `--repair-brief-output` 和 schema `notemd.circuitikz.repair-brief.v1` 写出 topology-preserving repair briefs、通过 `--repair-brief` 校验 repair candidates、解析 compile logs,并在不经过 shell parsing 的情况下运行显式本地 renderer、`--expected-artifact`、SVG `--expected-svg-text`、用于 path-only labels 的 `render-svg-text-path-only` / `pathOnlyGlyphUseCount` 分类、针对 `<use href="#...">` 的 path-only glyph placement checks、通过 `render-svg-path-glyph-overlap` 执行的 path-only glyph overlap diagnostics、针对 A/a arc extrema 的 exact arc bounds、针对 C/S/Q/T curve extrema 的 exact Bezier curve bounds、stroke-width-aware SVG bounds 与 label overlap checks、`polyline` / `polygon` drawing geometry checks、positioned `tspan` label geometry、带 transform-aware geometry 的 SVG bounded-canvas / text-overlap 以及通过 `render-svg-label-overlap` 报告的 label-vs-drawing smoke,以及通过 `foregroundBounds`、`foregroundDensity`、`render-png-content-clipped` 和 `render-png-foreground-dense` 执行的 PNG nonblank / clipped / dense-foreground screenshot smoke checks,包含 indexed-color palette alpha 与 grayscale/RGB tRNS transparent samples;现在还通过 `npm run diagram:smoke-circuitikz` 提供聚合 maintainer smoke fixtures,并通过 `RenderArtifact.diagnostics` 和 preview modal 具备通用 preview diagnostics、diagnostic summary counts、diagnostics-aware history entries 与 source-only fallback | 增加 OCR-level label recognition、精确 pixel-level overlap checks、按需补齐更完整 SVG path coverage,并推进自动化 topology-preserving repair execution |
|
||||
| TikZJax integration | Obsidian-side display 的候选 render host | 保持可选,不把 TikZJax 变成硬 runtime 依赖 |
|
||||
|
||||
## Configuration
|
||||
|
|
|
|||
Loading…
Reference in a new issue