298 lines
8.6 KiB
Plaintext
298 lines
8.6 KiB
Plaintext
/**
|
|
* @file ttf转换为woff
|
|
* @author mengke01(kekee000@gmail.com)
|
|
*
|
|
* woff format:
|
|
* http://www.w3.org/TR/2012/REC-WOFF-20121213/
|
|
*
|
|
* references:
|
|
* https://github.com/fontello/ttf2woff
|
|
* https://github.com/nodeca/pako
|
|
*/
|
|
/* eslint-disable no-multi-spaces */
|
|
import Reader from './reader';
|
|
import Writer from './writer';
|
|
import string from '../common/string';
|
|
import utilString from './util/string';
|
|
import error from './error';
|
|
import config from './data/default';
|
|
|
|
/**
|
|
* metadata 转换成XML
|
|
*
|
|
* @param {Object} metadata metadata
|
|
*
|
|
* @example
|
|
* metadata json:
|
|
*
|
|
* {
|
|
* "uniqueid": "",
|
|
* "vendor": {
|
|
* "name": "",
|
|
* "url": ""
|
|
* },
|
|
* "credit": [
|
|
* {
|
|
* "name": "",
|
|
* "url": "",
|
|
* "role": ""
|
|
* }
|
|
* ],
|
|
* "description": "",
|
|
* "license": {
|
|
* "id": "",
|
|
* "url": "",
|
|
* "text": ""
|
|
* },
|
|
* "copyright": "",
|
|
* "trademark": "",
|
|
* "licensee": ""
|
|
* }
|
|
*
|
|
* @return {string} xml字符串
|
|
*/
|
|
function metadata2xml(metadata) {
|
|
let xml = ''
|
|
+ '<?xml version="1.0" encoding="UTF-8"?>'
|
|
+ '<metadata version="1.0">';
|
|
|
|
metadata.uniqueid = metadata.uniqueid || (config.fontId + '.' + Date.now());
|
|
xml += '<uniqueid id="' + string.encodeHTML(metadata.uniqueid) + '" />';
|
|
|
|
if (metadata.vendor) {
|
|
xml += '<vendor name="' + string.encodeHTML(metadata.vendor.name) + '"'
|
|
+ ' url="' + string.encodeHTML(metadata.vendor.url) + '" />';
|
|
}
|
|
|
|
if (metadata.credit) {
|
|
xml += '<credits>';
|
|
const credits = metadata.credit instanceof Array ? metadata.credit : [metadata.credit];
|
|
|
|
credits.forEach((credit) => {
|
|
xml += '<credit name="' + string.encodeHTML(credit.name) + '"'
|
|
+ ' url="' + string.encodeHTML(credit.url) + '"'
|
|
+ ' role="' + string.encodeHTML(credit.role || 'Contributor') + '" />';
|
|
});
|
|
|
|
xml += '</credits>';
|
|
}
|
|
|
|
if (metadata.description) {
|
|
xml += '<description><text xml:lang="en">'
|
|
+ string.encodeHTML(metadata.description)
|
|
+ '</text></description>';
|
|
}
|
|
|
|
if (metadata.license) {
|
|
xml += '<license url="' + string.encodeHTML(metadata.license.url) + '"'
|
|
+ ' id="' + string.encodeHTML(metadata.license.id) + '"><text xml:lang="en">';
|
|
xml += string.encodeHTML(metadata.license.text);
|
|
xml += '</text></license>';
|
|
}
|
|
|
|
if (metadata.copyright) {
|
|
xml += '<copyright><text xml:lang="en">';
|
|
xml += string.encodeHTML(metadata.copyright);
|
|
xml += '</text></copyright>';
|
|
}
|
|
|
|
if (metadata.trademark) {
|
|
xml += '<trademark><text xml:lang="en">'
|
|
+ string.encodeHTML(metadata.trademark)
|
|
+ '</text></trademark>';
|
|
}
|
|
|
|
if (metadata.licensee) {
|
|
xml += '<licensee name="' + string.encodeHTML(metadata.licensee) + '"/>';
|
|
}
|
|
|
|
xml += '</metadata>';
|
|
|
|
return xml;
|
|
}
|
|
|
|
|
|
/**
|
|
* ttf格式转换成woff字体格式
|
|
*
|
|
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
|
|
* @param {Object} options 选项
|
|
* @param {Object} options.metadata 字体相关的信息
|
|
* @param {Object} options.deflate 压缩相关函数
|
|
*
|
|
* @return {ArrayBuffer} woff格式byte流
|
|
*/
|
|
export default function ttf2woff(ttfBuffer, options = {}) {
|
|
|
|
// woff 头部结构
|
|
const woffHeader = {
|
|
signature: 0x774F4646, // for woff
|
|
flavor: 0x10000, // for ttf
|
|
length: 0,
|
|
numTables: 0,
|
|
reserved: 0,
|
|
totalSfntSize: 0,
|
|
majorVersion: 0,
|
|
minorVersion: 0,
|
|
metaOffset: 0,
|
|
metaLength: 0,
|
|
metaOrigLength: 0,
|
|
privOffset: 0,
|
|
privLength: 0
|
|
};
|
|
|
|
const ttfReader = new Reader(ttfBuffer);
|
|
let tableEntries = [];
|
|
const numTables = ttfReader.readUint16(4); // 读取ttf表个数
|
|
let tableEntry;
|
|
let deflatedData;
|
|
let i;
|
|
let l;
|
|
|
|
if (numTables <= 0 || numTables > 100) {
|
|
error.raise(10101);
|
|
}
|
|
|
|
// 读取ttf表索引信息
|
|
ttfReader.seek(12);
|
|
|
|
for (i = 0; i < numTables; ++i) {
|
|
|
|
tableEntry = {
|
|
tag: ttfReader.readString(ttfReader.offset, 4),
|
|
checkSum: ttfReader.readUint32(),
|
|
offset: ttfReader.readUint32(),
|
|
length: ttfReader.readUint32()
|
|
};
|
|
|
|
const entryOffset = ttfReader.offset;
|
|
|
|
if (tableEntry.tag === 'head') {
|
|
// 读取font revision
|
|
woffHeader.majorVersion = ttfReader.readUint16(tableEntry.offset + 4);
|
|
woffHeader.minorVersion = ttfReader.readUint16(tableEntry.offset + 6);
|
|
}
|
|
|
|
// ttf 表数据
|
|
const sfntData = ttfReader.readBytes(tableEntry.offset, tableEntry.length);
|
|
|
|
// 对数据进行压缩
|
|
if (options.deflate) {
|
|
deflatedData = options.deflate(sfntData);
|
|
|
|
// 这里需要判断是否压缩后数据小于原始数据
|
|
if (deflatedData.length < sfntData.length) {
|
|
tableEntry.data = deflatedData;
|
|
tableEntry.deflated = true;
|
|
}
|
|
else {
|
|
tableEntry.data = sfntData;
|
|
}
|
|
}
|
|
else {
|
|
tableEntry.data = sfntData;
|
|
}
|
|
|
|
tableEntry.compLength = tableEntry.data.length;
|
|
tableEntries.push(tableEntry);
|
|
ttfReader.seek(entryOffset);
|
|
}
|
|
|
|
if (!tableEntries.length) {
|
|
error.raise(10204);
|
|
}
|
|
|
|
// 对table进行排序
|
|
tableEntries = tableEntries.sort((a, b) => a.tag === b.tag ? 0 : a.tag < b.tag ? -1 : 1);
|
|
|
|
// 计算offset和 woff size
|
|
let woffSize = 44 + 20 * numTables; // header size + table entries
|
|
let ttfSize = 12 + 16 * numTables;
|
|
|
|
for (i = 0, l = tableEntries.length; i < l; ++i) {
|
|
tableEntry = tableEntries[i];
|
|
tableEntry.offset = woffSize;
|
|
// 4字节对齐
|
|
woffSize += tableEntry.compLength + (tableEntry.compLength % 4 ? 4 - tableEntry.compLength % 4 : 0);
|
|
ttfSize += tableEntry.length + (tableEntry.length % 4 ? 4 - tableEntry.length % 4 : 0);
|
|
}
|
|
|
|
// 计算metaData
|
|
let metadata = null;
|
|
if (options.metadata) {
|
|
const xml = utilString.toUTF8Bytes(metadata2xml(options.metadata));
|
|
|
|
if (options.deflate) {
|
|
deflatedData = options.deflate(xml);
|
|
if (deflatedData.length < xml.length) {
|
|
metadata = deflatedData;
|
|
}
|
|
else {
|
|
metadata = xml;
|
|
}
|
|
}
|
|
else {
|
|
metadata = xml;
|
|
}
|
|
|
|
woffHeader.metaLength = metadata.length;
|
|
woffHeader.metaOrigLength = xml.length;
|
|
woffHeader.metaOffset = woffSize;
|
|
// metadata header + length
|
|
woffSize += woffHeader.metaLength + (woffHeader.metaLength % 4 ? 4 - woffHeader.metaLength % 4 : 0);
|
|
}
|
|
|
|
woffHeader.numTables = tableEntries.length;
|
|
woffHeader.length = woffSize;
|
|
woffHeader.totalSfntSize = ttfSize;
|
|
|
|
// 写woff数据
|
|
const woffWriter = new Writer(new ArrayBuffer(woffSize));
|
|
|
|
// 写woff头部
|
|
woffWriter.writeUint32(woffHeader.signature);
|
|
woffWriter.writeUint32(woffHeader.flavor);
|
|
woffWriter.writeUint32(woffHeader.length);
|
|
woffWriter.writeUint16(woffHeader.numTables);
|
|
woffWriter.writeUint16(woffHeader.reserved);
|
|
woffWriter.writeUint32(woffHeader.totalSfntSize);
|
|
woffWriter.writeUint16(woffHeader.majorVersion);
|
|
woffWriter.writeUint16(woffHeader.minorVersion);
|
|
woffWriter.writeUint32(woffHeader.metaOffset);
|
|
woffWriter.writeUint32(woffHeader.metaLength);
|
|
woffWriter.writeUint32(woffHeader.metaOrigLength);
|
|
woffWriter.writeUint32(woffHeader.privOffset);
|
|
woffWriter.writeUint32(woffHeader.privLength);
|
|
|
|
|
|
// 写woff表索引
|
|
for (i = 0, l = tableEntries.length; i < l; ++i) {
|
|
tableEntry = tableEntries[i];
|
|
woffWriter.writeString(tableEntry.tag);
|
|
woffWriter.writeUint32(tableEntry.offset);
|
|
woffWriter.writeUint32(tableEntry.compLength);
|
|
woffWriter.writeUint32(tableEntry.length);
|
|
woffWriter.writeUint32(tableEntry.checkSum);
|
|
}
|
|
|
|
// 写表数据
|
|
for (i = 0, l = tableEntries.length; i < l; ++i) {
|
|
tableEntry = tableEntries[i];
|
|
woffWriter.writeBytes(tableEntry.data);
|
|
|
|
if (tableEntry.compLength % 4) {
|
|
woffWriter.writeEmpty(4 - tableEntry.compLength % 4);
|
|
}
|
|
}
|
|
|
|
// 写metadata
|
|
if (metadata) {
|
|
woffWriter.writeBytes(metadata);
|
|
if (woffHeader.metaLength % 4) {
|
|
woffWriter.writeEmpty(4 - woffHeader.metaLength % 4);
|
|
}
|
|
}
|
|
|
|
return woffWriter.getBuffer();
|
|
}
|