diff --git a/README.md b/README.md index d172e9c..bda20a9 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,10 @@ const font = Font.create(buffer, { type: 'ttf', // only read `a`, `b` glyphs subset: [65, 66], - // save font hinting + // read font hinting tables, default false hinting: true, + // read font kerning tables, default false + kerning: true, // transform ttf compound glyph to simple compound2simple: true, // inflate function for woff @@ -71,8 +73,10 @@ console.log(Object.keys(fontObject)); const buffer = font.write({ // support ttf, woff, woff2, eot, svg type: 'woff', - // save font hinting, default false - hinting: true, + // save font hinting tables, default false + hinting: false, + // save font kerning tables, default false + kerning: false, // write glyf data when simple glyph has no contours, default false writeZeroContoursGlyfData: false, // deflate function for woff, eg. pako.deflate diff --git a/demo/js/ttfmin.js b/demo/js/ttfmin.js index fc8b635..019c2e4 100644 --- a/demo/js/ttfmin.js +++ b/demo/js/ttfmin.js @@ -17,12 +17,12 @@ function onUpFileChange(e) { let file = e.target.files[0]; let reader = new FileReader(); reader.onload = function (e) { - let ttfReader = new TTFReader({ - hinting: true + hinting: true, + kerning: true, }); curttfData = ttfReader.read(e.target.result); - + console.log(curttfData); ttfmin(); }; @@ -69,7 +69,8 @@ function ttfmin() { let family = 'font-with-hitting'; ttf.get().name.fontFamily = family; let writer = new TTFWriter({ - hinting: true + hinting: true, + kerning: true, }); let buffer = writer.write(ttf.get()); setFont({ diff --git a/demo/ttfmin.html b/demo/ttfmin.html index bd469f6..86602dc 100644 --- a/demo/ttfmin.html +++ b/demo/ttfmin.html @@ -17,9 +17,13 @@ div { padding: 10px; } - + .text-title { + display: inline-block; + width: 140px; + } .ttf-text { font-size: 14px; + letter-spacing: 1px; } .ttf-min-with-hitting { @@ -39,16 +43,24 @@
相关文字: - + 字号:
- 带hinting的字体: + 带hinting的字体: +
+ +
+ 不带hinting的字体: +
+ +
+ 带kerning的字体:
- 不带hinting的字体: + 不带kerning的字体:
diff --git a/index.d.ts b/index.d.ts index 9fd5519..bb9046a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -194,10 +194,16 @@ export namespace FontEditor { subset?: TTF.CodePoint[]; /** - * keep hinting or not, default false + * keep hinting table or not, default false */ hinting?: boolean; + /** + * keep kerning table or not, default false + * kerning table adjusting the space between individual letters or characters + */ + kerning?: boolean; + /** * tranfrom compound glyph to simple, * @default true diff --git a/package.json b/package.json index aed5dac..e5062ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fonteditor-core", - "version": "2.3.3", + "version": "2.4.0", "description": "fonts (ttf, woff, woff2, eot, svg, otf) parse, write, transform, glyph adjust.", "keywords": [ "sfnt", diff --git a/src/ttf/font.js b/src/ttf/font.js index 69c5b0e..7905cb5 100644 --- a/src/ttf/font.js +++ b/src/ttf/font.js @@ -79,7 +79,8 @@ export default class Font { * @param {string} options.type 字体类型 * * ttf, woff , eot 读取配置 - * @param {boolean} options.hinting 保留hinting信息 + * @param {boolean} options.hinting 是否保留 hinting 信息 + * @param {boolean} options.kerning 是否保留 kerning 信息 * @param {boolean} options.compound2simple 复合字形转简单字形 * * woff 读取配置 @@ -134,8 +135,8 @@ export default class Font { * @param {boolean} options.toBuffer nodejs 环境中返回 Buffer 对象, 默认 true * * ttf 字体参数 - * @param {boolean} options.hinting 保留hinting信息 - * + * @param {boolean} options.hinting 是否保留 hinting 信息 + * @param {boolean} options.kerning 是否保留 kerning 信息 * svg,woff 字体参数 * @param {Object} options.metadata 字体相关的信息 * diff --git a/src/ttf/table/GPOS.js b/src/ttf/table/GPOS.js index 9f7ad4c..9e19e9d 100644 --- a/src/ttf/table/GPOS.js +++ b/src/ttf/table/GPOS.js @@ -2,7 +2,7 @@ * @file GPOS * @author fr33z00(https://github.com/fr33z00) * - * @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cvt.html + * @reference: https://learn.microsoft.com/en-us/typography/opentype/spec/gpos */ import table from './table'; diff --git a/src/ttf/table/kern.js b/src/ttf/table/kern.js index 67bc49a..324a972 100644 --- a/src/ttf/table/kern.js +++ b/src/ttf/table/kern.js @@ -2,7 +2,7 @@ * @file kern * @author fr33z00(https://github.com/fr33z00) * - * @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cvt.html + * @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html */ import table from './table'; diff --git a/src/ttf/table/kerx.js b/src/ttf/table/kerx.js new file mode 100644 index 0000000..6397787 --- /dev/null +++ b/src/ttf/table/kerx.js @@ -0,0 +1,30 @@ +/** + * @file kerx + * @author mengke01(kekee000@gmail.com) + * + * @reference: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html + */ + +import table from './table'; + +export default table.create( + 'kerx', + [], + { + + read(reader, ttf) { + const length = ttf.tables.kerx.length; + return reader.readBytes(this.offset, length); + }, + + write(writer, ttf) { + if (ttf.kerx) { + writer.writeBytes(ttf.kerx, ttf.kerx.length); + } + }, + + size(ttf) { + return ttf.kerx ? ttf.kerx.length : 0; + } + } +); diff --git a/src/ttf/table/support.js b/src/ttf/table/support.js index ff5b2e6..41f3a12 100644 --- a/src/ttf/table/support.js +++ b/src/ttf/table/support.js @@ -19,7 +19,7 @@ import prep from './prep'; import gasp from './gasp'; import GPOS from './GPOS'; import kern from './kern'; - +import kerx from './kerx'; export default { head, @@ -37,5 +37,6 @@ export default { prep, gasp, GPOS, - kern + kern, + kerx }; diff --git a/src/ttf/table/table.js b/src/ttf/table/table.js index cc284f9..26b8a77 100644 --- a/src/ttf/table/table.js +++ b/src/ttf/table/table.js @@ -200,11 +200,11 @@ export default { * 创建一个表结构 * * @param {string} name 表名 - * @param {Object} struct 表结构 - * @param {Object} prototype 原型 + * @param {Array<[string, number]>} struct 表结构 + * @param {Object} proto 原型 * @return {Function} 表构造函数 */ - create(name, struct, prototype) { + create(name, struct, proto) { class Table { constructor(offset) { this.name = name; @@ -217,7 +217,7 @@ export default { Table.prototype.write = write; Table.prototype.size = size; Table.prototype.valueOf = valueOf; - Object.assign(Table.prototype, prototype); + Object.assign(Table.prototype, proto); return Table; } }; diff --git a/src/ttf/ttfreader.js b/src/ttf/ttfreader.js index 8015206..4b8ced5 100644 --- a/src/ttf/ttfreader.js +++ b/src/ttf/ttfreader.js @@ -26,7 +26,8 @@ export default class TTFReader { */ constructor(options = {}) { options.subset = options.subset || []; // 子集 - options.hinting = options.hinting || false; // 不保留hints信息 + options.hinting = options.hinting || false; // 默认不保留 hints 信息 + options.kerning = options.kerning || false; // 默认不保留 kerning 信息 options.compound2simple = options.compound2simple || false; // 复合字形转简单字形 this.options = options; } @@ -180,13 +181,17 @@ export default class TTFReader { delete ttf.fpgm; delete ttf.cvt; delete ttf.prep; - delete ttf.GPOS; - delete ttf.kern; ttf.glyf.forEach((glyf) => { delete glyf.instructions; }); } + if (!this.options.hinting && !this.options.kerning) { + delete ttf.GPOS; + delete ttf.kern; + delete ttf.kerx; + } + // 复合字形转简单字形 if (this.options.compound2simple && ttf.maxp.maxComponentElements) { ttf.glyf.forEach((glyf, index) => { diff --git a/src/ttf/ttfwriter.js b/src/ttf/ttfwriter.js index 22084f8..f2151e2 100644 --- a/src/ttf/ttfwriter.js +++ b/src/ttf/ttfwriter.js @@ -27,7 +27,8 @@ export default class TTFWriter { constructor(options = {}) { this.options = { writeZeroContoursGlyfData: options.writeZeroContoursGlyfData || false, // 不写入空 glyf 数据 - hinting: options.hinting || false, // 不保留hints信息 + hinting: options.hinting || false, // 默认不保留hints信息 + kerning: options.kerning || false, // 默认不保留 kernings space 信息 support: options.support // 自定义的导出表结构,可以自己修改某些表项目 }; } @@ -200,7 +201,15 @@ export default class TTFWriter { ttf.writeOptions = {}; // hinting tables direct copy if (this.options.hinting) { - ['cvt', 'fpgm', 'prep', 'gasp', 'GPOS', 'kern'].forEach((table) => { + ['cvt', 'fpgm', 'prep', 'gasp', 'GPOS', 'kern', 'kerx'].forEach((table) => { + if (ttf[table]) { + tables.push(table); + } + }); + } + // copy kerning space table + if (this.options.kerning) { + ['GPOS', 'kern', 'kerx'].forEach((table) => { if (ttf[table]) { tables.push(table); } @@ -208,6 +217,7 @@ export default class TTFWriter { } ttf.writeOptions.writeZeroContoursGlyfData = !!this.options.writeZeroContoursGlyfData; ttf.writeOptions.hinting = !!this.options.hinting; + ttf.writeOptions.kerning = !!this.options.kerning; ttf.writeOptions.tables = tables.sort(); } diff --git a/test/data/FiraSansMedium.ttf b/test/data/FiraSansMedium.ttf new file mode 100644 index 0000000..8e9d018 Binary files /dev/null and b/test/data/FiraSansMedium.ttf differ diff --git a/test/example/example.ts b/test/example/example.ts index 91e2f0d..d9a30c6 100644 --- a/test/example/example.ts +++ b/test/example/example.ts @@ -11,8 +11,10 @@ const font = Font.create(buffer, { type: 'ttf', // only read 0x21, 0x22 glyphs subset: [0x21, 0x22], - // save font hinting + // read font hinting hinting: true, + // read font kerning + kerning: true, // transform ttf compound glyph to simple compound2simple: true, // inflate function for woff @@ -47,6 +49,8 @@ fs.writeFileSync(`${baseDir}/output/font.eot`, utils.toBuffer(utils.ttf2eot(util type: 'woff', // save font hinting hinting: true, + // save font kerning + kerning: true, // deflate function for woff, eg. pako.deflate deflate: undefined, // for user to overwrite head.xMin, head.xMax, head.yMin, head.yMax, hhea etc. @@ -61,8 +65,6 @@ fs.writeFileSync(`${baseDir}/output/font.eot`, utils.toBuffer(utils.ttf2eot(util const svg = font.write({ // support ttf, woff, woff2, eot, svg type: 'svg', - // save font hinting - hinting: true, // deflate function for woff, eg. pako.deflate deflate: undefined, // for user to overwrite head.xMin, head.xMax, head.yMin, head.yMax, hhea etc. diff --git a/test/spec/ttf/otfreader.spec.js b/test/spec/ttf/otfreader.spec.js index 1a2377e..8eeaf6c 100644 --- a/test/spec/ttf/otfreader.spec.js +++ b/test/spec/ttf/otfreader.spec.js @@ -77,10 +77,10 @@ describe('read otf buffer', function () { }); -describe('read otf hinting GPOS kern', function () { +describe('read otf kerning GPOS kern', function () { let fontObject = new OTFReader().read(readData('SFNSDisplayCondensed-Black.otf')); - it('test read hinting', function () { + it('test read kerning', function () { assert.equal(fontObject.GPOS.length, 57290); }); diff --git a/test/spec/ttf/ttfreader.spec.js b/test/spec/ttf/ttfreader.spec.js index 528e4c3..dc6e099 100644 --- a/test/spec/ttf/ttfreader.spec.js +++ b/test/spec/ttf/ttfreader.spec.js @@ -86,22 +86,17 @@ describe('read ttf hinting', function () { assert.equal(fontObject.prep.length, 204); assert.equal(fontObject.gasp.length, 8); assert.equal(fontObject.GPOS.length, 18); - fontObject.kern && assert.equal(fontObject.kern.length, 8); }); }); -describe('read ttf hinting GPOS kern', function () { +describe('read ttf kerning GPOS kern kerx', function () { let fontObject = new TTFReader({ - hinting: true - }).read(readData('baiduHealth-hinting.ttf')); + kerning: true + }).read(readData('FiraSansMedium.ttf')); - it('test read hinting', function () { - assert.equal(fontObject.cvt.length, 24); - assert.equal(fontObject.fpgm.length, 371); - assert.equal(fontObject.prep.length, 204); - assert.equal(fontObject.gasp.length, 8); - assert.equal(fontObject.GPOS.length, 18); + it('test read kerning', function () { + assert.equal(fontObject.GPOS.length, 5960); }); }); diff --git a/test/spec/ttf/ttfwriter.spec.js b/test/spec/ttf/ttfwriter.spec.js index e8443e4..ce8dd12 100644 --- a/test/spec/ttf/ttfwriter.spec.js +++ b/test/spec/ttf/ttfwriter.spec.js @@ -140,16 +140,53 @@ describe('write ttf hinting', function () { }).read(readData('baiduHealth-hinting.ttf')); it('test write ttf hinting', function () { + assert.equal(fontObject.cvt.length, 24); + assert.equal(fontObject.fpgm.length, 371); + assert.equal(fontObject.prep.length, 204); + assert.equal(fontObject.gasp.length, 8); + assert.equal(fontObject.GPOS.length, 18); + let buffer = new TTFWriter({ hinting: true }).write(fontObject); assert.ok(buffer.byteLength > 1000); assert.ok(buffer.byteLength < 10000); - assert.equal(fontObject.cvt.length, 24); - assert.equal(fontObject.fpgm.length, 371); - assert.equal(fontObject.prep.length, 204); - assert.equal(fontObject.gasp.length, 8); - assert.equal(fontObject.GPOS.length, 18); + let fontObject1 = new TTFReader({ + hinting: true + }).read(buffer); + assert.equal(fontObject1.cvt.length, 24); + assert.equal(fontObject1.fpgm.length, 371); + assert.equal(fontObject1.prep.length, 204); + assert.equal(fontObject1.gasp.length, 8); + assert.equal(fontObject1.GPOS.length, 18); + }); +}); + +describe('write ttf kerning', function () { + + let fontObject = new TTFReader({ + kerning: true + }).read(readData('FiraSansMedium.ttf')); + + it('test write ttf no kerning', function () { + let buffer = new TTFWriter({ + kerning: false + }).write(fontObject); + assert.ok(buffer.byteLength > 100000); + assert.ok(buffer.byteLength < 110000); + }); + + it('test write ttf kerning', function () { + assert.equal(fontObject.GPOS.length, 5960); + let buffer = new TTFWriter({ + kerning: true + }).write(fontObject); + assert.ok(buffer.byteLength > 110000); + assert.ok(buffer.byteLength < 120000); + let fontObject1 = new TTFReader({ + kerning: true + }).read(buffer); + assert.equal(fontObject1.GPOS.length, 5960, 'GPOS size'); }); });