diff --git a/eslint-local-rules.js b/eslint-local-rules.js index fdcc8538..66a3d4f9 100644 --- a/eslint-local-rules.js +++ b/eslint-local-rules.js @@ -1,6 +1,30 @@ 'use strict'; module.exports = { + 'ban-concat': { + meta: { + type: 'suggestion', + schema: [], + }, + create(context) { + return { + CallExpression(node) { + if ( + ( + node.callee.property && + node.callee.property.name === 'concat' && + node.callee?.object?.name !== 'Buffer' + ) || ( + node.callee?.object?.property?.name === 'concat' && + node.callee?.object?.object?.type === 'ArrayExpression' + ) + ) { + context.report({node, message: 'Use obj.push(...data) instead of obj = obj.concat(data)'}) + } + }, + } + }, + }, 'ban-foreach': { meta: { type: 'suggestion', diff --git a/package.json b/package.json index 08ff3227..d00732ec 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "error", "always" ], + "local-rules/ban-concat": 2, "local-rules/ban-foreach": 2, "local-rules/import-extensions": 2 } diff --git a/src/bidi.js b/src/bidi.js index 7d98c665..95254ea0 100644 --- a/src/bidi.js +++ b/src/bidi.js @@ -101,8 +101,7 @@ Bidi.prototype.registerFeatures = function (script, tags) { if (!Object.prototype.hasOwnProperty.call(this.featuresTags, script)) { this.featuresTags[script] = supportedTags; } else { - this.featuresTags[script] = - this.featuresTags[script].concat(supportedTags); + this.featuresTags[script].push(...supportedTags); } }; diff --git a/src/features/arab/arabicPresentationForms.js b/src/features/arab/arabicPresentationForms.js index 7d343a5b..aa6ef63a 100644 --- a/src/features/arab/arabicPresentationForms.js +++ b/src/features/arab/arabicPresentationForms.js @@ -12,7 +12,8 @@ import applySubstitution from '../applySubstitution.js'; * @param {ContextParams} charContextParams context params of a char */ function willConnectPrev(charContextParams) { - let backtrack = [].concat(charContextParams.backtrack); + let backtrack = [...charContextParams.backtrack]; + for (let i = backtrack.length - 1; i >= 0; i--) { const prevChar = backtrack[i]; const isolated = isIsolatedArabicChar(prevChar); diff --git a/src/features/featureQuery.js b/src/features/featureQuery.js index 93614a18..20b3d368 100644 --- a/src/features/featureQuery.js +++ b/src/features/featureQuery.js @@ -128,7 +128,7 @@ function chainingSubstitutionFormat3(contextParams, subtable) { subtable.lookaheadCoverage, lookaheadParams ); // BACKTRACK LOOKUP // - let backtrackContext = [].concat(contextParams.backtrack); + let backtrackContext = [...contextParams.backtrack]; backtrackContext.reverse(); while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { backtrackContext.shift(); @@ -375,7 +375,7 @@ FeatureQuery.prototype.lookupFeature = function (query) { `for script '${query.script}'.` ); const lookups = this.getFeatureLookups(feature); - const substitutions = [].concat(contextParams.context); + const substitutions = [...contextParams.context]; for (let l = 0; l < lookups.length; l++) { const lookupTable = lookups[l]; const subtables = this.getLookupSubtables(lookupTable); diff --git a/src/path.js b/src/path.js index b29c3d0d..9959acfb 100644 --- a/src/path.js +++ b/src/path.js @@ -76,8 +76,7 @@ function optimizeCommands(commands) { } } } - commands = [].concat.apply([], subpaths); // flatten again - return commands; + return [...subpaths.flat()]; // flatten again } /** diff --git a/src/substitution.js b/src/substitution.js index 4774ecf7..396390ec 100644 --- a/src/substitution.js +++ b/src/substitution.js @@ -161,7 +161,7 @@ Substitution.prototype.getLigatures = function(feature, script, language) { for (let k = 0; k < ligSet.length; k++) { const lig = ligSet[k]; ligatures.push({ - sub: [startGlyph].concat(lig.components), + sub: [startGlyph, ...lig.components], by: lig.ligGlyph }); } @@ -309,15 +309,19 @@ Substitution.prototype.getFeature = function(feature, script, language) { switch (feature) { case 'aalt': case 'salt': - return this.getSingle(feature, script, language) - .concat(this.getAlternates(feature, script, language)); + return [ + ...this.getSingle(feature, script, language), + ...this.getAlternates(feature, script, language) + ]; case 'dlig': case 'liga': case 'rlig': return this.getLigatures(feature, script, language); case 'ccmp': - return this.getMultiple(feature, script, language) - .concat(this.getLigatures(feature, script, language)); + return [ + ...this.getMultiple(feature, script, language), + ...this.getLigatures(feature, script, language) + ]; case 'stch': return this.getMultiple(feature, script, language); } diff --git a/src/table.js b/src/table.js index 6e10ffff..603ba233 100644 --- a/src/table.js +++ b/src/table.js @@ -85,7 +85,7 @@ function recordList(itemName, records, itemCallback) { 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)); + fields.push(...itemCallback(records[i], i)); } return fields; } @@ -101,21 +101,19 @@ function recordList(itemName, records, itemCallback) { */ function Coverage(coverageTable) { if (coverageTable.format === 1) { - Table.call(this, 'coverageTable', - [{name: 'coverageFormat', type: 'USHORT', value: 1}] - .concat(ushortList('glyph', coverageTable.glyphs)) - ); + Table.call(this, 'coverageTable', [ + {name: 'coverageFormat', type: 'USHORT', value: 1}, + ...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, i) { - return [ - {name: 'startGlyphID' + i, type: 'USHORT', value: RangeRecord.start}, - {name: 'endGlyphID' + i, type: 'USHORT', value: RangeRecord.end}, - {name: 'startCoverageIndex' + i, type: 'USHORT', value: RangeRecord.index}, - ]; - })) - ); + Table.call(this, 'coverageTable', [ + {name: 'coverageFormat', type: 'USHORT', value: 2}, + ...recordList('rangeRecord', coverageTable.ranges, (RangeRecord, i) => [ + {name: 'startGlyphID' + i, type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID' + i, type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex' + i, type: 'USHORT', value: RangeRecord.index}, + ]) + ]); } else { check.assert(false, 'Coverage format must be 1 or 2.'); } @@ -134,19 +132,22 @@ function ScriptList(scriptListTable) { {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)))} - ]; - })))} - ]; + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}, + ...ushortList('featureIndex', defaultLangSys.featureIndexes) + ])}, + ...recordList('langSys', script.langSysRecords, (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}, + ...ushortList('featureIndex', langSys.featureIndexes) + ])} + ]; + }) + ])} + ]; }) ); } @@ -168,8 +169,9 @@ function FeatureList(featureListTable) { {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)))} - ]; + ...ushortList('lookupListIndex', feature.lookupListIndexes) + ])} + ]; }) ); } @@ -190,8 +192,9 @@ function LookupList(lookupListTable, subtableMakers) { 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))); + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag}, + ...tableList('subtable', lookupTable.subtables, subtableCallback) + ]); })); } LookupList.prototype = Object.create(Table.prototype); @@ -209,24 +212,21 @@ LookupList.prototype.constructor = LookupList; */ function ClassDef(classDefTable) { if (classDefTable.format === 1) { - Table.call(this, 'classDefTable', - [ - {name: 'classFormat', type: 'USHORT', value: 1}, - {name: 'startGlyphID', type: 'USHORT', value: classDefTable.startGlyph} - ] - .concat(ushortList('glyph', classDefTable.classes)) - ); + Table.call(this, 'classDefTable', [ + {name: 'classFormat', type: 'USHORT', value: 1}, + {name: 'startGlyphID', type: 'USHORT', value: classDefTable.startGlyph}, + ...ushortList('glyph', classDefTable.classes) + ]); } else if (classDefTable.format === 2) { - Table.call(this, 'classDefTable', - [{name: 'classFormat', type: 'USHORT', value: 2}] - .concat(recordList('rangeRecord', classDefTable.ranges, function(RangeRecord, i) { - return [ - {name: 'startGlyphID' + i, type: 'USHORT', value: RangeRecord.start}, - {name: 'endGlyphID' + i, type: 'USHORT', value: RangeRecord.end}, - {name: 'class' + i, type: 'USHORT', value: RangeRecord.classId}, - ]; - })) - ); + Table.call(this, 'classDefTable', [ + {name: 'classFormat', type: 'USHORT', value: 2}, + ...recordList('rangeRecord', classDefTable.ranges, (RangeRecord, i) => [ + {name: 'startGlyphID' + i, type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID' + i, type: 'USHORT', value: RangeRecord.end}, + {name: 'class' + i, type: 'USHORT', value: RangeRecord.classId}, + ]) + ]); + } else { check.assert(false, 'Class format must be 1 or 2.'); } diff --git a/src/tables/avar.js b/src/tables/avar.js index fe1f934c..7d00ee0f 100644 --- a/src/tables/avar.js +++ b/src/tables/avar.js @@ -20,10 +20,10 @@ function makeAvarSegmentMap(n, axis) { let axisValueMaps = []; for (let i = 0; i < axis.axisValueMaps.length; i++) { const valueMap = makeAvarAxisValueMap(`${n}_${i}`, axis.axisValueMaps[i]); - axisValueMaps = axisValueMaps.concat(valueMap.fields); + axisValueMaps.push(...valueMap.fields); } - returnTable.fields = returnTable.fields.concat(axisValueMaps); + returnTable.fields.push(...axisValueMaps); return returnTable; } @@ -40,7 +40,7 @@ function makeAvarTable(avar, fvar) { for (let i = 0; i < avar.axisSegmentMaps.length; i++) { const axisRecord = makeAvarSegmentMap(i, avar.axisSegmentMaps[i]); - result.fields = result.fields.concat(axisRecord.fields); + result.fields.push(...axisRecord.fields); } return result; diff --git a/src/tables/fvar.js b/src/tables/fvar.js index 058d137f..b859221e 100644 --- a/src/tables/fvar.js +++ b/src/tables/fvar.js @@ -99,11 +99,11 @@ function makeFvarTable(fvar, names) { result.offsetToData = result.sizeOf(); for (let i = 0; i < fvar.axes.length; i++) { - result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + result.fields.push(...makeFvarAxis(i, fvar.axes[i], names)); } for (let j = 0; j < fvar.instances.length; j++) { - result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + result.fields.push(...makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); } return result; diff --git a/src/tables/glyf.js b/src/tables/glyf.js index dded5951..cb09963d 100644 --- a/src/tables/glyf.js +++ b/src/tables/glyf.js @@ -290,7 +290,7 @@ function buildPath(glyphs, glyph) { transform.dy = firstPt.y - secondPt.y; transformedPoints = transformPoints(componentGlyph.points, transform); } - glyph.points = glyph.points.concat(transformedPoints); + glyph.points.push(...transformedPoints); } } } diff --git a/src/tables/gsub.js b/src/tables/gsub.js index 3fb27276..3c0ec773 100644 --- a/src/tables/gsub.js +++ b/src/tables/gsub.js @@ -224,8 +224,9 @@ subtableMakers[1] = function makeLookup1(subtable) { } else if (subtable.substFormat === 2) { return new table.Table('substitutionTable', [ {name: 'substFormat', type: 'USHORT', value: 2}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.ushortList('substitute', subtable.substitute))); + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + ...table.ushortList('substitute', subtable.substitute) + ]); } check.fail('Lookup type 1 substFormat must be 1 or 2.'); }; @@ -234,84 +235,95 @@ subtableMakers[2] = function makeLookup2(subtable) { check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); return new table.Table('substitutionTable', [ {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { - return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); - }))); + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + ...table.tableList('seqSet', subtable.sequences, sequenceSet => + new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)) + ) + ]); }; subtableMakers[3] = function makeLookup3(subtable) { check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); return new table.Table('substitutionTable', [ {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { - return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); - }))); + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + ...table.tableList('altSet', subtable.alternateSets, alternateSet => + new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)) + ) + ]); }; subtableMakers[4] = function makeLookup4(subtable) { check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); return new table.Table('substitutionTable', [ {name: 'substFormat', type: 'USHORT', value: 1}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { - return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { - return new table.Table('ligatureTable', - [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] - .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) - ); - })); - }))); + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + ...table.tableList('ligSet', subtable.ligatureSets, ligatureSet => + new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, ligature => + new table.Table('ligatureTable', [ + {name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}, + ...table.ushortList('component', ligature.components, ligature.components.length + 1) + ]) + )) + ) + ]); }; subtableMakers[5] = function makeLookup5(subtable) { if (subtable.substFormat === 1) { return new table.Table('contextualSubstitutionTable', [ {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, - {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('sequenceRuleSet', subtable.ruleSets, function(sequenceRuleSet) { - if (!sequenceRuleSet) { - return new table.Table('NULL', null); - } - return new table.Table('sequenceRuleSetTable', table.tableList('sequenceRule', sequenceRuleSet, function(sequenceRule) { - let tableData = table.ushortList('seqLookup', [], sequenceRule.lookupRecords.length) - .concat(table.ushortList('inputSequence', sequenceRule.input, sequenceRule.input.length + 1)); - - // swap the first two elements, because inputSequenceCount - // ("glyphCount" in the spec) comes before seqLookupCount - [tableData[0], tableData[1]] = [tableData[1], tableData[0]]; - - for(let i = 0; i < sequenceRule.lookupRecords.length; i++) { - const record = sequenceRule.lookupRecords[i]; - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + ...table.tableList('sequenceRuleSet', subtable.ruleSets, sequenceRuleSet => { + if (!sequenceRuleSet) { + return new table.Table('NULL', null); } - return new table.Table('sequenceRuleTable', tableData); - })); - }))); + return new table.Table('sequenceRuleSetTable', table.tableList('sequenceRule', sequenceRuleSet, sequenceRule => { + let tableData = [ + ...table.ushortList('seqLookup', [], sequenceRule.lookupRecords.length), + ...table.ushortList('inputSequence', sequenceRule.input, sequenceRule.input.length + 1) + ]; + + // swap the first two elements, because inputSequenceCount + // ("glyphCount" in the spec) comes before seqLookupCount + [tableData[0], tableData[1]] = [tableData[1], tableData[0]]; + + for (let i = 0; i < sequenceRule.lookupRecords.length; i++) { + const record = sequenceRule.lookupRecords[i]; + tableData.push( + {name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}, + {name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex} + ); + } + return new table.Table('sequenceRuleTable', tableData); + })); + }) + ]); } else if (subtable.substFormat === 2) { return new table.Table('contextualSubstitutionTable', [ {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, - {name: 'classDef', type: 'TABLE', value: new table.ClassDef(subtable.classDef)} - ].concat(table.tableList('classSeqRuleSet', subtable.classSets, function(classSeqRuleSet) { - if (!classSeqRuleSet) { - return new table.Table('NULL', null); - } - return new table.Table('classSeqRuleSetTable', table.tableList('classSeqRule', classSeqRuleSet, function(classSeqRule) { - let tableData = table.ushortList('classes', classSeqRule.classes, classSeqRule.classes.length + 1) - .concat(table.ushortList('seqLookupCount', [], classSeqRule.lookupRecords.length)); - for(let i = 0; i < classSeqRule.lookupRecords.length; i++) { - const record = classSeqRule.lookupRecords[i]; - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + {name: 'classDef', type: 'TABLE', value: new table.ClassDef(subtable.classDef)}, + ...table.tableList('classSeqRuleSet', subtable.classSets, classSeqRuleSet => { + if (!classSeqRuleSet) { + return new table.Table('NULL', null); } - return new table.Table('classSeqRuleTable', tableData); - })); - }))); + return new table.Table('classSeqRuleSetTable', table.tableList('classSeqRule', classSeqRuleSet, classSeqRule => { + let tableData = [ + ...table.ushortList('classes', classSeqRule.classes, classSeqRule.classes.length + 1), + ...table.ushortList('seqLookupCount', [], classSeqRule.lookupRecords.length) + ]; + for (let i = 0; i < classSeqRule.lookupRecords.length; i++) { + const record = classSeqRule.lookupRecords[i]; + tableData.push( + {name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}, + {name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex} + ); + } + return new table.Table('classSeqRuleTable', tableData); + })); + }) + ]); } else if (subtable.substFormat === 3) { let tableData = [ {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, @@ -326,9 +338,10 @@ subtableMakers[5] = function makeLookup5(subtable) { for(let i = 0; i < subtable.lookupRecords.length; i++) { const record = subtable.lookupRecords[i]; - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + tableData.push( + {name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}, + {name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex} + ); } let returnTable = new table.Table('contextualSubstitutionTable', tableData); @@ -344,22 +357,26 @@ subtableMakers[6] = function makeLookup6(subtable) { let returnTable = new table.Table('chainContextTable', [ {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} - ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { - return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { - let tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) - .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) - .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) - .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); - - for(let i = 0; i < chainRule.lookupRecords.length; i++) { + ]); + returnTable.fields.push(...table.tableList('chainRuleSet', subtable.chainRuleSets, chainRuleSet => + new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, chainRule => { + let tableData = [ + ...table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length), + ...table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1), + ...table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length), + ...table.ushortList('substitution', [], chainRule.lookupRecords.length) + ]; + + for (let i = 0; i < chainRule.lookupRecords.length; i++) { const record = chainRule.lookupRecords[i]; - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + tableData.push( + {name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}, + {name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex} + ); } return new table.Table('chainRuleTable', tableData); - })); - }))); + })) + )); return returnTable; } else if (subtable.substFormat === 2) { check.assert(false, 'lookup type 6 format 2 is not yet supported.'); @@ -389,9 +406,10 @@ subtableMakers[6] = function makeLookup6(subtable) { tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); for(let i = 0; i < subtable.lookupRecords.length; i++) { const record = subtable.lookupRecords[i]; - tableData = tableData - .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) - .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + tableData.push( + {name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}, + {name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex} + ); } let returnTable = new table.Table('chainContextTable', tableData); diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 8abdb844..ac0dee02 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -104,8 +104,7 @@ function makeSfntTable(tables) { } }); - sfnt.fields = sfnt.fields.concat(recordFields); - sfnt.fields = sfnt.fields.concat(tableFields); + sfnt.fields.push(...recordFields, ...tableFields); return sfnt; } diff --git a/src/tables/stat.js b/src/tables/stat.js index c4c2a1c9..aed2708c 100644 --- a/src/tables/stat.js +++ b/src/tables/stat.js @@ -180,10 +180,10 @@ axisValueMakers[4] = function axisValueMaker4(n, table) { ]; for (let i = 0; i < table.axisValues.length; i++) { - returnFields = returnFields.concat([ + returnFields.push( {name: `format${n}axisIndex${i}`, type: 'USHORT', value: table.axisValues[i].axisIndex}, {name: `format${n}value${i}`, type: 'FLOAT', value: table.axisValues[i].value}, - ]); + ); } return returnFields; @@ -222,7 +222,7 @@ function makeSTATTable(STAT) { for (let i = 0; i < STAT.axes.length; i++) { const axisRecord = makeSTATAxisRecord(i, STAT.axes[i]); result.offsetToAxisValueOffsets += axisRecord.sizeOf(); - result.fields = result.fields.concat(axisRecord.fields); + result.fields.push(...axisRecord.fields); } const axisValueOffsets = []; @@ -237,11 +237,10 @@ function makeSTATTable(STAT) { value: axisValueTableOffset }); axisValueTableOffset += axisValueTable.sizeOf(); - axisValueTables = axisValueTables.concat(axisValueTable.fields); + axisValueTables.push(...axisValueTable.fields); } - result.fields = result.fields.concat(axisValueOffsets); - result.fields = result.fields.concat(axisValueTables); + result.fields.push(...axisValueOffsets, ...axisValueTables); return result; } diff --git a/src/tokenizer.js b/src/tokenizer.js index ab5e64dc..586939f9 100644 --- a/src/tokenizer.js +++ b/src/tokenizer.js @@ -154,7 +154,7 @@ Tokenizer.prototype.inboundIndex = function(index) { Tokenizer.prototype.composeRUD = function (RUDs) { const silent = true; const state = RUDs.map(RUD => ( - this[RUD[0]].apply(this, RUD.slice(1).concat(silent)) + this[RUD[0]].apply(this, [...RUD.slice(1), silent]) )); const hasFAILObject = obj => ( typeof obj === 'object' && @@ -181,7 +181,7 @@ Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) const isTokenType = tokens.every(token => token instanceof Token); if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { const replaced = this.tokens.splice.apply( - this.tokens, [startIndex, offset].concat(tokens) + this.tokens, [startIndex, offset, ...tokens] ); if (!silent) this.dispatch('replaceToken', [startIndex, offset, tokens]); return [replaced, tokens]; @@ -246,8 +246,8 @@ Tokenizer.prototype.insertToken = function (tokens, index, silent) { ); if (tokenType) { this.tokens.splice.apply( - this.tokens, [index, 0].concat(tokens) - ); + this.tokens, [index, 0, ...tokens] + ); if (!silent) this.dispatch('insertToken', [tokens, index]); return tokens; } else { @@ -420,10 +420,10 @@ Tokenizer.prototype.registerContextChecker = function(contextName, contextStartC */ Tokenizer.prototype.getRangeTokens = function(range) { const endIndex = range.startIndex + range.endOffset; - return [].concat( - this.tokens - .slice(range.startIndex, endIndex) - ); + return [ + ...this.tokens.slice(range.startIndex, endIndex) + ]; + }; /** diff --git a/src/types.js b/src/types.js index 1ded9b37..7d70b414 100644 --- a/src/types.js +++ b/src/types.js @@ -753,10 +753,12 @@ encode.INDEX = function(l) { Array.prototype.push.apply(encodedOffsets, encodedOffset); } - return Array.prototype.concat(encode.Card16(l.length), - encode.OffSize(offSize), - encodedOffsets, - data); + return [ + ...encode.Card16(l.length), + ...encode.OffSize(offSize), + ...encodedOffsets, + ...data + ]; }; /** diff --git a/test/fonts/LICENSE b/test/fonts/LICENSE index 4fad27ee..a3eede5d 100644 --- a/test/fonts/LICENSE +++ b/test/fonts/LICENSE @@ -26,6 +26,12 @@ Jomhuria-Regular.ttf SIL Open Font License, Version 1.1. https://www.fontsquirrel.com/license/jomhuria +notosanssc-bold.ttf +NotoSansThai-Medium-Testing-v1.ttf + Copyright 2012 Google Inc. All Rights Reserved. + SIL Open Font License v1.10 + https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL + OpenMojiCOLORv0-subset.ttf All emojis designed by OpenMoji – the open-source emoji and icon project. Creative Commons Share Alike License 4.0 (CC BY-SA 4.0) diff --git a/test/fonts/notosanssc-bold.ttf b/test/fonts/notosanssc-bold.ttf new file mode 100644 index 00000000..6e4b4684 Binary files /dev/null and b/test/fonts/notosanssc-bold.ttf differ diff --git a/test/performance.js b/test/performance.js new file mode 100644 index 00000000..7e9be2e1 --- /dev/null +++ b/test/performance.js @@ -0,0 +1,17 @@ +import assert from 'assert'; +import { parse } from '../src/opentype.js'; +import { readFileSync } from 'fs'; +import { performance } from 'perf_hooks'; +const loadSync = (url, opt) => parse(readFileSync(url), opt); + +describe('performance tests', function() { + const notoSansSC = loadSync('./test/fonts/notosanssc-bold.ttf'); + + it('should not take too long to use toArrayBuffer() on large fonts', function() { + const start = performance.now(); + const time = performance.now() - start; + notoSansSC.toArrayBuffer(); + assert(time < 16000, true); + }).timeout(16000); +}); +