diff --git a/testsuite/tests/util/FunctionList.test.ts b/testsuite/tests/util/FunctionList.test.ts index 52f3d2307..901cb8d1b 100644 --- a/testsuite/tests/util/FunctionList.test.ts +++ b/testsuite/tests/util/FunctionList.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from '@jest/globals'; -import {FunctionList} from '#js/util/FunctionList.js'; +import {FunctionList, AnyFunction} from '#js/util/FunctionList.js'; // // Set up a function list with 6 functions that captures output @@ -43,6 +43,15 @@ describe('FunctionList functionality', () => { expect(item).toBe(fn); }); + test('Adding a list of items', () => { + const fns = [(_: any) => {}, [(_: any) => {}, 1]] as [AnyFunction, [AnyFunction, number]]; + const list = new FunctionList(fns); + expect(Array.from(list)).toEqual([ + {item: fns[1][0], priority: 1}, + {item: fns[0], priority: 5}, + ]); + }); + test('Removing one item', () => { const list = new FunctionList(); const fn = list.add(FN(0)); diff --git a/ts/core/InputJax.ts b/ts/core/InputJax.ts index 1d41c18ac..7d42d14a3 100644 --- a/ts/core/InputJax.ts +++ b/ts/core/InputJax.ts @@ -129,7 +129,10 @@ export abstract class AbstractInputJax implements InputJax { /** * The default options for the input jax */ - public static OPTIONS: OptionList = {}; + public static OPTIONS: OptionList = { + preFilters: [], + postFilters: [], + }; /** * The actual options supplied to the input jax @@ -163,8 +166,8 @@ export abstract class AbstractInputJax implements InputJax { constructor(options: OptionList = {}) { const CLASS = this.constructor as typeof AbstractInputJax; this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options); - this.preFilters = new FunctionList(); - this.postFilters = new FunctionList(); + this.preFilters = new FunctionList(this.options.preFilters); + this.postFilters = new FunctionList(this.options.postFilters); } /** diff --git a/ts/core/OutputJax.ts b/ts/core/OutputJax.ts index b052a1d82..58e65c4f2 100644 --- a/ts/core/OutputJax.ts +++ b/ts/core/OutputJax.ts @@ -47,7 +47,12 @@ export interface OutputJax { options: OptionList; /** - * Lists of post-filters to call after typesetting the math + * Lists of pre-filters to call after typesetting the math + */ + preFilters: FunctionList; + + /** + * Lists of post-filters to call before typesetting the math */ postFilters: FunctionList; @@ -130,13 +135,21 @@ export abstract class AbstractOutputJax implements OutputJax { /** * The default options for the output jax */ - public static OPTIONS: OptionList = {}; + public static OPTIONS: OptionList = { + preFilters: [], + postFilters: [], + }; /** * The actual options supplied to the output jax */ public options: OptionList; + /** + * Filters to run before the output is processed + */ + public preFilters: FunctionList; + /** * Filters to run after the output is processed */ @@ -153,7 +166,8 @@ export abstract class AbstractOutputJax implements OutputJax { constructor(options: OptionList = {}) { const CLASS = this.constructor as typeof AbstractOutputJax; this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options); - this.postFilters = new FunctionList(); + this.preFilters = new FunctionList(this.options.preFilters); + this.postFilters = new FunctionList(this.options.postFilters); } /** diff --git a/ts/input/mathml.ts b/ts/input/mathml.ts index 6f71a4162..e796da765 100644 --- a/ts/input/mathml.ts +++ b/ts/input/mathml.ts @@ -57,6 +57,7 @@ export class MathML extends AbstractInputJax { public static OPTIONS: OptionList = defaultOptions({ parseAs: 'html', // Whether to use HTML or XML parsing for the MathML string forceReparse: false, // Whether to force the string to be reparsed, or use the one from the document DOM + mmlFilters: [], // Filters to add to the mmlFilters lost FindMathML: null, // The FindMathML instance to override the default one MathMLCompile: null, // The MathMLCompile instance to override the default one /* @@ -92,11 +93,10 @@ export class MathML extends AbstractInputJax { MathMLCompile.OPTIONS ); super(mml); - this.findMathML = - this.options['FindMathML'] || new FindMathML(find); + this.findMathML = this.options.FindMathML || new FindMathML(find); this.mathml = - this.options['MathMLCompile'] || new MathMLCompile(compile); - this.mmlFilters = new FunctionList(); + this.options.MathMLCompile || new MathMLCompile(compile); + this.mmlFilters = new FunctionList(this.options.mmlFilters); } /** diff --git a/ts/input/tex.ts b/ts/input/tex.ts index f3d488c42..54d4658a1 100644 --- a/ts/input/tex.ts +++ b/ts/input/tex.ts @@ -157,13 +157,15 @@ export class TeX extends AbstractInputJax { userOptions(parseOptions.options, rest); configuration.config(this); TeX.tags(parseOptions, configuration); - this.postFilters.add(FilterUtil.cleanSubSup, -7); - this.postFilters.add(FilterUtil.setInherited, -6); - this.postFilters.add(FilterUtil.checkScriptlevel, -5); - this.postFilters.add(FilterUtil.moveLimits, -4); - this.postFilters.add(FilterUtil.cleanStretchy, -3); - this.postFilters.add(FilterUtil.cleanAttributes, -2); - this.postFilters.add(FilterUtil.combineRelations, -1); + this.postFilters.addList([ + [FilterUtil.cleanSubSup, -7], + [FilterUtil.setInherited, -6], + [FilterUtil.checkScriptlevel, -5], + [FilterUtil.moveLimits, -4], + [FilterUtil.cleanStretchy, -3], + [FilterUtil.cleanAttributes, -2], + [FilterUtil.combineRelations, -1], + ]); } /** diff --git a/ts/output/common.ts b/ts/output/common.ts index dc95812a5..b8d3c760e 100644 --- a/ts/output/common.ts +++ b/ts/output/common.ts @@ -414,6 +414,7 @@ export abstract class CommonOutputJax< this.math = math; this.container = node; this.pxPerEm = math.metrics.ex / this.font.params.x_height; + this.executeFilters(this.preFilters, math, html, node); this.nodeMap = new Map(); math.root.attributes.getAllInherited().overflow = this.options.displayOverflow; diff --git a/ts/util/FunctionList.ts b/ts/util/FunctionList.ts index 6b6660d7d..e6a740b22 100644 --- a/ts/util/FunctionList.ts +++ b/ts/util/FunctionList.ts @@ -23,7 +23,9 @@ import { PrioritizedList, PrioritizedListItem } from './PrioritizedList.js'; -type AnyFunction = (...args: unknown[]) => unknown; +export type AnyFunction = (...args: unknown[]) => unknown; +export type AnyFunctionDef = AnyFunction | [AnyFunction, number]; +export type AnyFunctionList = AnyFunctionDef[]; /*****************************************************************/ /** @@ -38,6 +40,32 @@ export interface FunctionListItem extends PrioritizedListItem {} */ export class FunctionList extends PrioritizedList { + /** + * @override + * @param {AnyFunctionList} list The initial list of functions to add + */ + constructor(list: AnyFunctionList = null) { + super(); + if (list) { + this.addList(list); + } + } + + /** + * Add a list of filter functions, possibly with priorities. + * + * @param {AnyFunctionList} list The list of functions to add + */ + public addList(list: AnyFunctionList) { + for (const item of list) { + if (Array.isArray(item)) { + this.add(item[0], item[1]); + } else { + this.add(item); + } + } + } + /** * Executes the functions in the list (in prioritized order), * passing the given data to the functions. If any return