From 8f43fb6e1f43c440ac826b844564acc77e7789c7 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 27 Aug 2023 14:23:29 -0400 Subject: [PATCH] Set TeX class OP for multi-letter mo elements, as in v2. (mathjax/MathJax#3095) --- ts/core/MmlTree/MmlNodes/mo.ts | 70 +++++++++++++++----------- ts/core/MmlTree/OperatorDictionary.ts | 8 +-- ts/input/tex/base/BaseConfiguration.ts | 4 +- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/ts/core/MmlTree/MmlNodes/mo.ts b/ts/core/MmlTree/MmlNodes/mo.ts index 4bfa37044..70c39fdb0 100644 --- a/ts/core/MmlTree/MmlNodes/mo.ts +++ b/ts/core/MmlTree/MmlNodes/mo.ts @@ -25,7 +25,7 @@ import {PropertyList} from '../../Tree/Node.js'; import {AbstractMmlTokenNode, MmlNode, AttributeList, TEXCLASS} from '../MmlNode.js'; import {MmlMrow} from './mrow.js'; import {MmlMover, MmlMunder, MmlMunderover} from './munderover.js'; -import {OperatorList, OPTABLE, getRange, MMLSPACING} from '../OperatorDictionary.js'; +import {OperatorList, OPTABLE, OPDEF, getRange, MMLSPACING} from '../OperatorDictionary.js'; import {unicodeChars, unicodeString} from '../../../util/string.js'; /*****************************************************************/ @@ -101,6 +101,11 @@ export class MmlMo extends AbstractMmlTokenNode { ']+$' ].join('')); + /** + * Pattern to use to identify a multiletter operator + */ + protected static opPattern = /^[a-zA-Z]{2,}$/; + /** * Default map for remapping prime characters */ @@ -150,11 +155,7 @@ export class MmlMo extends AbstractMmlTokenNode { */ public get texClass() { if (this._texClass === null) { - let mo = this.getText(); - let [form1, form2, form3] = this.handleExplicitForm(this.getForms()); - let OPTABLE = (this.constructor as typeof MmlMo).OPTABLE; - let def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo]; - return def ? def[2] : TEXCLASS.REL; + return this.getOperatorDef(this.getText())[2]; } return this._texClass; } @@ -348,35 +349,44 @@ export class MmlMo extends AbstractMmlTokenNode { } /** - * Set the attributes from the operator table + * get the operator definition from the operator table * - * @param {string} mo The test of the mo element + * @param {string} mo The text of the mo element */ - protected checkOperatorTable(mo: string) { - let [form1, form2, form3] = this.handleExplicitForm(this.getForms()); + protected getOperatorDef(mo: string) { + const [form1, form2, form3] = this.handleExplicitForm(this.getForms()); this.attributes.setInherited('form', form1); - let OPTABLE = (this.constructor as typeof MmlMo).OPTABLE; - let def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo]; + const CLASS = this.constructor as typeof MmlMo + const OPTABLE = CLASS.OPTABLE; + const def = OPTABLE[form1][mo] || OPTABLE[form2][mo] || OPTABLE[form3][mo]; if (def) { - if (this.getProperty('texClass') === undefined) { - this.texClass = def[2]; - } - for (const name of Object.keys(def[3] || {})) { - this.attributes.setInherited(name, def[3][name]); - } - this.lspace = (def[0] + 1) / 18; - this.rspace = (def[1] + 1) / 18; - } else { - let range = getRange(mo); - if (range) { - if (this.getProperty('texClass') === undefined) { - this.texClass = range[2]; - } - const spacing = (this.constructor as typeof MmlMo).MMLSPACING[range[2]]; - this.lspace = (spacing[0] + 1) / 18; - this.rspace = (spacing[1] + 1) / 18; - } + return def; + } + const limits = this.attributes.get('movablelimits'); + const isOP = !!mo.match(CLASS.opPattern); + if ((isOP || limits) && this.getProperty('texClass') === undefined) { + return OPDEF(1, 2, TEXCLASS.OP); + } + const range = getRange(mo); + const [l, r] = CLASS.MMLSPACING[range[2]]; + return OPDEF(l, r, range[2]); + } + + /** + * Set the attributes from the operator table + * + * @param {string} mo The text of the mo element + */ + protected checkOperatorTable(mo: string) { + const def = this.getOperatorDef(mo); + if (this.getProperty('texClass') === undefined) { + this.texClass = def[2]; + } + for (const name of Object.keys(def[3] || {})) { + this.attributes.setInherited(name, def[3][name]); } + this.lspace = (def[0] + 1) / 18; + this.rspace = (def[1] + 1) / 18; } /** diff --git a/ts/core/MmlTree/OperatorDictionary.ts b/ts/core/MmlTree/OperatorDictionary.ts index 3830ba781..cc0a163a9 100644 --- a/ts/core/MmlTree/OperatorDictionary.ts +++ b/ts/core/MmlTree/OperatorDictionary.ts @@ -136,10 +136,10 @@ export const RANGES: RangeDef[] = [ /** * Get the Unicode range for the first character of a string * - * @param {string} text The character to check - * @return {RangeDef|null} The range containing that character, or null + * @param {string} text The character to check + * @return {RangeDef} The range containing that character, or null */ -export function getRange(text: string): RangeDef | null { +export function getRange(text: string): RangeDef { const n = text.codePointAt(0); for (const range of RANGES) { if (n <= range[1]) { @@ -149,7 +149,7 @@ export function getRange(text: string): RangeDef | null { break; } } - return null; + return [0, 0, TEXCLASS.REL, 'mo']; } /** diff --git a/ts/input/tex/base/BaseConfiguration.ts b/ts/input/tex/base/BaseConfiguration.ts index 9c4e3b880..20f38f719 100644 --- a/ts/input/tex/base/BaseConfiguration.ts +++ b/ts/input/tex/base/BaseConfiguration.ts @@ -58,11 +58,11 @@ export function Other(parser: TexParser, char: string) { {mathvariant: parser.stack.env['font']} : {}; const remap = (MapHandler.getMap('remap') as CharacterMap).lookup(char); const range = getRange(char); - const type = (range ? range[3] : 'mo'); + const type = range[3] // @test Other // @test Other Remap let mo = parser.create('token', type, def, (remap ? remap.char : char)); - const variant = (range?.[4] || + const variant = (range[4] || (ParseUtil.isLatinOrGreekChar(char) ? parser.configuration.mathStyle(char, true) : '')); if (variant) { mo.attributes.set('mathvariant', variant);