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.

305 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;