Skip to content

Commit 96d133f

Browse files
committed
handle CMAP format 0 for platform 1, encoding 0 (legacy Macintosh encoding)
1 parent 55cf26c commit 96d133f

File tree

6 files changed

+58
-9
lines changed

6 files changed

+58
-9
lines changed

src/tables/cmap.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44
import check from '../check.js';
55
import parse from '../parse.js';
66
import table from '../table.js';
7+
import { eightBitMacEncodings } from '../types.js';
8+
import { getEncoding } from '../tables/name.js';
9+
10+
function parseCmapTableFormat0(cmap, p, platformID, encodingID) {
11+
// Length in bytes of the index map
12+
cmap.length = p.parseUShort();
13+
// see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
14+
// section "Macintosh Language Codes"
15+
cmap.language = p.parseUShort() - 1;
16+
17+
const indexMap = p.parseByteList(cmap.length);
18+
const glyphIndexMap = Object.assign({}, indexMap);
19+
const encoding = getEncoding(platformID, encodingID, cmap.language);
20+
const decodingTable = eightBitMacEncodings[encoding];
21+
for (let i = 0; i < decodingTable.length; i++) {
22+
glyphIndexMap[decodingTable.charCodeAt(i)] = indexMap[0x80 + i];
23+
}
24+
cmap.glyphIndexMap = glyphIndexMap;
25+
}
726

827
function parseCmapTableFormat12(cmap, p) {
928
//Skip reserved.
@@ -150,11 +169,15 @@ function parseCmapTable(data, start) {
150169
let format14Parser = null;
151170
let format14offset = -1;
152171
let offset = -1;
172+
let platformId = null;
173+
let encodingId = null;
153174
for (let i = cmap.numTables - 1; i >= 0; i -= 1) {
154-
const platformId = parse.getUShort(data, start + 4 + (i * 8));
155-
const encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
175+
platformId = parse.getUShort(data, start + 4 + (i * 8));
176+
encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
156177
if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) ||
157-
(platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) {
178+
(platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4)) ||
179+
(platformId === 1 && encodingId === 0) // MacOS <= 9
180+
) {
158181
offset = parse.getULong(data, start + 4 + (i * 8) + 4);
159182
// allow for early break
160183
if (format14Parser) {
@@ -178,12 +201,17 @@ function parseCmapTable(data, start) {
178201
const p = new parse.Parser(data, start + offset);
179202
cmap.format = p.parseUShort();
180203

181-
if (cmap.format === 12) {
204+
if (cmap.format === 0) {
205+
parseCmapTableFormat0(cmap, p, platformId, encodingId);
206+
} else if (cmap.format === 12) {
182207
parseCmapTableFormat12(cmap, p);
183208
} else if (cmap.format === 4) {
184209
parseCmapTableFormat4(cmap, p, data, start, offset);
185210
} else {
186-
throw new Error('Only format 4, 12 and 14 cmap tables are supported (found format ' + cmap.format + ').');
211+
throw new Error(
212+
'Only format 0 (platformId 1, encodingId 0), 4, 12 and 14 cmap tables are supported ' +
213+
'(found format ' + cmap.format + ', platformId ' + platformId + ', encodingId ' + encodingId + ').'
214+
);
187215
}
188216

189217
// format 14 is the only one that's not exclusive but can be used as a supplement.
@@ -361,4 +389,4 @@ function makeCmapTable(glyphs) {
361389

362390
export default { parse: parseCmapTable, make: makeCmapTable };
363391

364-
export { parseCmapTableFormat14 };
392+
export { parseCmapTableFormat0, parseCmapTableFormat14 };

src/tables/name.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ const macLanguageEncodings = {
604604
146: 'x-mac-gaelic' // langIrishGaelicScript
605605
};
606606

607-
function getEncoding(platformID, encodingID, languageID) {
607+
export function getEncoding(platformID, encodingID, languageID) {
608608
switch (platformID) {
609609
case 0: // Unicode
610610
return utf16;

src/types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ sizeOf.UTF16 = function(v) {
437437
/**
438438
* @private
439439
*/
440-
const eightBitMacEncodings = {
440+
export const eightBitMacEncodings = {
441441
'x-mac-croatian': // Python: 'mac_croatian'
442442
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' +
443443
'¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ',

test/fonts/LICENSE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ TestCMAP14.otf
4242
http://www.apache.org/licenses/LICENSE-2.0
4343
https://github.com/unicode-org/text-rendering-tests/blob/main/LICENSE.md
4444

45+
TestCMAPMacTurkish.ttf
46+
Copyright © 2016 by Unicode Inc.
47+
SIL Open Font License, Version 1.1
48+
https://opensource.org/licenses/OFL-1.1
49+
4550
Vibur.woff
4651
Copyright (c) 2010, Johan Kallas ([email protected]).
4752
SIL Open Font License, Version 1.1

test/fonts/TestCMAPMacTurkish.ttf

19.2 KB
Binary file not shown.

test/tables/cmap.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import assert from 'assert';
22
import { unhex } from '../testutil';
33
import { Parser } from '../../src/parse';
4-
import { parseCmapTableFormat14 } from '../../src/tables/cmap';
4+
import { parseCmapTableFormat14, parseCmapTableFormat0 } from '../../src/tables/cmap';
5+
import { parse } from '../../src/opentype.js';
6+
import { readFileSync } from 'fs';
7+
const loadSync = (url, opt) => parse(readFileSync(url), opt);
58

69
describe('tables/cmap.js', function() {
710

@@ -55,4 +58,17 @@ describe('tables/cmap.js', function() {
5558
assert.deepEqual(cmap.varSelectorList, expectedData);
5659
});
5760

61+
it('can parse CMAP format 0 legacy Mac encoding', function() {
62+
let font;
63+
assert.doesNotThrow(function() {
64+
font = loadSync('./test/fonts/TestCMAPMacTurkish.ttf');
65+
});
66+
const testString = '“ABÇĞIİÖŞÜ”abçğıiöşüă';
67+
const glyphIds = [];
68+
const expectedGlyphIds = [200,34,35,126,176,42,178,140,181,145,201,66,67,154,177,222,74,168,182,174,123,184];
69+
for (let i = 0; i < testString.length; i++) {
70+
glyphIds.push(font.charToGlyphIndex(testString.charAt(i)));
71+
}
72+
assert.deepEqual(glyphIds, expectedGlyphIds);
73+
});
5874
});

0 commit comments

Comments
 (0)