You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
7.7 KiB
304 lines
7.7 KiB
'use strict'; |
|
|
|
// See documentation here: http://www.microsoft.com/typography/otspec/cmap.htm |
|
|
|
var _ = require('lodash'); |
|
var ByteBuffer = require('microbuffer'); |
|
|
|
function getIDByUnicode(font, unicode) { |
|
return font.codePoints[unicode] ? font.codePoints[unicode].id : 0; |
|
} |
|
|
|
// Calculate character segments with non-interruptable chains of unicodes |
|
function getSegments(font, bounds) { |
|
bounds = bounds || Number.MAX_VALUE; |
|
|
|
var result = []; |
|
var segment; |
|
|
|
// prevEndCode only changes when a segment closes |
|
_.forEach(font.codePoints, function (glyph, unicode) { |
|
unicode = parseInt(unicode, 10); |
|
if (unicode >= bounds) { |
|
return false; |
|
} |
|
// Initialize first segment or add new segment if code "hole" is found |
|
if (!segment || unicode !== (segment.end + 1)) { |
|
if (segment) { |
|
result.push(segment); |
|
} |
|
segment = { |
|
start: unicode |
|
}; |
|
} |
|
segment.end = unicode; |
|
}); |
|
|
|
// Need to finish the last segment |
|
if (segment) { |
|
result.push(segment); |
|
} |
|
|
|
_.forEach(result, function (segment) { |
|
segment.length = segment.end - segment.start + 1; |
|
}); |
|
|
|
return result; |
|
} |
|
|
|
// Returns an array of {unicode, glyph} sets for all valid code points up to bounds |
|
function getCodePoints(codePoints, bounds) { |
|
bounds = bounds || Number.MAX_VALUE; |
|
|
|
var result = []; |
|
|
|
_.forEach(codePoints, function (glyph, unicode) { |
|
unicode = parseInt(unicode, 10); |
|
// Since this is a sparse array, iterating will only yield the valid code points |
|
if (unicode > bounds) { |
|
return false; |
|
} |
|
result.push({ |
|
unicode: unicode, |
|
glyph: glyph |
|
}); |
|
}); |
|
return result; |
|
} |
|
|
|
function bufferForTable(format, length) { |
|
var fieldWidth = format === 8 || format === 10 || format === 12 || format === 13 ? 4 : 2; |
|
|
|
length += (0 |
|
+ fieldWidth // Format |
|
+ fieldWidth // Length |
|
+ fieldWidth // Language |
|
); |
|
|
|
var LANGUAGE = 0; |
|
var buffer = new ByteBuffer(length); |
|
|
|
var writer = fieldWidth === 4 ? buffer.writeUint32 : buffer.writeUint16; |
|
|
|
// Format specifier |
|
buffer.writeUint16(format); |
|
if (fieldWidth === 4) { |
|
// In case of formats 8.…, 10.…, 12.… and 13.…, this is the decimal part of the format number |
|
// But since have not been any point releases, this can be zero in that case as well |
|
buffer.writeUint16(0); |
|
} |
|
// Length |
|
writer.call(buffer, length); |
|
// Language code (0, only used for legacy quickdraw tables) |
|
writer.call(buffer, LANGUAGE); |
|
|
|
return buffer; |
|
} |
|
|
|
function createFormat0Table(font) { |
|
var FORMAT = 0; |
|
|
|
var i; |
|
|
|
var length = 0xff + 1; //Format 0 maps only single-byte code points |
|
|
|
var buffer = bufferForTable(FORMAT, length); |
|
|
|
for (i = 0; i < length; i++) { |
|
buffer.writeUint8(getIDByUnicode(font, i)); // existing char in table 0..255 |
|
} |
|
return buffer; |
|
} |
|
|
|
function createFormat4Table(font) { |
|
var FORMAT = 4; |
|
|
|
var i; |
|
|
|
var segments = getSegments(font, 0xFFFF); |
|
var glyphIndexArrays = []; |
|
|
|
_.forEach(segments, function (segment) { |
|
var glyphIndexArray = []; |
|
|
|
for (var unicode = segment.start; unicode <= segment.end; unicode++) { |
|
glyphIndexArray.push(getIDByUnicode(font, unicode)); |
|
} |
|
glyphIndexArrays.push(glyphIndexArray); |
|
}); |
|
|
|
var segCount = segments.length + 1; // + 1 for the 0xFFFF section |
|
var glyphIndexArrayLength = _.reduce(_.map(glyphIndexArrays, 'length'), function (result, count) { return result + count; }, 0); |
|
|
|
var length = (0 |
|
+ 2 // segCountX2 |
|
+ 2 // searchRange |
|
+ 2 // entrySelector |
|
+ 2 // rangeShift |
|
+ 2 * segCount // endCodes |
|
+ 2 // Padding |
|
+ 2 * segCount //startCodes |
|
+ 2 * segCount //idDeltas |
|
+ 2 * segCount //idRangeOffsets |
|
+ 2 * glyphIndexArrayLength |
|
); |
|
|
|
var buffer = bufferForTable(FORMAT, length); |
|
|
|
buffer.writeUint16(segCount * 2); // segCountX2 |
|
var maxExponent = Math.floor(Math.log(segCount) / Math.LN2); |
|
var searchRange = 2 * Math.pow(2, maxExponent); |
|
|
|
buffer.writeUint16(searchRange); // searchRange |
|
buffer.writeUint16(maxExponent); // entrySelector |
|
buffer.writeUint16(2 * segCount - searchRange); // rangeShift |
|
|
|
// Array of end counts |
|
_.forEach(segments, function (segment) { |
|
buffer.writeUint16(segment.end); |
|
}); |
|
buffer.writeUint16(0xFFFF); // endCountArray should be finished with 0xFFFF |
|
|
|
buffer.writeUint16(0); // reservedPad |
|
|
|
// Array of start counts |
|
_.forEach(segments, function (segment) { |
|
buffer.writeUint16(segment.start); //startCountArray |
|
}); |
|
buffer.writeUint16(0xFFFF); // startCountArray should be finished with 0xFFFF |
|
|
|
// Array of deltas. Leave it zero to not complicate things when using the glyph index array |
|
for (i = 0; i < segments.length; i++) { |
|
buffer.writeUint16(0); // delta is always zero because we use the glyph array |
|
} |
|
buffer.writeUint16(1); // idDeltaArray should be finished with 1 |
|
|
|
// Array of range offsets |
|
var offset = 0; |
|
|
|
for (i = 0; i < segments.length; i++) { |
|
buffer.writeUint16(2 * ((segments.length - i + 1) + offset)); |
|
offset += glyphIndexArrays[i].length; |
|
} |
|
buffer.writeUint16(0); // rangeOffsetArray should be finished with 0 |
|
|
|
_.forEach(glyphIndexArrays, function (glyphIndexArray) { |
|
_.forEach(glyphIndexArray, function (glyphId) { |
|
buffer.writeUint16(glyphId); |
|
}); |
|
}); |
|
|
|
return buffer; |
|
} |
|
|
|
function createFormat12Table(font) { |
|
var FORMAT = 12; |
|
|
|
var codePoints = getCodePoints(font.codePoints); |
|
|
|
var length = (0 |
|
+ 4 // nGroups |
|
+ 4 * codePoints.length // startCharCode |
|
+ 4 * codePoints.length // endCharCode |
|
+ 4 * codePoints.length // startGlyphCode |
|
); |
|
|
|
var buffer = bufferForTable(FORMAT, length); |
|
|
|
buffer.writeUint32(codePoints.length); // nGroups |
|
_.forEach(codePoints, function (codePoint) { |
|
buffer.writeUint32(codePoint.unicode); // startCharCode |
|
buffer.writeUint32(codePoint.unicode); // endCharCode |
|
buffer.writeUint32(codePoint.glyph.id); // startGlyphCode |
|
}); |
|
|
|
return buffer; |
|
} |
|
|
|
function createCMapTable(font) { |
|
var TABLE_HEAD = (0 |
|
+ 2 // platform |
|
+ 2 // encoding |
|
+ 4 // offset |
|
); |
|
|
|
var singleByteTable = createFormat0Table(font); |
|
var twoByteTable = createFormat4Table(font); |
|
var fourByteTable = createFormat12Table(font); |
|
|
|
// Subtable headers must be sorted by platformID, encodingID |
|
var tableHeaders = [ |
|
// subtable 4, unicode |
|
{ |
|
platformID: 0, |
|
encodingID: 3, |
|
table: twoByteTable |
|
}, |
|
// subtable 12, unicode |
|
{ |
|
platformID: 0, |
|
encodingID: 4, |
|
table: fourByteTable |
|
}, |
|
// subtable 0, mac standard |
|
{ |
|
platformID: 1, |
|
encodingID: 0, |
|
table: singleByteTable |
|
}, |
|
// subtable 4, windows standard, identical to the unicode table |
|
{ |
|
platformID: 3, |
|
encodingID: 1, |
|
table: twoByteTable |
|
}, |
|
// subtable 12, windows ucs4 |
|
{ |
|
platformID: 3, |
|
encodingID: 10, |
|
table: fourByteTable |
|
} |
|
]; |
|
|
|
var tables = [ |
|
twoByteTable, |
|
singleByteTable, |
|
fourByteTable |
|
]; |
|
|
|
var tableOffset = (0 |
|
+ 2 // version |
|
+ 2 // number of subtable headers |
|
+ tableHeaders.length * TABLE_HEAD |
|
); |
|
|
|
// Calculate offsets for each table |
|
_.forEach(tables, function (table) { |
|
table._tableOffset = tableOffset; |
|
tableOffset += table.length; |
|
}); |
|
|
|
var length = tableOffset; |
|
|
|
var buffer = new ByteBuffer(length); |
|
|
|
// Write table header. |
|
buffer.writeUint16(0); // version |
|
buffer.writeUint16(tableHeaders.length); // count |
|
|
|
// Write subtable headers |
|
_.forEach(tableHeaders, function (header) { |
|
buffer.writeUint16(header.platformID); // platform |
|
buffer.writeUint16(header.encodingID); // encoding |
|
buffer.writeUint32(header.table._tableOffset); // offset |
|
}); |
|
|
|
// Write subtables |
|
_.forEach(tables, function (table) { |
|
buffer.writeBytes(table.buffer); |
|
}); |
|
|
|
return buffer; |
|
} |
|
|
|
module.exports = createCMapTable;
|
|
|