From 2e4ccd2c2ddb38a6c686ba6746d914cb0f0b391e Mon Sep 17 00:00:00 2001 From: canda Date: Wed, 20 May 2020 21:04:37 -0300 Subject: [PATCH] Fix Noto font bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A little bit of context. While working with pdfkit we found this error. https://github.com/foliojs/pdfkit/issues/1106 We narrowed down the repro to fontkit with this little script ``` const fs = require('fs'); const notosans = require('fontkit').openSync('./NotoSansCJKsc-Bold.otf'); const subsetFor = (text, file) => { const run = notosans.layout(text); const subset = notosans.createSubset(); run.glyphs.forEach((glyph) => subset.includeGlyph(glyph)); subset.encodeStream().pipe(fs.createWriteStream(file)); }; subsetFor('间。楼', 'output.ttf'); ``` With this script you can see that glyphs were missing on the subset font if you opened the font with something like fontforge Regarding the logic behind this fix, when `!used_fds[fd]` was false, last items in `topDict.FDArray` and `used_subrs` weren't really the ones corresponding to the current `fd`. So we are saving a dictionary to find the real index were those values were stored. What do you think about this fix? --- src/subset/CFFSubset.js | 6 ++++-- test/subset.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/subset/CFFSubset.js b/src/subset/CFFSubset.js index fdce7e61..f79514ba 100644 --- a/src/subset/CFFSubset.js +++ b/src/subset/CFFSubset.js @@ -55,6 +55,7 @@ export default class CFFSubset extends Subset { let used_fds = {}; let used_subrs = []; + let fd_select = {}; for (let gid of this.glyphs) { let fd = this.cff.fdForGlyph(gid); if (fd == null) { @@ -64,15 +65,16 @@ export default class CFFSubset extends Subset { if (!used_fds[fd]) { topDict.FDArray.push(Object.assign({}, this.cff.topDict.FDArray[fd])); used_subrs.push({}); + fd_select[fd] = topDict.FDArray.length - 1; } used_fds[fd] = true; - topDict.FDSelect.fds.push(topDict.FDArray.length - 1); + topDict.FDSelect.fds.push(fd_select[fd]); let glyph = this.font.getGlyph(gid); let path = glyph.path; // this causes the glyph to be parsed for (let subr in glyph._usedSubrs) { - used_subrs[used_subrs.length - 1][subr] = true; + used_subrs[fd_select[fd]][subr] = true; } } diff --git a/test/subset.js b/test/subset.js index 03626bc4..739d6b80 100644 --- a/test/subset.js +++ b/test/subset.js @@ -125,5 +125,27 @@ describe('font subsetting', function() { return done(); })); }); + + it('should produce a subset with asian punctuation corretly', function(done) { + const koreanFont = fontkit.openSync(__dirname + '/data/NotoSansCJK/NotoSansCJKkr-Regular.otf'); + const subset = koreanFont.createSubset(); + const iterable = koreanFont.glyphsForString('a。d'); + for (let i = 0; i < iterable.length; i++) { + const glyph = iterable[i]; + subset.includeGlyph(glyph); + } + + return subset.encodeStream().pipe(concat(function(buf) { + const stream = new r.DecodeStream(buf); + const cff = new CFFFont(stream); + let glyph = new CFFGlyph(1, [], { stream, 'CFF ': cff }); + assert.equal(glyph.path.toSVG(), koreanFont.glyphsForString('a')[0].path.toSVG()); + glyph = new CFFGlyph(2, [], { stream, 'CFF ': cff }); + assert.equal(glyph.path.toSVG(), koreanFont.glyphsForString('。')[0].path.toSVG()); + glyph = new CFFGlyph(3, [], { stream, 'CFF ': cff }); + assert.equal(glyph.path.toSVG(), koreanFont.glyphsForString('d')[0].path.toSVG()); + return done(); + })); + }); }); });