Skip to content

Commit

Permalink
fix: fixed #787, fixed #1869. Refactor symbol function detection
Browse files Browse the repository at this point in the history
  • Loading branch information
arnog committed Dec 15, 2023
1 parent 04f2ae3 commit 07ef4b4
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 30 deletions.
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,36 @@
as their LaTeX equivalent. This also affects parsing of the `value` property.
- When typing a superscript after `f`, `g` or some other function, correctly
interpret the superscript as an exponent, not as a function argument.
- **#787**, **#1869** The `f`, `g` and `h` symbols are no longer hardcoded as
functions.

Whether a symbol is considered a function affects the layout of a formula,
specifically the amount of space between the symbol and a subsequent delimiter
such as a parenthesis.

Now whether a symbol should be treated as a function is
determined by the `MathfieldElement.isFunctions` hook.

By the default, this hook uses the `MathfieldElement.computeEngine` to
determine if the domain of a symbol is a function.

This can be customized by setting the `isFunctions` property of the
mathfield or by declaring a symbol as a function using the `declare()`
method of the compute engine. For example:

```js
MathfieldElement.computeEngine.declare("f", "Functions");
```

In addition, a new `isImplicitFunction` option has been added which
can be used to indicate which symbols or commands are expected
to be followed by an implicit argument. For example, the `\sin` function
can be followed by an implicit argument without parentheses, as in
`\sin \frac{\pi}{2}`. This affects the editing behavior when typing a `/`
after the symbol. If an implicit function, the `/` will be interpreted as
an argument to the function, otherwise it will be interpreted as a fraction
with the symbol as the numerator.


## 0.98.3 (2023-12-07)

Expand Down
10 changes: 7 additions & 3 deletions src/core/modes-math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export class MathMode extends Mode {
style,
});
}

const isFunction = window.MathfieldElement.isFunction(
info.command ?? command
);

if (info.definitionType === 'symbol') {
const result = new Atom({
type: info.type ?? 'mord',
Expand All @@ -117,7 +122,7 @@ export class MathMode extends Mode {
value: String.fromCodePoint(info.codepoint),
style,
});
if (info.isFunction ?? false) result.isFunction = true;
if (isFunction) result.isFunction = true;

if (command.startsWith('\\')) result.verbatimLatex = command;
return result;
Expand All @@ -129,8 +134,7 @@ export class MathMode extends Mode {
value: command,
style,
});
if (info.isFunction ?? false) result.isFunction = true;

if (isFunction) result.isFunction = true;
if (command.startsWith('\\')) result.verbatimLatex = command;

return result;
Expand Down
5 changes: 4 additions & 1 deletion src/editor-mathfield/mode-editor-math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,10 @@ export class MathModeEditor extends ModeEditor {
if (
insertingFraction &&
implicitArgumentOffset >= 0 &&
model.at(model.position).isFunction
typeof model.mathfield.options.isImplicitFunction === 'function' &&
model.mathfield.options.isImplicitFunction(
model.at(model.position).command
)
) {
// If this is a fraction, and the implicit argument is a function,
// try again, but without the implicit argument
Expand Down
40 changes: 40 additions & 0 deletions src/editor-mathfield/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,46 @@ export function getDefault(): Required<_MathfieldOptions> {
smartSuperscript: true,
scriptDepth: [Infinity, Infinity],
removeExtraneousParentheses: true,
isImplicitFunction: (x) =>
[
'\\sin',
'\\cos',
'\\tan',

'\\arcsin',
'\\arccos',
'\\arctan',
'\\arcsec',
'\\arccsc',

'\\arsinh',
'\\arcosh',
'\\artanh',
'\\arcsech',
'\\arccsch',
'\\arg',
'\\ch',
'\\cosec',
'\\cosh',
'\\cot',
'\\cotg',
'\\coth',
'\\csc',
'\\ctg',
'\\cth',
'\\sec',
'\\sinh',
'\\sh',
'\\tanh',
'\\tg',
'\\th',

'\\lg',
'\\lb',
'\\log',
'\\ln',
].includes(x),

mathModeSpace: '',
placeholderSymbol: '▢',
contentPlaceholder: '',
Expand Down
11 changes: 0 additions & 11 deletions src/latex-commands/definitions-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,17 +852,6 @@ export function getDefinition(
};
}

// Special case `f`, `g` and `h` are recognized as functions.
if (
info &&
info.definitionType === 'symbol' &&
info.type === 'mord' &&
(info.codepoint === 0x66 ||
info.codepoint === 0x67 ||
info.codepoint === 0x68)
)
info.isFunction = true;

return info ?? null;
}

Expand Down
3 changes: 0 additions & 3 deletions src/latex-commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ export type LatexSymbolDefinition = {
codepoint: number;
variant?: Variant;

/** For "f", "g" and "h" */
isFunction?: boolean;

/** Note: symbols never have serialize or render functions. They are here because a TokenDefinition
* expect it.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/public/mathfield-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,21 @@ export class MathfieldElement extends HTMLElement implements Mathfield {
/** @internal */
private static _computeEngine: ComputeEngine | null;

/** @internal */
private static _isFunction: (command: string) => boolean = (command) => {
const ce = window.MathfieldElement.computeEngine;
return ce?.parse(command).domain?.isFunction ?? false;
};

static get isFunction(): (command: string) => boolean {
if (typeof this._isFunction !== 'function') return () => false;
return this._isFunction;
}

static set isFunction(value: (command: string) => boolean) {
this._isFunction = value;
}

static async loadSound(
sound: 'plonk' | 'keypress' | 'spacebar' | 'delete' | 'return'
): Promise<void> {
Expand Down
37 changes: 27 additions & 10 deletions src/public/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export type EditingOptions = {
* **Default**: `false`
*/
readOnly: boolean;

/**
* When `true`, during text input the field will switch automatically between
* 'math' and 'text' mode depending on what is typed and the context of the
Expand Down Expand Up @@ -358,6 +359,7 @@ export type EditingOptions = {
*
*/
smartMode: boolean;

/**
* When `true` and an open fence is entered via `typedText()` it will
* generate a contextually appropriate markup, for example using
Expand All @@ -366,6 +368,7 @@ export type EditingOptions = {
* When `false`, the literal value of the character will be inserted instead.
*/
smartFence: boolean;

/**
* When `true` and a digit is entered in an empty superscript, the cursor
* leaps automatically out of the superscript. This makes entry of common
Expand All @@ -379,6 +382,7 @@ export type EditingOptions = {
*
*/
smartSuperscript: boolean;

/**
* This option controls how many levels of subscript/superscript can be entered. For
* example, if `scriptDepth` is "1", there can be one level of superscript or
Expand All @@ -395,21 +399,35 @@ export type EditingOptions = {
* suppress the entry of subscripts, and allow one level of superscripts.
*/
scriptDepth: number | [number, number]; // For [superscript, subscript] or for both

/**
* If `true`, extra parentheses around a numerator or denominator are
* removed automatically.
*
* **Default**: `true`
*/
removeExtraneousParentheses: boolean;

/**
* Return true if the latex command is a function that could take
* implicit arguments. By default, this includes trigonometric function,
* so `\sin x` is interpreted as `\sin(x)`.
*
* This affects editing, for example how the `/` key is interpreted after
* such as symbol.
*
*/
isImplicitFunction: (name: string) => boolean;

/**
* The LaTeX string to insert when the spacebar is pressed (on the physical or
* virtual keyboard).
*
* Use `"\;"` for a thick space, `"\:"` for a medium space, `"\,"` for a thin space.
* Use `"\;"` for a thick space, `"\:"` for a medium space, `"\,"` for a
* thin space.
*
* Do not use `" "` (a regular space), as whitespace is skipped by LaTeX so this
* will do nothing.
* Do not use `" "` (a regular space), as whitespace is skipped by LaTeX
* so this will do nothing.
*
* **Default**: `""` (empty string)
*/
Expand Down Expand Up @@ -460,15 +478,14 @@ export type LayoutOptions = {
* For example, to add a new macro to the default macro dictionary:
*
```javascript
mf.setConfig({
macros: {
...mf.getOption('macros'),
smallfrac: '^{#1}\\!\\!/\\!_{#2}',
},
});
mf.macros = {
...mf.macros,
smallfrac: '^{#1}\\!\\!/\\!_{#2}',
};
```
*
* Note that `getOption()` is called to keep the existing macros and add to them.
* Note that `...mf.macros` is used to keep the existing macros and add to
* them.
* Otherwise, all the macros are replaced with the new definition.
*
* The code above will support the following notation:
Expand Down
7 changes: 5 additions & 2 deletions test/smoke/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h2>Latex to Speakable Text</h2>
<button id="grow">Grow</button>
<button id="shrink">Shrink</button>
</div>

<div>$$f(x) = \sin(x)$$</div>
<div style="height: 800px"></div>
</main>

Expand All @@ -126,7 +126,8 @@ <h2>Latex to Speakable Text</h2>

const MAX_LINE_LENGTH = 74;

renderMathInDocument();
// renderMathInDocument({ renderAccessibleContent: false });
renderMathInDocument({ renderAccessibleContent: false });

// const ce = new ComputeEngine({
// latexDictionary: [
Expand Down Expand Up @@ -158,6 +159,8 @@ <h2>Latex to Speakable Text</h2>
/^[a-zA-Z]+$/.test(s) ? "function" : "unknown",
};

MathfieldElement.computeEngine.declare("f", "Functions");

// mathVirtualKeyboard.layouts = [
// {
// label: 'minimal',
Expand Down

0 comments on commit 07ef4b4

Please sign in to comment.