简介:这是一套基于织梦(DedeCMS)内核的在线字体转换与艺术字生成平台源码,面向需要快速搭建艺术字在线生成工具的站长、建站者,支持自行添加字体,可解决从零开发在线转换器的难题。资源包共2000个文件,约696.71MB,以PHP后端逻辑、HTML/HTM页面模板、GIF/PNG图片素材、JavaScript交互脚本、CSS样式表及TTF字体文件为主,结构完整清晰,且大部分页面以HTML为主,后台管理仅作辅助,便于直接部署或二次调整。已有180人学习下载。平台界面美观大气,附带全站数据,支持无限制复制用于站群运营,无需频繁更新;各类型文件按模块组织,涵盖字体配置、样式布局与在线转换流程,能帮助使用者快速掌握艺术字生成系统的搭建思路与实际运用,是一份难得的完整参考。
1. 在线字体转换系统到底拆的是什么?
如果你只是需要给访客提供一个“输入文字 → 选个艺术字 → 下载图片”的工具页,其实根本不需要什么高并发架构,这套在线字体转换系统源码把关键点全部压在了静态资源和字体预置上。拿到手第一眼是 DedeCMS 织梦内核,以为要装一套 CMS 才能跑,但翻一眼文件目录就能发现,业务核心基本是 HTML、CSS 和几个 PHP 接口,织梦后台只是顺手挂了个壳。也就是说,迁移到任意一个能跑 PHP 的 VPS 上,甚至写成纯静态页面配一个生成接口,都能复现同样的效果。
这种结构的好处是:适合做站群、做快速交付、给不会维护后台的客户做演示站。缺点也很明显,系统没有复杂的用户体系,也没把前台和后台深度绑定。所以拆这套源码,真正有价值的不是织梦,而是那组物理字体、ziti.css 的呈现规则、以及预览到输出图片的完整链路。下文我会按“资源结构 — 字体加入 — 部署复制 — 性能验证”的顺序,把它拆到可以照抄的粒度。
2. 从 common.css 到 ziti.css:栅格、皮肤与字体规则
2.1 文件清单与职责划分
这套源码给的样式文件很典型:editor.css、page.css、dialog.css、album.css、common.css、dedecms.css、layout.css、ziti.css、style.css、base.css,猛一看很散,但基本能分三层。base.css负责 reset 和浏览器默认样式抹平;common.css是公共组件,按钮、表单、弹窗、分页这类跨页面公共元素都丢在它里面;layout.css控制整体版式,比如顶部横幅、左右栏、内容容器宽度;style.css算是主题定制层,颜色、圆角、背景图都在这里覆盖。
剩下几个文件更有针对性:editor.css是文字编辑器区域那部分的样式,负责可编辑层和字体选择器;dialog.css管的是文件上传弹窗、预览弹窗;album.css是艺术字效果图册的展示逻辑;dedecms.css则是为了让织梦后台输出的标签残片不变形,属于兼容性文件。而ziti.css是整个系统的“字库映射表”,核心中的核心。
我建议接手时先不要改其他文件,只动ziti.css。原因很简单,这套源码的可靠程度体现在文件逻辑非常脆,改common.css里的通用 class 容易影响编辑器和弹窗,而ziti.css只服务于字体定义,独立性强。
2.2 字体渲染的底层逻辑
在线艺术字转换器能不能吸引人,第一眼取决于字体是否丰富,第二眼才是操作方式。浏览器默认字体(黑体、宋体)在艺术字场景下没有任何竞争力,所以系统必须在页面载入时把字体资源拿过来,并用@font-face绑定到自定义字体族。
@font-face { font-family: 'Ziti-Lishu'; src: url('fonts/FZLITI.TTF') format('truetype'); font-weight: normal; font-style: normal; font-display: swap; } @font-face { font-family: 'Ziti-Xingkai'; src: url('fonts/FZXK.TTF') format('truetype'); font-display: swap; }代码中用font-display: swap是为了避免字体文件过大导致文字不可见。TTF 格式在 Windows 和移动端兼容性尚可,但字符集很大。这套源码给的是传统做法,全部加载完整的 TTF 字体文件,所以第一次打开预览时会有短暂白屏,这是可以接受的,因为艺术字站点本身不像正文阅读那样需要瞬间渲染。
值得注意的是,字体名要使用 URL 编码后的路径,且中文字体文件名最好改成英文拼音。常见问题是个别字体文件损坏,@font-face声明了但浏览器不渲染,这时候需要确认font-family名称是否和 CSS 里调用的名字完全一致。这类问题用 DevTools 的 Network 面板看字体文件是否返回 200 即可判断。
2.3 一个可复现的前台页面骨架
剥掉织梦标签后,艺术字生成页的实际骨架大概是这样一个结构:
<div class="layout-box"> <div class="toolbar"> <select id="fontSelector" class="font-select"> <option value="Ziti-Lishu">隶书</option> <option value="Ziti-Xingkai">行楷</option> <option value="Ziti-Huakang">华康少女</option> </select> <select id="sizeSelector"> <option value="64">64px</option> <option value="96">96px</option> </select> <input type="color" id="colorPicker" value="#ff0000" /> <button id="previewBtn">生成预览</button> </div> <div id="previewArea" class="art-text-preview"> <span id="artTextSpan" style="font-family: 'Ziti-Lishu';">艺术字体在线</span> </div> </div>这个骨架说明几个关键点:字体的切换不是加载不同图片,而是动态改变<span>的font-family;预览区字体从下拉框里取value对应字体类型,和ziti.css中声明的font-family名称要一一映射。换字体时一行el.style.fontFamily = value就能切换,不必刷新页面。
但是简单的字体切换只是第一步,输出为图片才是这类系统的收费点。前端在用户点“保存图片”时,会把预览区内容绘制到 canvas 上,再转成 base64 图片串提交给后端。
3. 添加自己的字体:从上传到实时预览
3.1 字体格式选型
中文艺术字体文件的体积普遍在 5~20 MB,尤其 TTF 格式带完整字形。如果要在一个页面上展示 20 种字体,全部加载会直接把服务器带宽打满。常见做法是给字体做子集化,把常用汉字提取出来生成一个新的字体文件,也叫动态字库。但在不改变源码的前提下,最简单的做法是先把字体统一转换为 WOFF2 格式,体积能比 TTF 小 30%~50%,而且现代浏览器全部支持。
用 FontForge 或者命令行工具fonttools做转换都行。我一般这样做:
# 安装 fonttools pip install fonttools brotli # 转换 TTF 到 WOFF2 fonttools ttLib -o FZXK.woff2 FZXK.TTF --flavor woff2 # 提取常用 3500 个字,控制体积 fonttools subset FZXK.TTF --text-file=common_chars.txt --output-file=FZXK-subset.woff2其中common_chars.txt是每行一个字的纯文本文件,建议包含 GB2312 常用字和数字标点。子集化后要重新声明@font-face,路径指向.woff2文件,format('woff2')单独写。参数上注意:如果用户输入的文字不在子集内,那个字就会回退到系统字体,视觉上很突兀。所以做完整站时,我会保留一个全集 TTF,作为后端生成图片时的渲染字体,而前端预览用子集字体,兼顾体积和体验。
3.2 按现有结构添加字体的完整步骤
这套源码的字体资源都放在fonts目录下,每一个字体对应一个目录或一个文件,控制入口是后台的字体配置表。但正如标题强调的,后台管理功能可有可无,直接改文件更快。具体分三步走:
第一步,把准备好的字体文件放到站点的fonts目录下,保持文件名全小写,避免 Windows 和 Linux 跨环境路径找不到。例如放置fonts/zaozigongfang.ttf。
第二步,在ziti.css里追加声明:
@font-face { font-family: 'ziti-zaozigongfang'; src: url('fonts/zaozigongfang.ttf') format('truetype'); font-display: swap; }第三步,找到前台模板中字体下拉框的代码,追加一个<option>。如果模板是通过织梦后台栏目内容维护的,那就直接修改数据库里对应的表字段,把ziti-zaozigongfang加入可选值列表。最保险的方式是在模板文件里写死一大段字体 option,再用 JS 读取。
字体下拉框不能只加一个 option 就完事。预览区初始化时往往有一个默认字体名,如果默认字体不存在,浏览器会回退到sans-serif,很容易被误判成“系统坏了”。所以我在初始化脚本里加了兜底逻辑:
const fontSelect = document.getElementById('fontSelector'); const artSpan = document.getElementById('artTextSpan'); const fontMap = { 'ziti-zaozigongfang': '造字工房', 'Ziti-Lishu': '隶书' }; function applyFont(value) { const displayFont = fontMap[value] || 'sans-serif'; artSpan.style.fontFamily = `'${value}', ${displayFont}`; } fontSelect.addEventListener('change', function() { applyFont(this.value); });fontMap的作用不只是中文显示名,还作为字体族 fallback 链。如果ziti-zaozigongfang加载失败,浏览器会渲染成“造字工房”这个系统字体名,视觉上仍然是类似风格,不至于瞬间变成黑体。这是做字体类工具站一个很值得保留的小技巧。
3.3 从预览到图片输出的接口逻辑
艺术字生成系统的价值终点是下载图。源码里大多把 canvas 画好的内容转成 base64 之后 POST 给后端 PHP,PHP 收到数据后写文件并返回 URL。这里有个容易被忽略的点:canvas 中使用自定义字体时,必须等字体加载完成才能绘制,否则画布上会出现空白或者豆腐块。
async function exportImage() { const canvas = document.createElement('canvas'); canvas.width = 800; canvas.height = 300; const ctx = canvas.getContext('2d'); // 确保目标字体加载完成 const font = new FontFace('ziti-zaozigongfang', 'url(fonts/zaozigongfang.ttf)'); await font.load(); document.fonts.add(font); ctx.font = '64px ziti-zaozigongfang'; ctx.fillStyle = '#ff0000'; ctx.textBaseline = 'middle'; ctx.fillText('艺术字体', 40, 150); const base64 = canvas.toDataURL('image/png'); const res = await fetch('/save_image.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({image: base64}) }); const data = await res.json(); console.log(data.url); }FontFace构造函数是浏览器级字体加载 API,比document.fonts.load更直观。toDataURL默认导出 PNG。服务端save_image.php只需要把 base64 字符串中的逗号前头去掉,再base64_decode写入uploads目录即可。
这里要特别提醒:上传接口必须限制文件大小和图片 MIME,因为 base64 可以塞任意内容,很多站点被挂马就是从这个口子进去的。建议校验图片宽高并且重新生成一张底图,不要直接落盘原始数据。
4. 部署与站群复制:织梦后台最容易被忽略的边界
4.1 运行环境与伪静态规则
源码基于 DedeCMS,所以环境需要 PHP 5.6 到 PHP 7.4 之间,如果 PHP 版本过高,织梦后台会出现each()函数报错。考虑到这套源码的一大卖点是“后台可有可无”,部署时直接把 PHP 版本固定在 7.0 是最省心的。但要注意,纯静态页面部分完全不需要数据库,只需保证fonts和uploads目录可写。
Nginx 环境下需要把非真实文件的请求交给 PHP 处理,这样织梦生成的栏目页和前端模板跳转才正常:
server { listen 80; server_name ziti.example.com; root /var/www/ziti; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(woff|woff2|ttf|css|js)$ { expires 7d; add_header Cache-Control "public"; access_log off; } }这段配置里try_files $uri $uri/是关键;如果 URL 对应的是一个真实存在的字体文件,Nginx 直接返回,不走 PHP,减少服务器压力。字体文件的expires 7d可以让访客二次浏览时从浏览器缓存读取,这个参数在站群模式下尤其重要,不然每台机器的带宽都会被打满。
4.2 织梦数据迁移的常见坑
虽然前台界面里织梦标签不多,但栏目页仍然依赖数据库里的dede_arctype等表。迁移时要同时迁移data目录和数据库。data目录存了缓存和 SQL 配置,很多新手直接打包上传后后台打不开,就是因为data/common.inc.php里的数据库密码没改。
应用层迁移的注意点:
- 数据库字符集必须保持
utf8,否则中文文字全部乱码; uploads目录中旧的演示图路径不删除,因为页面里的模板变量还引用着它们;- 表前缀
dede_不要更换,除非你有批量替换 SQL 的脚本。
建议在迁移前执行一次后台的“更新缓存”和“生成静态首页”,这样新站点打开时不会因为缓存路径不对报错。
4.3 站群复制的核心思路
这套源码所谓“可无限制复制”,本质是它前台依赖的模板少、静态资源独立。做站群时,两种做法最常见。
第一种是把整个织梦站点复制到多台服务器,每台独立配置数据库。操作步骤是:打包公共文件 → 导入数据库 → 修改data/common.inc.php中的数据库连接信息 → 修改模板中的域名 → 清掉data/cache/*.php缓存文件。每次都要记得清缓存,这是最容易出错的地方,因为织梦缓存会把旧域名写死在编译模板里。
第二种是把fonts、ziti.css、生成图片接口这些纯静态部分单独抽离成一个“字体服务”,挂到 CDN 或 OSS 上,所有站群节点都引用同一个字体域名。这样能大幅降低单台服务器的带宽压力。因为艺术字网站的流量大头就在字体文件上,1 个字体的体积顶得上 10 张页面。
我自己的经验是:站群方案不要用织梦自带的生成整站,太慢。直接把首页模板和 artText 模板写好,然后用 Nginx 的mirror指令,访问量分流到各镜像节点。每个节点只放字体和生成接口,不需要完整的织梦代码。
4.4 后台接口的暴露面收窄
如果不需要后台,部署后第一步就建议把/dede/目录改名,或者在 Nginx 层限制 IP 访问:
location ^~ /dede/ { allow 1.2.3.4; # 更换为自己的管理IP deny all; }因为后台管理功能在这个源码里只是辅助,公开出去只会引来扫描和弱口令爆破。关闭后台后,前台完全不受影响。这是很多客户定制源码时不理解的一点:DedeCMS 后台是给运维用的,不是给访客用的。
5. 字体预加载与子集化:艺术字服务的线上优化技巧
5.1 首屏字体加载控制
市面上很多在线字体转换器打开就卡十秒,原因就是一次性把所有@font-face字体文件全请求了一遍。虽然浏览器只在字体真正被使用时发起下载,但预览区默认展示的第一个字体如果特别大,首屏体验依然很差。我给出的处理方案是:首屏只加载默认字体,其他字体等到用户下拉切换时再异步注入。
实现上很简单,把完整字体列表写在一个 JS 变量里,每次用户选择某个字体时,动态创建<link rel="stylesheet" href="ziti-xxx.css">,并在onload回调里刷新预览。这样每种字体的加载动作被拆开了,整体页面首屏体积会小很多。
5.2 实现字体子集化的完整脚本
线上用户输入千奇百怪,要让每个字都有渲染,就必须有一个后端子集化服务。我写过一个最简版本,依赖fonttools和 Flask:
import os from fontTools.subset import Subsetter, Options from flask import Flask, request, jsonify app = Flask(__name__) SUBSET_BASE = "/var/www/ziti/fonts" @app.route("/subset", methods=["POST"]) def subset_font(): text = request.json.get("text", "") font_name = request.json.get("font", "FZXK") if not text: return jsonify({"error": "text required"}), 400 src_font = os.path.join(SUBSET_BASE, f"{font_name}.ttf") if not os.path.exists(src_font): return jsonify({"error": "font not found"}), 404 options = Options() options.flavor = "woff2" options.name_IDs = ["*"] # 保留所有name表项 options.name_legacy = True subsetter = Subsetter(options=options) subsetter.populate(text=text) subsetter.load_font(src_font) subsetter.subset() subsetter.save(os.path.join(SUBSET_BASE, f"{font_name}_subset.woff2")) return jsonify({"url": f"/fonts/{font_name}_subset.woff2"})populate(text=text)会把入参里的每个字符提取到子集中,这样用户输入“艺术字”三个字,生成的字体文件只包含这几个字,体积甚至可以压到几 KB。flavor="woff2"把子集输出直接压成 WOFF2,比 TTF 省流量。对于用户未输入但页面需要展示的固定文案,可以在 populate 前先把默认字符并进集合。
这个服务需要放在生成接口前面,用户在预览框失焦后先请求/subset,拿到子集字体的 URL,再把 URL 动态塞给@font-face,最后画 canvas 导出。整个链路从“加载全部字体”变成了“按需加载字体”,页面响应速度和服务器带宽消耗都会明显改善。
5.3 验证优化是否生效
改动后不能只看肉眼效果,要用抓包工具验证资源加载时序。在浏览器开发者工具里过滤字体文件请求,确认每个字体只有被选中时才发起请求,没有出现一次性挂载所有字体的情况。然后对子集化接口做一次响应测试:
curl -X POST http://yourdomain/subset \ -H "Content-Type: application/json" \ -d '{"text": "在线字体转换", "font": "FZXK"}' \ -o output.woff2 ls -lh output.woff2如果返回文件体积只有几 KB,说明子集化生效了。再查看响应头里有没有正确的缓存字段,没有的话在 Nginx 对应 location 里加上add_header Cache-Control "public, max-age=86400";,避免同一批文字反复生成重复字体文件。最后一步,用不同浏览器各翻一遍包含全部字体的页面,确认字图切换没有闪出不支持的字体样式,再清掉旧的浏览器缓存,整套优化就算闭环了。
本文还有配套的精品资源,点击获取