
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import {binarySearch} from './utils';
import {getEncoding} from './encodings';
import {cache} from './decorators';
import {range} from './utils';
// iconv-lite is an optional dependency.
try {
var iconv = require('iconv-lite');
} catch (err) {}
export default class CmapProcessor {
constructor(cmapTable) {
// Attempt to find a Unicode cmap first
this.encoding = null;
this.cmap = this.findSubtable(cmapTable, [
// 32-bit subtables
[3, 10],
[0, 6],
[0, 4],
// 16-bit subtables
[3, 1],
[0, 3],
[0, 2],
[0, 1],
[0, 0]
]);
// If not unicode cmap was found, and iconv-lite is installed,
// take the first table with a supported encoding.
if (!this.cmap && iconv) {
for (let cmap of cmapTable.tables) {
let encoding = getEncoding(cmap.platformID, cmap.encodingID, cmap.table.language - 1);
if (iconv.encodingExists(encoding)) {
this.cmap = cmap.table;
this.encoding = encoding;
}
}
}
if (!this.cmap) {
throw new Error("Could not find a supported cmap table");
}
this.uvs = this.findSubtable(cmapTable, [[0, 5]]);
if (this.uvs && this.uvs.version !== 14) {
this.uvs = null;
}
}
findSubtable(cmapTable, pairs) {
for (let [platformID, encodingID] of pairs) {
for (let cmap of cmapTable.tables) {
if (cmap.platformID === platformID && cmap.encodingID === encodingID) {
return cmap.table;
}
}
}
return null;
}
lookup(codepoint, variationSelector) {
// If there is no Unicode cmap in this font, we need to re-encode
// the codepoint in the encoding that the cmap supports.
if (this.encoding) {
let buf = iconv.encode(String.fromCodePoint(codepoint), this.encoding);
codepoint = 0;
for (let i = 0; i < buf.length; i++) {
codepoint = (codepoint << 8) | buf[i];
}
// Otherwise, try to get a Unicode variation selector for this codepoint if one is provided.
} else if (variationSelector) {
let gid = this.getVariationSelector(codepoint, variationSelector);
if (gid) {
return gid;
}
}
let cmap = this.cmap;
switch (cmap.version) {
case 0:
return cmap.codeMap.get(codepoint) || 0;
case 4: {
let min = 0;
let max = cmap.segCount - 1;
while (min <= max) {
let mid = (min + max) >> 1;
if (codepoint < cmap.startCode.get(mid)) {
max = mid - 1;
} else if (codepoint > cmap.endCode.get(mid)) {
min = mid + 1;
} else {
let rangeOffset = cmap.idRangeOffset.get(mid);
let gid;
if (rangeOffset === 0) {
gid = codepoint + cmap.idDelta.get(mid);
} else {
let index = rangeOffset / 2 + (codepoint - cmap.startCode.get(mid)) - (cmap.segCount - mid);
gid = cmap.glyphIndexArray.get(index) || 0;
if (gid !== 0) {
gid += cmap.idDelta.get(mid);
}
}
return gid & 0xffff;
}
}
return 0;
}
case 8:
throw new Error('TODO: cmap format 8');
case 6:
case 10:
return cmap.glyphIndices.get(codepoint - cmap.firstCode) || 0;
case 12:
case 13: {
let min = 0;
let max = cmap.nGroups - 1;
while (min <= max) {
let mid = (min + max) >> 1;
let group = cmap.groups.get(mid);
if (codepoint < group.startCharCode) {
max = mid - 1;
} else if (codepoint > group.endCharCode) {
min = mid + 1;
} else {
if (cmap.version === 12) {
return group.glyphID + (codepoint - group.startCharCode);
} else {
return group.glyphID;
}
}
}
return 0;
}
case 14:
throw new Error('TODO: cmap format 14');
default:
throw new Error(`Unknown cmap format ${cmap.version}`);
}
}
getVariationSelector(codepoint, variationSelector) {
if (!this.uvs) {
return 0;
}
let selectors = this.uvs.varSelectors.toArray();
let i = binarySearch(selectors, x => variationSelector - x.varSelector);
let sel = selectors[i];
if (i !== -1 && sel.defaultUVS) {
i = binarySearch(sel.defaultUVS, x =>
codepoint < x.startUnicodeValue ? -1 : codepoint > x.startUnicodeValue + x.additionalCount ? +1 : 0
);
}
if (i !== -1 && sel.nonDefaultUVS) {
i = binarySearch(sel.nonDefaultUVS, x => codepoint - x.unicodeValue);
if (i !== -1) {
return sel.nonDefaultUVS[i].glyphID;
}
}
return 0;
}
@cache
getCharacterSet() {
let cmap = this.cmap;
switch (cmap.version) {
case 0:
return range(0, cmap.codeMap.length);
case 4: {
let res = [];
let endCodes = cmap.endCode.toArray();
for (let i = 0; i < endCodes.length; i++) {
let tail = endCodes[i] + 1;
let start = cmap.startCode.get(i);
res.push(...range(start, tail));
}
return res;
}
case 8:
throw new Error('TODO: cmap format 8');
case 6:
case 10:
return range(cmap.firstCode, cmap.firstCode + cmap.glyphIndices.length);
case 12:
case 13: {
let res = [];
for (let group of cmap.groups.toArray()) {
res.push(...range(group.startCharCode, group.endCharCode + 1));
}
return res;
}
case 14:
throw new Error('TODO: cmap format 14');
default:
throw new Error(`Unknown cmap format ${cmap.version}`);
}
}
@cache
codePointsForGlyph(gid) {
let cmap = this.cmap;
switch (cmap.version) {
case 0: {
let res = [];
for (let i = 0; i < 256; i++) {
if (cmap.codeMap.get(i) === gid) {
res.push(i);
}
}
return res;
}
case 4: {
let res = [];
for (let i = 0; i < cmap.segCount; i++) {
let end = cmap.endCode.get(i);
let start = cmap.startCode.get(i);
let rangeOffset = cmap.idRangeOffset.get(i);
let delta = cmap.idDelta.get(i);
for (var c = start; c <= end; c++) {
let g = 0;
if (rangeOffset === 0) {
g = c + delta;
} else {
let index = rangeOffset / 2 + (c - start) - (cmap.segCount - i);
g = cmap.glyphIndexArray.get(index) || 0;
if (g !== 0) {
g += delta;
}
}
if (g === gid) {
res.push(c);
}
}
}
return res;
}
case 12: {
let res = [];
for (let group of cmap.groups.toArray()) {
if (gid >= group.glyphID && gid <= group.glyphID + (group.endCharCode - group.startCharCode)) {
res.push(group.startCharCode + (gid - group.glyphID));
}
}
return res;
}
case 13: {
let res = [];
for (let group of cmap.groups.toArray()) {
if (gid === group.glyphID) {
res.push(...range(group.startCharCode, group.endCharCode + 1));
}
}
return res;
}
default:
throw new Error(`Unknown cmap format ${cmap.version}`);
}
}
}