Files
MorphDoc/packages/docx-theme-engine/src/probe.ts
T
SkyJourney 2c5c1bd317 release: 发布 v0.6.1 DOCX 视觉一致性修复
新增通用 CSS 到 OOXML 翻译修复,统一字体、字距、精确行距、段落、列表、表格、引用、代码块与行内代码连续性,不引入按主题 ID 分支。

新增 MdTP Mono 并统一 Serif、Sans、Mono 三字体包的 Chromium 与 DOCX 使用链;字体声明、嵌入部件和 Word/WPS 实际采用均进入硬门禁。

重建封面整页及正文语义块视觉差分,14 套主题、纵横两个方向、五组页边距共 140 个真实场景全部通过,阻断失败和诊断失败均为零。

源码服务、Docker Web API 与实际安装 Desktop 的 red-briefing 导出均包含 5 个字体部件;Word/WPS 原生渲染和逐页复核通过。修复 Docker 构建上下文与运行层复用软链接,并完善 v0.6.1 版本、发行说明和发布归集。

验证:npm test(116 个文件、616 项测试)、npm run typecheck、npm run build、git diff --check 全部通过。Desktop 安装器与 ZIP、Docker v0.6.1 镜像已生成;Windows 产物仍为未签名内部发行。
2026-08-04 10:30:44 +08:00

133 lines
7.6 KiB
TypeScript
Raw 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.
import {
DOCX_STYLE_SLOT_NAMES,
type DocxStyleSlotName
} from "./slots.js";
function attribute(slot: DocxStyleSlotName) {
return `data-docx-slot="${slot}"`;
}
const transparentPixel =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
export function createDocxStyleProbeMarkup(): string {
return `
<article id="write" ${attribute("document")}>
<h1 ${attribute("heading-1")}>一级标题</h1>
<header class="doc-metadata">
<h1 class="doc-metadata-title" ${attribute("document-title")}>文档标题</h1>
<p class="doc-author" ${attribute("document-author")}>示例作者</p>
</header>
<section class="docx-probe-markdown">
<h2 ${attribute("heading-2")}>二级标题</h2>
<h3 ${attribute("heading-3")}>三级标题</h3>
<h4 ${attribute("heading-4")}>四级标题</h4>
<h5 ${attribute("heading-5")}>五级标题</h5>
<h6 ${attribute("heading-6")}>六级标题</h6>
<p ${attribute("paragraph")}>
正文
<strong ${attribute("strong")}>粗体</strong>
<em ${attribute("emphasis")}>斜体</em>
<del ${attribute("strikethrough")}>删除线</del>
<code ${attribute("inline-code")}>inline()</code>
<a href="#probe" ${attribute("hyperlink")}>链接</a>
</p>
<ul ${attribute("unordered-list")}><li ${attribute("list-item")}>无序列表</li></ul>
<ol ${attribute("ordered-list")}><li>有序列表</li></ol>
<blockquote ${attribute("block-quote")}><p ${attribute("block-quote-text")}>引用内容</p></blockquote>
<pre class="md-fences" ${attribute("code-block")}><code class="language-javascript" ${attribute("code-block-text")}><span class="hljs-keyword" ${attribute("code-token-keyword")}>const</span> <span class="hljs-attr" ${attribute("code-token-attribute")}>key</span> <span class="hljs-operator" ${attribute("code-token-operator")}>=</span> <span class="hljs-string" ${attribute("code-token-string")}>&quot;value&quot;</span>; <span class="hljs-literal" ${attribute("code-token-literal")}>true</span> <span class="hljs-title" ${attribute("code-token-title")}>title</span> <span class="hljs-number" ${attribute("code-token-number")}>1</span> <span class="hljs-comment" ${attribute("code-token-comment")}>// comment</span> <span class="hljs-built_in" ${attribute("code-token-built-in")}>Array</span> <span class="hljs-meta" ${attribute("code-token-meta")}>@meta</span> <span class="hljs-variable" ${attribute("code-token-variable")}>value</span> <span class="hljs-regexp" ${attribute("code-token-regexp")}>/x/</span> <span class="hljs-punctuation" ${attribute("code-token-punctuation")}>{}</span></code></pre>
<table ${attribute("table")}>
<thead><tr><th ${attribute("table-header")}>表头</th></tr></thead>
<tbody><tr><td ${attribute("table-cell")}>单元格</td></tr></tbody>
</table>
<figure ${attribute("figure")}>
<img src="${transparentPixel}" alt="样式探针" ${attribute("image")}>
<figcaption ${attribute("caption")}>图注</figcaption>
</figure>
<section class="footnotes" ${attribute("footnotes")}><p>脚注内容</p></section>
</section>
<section class="docx-probe-official">
<header class="doc-masthead doc-masthead-official" ${attribute("official-masthead")}>
<div class="doc-classification" ${attribute("official-classification")}>秘密 加急</div>
<p class="doc-issuer" ${attribute("official-issuer")}>示例发文机关</p>
<div class="doc-issue-row" ${attribute("official-issue-row")}>
<span class="doc-number" ${attribute("official-number")}>示例发〔20261号</span>
<span class="doc-signatory" ${attribute("official-signatory")}><span>签发人:</span>张三</span>
</div>
</header>
<h1 class="doc-title" ${attribute("official-title")}>公文标题</h1>
<section class="doc-signature" ${attribute("official-signature")}><p class="doc-signature-issuer" ${attribute("official-signature-issuer")}>示例发文机关</p><time class="doc-signature-date" ${attribute("official-signature-date")}>2026年7月30日</time></section>
<footer class="doc-edition" ${attribute("official-edition")}><p class="doc-copy-to" ${attribute("official-copy-to")}>抄送:示例单位</p><div class="doc-printing-row" ${attribute("official-printing-row")}><span>示例办公室</span><time>2026年7月30日印发</time></div></footer>
</section>
<section class="docx-probe-briefing">
<header class="doc-masthead doc-masthead-briefing" ${attribute("briefing-masthead")}>
<p class="doc-briefing-masthead" ${attribute("briefing-masthead-text")}>工作简报</p>
<div class="doc-briefing-meta" ${attribute("briefing-meta")}><span>第1期</span><span>示例单位</span><time>2026年7月30日</time></div>
</header>
<h1 class="doc-title" ${attribute("briefing-title")}>简报标题</h1>
<footer class="doc-briefing-contact" ${attribute("briefing-contact")}>联系人:示例</footer>
</section>
<header class="doc-cover doc-cover-project-report" ${attribute("project-report-cover")}>
<p class="doc-cover-project-name" ${attribute("project-report-project-name")}>示例建设项目</p>
<h1 class="doc-cover-title" ${attribute("project-report-title")}>可行性研究报告</h1>
<p class="doc-cover-version" ${attribute("project-report-version")}>送审稿 V1.0</p>
<p class="doc-cover-owner" ${attribute("project-report-owner")}><span>建设单位:</span>示例单位</p>
<p class="doc-cover-prepared-by" ${attribute("project-report-prepared-by")}><span>编制单位:</span>示例单位</p>
<time class="doc-cover-date" ${attribute("project-report-date")}>2026年7月</time>
</header>
<header class="doc-cover doc-cover-tender" ${attribute("tender-cover")}>
<p class="doc-cover-copy-mark" ${attribute("tender-copy-mark")}>正本</p>
<p class="doc-cover-project-name" ${attribute("tender-project-name")}>示例采购项目</p>
<p class="doc-cover-project-number" ${attribute("tender-project-number")}>ZB-2026-001</p>
<h1 class="doc-cover-title" ${attribute("tender-title")}>投标文件</h1>
<p class="doc-cover-volume" ${attribute("tender-volume")}>技术及商务文件</p>
<p class="doc-cover-bidder" ${attribute("tender-bidder")}><span>投标人:</span>示例公司</p>
<p class="doc-cover-representative" ${attribute("tender-representative")}><span>法定代表人或授权代表:</span>李四</p>
<time class="doc-cover-date" ${attribute("tender-date")}>2026年7月30日</time>
</header>
</article>`.trim();
}
export function listDocxStyleProbeSlots(
markup = createDocxStyleProbeMarkup()
): DocxStyleSlotName[] {
const matches = markup.matchAll(
/data-docx-slot="([^"]+)"/gu
);
return [...matches].map(
(match) => match[1] as DocxStyleSlotName
);
}
export function validateDocxStyleProbeMarkup(
markup = createDocxStyleProbeMarkup()
): void {
const actual = listDocxStyleProbeSlots(markup);
const duplicates = actual.filter(
(name, index) => actual.indexOf(name) !== index
);
const actualSet = new Set(actual);
const missing = DOCX_STYLE_SLOT_NAMES.filter(
(name) => !actualSet.has(name)
);
const unexpected = actual.filter(
(name) =>
!DOCX_STYLE_SLOT_NAMES.includes(name as DocxStyleSlotName)
);
if (duplicates.length || missing.length || unexpected.length) {
throw new Error(
[
duplicates.length
? `重复槽位:${[...new Set(duplicates)].join("、")}`
: "",
missing.length ? `缺少槽位:${missing.join("、")}` : "",
unexpected.length
? `未知槽位:${[...new Set(unexpected)].join("、")}`
: ""
]
.filter(Boolean)
.join("")
);
}
}