Current File : /home/tradevaly/www/node_modules/fontkit/src/CmapProcessor.js |
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}`);
}
}
}