216 lines
7.5 KiB
Plaintext
216 lines
7.5 KiB
Plaintext
|
// 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,
|
||
|
};
|