Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow tex packages that load font extensions to work better with \require and renderer changes. #1080

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 7 additions & 15 deletions components/mjs/input/tex/extension.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import {combineDefaults} from '#js/components/global.js';

export function fontExtension(id, name, pkg = `@mathjax/${name}`) {
if (MathJax.config?.loader) {
const FONTPATH = (typeof document === 'undefined' ? pkg :
`https://cdn.jsdelivr.net/npm/${name}`);
if (MathJax.loader) {
const FONTPATH = (typeof document === 'undefined' ? pkg : `https://cdn.jsdelivr.net/npm/${name}`);
const path = name.replace(/-font-extension$/, '-extension');
const extension = name.replace(/-font-extension$/, '');
const jax = (MathJax.config?.startup?.output || 'chtml');
combineDefaults(MathJax.config.loader, 'paths', {[path]: FONTPATH});
combineDefaults(MathJax.config.loader, 'dependencies', {
[`[${path}]/chtml`]: ['output/chtml'],
[`[${path}]/svg`]: ['output/svg']
combineDefaults(MathJax.config.loader, 'dependencies', {[`[${path}]/${jax}`]: [`output/${jax}`]});
MathJax.loader.addPackageData(id, {
extraLoads: [`[${path}]/${jax}`],
rendererExtensions: [path]
});
MathJax.config.loader[id] = {
checkReady() {
return MathJax.loader.load(
`[${path}]/${MathJax.config?.startup?.output || 'chtml'}`
).then(() => {
MathJax.startup.document?.outputJax?.addExtension(extension);
});
}
};
}
}
4 changes: 0 additions & 4 deletions components/mjs/output/chtml/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
"output/chtml.ts",
"output/chtml",
"output/common"
],
"exclude": [
"output/chtml/ts5",
"output/chtml/ts6"
]
},
"webpack": {
Expand Down
2 changes: 2 additions & 0 deletions components/mjs/output/chtml/nofont.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export class DefaultFont extends ChtmlFontData {};
export const fontName = 'nofont';

DefaultFont.OPTIONS = {fontURL: '.'};

export const Font = {fontName, DefaultFont};
4 changes: 0 additions & 4 deletions components/mjs/output/svg/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
"output/svg.ts",
"output/svg",
"output/common"
],
"exclude": [
"output/svg/ts5",
"output/svg/ts6"
]
},
"webpack": {
Expand Down
2 changes: 2 additions & 0 deletions components/mjs/output/svg/nofont.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ import {SvgFontData} from '#js/output/svg/FontData.js';

export class DefaultFont extends SvgFontData {};
export const fontName = 'nofont';

export const Font = {fontName, DefaultFont};
17 changes: 8 additions & 9 deletions components/mjs/output/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ export const OutputUtil = {

if (name !== defaultFont) {

combineDefaults(MathJax.config.loader, `output/${jax}`, {
checkReady() {
return MathJax.loader.load(`${font}/${jax}`).catch(err => console.log(err));
}
});
MathJax.loader.addPackageData(`output/${jax}`, {extraLoads: [`${font}/${jax}`]});

} else {

const extraLoads = MathJax.config.loader[`${font}/${jax}`]?.extraLoads;
if (extraLoads) {
MathJax.loader.addPackageData(`output/${jax}`, {extraLoads});
}

combineWithMathJax({_: {
output: {
fonts: {
Expand Down Expand Up @@ -78,14 +79,12 @@ export const OutputUtil = {

loadFont(startup, jax, font, preload) {
if (!MathJax.loader) {
return Promise.resolve();
return startup;
}
if (preload) {
MathJax.loader.preLoad(`[${font}]/${jax}`);
}
const check = MathJax.config.loader[`output/${jax}`];
const start = (check && check.checkReady ? check.checkReady().then(startup) : startup());
return start.catch(err => console.log(err));
return Package.loadPromise(`output/${jax}`).then(startup);
}

};
29 changes: 27 additions & 2 deletions ts/components/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import {MathJax as MJGlobal, MathJaxObject as MJObject, MathJaxLibrary,
MathJaxConfig as MJConfig, combineWithMathJax, combineDefaults} from './global.js';
import {Package, PackageError, PackageReady, PackageFailed} from './package.js';
import {Package, PackageError, PackageReady, PackageFailed, PackageConfig} from './package.js';
import {FunctionList} from '../util/FunctionList.js';
import {mjxRoot} from '#root/root.js';

Expand Down Expand Up @@ -73,7 +73,8 @@ export interface MathJaxObject extends MJObject {
getRoot: () => string; // Find the root URL for the MathJax files
checkVersion: (name: string, version: string) => boolean; // Check the version of an extension
saveVersion: (name: string) => void; // Set the version for a combined component
pathFilters: FunctionList; // the filters to use for looking for package paths
pathFilters: FunctionList; // The filters to use for looking for package paths
addPackageData: (name: string, data: PackageConfig) => void; // Add more package data for a package
};
startup?: any;
}
Expand Down Expand Up @@ -199,6 +200,30 @@ export namespace Loader {
}
}

/**
* Insert options into a package configuration
*
* @param {string} name The package whose configuration is being augmented
* @param {PackageConfig} data The extra configuraiton information to add
*/
export function addPackageData(name: string, data: PackageConfig) {
let config = CONFIG[name];
if (!config) {
config = CONFIG[name] = {};
}
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
if (!config[key]) {
config[key] = [];
}
const set = new Set([...config[key], ...value]);
config[key] = [...set];
} else {
config[key] = value;
}
}
}

/**
* The default function to perform when all the packages are loaded
*/
Expand Down
22 changes: 16 additions & 6 deletions ts/components/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export interface PackageConfig {
failed?: PackageFailed; // Function to call when package fails to load
checkReady?: () => Promise<void>; // Function called to see if package is fully loaded
// (may cause additional packages to load, for example)
extraLoads?: string[]; // Extra packages to load after this one
rendererExtensions?: string[]; // Font extensions to load when renderer changes
}

/**
Expand Down Expand Up @@ -143,6 +145,16 @@ export class Package {
return this.dependencyCount === 0 && !this.noLoad && !this.isLoading && !this.hasFailed;
}

/**
* @param {string} name A promise for when extra files and checkReady have been fulfilled
*/
public static loadPromise(name: string): Promise<void> {
const config = (CONFIG[name] || {}) as PackageConfig;
const promise = Promise.all((config.extraLoads || []).map((name) => Loader.load(name)));
const checkReady = config.checkReady || (() => Promise.resolve());
return promise.then(() => checkReady()) as Promise<void>;
}

/**
* Compute the path for a package using the loader's path filters
*
Expand Down Expand Up @@ -344,18 +356,16 @@ export class Package {
/**
* Check if a package is really ready to be marked as loaded
* (When it is loaded, it may set its own checkReady() function
* as a means of loading additional packages. E.g., an output
* jax may load a font package, dependent on its configuration.)
* or extraLoads array as a means of loading additional packages.
* E.g., an output jax may load a font package, dependent on its
* configuration.)
*
* The configuration's checkReady() function returns a promise
* that allows the loader to wait for addition actions to finish
* before marking the file as loaded (or failing to load).
*/
protected checkLoad() {
const config = (CONFIG[this.name] || {}) as PackageConfig;
const checkReady = config.checkReady || (() => Promise.resolve());
checkReady().then(() => this.loaded())
.catch((message) => this.failed(message));
Package.loadPromise(this.name).then(() => this.loaded()).catch((message) => this.failed(message));
}

/**
Expand Down
10 changes: 6 additions & 4 deletions ts/input/tex/require/RequireConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ import {ParseMethod} from '../Types.js';
import TexError from '../TexError.js';
import {TeX} from '../../tex.js';

import {MathJax} from '../../../components/global.js';
import {MathJax} from '../../../components/startup.js';
import {Package} from '../../../components/package.js';
import {Loader, CONFIG as LOADERCONFIG} from '../../../components/loader.js';
import {mathjax} from '../../../mathjax.js';
import {expandable} from '../../../util/Options.js';
import {MenuMathDocument} from '../../../ui/menu/MenuHandler.js';

/**
* The MathJax configuration block (for looking up user-defined package options)
Expand Down Expand Up @@ -147,11 +148,12 @@ export function RequireLoad(parser: TexParser, name: string) {
if (!allowed) {
throw new TexError('BadRequire', 'Extension "%1" is not allowed to be loaded', extension);
}
if (Package.packages.has(extension)) {
RegisterExtension(parser.configuration.packageData.get('require').jax, extension);
} else {
if (!Package.packages.has(extension)) {
mathjax.retryAfter(Loader.load(extension));
}
const require = LOADERCONFIG[extension]?.rendererExtensions;
(MathJax.startup.document as MenuMathDocument)?.menu?.addRequiredExtensions?.(require);
RegisterExtension(parser.configuration.packageData.get('require').jax, extension);
}

/**
Expand Down
9 changes: 6 additions & 3 deletions ts/output/chtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {MathItem} from '../core/MathItem.js';
import {ChtmlWrapper, ChtmlWrapperClass} from './chtml/Wrapper.js';
import {ChtmlWrapperFactory} from './chtml/WrapperFactory.js';
import {ChtmlCharOptions, ChtmlVariantData, ChtmlDelimiterData,
ChtmlFontData, ChtmlFontDataClass} from './chtml/FontData.js';
ChtmlFontData, ChtmlFontDataClass, FontExtensionData} from './chtml/FontData.js';
import {Usage} from './chtml/Usage.js';
import * as LENGTHS from '../util/lengths.js';
import {unicodeChars} from '../util/string.js';
Expand Down Expand Up @@ -165,8 +165,11 @@ CommonOutputJax<
/**
* @override
*/
public addExtension(name: string): string[] {
const css = super.addExtension(name);
public addExtension(
font: FontExtensionData<ChtmlCharOptions, ChtmlDelimiterData>,
prefix: string = ''
): string[] {
const css = super.addExtension(font, prefix);
if (css.length && this.options.adaptiveCSS && this.chtmlStyles) {
this.adaptor.insertRules(this.chtmlStyles, css);
}
Expand Down
13 changes: 7 additions & 6 deletions ts/output/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {MathDocument} from '../core/MathDocument.js';
import {MathItem, Metrics, STATE} from '../core/MathItem.js';
import {MmlNode, TEXCLASS} from '../core/MmlTree/MmlNode.js';
import {DOMAdaptor} from '../core/DOMAdaptor.js';
import {FontData, FontDataClass, CharOptions, VariantData, DelimiterData, CssFontData} from './common/FontData.js';
import {FontData, FontDataClass, CharOptions, VariantData, DelimiterData,
CssFontData, FontExtensionData} from './common/FontData.js';
import {OptionList, separateOptions} from '../util/Options.js';
import {CommonWrapper, CommonWrapperClass} from './common/Wrapper.js';
import {CommonWrapperFactory} from './common/WrapperFactory.js';
Expand Down Expand Up @@ -269,12 +270,12 @@ export abstract class CommonOutputJax<
/**
* Add a registered font extension to the output jax's font.
*
* @param {string} name The name of the extension to add to this output jax's font
* @return {string[]} New CSS rules needed for the font
* @param {FontExtensionData} font The name of the extension to add to this output jax's font
* @param {string} prefix The prefix for dynamically loaded files for this extension
* @return {string[]} New CSS rules needed for the font
*/
public addExtension(name: string): string[] {
const font = this.font.CLASS.dynamicExtensions.get(name);
return this.font.addExtension(font.data);
public addExtension(font: FontExtensionData<CC, DD>, prefix: string = ''): string[] {
return this.font.addExtension(font, prefix);
}


Expand Down
12 changes: 5 additions & 7 deletions ts/output/common/FontData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,6 @@ export type DynamicFont = {
files: DynamicFileList;
sizeN: number;
stretchN: number;
data: FontExtensionData<any, any>
};

/**
Expand Down Expand Up @@ -790,11 +789,10 @@ export class FontData<C extends CharOptions, V extends VariantData<C>, D extends
public static addExtension(data: FontExtensionData<CharOptions, DelimiterData>, prefix: string = '') {
const extension = {
name: data.name,
prefix: prefix,
prefix: prefix || `[${data.name}-extension]/${this.JAX.toLowerCase()}/dynamic`,
files: this.defineDynamicFiles(data.ranges, data.name),
sizeN: this.defaultSizeVariants.length,
stretchN: this.defaultStretchVariants.length,
data: data
stretchN: this.defaultStretchVariants.length
};
this.dynamicExtensions.set(data.name, extension);
for (const [src, dst] of [
Expand Down Expand Up @@ -858,13 +856,13 @@ export class FontData<C extends CharOptions, V extends VariantData<C>, D extends
* @return {string[]} The new CSS rules needed for this extension
*/
public addExtension(data: FontExtensionData<C, D>, prefix: string = ''): string[] {
const jax = (this.constructor as typeof FontData<C, V, D>).JAX.toLowerCase();
const dynamicFont = {
name: data.name,
prefix: prefix,
prefix: prefix || `[${data.name}-extension]/${jax}/dynamic`,
files: this.CLASS.defineDynamicFiles(data.ranges, prefix),
sizeN: this.sizeVariants.length,
stretchN: this.stretchVariants.length,
data: data
stretchN: this.stretchVariants.length
};
this.CLASS.dynamicExtensions.set(data.name, dynamicFont);

Expand Down
34 changes: 32 additions & 2 deletions ts/ui/menu/Menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ export class Menu {
*/
protected rerenderStart: number = STATE.LAST;

/**
* List of font extensions that were loaded via \require
*/
public requiredExtensions: string[] = [];

/**
* @returns {boolean} true when the menu is loading some component
*/
Expand Down Expand Up @@ -849,7 +854,8 @@ export class Menu {
}

/**
* Set up the new jax and link it to the document, then rerender the math
* Set up the new jax and link it to the document,
* load any needed extensions, and then rerender the math, if needed
*
* @param {string} jax The name of the jax to switch to
* @param {boolean} rerender True if the document should be rerendered
Expand All @@ -859,7 +865,31 @@ export class Menu {
protected setOutputJax(jax: string, rerender: boolean = true): Promise<void> {
this.jax[jax].setAdaptor(this.document.adaptor);
this.document.outputJax = this.jax[jax];
return (rerender ? mathjax.handleRetriesFor(() => this.rerender()) : Promise.resolve());
const promise = this.loadRequiredExtensions();
return (rerender ? promise.then(() => mathjax.handleRetriesFor(() => this.rerender())) : promise);
}

/**
* Load the required extensions into the new output jax
*/
protected loadRequiredExtensions() {
let jax = this.document.outputJax.name.toLowerCase();
let promises = [];
for (const path of this.requiredExtensions) {
promises.push(MathJax.loader.load(`[${path}]/${jax}`));
}
this.requiredExtensions = [];
return Promise.all(promises);
}

/**
* Add extensions that need to be loaded when the renderer changes
*/
public addRequiredExtensions(extensions: string[]) {
if (extensions) {
const set = new Set<string>([...this.requiredExtensions, ...extensions]);
this.requiredExtensions = [...set];
}
}

/**
Expand Down