To @flow: fontMetrics, fontMetricsData, Options, Settings, Style (#848)

*     To @flow: fontMetrics, fontMetricsData, Options, Settings, Style

* Don't overuse $Shape, improve type checking for fontMetrics*, make maxSize required in OptionsData and update callers.

* Remove eslintrc globals change, since eslint-plugin-flowtype makes it redundant.

* Remove extra ?s in Options and Settings

* Undo removal of width in fontMetrics and switch to `T | void` for nullable types in Options

* fix typing of FontMetrics
This commit is contained in:
Xuming Zeng
2017-09-09 16:21:52 -05:00
committed by Kevin Barabash
parent ccf09786cc
commit 0f9fb0a1ce
8 changed files with 139 additions and 54 deletions

View File

@@ -1,3 +1,4 @@
// @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
@@ -6,8 +7,8 @@
*/
import fontMetrics from "./fontMetrics";
const BASESIZE = 6;
import type {FontMetrics} from "./fontMetrics";
import type {StyleInterface} from "./Style";
const sizeStyleMap = [
// Each element contains [textsize, scriptsize, scriptscriptsize].
@@ -31,10 +32,20 @@ const sizeMultipliers = [
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, style) {
const sizeAtStyle = function(size: number, style: StyleInterface): number {
return style.size < 2 ? size : sizeStyleMap[size - 1][style.size - 1];
};
export type OptionsData = {
style: StyleInterface;
color?: string | void;
size?: number;
textSize?: number;
phantom?: boolean;
font?: string | void;
maxSize: number;
};
/**
* This is the main options class. It contains the current style, size, color,
* and font.
@@ -43,23 +54,38 @@ const sizeAtStyle = function(size, style) {
* different properties, call a `.having*` method.
*/
class Options {
constructor(data) {
style: StyleInterface;
color: string | void;
size: number;
textSize: number;
phantom: boolean;
font: string | void;
sizeMultiplier: number;
maxSize: number;
_fontMetrics: FontMetrics | void;
/**
* The base size index.
*/
static BASESIZE = 6;
constructor(data: OptionsData) {
this.style = data.style;
this.color = data.color;
this.size = data.size || BASESIZE;
this.size = data.size || Options.BASESIZE;
this.textSize = data.textSize || this.size;
this.phantom = data.phantom;
this.phantom = !!data.phantom;
this.font = data.font;
this.sizeMultiplier = sizeMultipliers[this.size - 1];
this.maxSize = data.maxSize;
this._fontMetrics = null;
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) {
extend(extension: $Shape<OptionsData>): Options {
const data = {
style: this.style,
size: this.size,
@@ -83,7 +109,7 @@ class Options {
* Return an options object with the given style. If `this.style === style`,
* returns `this`.
*/
havingStyle(style) {
havingStyle(style: StyleInterface): Options {
if (this.style === style) {
return this;
} else {
@@ -98,7 +124,7 @@ class Options {
* Return an options object with a cramped version of the current style. If
* the current style is cramped, returns `this`.
*/
havingCrampedStyle() {
havingCrampedStyle(): Options {
return this.havingStyle(this.style.cramp());
}
@@ -106,7 +132,7 @@ class Options {
* Return an options object with the given size and in at least `\textstyle`.
* Returns `this` if appropriate.
*/
havingSize(size) {
havingSize(size: number): Options {
if (this.size === size && this.textSize === size) {
return this;
} else {
@@ -122,17 +148,16 @@ class Options {
* Like `this.havingSize(BASESIZE).havingStyle(style)`. If `style` is omitted,
* changes to at least `\textstyle`.
*/
havingBaseStyle(style) {
havingBaseStyle(style: StyleInterface): Options {
style = style || this.style.text();
const wantSize = sizeAtStyle(BASESIZE, style);
if (this.size === wantSize && this.textSize === BASESIZE
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,
baseSize: BASESIZE,
});
}
}
@@ -140,7 +165,7 @@ class Options {
/**
* Create a new options object with the given color.
*/
withColor(color) {
withColor(color: string): Options {
return this.extend({
color: color,
});
@@ -149,7 +174,7 @@ class Options {
/**
* Create a new options object with "phantom" set to true.
*/
withPhantom() {
withPhantom(): Options {
return this.extend({
phantom: true,
});
@@ -158,7 +183,7 @@ class Options {
/**
* Create a new options objects with the give font.
*/
withFont(font) {
withFont(font: ?string): Options {
return this.extend({
font: font || this.font,
});
@@ -168,7 +193,7 @@ class Options {
* Return the CSS sizing classes required to switch from enclosing options
* `oldOptions` to `this`. Returns an array of classes.
*/
sizingClasses(oldOptions) {
sizingClasses(oldOptions: Options): Array<string> {
if (oldOptions.size !== this.size) {
return ["sizing", "reset-size" + oldOptions.size, "size" + this.size];
} else {
@@ -180,9 +205,9 @@ class Options {
* Return the CSS sizing classes required to switch to the base size. Like
* `this.havingSize(BASESIZE).sizingClasses(this)`.
*/
baseSizingClasses() {
if (this.size !== BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + BASESIZE];
baseSizingClasses(): Array<string> {
if (this.size !== Options.BASESIZE) {
return ["sizing", "reset-size" + this.size, "size" + Options.BASESIZE];
} else {
return [];
}
@@ -191,7 +216,7 @@ class Options {
/**
* Return the font metrics for this size.
*/
fontMetrics() {
fontMetrics(): FontMetrics {
if (!this._fontMetrics) {
this._fontMetrics = fontMetrics.getFontMetrics(this.size);
}
@@ -265,18 +290,18 @@ class Options {
* Gets the CSS color of the current options object, accounting for the
* `colorMap`.
*/
getColor() {
getColor(): string | void {
if (this.phantom) {
return "transparent";
} else if (
this.color != null &&
Options.colorMap.hasOwnProperty(this.color)
) {
return Options.colorMap[this.color];
} else {
return Options.colorMap[this.color] || this.color;
return this.color;
}
}
}
/**
* The base size index.
*/
Options.BASESIZE = BASESIZE;
module.exports = Options;

View File

@@ -1,3 +1,4 @@
// @flow
/**
* This is a module for storing settings passed into KaTeX. It correctly handles
* default settings.
@@ -5,6 +6,15 @@
import utils from "./utils";
type SettingsOptions = {
displayMode?: boolean;
throwOnError?: boolean;
errorColor?: string;
macros?: {[macroName: string]: string};
colorIsTextColor?: boolean;
maxSize?: number;
};
/**
* The main Settings object
*
@@ -16,7 +26,14 @@ import utils from "./utils";
* and is placed in a block with vertical margin.
*/
class Settings {
constructor(options) {
displayMode: boolean;
throwOnError: boolean;
errorColor: string;
macros: {[macroName: string]: string};
colorIsTextColor: boolean;
maxSize: number;
constructor(options: SettingsOptions) {
// allow null options
options = options || {};
this.displayMode = utils.deflt(options.displayMode, false);

View File

@@ -1,3 +1,4 @@
// @flow
/**
* This file contains information and classes for the various kinds of styles
* used in TeX. It provides a generic `Style` class, which holds information
@@ -10,8 +11,12 @@
* The main style class. Contains a unique id for the style, a size (which is
* the same for cramped and uncramped version of a style), and a cramped flag.
*/
class Style {
constructor(id, size, cramped) {
class Style implements StyleInterface {
id: number;
size: number;
cramped: boolean;
constructor(id: number, size: number, cramped: boolean) {
this.id = id;
this.size = size;
this.cramped = cramped;
@@ -20,14 +25,14 @@ class Style {
/**
* Get the style of a superscript given a base in the current style.
*/
sup() {
sup(): Style {
return styles[sup[this.id]];
}
/**
* Get the style of a subscript given a base in the current style.
*/
sub() {
sub(): Style {
return styles[sub[this.id]];
}
@@ -35,7 +40,7 @@ class Style {
* Get the style of a fraction numerator given the fraction in the current
* style.
*/
fracNum() {
fracNum(): Style {
return styles[fracNum[this.id]];
}
@@ -43,7 +48,7 @@ class Style {
* Get the style of a fraction denominator given the fraction in the current
* style.
*/
fracDen() {
fracDen(): Style {
return styles[fracDen[this.id]];
}
@@ -51,25 +56,41 @@ class Style {
* Get the cramped version of a style (in particular, cramping a cramped style
* doesn't change the style).
*/
cramp() {
cramp(): Style {
return styles[cramp[this.id]];
}
/**
* Get a text or display version of this style.
*/
text() {
text(): Style {
return styles[text[this.id]];
}
/**
* Return if this style is tightly spaced (scriptstyle/scriptscriptstyle)
* Return true if this style is tightly spaced (scriptstyle/scriptscriptstyle)
*/
isTight() {
isTight(): boolean {
return this.size >= 2;
}
}
// Export an interface for type checking, but don't expose the implementation.
// This way, no more styles can be generated.
export interface StyleInterface {
id: number;
size: number;
cramped: boolean;
sup(): StyleInterface;
sub(): StyleInterface;
fracNum(): StyleInterface;
fracDen(): StyleInterface;
cramp(): StyleInterface;
text(): StyleInterface;
isTight(): boolean;
}
// IDs of the different styles
const D = 0;
const Dc = 1;
@@ -100,8 +121,7 @@ const fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc];
const cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc];
const text = [D, Dc, T, Tc, T, Tc, T, Tc];
// We only export some of the styles. Also, we don't export the `Style` class so
// no more styles can be generated.
// We only export some of the styles.
module.exports = {
DISPLAY: styles[D],
TEXT: styles[T],

View File

@@ -3,8 +3,7 @@ import functions from "./functions";
import {groupTypes as htmlGroupTypes} from "./buildHTML";
import {groupTypes as mathmlGroupTypes} from "./buildMathML";
// TODO(kevinb) use flow to define a proper type for Options
type Options = any;
import type Options from "./Options";
type FunctionSpec<T> = {
// Unique string to differentiate parse nodes.

View File

@@ -1,3 +1,4 @@
// @flow
import { cjkRegex } from "./unicodeRegexes";
/**
@@ -226,21 +227,32 @@ const extraCharacterMap = {
'я': 'r',
};
export type CharacterMetrics = {
depth: number;
height: number;
italic: number;
skew: number;
width: number;
};
/**
* This function is a convenience function for looking up information in the
* metricMap table. It takes a character as a string, and a style.
* metricMap table. It takes a character as a string, and a font.
*
* Note: the `width` property may be undefined if fontMetricsData.js wasn't
* built using `Make extended_metrics`.
*/
const getCharacterMetrics = function(character, style) {
const getCharacterMetrics = function(
character: string,
font: string,
): ?CharacterMetrics {
let ch = character.charCodeAt(0);
if (character[0] in extraCharacterMap) {
ch = extraCharacterMap[character[0]].charCodeAt(0);
} else if (cjkRegex.test(character[0])) {
ch = 'M'.charCodeAt(0);
}
const metrics = metricMap[style][ch];
const metrics = metricMap[font]['' + ch];
if (metrics) {
return {
depth: metrics[0],
@@ -252,13 +264,19 @@ const getCharacterMetrics = function(character, style) {
}
};
const fontMetricsBySizeIndex = {};
type FontSizeIndex = 0 | 1 | 2;
export type FontMetrics = {
cssEmPerMu: number,
[string]: number,
};
const fontMetricsBySizeIndex: {[FontSizeIndex]: FontMetrics} = {};
/**
* Get the font metrics for a given size.
*/
const getFontMetrics = function(size) {
let sizeIndex;
const getFontMetrics = function(size: number): FontMetrics {
let sizeIndex: FontSizeIndex;
if (size >= 5) {
sizeIndex = 0;
} else if (size >= 3) {
@@ -267,13 +285,14 @@ const getFontMetrics = function(size) {
sizeIndex = 2;
}
if (!fontMetricsBySizeIndex[sizeIndex]) {
const metrics = fontMetricsBySizeIndex[sizeIndex] = {};
const metrics = fontMetricsBySizeIndex[sizeIndex] = {
cssEmPerMu: sigmasAndXis.quad[sizeIndex] / 18,
};
for (const key in sigmasAndXis) {
if (sigmasAndXis.hasOwnProperty(key)) {
metrics[key] = sigmasAndXis[key][sizeIndex];
}
}
metrics.cssEmPerMu = metrics.quad / 18;
}
return fontMetricsBySizeIndex[sizeIndex];
};

View File

@@ -1,4 +1,5 @@
module.exports = {
// @flow
const fontMetricsData = {
"AMS-Regular": {
"65": [0, 0.68889, 0, 0],
"66": [0, 0.68889, 0, 0],
@@ -1750,3 +1751,5 @@ module.exports = {
"8242": [0, 0.61111, 0, 0],
},
};
module.exports = fontMetricsData;

View File

@@ -17,6 +17,7 @@ const defaultSettings = new Settings({});
const defaultOptions = new Options({
style: Style.TEXT,
size: 5,
maxSize: Infinity,
});
const _getBuilt = function(expr, settings) {

View File

@@ -21,6 +21,7 @@ const getMathML = function(expr, settings) {
// Setup the default options
const options = new Options({
style: startStyle,
maxSize: Infinity,
});
const built = buildMathML(parseTree(expr, usedSettings), expr, options);