前段时间在给一家半导体制造相关的企业做内部知识库改造时,接到一个听起来很细、实际让多个工程师挠头的问题:CAD 图纸粘贴到 TinyMCE 里之后,要么糊成一片位图,要么干脆粘贴失败,更别提矢量输出了。这里说的“矢量输出”不是论文里的漂亮词,而是芯片制造企业内部评审、问题单流转、工艺文档归档里真正卡脖子的需求——掩膜上一条线宽差零点几微米,如果图纸在文档里变成像素点,评审时稍微一放大就看不清是设计意图还是缺陷。当时我盯着这个诉求,把从剪贴板到编辑器内容模型的整条链路翻了个底朝天。
这篇总结不讲空泛的“如何用好富文本”,只讲清楚一件事:芯片企业场景下,CAD 图纸如何以矢量形态稳定进入 TinyMCE,并且在后端回存、前端渲染、权限控制上都站得住脚。如果你也在做制造行业的知识管理系统、质量追溯系统或者研发协作平台,大概率会踩到同样的坑。
1. 为什么芯片制造企业必须较真“矢量输出”
1.1 位图和矢量图在版图/图纸评审里的真实差异
先明确一下场景:芯片制造企业里的 CAD 图纸,通常不是普通的机械零件图,而是版图(mask layout)、封装基板图、光刻层结构,或者设备的工装夹具图。这些图纸有一个共同特点:看图纸的人是要对着坐标和尺寸做判断的。
位图(PNG/JPG)在屏幕上看起来没问题,但它在保存时已经被“拍照”了。你粘贴进 TinyMCE 的那一瞬间,CAD 软件里本来连续的几何路径、图层属性、线型,以某个固定分辨率被点阵化。这个分辨率一旦低于 150 DPI,掩膜上细微的互连线条在放大后就直接糊成一团。即便你超高清截图,位图也无法承载图层信息——工艺工程师看到的是“一张图”,而不是“第一层金属走线、第二层多晶硅、接触孔”这些可以被检索和判断的独立元素。
矢量输出的价值在于,图纸里的每条线、每个端点、每层多边形都以数学描述保存。放到 TinyMCE 里之后,它不是一张静态照片,而是一种结构化的图形数据:可以无损缩放、可以按图层改颜色、可以在评审时按需强调某一块区域。芯片制造企业的图纸一旦进入知识库或问题单系统,下游还要做尺寸比对、异常定位、差异标注,矢量数据几乎是唯一能被机器继续处理的形态。
1.2 TinyMCE 默认粘贴链路里,矢量信息是怎样丢掉的
很多人在编辑器里粘贴图片时,只看到一张图进来了,好像挺正常。但如果你打开浏览器开发者工具,查看剪贴板的实际数据结构,就会发现链路远比想象中复杂。
CAD 软件复制图形时,通常会向系统剪贴板写入多种格式:原生矢量格式(EMF/WMF/SVG)、位图格式(PNG/BMP)、纯文本描述等。浏览器读取剪贴板时,不同操作系统和浏览器对格式的支持差异很大。TinyMCE 在默认配置下,只会把剪贴板里浏览器能识别为 image 的类型(往往就是 PNG)作为图片插入,而 SVG、EMF 这类矢量数据在大多数情况下根本不进编辑器的内容模型。
这还没完。即使你把 SVG 文件直接拖进 TinyMCE,默认的 xss_sanitization 和 valid_elements 规则也可能把 SVG 里的 fill、stroke、viewBox 等属性清理掉,或者因为不允许 svg 作为 body 的直接子节点而直接丢弃。所以很多时候用户抱怨“我粘贴失败”,实际不是剪贴板没数据,而是编辑器的安全策略把数据当作非法内容拦住了。
1.3 芯片场景里,一份“合格”的矢量图纸该满足什么标准
结合我做过的几个内部系统,芯片制造企业对“矢量图纸进文档”的验收标准可以归纳成这几点:
- 加载到文档里以后,放大到 500% 依然没有锯齿、没有模糊。
- 图层颜色和 CAD 原图一致,金属层、多晶硅层、有源区不能因为导出配置而混成同一种颜色。
- 保存到后端时,矢量数据本身是自包含的,不依赖外部图片链接或者 CAD 软件环境。
- 文档系统里的其他用户不需要安装 CAD 软件,打开页面就能顺畅查看和缩放。
- 不能被注入脚本,SVG 里的恶意内容(事件属性、script、外部实体引用)必须被拦截。
这些条件放在普通办公文档场景里可能是“加分项”,但在芯片企业的工艺文档里是“底线”。有了这个结论,后面的技术方案都围绕这五条展开。
2. 技术选型:为什么是 SVG 而不是 DXF、DWG 或 EMF
2.1 直接内嵌 CAD 原生格式为什么不现实
有人会问:既然要矢量,为什么不把 DWG/DXF 文件直接当附件放进 TinyMCE?原因很简单:TinyMCE 的内容本质是 HTML,浏览器不可能原生渲染 DWG/DXF。如果强行做成附件链接,评审人员等于要下载文件再打开 CAD 软件,整个流程的即时性就没了;而且 DWG/DXF 里常带有字体、块定义、外部参照,在 Web 端根本无法保证显示一致性。
EMF 是 Windows 平台常见的矢量图元文件,浏览器并不原生支持,而且芯片行业跨 Windows/Linux 的情况很普遍,一台 Linux 服务器上的文档库里如果存了 EMF,同事用浏览器打开时大概率是一张空白或乱码图。SVG 是唯一一个能被浏览器原生解析、可以直接内嵌在 HTML 中、同时还能保持矢量数学描述的矢量格式。从工程角度看,这是零运行时依赖的选择。
2.2 芯片版图导出 SVG 的三条实际路径
芯片制造企业手里的图纸来源并不只有一种,我把它粗分为三类:
第一类是真正的 IC 版图,通常来自版图设计工具。版图数据本身常见是 GDSII/OASIS 格式,需要转换成 SVG。GDSII 里的几何形状是边界多边形,天然适合转成 SVG path;OASIS 稍微复杂一些,但也可以通过解析库转成 polygon 集合。如果团队里没有人写解析代码,很多版图工具也支持直接导出 SVG,只是导出时要注意图层映射和精度舍入。
第二类是机械结构类 CAD 图纸,比如光刻设备上的治具、装配体,这些通常用常见 CAD 软件打开。这类软件导出 SVG 时要特别留意标注文字、线型比例和虚线样式,很多机械图纸的虚线和中心线在导出后会被合并成同一样式,导致下游理解困难。
第三类是封装设计图纸,介于版图和机械图之间,常见格式有 DXF 导入后二次编辑。处理思路和第一类类似,但封装图里往往有焊盘坐标和位号文字,导出 SVG 时必须让 text 元素保留可读性,不能转成 path 一笔带过。
不管哪种来源,导出后都要做一次几何校验:打开生成的 SVG,把关键尺寸和 CAD 原图做抽样比对。这一步不能省,因为芯片领域一个坐标点差 1um,到了掩膜阶段就是整片失效。
2.3 图层到颜色的映射规则:不是随便给个色号就完事
许多做 CAD 转 SVG 的人只关心图形轮廓,却忽略了图层语义。芯片图纸里,图层并不是“方便显示的分组”,而是掩膜层的映射。以版图为例,金属1、金属2、多晶硅、接触孔、钝化层,每一层对应一道独立的光刻工序。如果导出 SVG 时把不同层映射成了相近的颜色,工艺人员一眼看去以为结构正常,实际却是两层连在一起,这是评审事故级别的低级错误。
我在方案里坚持做一套图层-颜色映射表,放在导出配置的最前面。映射表至少包含:层名称、GDS 层号、数据类型、SVG 填充色、描边色、透明度。建议把金属层统一用冷色系、介质层用暖色系、孔层用中性色,并保持与原版图工具的显示色一致。如果你用的版图工具能导出 layer color map,优先直接复用,别自己在导出工具里重新配色。
3. TinyMCE 端改造:让编辑器真正认识和容忍 SVG
这一部分是很多团队踩坑最多的地方。SVG 不是不能进 TinyMCE,而是默认配置从来没把它当成有效内容。
3.1 配置 valid_children 与 extended_valid_elements
TinyMCE 的 schema 对 HTML 元素有严格约束。默认情况下,body 下不允许直接出现 svg 子节点,所以你必须告诉编辑器:“SVG 这个元素在我的内容模型里是合法的,而且我要保留它的属性”。
我常用的配置片段如下:
tinymce.init({ selector: '#contentEditor', extended_valid_elements: 'svg[viewBox|xmlns|width|height|preserveAspectRatio],path[d|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|fill-opacity|style],g[transform|fill|stroke],rect[x|y|width|height|rx|ry|fill|stroke],circle[cx|cy|r|fill|stroke],line[x1|y1|x2|y2|stroke],polyline[points|fill|stroke],polygon[points|fill|stroke],text[x|y|font-size|font-family|fill|transform],tspan[x|y|dx|dy|text-anchor|font-size|font-family|fill]', valid_children: '+body[svg],+svg[g|path|rect|circle|line|polyline|polygon|text|tspan],+g[g|path|rect|circle|line|polyline|polygon|text|tspan]', custom_elements: 'svg,path,g,rect,circle,line,polyline,polygon,text,tspan' });这里最容易被忽略的是 valid_children。TinyMCE 不检查 SVG 内部嵌套的合法性时,你粘贴进去的 group 嵌套结构会被抹平,导致图形错乱。上面的配置里我特意声明了 svg 允许容纳哪些子元素、g 又允许继续嵌套哪些元素,否则一张多层次的版图 SVG 进去,group 树被拆散后,图层叠放顺序就全乱了。
3.2 自定义粘贴插件:接管剪贴板里的 SVG 与位图
配置了 schema 只是让编辑器“不拒绝”SVG,真正让用户粘贴进来的是自定义插件。TinyMCE 提供了 paste_preprocess 和 paste_postprocess 两个钩子。我的做法是在 paste_postprocess 里检查粘贴进来的内容,如果是位图,调用后端接口做一次 SVG 匹配查找;如果是 SVG 的 HTML 片段,则先过安全校验再放行。
核心思路是:剪贴板里如果同时有 CAD 矢量数据和位图数据,优先用矢量数据,不要拿位图充当内容。实现层面,我监听了编辑器粘贴事件,读取 clipboardData 里所有可用类型。
editor.on('PastePreProcess', function (e) { var clipboard = e.clipboardData || window.clipboardData; var items = clipboard && clipboard.items; if (!items) return; var svgFound = false; for (var i = 0; i < items.length; i++) { if (items[i].type === 'image/svg+xml') { var item = items[i]; var file = item.getAsFile(); if (file) { var reader = new FileReader(); reader.onload = function (evt) { var svgText = evt.target.result; // 这里交给安全校验函数,通过后才插入 if (safelyValidateSvg(svgText)) { editor.insertContent(wrapSvgAsHtml(svgText)); } else { editor.windowManager.alert('SVG 内容未通过安全校验'); } }; reader.readAsText(file); e.preventDefault(); svgFound = true; break; } } } // 如果没有 SVG,只是普通位图,让默认逻辑继续 if (!svgFound) return; });这里有个细节:有些 CAD 软件复制时写入的 SVG 是独立的<svg>文档格式,带有<?xml version="1.0"?>声明,直接 insertContent 进 TinyMCE 可能会解析出错。稳妥的方法是先解析为 DOM,剥离 XML 声明,再转换成 HTML 片段字符串插入。
3.3 保存入库前的 SVG 安全校验:不是“写个正则”那么简单
SVG 本质是 XML,可以被塞进各种危险载荷。芯片企业图纸系统通常在内网,但这不代表可以放松安全。恶性案例里出现过有人用 SVG 内嵌 script 窃取登录票据,也出现过外链引用了被污染的公共图片地址导致页面崩溃。
我的安全校验函数至少做四件事:
- 解析 SVG 文本为 DOM 对象,如果 parse 失败直接拒绝;
- 递归遍历所有节点,只要出现 script、foreignObject、iframe、object、embed 就拒绝;
- 删除所有 on* 事件属性,包括 onclick、onload、onmouseover,一个不留;
- 检查所有 href/xlink:href 属性的值,只允许 data:、http/https 且域名在白名单内,其他一律剥离。
这个函数必须同时在前端拦截一次、在后端再执行一次。前端的拦截是为了给用户即时反馈,后端的校验才是真正的安全边界。
4. 粘贴链路全流程实现:从 CAD 复制到入库回显
4.1 一次完整粘贴的流程拆解
把上面的各个点串起来,一次“CAD 图纸粘贴到 TinyMCE”的完整流程是这样:
用户在 CAD 软件里选中图形并按复制键 → CAD 向系统剪贴板写入 SVG、PNG、EMF 等多种格式 → 用户在 TinyMCE 里按下粘贴快捷键 → 浏览器触发 paste 事件,clipboardData 暴露可用类型 → 自定义插件检查是否存在 image/svg+xml 类型 → 存在则读取 SVG 文本,执行前端安全校验 → 校验通过后把 SVG 以 HTML 形式插入编辑器内容模型 → 用户点击保存 → 前端把包含 SVG 的内容提交给后端 → 后端再次执行 SVG 安全校验并入库 → 其他用户打开文档时,浏览器原生渲染 SVG,可以任意缩放和检索。
其中最容易断的环节有三个:CAD 软件是否真的往剪贴板写 SVG(很多老版本软件默认只写 EMF 和 PNG)、浏览器是否允许读取 SVG 类型的剪贴板内容(某些浏览器出于安全限制只开放 image/png)、TinyMCE 的 schema 配置是否正确(配置错了会静默丢内容)。
4.2 处理“剪贴板里只有位图”的降级方案
不是所有 CAD 软件都能往剪贴板里放 SVG。我在实际项目里就遇到一个老版本工具,复制出来的格式只有 PNG 和 EMF,而 EMF 在 Linux 服务器上根本没法用。这时要有一个降级方案:如果是位图,不让它直接进编辑器,而是先触发一次“矢量查找/导出”请求。
具体做法是,拿到用户粘贴的位图数据,连同用户在 CAD 里的当前图层信息一起发给后端;后端根据这个位图的哈希值或文件特征,去关联服务器上已导出的 SVG 文件。很多制造业企业的工作流是固定图纸编号的,同一张图经常被重复复制,只要第一次导出了 SVG,后续再粘贴就可以从缓存里直接取 SVG 回填到编辑器。
如果后端确实没有匹配的 SVG,就给用户一个明确提示:“当前剪贴板只有位图,请先在 CAD 中导出/复制 SVG,或使用上传功能上传工程图”。不要把一张分辨率不够的位图默默贴进去,否则评审时一定是坑。
4.3 编辑器内 SVG 与文字、表格混排时的注意点
TinyMCE 默认是流式排版,插入一个超大 SVG 时会被当作内联元素或者行内块,经常撑破表格布局。我强烈建议把插入的 SVG 统一包一层容器 div,并设置样式控制最大宽度和居中。
<div class="cad-vector-block" style="max-width:100%; margin:16px auto;"> <svg viewBox="0 0 2000 1500" width="100%" preserveAspectRatio="xMidYMid meet"> <!-- 版图路径数据 --> </svg> <p class="cad-vector-caption">图形名称 / 层号 / 版本号</p> </div>这里有一个容易被忽视的问题:SVG 的 viewBox 宽高比和原始 CAD 图幅不一致时,直接设 width=100% 会导致图形被拉伸变形。正确做法是始终保留 viewBox,并用 preserveAspectRatio 控制缩放策略。caption 里的版本信息建议使用<p>而不是 SVG 内部<text>,否则文档检索系统无法索引中文标题。
5. 上线后最常踩的坑与排查实录
5.1 SVG 粘贴后被自动转成 PNG 的问题
这是出现频率最高的问题。原因往往不是 TinyMCE 本身,而是剪贴板事件处理顺序。某些版本的 TinyMCE 自带 paste 插件会把image/svg+xml识别成普通图片,再用默认的图片处理逻辑把它转成 base64 位图。排查时先看编辑器内容:如果插入后被包在<img src="data:image/png;base64,...">里,基本就是这个问题。
解决办法是在插件优先级上做处理:在PastePreProcess里直接调用preventDefault(),并且不要调用editor.execCommand('mceInsertClipboardContent')让默认逻辑继续执行,而是自己接管插入流程。同时检查 TinyMCE 配置里paste_data_images是否设置为 false,避免编辑器把剪贴板内的图片数据一律视为位图处理。
5.2 图层颜色导出后全部变成黑色或同色
芯片版图常见的骗局是:GDS 转换 SVG 的工具默认没有做图层映射,所有 polygon 全用黑色填充。当场看图就像一份“黑白负片”,有源区和金属区根本分不清。这个问题在导出流程里就要堵住,不要等到 TinyMCE 里才发现。
我在项目中固定了一个校验步骤:SVG 导出后,统计 SVG 中存在的 fill 颜色总数,如果小于 CAD 源文件里的图层数,直接判定导出失败并告警。这个规则很粗暴,但非常有效,能拦住 90% 以上的图层丢失问题。
5.3 大图纸 SVG 导致编辑器卡死或页面崩溃
版图 SVG 动辄几十 MB 是很正常的。直接把几十 MB 的 SVG 文本塞进 TinyMCE,编辑器和浏览器基本都会卡到无法操作。这块我采用的策略是“预览与全量分离”:编辑器里只放一个低精度预览版 SVG(可以保留全部几何轮廓,但简化坐标精度到 0.01mm 或者只在 viewBox 里保留轮廓),真正的全量 SVG 入库后作为附件关联。
如果评审时必须看全量,利用 SVG 的use标签按需加载外部文件,或者其他方式异步获取。但注意,TinyMCE 的文档内容是要被搜索引擎和检索系统索引的,所以正文里放预览 SVG、附件链放全量 SVG 这个折中方案在制造业系统里最容易落地。
5.4 保存后再打开发现 SVG 被过滤或丢失
进一步追踪发现,很多团队在后端入库时用了第三方的 HTML 清理库,这些库默认把svg、path等元素视为危险内容,清理时直接移除或转义。这是“入库丢图”的最大元凶。解决方式是,在后端 HTML 白名单里显式放行 SVG 相关元素和属性,同时不要对整个 HTML 做全局的 xss 清理,而是只对非白名单区域做处理。
我在实际项目里的做法是:入库时把整个文档中的<svg>...</svg>块先抽取出来单独存储,剩余 HTML 走常规清理,保存时再用安全校验后的 SVG 块拼接回去。这样既能防止脚本注入,也不会误杀合法 SVG。
另外一个隐藏很深的问题:TinyMCE 的 content_css 或者浏览器默认svg { display: none; }样式会导致编辑时能看到图、发布后看不到图。排查这类问题要打开浏览器开发者工具看 SVG 元素的实际 CSS 计算样式,而不是盯着内容面板猜。
5.5 问题速查表
| 现象 | 常见根因 | 排查方向 |
|---|---|---|
| 粘贴后变成 PNG | paste 插件默认处理 | 检查 paste_data_images、PastePreProcess 是否接管 |
| 粘贴无反应 | CAD 未写 SVG 到剪贴板 | 在本机另存 SVG 并拖拽测试 |
| 图纸进来自动变形 | viewBox 缺失或宽高比例不对 | 检查导出的 SVG 是否有 viewBox 属性 |
| 颜色全黑/混色 | 图层映射表缺失 | 比对源图层数与导出颜色数 |
| 保存后 SVG 丢失 | 后端 HTML 清理库误杀 | 白名单放行 svg/path 系列元素 |
| 编辑器明显卡顿 | SVG 体积过大 | 预览版与全量版分离 |
| 有图但发布后不显示 | CSS 隐藏了 svg 默认 display | 检查全局样式表 |
| 打开文档报脚本警告 | SVG 内嵌了事件属性或外部链接 | 加强前后端双重校验 |
6. 一些更深的实践经验
如果只看上面的配置和代码,你可能觉得这套方案已经完整了。但真正在生产环境稳定跑下来,还要补几个容易被忽略的细节。
后端存储时,建议把 SVG 的命名规范和版本管理纳入文档系统的主线。CAD 图纸是会迭代的,同一个图号可能一周内更新三四次。知识库里如果只认“最新一张”,历史版本里的评审意见就丢了依据。我在这套方案里做的是:SVG 块本身携带图号、版本号、导出时间这三个元数据属性,保存时作为 HTML 自定义属性写进<svg>标签。这样即使以后从 TinyMCE 里导出 PDF 或者归档到别的系统,图纸的身份信息也不会丢。
关于 CAD 侧复制行为,我还做了一点补充:很多制图人员习惯在 CAD 里全选图纸再复制,此时剪贴板里会带上一堆屏幕外的空白区域或者辅助线。进入 TinyMCE 后你会发现图纸四周是巨大空白,比例完全失调。这个问题的源头在 CAD 导出,所以我在使用规范里要求:导出 SVG 前先框选有效图纸区域,或者使用 CAD 的“打印窗口”模式,把有效范围限定好。这个操作听上去很基础,但实际项目中 80% 的显示比例问题都是这里引起的。
还有一点是关于浏览器兼容性。TinyMCE 本身跨浏览器没问题,但 SVG 剪贴板数据的读取在不同浏览器里差异很大。Chromium 系浏览器对剪贴板格式支持最好,基本能读到 image/svg+xml;某些相对严格的浏览器版本会只暴露 text/html。如果真的遇到拿不到 SVG 但又确定剪贴板里有,需要引导用户改用“直接拖拽 SVG 文件进编辑器”的方式,插件里同样要监听 drop 事件,逻辑和 paste 处理保持一致。
最后说一个我踩过几次坑之后总结出来的小技巧:在开发环境调试 SVG 粘贴问题时,别光看编辑器界面上有没有图,要随时打开浏览器开发者工具,把编辑器 iframe 里的 HTML 片段取出来检查。很多 TinyMCE 的清理动作是发生在内容模型层面的,界面上看着正常,但底层的 DOM 已经被悄悄改过。从 iframe 的contentDocument里拷贝一段内容出来,和 CAD 原始 SVG 做 diff,能最快定位问题出在预处理、粘贴回调还是保存策略。这个习惯,比看一百遍配置文档都管用。