zFonts在线生成需要的字体

792 阅读3分钟

在线演示

背景

前端一般都会遇到这个场景,页面使用酷炫的字体,有时候可能会使用图片,但是文字过多,或者想更加灵活的使用这些文字可能就需要引入一些字体了,但往往会有一个问题就是,一般中文字体的大小都是10M左右,这让前端很头痛(shit, 这引用个毛毛),当然也有一些优秀的解决方案,比如fontmin, font-spider,之类的,该工具就是以fontmin为插件完成的一个可视化工具。

前端部分

前端我是使用vue-cli3搭建的项目,但是其实主要的功能其实已经在后端部分完成,前端只是多以展示为主,主要功能分为几个

  • 字体转化
  • 上传新字体
  • 下载源字体
  • 下载转化字体 功能上,主要为后端完成,包含转化,上传处理,下载
字体转化

这个是最主要的部分,使用接口文字部分传给后端,让后端处理文字后,生成文字地址返回给前端

上传新字体

前端将文件传给后端,后端返回链接

下载

分两个,一个是源字体下载,即上传后生成的链接下载,一个是转化字体下载,其实就是将生成的字体的链接返回给前端,提供下载

这里考虑到上传和下载源字体的风险比较大,所以做了权限处理,使用的jsonwebtoken,校验使用者是否合法,即我们的常见的登录,将返回的token带入上传和删除的接口中,判断是否有权限删除

注意点

前端处理展示问题的时候有个主意事项,即转化文字后的展示部分因为需要动态显示不同字体,所以需要动态替换当前的font-face,所以生成的文字需要是有哈希值的避免引用的时候有缓存,因为生成的字体替换到css需要下载下来所以我们还需要发起一个请求,下载字体,将字体转化为base64,并将解析格式application/octet-stream转化为font/ttf,否则浏览器不能解析当前的字体文件

// 将文件转化为base64
blobToDataURI (blob, callback) {
    var reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onload = function (e) {
        callback(e.target.result);
    }
}
// 获取字体并动态转化
axios({
    url: src,
    method: 'GET',
    responseType: 'blob'
}).then(res => {
    this.blobToDataURI(res.data, (e) => {
    let a = e.replace('application/octet-stream', 'font/ttf')
    this.addCSS(`@font-face {
       font-family: 'webFont';
        src: url('${a}') format('truetype');
    }`)
})

后端部分

后端服务是使用koa搭建的,因为比较简单,主要使用的插件是fontmin(字体转化的核心),koa-body(上传),jsonwebtoken(auth校验),compressing(压缩文件),功能罗列出来后其实就是一一对应实现就行,而大部分的实现其实都有先例或者一般都是插件的使用,所以即便遇到问题也很好解决

  • 上传
  • 压缩
  • 转化

上传

const koaBody = require('koa-body')
const SIZE = 10 // 10M
const uploadConfig = () => {
  return koaBody({
    multipart: true, // 支持文件上传
    // encoding: 'gzip',
    formidable: {
      // uploadDir: UPLOADPATH, // 设置文件上传目录
      keepExtensions: true, // 保持文件的后缀
      maxFieldsSize: SIZE * 1024 * 1024 // 文件上传大小
    }
  })
}

const uploadFn = ()=> {
    ....
    let uploadUrl = path.join(__dirname, './../../fonts', name)
    const reader = fs.createReadStream(ctx.request.files.file.path);
    // 创建可写流
    const upStream = fs.createWriteStream(uploadUrl);
    // 可读流通过管道写入可写流
    reader.pipe(upStream);
    ....
}

压缩

// 获取下载处理后的ttf压缩文件地址
const downLoad = () => {
  router.post(`${proName}/download`, async (ctx, next) => {
    let downloadUrl = path.resolve(__dirname, `./../example.zip`)
    let query = ctx.body
    let durl = `./../font`
    if (query.hash) {
      durl = `./../font/${query.hash}`
    }
    await compressing.zip.compressDir(path.resolve(__dirname, durl), path.resolve(__dirname, '../example.zip'))
      .then(() => {
        ctx.body = {
          res: 'success',
          data: downloadUrl
        }
      })
      .catch(err => {
        console.error(err);
      });
    await next()
  })
}

字体转化

const dealFont = (query) => {
  const fontMinObj = new Fontmin()
  let ttfPath = path.resolve(__dirname, `./../../fonts/${query.type}.ttf`) // 获取字体
  let random = randomNum() // 生成随机字符串当做hash值
  let renameFont = 'font.ttf' // 重命名字体
  let ppath = `../font`
  if (query.hash) { // 部署到远程服务器需要开启哈希值 否则获取字体会有缓存
    renameFont = `font.${random}.ttf`
    ppath = `../font/${random}`
  }
  const fontmin = fontMinObj.src(ttfPath)
    .use(Fontmin.glyph({ // 转化的核心
      text: query.text,
      hinting: false // keep ttf hint info (fpgm, prep, cvt). default = true
    }))
    .use(rename(renameFont)) // 重命名
    .dest(path.resolve(__dirname, ppath)) // 生成到xx目录
    .use(Fontmin.ttf2eot()) // 转化为eot
    .use(Fontmin.ttf2woff()) // 转化为woff

  fontmin.run(function (err, files) {
    if (err) {
      throw err;
    }
    console.log('\n', files[0]);
    // => { contents: <Buffer 00 01 00 ...> }
  });
  return new Promise((resolve, reject) => {
    resolve(random)
  })
}

以上就是核心的部分,第一次做工具类的东西,如有差错或建议还请指教或留言,万分感谢

更多详情请看这里github

在线演示