diff --git a/font/baiduHealth.fcp b/font/baiduHealth.fcp new file mode 100644 index 0000000..405954c Binary files /dev/null and b/font/baiduHealth.fcp differ diff --git a/font/baiduHealth.ttf b/font/baiduHealth.ttf new file mode 100644 index 0000000..126b2d0 Binary files /dev/null and b/font/baiduHealth.ttf differ diff --git a/font/baiduHealth.woff b/font/baiduHealth.woff new file mode 100644 index 0000000..ac427e5 Binary files /dev/null and b/font/baiduHealth.woff differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..73312dc --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + font编辑器 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/common/ajaxBinaryFile.js b/src/common/ajaxBinaryFile.js new file mode 100644 index 0000000..d933bbb --- /dev/null +++ b/src/common/ajaxBinaryFile.js @@ -0,0 +1,64 @@ +/** + * @file ajaxBinaryFile.js + * @author mengke01 + * @date + * @description + * ajax获取二进制数据 + */ + + +define( + function(require) { + + /** + * ajax获取二进制数据 + * + * @param {Object} options 参数选项 + * @param {string=} options.method method + * @param {Function=} options.onSuccess 成功回调 + * @param {Function=} options.onError 失败回调 + * @param {Object=} options.params 参数集合 + */ + function ajaxBinaryFile(options) { + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function(){ + if (xhr.readyState=== 4 && xhr.status === 200){ + if(options.onSuccess) { + var buffer = xhr.responseBlob || xhr.response; + options.onSuccess(buffer); + } + } + else if(xhr.status > 200){ + if(options.onError) { + options.onError(xhr, xhr.status); + } + } + }; + + var method = (options.method || 'GET').toUpperCase(); + var params = null; + + if (options.params) { + + var str = []; + Object.keys(options.params).forEach(function(key) { + str.push(key + '=' + encodeURIComponent(options.params[key])); + }); + str = str.join('&'); + if(method == 'GET') { + options.url += (options.url.indexOf('?') == -1 ? '?' : '&') + str; + } + else { + params = str; + } + } + + xhr.open(method, options.url, true); + xhr.responseType = "arraybuffer"; + xhr.send(null); + } + + return ajaxBinaryFile; + } +); diff --git a/src/common/lang.js b/src/common/lang.js new file mode 100644 index 0000000..ec008e1 --- /dev/null +++ b/src/common/lang.js @@ -0,0 +1,126 @@ +/** + * @file lang.js + * @author mengke01 + * @date + * @description + * 语言相关函数 + */ + + +define( + function(require) { + + /** + * 为函数提前绑定前置参数(柯里化) + * + * @see http://en.wikipedia.org/wiki/Currying + * @param {Function} fn 要绑定的函数 + * @param {...*=} args 函数执行时附加到执行时函数前面的参数 + * @return {Function} + */ + function curry( fn ) { + var xargs = [].slice.call( arguments, 1 ); + return function () { + var args = xargs.concat( [].slice.call( arguments ) ); + return fn.apply( this, args ); + }; + } + + + /** + * 方法静态化 + * + * 反绑定、延迟绑定 + * @inner + * @param {Function} method 待静态化的方法 + * + * @return {Function} 静态化包装后方法 + */ + function generic(method) { + return function () { + return Function.call.apply(method, arguments); + }; + } + + + /** + * 为函数绑定this与前置参数 + * + * @param {Function} fn 需要操作的函数 + * @param {Object} thisArg 需要绑定的this + * @param {...*=} args 函数执行时附加的前置绑定参数 + * @return {Function} + */ + function bind(fn, thisArg) { + var args = Array.prototype.slice.call(arguments, 2); + return function () { + return fn.apply( + thisArg, + // 绑定参数先于扩展参数 + // see http://es5.github.io/#x15.3.4.5.1 + args.concat(Array.prototype.slice.call(arguments)) + ); + }; + } + + /** + * 为类型构造器建立继承关系 + * + * @param {Function} subClass 子类构造器 + * @param {Function} superClass 父类构造器 + * @return {Function} + */ + function inherits( subClass, superClass ) { + var Empty = function () {}; + Empty.prototype = superClass.prototype; + var selfPrototype = subClass.prototype; + var proto = subClass.prototype = new Empty(); + + for ( var key in selfPrototype ) { + proto[ key ] = selfPrototype[ key ]; + } + subClass.prototype.constructor = subClass; + + return subClass; + } + + /** + * 对象属性拷贝 + * + * @param {Object} target 目标对象 + * @param {...Object} source 源对象 + * @return {Object} + */ + function extend( target, source ) { + for ( var i = 1, len = arguments.length; i < len; i++ ) { + source = arguments[ i ]; + + if ( !source ) { + continue; + } + + for ( var key in source ) { + if ( source.hasOwnProperty( key ) ) { + target[ key ] = source[ key ]; + } + } + + } + + return target; + } + + + + + var exports = { + extend: extend, + bind: bind, + inherits: inherits, + curry: curry, + uncurry: generic + }; + + return exports; + } +); diff --git a/src/common/pad.js b/src/common/pad.js new file mode 100644 index 0000000..f51675d --- /dev/null +++ b/src/common/pad.js @@ -0,0 +1,30 @@ +/** + * @file pad.js + * @author mengke01 + * @date + * @description + * 使用指定字符填充 + */ + + +define( + function(require) { + + /** + * 使用指定字符填充字符串,默认`0` + * + * @param {string} str 字符串 + * @param {number} size 填充到的大小 + * @param {string=} ch 填充字符 + * @return {string} 字符串 + */ + function pad(str, size, ch) { + if(str.length > size) { + return str.slice(str.length - size); + } + return new Array(size - str.length + 1).join(ch || '0') + str; + } + + return pad; + } +); diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..e6e7eac --- /dev/null +++ b/src/main.js @@ -0,0 +1,56 @@ +/** + * @file main.js + * @author mengke01 + * @date + * @description + * 主函数入口 + */ + + +define( + function(require) { + var ttfreader = require('ttf/ttfreader'); + var ajaxBinaryFile = require('common/ajaxBinaryFile'); + + function onUpFileChange(e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + var ttf = new ttfreader().read(e.target.result); + } + + reader.onerror = function(e) { + console.error(e); + }; + + reader.readAsArrayBuffer(file); + } + + var entry = { + + /** + * 初始化 + */ + init: function() { + var upFile = document.getElementById('upload-file'); + upFile.addEventListener('change', onUpFileChange); + + ajaxBinaryFile({ + url: 'font/baiduHealth.ttf', + onSuccess: function(binaryData) { + var ttf = new ttfreader().read(binaryData); + }, + onError: function() { + console.error('error read file'); + } + }); + + + } + }; + + entry.init(); + + return entry; + } +); diff --git a/src/ttf/enum/componentFlag.js b/src/ttf/enum/componentFlag.js new file mode 100644 index 0000000..bbccd73 --- /dev/null +++ b/src/ttf/enum/componentFlag.js @@ -0,0 +1,33 @@ +/** + * @file componentFlag.js + * @author mengke01 + * @date + * @description + * + * 复合图元标记位 + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html + */ + + +define( + function(require) { + + var componentFlag= { + ARG_1_AND_2_ARE_WORDS: 0x01, + ARGS_ARE_XY_VALUES: 0x02, + ROUND_XY_TO_GRID: 0x04, + WE_HAVE_A_SCALE: 0x08, + RESERVED: 0x10, + MORE_COMPONENTS: 0x20, + WE_HAVE_AN_X_AND_Y_SCALE: 0x40, + WE_HAVE_A_TWO_BY_TWO: 0x80, + WE_HAVE_INSTRUCTIONS: 0x100, + USE_MY_METRICS: 0x200, + OVERLAP_COMPOUND: 0x400, + SCALED_COMPONENT_OFFSET: 0x800, + UNSCALED_COMPONENT_OFFSET: 0x1000 + }; + + return componentFlag; + } +); diff --git a/src/ttf/enum/glyFlag.js b/src/ttf/enum/glyFlag.js new file mode 100644 index 0000000..240639a --- /dev/null +++ b/src/ttf/enum/glyFlag.js @@ -0,0 +1,35 @@ +/** + * @file glyFlag.js + * @author mengke01 + * @date + * @description + * + * 轮廓标记位 + * + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html + */ + + +define( + function(require) { + + var glyFlag = { + + G_ONCURVE: 0x01, // on curve ,off curve + G_REPEAT: 0x08, //next byte is flag repeat count + G_XMASK: 0x12, + G_XADDBYTE: 0x12, //X is positive byte + G_XSUBBYTE: 0x12, //X is negative byte + G_XSAME: 0x10, //X is same + G_XADDINT: 0x00, //X is signed word + + G_YMASK: 0x24, + G_YADDBYTE: 0x24, //Y is positive byte + G_YSUBBYTE: 0x04, //Y is negative byte + G_YSAME: 0x20, //Y is same + G_YADDINT: 0x00, //Y is signed word + }; + + return glyFlag; + } +); diff --git a/src/ttf/enum/platform.js b/src/ttf/enum/platform.js new file mode 100644 index 0000000..8c5d6cb --- /dev/null +++ b/src/ttf/enum/platform.js @@ -0,0 +1,22 @@ +/** + * @file platform.js + * @author mengke01 + * @date + * @description + * 字体所属平台 + */ + + +define( + function(require) { + + var platform = { + Unicode: 0, + Macintosh: 1, + reserved: 2, + Microsoft: 3 + }; + + return platform; + } +); diff --git a/src/ttf/enum/platformSpec.js b/src/ttf/enum/platformSpec.js new file mode 100644 index 0000000..544de59 --- /dev/null +++ b/src/ttf/enum/platformSpec.js @@ -0,0 +1,25 @@ +/** + * @file encodingIdentifiers.js + * @author mengke01 + * @date + * @description + * Unicode Platform-specific Encoding Identifiers + */ + + +define( + function(require) { + + var spec = { + 'Default': 0, + 'Version1.1': 1, + 'ISO10646': 2, + 'UnicodeBMP': 3, + 'UnicodenonBMP': 4, + 'UnicodeVariationSequences': 5, + 'FullUnicodecoverage': 6 + }; + + return spec; + } +); diff --git a/src/ttf/reader.js b/src/ttf/reader.js new file mode 100644 index 0000000..6c5323c --- /dev/null +++ b/src/ttf/reader.js @@ -0,0 +1,208 @@ +/** + * @file reader.js + * @author mengke01 + * @date + * @description + * ttf读取器 + */ + +define( + function(require) { + + var extend = require('common/lang').extend; + var curry = require('common/lang').curry; + + // 检查数组支持情况 + if(typeof ArrayBuffer === 'undefined' || typeof DataView === 'undefined') { + throw 'not support ArrayBuffer and DataView'; + } + + // 数据类型 + var dataType = { + 'Int8': 1, + 'Int16': 2, + 'Int32': 4, + 'Uint8': 1, + 'Uint16': 2, + 'Uint32': 4, + 'Float32': 4, + 'Float64': 8 + }; + + + var proto = {}; + + /** + * 读取指定的数据类型 + * + * @param {number} size 大小 + * @param {number=} offset 位移 + * @param {boolean=} littleEndian 是否小尾 + * @return {number} 返回值 + */ + function read(type, offset, littleEndian) { + var size = dataType[type]; + // 使用当前位移 + if(undefined == offset) { + offset = this.offset; + } + + // 使用小尾 + if(undefined == littleEndian) { + littleEndian = this.littleEndian; + } + this.offset = offset + size; + + return this.view['get' + type](offset, littleEndian); + } + + // 直接支持的数据类型 + Object.keys(dataType).forEach(function(type) { + proto['read' + type] = curry(read, type); + }); + + + /** + * 读取器 + * + * @constructor + * @param {Array.} buffer 缓冲数组 + * @param {number} offset 起始偏移 + * @param {number} length 数组长度 + * @param {boolean} bigEndian 是否大尾 + */ + function Reader(buffer, offset, length, littleEndian) { + + var bufferLength = buffer.byteLength || buffer.length; + + this.offset = offset || 0; + this.length = length || (bufferLength - this.offset); + this.littleEndian = littleEndian || false; + + this.view = new DataView(buffer, this.offset, this.length); + } + + Reader.prototype = { + read: read, + + /** + * 读取一个string + * + * @param {number} offset 偏移 + * @param {number} length 长度 + * @return {string} 字符串 + */ + readString: function(offset, length) { + + if(arguments.length == 1) { + length = arguments[0]; + offset = this.offset; + } + + if(length < 0 || offset + length > this.length) { + throw 'length out of range:' + offset + ',' + length; + } + + var value = ''; + for (var i = 0; i < length; ++i) { + var c = this.readUint8(offset + i); + value += String.fromCharCode(c > 127 ? 65533 : c); + } + + this.offset = offset + length; + + return value; + }, + + /** + * 读取一个字符 + * + * @param {number} offset 偏移 + * @return {string} 字符串 + */ + readChar: function(offset) { + return this.readString(offset, 1); + }, + + /** + * 读取fixed类型 + * + * @param {number} offset 偏移 + * @return {number} float + */ + readFixed: function(offset) { + if(undefined == offset) { + offset = this.offset; + } + var val = this.readInt32(offset, false) / 65536.0; + return Math.ceil(val * 100000) / 100000; + }, + + /** + * 读取长日期 + * + * @param {number} offset 偏移 + * @return {Date} Date对象 + */ + readLongDateTime: function(offset) { + if(undefined == offset) { + offset = this.offset; + } + var delta = -2080198800000;// (new Date(1904, 1, 1)).getTime(); + var date = new Date(); + date.setTime(this.readUint32(offset + 4, false)); + return date; + }, + + /** + * 跳转到指定偏移 + * + * @param {number} offset 偏移 + * @return {Object} this + */ + seek: function (offset) { + if (undefined == offset) { + this.offset = 0; + } + + if (offset < 0 || offset > this.length) { + throw 'offset out of range:' + offset; + } + + this.offset = offset; + + return this; + }, + + /** + * 获取指定的字节数组 + * + * @return {Array} 字节数组 + */ + readBytes: function(offset, length) { + + if(arguments.length == 1) { + length = arguments[0]; + offset = this.offset; + } + + if(length < 0 || offset + length > this.length) { + throw 'length out of range:' + offset + ',' + length; + } + + var buffer = []; + for (var i = 0; i < length; ++i) { + buffer.push(this.view.getUint8(offset + i)); + } + + this.offset = offset + length; + return buffer; + } + + }; + + extend(Reader.prototype, proto); + + return Reader; + } +); diff --git a/src/ttf/table/cmap.js b/src/ttf/table/cmap.js new file mode 100644 index 0000000..ac380a7 --- /dev/null +++ b/src/ttf/table/cmap.js @@ -0,0 +1,168 @@ +/** + * @file cmap.js + * @author mengke01 + * @date + * @description + * cmap 表 + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + + + /** + * 读取子表 + * Each 'cmap' subtable is in one of nine currently available + * formats. These are format 0, format 2, format 4, + * format 6, format 8.0, format 10.0, format 12.0, + * format 13.0, and format 14 described in the next section. + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html + */ + function readSubTable(reader, ttf, subTable, cmapOffset) { + var startOffset = cmapOffset + subTable.offset; + subTable.format = reader.readUint16(startOffset); + + var i; + + // 双字节编码,非紧凑排列 + if(subTable.format == 4) { + var format4 = subTable; + // 跳过format字段 + format4.length = reader.readUint16(); + format4.language = reader.readUint16(); + format4.segCountX2 = reader.readUint16(); + format4.searchRange = reader.readUint16(); + format4.entrySelector = reader.readUint16(); + format4.rangeShift = reader.readUint16(); + + var segCount = format4.segCountX2 / 2; + + // end code + var endCode = []; + for(i = 0; i < segCount; ++i) { + endCode.push(reader.readUint16()); + } + format4.endCode = endCode; + + format4.reservedPad = reader.readUint16(); + + // start code + var startCode = []; + for(i = 0; i < segCount; ++i) { + startCode.push(reader.readUint16()); + } + format4.startCode = startCode; + + // idDelta + var idDelta = []; + for(i = 0; i < segCount; ++i) { + idDelta.push(reader.readUint16()); + } + format4.idDelta = idDelta; + + // idRangeOffset + var idRangeOffset = []; + for(i = 0; i < segCount; ++i) { + idRangeOffset.push(reader.readUint16()); + } + format4.idRangeOffset = idRangeOffset; + + // 总长度 - glyphIdArray起始偏移/2 + var glyphCount = (format4.length - (reader.offset - startOffset)) / 2; + + // glyphIdArray + var glyphIdArray = []; + for(i = 0; i < glyphCount; ++i) { + glyphIdArray.push(reader.readUint16()); + } + + format4.glyphIdArray = glyphIdArray; + + } + // The firstCode and entryCount values in the subtable specify + // the useful subrange within the range of possible character codes. + // The range begins with firstCode and has a length equal to entryCount. + else if(subTable.format == 6) { + var format6 = subTable; + + format6.length = reader.readUint16(); + format6.language = reader.readUint16(); + format6.firstCode = reader.readUint16(); + format6.entryCount = reader.readUint16(); + + var glyphIndexArray = []; + var entryCount = format6.entryCount; + // 读取字符分组 + for (i = 0; i < entryCount; ++i){ + glyphIndexArray.push(reader.readUint16()); + } + format6.glyphIdArray = glyphIndexArray; + + } + // defines segments for sparse representation in 4-byte character space + else if(subTable.format == 12) { + var format12 = subTable; + + format12.reserved = reader.readUint16(); + format12.length = reader.readUint32(); + format12.language = reader.readUint32(); + format12.nGroups = reader.readUint32(); + + var groups = []; + var nGroups = format12.nGroups; + // 读取字符分组 + for (i = 0; i < nGroups; ++i){ + var group = {}; + group.startCharCode = reader.readUint32(); + group.endCharCode = reader.readUint32(); + group.startGlyphID = reader.readUint32(); + groups.push(groups); + } + + } + } + + + + var cmap = table.create( + 'cmap', + [], + { + read: function(reader, ttf) { + var tcmap = {}; + var cmapOffset = this.offset; + + reader.seek(cmapOffset); + + tcmap.version = reader.readUint16(); // 编码方式 + var numberSubtables = tcmap.numberSubtables = reader.readUint16(); // 表个数 + + + var subTables = tcmap.tables = []; // 名字表 + var offset = reader.offset; + + // 使用offset读取,以便于查找 + for(var i = 0, l = numberSubtables; i < l; i++) { + var subTable = {}; + subTable.platformID = reader.readUint16(offset); + subTable.encodingID = reader.readUint16(offset + 2); + subTable.offset = reader.readUint32(offset + 4); + + readSubTable(reader, ttf, subTable, cmapOffset); + subTables.push(subTable); + + offset += 8; + } + + tcmap.tables = subTables; + + return tcmap; + } + } + ); + + return cmap; + } +); \ No newline at end of file diff --git a/src/ttf/table/gasp.js b/src/ttf/table/gasp.js new file mode 100644 index 0000000..165cb6d --- /dev/null +++ b/src/ttf/table/gasp.js @@ -0,0 +1,47 @@ +/** + * @file gasp.js + * @author mengke01 + * @date + * @description + * gasp表 + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gasp.html + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + + var gasp = table.create( + 'gasp', + [], + { + /** + * 解析gasp表 + */ + read: function(reader, ttf) { + var offset = this.offset; + var gasp = {}; + + reader.seek(offset); + + gasp.version = reader.readUint16(); + gasp.numRanges = reader.readUint16(); + + var GASPRangeTbl = []; + for (var i = 0; i < gasp.numRanges; ++i) { + var GASPRange = {}; + GASPRange.rangeMaxPPEM = reader.readUint16(); + GASPRange.rangeGaspBehavior = reader.readUint16(); + GASPRangeTbl.push(GASPRange); + }; + gasp.GASPRangeTbl = GASPRangeTbl; + + return gasp; + } + } + ); + + return gasp; + } +); \ No newline at end of file diff --git a/src/ttf/table/glyf.js b/src/ttf/table/glyf.js new file mode 100644 index 0000000..991fd11 --- /dev/null +++ b/src/ttf/table/glyf.js @@ -0,0 +1,56 @@ +/** + * @file glyf.js + * @author mengke01 + * @date + * @description + * glyf表 + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var ttfglyf = require('./ttfglyf'); + + var glyf = table.create( + 'glyf', + [], + { + /** + * 解析glyfl表 + */ + read: function(reader, ttf) { + var glyfOffset = this.offset; + var loca = ttf.loca; + var numGlyphs = ttf.maxp.numGlyphs; + var glyf = []; + var glyfDataList = {}; + var glyfPath = new ttfglyf(); + + reader.seek(glyfOffset); + + // 解析字体轮廓 + for ( var i = 0, l = numGlyphs; i < l; i++) { + var offset = glyfOffset + loca[i]; + + //保存offset下的重复图形 + if (undefined == glyfDataList[offset]) { + // 空路径 + if(i + 1 < l && loca[i] === loca[i + 1]) { + glyfDataList[offset] = ttfglyf.empty(); + } + else { + glyfPath.offset = offset; + glyfDataList[offset] = glyfPath.read(reader, ttf); + } + } + glyf[i] = glyfDataList[offset]; + } + return glyf; + } + } + ); + + return glyf; + } +); \ No newline at end of file diff --git a/src/ttf/table/head.js b/src/ttf/table/head.js new file mode 100644 index 0000000..479a172 --- /dev/null +++ b/src/ttf/table/head.js @@ -0,0 +1,39 @@ +/** + * @file head.js + * @author mengke01 + * @date + * @description + * head表 + */ + + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var head = table.create( + 'head', + [ + ['version', struct.Fixed], + ['fontRevision', struct.Fixed], + ['checkSumAdjustment', struct.Uint32], + ['magickNumber', struct.Uint32], + ['flags', struct.Uint16], + ['unitsPerEm', struct.Uint16], + ['created', struct.LongDateTime], + ['modified', struct.LongDateTime], + ['xMin', struct.Int16], + ['yMin', struct.Int16], + ['xMax', struct.Int16], + ['yMax', struct.Int16], + ['macStyle', struct.Uint16], + ['lowestRecPPEM', struct.Uint16], + ['fontDirectionHint', struct.Int16], + ['indexToLocFormat', struct.Int16], + ['glyphDataFormat', struct.Int16] + ] + ); + + return head; + } +); \ No newline at end of file diff --git a/src/ttf/table/hhea.js b/src/ttf/table/hhea.js new file mode 100644 index 0000000..c1eb415 --- /dev/null +++ b/src/ttf/table/hhea.js @@ -0,0 +1,49 @@ +/** + * @file hhea.js + * @author mengke01 + * @date + * @description + * hhea 表 + * + * The 'hhea' table contains information needed to + * layout fonts whose characters are written + * horizontally, that is, either left + * to right or right to left. + * This table contains information that is general + * to the font as a whole. Information which + * pertains to specific glyphs is given in the + * 'hmtx' table defined below. + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hhea.html + */ + + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var hhea = table.create( + 'hhea', + [ + ['version', struct.Fixed], + ['ascent', struct.Int16], + ['descent', struct.Int16], + ['lineGap', struct.Int16], + ['advanceWidthMax', struct.Uint16], + ['minLeftSideBearing', struct.Int16], + ['minRightSideBearing', struct.Int16], + ['xMaxExtent', struct.Int16], + ['caretSlopeRise', struct.Int16], + ['caretSlopeRun', struct.Int16], + ['caretOffset', struct.Int16], + ['reserved0', struct.Int16], + ['reserved1', struct.Int16], + ['reserved2', struct.Int16], + ['reserved3', struct.Int16], + ['metricDataFormat', struct.Int16], + ['numOfLongHorMetrics', struct.Uint16] + ] + ); + + return hhea; + } +); diff --git a/src/ttf/table/hmtx.js b/src/ttf/table/hmtx.js new file mode 100644 index 0000000..d26ae66 --- /dev/null +++ b/src/ttf/table/hmtx.js @@ -0,0 +1,58 @@ +/** + * @file hmtx.js + * @author mengke01 + * @date + * @description + * hmtx 表 + * + * The 'hmtx' table contains metric information + * for the horizontal layout each of the glyphs in the font + * + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hmtx.html + */ + + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var hmtx = table.create( + 'hmtx', + [ + ], + { + read: function(reader, ttf) { + var offset = this.offset; + reader.seek(offset); + + var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics; + var hMetrics = []; + + for (var i = 0; i < numOfLongHorMetrics; ++i) { + var hMetric = {}; + hMetric.advanceWidth = reader.readUint16(); + hMetric.leftSideBearing = reader.readInt16(); + hMetrics.push(hMetric); + } + + // 最后一个宽度 + var advanceWidth = hMetrics[numOfLongHorMetrics - 1].advanceWidth; + var numOfLast = ttf.maxp.numGlyphs - numOfLongHorMetrics; + + // 获取后续的hmetrics + for (var i = 0; i < numOfLast; ++i) { + var hMetric = {}; + hMetric.advanceWidth = advanceWidth; + hMetric.leftSideBearing = reader.readInt16(); + hMetrics.push(hMetric); + } + + return hMetrics; + + } + } + ); + + return hmtx; + } +); diff --git a/src/ttf/table/loca.js b/src/ttf/table/loca.js new file mode 100644 index 0000000..4e75eaa --- /dev/null +++ b/src/ttf/table/loca.js @@ -0,0 +1,44 @@ +/** + * @file loca.js + * @author mengke01 + * @date + * @description + * loca表 + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var loca = table.create( + 'loca', + [], + { + /** + * 解析local表 + */ + read: function(reader, ttf) { + var offset = this.offset; + var indexToLocFormat = ttf.head.indexToLocFormat; + // indexToLocFormat有2字节和4字节的区别 + var type = struct.names[(indexToLocFormat === 0) ? struct.Uint16 : struct.Uint32]; + var size = (indexToLocFormat === 0) ? 2 : 4; //字节大小 + var sizeRatio = (indexToLocFormat === 0) ? 2 : 1; //真实地址偏移 + var wordOffset = []; + + reader.seek(offset); + + var numGlyphs = ttf.maxp.numGlyphs; + for (var i = 0; i < numGlyphs; ++i) { + wordOffset.push(reader.read(type, offset, false) * sizeRatio); + offset += size; + } + + return wordOffset; + } + } + ); + + return loca; + } +); \ No newline at end of file diff --git a/src/ttf/table/maxp.js b/src/ttf/table/maxp.js new file mode 100644 index 0000000..6e92fd8 --- /dev/null +++ b/src/ttf/table/maxp.js @@ -0,0 +1,36 @@ +/** + * @file maxp.js + * @author mengke01 + * @date + * @description + * maxp 表 + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var maxp = table.create( + 'maxp', + [ + ['version', struct.Fixed], + ['numGlyphs', struct.Uint16], + ['maxPoints', struct.Uint16], + ['maxCompositePoints', struct.Uint16], + ['maxCompositeContours', struct.Uint16], + ['maxZones', struct.Uint16], + ['maxTwilightPoints', struct.Uint16], + ['maxStorage', struct.Uint16], + ['maxFunctionDefs', struct.Uint16], + ['maxInstructionDefs', struct.Uint16], + ['maxStackElements', struct.Uint16], + ['maxSizeOfInstructions', struct.Uint16], + ['macStyle', struct.Uint16], + ['maxComponentElements', struct.Uint16], + ['maxComponentDepth', struct.Int16] + ] + ); + + return maxp; + } +); \ No newline at end of file diff --git a/src/ttf/table/name.js b/src/ttf/table/name.js new file mode 100644 index 0000000..6ff3448 --- /dev/null +++ b/src/ttf/table/name.js @@ -0,0 +1,56 @@ +/** + * @file name.js + * @author mengke01 + * @date + * @description + * name表 + */ +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var name = table.create( + 'name', + [], { + + read: function(reader, ttf) { + var offset = this.offset; + reader.seek(offset); + + var nameTbl = {}; + nameTbl.format = reader.readUint16(); + nameTbl.count = reader.readUint16(); + nameTbl.stringOffset = reader.readUint16(); + + var nameRecordTbl = []; + var count = nameTbl.count; + for (var i = 0; i < count; ++i) { + var nameRecord = {}; + nameRecord.platformID = reader.readUint16(); + nameRecord.platformSpecificID = reader.readUint16(); + nameRecord.languageID = reader.readUint16(); + nameRecord.nameID = reader.readUint16(); + nameRecord.length = reader.readUint16(); + nameRecord.offset = reader.readUint16(); + nameRecordTbl.push(nameRecord); + }; + + + //nameTbl.name = reader.readString(reader.offset, nameTbl.stringOffset); + + offset = offset + nameTbl.stringOffset; + // 读取字符名字 + for (var i = 0; i < count; ++i) { + var nameRecord = nameRecordTbl[i]; + nameRecord.name = reader.readString(offset + nameRecord.offset, nameRecord.length); + } + + nameTbl.nameRecord = nameRecordTbl; + return nameTbl; + } + } + ); + + return name; + } +); \ No newline at end of file diff --git a/src/ttf/table/post.js b/src/ttf/table/post.js new file mode 100644 index 0000000..5392ac2 --- /dev/null +++ b/src/ttf/table/post.js @@ -0,0 +1,87 @@ +/** + * @file post.js + * @author mengke01 + * @date + * @description + * + * post 表 + * + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html + */ + +define( + function(require) { + var table = require('./table'); + var struct = require('./struct'); + var posthead = table.create( + 'posthead', + [ + ['format', struct.Fixed], + ['italicAngle', struct.Fixed], + ['postoints', struct.Uint16], + ['underlinePosition', struct.Int16], + ['underlineThickness', struct.Int16], + ['isFixedPitch', struct.Uint32], + ['minMemType42', struct.Uint32], + ['maxMemType42', struct.Uint32], + ['minMemType1', struct.Uint32], + ['maxMemType1', struct.Uint32] + ] + ); + + /** + * 读取poststring + * + * @param {Array.} byteArray byte数组 + * @return {Array.} 读取后的字符串数组 + */ + function readPascalString(byteArray) { + var strArray = []; + var i = 0; + var l = byteArray.length; + while(i < l) { + var strLength = byteArray[i]; + var str = ''; + while(strLength-- >= 0 && i < l) { + str += String.fromCharCode(byteArray[++i]); + } + strArray.push(str); + } + return strArray; + } + + var post = table.create( + 'post', + [ + ], + { + read: function(reader, ttf) { + // 读取表头 + var tbl = new posthead(this.offset).read(reader, ttf); + var offset = reader.offset; + + // format2 + if(tbl.format == 2) { + var numberOfGlyphs = ttf.maxp.numGlyphs; + + var glyphNameIndex = []; + for(var i = 0; i < numberOfGlyphs; ++i) { + glyphNameIndex.push(reader.readUint16()); + } + + tbl.glyphNameIndex = glyphNameIndex; + + var pascalStringOffset = reader.offset; + var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset); + var pascalStringBytes = reader.readBytes(reader.offset, pascalStringLength); + tbl.names = readPascalString(pascalStringBytes); + } + + return tbl; + } + } + ); + + return post; + } +); \ No newline at end of file diff --git a/src/ttf/table/struct.js b/src/ttf/table/struct.js new file mode 100644 index 0000000..4265fe7 --- /dev/null +++ b/src/ttf/table/struct.js @@ -0,0 +1,43 @@ +/** + * @file struct.js + * @author mengke01 + * @date + * @description + * 基本数据结构 + */ + + +define( + function(require) { + + var struct = { + Int8: 1, + Uint8: 2, + Int16: 3, + Uint16: 4, + Int32: 5, + Uint32: 6, + Fixed: 7, // 32-bit signed fixed-point number (16.16) + FUnit: 8, // Smallest measurable distance in the em space + // 16-bit signed integer (SHORT) that describes a quantity in FUnits. + //FWord: 9, + // Unsigned 16-bit integer (USHORT) that describes a quantity in FUnits + //UFWord: 10, + // 16-bit signed fixed number with the low 14 bits of fraction + F2Dot14: 11, + // The long internal format of a date in seconds since 12:00 midnight, + // January 1, 1904. It is represented as a signed 64-bit integer. + LongDateTime: 12 + }; + + var names = {}; + + for(var key in struct) { + names[struct[key]] = key; + } + + struct.names = names; + + return struct; + } +); diff --git a/src/ttf/table/support.js b/src/ttf/table/support.js new file mode 100644 index 0000000..ba6e8c0 --- /dev/null +++ b/src/ttf/table/support.js @@ -0,0 +1,28 @@ +/** + * @file support.js + * @author mengke01 + * @date + * @description + * 列举支持的表 + */ + + +define( + function(require) { + + var support = { + 'head': require('./head'), + 'maxp': require('./maxp'), + 'loca': require('./loca'), + 'glyf': require('./glyf'), + 'cmap': require('./cmap'), + 'name': require('./name'), + 'gasp': require('./gasp'), + 'hhea': require('./hhea'), + 'hmtx': require('./hhea'), + 'post': require('./post') + }; + + return support; + } +); diff --git a/src/ttf/table/table.js b/src/ttf/table/table.js new file mode 100644 index 0000000..f4a2d0d --- /dev/null +++ b/src/ttf/table/table.js @@ -0,0 +1,104 @@ +/** + * @file table.js + * @author mengke01 + * @date + * @description + * ttf表操作类 + */ + + +define( + function(require) { + var struct = require('./struct'); + var extend = require('common/lang').extend; + + + /** + * 读取一个表结构 + * + * @param {Object} reader reader对象 + * @param {Object} reader 已解析的ttf对象 + * @return {Object} 当前对象 + */ + function read(reader, ttf) { + var offset = this.offset; + + if(undefined !== offset) { + reader.seek(offset); + } + + var me = this; + + this.struct.forEach(function(item){ + var name = item[0]; + var type = item[1]; + + switch (type) { + case struct.Int8: + case struct.Uint8: + case struct.Int16: + case struct.Uint16: + case struct.Int32: + case struct.Uint32: + var typeName = struct.names[type]; + me[name] = reader.read(typeName); + break; + case struct.Fixed: + me[name] = reader.readFixed(); + break; + case struct.LongDateTime: + me[name] = reader.readLongDateTime(); + break; + default: + throw 'unknown type:' + name + ':' + type; + } + }); + + return this.valueOf(); + } + + + /** + * 获取对象的值 + * + * @return {*} 当前对象的值 + */ + function valueOf() { + var val = {}; + var me = this; + this.struct.forEach(function(item){ + val[item[0]] = me[item[0]]; + }); + + return val; + } + + var exports = { + + /** + * 创建一个表结构 + * + * @param {string} name 表名 + * @param {Object} struct 表结构 + * @param {Object} prototype 原型 + * @return {Function} 表构造函数 + */ + create: function(name, struct, prototype) { + function Table(offset) { + this._name = name; + this.struct = struct; + this.offset = offset; + } + + Table.prototype.read = read; + Table.prototype.valueOf = valueOf; + + extend(Table.prototype, prototype); + + return Table; + } + }; + + return exports; + } +); diff --git a/src/ttf/table/ttfglyf.js b/src/ttf/table/ttfglyf.js new file mode 100644 index 0000000..730f162 --- /dev/null +++ b/src/ttf/table/ttfglyf.js @@ -0,0 +1,195 @@ +/** + * @file ttfglyf.js + * @author mengke01 + * @date + * @description + * + * ttf的glyf轮廓,用来解析单个路径 + * + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html + */ + + +define( + function(require) { + + var table = require('./table'); + + function readSimpleGlyf(reader, ttf, offset, val) { + + reader.seek(offset); + + // 轮廓个数 + var contours = val.endPtsOfContours[ + val.endPtsOfContours.length - 1 + ] + 1; + + val.contours = contours; + + // 获取flag标志 + var i = 0; + while (i < contours) { + var flag = reader.readUint8(); + val.flags.push(flag); + i++; + + // 标志位3表示重复flag + // If set, the next byte specifies the number of additional + // times this set of flags is to be repeated. In this way, + // the number of flags listed can be smaller than the + // number of points in a character + // + if (flag & 8 && i < contours) { + // 重复个数 + var repeat = reader.readUint8(); + for ( var j = 0; j < repeat; j++) { + val.flags.push(flag); + i++; + } + } + } + + // xCoordinates + val.xCoorinateOffset = reader.offset; + var prevX = 0; + for (var i = 0, l = val.flags.length; i < l; ++i) { + var x = 0; + var flag = val.flags[i]; + + //标志位1 + // If set, the corresponding y-coordinate is 1 byte long, not 2 + if (flag & 2) { + x = reader.readUint8(); + offset += 1; + + //标志位5, 是否负值 + x = (flag & 16) ? x : -1 * x; + } + + //标志位5,空值 + // This flag has two meanings, depending on how the x-Short Vector flag is set. If x-Short Vector is set, this + // bit describes the sign of the value, with 1 equalling + // positive and 0 negative. If the x-Short Vector bit is + // not set and this bit is set, then the current x-coordinate is the same as the previous x-coordinate. + // If the x-Short Vector bit is not set and this bit is also + // not set, the current x-coordinate is a signed 16-bit + // delta vector + else if (flag & 16) { + x = 0; + } + + else { + x = reader.readInt16(); + } + + prevX += x; + val.xCoordinates[i] = prevX; + val.coordinates[i] = { + x : prevX, + y : 0, + isOnCurve : Boolean(flag & 1) + }; + } + + // yCoordinates + val.yCoorinateOffset = reader.offset; + var prevY = 0; + for ( var i = 0, l = val.flags.length; i < l; i++) { + var y = 0; + var flag = val.flags[i]; + + if (flag & 4) { + y = reader.readUint8(); + y = (flag & 32) ? y : -1 * y; + } + + else if (flag & 32) { + y = 0; + } + + else { + y = reader.readInt16(); + } + + prevY += y; + val.yCoordinates[i] = prevY; + if (val.coordinates[i]) { + val.coordinates[i].y = prevY; + } + } + } + + var ttfglyf = table.create( + 'ttfglyf', + [], + { + /** + * 解析ttfglyfl表 + */ + read: function(reader, ttf) { + var offset = this.offset; + var val = ttfglyf.empty(); + + reader.seek(offset); + + // 边界值 + val.numberOfContours = reader.readInt16(); + val.xMin = reader.readInt16(); + val.yMin = reader.readInt16(); + val.xMax = reader.readInt16(); + val.yMax = reader.readInt16(); + + // endPtsOfConturs + var endPtsOfContours = []; + if (val.numberOfContours >= 0) { + for ( var i = 0; i < val.numberOfContours; i++) { + endPtsOfContours.push(reader.readUint16()); + } + val.endPtsOfContours = endPtsOfContours; + } + + // instructions + var length = reader.readUint16(); + var instructions = []; + for ( var i = 0; i < length; ++i) { + instructions.push(reader.readUint8()); + } + + val.instructions = instructions; + + // 读取简单字形 + if (val.numberOfContours >= 0) { + readSimpleGlyf.call( + this, + reader, + ttf, + reader.offset, + val + ); + } + else { + // 读取复杂字形 + throw 'not support Compound glyf'; + } + + return val; + } + } + ); + + // 空路径 + ttfglyf.empty = function() { + var val = {}; + val.flags = []; + val.contours = 0; + val.coordinates = []; + val.xCoorinateOffset = 0; // x偏移 + val.xCoordinates = []; //x 坐标集合 + val.yCoorinateOffset = 0; // y偏移 + val.yCoordinates = []; // y坐标集合 + return val; + }; + + return ttfglyf; + } +); diff --git a/src/ttf/ttf.js b/src/ttf/ttf.js new file mode 100644 index 0000000..890236c --- /dev/null +++ b/src/ttf/ttf.js @@ -0,0 +1,35 @@ +/** + * @file ttf.js + * @author mengke01 + * @date + * @description + * + * ttf 信息读取函数 + */ + + +define( + function(require) { + + /** + * ttf读取函数 + * + * @constructor + * @param {Object} ttf ttf文件结构 + */ + function TTF(ttf) { + this.ttf = ttf; + } + + /** + * 获取所有的字符信息 + * + * @return {Object} 字符信息 + */ + TTF.prototype.chars = function() { + + }; + + return ttf; + } +); diff --git a/src/ttf/ttfreader.js b/src/ttf/ttfreader.js new file mode 100644 index 0000000..b412d3e --- /dev/null +++ b/src/ttf/ttfreader.js @@ -0,0 +1,104 @@ +/** + * @file ttfreader.js + * @author mengke01 + * @date + * @description + * ttf定义 + * + * thanks to: + * ynakajima/ttf.js + * https://github.com/ynakajima/ttf.js + */ + + +define( + function(require) { + + var supportTables = require('./table/support'); + var reader = require('./reader'); + + + /** + * 读取ttf表偏移量 + * + */ + function readTableOffset() { + var tables = {}; + var numTables = this.ttf.numTables; + var offset = this.reader.offset; + var reader = this.reader; + for (var i = offset, l = numTables * 16; i < l; i += 16) { + + var name = reader.readString(i, 4); + var checkSum = reader.readUint32(i + 4); + var tblOffset = reader.readUint32(i + 8); + var length = reader.readUint32(i + 12); + + tables[name] = { + name : name, + checkSum : checkSum, + offset : tblOffset, + length : length, + }; + } + + return tables; + } + + + /** + * 初始化 + */ + function init() { + var reader = this.reader; + var ttf = this.ttf; + + // version + ttf.version = reader.readFixed(0); + + // num tables + ttf.numTables = reader.readUint16(); + + // searchRenge + ttf.searchRenge = reader.readUint16(); + + // entrySelector + ttf.entrySelector = reader.readUint16(); + + // rengeShift + ttf.rengeShift = reader.readUint16(); + + ttf.tables = readTableOffset.call(this); + + // 读取支持的表数据 + Object.keys(supportTables).forEach(function(tableName) { + console.log(tableName); + var offset = ttf.tables[tableName].offset; + ttf[tableName] = new supportTables[tableName](offset).read(reader, ttf); + }); + } + + /** + * TTF的构造函数 + * + * @constructor + */ + function TTFReader() { + } + + /** + * 获取解析后的ttf文档 + * + * @return {Object} ttf文档 + */ + TTFReader.prototype.read = function(buffer) { + this.reader = new reader(buffer, 0, buffer.byteLength, false); + this.ttf = {}; + init.call(this); + console.log(this.ttf); + return this.ttf; + }; + + return TTFReader; + } +);