// @flow /** * This file contains information about the options that the Parser carries * around with it while parsing. Data is held in an `Options` object, and when * recursing, a new `Options` object can be created with the `.with*` and * `.reset` functions. */ import {getGlobalMetrics} from "./fontMetrics"; import type {FontMetrics} from "./fontMetrics"; import type {StyleInterface} from "./Style"; const sizeStyleMap = [ // Each element contains [textsize, scriptsize, scriptscriptsize]. // The size mappings are taken from TeX with \normalsize=10pt. [1, 1, 1], // size1: [5, 5, 5] \tiny [2, 1, 1], // size2: [6, 5, 5] [3, 1, 1], // size3: [7, 5, 5] \scriptsize [4, 2, 1], // size4: [8, 6, 5] \footnotesize [5, 2, 1], // size5: [9, 6, 5] \small [6, 3, 1], // size6: [10, 7, 5] \normalsize [7, 4, 2], // size7: [12, 8, 6] \large [8, 6, 3], // size8: [14.4, 10, 7] \Large [9, 7, 6], // size9: [17.28, 12, 10] \LARGE [10, 8, 7], // size10: [20.74, 14.4, 12] \huge [11, 10, 9], // size11: [24.88, 20.74, 17.28] \HUGE ]; const sizeMultipliers = [ // fontMetrics.js:getGlobalMetrics also uses size indexes, so if // you change size indexes, change that function. 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488, ]; const sizeAtStyle = function(size: number, style: StyleInterface): number { return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1]; }; // In these types, "" (empty string) means "no change". export type FontWeight = "textbf" | "textmd" | ""; export type FontShape = "textit" | "textup" | ""; export type OptionsData = { style: StyleInterface; color?: string | void; size?: number; textSize?: number; phantom?: boolean; font?: string; fontFamily?: string; fontWeight?: FontWeight; fontShape?: FontShape; sizeMultiplier?: number; maxSize: number; minRuleThickness: number; }; /** * This is the main options class. It contains the current style, size, color, * and font. * * Options objects should not be modified. To create a new Options with * different properties, call a `.having*` method. */ class Options { style: StyleInterface; color: string | void; size: number; textSize: number; phantom: boolean; // A font family applies to a group of fonts (i.e. SansSerif), while a font // represents a specific font (i.e. SansSerif Bold). // See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm font: string; fontFamily: string; fontWeight: FontWeight; fontShape: FontShape; sizeMultiplier: number; maxSize: number; minRuleThickness: number; _fontMetrics: FontMetrics | void; /** * The base size index. */ static BASESIZE: number = 6; constructor(data: OptionsData) { this.style = data.style; this.color = data.color; this.size = data.size || Options.BASESIZE; this.textSize = data.textSize || this.size; this.phantom = !!data.phantom; this.font = data.font || ""; this.fontFamily = data.fontFamily || ""; this.fontWeight = data.fontWeight || ''; this.fontShape = data.fontShape || ''; this.sizeMultiplier = sizeMultipliers[this.size - 1]; this.maxSize = data.maxSize; this.minRuleThickness = data.minRuleThickness; this._fontMetrics = undefined; } /** * Returns a new options object with the same properties as "this". Properties * from "extension" will be copied to the new options object. */ extend(extension: $Shape): Options { const data = { style: this.style, size: this.size, textSize: this.textSize, color: this.color, phantom: this.phantom, font: this.font, fontFamily: this.fontFamily, fontWeight: this.fontWeight, fontShape: this.fontShape, maxSize: this.maxSize, minRuleThickness: this.minRuleThickness, }; for (const key in extension) { if (extension.hasOwnProperty(key)) { data[key] = extension[key]; } } return new Options(data); } /** * Return an options object with the given style. If `this.style === style`, * returns `this`. */ havingStyle(style: StyleInterface): Options { if (this.style === style) { return this; } else { return this.extend({ style: style, size: sizeAtStyle(this.textSize, style), }); } } /** * Return an options object with a cramped version of the current style. If * the current style is cramped, returns `this`. */ havingCrampedStyle(): Options { return this.havingStyle(this.style.cramp()); } /** * Return an options object with the given size and in at least `\textstyle`. * Returns `this` if appropriate. */ havingSize(size: number): Options { if (this.size === size && this.textSize === size) { return this; } else { return this.extend({ style: this.style.text(), size: size, textSize: size, sizeMultiplier: sizeMultipliers[size - 1], }); } } /** * Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted, * changes to at least `\textstyle`. */ havingBaseStyle(style: StyleInterface): Options { style = style || this.style.text(); const wantSize = sizeAtStyle(Options.BASESIZE, style); if (this.size === wantSize && this.textSize === Options.BASESIZE && this.style === style) { return this; } else { return this.extend({ style: style, size: wantSize, }); } } /** * Remove the effect of sizing changes such as \Huge. * Keep the effect of the current style, such as \scriptstyle. */ havingBaseSizing(): Options { let size; switch (this.style.id) { case 4: case 5: size = 3; // normalsize in scriptstyle break; case 6: case 7: size = 1; // normalsize in scriptscriptstyle break; default: size = 6; // normalsize in textstyle or displaystyle } return this.extend({ style: this.style.text(), size: size, }); } /** * Create a new options object with the given color. */ withColor(color: string): Options { return this.extend({ color: color, }); } /** * Create a new options object with "phantom" set to true. */ withPhantom(): Options { return this.extend({ phantom: true, }); } /** * Creates a new options object with the given math font or old text font. * @type {[type]} */ withFont(font: string): Options { return this.extend({ font, }); } /** * Create a new options objects with the given fontFamily. */ withTextFontFamily(fontFamily: string): Options { return this.extend({ fontFamily, font: "", }); } /** * Creates a new options object with the given font weight */ withTextFontWeight(fontWeight: FontWeight): Options { return this.extend({ fontWeight, font: "", }); } /** * Creates a new options object with the given font weight */ withTextFontShape(fontShape: FontShape): Options { return this.extend({ fontShape, font: "", }); } /** * Return the CSS sizing classes required to switch from enclosing options * `oldOptions` to `this`. Returns an array of classes. */ sizingClasses(oldOptions: Options): Array { if (oldOptions.size !== this.size) { return ["sizing", "reset-size" + oldOptions.size, "size" + this.size]; } else { return []; } } /** * Return the CSS sizing classes required to switch to the base size. Like * `this.havingSize(BASESIZE).sizingClasses(this)`. */ baseSizingClasses(): Array { if (this.size !== Options.BASESIZE) { return ["sizing", "reset-size" + this.size, "size" + Options.BASESIZE]; } else { return []; } } /** * Return the font metrics for this size. */ fontMetrics(): FontMetrics { if (!this._fontMetrics) { this._fontMetrics = getGlobalMetrics(this.size); } return this._fontMetrics; } /** * Gets the CSS color of the current options object */ getColor(): string | void { if (this.phantom) { return "transparent"; } else { return this.color; } } } export default Options;