Skip to content

Commit

Permalink
fix(charset): Implemented charset according to specification
Browse files Browse the repository at this point in the history
  • Loading branch information
ln-north committed Sep 21, 2023
1 parent bcdd238 commit bcf8ca1
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 31 deletions.
76 changes: 70 additions & 6 deletions src/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -303,6 +364,9 @@ function addGlyphNames(font, opt) {

export {
cffStandardStrings,
cffISOAdobeStrings,
cffIExpertStrings,
cffExpertSubsetStrings,
cffStandardEncoding,
cffExpertEncoding,
standardNames,
Expand Down
6 changes: 1 addition & 5 deletions src/glyphset.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
73 changes: 55 additions & 18 deletions src/tables/cff.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
Expand All @@ -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;
}
}
Expand All @@ -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();
Expand All @@ -559,15 +582,15 @@ 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;
}
}
} else {
throw new Error('Unknown encoding format ' + format);
}

return new CffEncoding(enc, charset);
return encoding;
}

function parseBlend(operands) {
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions test/opentypeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down

0 comments on commit bcf8ca1

Please sign in to comment.