From bcf8ca1d80a6c80eb38f83e2a94c491c4f34bfdd Mon Sep 17 00:00:00 2001 From: Ln-north Date: Thu, 21 Sep 2023 16:13:10 +0900 Subject: [PATCH 1/2] fix(charset): Implemented charset according to specification --- src/encoding.js | 76 ++++++++++++++++++++++++++++++++++++++++---- src/glyphset.js | 6 +--- src/tables/cff.js | 73 +++++++++++++++++++++++++++++++----------- test/opentypeSpec.js | 4 +-- 4 files changed, 128 insertions(+), 31 deletions(-) diff --git a/src/encoding.js b/src/encoding.js index 865fad9f..be4b3d64 100644 --- a/src/encoding.js +++ b/src/encoding.js @@ -45,6 +45,71 @@ const cffStandardStrings = [ 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; +// Strings below index 392 are standard CFF strings and are not encoded in the font. + +const cffISOAdobeStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', + 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', + 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', + 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla', + 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', + 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 'trademark', 'Eth', + 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', + 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 'Aacute', 'Acircumflex', + 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', + 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', + 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', + 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', + 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron']; + +const cffIExpertStrings = [ + '.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', + 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', + 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', + 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior', + 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', + 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', + 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', + 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', + 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', + 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', + 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', + 'Cedillasmall', 'onequarter', 'onehalf', 'threequarters', 'questiondownsmall', 'oneeighth', 'threeeighths', + 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', + 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', + 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', + 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', + 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', + 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', + 'Yacutesmall', 'Thornsmall', 'Ydieresissmall' +]; + +const cffExpertSubsetStrings = [ + '.notdef', 'space', 'dollaroldstyle', 'dollarsuperior', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', + 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', + 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', + 'colon', 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior', + 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', + 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', + 'hyphensuperior', 'colonmonetary', 'onefitted', 'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', + 'onequarter', 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', + 'twothirds', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', + 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior' +]; + const cffStandardEncoding = [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', @@ -117,7 +182,7 @@ const standardNames = [ 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; /** * This is the encoding used for fonts created from scratch. @@ -258,11 +323,7 @@ function addGlyphNamesAll(font) { for (let i = 0; i < font.glyphs.length; i += 1) { glyph = font.glyphs.get(i); if (font.cffEncoding) { - if (font.isCIDFont) { - glyph.name = 'gid' + i; - } else { - glyph.name = font.cffEncoding.charset[i]; - } + glyph.name = font.cffEncoding.charset[i]; } else if (font.glyphNames.names) { glyph.name = font.glyphNames.glyphIndexToName(i); } @@ -303,6 +364,9 @@ function addGlyphNames(font, opt) { export { cffStandardStrings, + cffISOAdobeStrings, + cffIExpertStrings, + cffExpertSubsetStrings, cffStandardEncoding, cffExpertEncoding, standardNames, diff --git a/src/glyphset.js b/src/glyphset.js index c73688ee..98e66653 100644 --- a/src/glyphset.js +++ b/src/glyphset.js @@ -62,11 +62,7 @@ GlyphSet.prototype.get = function(index) { } if (this.font.cffEncoding) { - if (this.font.isCIDFont) { - glyph.name = 'gid' + index; - } else { - glyph.name = this.font.cffEncoding.charset[index]; - } + glyph.name = this.font.cffEncoding.charset[index]; } else if (this.font.glyphNames.names) { glyph.name = this.font.glyphNames.glyphIndexToName(index); } diff --git a/src/tables/cff.js b/src/tables/cff.js index 0cd5d6b5..40ed2dc9 100755 --- a/src/tables/cff.js +++ b/src/tables/cff.js @@ -5,7 +5,14 @@ // @TODO: refactor parsing using stateful parser? -import { CffEncoding, cffStandardEncoding, cffExpertEncoding, cffStandardStrings } from '../encoding.js'; +import { + CffEncoding, + cffStandardEncoding, + cffExpertEncoding, + cffStandardStrings, + cffISOAdobeStrings, + cffIExpertStrings, + cffExpertSubsetStrings } from '../encoding.js'; import glyphset from '../glyphset.js'; import parse from '../parse.js'; import Path from '../path.js'; @@ -33,7 +40,7 @@ function equals(a, b) { } // Subroutines are encoded using the negative half of the number space. -// See type 2 chapter 4.7 "Subroutine operators". +// See type 2 chapter 4.7 'Subroutine operators'. function calcCFFSubroutineBias(subrs) { let bias; if (subrs.length < 1240) { @@ -453,7 +460,7 @@ function gatherCFF2FontDicts(data, start, fdArray) { return fontDictArray; } -// Returns a list of "Top DICT"s found using an INDEX list. +// Returns a list of 'Top DICT's found using an INDEX list. // Used to read both the usual high-level Top DICTs and also the FDArray // discovered inside CID-keyed fonts. When a Top DICT has a reference to // a Private DICT that is read and saved into the Top DICT. @@ -498,8 +505,8 @@ function gatherCFFTopDicts(data, start, cffIndex, strings, version) { // Parse the CFF charset table, which contains internal names for all the glyphs. // This function will return a list of glyph names. -// See Adobe TN #5176 chapter 13, "Charsets". -function parseCFFCharset(data, start, nGlyphs, strings) { +// See Adobe TN #5176 chapter 13, 'Charsets'. +function parseCFFCharset(data, start, nGlyphs, strings, isCIDFont) { let sid; let count; const parser = new parse.Parser(data, start); @@ -512,14 +519,24 @@ function parseCFFCharset(data, start, nGlyphs, strings) { if (format === 0) { for (let i = 0; i < nGlyphs; i += 1) { sid = parser.parseSID(); - charset.push(getCFFString(strings, sid) || sid); + + if(isCIDFont) { + charset.push(sid); + } else { + charset.push(getCFFString(strings, sid) || sid); + } + } } else if (format === 1) { while (charset.length <= nGlyphs) { sid = parser.parseSID(); count = parser.parseCard8(); for (let i = 0; i <= count; i += 1) { - charset.push(getCFFString(strings, sid) || sid); + if(isCIDFont) { + charset.push('cid' + ('00000' + sid).slice(-5)); + } else { + charset.push(getCFFString(strings, sid) || sid); + } sid += 1; } } @@ -528,7 +545,11 @@ function parseCFFCharset(data, start, nGlyphs, strings) { sid = parser.parseSID(); count = parser.parseCard16(); for (let i = 0; i <= count; i += 1) { - charset.push(getCFFString(strings, sid) || sid); + if(isCIDFont) { + charset.push('cid' + ('00000' + sid).slice(-5)); + } else { + charset.push(getCFFString(strings, sid) || sid); + } sid += 1; } } @@ -537,20 +558,22 @@ function parseCFFCharset(data, start, nGlyphs, strings) { } return charset; + + } // Parse the CFF encoding data. Only one encoding can be specified per font. -// See Adobe TN #5176 chapter 12, "Encodings". -function parseCFFEncoding(data, start, charset) { +// See Adobe TN #5176 chapter 12, 'Encodings'. +function parseCFFEncoding(data, start) { let code; - const enc = {}; + const encoding = {}; const parser = new parse.Parser(data, start); const format = parser.parseCard8(); if (format === 0) { const nCodes = parser.parseCard8(); for (let i = 0; i < nCodes; i += 1) { code = parser.parseCard8(); - enc[code] = i; + encoding[code] = i; } } else if (format === 1) { const nRanges = parser.parseCard8(); @@ -559,7 +582,7 @@ function parseCFFEncoding(data, start, charset) { const first = parser.parseCard8(); const nLeft = parser.parseCard8(); for (let j = first; j <= first + nLeft; j += 1) { - enc[j] = code; + encoding[j] = code; code += 1; } } @@ -567,7 +590,7 @@ function parseCFFEncoding(data, start, charset) { throw new Error('Unknown encoding format ' + format); } - return new CffEncoding(enc, charset); + return encoding; } function parseBlend(operands) { @@ -1203,17 +1226,31 @@ function parseCFFTable(data, start, font, opt) { } if (header.formatMajor < 2) { - const charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + let charset = []; + let encoding = []; + + if(topDict.charset === 0) { + charset = cffISOAdobeStrings; + } else if(topDict.charset === 1) { + charset = cffIExpertStrings; + } else if (topDict.charset === 2) { + charset = cffExpertSubsetStrings; + } else { + charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects, font.isCIDFont); + } + if (topDict.encoding === 0) { // Standard encoding - font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + encoding = cffStandardEncoding; } else if (topDict.encoding === 1) { // Expert encoding - font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + encoding = cffExpertEncoding; } else { - font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + encoding = parseCFFEncoding(data, start + topDict.encoding); } + font.cffEncoding = new CffEncoding(encoding, charset); + // Prefer the CMAP encoding to the CFF encoding. font.encoding = font.encoding || font.cffEncoding; } diff --git a/test/opentypeSpec.js b/test/opentypeSpec.js index 5ca7e69a..8e62cb42 100644 --- a/test/opentypeSpec.js +++ b/test/opentypeSpec.js @@ -51,7 +51,7 @@ describe('opentype.js', function() { assert.equal(font.unitsPerEm, 1000); assert.equal(font.glyphs.length, 257); const aGlyph = font.glyphs.get(2); - assert.equal(aGlyph.name, 'gid2'); + assert.equal(aGlyph.name, 'cid00002'); assert.equal(aGlyph.unicode, 1); assert.equal(aGlyph.path.commands.length, 24); }); @@ -140,7 +140,7 @@ describe('opentype.js on low memory mode', function() { assert.equal(font.unitsPerEm, 1000); assert.equal(font.glyphs.length, 0); const aGlyph = font.glyphs.get(2); - assert.equal(aGlyph.name, 'gid2'); + assert.equal(aGlyph.name, 'cid00002'); assert.equal(aGlyph.unicode, 1); assert.equal(aGlyph.path.commands.length, 24); }); From 3e307f13e3e22128257742c67593cc72d053d042 Mon Sep 17 00:00:00 2001 From: Ln-north Date: Mon, 4 Dec 2023 11:15:10 +0900 Subject: [PATCH 2/2] fix(coding-style): Format coding-style --- src/encoding.js | 2 +- src/tables/cff.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/encoding.js b/src/encoding.js index be4b3d64..0902b8c8 100644 --- a/src/encoding.js +++ b/src/encoding.js @@ -182,7 +182,7 @@ const standardNames = [ 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', - 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; /** * This is the encoding used for fonts created from scratch. diff --git a/src/tables/cff.js b/src/tables/cff.js index ef3aa392..334ce92e 100755 --- a/src/tables/cff.js +++ b/src/tables/cff.js @@ -40,7 +40,7 @@ function equals(a, b) { } // Subroutines are encoded using the negative half of the number space. -// See type 2 chapter 4.7 'Subroutine operators'. +// See type 2 chapter 4.7 "Subroutine operators". function calcCFFSubroutineBias(subrs) { let bias; if (subrs.length < 1240) { @@ -460,7 +460,7 @@ function gatherCFF2FontDicts(data, start, fdArray) { return fontDictArray; } -// Returns a list of 'Top DICT's found using an INDEX list. +// Returns a list of "Top DICT"s found using an INDEX list. // Used to read both the usual high-level Top DICTs and also the FDArray // discovered inside CID-keyed fonts. When a Top DICT has a reference to // a Private DICT that is read and saved into the Top DICT. @@ -505,7 +505,7 @@ function gatherCFFTopDicts(data, start, cffIndex, strings, version) { // Parse the CFF charset table, which contains internal names for all the glyphs. // This function will return a list of glyph names. -// See Adobe TN #5176 chapter 13, 'Charsets'. +// See Adobe TN #5176 chapter 13, "Charsets". function parseCFFCharset(data, start, nGlyphs, strings, isCIDFont) { let sid; let count; @@ -558,12 +558,10 @@ function parseCFFCharset(data, start, nGlyphs, strings, isCIDFont) { } return charset; - - } // Parse the CFF encoding data. Only one encoding can be specified per font. -// See Adobe TN #5176 chapter 12, 'Encodings'. +// See Adobe TN #5176 chapter 12, "Encodings". function parseCFFEncoding(data, start) { let code; const encoding = {};