Show More
Commit Description:
utf8mb4
Commit Description:
utf8mb4
References:
File last commit:
Show/Diff file:
Action:
node_modules/fontkit/src/CmapProcessor.js
| 295 lines
| 7.4 KiB
| application/javascript
| JavascriptGenshiLexer
|
r789 | 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}`); | ||||
} | ||||
} | ||||
} | ||||