把一张穿孔卡做成可导出的 SVG

发布时间:2026/10/25 20:36:09
把一张穿孔卡做成可导出的 SVG

复古界面最容易做出“像真的”,也最容易顺手写下“仿真”。一张浅黄色卡片,配上等距孔位,就有了老机器的气息;但它并没有因此成为历史设备的编码器。

这个小工具只做一件事:把一段短 ASCII 文本转换为八位二进制,再画成 SVG 孔位。每个字符占一行,最高位在左,1 画深色孔,0 画浅色格。这里采用的是为了阅读而设计的八列排布,不是 Hollerith 卡片的列码,也不承诺能被任何实体读卡器识别。

先约定编码,再画纸张

例如H的码值是 72,对应01001000。如果只从第二、第五格打孔,读者就能手工检查这一行。中文字符则不在本例的范围内;不要悄悄取 UTF-16 低八位,那会产生可画却不可解释的结果。

SVG 可以把几何与导出内容放在同一份文本里。SVG 2 的路径规范明确区分位置、线段和曲线命令;这个卡片甚至用不到复杂路径,圆角矩形就足够。图形简单,错误也更容易看见。

下面代码在 Node.js 中直接运行,会生成一份card.svg。标签经过 XML 转义,输入限制为最多 24 个可打印 ASCII 字符。导出的是矢量文档,尺寸可以缩放;字体外观仍取决于打开它的环境。若做浏览器下载,需要额外处理 Blob URL 的释放和用户触发,不是把文件扩展名改成 SVG 就算完成。

复古质感别抢走信息

纸边噪点、折痕和编号可以后加,但不能覆盖孔位。可访问版本还应保留原文本和二进制字符串,让不方便读图的人也能拿到同样的信息。例子在desc中放入了原文,并在每行右侧写出位串。

本地检查验证了H的位串、孔数量、XML 特殊字符转义和非 ASCII 拒绝;没有对实体打印、读卡器、浏览器字体做兼容测试。它适合做一张程序员节纪念卡。只要把装饰规则说清楚,玩具也可以很认真。

可独立运行的核心例子

const fs = require('node:fs'); const xml = s => s.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[c])); function bits(text) { if (!text.length || text.length>24 || /[^\x20-\x7E]/.test(text)) throw Error('只接受1到24个可打印ASCII字符'); return [...text].map(c => c.charCodeAt(0).toString(2).padStart(8,'0')); } function card(text) { const rows=bits(text), h=80+rows.length*28; const cells=rows.flatMap((row,y) => [...row].map((b,x) => `<rect x="${28+x*22}" y="${48+y*28}" width="14" height="18" rx="3" fill="${b==='1'?'#453f4d':'#e3d7c4'}"/>`)); const labels=rows.map((row,y) => `<text x="220" y="${62+y*28}" font-size="12">${row}</text>`); return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340 ${h}"> <desc>ASCII八位装饰卡:${xml(text)}</desc> <rect width="340" height="${h}" rx="12" fill="#f6ead6"/> <text x="28" y="28">DECORATIVE ASCII CARD</text> ${cells.join('')}${labels.join('')}</svg>`; } if(bits('H')[0]!=='01001000') throw Error('编码错误'); if((bits('H')[0].match(/1/g)||[]).length!==2) throw Error('孔数错误'); if(!card('<&').includes('&lt;&amp;')) throw Error('转义错误'); let rejected=false;try{bits('秋');}catch{rejected=true;} if(!rejected) throw Error('非ASCII不应静默截断'); fs.writeFileSync('card.svg',card('HELLO 1024')); console.log('已生成 card.svg;装饰编码,不用于实体检票或读卡');