在芯片厂做文档系统集成,最常碰到的一个怪问题就是:工程师辛辛苦苦在AutoCAD里画好的版图、封装示意图、工艺流程图,复制粘贴到基于TinyMCE的工艺管理系统后,保存出来变成一张模糊的图片。缩放一下,字体边缘全是锯齿,标注尺寸线也糊成一团。拿去打印评审,更是不敢看。图纸的矢量信息,在粘贴的过程中就这么丢了。
这个问题的本质,其实不在于TinyMCE,也不在于CAD,而在于剪贴板里的格式博弈。CAD软件复制对象时,往剪贴板里塞了多种格式的数据:原生的DWG对象、Windows增强图元文件EMF,还有给屏幕显示用的位图。TinyMCE作为一个基于浏览器contenteditable的富文本编辑器,它读剪贴板时只认HTML结构、图片和文本,不会主动去取EMF或DWG。最终用户看到的粘贴结果,就是那个位图版本。
这篇文章我不打算只停留在解释层面,我会结合我在芯片制造企业里落地工艺文档系统的实际经验,把整个链路掰开揉碎:CAD端怎么处理、TinyMCE端怎么配置、后端存储怎么配合、实际部署中有哪些坑。适合正在做企业知识库、工艺文档在线编辑、图纸管理系统的开发人员和IT实施工程师参考。
1. 图纸粘贴到TinyMCE之后,矢量信息到底丢在了哪一步
想解决矢量输出,先得搞清楚信息是在哪个环节丢的。很多人以为是TinyMCE把图片压缩了,其实不是。从CAD复制到最终保存,一共经过四个环节:CAD剪贴板、浏览器剪贴板读取、TinyMCE的粘贴处理、后端存储。每一个环节都可能发生格式转换和降级。
1.1 CAD剪贴板里的“富格式”到底有什么
在Windows系统里,CAD程序复制对象时(Ctrl+C),会同时向剪贴板写入多个标准格式。以AutoCAD为例,它通常写入:CF_DIB(设备无关位图,也就是普通的栅格图)、CF_ENHMETFILE(增强型Windows图元文件,属于矢量格式),有些版本还会写入CF_HDROP或CF_TEXT等。
这里有个关键认知:TinyMCE运行时是一个网页,浏览器出于安全沙箱限制,JavaScript只能读取DataTransfer对象暴露的那部分剪贴板内容。对Blob/文件类型,浏览器允许读取;对EMF这种系统级gdi格式,浏览器根本不会暴露给网页。所以TinyMCE的paste事件里,你能拿到的只有:text/plain、text/html、或是用户所选文件对应的File对象(比如PNG、SVG、DWG文件)。EMF数据被浏览器挡在沙箱之外。
这个结论很重要,它意味着单纯靠前端截获剪贴板、直接拿到CAD的矢量几何数据,这条路在浏览器环境里走不通。
1.2 TinyMCE为什么选择了位图而不是EMF
TinyMCE的粘贴流程大致是:paste事件触发后,paste插件读取剪贴板内容。如果发现是图片(Files里的image/*),就直接插入<img>标签;如果发现是HTML,就经过paste_preprocess、内容过滤器,最后作为富文本插入;如果只有纯文本,就以文本插入。
问题在于,当你从CAD复制并切到浏览器时,Chrome、Edge等浏览器对剪贴板内容的选择性暴露,很多时候只给你一份HTML片段,里面的图片被转成了base64的PNG,或者说干脆把EMF渲染成了一个DIB写入HTML里。TinyMCE拿到这份HTML,自然就按位图形式插入了。
所以你可以回想一下,凡是CAD粘贴到在线文档变成清晰度尚可的图片,基本就是走了位图;偶尔某些CAD版本复制后粘贴成不可选中的一大块,那就是HTML里嵌入了base64。这两种结果,都不是我们要的矢量输出。
1.3 芯片制造场景里为什么死磕矢量
芯片制造企业里,文档系统里流转最多的图纸是:封装基板版图、晶圆工艺流程示意图、引线框架图纸、设备治具图、厂务动力管道布局图。这些图纸有几个共同点:线条密集、尺寸标注精确到微米级、有大量图层区分。
如果用位图粘贴,打印成PDF或者放大评审时,线条会断、标注数字会糊,更有甚者在喷绘大幅面工艺图时,一张A0图纸全是马赛克,根本没法用。而矢量格式比如SVG,本质是数学描述,任意缩放不损失精度,还能保留图层、颜色、文字信息,文件体积也小得多。
所以芯片企业的需求不是“看得清就行”,而是要精确、可检索、可打印、可再加工。位图根本无法满足。
2. 从CAD端动手:把图纸变成TinyMCE能真正吃进去的矢量格式
既然浏览器剪贴板通路不支持EMF,我们要换个思路:不在粘贴那一刻临时抱佛脚,而是在CAD端或转换端就把图纸导出成浏览器和TinyMCE都认的矢量格式——SVG。SVG是浏览器的原生矢量格式,是真正能让TinyMCE保留矢量结构的方案。
2.1 少量图纸手动导出:EMF中转加转换工具
如果你只是偶尔往文档系统里贴一两张图纸,最快的方法是手动中转。在AutoCAD里用WMFOUT命令,把选中的对象导出为WMF或EMF文件。导出时注意一个设置:系统变量WMFBKGND,值为0时导出背景透明,值为1时背景为当前绘图背景色。芯片厂的图纸背景多半是黑色,放到在线文档里会非常突兀,建议导出前设为0。
拿到EMF文件之后,用Awesomium、Inkscape、LibreCAD这类工具转成SVG。Inkscape的命令行方式比较方便:
inkscape input.emf --export-type=svg --export-filename=output.svg这个方式对偶尔几张图够用,但芯片厂的图纸数量动辄成百上千,手动导出不现实。所以生产系统里一般不建议走这条链路。
2.2 批量转换:DWG到DXF再到SVG的自动化管线
对批量场景,我推荐的做法是:建立一条转换管线,让工程师把DWG或DXF源文件丢进统一目录,后端自动转换为SVG,并给文档系统生成一个可插入的SVG文件ID。
第一步,用ODA File Converter或者LibreCAD把DWG批量转成DXF。ODA的转换器支持命令行调用,适合部署在Linux服务器上:
ODAFileConverter input_dir output_dir ACAD2018 DXF 0 1 "*.dwg"第二步,把DXF转SVG。这步可选的工具比较多:
- LibreCAD的命令行转SVG:
librecad -t svg input.dxf -o output.svg - Python生态里,用
ezdxf读取DXF实体,再用matplotlib或svgwrite自己写SVG。对于需要按图层、按线型过滤的场景,这种自研方式最灵活。 - 复杂图纸可以用
dwg2svg(基于ODA)或商业转换SDK。
我实际项目里用的是Python方案。因为芯片厂的图纸往往有几十个图层,我们需要按文档模板过滤,只保留造型层、尺寸标注层,去掉无关的参考层。用ezdxf可以精确控制:
import ezdxf from ezdxf.addons.drawing import RenderContext, Frontend from ezdxf.addons.drawing.svg import SVGBackend doc = ezdxf.readfile("input.dxf") msp = doc.modelspace() # 按图层名过滤 for entity in msp: if entity.dxf.layer not in allowed_layers: msp.delete_entity(entity) backend = SVGBackend() ctx = RenderContext(doc) Frontend(ctx, backend).draw_layout(msp) with open("output.svg", "w", encoding="utf-8") as f: backend.save(f)这只是一个基本骨架,实际生产还要处理中文乱码、字体路径、线宽映射等问题。后面我会专门讲坑。
2.3 CAD里直接输出SVG的兼容性真相
有工程师会问:AutoCAD能不能直接plot成SVG?AutoCAD自带PDF、DWF、PLT等虚拟打印,原生没有SVG。装第三方SVG打印驱动倒是可以,但图纸里的SHX形文件字体、填充图案、光栅参考底图,转到SVG后经常出现样式错乱。
还有一部分图纸是EDA工具生成的,比如Cadence Allegro、Mentor PADS,这类工具导出DXF时精度和图层映射需要专门配置。在芯片厂的工艺文档流程中,我的建议是:把DXF当作交换标准,不要让源文件格式五花八门,统一入口转换成SVG。这样TinyMCE里插入的都是同一种结构的数据,后端的解析、搜索、权限控制也好做。
3. TinyMCE端的接收与配置:插入的SVG不被过滤掉
CAD端生成SVG之后,接下来要让TinyMCE接收这个SVG。SVG不像PNG那样默认被富文本编辑器接受,它含有一堆标签和属性,如果不做配置,TinyMCE的内容过滤器会把标签当作非法HTML给删掉。这一节讲清楚几个关键配置。
3.1 配置extended_valid_elements和valid_children
TinyMCE内部有一个Schema,用来判断哪些标签合法。默认情况下,<svg>不在合法列表中,会被剥掉。必须在初始化时扩展白名单。以TinyMCE 5/6为例:
tinymce.init({ selector: '#editor', plugins: 'paste code autolink lists link image', extended_valid_elements: 'svg[*],defs[*],g[*],path[*],circle[*],rect[*],line[*],polyline[*],polygon[*],' + 'ellipse[*],text[*],tspan[*],linearGradient[*],radialGradient[*],stop[*],' + 'use[*],image[*],clipPath[*],mask[*],pattern[*],symbol[*],marker[*],desc[*],title[*]', valid_children: '+body[svg],+div[svg],+p[svg]', valid_elements: '*[*]', // 仅测试用,生产环境不建议这样 });这里有个细节:extended_valid_elements里的svg[*]表示svg标签下所有属性放行。芯片图纸转出的SVG里经常用到transform、stroke-dasharray、stroke-width、fill-rule等属性,只有用通配符才能完全保留。
配置valid_children是为了让SVG能放在div或p里。默认Schema里svg不能作为这些块级元素的子节点,不加这个配置SVG会被移到body下甚至丢弃。
3.2 在paste_preprocess里识别并转换SVG
如果你没有走文件插入,而是希望工程师直接粘贴SVG源码进编辑器,可以在粘贴阶段做一道预处理。TinyMCE的paste_preprocess钩子能拿到待插入的HTML,在里面判断是否包含<svg>,有则保留,没有则走默认逻辑。
另外,用户从某个SVG查看器里复制图形时,剪贴板可能同时有SVG源码和PNG图片。粘贴时TinyMCE默认插入HTML的一部分,如果HTML里包含<img src="data:image/png;base64,...">,就还是位图。可以在paste_preprocess里检测到剪贴板HTML包含SVG时,主动剔除img节点,保留SVG节点:
paste_preprocess: function(plugin, args) { var content = args.content; if (content.indexOf('<svg') !== -1) { // 丢掉位图兜底,保留SVG args.content = content.replace(/<img[^>]*data:image\/[^>]*>/gi, ''); // 如果SVG外层被包了p或div,保留结构 } }注意,这个方案的前提是浏览器把SVG源码放到了剪贴板的text/html里。实测Chrome从Inkscape里复制图形到网页,并不总能拿到SVG源码;如果拿不到,前端就无能为力,必须回到文件上传通道。
3.3 后端存储与渲染的配套改造
前端把SVG插入编辑器后,保存时有两种选择:一是把SVG字符串连同正文HTML一起存到数据库字段里;二是把SVG作为独立资源存到对象存储,正文里只存一个<img src="/svg/xxx.svg">引用。
我强烈建议第二种。原因是正文HTML可能被搜索索引、做PDF导出、做版本diff,内嵌大量的SVG源码会让整个文档异常臃肿,而且如果同一张图纸被多个文档引用,内嵌会造成数据冗余。
具体做法是:编辑器保存时,前端把SVG元素序列化,POST到一个上传接口,接口返回SVG资源的URL;然后在正文中替换SVG代码为:
<img src="/api/cad-svg/20240513-001.svg" alt="封装基板版图-RevC" style="width:100%;">这里有个很多人忽略的问题:SVG文件如果带了<script>或外部引用,直接当作IMG加载没有真正脚本执行,风险可控;如果需要通过<object>或<iframe>嵌入,就要严格做白名单过滤,防止恶意SVG内容。芯片企业内部文档系统还要做权限控制,不能让人把图纸地址猜测出来随便看。
4. 生产级落地:芯片企业文档系统里的完整流程与规范
在理论配置之外,真正让这套机制在芯片厂里跑起来,需要一整套流程规范。否则工程师觉得麻烦,还是会悄悄截图粘贴,矢量输出的目标就落空了。
4.1 从“粘贴图纸”改为“插入图纸”的操作重塑
我在项目里推的第一条规则,就是不要在文档编辑器里直接从CAD复制粘贴。改成一个统一的“图纸插入”入口:工程师点击编辑器工具栏的“插入CAD图纸”按钮,弹窗中搜索或上传图纸,系统调用后端转换服务,返回SVG预览和缩略图,确认后插入正文。
这么设计的目的,是让图纸的源头可控。每一次插入都会在系统里留下关联记录:图纸ID、版本号、插入时间、插入人。评审时出现图纸版本不一致问题,可以直接追溯到是哪个文档用了哪个版本的图纸。
工具栏按钮的实现非常简单,注册一个TinyMCE按钮或菜单项,打开自定义弹窗,然后用editor.insertContent()把选中的SVG或引用插入即可。
4.2 大批量版图转换时的性能与精度控制
芯片厂的版图文件动辄几十MB,直接转换SVG可能产出几十万行路径数据,浏览器渲染都卡,更不用说在TinyMCE里编辑。必须有降级策略。
第一种是抽取需要的图层。版图源文件一般分很多层:boundary、metal1、metal2、via1、pads、silkscreen等等。工艺文档里通常只需要示意级精度,可以将细小的多边形合并,或者用Douglas-Peucker算法抽稀路径点。用ezdxf或底层库做实体级别的简化,可以大幅降低SVG体积。
第二种是分区域输出。如果文档只想展示芯片封装某个局部门细节,比如bonding区域,可以在转换前按坐标范围裁剪。
还要注意SVG里的坐标系统。CAD的DWG/DXF用的是世界坐标系,原点通常在左下角,y轴向上;SVG的坐标原点在左上角,y轴向下。转换时如果不做翻转,生成的图纸会上下颠倒。在ezdxf绘制后端里通常自动处理了,但如果自己拼SVG字符串,一定要记得做矩阵变换:matrix(1 0 0 -1 0 height)。
4.3 与EDA工具的对接细节
芯片企业的CAD图纸不只是AutoCAD,还有大量来自Cadence、Mentor、Zuken等EDA工具的图纸。这些工具导DXF时,文字经常会变成一个个单独的line或polyline,也就是我们常说的“打碎”状态,中文还会出现乱码。
处理经验是:在EDA工具导出DXF的对话框里,把文字选项设为“保留文本(AISO/TEXT)”,而不是“分解为线段”。如果源文件已经被打碎,那只能依靠OCR重建文字,准确率不够理想。更好的做法是推动工程师在导出时使用标准模板,把映射关系固化下来。
4.4 安全合规检查不可省
芯片制造企业的图纸有保密要求。外来CAD文件在进入内部系统前,需要经过恶意宏检查、外部引用检查、水印配置等。SVG本身是纯文本,但要注意两点:一是SVG内可能包含外部实体引用或本地文件路径信息,转换时应当剥离xmlns:xlink之外的一切外部引用;二是SVG中的文字信息可能包含敏感图层名、设计者姓名,上传到文档系统时要注意脱敏。
在自动化转换管线上,这一步可以用脚本批量扫描SVG源码,匹配敏感关键字并删除对应节点。我通常会保留一份转换日志,包含源文件哈希、转换时间、输出文件哈希,方便审计。
5. 避坑排查:项目中实际踩过的五个问题
这一节的内容,是我在实施过程中真正踩过的坑,每一个都花了相当时间排查。列出来供你少走弯路。
5.1 辛辛苦苦转出来的SVG,内部竟然还是位图
有次我让组员开发批量转换任务,输出的SVG文件体积倒是不大,但放大了看关键线条还是糊的。排查发现,源DXF里的某些复杂块,被转换工具自动做了光栅化处理,SVG文件里嵌了<image>标签,图片内容是base64编码的PNG。
这种SVG名义上是矢量,实际传输和显示还是位图。检查方法很简单:把SVG文件用文本编辑器打开,搜一下<image或data:image/png;base64,有就是存在内嵌位图。根治办法是把转换工具的光栅化阈值调高,或改用不主动光栅化的后端。
5.2 SVG插入TinyMCE后,编辑模式预览正常,保存输出后样式全丢
这个坑出在TinyMCE的编辑器内容和最终产物不一致。TinyMCE在编辑状态看到的SVG样式来自content_style,保存时如果后端做了净化处理,例如Java后端的Jsoup、PHP的HTMLPurifier,会默认删除SVG标签。富文本编辑器保存流程里嵌入一个HTML白名单过滤逻辑,很容易把SVG给洗掉。
解决思路有两个:一是让后端过滤逻辑直接放行白名单内的SVG标签;二是在保存前的前端钩子里,把SVG内容先提取成占位符,等过滤完成后再把占位符替换回SVG源码。项目里我倾向于用第二种,因为后端的净化策略往往由安全团队统一管控,前端拆分工更灵活。
5.3 中文标注在SVG里全部显示为方块
DWG/DXF里的中文标注,转换时依赖字体匹配。在Linux服务器上部署转换服务,如果系统没有安装中文字体,比如宋体、黑体、楷体,生成的SVG里文字会以缺少字形的形式存在,显示成方块或乱码。
处理方法很朴素:在转换服务器上装好常用中文字体,并配置fontconfig。如果图纸文字是SHX形文件,最好在CAD里先用TEXTTOFRONT或批量替换为标准TrueType字体,再转DXF。你也可以在设计转换Template时,把所有文字统一映射到指定的中文字体族。
5.4 大文件转换导致的内存溢出和超时
几十MB的DWG文件,在单机转换服务上容易把内存吃满,进程卡死。这个问题在使用ODA转换器的时候尤其明显。后来我改成按图层拆分转换:先读取DXF的头部和图层表,分析出图纸中有实际几何元素的图层,再按图层输出SVG,最后在SVG层用<g id="layer-name">分组,这样既保留了图层语义,又控制了单次转换规模。
前端插入的时候,也可以让用户选择加载哪些图层,比如只看silkscreen和assembly,不加载布线层,降低浏览器渲染压力。
5.5 TinyMCE版本升级导致的SVG配置失效
TinyMCE从4升级到5再升级到6,对自定义有效元素和Schema的合并逻辑有调整。6.x版本里,valid_children和extended_valid_elements的优先级、语法顺序都有变化。有时明明配置了,但检查编辑器输出时SVG标签还是被丢弃。
排查时先在浏览器控制台执行tinymce.activeEditor.schema.getValidElements(),看svg是否出现在结果里。如果不在,说明配置被后面的默认Schema覆盖了。试试把配置放在初始化参数的顶层,不要嵌套在paste插件设置的子对象里。还有content_css里给SVG元素补基础样式,防止被安全扫描规则识别为未知标签。
6. 不硬磕粘贴:替代思路与混合方案
有些场景,即使做了上述所有配置,前端直接插入SVG仍然不理想,比如:工程师在浏览器端根本没有本机CAD文件,或者图纸太大不适宜全部加载到编辑器里。这时需要跳出“粘贴”这个动作本身。
6.1 图纸转换微服务化
把DWG转SVG做成一个独立的微服务,对外只提供REST接口:上传源文件,返回SVG和预览图。文档系统、IM工具、邮件系统、PDM系统都可以复用。芯片企业内部,CAD数据往往是机密资产,统一转换服务还能集中审计谁在什么时候转换了什么图纸,避免信息泄露。
6.2 嵌入CAD Web Viewer,而不是插入静态SVG
如果文档评审时需要对图纸做交互操作,比如平移、缩放、图层开关、测量尺寸,静态SVG就力不从心了。这时候可以在TinyMCE内容区插入一个<iframe>,指向Web CAD Viewer组件,后端把DWG转换后的轻量化模型数据传给Viewer。这样正文保留一个预览缩略图,点击后打开查看器,两者兼顾。
6.3 矢量预览加位图兜底的混合策略
最后说一个稳妥的混合方案:在文档系统里,每条图纸记录同时保存两套衍生文件——SVG用于Web端矢量显示和打印输出,PDF或PNG用于快速预览和兼容老旧的办公软件。TinyMCE插入时优先使用SVG;对于对外发送的文档,自动转成PDF并替换为位图引用,防止源矢量数据流出。
这算是我在保密要求比较高的项目里,常用的一套折中做法。既能保证内部评审的高清晰度,也能在外部协作时控制图纸的精确几何信息不外泄。
如果你正在搭类似系统,我给你的第一个建议是:尽早统一“图纸以SVG为交换格式”的规范,不要等文档积累到几万份再回头迁移。第二个建议是,对粘贴动作本身降低预期,引导工程师使用“插入图纸”而不是“复制粘贴”,这比任何配置都管用。