Document Builder 文档生成器 alpha 版本功能演示引言经过若干个周末的编写，终于完成了 DocumentBuilder.js 1.0-alpha 版本的开发。现在先来简单介绍一下何为 DocumentBuilder.js。简介DocumentBuilder.js, 顾名思义，就是一个基于 JavaScript 的原生文档排版框架。原生 JS 排版引擎，将 JSON AST 渲染为分页文档。无框架依赖，纯 Shadow DOM 隔离。主要由钩子（Hook）、主题（Theme）、插件（Plugin）三大系统驱动。主程序只提供最基本的功能，样式与组件都由主题和插件提供，具有高可拓展性。程序可以在指定容器上生成文档的 DOM 结构，并支持实时预览与修改。打印通过浏览器原生功能实现，也可以搭配 html2pdf.js 等库导出为 PDF。本程序的功能基本都不在主程序，而是在插件中。这里我也写了几个基本的插件与主题，覆盖了从党政公文到公司文档的常见场景。主题GB/T 9704 党政机关公文标准公司内部标准文档格式组件演示插件纸张（Paper）：定义了 ISO A1—A5、US Letter 等标准纸张的尺寸与边距常用组件：paragraph（段落）、image（图片）、signature（签章）、red-header（红头文件头）、hr（分割线）、footer（公文版记）、cover（封面）、math（基于 KaTeX 的公式渲染）、heading（标题）、bullets-list（列表）水印（Watermark）：支持平铺、斜铺、居中三种布局，可自定义文字、Logo、字号、透明度PaperProfiles（信函格式）：手写信函纸，支持发文机构 + 双横线边框，以及空白纸（仅保留顶部底部双横线）有了这些插件与主题后，DocumentBuilder.js 的功能已经基本完整，能够覆盖日常文档排版的绝大多数需求。本程序的开发主要是为了解决懒得使用 Word 受挫文档的问题。方便一键生成公文(虽然这个功能我们可能根本用不到)。程序还有很多组件功能，比如双面打印、水印等插件功能没有演示，需要可以自行探索。瞎 BB我们的演示文件使用了《流浪地球》里的地球联合政府，Belike 最近球瘾犯了，比较期待《流浪地球3》.具体的实现方法这个章节可能会比较无聊，都是在写 DocumentBuilder.js 的具体实现细节。本章节由 Claude Code 根据项目具体上下文编写, 人工参与润色。整体架构DocumentBuilder.js 的整体架构可以分为三层：┌─────────────────────────────────────┐ │ 插件 / 主题层 │ │ plugins/*.js themes/*.js │ ├─────────────────────────────────────┤ │ 核心引擎 │ │ document-builder.js │ │ ├─ ComponentRegistry 组件注册表 │ │ ├─ ThemeRegistry 主题注册表 │ │ ├─ TextSplitter 文本截断器 │ │ ├─ Paginator 分页引擎 │ │ ├─ HookManager 钩子管理器 │ │ └─ StyleManager 样式管理器 │ ├─────────────────────────────────────┤ │ Shadow DOM 容器 │ │ #shadow-root > .db-workspace │ └─────────────────────────────────────┘所有渲染结果都存放在 Shadow DOM 中，与页面主文档完全隔离。这意味着页面上的全局 CSS 不会影响文档排版，文档内部的样式也不会泄漏到页面中。渲染管线整个渲染流程从 setDocument(jsonAST) 开始，大致分为以下几个步骤：解析配置：读取 JSON 中的 theme、paper、orientation 等字段，确定纸张大小、方向和边距创建离屏测量容器：在 Shadow DOM 中创建 .db-measure 容器，用于文本截断时的尺寸测量遍历 content 数组：对每个 block 调用 render() 生成 DOM 元素，追加到当前页分页检测：每次追加后检查页面是否溢出：若组件支持跨页截断（如段落），则用 TextSplitter 将文本拆为"留在本页"和"移到下页"两部分若不支持截断，将整个块移动到下一页若组件标记了 forcePageBreak，渲染后强制分页应用页眉页脚：根据主题配置和分页段设置，为每页渲染页眉和页脚销毁测量容器，触发 afterRender 钩子这个管线是纯同步的、单遍扫描——不需要二次回流或重排。插件系统插件通过 documentBuilder.registerComponent(name, definition) 注册。主程序对插件的存在一无所知——插件不引入时主程序正常运行，遇到未知 type 时仅 console.warn 并跳过。组件定义需要包含以下接口：documentBuilder.registerComponent('paragraph', { shouldSplit: true, textContentSelector: '.db-block-paragraph', atomicSelector: '.db-atomic-inline', defaultAttrs: { fontSize: 14, lineHeight: 1.6 }, styles: '.db-block-paragraph { ... }', render: function (attrs, content, ctx) { // attrs: 合并了 defaultAttrs 和用户传入的属性 // content: 子节点数组（文本节点或内联元素） // ctx: { pageWidth, pageHeight, paper, margins } // 返回 DOM 元素 }, measure: function (el) { return { height: el.offsetHeight }; } });render 函数接收三个参数：attrs：合并了 defaultAttrs 和 JSON 中传入的 block.attrs，包含对齐方式、字体大小、间距等content：子节点数组，可能包含 { type: 'text', text: '...', marks: [...] } 文本节点，或 { type: 'inline-math', text: '...' } 行内公式ctx：渲染上下文，包含 pageWidth、pageHeight（像素值）、paper（纸张毫米尺寸）、margins（边距毫米值）主程序对组件的工作方式相当"粗暴"：调用 render → 获取 DOM → append 到页面 → 测量 offsetHeight → 判断是否需要分页。主题系统主题通过 documentBuilder.registerTheme(name, config) 注册，可以设定：默认纸张与边距字体定义（自动注入 @font-face）全局样式页眉页脚模板页眉页脚是比较复杂的一部分。主题中可以定义多个模板，并支持奇偶页不同、首页单独设置：footer: { defaultTemplate: 'default', oddEven: true, oddTemplate: 'odd-style', evenTemplate: 'even-style', templates: { 'default': { render: function(ctx) { ... }, styles: '...' }, 'odd-style': { render: function(ctx) { ... }, styles: '...' }, 'even-style': { render: function(ctx) { ... }, styles: '...' } } }页脚模板的 ctx 额外包含 formatPageNumber() 函数，支持阿拉伯数字、罗马数字、中文数字、字母序号等多种页码格式。配合 headerFooterPages.segments 配置，可以实现"第 1 页无页码，第 2 页起显示 '— 2 —'"这样的段式页码逻辑。分页与文字截断文档排版中最核心的问题是"文字太多，放不下了怎么办"。对于不支持跨页的组件（图片、表格、封面等），做法很简单：整个块搬到下一页。对于段落这类长文本，实现 TextSplitter 在离屏容器中做二分查找：将待截断元素的克隆放入测量容器用 TreeWalker 遍历所有文本节点（跳过 atomicSelector 标记的原子元素，如行内公式）从最后一个文本节点开始，逐字符移除，每移除一个字符检查高度找到"刚好放下"的分界点后，将文本拆为 fits 和 overflow 两部分fits 留在本页，overflow 放到下一页为什么是倒序逐字符？因为大多数场景下需要截断的文字并不多（可能只多了一两行），从末尾开始删效率最高。极端情况（整块文字几乎都不够放）下性能的确会差一些，但考虑到每页截断只发生一次，且文本长度通常不超过几千字，实测影响不大。原子元素（atomicSelector）——比如行内 KaTeX 公式——在截断时不会被拆开。它们会作为一个整体被移到 fits 或 overflow 中，保证公式的完整性。文字缩放在公文排版中，发文机关名称需要以醒目的大号红色字体显示在版心顶部。这里遇到一个问题：机关名称的长度变化很大，短则三四个字，长达十几个字，而版心宽度是固定的。解决思路来自活字排版中的"均匀"与"缩放"：模式 A：文字过短——分散对齐当文字宽度小于版心宽度时，用 letter-spacing 将文字均匀撑开：var totalPadding = containerWidth - textWidth; var ls = totalPadding / (charCount - 1); el.style.letterSpacing = (ls / MM_TO_PX) + 'mm';每个字之间的空白均匀分布，视觉上填满一行，类似报纸标题的"疏排"效果。模式 B：文字过长——横向缩放当文字宽度超出版心时，计算缩放比例后用 CSS transform 压缩：var scale = containerWidth / textWidth; el.style.transform = 'scaleX(' + scale + ')'; el.style.transformOrigin = 'center center';这里有个坑：如果文字所在的元素默认 stretch 撑满父容器，文字溢出只会往右侧延伸，缩放后就会偏右。解决方法是让容器 display: flex; align-items: center;，文字元素的宽度自动等于内容宽度，过长时向两侧均匀溢出，scaleX 后自然居中。钩子系统钩子是插件与主程序交互的另一种方式，适用于"不需要渲染具体内容，但需要在某个时机执行操作"的场景——比如水印插件在渲染完成后叠加水印图层。支持以下生命周期钩子：钩子触发时机用途举例beforeRender开始渲染前修改 JSON ASTbeforeBlockRender每个 block 渲染前动态调整属性afterBlockRender每个 block 渲染后追加额外样式beforePageBreak分页前记录分页位置afterPageBreak分页后更新目录结构afterRender全部渲染完成叠加水印、更新页码onComponentMount元素挂载到页面触发动画、懒加载钩子的注册与移除通过 on() / off() 方法操作，支持注册多个回调。写在最后DocumentBuilder.js 目前还是一个 alpha 版本，代码大约 1200 行，加上十几个插件总共不到 3000 行。功能上还有很多不完善的地方，[x] 基础排版与分页[x] 插件与主题系统[x] 公文标准（GB/T 9704）[x] 水印与封面[x] 信函格式[ ] 表格组件[ ] 目录生成[ ] 批量文档生成（PS: 待办组件博客还没写，懒得写这个其实 ╭(￣▽￣)"）后续会根据实际使用场景逐步完善。项目的完整源码和在线 Demo 可以访问 GitHub 仓库 document-builder。如果你对这类工具感兴趣，欢迎关注或参与贡献。
关于我的 API 小站点和公司官网前言在 2月13日 我曾在"说说"页面提到过，我要使用Go 语言开发一个面向开发者的在线api服务项目。好吧，这两天其实没有在开发这个项目。因为过年了不曾有空闲时间嘛(其实不是)。对于这个项目，理论上是要多服务器部署的，再加上我的网站肯定是微服务的嘛，使用容器部署是好一些。但是我又不想使用 k8s 这个工具。1Panel 的多机管理也不是很好。我就打算自己来实现一个多服务器容器管理系统，在api开发之前。API 开发正如前言所说，我正打算开发这个管理面板。现在已经完成了呃，不到20%，仅仅是用户认证和WebSSH写完了，容器的管理还是处于"新建文件夹"阶段。这个项目使用了 Echo 框架作为后端，前端本来是想使用某个 Vue.js 的管理系统模板的，但是因为 vue 和后端不在一个服务，整了半天没搞好跨域给我整崩溃了。最后使用了 光年后台管理系统 模板，这个是纯 HTML 的，我所使用的是基于 bootstrap5 和 jQuery 的 iframe 版本，虽然这套配置在今天略显过时。但是不妨碍它作为管理系统前端。但也并非没有好处，好处就是有更快的加载速度，更高的 SEO 性能(有什么用啊这⦁֊⦁꧞)API 服务简述好了，来说说 API 服务网站的简介吧。我为其注册了一个域名 wenziit.com 我欲将其运行在 api.wenziit.com 上，其前端我想使用 Google 的 MD3 设计风格，很简洁的。对于 api 我也会提供文档、事例代码和SDK，方便大家进行开发。接口上，也会比较全面，比如文本类的 IP 取属地、取天气、取QQ信息等。图片类的 图片压缩、图片加水印 等，文件类的 PDFtoWord 等。关于收费这一块，暂时并不打算，只是限制一下 QPM，毕竟我也只有一台服务器嘛。宕机了就彻底没了。我也会顺便去做一个 tool.wenziit.com 在线工具箱网站，可以在线可视化使用 api 的各种服务，这个也是免费的，登录即可免费使用。关于公司官网嗯，这个嘛。虽然说暂时还没有公司，只有一个工作室(个体户)。啊，那我做这个何意味？闲的嘛？好吧，毕竟做都做了，我参考了挺多网站，最后做了一个国企稳重风格的，目前是仅前端，后面会与我的 docker 管理器结合一下，可以直接在管理系统里管理网站后台，就不是基于 Typecho 的了，而是 Go Template. 官网截图也看看吧，好吧，西安市文梓信息技术有限公司。这个名字呢，取自《太平御览》啥意思的话，建议自己上 bing.com 搜索一下，不要上百度呀。logo呢，也是自己5分钟设计的 SVG logo，我的审美几乎跟没有一样，所以并不好看。字体是思源宋体Bold，大字重，开源的。这个网站呢，还有一个党建专栏，整体上没有太多卡片设计，我注意到别的科技网站也没有，所以我也没有。有多语言支持和无障碍支持，可以说是很"企业"了。也支持大部分主流浏览器(别提IE( ⩌ - ⩌ ))碎碎念好了，感觉写了很多了嘛，其实也才没多少。也不知道考场上800字作文我是咋硬写出来的。简述一下就是 妈妈、暴雨、夜晚、发烧、我、医院 这公式法做题就是快。啊，对了，还记得我最初其实是想做小说网站的嘛？这个嘛，主要是我把网站写出来也不一定有人去写小说呀，是不是。最后，快要开学了，我的作业才完成 1/2, 然后 duolingo 嘛，本来说是要打卡的，结果一个寒假就打卡了两天，俄语还是太难了呜呜呜。(毕竟战斗民族的语言嘛)学习的敌人是自己的满足，要认真学习一点东西，必须从不自满开始！——毛泽东(全文完)
“写博客已经够难了，如果还要和丑陋的主题搏斗，那简直是双重暴击。”—— 某位被前任主题伤透心的博主（也就是我）简介我在 关于 页面曾提到过 Inaline 主题，也就是现在博客使用的主题。整体使用了类似 Card 的风格，设计上还参考了 Joe 主题。Joe 确实是一款不错的主题，但是原作者放弃了更新，而且也不支持最新的 Typecho 版本了。你就必须在安全性与主题之间做选择。而 Inaline 主题。在于它不仅美观、安全，而且支持了最新的 Typecho 版本，并在持续不断的保持更新。接下来放几张截图，来欣赏一下 Inaline 主题的设计。Inaline 主题支持了桌面端和移动端的 UI，且并不是仅仅停留在「能用」的层面，而是针对移动端，对操作逻辑和控件大小都做了优化。使用了侧边栏和各种展开栏来在不减少功能的情况下支持移动端，字体也缩放的恰到好处。为啥选择 Inaline 主题性能全站 服务端渲染 + 静态缓存，没有花里胡哨的 React Hydrate。还预计支持 RESTful APIJS 和 CSS 加起来不超过 100KB, 不依赖 jQuery，加载速度是👍这个功能Inaline 主题在不破坏 Typecho 的原生后台的情况下，针对后台做了许多优化。编辑器新增了 网易云音乐歌单插入、单曲插入、B站视频插入、网盘链接插入、数学公式插入、卡片插入 等功能和可视化的语法。编辑器添了行号，支持自动折行，让你不用移动光标来判断「这™的到底在不在一行啊」颜值Inaline 主题非常现代化，支持了暗色模式，并能根据用户的浏览器设置自动切换。还可以在后台设置。别的不说，是比那些添改还得修改源码的主题强。SEO 友好自动生成 JSON-LD 结构化数据，Google 搜索直接出“富卡片”。外链 Sitemap 可以直接提交搜索引擎的站长工具。如何安装?说这么多其实大部分人也不想听我吹，接下来就是重点了，快从裤兜掏出小本本记上(｡>∀<｡)环境要求PHP ≥ 7.4（Typecho 1.2+）MySQL ≥ 5.7伪静态(URL Rewrite) Nginx/Apache 均可一键安装通过 Git 安装(推荐)依次执行如下两条命令$ cd path/to/typecho/usr/themes $ git clone https://gitee.com/inaline/typecho-theme.git inaline通过文件安装本方法可能安装的不是最新版本，因为我可能会忘记更换链接。从 Gitee 或 Github(下文会有链接) 下载 release 或从本站下载 .zip解压后得到一个 inaline 文件夹将它复制到 cd path/to/typecho/usr/themes启用主题后台 → 控制台 → 外观 → 启用 → 搞定！ 启用主题后，必须点击主题设置保存一次，不然默认设置不会生效 项目传送门Gitee /Inaline/typecho-themeGithub /Inaline/typecho-theme喜欢的话，点个 Star, 蟹蟹(｡>∀<｡)
简介今天注册了 梓樱.我爱你 域名，说什么也不能让它闲着吧，毕竟这 60 CNY 可不能就买一行只能看的文字。所以，我就稍稍花了一点儿时间，使用 HTML 开发了一个表白网页，可以这么说吧。也是非常的适合 .我爱你 域名了。我没有选择使用PHP等语言去开发一个带管理后台的版本，虽然那样用起来更方便，功能也更强大。但是纯静态可以白嫖 Github Pages/Cloudflare Pages. 也就是说省去了服务器的费用。废话不多说，不多说废话; 多说的都是废话, 不多说的都不是废话。上截图吧。~(￣▽￣)~*如你所见，整个 UI 偏向二次元的风格，支持桌面端和移动端的响应式。整个项目也就只有两个页面。页面的设计上，有点儿小清新的感觉，然后有一个 svg 的 梓樱.我爱你 logo, 这个 logo 是之前的一个 python 脚本生成的，可以把字体里的单字转为矢量图，虽然说字体本来就是。点击开始预览后，程序会弹出提示问你要不要播放背景音乐，也是十分的贴心了呢。然后就会以打字机效果来展示第二张图片中的文字。想自己看看的话见 在线演示技术栈开发这个网页，我选用的原生的 HTML, JavaScript, CSS. 没有使用任何框架, 所以顶多可以说是个半成品吧。开发本项目用到了如下开源项目:MDI 图标库Sweetalert2 弹窗提示库Aplayer 音乐播放器下载地址两个网盘你都可以 免登录、不限速 下载哦%%{"type": "netdisk", "data": {"type":"lanzou","filename":"梓樱.我爱你 HTML 表白网页.zip","url":"https://wwp.lanzoup.com/ibDeM3i8u5uf","code":"6rd2"}}%%%%{"type": "netdisk", "data": {"type":"openlist","filename":"梓樱.我爱你 HTML 表白网页.zip","url":"https://openlist.inaline.net/@s/a9D6QltJ","code":"1145"}}%%