add woff2ttf modify import

This commit is contained in:
mkwiser
2014-10-06 12:09:02 +08:00
parent f3ccd474fa
commit 76e1a51bfd
10 changed files with 428 additions and 80 deletions

55
demo/js/woff2ttf.js Normal file
View File

@@ -0,0 +1,55 @@
/**
* @file woff2ttf.js
* @author mengke01
* @date
* @description
* woff 转ttf
*/
define(
function(require) {
var ajaxBinaryFile = require('common/ajaxBinaryFile');
var woff2ttf = require('ttf/woff2ttf');
var TTFReader = require('ttf/ttfreader');
var inflate = require('inflate').inflate;
var ttf2base64 = require('ttf/ttf2base64');
function write() {
ajaxBinaryFile({
url: '../font/iconfont.woff',
onSuccess: function(buffer) {
var ttfBuffer = woff2ttf(buffer, {
inflate: inflate
});
var saveBtn = $('.saveas');
saveBtn.attr('href', ttf2base64(ttfBuffer));
saveBtn.attr('download', 'save.woff');
var ttfReader = new TTFReader();
var ttfData = ttfReader.read(ttfBuffer);
console.log(ttfData);
},
onError: function() {
console.error('error read file');
}
});
}
var entry = {
/**
* 初始化
*/
init: function () {
write();
}
};
entry.init();
return entry;
}
);

29
demo/woff2ttf.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>woff2ttf</title>
<script src="../dep/esl.js"></script>
<script src="../dep/jquery.min.js"></script>
</head>
<body>
<a class="saveas" href="" download="">保存</a>
<script>
require.config({
baseUrl: '../src',
paths: {
demo: '../demo/js',
inflate: '../dep/pako_inflate.min'
}
});
define('jquery', $);
</script>
<script>
require(['demo/woff2ttf']);
</script>
</body>
</html>

2
dep/pako_inflate.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -11,18 +11,22 @@ define(
var program = require('../program');
var TTFReader = require('ttf/ttfreader');
var contours2svg = require('ttf/util/contours2svg');
var glyf2svg = require('ttf/util/glyf2svg');
var svg2ttfobject = require('ttf/svg2ttfobject');
var string = require('common/string');
var pathAdjust = require('graphics/pathAdjust');
var TTFWriter = require('ttf/ttfwriter');
var ttf2woff = require('ttf/ttf2woff');
var woff2ttf = require('ttf/woff2ttf');
var ttf2base64 = require('ttf/ttf2base64');
var woff2base64 = require('ttf/woff2base64');
var woffOptions = {
inflate: require('inflate').inflate
};
var GLYF_ITEM_TPL = ''
+ '<div class="glyf-item ${modify}">'
+ '<div data-index="${index}" class="glyf-item ${modify}">'
+ '<svg class="glyf" viewbox="0 0 ${unitsPerEm} ${unitsPerEm}"><g transform="scale(1, -1) translate(0, -${descent}) scale(0.95, 0.95) "><path class="path" ${d}/></g></svg>'
+ '<div class="unicode" title="${unicode}">${unicode}</div><div class="name" title="${name}">${name}</div>'
+ '</div>';
@@ -35,7 +39,7 @@ define(
$('#font-import').click();
},
export: function() {
$(this).attr('href', '#');
//$(this).attr('href', '#');
}
};
@@ -43,18 +47,23 @@ define(
function showGLYF(ttf) {
var unitsPerEm = ttf.head.unitsPerEm;
var descent = unitsPerEm + ttf.hhea.descent;
var glyfStr = '';
ttf.glyf.forEach(function(glyf) {
var glyfStr = '', d = '';
ttf.glyf.forEach(function(glyf, index) {
var g = {
index: index,
modify: glyf.modify,
unitsPerEm: unitsPerEm,
descent: descent,
unicode: (glyf.unicode || []).map(function(u) {
return '$' + u.toString(16).toUpperCase();
}).join(','),
name: glyf.name,
d: glyf.contours && glyf.contours.length ? 'd="'+ contours2svg(glyf.contours) +'"': ''
name: glyf.name
};
if (d = glyf2svg(glyf, ttf)) {
g.d = 'd="'+ d +'"';
}
glyfStr += string.format(GLYF_ITEM_TPL, g);
});
@@ -62,11 +71,16 @@ define(
}
// 加载ttf
function loadTTF(file) {
function loadSFNT(file, type) {
program.loading.show();
var fileReader = new FileReader();
fileReader.onload = function(e) {
var buffer = e.target.result;
if (type == 'woff') {
buffer = woff2ttf(buffer, woffOptions);
}
var ttfReader = new TTFReader();
program.data.ttf = ttfReader.read(buffer);
showGLYF(program.data.ttf);
@@ -127,9 +141,9 @@ define(
function onUpFile(e) {
var file = e.target.files[0];
if (program.action == 'open' && file.name.match(/\.ttf$/)) {
if (program.action == 'open' && file.name.match(/(\.ttf|\.woff)$/)) {
program.data.file = file.name;
loadTTF(file);
loadSFNT(file, file.name.slice(file.name.lastIndexOf('.') + 1));
}
else if (program.action == 'svg' && file.name.match(/\.svg$/)) {
if (program.data.ttf) {
@@ -149,8 +163,9 @@ define(
var ttf = program.data.ttf;
if (ttf) {
var buffer = new TTFWriter().write(ttf);
e.target.download = (ttf.name.fontFamily || 'export') + '.ttf';
e.target.href = ttf2base64(buffer);
var target = $(e.target);
target.attr('download', (ttf.name.fontFamily || 'export') + '.ttf');
target.attr('href', ttf2base64(buffer));
}
}

View File

@@ -76,7 +76,7 @@ define(
ttf.head.xMax = Math.round(ttf.head.xMax);
ttf.head.yMin = Math.round(ttf.head.yMin);
ttf.head.yMax = Math.round(ttf.head.yMax);
ttf.head.unitsPerEm = Math.round(ttf.head.unitsPerEm || 1024);
ttf.head.unitsPerEm = ttf.head.unitsPerEm ? Math.round(ttf.head.unitsPerEm) : 0;
return ttf;
}
@@ -148,6 +148,14 @@ define(
}
}
// 如果没有定义unitsPerEm可以用viewBox代替
if (!ttf.head.unitsPerEm && svgNode.getAttribute('viewBox')) {
var bound = svgNode.getAttribute('viewBox').split(' ');
if (bound.length == 4) {
ttf.head.unitsPerEm = +bound[2];
}
}
// 解析glyf
var d, unicode;
if (missingNode) {

View File

@@ -19,7 +19,6 @@ define(
function(require) {
var Reader = require('./reader');
var Writer = require('./writer');
var checkSum = require('./util/checkSum');
var string = require('common/string');
/**
@@ -134,6 +133,7 @@ define(
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
* @param {Object} options 选项
* @param {Object} options.metadata 字体相关的信息
* @param {Object} options.deflate 压缩相关函数
*
* @return {ArrayBuffer} woff格式byte流
*/

View File

@@ -14,8 +14,10 @@
define(
function(require) {
var contour2svg = require('./contour2svg');
var contours2svg = require('./contours2svg');
var pathAdjust = require('graphics/pathAdjust');
var matrixTransform = require('graphics/transform');
var lang = require('common/lang');
/**
* glyf转换svg
@@ -23,29 +25,25 @@ define(
* @param {Object} glyf 解析后的glyf结构
* @return {string} svg文本
*/
function glyf2svg(glyf, options) {
if(!glyf) {
return null;
}
var pathArray = [];
var contours = glyf.contours;
var height = glyf.yMax;
var x = options.x || 0;
var y = height + (options.y || 0);
var scale = options.scale || 1;
function glyf2svg(glyf, ttf) {
if(!glyf) {
return '';
}
var pathArray = [];
if (!glyf.compound) {
for ( var i = 0, l = contours.length; i < l; i++) {
pathAdjust(contours[i], 1, -1);
pathAdjust(contours[i], scale, scale, x, y);
pathArray.push(contour2svg(contours[i]));
if (glyf.contours && glyf.contours.length) {
pathArray.push(contours2svg(glyf.contours));
}
}
else {
var glyfs = glyf.glyfs;
glyfs.forEach(function(g) {
var contours = g.glyf.contours;
var compound = ttf.glyf[g.glyphIndex];
var contours = lang.clone(compound.contours); // 这里需要进行matrix变换需要复制一份
var transform = g.transform;
for ( var i = 0, l = contours.length; i < l; i++) {
matrixTransform(contours[i],
@@ -56,8 +54,6 @@ define(
transform.e,
transform.f
);
pathAdjust(contours[i], 1, -1);
pathAdjust(contours[i], scale, scale, x, y);
pathArray.push(contour2svg(contours[i]));
}
});

View File

@@ -71,9 +71,10 @@ define(
// 获取segments
var segments = [];
var cmd, lastIndex, args, segReg = /\-?\d+(?:\.\d+)?\b/g;
var cmd, relative = false, c, r, lastIndex, args, segReg = /\-?\d+(?:\.\d+)?\b/g;
for (var i = 0, l = path.length;i < l; i++) {
var c = path.charAt(i).toUpperCase();
c = path[i].toUpperCase();
r = c != path[i];
switch (c) {
case 'M':
if (i == 0) {
@@ -97,6 +98,7 @@ define(
args = path.slice(lastIndex, i);
segments.push({
cmd: cmd,
relative: relative,
args: args.match(segReg).map(function(d) {
return +d.trim();
})
@@ -104,6 +106,7 @@ define(
}
cmd = c;
relative = r;
lastIndex = i + 1;
break;
}
@@ -115,39 +118,86 @@ define(
var contours = [], contour = [], prevX = 0, prevY = 0, prevc1;
for (var i = 0, l = segments.length;i < l; i++) {
segment = segments[i];
cmd = segment.cmd;
relative = segment.relative;
if (segment.cmd === 'Z') {
if (cmd === 'Z') {
contours.push(contour);
contour = [];
}
else if (segment.cmd === 'M') {
prevX = segment.args[0];
prevY = segment.args[1];
else if (cmd === 'M') {
if (relative) {
prevX += segment.args[0];
prevY += segment.args[1];
}
else {
prevX = segment.args[0];
prevY = segment.args[1];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
else if (segment.cmd === 'H') {
prevX += segment.args[0];
else if (cmd === 'H') {
if (relative) {
prevX += segment.args[0];
}
else {
prevX = segment.args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
else if (segment.cmd === 'V') {
prevY += segment.args[0];
else if (cmd === 'V') {
if (relative) {
prevY += segment.args[0];
}
else {
prevY = segment.args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
else if (segment.cmd === 'L') {
prevX += segment.args[0];
prevY += segment.args[1];
else if (cmd === 'L') {
// 这里可能会连续绘制,最后一个是终点
var q = 0, ql = segment.args.length - 2, px = 0, py = 0;
if (relative) {
px = prevX;
py = prevY;
}
for (; q < ql ; q += 2) {
contour.push({
x: px + segment.args[q],
y: py + segment.args[q + 1],
onCurve: true
});
}
if (relative) {
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
}
else {
prevX = segment.args[ql];
prevY = segment.args[ql + 1];
}
contour.push({
x: prevX,
y: prevY,
@@ -155,40 +205,78 @@ define(
});
}
// 二次贝塞尔
else if (segment.cmd === 'Q') {
else if (cmd === 'Q') {
// 这里可能会连续绘制,最后一个是终点
var q = 0, ql = segment.args.length - 2;
for (; q < ql ; q += 2) {
var q = 0, ql = segment.args.length, px = 0, py = 0;
if (relative) {
px = prevX;
py = prevY;
}
for (; q < ql ; q += 4) {
contour.push({
x: prevX + segment.args[q],
y: prevY + segment.args[q + 1]
x: px + segment.args[q],
y: py + segment.args[q + 1]
});
contour.push({
x: px + segment.args[q + 2],
y: py + segment.args[q + 3],
onCurve: true
});
}
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
ql = segment.args.length - 2;
if (relative) {
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
}
else {
prevX = segment.args[ql];
prevY = segment.args[ql + 1];
}
}
// 二次贝塞尔平滑
else if (segment.cmd === 'T') {
else if (cmd === 'T') {
var pc = contour[contour.length - 2];
// 这里需要移除上一个曲线的终点
contour.pop();
var last = contour.pop();
var pc = contour[contour.length - 1];
contour.push({
x: 2 * prevX - pc.x,
y: 2 * prevY - pc.y
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
prevX += segment.args[0];
prevY += segment.args[1];
var q = 0, ql = segment.args.length - 2, px = 0, py = 0;
if (relative) {
px = prevX;
py = prevY;
}
for (; q < ql ; q += 2) {
pc = contour[contour.length - 1];
last = {
x: px + segment.args[q],
y: py + segment.args[q + 1]
};
contour.push({
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
}
if (relative) {
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
}
else {
prevX = segment.args[ql];
prevY = segment.args[ql + 1];
}
contour.push({
x: prevX,
y: prevY,
@@ -197,21 +285,26 @@ define(
}
// 三次贝塞尔
else if (segment.cmd === 'C') {
else if (cmd === 'C') {
// 这里可能会连续绘制,最后一个是终点
var q = 0, ql = segment.args.length - 2;
var q = 0, ql = segment.args.length - 2, px = 0, py = 0;
var cubicList = [];
if (relative) {
px = prevX;
py = prevY;
}
for (; q < ql ; q += 4) {
var cubic = [];
var c1 = {
x: prevX + segment.args[q],
y: prevY + segment.args[q + 1]
x: px + segment.args[q],
y: py + segment.args[q + 1]
};
var c2 = {
x: prevX + segment.args[q + 2],
y: prevY + segment.args[q + 3]
x: px + segment.args[q + 2],
y: py + segment.args[q + 3]
};
// 计算中间点
@@ -236,29 +329,53 @@ define(
cubicList.push(cubic);
}
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
if (relative) {
prevX += segment.args[ql];
prevY += segment.args[ql + 1];
}
else {
prevX = segment.args[ql];
prevY = segment.args[ql + 1];
}
cubicList[cubicList.length - 1].push({x: prevX, y: prevY});
cubic2Points(cubicList, contour);
prevc1 = cubicList[cubicList.length - 1][2];
}
// 三次贝塞尔平滑
else if (segment.cmd === 'S') {
else if (cmd === 'S') {
// TODO 这里没有支持连续的情况,有时间再搞
if (segment.args.length > 4) {
throw 'not support svg "S" command continuous!'
}
// 这里需要移除上一个曲线的终点
var p1 = contour.pop();
var c1 = {
x: 2 * p1.x - prevc1.x,
y: 2 * p1.y - prevc1.y
};
var px = 0, py = 0;
if (relative) {
px = prevX;
py = prevY;
}
var c2 = {
x: prevX + segment.args[0],
y: prevX + segment.args[1]
x: px + segment.args[0],
y: py + segment.args[1]
};
prevX += segment.args[2];
prevY += segment.args[3];
if (relative) {
prevX += segment.args[2];
prevY += segment.args[3];
}
else {
prevX = segment.args[2];
prevY = segment.args[3];
}
var p2 = {
x: prevX,
@@ -269,7 +386,7 @@ define(
prevc1 = c2;
}
// 圆弧
else if (segment.cmd === 'A') {
else if (cmd === 'A') {
throw 'not support arc';
}
}

122
src/ttf/woff2ttf.js Normal file
View File

@@ -0,0 +1,122 @@
/**
* @file woff2ttf.js
* @author mengke01
* @date
* @description
* woff转换ttf
*/
define(
function(require) {
var Reader = require('./reader');
var Writer = require('./writer');
/**
* 写空数据
*
* @param {Writer} writer writer对象
* @param {number} length 大小
* @return {Writer}
*/
function writeEmpty(writer, length) {
while (length-- > 0) {
writer.writeUint8(0);
}
return writer;
}
/**
* woff格式转换成ttf字体格式
*
* @param {ArrayBuffer} woffBuffer ttf缓冲数组
* @param {Object} options 选项
* @param {Object} options.inflate 解压相关函数
*
* @return {ArrayBuffer} woff格式byte流
*/
function woff2ttf(woffBuffer, options) {
options = options || {};
var reader = new Reader(woffBuffer);
var signature = reader.readUint32(0);
var flavor = reader.readUint32(4);
if (signature !== 0x774F4646 || flavor !== 0x10000) {
reader.dispose();
throw 'not a ttf sfnt format!';
}
var numTables = reader.readUint16(12);
var ttfSize = reader.readUint32(16);
var tableEntries = [];
// 读取woff表索引信息
for (var i = 0; i < numTables; ++i) {
reader.seek(44 + i * 20);
var tableEntry = {
tag: reader.readString(reader.offset, 4),
offset: reader.readUint32(),
compLength: reader.readUint32(),
length: reader.readUint32(),
checkSum: reader.readUint32()
};
// ttf 表数据
var deflateData = reader.readBytes(tableEntry.offset, tableEntry.compLength);
// 需要解压
if (deflateData.length < tableEntry.length) {
if (!options.inflate) {
reader.dispose();
throw 'no inflate function!';
}
tableEntry.data = options.inflate(deflateData);
}
else {
tableEntry.data = deflateData;
}
tableEntry.length = tableEntry.data.length;
tableEntries.push(tableEntry);
}
var writer = new Writer(new ArrayBuffer(ttfSize));
// 写头部
var entrySelector = Math.floor(Math.log(numTables)/Math.LN2);
var searchRange = Math.pow(2, entrySelector) * 16;
var rangeShift = numTables * 16 - searchRange;
writer.writeFixed(1);
writer.writeUint16(numTables);
writer.writeUint16(searchRange);
writer.writeUint16(entrySelector);
writer.writeUint16(rangeShift);
// 写ttf表索引
var tblOffset = 12 + 16 * tableEntries.length;
for (var i = 0, l = tableEntries.length; i < l; ++i) {
var tableEntry = tableEntries[i];
writer.writeString(tableEntry.tag);
writer.writeUint32(tableEntry.checkSum);
writer.writeUint32(tblOffset);
writer.writeUint32(tableEntry.length);
tblOffset += tableEntry.length + (tableEntry.length % 4 ? 4 - tableEntry.length % 4 : 0)
}
// 写ttf表数据
for (var i = 0, l = tableEntries.length; i < l; ++i) {
var tableEntry = tableEntries[i];
writer.writeBytes(tableEntry.data);
if (tableEntry.length % 4) {
writeEmpty(writer, 4 - tableEntry.length % 4);
}
}
return writer.getBuffer();
}
return woff2ttf;
}
);

View File

@@ -36,7 +36,11 @@
<script src="./dep/bootstrap/js/bootstrap.min.js"></script>
<script>
require.config({
baseUrl: './src'
baseUrl: './src',
paths: {
deflate: '../dep/pako_deflate.min',
inflate: '../dep/pako_inflate.min'
}
});
define('jquery', $);
require(['fonteditor/ttf/main'])