UNPKG

svg2ttf

Version:

Converts SVG font to TTF font

305 lines (244 loc) 7.74 kB
'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