123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- /*************************************************************
- *
- * 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;
- }
|