半年前,我们帮一家芯片制造企业改造内部研发文档系统,碰到一个特别具体又特别折磨人的问题:工程师在CAD里画好的封装图、版图示意、装配工序图,复制之后直接粘贴到TinyMCE编辑器,出来的图总是“扁扁的”位图,放大就糊,换台电脑打开更糊,有些细线甚至直接消失。项目方给的要求非常明确——CAD图纸粘贴到TinyMCE之后,必须是矢量输出,不能是像素图。
一开始我以为这只是一个插件配置问题,查了一圈才发现,这件事涉及剪贴板机制、浏览器安全策略、TinyMCE的HTML schema校验,以及CAD端该怎么配合导出。今天就把我走通这条路的过程完整写出来,重点讲清楚为什么直接粘贴拿不到矢量、怎样让TinyMCE真正支持SVG、以及CAD图纸在芯片制造场景下转矢量的具体做法。这篇内容适合在搞文档协同、知识库、流程制造系统的工程师看,也适合被“CAD图贴进CMS后始终不清楚”逼疯的产品和IT同学。
1. 芯片文档场景下,CAD图“粘贴”进TinyMCE为什么必然丢矢量
先说结论:只要走浏览器标准粘贴流程,TinyMCE从剪贴板里拿到的就不是CAD的矢量数据。这跟TinyMCE本身没关系,是浏览器在中间做了一层“翻译”,把矢量信息丢掉,转成了PNG。理解这一点,你才会明白,后面所有方案都绕不开“换一种进图方式”。
1.1 剪贴板里的CAD图纸其实带着“矢量底稿”
你在CAD里选中图形,按下Ctrl+C,CAD程序并不是只在剪贴板里放一张图片。它会同时写入好几种格式,用来兼容不同目标程序。以Windows平台为例,典型会写入:
| 剪贴板格式 | 说明 |
|---|---|
| Native CAD格式 | 专有格式,只有CAD自身认识 |
| EMF / WMF | 矢量元文件,几何信息保留完整 |
| DIB / Bitmap | 位图快照,作为兼容兜底 |
| PNG / JPEG | 某些CAD版本会额外输出 |
EMF是Windows上很有历史的矢量元文件格式,理论上你能从剪贴板里拿到它,而不是一张位图。AutoCAD、中望CAD、浩辰CAD这些桌面程序,在复制对象时都会尽量把EMF写入系统剪贴板,让Word、PPT这类能识别EMF的程序获得矢量数据。所以“剪贴板里没有矢量”这个说法并不准确,矢量底稿其实一直在。
1.2 浏览器为什么只把图当位图交出来
问题出在浏览器这一侧。Chrome、Edge、Firefox这类浏览器内核,出于安全和跨平台考虑,并不会把Windows系统剪贴板里的EMF/WMF直接暴露给网页。浏览器不认识EMF,也不打算去解析它。当网页触发paste事件,页面通过e.clipboardData去读取内容时,浏览器会做一个合理的简化——把能显示的图像统一转成PNG。
这个简化过程,相当于“系统剪贴板里有一堆格式,浏览器只挑了位图那一层,再封装为PNG给你”。你已经看不到EMF那一路了。所以TinyMCE收到的粘贴内容,就是一张位图加一堆HTML片段。任何基于标准paste事件的纯前端方案,想从剪贴板里直接挖出CAD矢量,都是在跟浏览器安全模型作对。
navigator.clipboard.read()理论上能读更多文件格式,但它需要用户授权,浏览器对自定义格式的限制依然很严,而且实际把EMF读出来后,前端也没有能力直接把它插入TinyMCE的可编辑区。这条路我试过,不实用。
1.3 位图对芯片图纸意味着什么:精度、体积与二次流转损失
芯片制造行业的图纸,和普通建筑机械图纸最大的区别在于细节密度极高。一条走线可能只有0.05毫米宽,一个BGA封装的锡球排列密密麻麻,一张中等复杂的封装图可能包含上万个图元。如果只是最终打印出来看,位图勉强能应付;但放在文档系统里做在线评审、变更对比,位图的缺点会被放大得非常明显。
- 精度损失:位图刷新率取决于分辨率,96dpi下导出的PNG,一放大就模糊,细线在缩放后会变成断续的虚线;
- 体积失控:保持局部细节清晰,PNG动辄几十MB,浏览器加载卡顿,文档系统数据库也遭殃;
- 后续处理困难:位图上不能单独高亮一条走线,不能按图层隐藏,不能做坐标标注,也没办法做版本Diff,更没办法让下游系统自动识别引脚名称。
这三点在芯片文档系统里都是致命伤。所以“只要清晰就行”这种传统思路在这里不成立,矢量输出不是锦上添花,是硬需求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用SVG当统一载体:TinyMCE的“矢量通道”怎么打开
既然浏览器标准粘贴拿不到CAD矢量,就换个思路:让CAD图纸在进入TinyMCE之前,先变成SVG。SVG是浏览器原生支持的矢量格式,TinyMCE也认它。但默认情况下,TinyMCE并不会让你随便往内容里塞SVG,它的HTML校验器会过滤掉这些“非HTML”标签。你需要给TinyMCE开一个口子。
2.1 TinyMCE默认tag白名单里没有SVG
很多同学第一次这样做时,会直接把一段SVG字符串扔给editor.setContent(),结果发现要么被清空,要么只剩一段空白,要么SVG标签被拆得七零八落。这真不是TinyMCE故意找茬,而是它为了安全,默认只放行标准HTML标签。TinyMCE内部有一张schema表,凡是表里没有的标签,默认都会被当作非法元素肢解掉。
<svg>、<path>、<rect>这些标签,在TinyMCE看来都是“来路不明的陌生人”。你需要在初始化配置里,通过extended_valid_elements、valid_children和custom_elements三个参数把它们加进白名单,并明确告诉TinyMCE:这些标签允许出现在内容区,允许作为某些节点的子节点。
2.2 一批安全的SVG标签/属性白名单
下面是我在项目中实际用到的TinyMCE初始化配置,覆盖了CAD图纸转SVG后最常用的标签。你不用照抄,但可以作为底稿。
javascript复制tinymce.init({
selector: '#editor',
height: 700,
plugins: 'paste',
extended_valid_elements:
'svg[*],' +
'defs[*],symbol[*],marker[*],mask[*],pattern[*],' +
'g[*],path[*],line[*],rect[*],circle[*],ellipse[*],' +
'polyline[*],polygon[*],text[*],tspan[*],textPath[*],' +
'use[*],clipPath[*]',
valid_children:
'+body[svg],' +
'+div[svg],' +
'+svg[defs|g|path|line|rect|circle|ellipse|polyline|polygon|text|use|clipPath|symbol|marker|mask|pattern],' +
'+g[g|path|line|rect|circle|ellipse|polyline|polygon|text|use|defs],' +
'+text[tspan|textPath],' +
'+defs[g|path|rect|circle|ellipse|clipPath|marker|mask|pattern|symbol]'
});
注意,[*]表示允许标签上的所有属性。这种做法在可控内网、图纸来源可信的前提下,配置成本最低。但如果你的系统会接收外部上传的PDF、SVG、图纸,就不能用这种松散的写法,必须走严格的属性白名单,比如只允许svg[width|height|viewBox|preserveAspectRatio]、path[d|stroke|fill|fill-rule|transform]、rect[x|y|width|height|stroke|fill|rx|ry]这种。宁可多花时间维护白名单,也别把执行入口敞开。
2.3 内容安全的底线:拒绝脚本和事件属性
SVG不是纯图片格式,它支持<script>标签,也支持onload、onclick这些事件属性。如果让恶意的SVG混进TinyMCE,等于让攻击者在你内部文档系统里跑脚本。而CAD图纸转出来的SVG,虽然大概率是“干净”的,但一旦文档系统允许多人上传,你就要把所有人当成潜在风险源来处理。
推荐的做法是独立写一个sanitizeSvg()函数,在SVG插入编辑器之前,统一做三道清理:
- 删除所有
<script>节点; - 删除所有以
on开头的事件属性; - 删除
href或xlink:href属性里带javascript:前缀的危险链接。
这步不能省。我在实际项目中见过一位同事直接把从网上下载的素材SVG粘贴进编辑器,里面藏着一句<script>fetch(...)</script>,如果不是提前做了消毒,后台管理员的Cookie可能就漏了。
3. 常见平台中,从CAD图纸到SVG文件的落地路径
现在进入正题。要让TinyMCE拿到矢量图纸,你至少需要一个环节能把CAD图纸转成SVG。根据团队规模和图纸来源,我拆成三条路:CAD端手动导出、服务器端批量转换、以及面向工程插件的一键式对接。
3.1 AutoCAD/中望CAD/浩辰CAD的SVG导出
AutoCAD自带SVGOUT命令,这是最直接的办法。操作人员打开图纸后,在命令行输入SVGOUT,选择要导出的对象或视图范围,命名保存,就能得到一个SVG文件。这个文件保留了CAD图形的矢量轮廓,放大不变形,线条信息都在。
中望CAD和浩辰CAD没有完全同名的命令,但你在“打印/导出”菜单里通常能看到“导出为SVG”的选项。如果菜单里找不到,还有一条绕行方案:先把图纸打印为PDF或PostScript,再用Ghostscript在线转换成SVG。就是多了两步,不优雅,但在没有CAD二次开发权限时能应急。
这里特别想说一下,很多芯片制造企业的CAD操作员,长期只画图和出图,对SVGOUT这类命令不熟。你可以把导出的完整操作步骤写进部门SOP文档里,甚至录一个30秒的短视频,比让信息部门反复培训更有效。
3.2 转换服务器的做法:DWG/DXF到SVG的加工线
如果文档平台要承接大量历史图纸,点对点手动导出就不现实了。更合理的做法是搭一个文件转换服务,放在后端:用户把DWG或DXF传到服务器,服务器跑转换程序,生成SVG,再回传文档系统。
业界常见的转换项有Aspose.CAD、AutoDWG系列工具、以及开源的的一些 dxf 解析器。对于DXF,我比较推荐用开源方案,因为DXF本身是文本格式,只要你能理解它的实体表结构,就可以写脚本抽取LINE、LWPOLYLINE、CIRCLE、ARC、HATCH这些实体,再映射成SVG的<path>或<line>元素。这样可以做到对图层的完整控制。但DWG是闭源格式,开源生态没有那么好,商业软件更稳妥。
我当时的做法是:折,统一走一条.NET Web API。接口接收DWG/DXF后,用Aspose.CAD加载图纸,设置好输出尺寸和背景,转成SVG保存到对象存储,返回一个文件ID给前端。流程图不画了,说白就是一条“图纸进、SVG出”的管道。
3.3 导出参数这样设置才不容易掉精度
无论用哪条路,转换时都有几个参数决定最终质量,踩过坑之后我才总结清楚。
- 单位:芯片行业经常用mil和微米,转换时不要想当然按毫米处理。DWG文件里的绘图单位是毫米还是inch,要提前识别,否则导出后比例可能整体缩小25.4倍;
- viewBox:生成的SVG一定带上
viewBox="整数 整数 宽度 高度",并且让width和height保持与viewBox一致,不要在HTML里用width="100%",否则编辑器布局一变化,图纸就被拉伸变形; - preserveAspectRatio:设置成
xMidYMid meet,保证图形等比缩放,不因为容器尺寸变化而扭曲; - 线宽:芯片图纸的细线很多,导出时不要把线宽设成0,否则部分浏览器在低缩放级别下会看不到线。我给团队定的规则是,0.01mm以下的线宽强制提高为0.01mm,既不影响精度感,又保证渲染可见。
这些参数,手动导出时容易被忽略,服务器批量转换时一定要写死在配置里,并经过多张图纸验证再上线。
4. 给TinyMCE做粘贴拦截与SVG插入插件
CAD图已经转成SVG文件了,接下来要让TinyMCE把它优雅地“吃”进去。我建议你不要只依赖复制粘贴,而是做一个小插件,让用户在编辑器里点击一个按钮,上传SVG或粘贴SVG文本,插件经过安全清洗后插入到内容区。
4.1 核心思路:让“SVG文本粘贴”走专用通道
实际使用中,工程师手里的SVG来源五花八门:有的是从CAD导出后的文件,有的是从转换系统下载的文本内容,还有的是从别的网页里直接复制的一段图表代码。如果让TinyMCE默认去分析这些内容,很容易出错或被清洗。
更稳的思路是,在编辑器里增加一个自定义插件,识别到内容是<svg>开头的纯文本时,就阻止默认粘贴行为,走专用逻辑插入内联SVG。这样既能保留矢量特性,又不会干扰普通文字、表格、图片的粘贴。
4.2 一个可运行的insertContent插件实现
我用TinyMCE 5/6的PluginManager写了一个最小可用版本。核心逻辑分三步:读取剪贴板文本、用正则提取SVG片段、消毒后用editor.insertContent()插入。
javascript复制tinymce.PluginManager.add('cadsvg', function(editor) {
function sanitizeSvg(svg) {
const parser = new DOMParser();
const doc = parser.parseFromString(svg, 'image/svg+xml');
doc.querySelectorAll('script').forEach(function(n) { n.remove(); });
doc.querySelectorAll('*').forEach(function(node) {
Array.prototype.slice.call(node.attributes).forEach(function(attr) {
const name = attr.name.toLowerCase();
if (name.indexOf('on') === 0 ||
(name === 'href' && /javascript:|data:text\/html/i.test(attr.value))) {
node.removeAttribute(attr.name);
}
});
});
return new XMLSerializer().serializeToString(doc);
}
editor.on('paste', function(e) {
const cb = e.clipboardData || window.clipboardData;
if (!cb) return;
let text = cb.getData('text/plain');
if (!text) text = cb.getData('text/html');
const m = text && text.match(/<svg[\s\S]*?<\/svg>/i);
if (!m) return;
e.preventDefault();
const safe = sanitizeSvg(m[0]);
if (/^<svg[\s>]/i.test(safe)) {
editor.insertContent(safe);
}
});
editor.ui.registry.addButton('cadsvg', {
text: '插入SVG图',
onAction: function() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.svg,image/svg+xml';
input.onchange = function() {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(evt) {
const safe = sanitizeSvg(String(evt.target.result));
editor.insertContent(safe);
};
reader.readAsText(file, 'utf-8');
};
input.click();
}
});
return {
getMetadata: function() {
return { name: 'CAD SVG Plugin', version: '1.0' };
}
};
});
这段代码在TinyMCE 5和6里都能跑,只是editor.ui.registry.addButton的API需要你引用对应版本的UI资源。如果你在用TinyMCE 4,得把addButton改成addButton的老式写法,不过现在升级到5/6是主流,建议直接在这两个版本上做。
4.3 为什么不建议直接把DXF/DWG塞进浏览器
有人可能会想,既然CAD转SVG这么麻烦,能不能让浏览器直接支持DWG粘贴?答案是当前阶段很不现实。DWG是闭源二进制格式,前端没有稳定解析库;DXF虽然是文本,但一个真实DXF文件动辄几十MB,实体种类多到可以写一本书,光靠浏览器解析完再渲染,页面的性能就崩了。
更重要的是,芯片制造企业要的不是“浏览器里出现图片”,而是“图纸能进入业务系统,支持检索、批注、版本对比”。所以,把解析和转换放在后端管道,前端只消费标准SVG,才是企业级该有的架构。这也是我把重心固定在SVG上的原因——它不是最花哨的,但它是现网兼容性最好、可扩展路径最宽的矢量载体。
5. 芯片制造场景里的“矢量”不只是清晰:单位、图层、文字、性能
当SVG真正进入TinyMCE,你会发现“矢量”这两个字要落在具体业务细节上,远不是“放大不糊”那么简单。芯片行业有它自己的特殊习惯,下面这四个点,每一个都值得在方案评审时单独过一遍。
5.1 坐标单位与viewBox比例
SVG用的是CSS像素坐标,不和CAD里的毫米/微米直接挂钩。很多人第一次做转换时,会把CAD坐标原封不动塞进SVG
