site/node_modules/mathjax-full/ts/output/chtml/FontData.ts
2024-10-14 08:09:33 +02:00

481 lines
16 KiB
TypeScript

/*************************************************************
*
* Copyright (c) 2017-2022 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Implements the CHTMLFontData class and AddCSS() function.
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {CharMap, CharOptions, CharData, VariantData, DelimiterData, FontData, DIRECTION} from '../common/FontData.js';
import {Usage} from './Usage.js';
import {StringMap} from './Wrapper.js';
import {StyleList, StyleData} from '../../util/StyleList.js';
import {em} from '../../util/lengths.js';
export * from '../common/FontData.js';
/****************************************************************************/
/**
* Add the extra data needed for CharOptions in CHTML
*/
export interface CHTMLCharOptions extends CharOptions {
c?: string; // the content value (for css)
f?: string; // the font postfix (for css)
}
/**
* Shorthands for CHTML char maps and char data
*/
export type CHTMLCharMap = CharMap<CHTMLCharOptions>;
export type CHTMLCharData = CharData<CHTMLCharOptions>;
/**
* The extra data needed for a Variant in CHTML output
*/
export interface CHTMLVariantData extends VariantData<CHTMLCharOptions> {
classes?: string; // the classes to use for this variant
letter: string; // the font letter(s) for the default font for this variant
}
/**
* The extra data needed for a Delimiter in CHTML output
*/
export interface CHTMLDelimiterData extends DelimiterData {
}
/****************************************************************************/
/**
* The CHTML FontData class
*/
export class CHTMLFontData extends FontData<CHTMLCharOptions, CHTMLVariantData, CHTMLDelimiterData> {
/**
* Default options
*/
public static OPTIONS = {
...FontData.OPTIONS,
fontURL: 'js/output/chtml/fonts/tex-woff-v2'
};
/**
* @override
*/
public static JAX = 'CHTML';
/**
* The default class names to use for each variant
*/
protected static defaultVariantClasses: StringMap = {};
/**
* The default font letter to use for each variant
*/
protected static defaultVariantLetters: StringMap = {};
/**
* The CSS styles needed for this font.
*/
protected static defaultStyles = {
'mjx-c::before': {
display: 'block',
width: 0
}
};
/**
* The default @font-face declarations with %%URL%% where the font path should go
*/
protected static defaultFonts = {
'@font-face /* 0 */': {
'font-family': 'MJXZERO',
src: 'url("%%URL%%/MathJax_Zero.woff") format("woff")'
}
};
/***********************************************************************/
/**
* Data about the characters used (for adaptive CSS)
*/
public charUsage: Usage<[string, number]> = new Usage<[string, number]>();
/**
* Data about the delimiters used (for adpative CSS)
*/
public delimUsage: Usage<number> = new Usage<number>();
/***********************************************************************/
/**
* @override
*/
public static charOptions(font: CHTMLCharMap, n: number) {
return super.charOptions(font, n) as CHTMLCharOptions;
}
/***********************************************************************/
/**
* @param {boolean} adapt Whether to use adaptive CSS or not
*/
public adaptiveCSS(adapt: boolean) {
this.options.adaptiveCSS = adapt;
}
/**
* Clear the cache of which characters have been used
*/
public clearCache() {
if (this.options.adaptiveCSS) {
this.charUsage.clear();
this.delimUsage.clear();
}
}
/**
* @override
*/
public createVariant(name: string, inherit: string = null, link: string = null) {
super.createVariant(name, inherit, link);
let CLASS = (this.constructor as CHTMLFontDataClass);
this.variant[name].classes = CLASS.defaultVariantClasses[name];
this.variant[name].letter = CLASS.defaultVariantLetters[name];
}
/**
* @override
*/
public defineChars(name: string, chars: CHTMLCharMap) {
super.defineChars(name, chars);
const letter = this.variant[name].letter;
for (const n of Object.keys(chars)) {
const options = CHTMLFontData.charOptions(chars, parseInt(n));
if (options.f === undefined) {
options.f = letter;
}
}
}
/***********************************************************************/
/**
* @return {StyleList} The (computed) styles for this font
*/
get styles(): StyleList {
const CLASS = this.constructor as typeof CHTMLFontData;
//
// Include the default styles
//
const styles: StyleList = {...CLASS.defaultStyles};
//
// Add fonts with proper URL
//
this.addFontURLs(styles, CLASS.defaultFonts, this.options.fontURL);
//
// Add the styles for delimiters and characters
//
if (this.options.adaptiveCSS) {
this.updateStyles(styles);
} else {
this.allStyles(styles);
}
//
// Return the final style sheet
//
return styles;
}
/**
* Get the styles for any newly used characters and delimiters
*
* @param {StyleList} styles The style list to add delimiter styles to.
* @return {StyleList} The modified style list.
*/
public updateStyles(styles: StyleList): StyleList {
for (const N of this.delimUsage.update()) {
this.addDelimiterStyles(styles, N, this.delimiters[N]);
}
for (const [name, N] of this.charUsage.update()) {
const variant = this.variant[name];
this.addCharStyles(styles, variant.letter, N, variant.chars[N]);
}
return styles;
}
/**
* @param {StyleList} styles The style list to add characters to
*/
protected allStyles(styles: StyleList) {
//
// Create styles needed for the delimiters
//
for (const n of Object.keys(this.delimiters)) {
const N = parseInt(n);
this.addDelimiterStyles(styles, N, this.delimiters[N]);
}
//
// Add style for all character data
//
for (const name of Object.keys(this.variant)) {
const variant = this.variant[name];
const vletter = variant.letter;
for (const n of Object.keys(variant.chars)) {
const N = parseInt(n);
const char = variant.chars[N];
if ((char[3] || {}).smp) continue;
if (char.length < 4) {
(char as CHTMLCharData)[3] = {};
}
this.addCharStyles(styles, vletter, N, char);
}
}
}
/**
* @param {StyleList} styles The style object to add styles to
* @param {StyleList} fonts The default font-face directives with %%URL%% where the url should go
* @param {string} url The actual URL to insert into the src strings
*/
protected addFontURLs(styles: StyleList, fonts: StyleList, url: string) {
for (const name of Object.keys(fonts)) {
const font = {...fonts[name]};
font.src = (font.src as string).replace(/%%URL%%/, url);
styles[name] = font;
}
}
/*******************************************************/
/**
* @param {StyleList} styles The style object to add styles to
* @param {number} n The unicode character number of the delimiter
* @param {CHTMLDelimiterData} data The data for the delimiter whose CSS is to be added
*/
protected addDelimiterStyles(styles: StyleList, n: number, data: CHTMLDelimiterData) {
let c = this.charSelector(n);
if (data.c && data.c !== n) {
c = this.charSelector(data.c);
styles['.mjx-stretched mjx-c' + c + '::before'] = {
content: this.charContent(data.c)
};
}
if (!data.stretch) return;
if (data.dir === DIRECTION.Vertical) {
this.addDelimiterVStyles(styles, c, data);
} else {
this.addDelimiterHStyles(styles, c, data);
}
}
/*******************************************************/
/**
* @param {StyleList} styles The style object to add styles to
* @param {string} c The delimiter character string
* @param {CHTMLDelimiterData} data The data for the delimiter whose CSS is to be added
*/
protected addDelimiterVStyles(styles: StyleList, c: string, data: CHTMLDelimiterData) {
const HDW = data.HDW as CHTMLCharData;
const [beg, ext, end, mid] = data.stretch;
const Hb = this.addDelimiterVPart(styles, c, 'beg', beg, HDW);
this.addDelimiterVPart(styles, c, 'ext', ext, HDW);
const He = this.addDelimiterVPart(styles, c, 'end', end, HDW);
const css: StyleData = {};
if (mid) {
const Hm = this.addDelimiterVPart(styles, c, 'mid', mid, HDW);
css.height = '50%';
styles['mjx-stretchy-v' + c + ' > mjx-mid'] = {
'margin-top': this.em(-Hm / 2),
'margin-bottom': this.em(-Hm / 2)
};
}
if (Hb) {
css['border-top-width'] = this.em0(Hb - .03);
}
if (He) {
css['border-bottom-width'] = this.em0(He - .03);
styles['mjx-stretchy-v' + c + ' > mjx-end'] = {'margin-top': this.em(-He)};
}
if (Object.keys(css).length) {
styles['mjx-stretchy-v' + c + ' > mjx-ext'] = css;
}
}
/**
* @param {StyleList} styles The style object to add styles to
* @param {string} c The vertical character whose part is being added
* @param {string} part The name of the part (beg, ext, end, mid) that is being added
* @param {number} n The unicode character to use for the part
* @param {number} HDW The height-depth-width data for the stretchy delimiter
* @return {number} The total height of the character
*/
protected addDelimiterVPart(styles: StyleList, c: string, part: string, n: number, HDW: CHTMLCharData): number {
if (!n) return 0;
const data = this.getDelimiterData(n);
const dw = (HDW[2] - data[2]) / 2;
const css: StyleData = {content: this.charContent(n)};
if (part !== 'ext') {
css.padding = this.padding(data, dw);
} else {
css.width = this.em0(HDW[2]);
if (dw) {
css['padding-left'] = this.em0(dw);
}
}
styles['mjx-stretchy-v' + c + ' mjx-' + part + ' mjx-c::before'] = css;
return data[0] + data[1];
}
/*******************************************************/
/**
* @param {StyleList} styles The style object to add styles to
* @param {string} c The delimiter character string
* @param {CHTMLDelimiterData} data The data for the delimiter whose CSS is to be added
*/
protected addDelimiterHStyles(styles: StyleList, c: string, data: CHTMLDelimiterData) {
const [beg, ext, end, mid] = data.stretch;
const HDW = data.HDW as CHTMLCharData;
this.addDelimiterHPart(styles, c, 'beg', beg, HDW);
this.addDelimiterHPart(styles, c, 'ext', ext, HDW);
this.addDelimiterHPart(styles, c, 'end', end, HDW);
if (mid) {
this.addDelimiterHPart(styles, c, 'mid', mid, HDW);
styles['mjx-stretchy-h' + c + ' > mjx-ext'] = {width: '50%'};
}
}
/**
* @param {StyleList} styles The style object to add styles to
* @param {string} c The vertical character whose part is being added
* @param {string} part The name of the part (beg, ext, end, mid) that is being added
* @param {number} n The unicode character to use for the part
* @param {CHTMLCharData} HDW The height-depth-width data for the stretchy character
*/
protected addDelimiterHPart(styles: StyleList, c: string, part: string, n: number, HDW: CHTMLCharData) {
if (!n) return;
const data = this.getDelimiterData(n);
const options = data[3] as CHTMLCharOptions;
const css: StyleData = {content: (options && options.c ? '"' + options.c + '"' : this.charContent(n))};
css.padding = this.padding(HDW as CHTMLCharData, 0, -HDW[2]);
styles['mjx-stretchy-h' + c + ' mjx-' + part + ' mjx-c::before'] = css;
}
/*******************************************************/
/**
* @param {StyleList} styles The style object to add styles to
* @param {string} vletter The variant class letter (e.g., `B`, `SS`) where this character is being defined
* @param {number} n The unicode character being defined
* @param {CHTMLCharData} data The bounding box data and options for the character
*/
protected addCharStyles(styles: StyleList, vletter: string, n: number, data: CHTMLCharData) {
const options = data[3] as CHTMLCharOptions;
const letter = (options.f !== undefined ? options.f : vletter);
const selector = 'mjx-c' + this.charSelector(n) + (letter ? '.TEX-' + letter : '');
styles[selector + '::before'] = {
padding: this.padding(data, 0, options.ic || 0),
content: (options.c != null ? '"' + options.c + '"' : this.charContent(n))
};
}
/***********************************************************************/
/**
* @param {number} n The character number to find
* @return {CHTMLCharData} The data for that character to be used for stretchy delimiters
*/
protected getDelimiterData(n: number): CHTMLCharData {
return this.getChar('-smallop', n);
}
/**
* @param {number} n The number of ems
* @return {string} The string representing the number with units of "em"
*/
public em(n: number): string {
return em(n);
}
/**
* @param {number} n The number of ems (will be restricted to non-negative values)
* @return {string} The string representing the number with units of "em"
*/
public em0(n: number): string {
return em(Math.max(0, n));
}
/**
* @param {CHTMLCharData} data The [h, d, w] data for the character
* @param {number} dw The (optional) left offset of the glyph
* @param {number} ic The (optional) italic correction value
* @return {string} The padding string for the h, d, w.
*/
public padding([h, d, w]: CHTMLCharData, dw: number = 0, ic: number = 0): string {
return [h, w + ic, d, dw].map(this.em0).join(' ');
}
/**
* @param {number} n A unicode code point to be converted to character content for use with the
* CSS rules for fonts (either a literal character for most ASCII values, or \nnnn
* for higher values, or for the double quote and backslash characters).
* @return {string} The character as a properly encoded string in quotes.
*/
public charContent(n: number): string {
return '"' + (n >= 0x20 && n <= 0x7E && n !== 0x22 && n !== 0x27 && n !== 0x5C ?
String.fromCharCode(n) : '\\' + n.toString(16).toUpperCase()) + '"';
}
/**
* @param {number} n A unicode code point to be converted to a selector for use with the
* CSS rules for fonts
* @return {string} The character as a selector value.
*/
public charSelector(n: number): string {
return '.mjx-c' + n.toString(16).toUpperCase();
}
}
/**
* The CHTMLFontData constructor class
*/
export type CHTMLFontDataClass = typeof CHTMLFontData;
/****************************************************************************/
/**
* Data needed for AddCSS()
*/
export type CharOptionsMap = {[name: number]: CHTMLCharOptions};
export type CssMap = {[name: number]: number};
/**
* @param {CHTMLCharMap} font The font to augment
* @param {CharOptionsMap} options Any additional options for characters in the font
* @return {CHTMLCharMap} The augmented font
*/
export function AddCSS(font: CHTMLCharMap, options: CharOptionsMap): CHTMLCharMap {
for (const c of Object.keys(options)) {
const n = parseInt(c);
Object.assign(FontData.charOptions(font, n), options[n]);
}
return font;
}