146 lines
5.1 KiB
Plaintext
146 lines
5.1 KiB
Plaintext
// The `cmap` table stores the mappings from characters to glyphs.
|
|
// https://www.microsoft.com/typography/OTSPEC/cmap.htm
|
|
|
|
import check from '../check';
|
|
import parse from '../parse';
|
|
|
|
function parseCmapTableFormat12(cmap, p) {
|
|
//Skip reserved.
|
|
p.parseUShort();
|
|
|
|
// Length in bytes of the sub-tables.
|
|
cmap.length = p.parseULong();
|
|
cmap.language = p.parseULong();
|
|
|
|
let groupCount;
|
|
cmap.groupCount = groupCount = p.parseULong();
|
|
cmap.glyphIndexMap = {};
|
|
|
|
for (let i = 0; i < groupCount; i += 1) {
|
|
const startCharCode = p.parseULong();
|
|
const endCharCode = p.parseULong();
|
|
let startGlyphId = p.parseULong();
|
|
|
|
for (let c = startCharCode; c <= endCharCode; c += 1) {
|
|
cmap.glyphIndexMap[c] = startGlyphId;
|
|
startGlyphId++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseCmapTableFormat4(cmap, p, data, start, offset) {
|
|
// Length in bytes of the sub-tables.
|
|
cmap.length = p.parseUShort();
|
|
cmap.language = p.parseUShort();
|
|
|
|
// segCount is stored x 2.
|
|
let segCount;
|
|
cmap.segCount = segCount = p.parseUShort() >> 1;
|
|
|
|
// Skip searchRange, entrySelector, rangeShift.
|
|
p.skip('uShort', 3);
|
|
|
|
// The "unrolled" mapping from character codes to glyph indices.
|
|
cmap.glyphIndexMap = {};
|
|
const endCountParser = new parse.Parser(data, start + offset + 14);
|
|
const startCountParser = new parse.Parser(
|
|
data,
|
|
start + offset + 16 + segCount * 2
|
|
);
|
|
const idDeltaParser = new parse.Parser(
|
|
data,
|
|
start + offset + 16 + segCount * 4
|
|
);
|
|
const idRangeOffsetParser = new parse.Parser(
|
|
data,
|
|
start + offset + 16 + segCount * 6
|
|
);
|
|
let glyphIndexOffset = start + offset + 16 + segCount * 8;
|
|
for (let i = 0; i < segCount - 1; i += 1) {
|
|
let glyphIndex;
|
|
const endCount = endCountParser.parseUShort();
|
|
const startCount = startCountParser.parseUShort();
|
|
const idDelta = idDeltaParser.parseShort();
|
|
const idRangeOffset = idRangeOffsetParser.parseUShort();
|
|
for (let c = startCount; c <= endCount; c += 1) {
|
|
if (idRangeOffset !== 0) {
|
|
// The idRangeOffset is relative to the current position in the idRangeOffset array.
|
|
// Take the current offset in the idRangeOffset array.
|
|
glyphIndexOffset =
|
|
idRangeOffsetParser.offset +
|
|
idRangeOffsetParser.relativeOffset -
|
|
2;
|
|
|
|
// Add the value of the idRangeOffset, which will move us into the glyphIndex array.
|
|
glyphIndexOffset += idRangeOffset;
|
|
|
|
// Then add the character index of the current segment, multiplied by 2 for USHORTs.
|
|
glyphIndexOffset += (c - startCount) * 2;
|
|
glyphIndex = parse.getUShort(data, glyphIndexOffset);
|
|
if (glyphIndex !== 0) {
|
|
glyphIndex = (glyphIndex + idDelta) & 0xffff;
|
|
}
|
|
} else {
|
|
glyphIndex = (c + idDelta) & 0xffff;
|
|
}
|
|
|
|
cmap.glyphIndexMap[c] = glyphIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse the `cmap` table. This table stores the mappings from characters to glyphs.
|
|
// There are many available formats, but we only support the Windows format 4 and 12.
|
|
// This function returns a `CmapEncoding` object or null if no supported format could be found.
|
|
function parseCmapTable(data, start) {
|
|
const cmap = {};
|
|
cmap.version = parse.getUShort(data, start);
|
|
check.argument(cmap.version === 0, 'cmap table version should be 0.');
|
|
|
|
// The cmap table can contain many sub-tables, each with their own format.
|
|
// We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table.
|
|
cmap.numTables = parse.getUShort(data, start + 2);
|
|
let offset = -1;
|
|
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);
|
|
if (
|
|
(platformId === 3 &&
|
|
(encodingId === 0 || encodingId === 1 || encodingId === 10)) ||
|
|
(platformId === 0 &&
|
|
(encodingId === 0 ||
|
|
encodingId === 1 ||
|
|
encodingId === 2 ||
|
|
encodingId === 3 ||
|
|
encodingId === 4))
|
|
) {
|
|
offset = parse.getULong(data, start + 4 + i * 8 + 4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (offset === -1) {
|
|
// There is no cmap table in the font that we support.
|
|
throw new Error('No valid cmap sub-tables found.');
|
|
}
|
|
|
|
const p = new parse.Parser(data, start + offset);
|
|
cmap.format = p.parseUShort();
|
|
|
|
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 and 12 cmap tables are supported (found format ' +
|
|
cmap.format +
|
|
').'
|
|
);
|
|
}
|
|
|
|
return cmap;
|
|
}
|
|
|
|
export default { parse: parseCmapTable };
|