Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/mattlag/opentype.js
Browse files Browse the repository at this point in the history
  • Loading branch information
mattlag committed Nov 6, 2023
2 parents e4f8ee7 + db61eab commit 67ecfeb
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/font.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function Font(options) {
* @return {Boolean}
*/
Font.prototype.hasChar = function(c) {
return this.encoding.charToGlyphIndex(c) !== null;
return this.encoding.charToGlyphIndex(c) > 0;
};

/**
Expand Down
47 changes: 46 additions & 1 deletion src/tables/cff.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,52 @@ function parseCFFCharstring(font, glyph, code, version) {
break;
}

if (stack.length > 0 && !haveWidth) {
if (stack.length >= 4) {
// Type 2 Charstring Format Appendix C
// treat like Type 1 seac command (standard encoding accented character)
const acharName = cffStandardEncoding[stack.pop()];
const bcharName = cffStandardEncoding[stack.pop()];
const ady = stack.pop();
const adx = stack.pop();
// const asb = stack.pop(); // ignored for Type 2
if ( acharName && bcharName ) {
glyph.isComposite = true;
glyph.components = [];

const acharGlyphIndex = font.cffEncoding.charset.indexOf(acharName);
const bcharGlyphIndex = font.cffEncoding.charset.indexOf(bcharName);

glyph.components.push({
glyphIndex: bcharGlyphIndex,
dx: 0,
dy: 0
});
glyph.components.push({
glyphIndex: acharGlyphIndex,
dx: adx,
dy: ady
});
p.extend(font.glyphs.get(bcharGlyphIndex).path);
const acharGlyph = font.glyphs.get(acharGlyphIndex);
const shiftedCommands = JSON.parse(JSON.stringify(acharGlyph.path.commands)); // make a deep clone
for (let i = 0; i < shiftedCommands.length; i += 1) {
const cmd = shiftedCommands[i];
if (cmd.type !== 'Z') {
cmd.x += adx;
cmd.y += ady;
}
if ( cmd.type === 'Q' || cmd.type === 'C' ) {
cmd.x1 += adx;
cmd.y1 += ady;
}
if ( cmd.type === 'C' ) {
cmd.x2 += adx;
cmd.y2 += ady;
}
}
p.extend(shiftedCommands);
}
} else if (stack.length > 0 && !haveWidth) {
width = stack.shift() + nominalWidthX;
haveWidth = true;
}
Expand Down
40 changes: 34 additions & 6 deletions src/tables/cmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@
import check from '../check.js';
import parse from '../parse.js';
import table from '../table.js';
import { eightBitMacEncodings } from '../types.js';
import { getEncoding } from '../tables/name.js';

function parseCmapTableFormat0(cmap, p, platformID, encodingID) {
// Length in bytes of the index map
cmap.length = p.parseUShort();
// see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
// section "Macintosh Language Codes"
cmap.language = p.parseUShort() - 1;

const indexMap = p.parseByteList(cmap.length);
const glyphIndexMap = Object.assign({}, indexMap);
const encoding = getEncoding(platformID, encodingID, cmap.language);
const decodingTable = eightBitMacEncodings[encoding];
for (let i = 0; i < decodingTable.length; i++) {
glyphIndexMap[decodingTable.charCodeAt(i)] = indexMap[0x80 + i];
}
cmap.glyphIndexMap = glyphIndexMap;
}

function parseCmapTableFormat12(cmap, p) {
//Skip reserved.
Expand Down Expand Up @@ -150,11 +169,15 @@ function parseCmapTable(data, start) {
let format14Parser = null;
let format14offset = -1;
let offset = -1;
let platformId = null;
let encodingId = null;
for (let i = cmap.numTables - 1; i >= 0; i -= 1) {
const platformId = parse.getUShort(data, start + 4 + (i * 8));
const encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
platformId = parse.getUShort(data, start + 4 + (i * 8));
encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2);
if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) ||
(platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) {
(platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4)) ||
(platformId === 1 && encodingId === 0) // MacOS <= 9
) {
offset = parse.getULong(data, start + 4 + (i * 8) + 4);
// allow for early break
if (format14Parser) {
Expand All @@ -178,12 +201,17 @@ function parseCmapTable(data, start) {
const p = new parse.Parser(data, start + offset);
cmap.format = p.parseUShort();

if (cmap.format === 12) {
if (cmap.format === 0) {
parseCmapTableFormat0(cmap, p, platformId, encodingId);
} else if (cmap.format === 12) {
parseCmapTableFormat12(cmap, p);
} else if (cmap.format === 4) {
parseCmapTableFormat4(cmap, p, data, start, offset);
} else {
throw new Error('Only format 4, 12 and 14 cmap tables are supported (found format ' + cmap.format + ').');
throw new Error(
'Only format 0 (platformId 1, encodingId 0), 4, 12 and 14 cmap tables are supported ' +
'(found format ' + cmap.format + ', platformId ' + platformId + ', encodingId ' + encodingId + ').'
);
}

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

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

export { parseCmapTableFormat14 };
export { parseCmapTableFormat0, parseCmapTableFormat14 };
2 changes: 1 addition & 1 deletion src/tables/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ const macLanguageEncodings = {
146: 'x-mac-gaelic' // langIrishGaelicScript
};

function getEncoding(platformID, encodingID, languageID) {
export function getEncoding(platformID, encodingID, languageID) {
switch (platformID) {
case 0: // Unicode
return utf16;
Expand Down
2 changes: 1 addition & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ sizeOf.UTF16 = function(v) {
/**
* @private
*/
const eightBitMacEncodings = {
export const eightBitMacEncodings = {
'x-mac-croatian': // Python: 'mac_croatian'
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' +
'¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ',
Expand Down
13 changes: 13 additions & 0 deletions test/font.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,17 @@ describe('font.js', function() {
});

});

describe('hasChar', function() {
it('returns correct results for non-CMAP fonts', function() {
assert.equal(font.hasChar('i'), true);
assert.equal(font.hasChar('x'), false);
});

it('returns correct results for CMAP fonts', function() {
const cmapFont = loadSync('./test/fonts/TestCMAP14.otf');
assert.equal(cmapFont.hasChar('a'), false);
assert.equal(cmapFont.hasChar('≩'), true);
});
});
});
Binary file added test/fonts/AbrilFatface-Regular.otf
Binary file not shown.
10 changes: 10 additions & 0 deletions test/fonts/LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
AbrilFatface-Regular.otf
Copyright (c) 2011, Copyright (c) 2011, TypeTogether (www.type-together.com [email protected]), with Reserved Font Names “Abril” and “Abril Fatface”
SIL Open Font License, Version 1.1.
https://www.fontsquirrel.com/license/abril-fatface

Changa-Regular.ttf
Copyright 2011 Eduardo Tunni (www.tipo.net.ar)
SIL Open Font License, Version 1.1.
Expand Down Expand Up @@ -37,6 +42,11 @@ TestCMAP14.otf
http://www.apache.org/licenses/LICENSE-2.0
https://github.com/unicode-org/text-rendering-tests/blob/main/LICENSE.md

TestCMAPMacTurkish.ttf
Copyright © 2016 by Unicode Inc.
SIL Open Font License, Version 1.1
https://opensource.org/licenses/OFL-1.1

Vibur.woff
Copyright (c) 2010, Johan Kallas ([email protected]).
SIL Open Font License, Version 1.1
Expand Down
Binary file added test/fonts/TestCMAPMacTurkish.ttf
Binary file not shown.
13 changes: 13 additions & 0 deletions test/tables/cff.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,17 @@ describe('tables/cff.js', function () {
{ type: 'L', x: 50, y: 500 }
] );
});

it('can handle standard encoding accented characters via endchar', function() {
const font = loadSync('./test/fonts/AbrilFatface-Regular.otf', { lowMemory: true });
const glyph13 = font.glyphs.get(13); // the semicolon is combined of comma and period
const commands = glyph13.path.commands;
assert.equal(glyph13.isComposite, true);
assert.equal(commands.length, 15);
assert.deepEqual(commands[0], { type: 'M', x: 86, y: -156 });
assert.deepEqual(commands[7], { type: 'C', x: 74, y: -141, x1: 174, y1: -35, x2: 162, y2: -66 });
assert.deepEqual(commands[9], { type: 'M', x: 36, y: 407 });
assert.deepEqual(commands[13], { type: 'C', x: 36, y: 407, x1: 66, y1: 495, x2: 36, y2: 456 });
assert.deepEqual(commands[14], { type: 'Z' });
});
});
18 changes: 17 additions & 1 deletion test/tables/cmap.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import assert from 'assert';
import { unhex } from '../testutil';
import { Parser } from '../../src/parse';
import { parseCmapTableFormat14 } from '../../src/tables/cmap';
import { parseCmapTableFormat14, parseCmapTableFormat0 } from '../../src/tables/cmap';
import { parse } from '../../src/opentype.js';
import { readFileSync } from 'fs';
const loadSync = (url, opt) => parse(readFileSync(url), opt);

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

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

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

0 comments on commit 67ecfeb

Please sign in to comment.