// 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 };