UNPKG

svg2ttf

Version:

Converts SVG font to TTF font

409 lines (307 loc) 9.17 kB
'use strict' // See documentation here: http://www.microsoft.com/typography/otspec/GSUB.htm var _ = require('lodash') var identifier = require('../utils.js').identifier var ByteBuffer = require('microbuffer') function createScript () { var scriptRecord = (0 + 2 + // Script DefaultLangSys Offset 2 // Script[0] LangSysCount (0) ) var langSys = (0 + 2 + // Script DefaultLangSys LookupOrder 2 + // Script DefaultLangSys ReqFeatureIndex 2 + // Script DefaultLangSys FeatureCount (0?) 2 // Script Optional Feature Index[0] ) var length = (0 + scriptRecord + langSys ) var buffer = new ByteBuffer(length) // Script Record // Offset to the start of langSys from the start of scriptRecord buffer.writeUint16(scriptRecord) // DefaultLangSys // Number of LangSys entries other than the default (none) buffer.writeUint16(0) // LangSys record (DefaultLangSys) // LookupOrder buffer.writeUint16(0) // ReqFeatureIndex -> only one required feature: all ligatures buffer.writeUint16(0) // Number of FeatureIndex values for this language system (excludes the required feature) buffer.writeUint16(1) // FeatureIndex for the first optional feature // Note: Adding the same feature to both the optional // and the required features is a clear violation of the spec // but it fixes IE not displaying the ligatures. // See http://partners.adobe.com/public/developer/opentype/index_table_formats.html, Section “Language System Table” // “FeatureCount: Number of FeatureIndex values for this language system-*excludes the required feature*” (emphasis added) buffer.writeUint16(0) return buffer } function createScriptList () { var scriptSize = (0 + 4 + // Tag 2 // Offset ) // tags should be arranged alphabetically var scripts = [ ['DFLT', createScript()], ['latn', createScript()] ] var header = (0 + 2 + // Script count scripts.length * scriptSize ) var tableLengths = _.reduce(_.map(scripts, function (script) { return script[1].length }), function (result, count) { return result + count }, 0) var length = (0 + header + tableLengths ) var buffer = new ByteBuffer(length) // Script count buffer.writeUint16(scripts.length) // Write all ScriptRecords var offset = header _.forEach(scripts, function (script) { var name = script[0] var table = script[1] // Script identifier (DFLT/latn) buffer.writeUint32(identifier(name)) // Offset to the ScriptRecord from start of the script list buffer.writeUint16(offset) // Increment offset by script table length offset += table.length }) // Write all ScriptTables _.forEach(scripts, function (script) { var table = script[1] buffer.writeBytes(table.buffer) }) return buffer } // Write one feature containing all ligatures function createFeatureList () { var header = (0 + 2 + // FeatureCount 4 + // FeatureTag[0] 2 // Feature Offset[0] ) var length = (0 + header + 2 + // FeatureParams[0] 2 + // LookupCount[0] 2 // Lookup[0] LookupListIndex[0] ) var buffer = new ByteBuffer(length) // FeatureCount buffer.writeUint16(1) // FeatureTag[0] buffer.writeUint32(identifier('liga')) // Feature Offset[0] buffer.writeUint16(header) // FeatureParams[0] buffer.writeUint16(0) // LookupCount[0] buffer.writeUint16(1) // Index into lookup table. Since we only have ligatures, the index is always 0 buffer.writeUint16(0) return buffer } function createLigatureCoverage (font, ligatureGroups) { var glyphCount = ligatureGroups.length var length = (0 + 2 + // CoverageFormat 2 + // GlyphCount 2 * glyphCount // GlyphID[i] ) var buffer = new ByteBuffer(length) // CoverageFormat buffer.writeUint16(1) // Length buffer.writeUint16(glyphCount) _.forEach(ligatureGroups, function (group) { buffer.writeUint16(group.startGlyph.id) }) return buffer } function createLigatureTable (font, ligature) { var allCodePoints = font.codePoints var unicode = ligature.unicode var length = (0 + 2 + // LigGlyph 2 + // CompCount 2 * (unicode.length - 1) ) var buffer = new ByteBuffer(length) // LigGlyph var glyph = ligature.glyph buffer.writeUint16(glyph.id) // CompCount buffer.writeUint16(unicode.length) // Compound glyphs (excluding first as it’s already in the coverage table) for (var i = 1; i < unicode.length; i++) { glyph = allCodePoints[unicode[i]] buffer.writeUint16(glyph.id) } return buffer } function createLigatureSet (font, codePoint, ligatures) { var ligatureTables = [] _.forEach(ligatures, function (ligature) { ligatureTables.push(createLigatureTable(font, ligature)) }) var tableLengths = _.reduce(_.map(ligatureTables, 'length'), function (result, count) { return result + count }, 0) var offset = (0 + 2 + // LigatureCount 2 * ligatures.length ) var length = (0 + offset + tableLengths ) var buffer = new ByteBuffer(length) // LigatureCount buffer.writeUint16(ligatures.length) // Ligature offsets _.forEach(ligatureTables, function (table) { // The offset to the current set, from SubstFormat buffer.writeUint16(offset) offset += table.length }) // Ligatures _.forEach(ligatureTables, function (table) { buffer.writeBytes(table.buffer) }) return buffer } function createLigatureList (font, ligatureGroups) { var sets = [] _.forEach(ligatureGroups, function (group) { var set = createLigatureSet(font, group.codePoint, group.ligatures) sets.push(set) }) var setLengths = _.reduce(_.map(sets, 'length'), function (result, count) { return result + count }, 0) var coverage = createLigatureCoverage(font, ligatureGroups) var tableOffset = (0 + 2 + // Lookup type 2 + // Lokup flag 2 + // SubTableCount 2 // SubTable[0] Offset ) var setOffset = (0 + 2 + // SubstFormat 2 + // Coverage offset 2 + // LigSetCount 2 * sets.length // LigSet Offsets ) var coverageOffset = setOffset + setLengths var length = (0 + tableOffset + coverageOffset + coverage.length ) var buffer = new ByteBuffer(length) // Lookup type 4 – ligatures buffer.writeUint16(4) // Lookup flag – empty buffer.writeUint16(0) // Subtable count buffer.writeUint16(1) // Subtable[0] offset buffer.writeUint16(tableOffset) // SubstFormat buffer.writeUint16(1) // Coverage buffer.writeUint16(coverageOffset) // LigSetCount buffer.writeUint16(sets.length) _.forEach(sets, function (set) { // The offset to the current set, from SubstFormat buffer.writeUint16(setOffset) setOffset += set.length }) _.forEach(sets, function (set) { buffer.writeBytes(set.buffer) }) buffer.writeBytes(coverage.buffer) return buffer } // Add a lookup for each ligature function createLookupList (font) { var ligatures = font.ligatures var groupedLigatures = {} // Group ligatures by first code point _.forEach(ligatures, function (ligature) { var first = ligature.unicode[0] if (!_.has(groupedLigatures, first)) { groupedLigatures[first] = [] } groupedLigatures[first].push(ligature) }) var ligatureGroups = [] _.forEach(groupedLigatures, function (ligatures, codePoint) { codePoint = parseInt(codePoint, 10) // Order ligatures by length, descending // “Ligatures with more components must be stored ahead of those with fewer components in order to be found” // From: http://partners.adobe.com/public/developer/opentype/index_tag7.html#liga ligatures.sort(function (ligA, ligB) { return ligB.unicode.length - ligA.unicode.length }) ligatureGroups.push({ codePoint: codePoint, ligatures: ligatures, startGlyph: font.codePoints[codePoint] }) }) ligatureGroups.sort(function (a, b) { return a.startGlyph.id - b.startGlyph.id }) var offset = (0 + 2 + // Lookup count 2 // Lookup[0] offset ) var set = createLigatureList(font, ligatureGroups) var length = (0 + offset + set.length ) var buffer = new ByteBuffer(length) // Lookup count buffer.writeUint16(1) // Lookup[0] offset buffer.writeUint16(offset) // Lookup[0] buffer.writeBytes(set.buffer) return buffer } function createGSUB (font) { var scriptList = createScriptList() var featureList = createFeatureList() var lookupList = createLookupList(font) var lists = [scriptList, featureList, lookupList] var offset = (0 + 4 + // Version 2 * lists.length // List offsets ) // Calculate offsets _.forEach(lists, function (list) { list._listOffset = offset offset += list.length }) var length = offset var buffer = new ByteBuffer(length) // Version buffer.writeUint32(0x00010000) // Offsets _.forEach(lists, function (list) { buffer.writeUint16(list._listOffset) }) // List contents _.forEach(lists, function (list) { buffer.writeBytes(list.buffer) }) return buffer } module.exports = createGSUB