// Table metadata import check from './check'; import { encode, sizeOf } from './types'; /** * @exports opentype.Table * @class * @param {string} tableName * @param {Array} fields * @param {Object} options * @constructor */ function Table(tableName, fields, options) { // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, // as this will result in wrong encoding order of the coverage data on serialization to bytes. // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { for (let i = 0; i < fields.length; i += 1) { const field = fields[i]; this[field.name] = field.value; } } this.tableName = tableName; this.fields = fields; if (options) { const optionKeys = Object.keys(options); for (let i = 0; i < optionKeys.length; i += 1) { const k = optionKeys[i]; const v = options[k]; if (this[k] !== undefined) { this[k] = v; } } } } /** * Encodes the table and returns an array of bytes * @return {Array} */ Table.prototype.encode = function() { return encode.TABLE(this); }; /** * Get the size of the table. * @return {number} */ Table.prototype.sizeOf = function() { return sizeOf.TABLE(this); }; /** * @private */ function ushortList(itemName, list, count) { if (count === undefined) { count = list.length; } const fields = new Array(list.length + 1); fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; for (let i = 0; i < list.length; i++) { fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; } return fields; } /** * @private */ function tableList(itemName, records, itemCallback) { const count = records.length; const fields = new Array(count + 1); fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; for (let i = 0; i < count; i++) { fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; } return fields; } /** * @private */ function recordList(itemName, records, itemCallback) { const count = records.length; let fields = []; fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; for (let i = 0; i < count; i++) { fields = fields.concat(itemCallback(records[i], i)); } return fields; } // Common Layout Tables /** * @exports opentype.Coverage * @class * @param {opentype.Table} * @constructor * @extends opentype.Table */ function Coverage(coverageTable) { if (coverageTable.format === 1) { Table.call(this, 'coverageTable', [{name: 'coverageFormat', type: 'USHORT', value: 1}] .concat(ushortList('glyph', coverageTable.glyphs)) ); } else if (coverageTable.format === 2) { Table.call(this, 'coverageTable', [{name: 'coverageFormat', type: 'USHORT', value: 2}] .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { return [ {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index}, ]; })) ); } else { check.assert(false, 'Coverage format must be 1 or 2.'); } } Coverage.prototype = Object.create(Table.prototype); Coverage.prototype.constructor = Coverage; function ScriptList(scriptListTable) { Table.call(this, 'scriptListTable', recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { const script = scriptRecord.script; let defaultLangSys = script.defaultLangSys; check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); return [ {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ {name: 'lookupOrder', type: 'USHORT', value: 0}, {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { const langSys = langSysRecord.langSys; return [ {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ {name: 'lookupOrder', type: 'USHORT', value: 0}, {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} ].concat(ushortList('featureIndex', langSys.featureIndexes)))} ]; })))} ]; }) ); } ScriptList.prototype = Object.create(Table.prototype); ScriptList.prototype.constructor = ScriptList; /** * @exports opentype.FeatureList * @class * @param {opentype.Table} * @constructor * @extends opentype.Table */ function FeatureList(featureListTable) { Table.call(this, 'featureListTable', recordList('featureRecord', featureListTable, function(featureRecord, i) { const feature = featureRecord.feature; return [ {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ {name: 'featureParams', type: 'USHORT', value: feature.featureParams}, ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} ]; }) ); } FeatureList.prototype = Object.create(Table.prototype); FeatureList.prototype.constructor = FeatureList; /** * @exports opentype.LookupList * @class * @param {opentype.Table} * @param {Object} * @constructor * @extends opentype.Table */ function LookupList(lookupListTable, subtableMakers) { Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { let subtableCallback = subtableMakers[lookupTable.lookupType]; check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); return new Table('lookupTable', [ {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); })); } LookupList.prototype = Object.create(Table.prototype); LookupList.prototype.constructor = LookupList; // Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) // Don't use offsets inside Records (probable bug), only in Tables. export default { Table, Record: Table, Coverage, ScriptList, FeatureList, LookupList, ushortList, tableList, recordList, };