Stacking text commands (#1009)

* Adding support for SansSerif-Bold

* Updating to include SansSerif Italic.

* WIP

* Working text stacking

* More robust screenshot.

* Don't want to break users :)

* Updating per PR comments.

* Fixing Unicode and updating snapshots.

* Adding suggested tests.

* Opting to use old method for unit testing.

* Adding TODO
This commit is contained in:
Ryan Randall
2017-12-13 09:10:23 -05:00
committed by Kevin Barabash
parent cf23517499
commit 50765a0ccd
15 changed files with 533 additions and 48 deletions

View File

@@ -42,7 +42,11 @@ export type OptionsData = {
size?: number;
textSize?: number;
phantom?: boolean;
font?: string | void;
// TODO(#1009): Keep consistent with fontFamily/fontWeight. Ensure this has a
// string value.
fontFamily?: string | void;
fontWeight?: string;
fontShape?: string;
maxSize: number;
};
@@ -59,7 +63,9 @@ class Options {
size: number;
textSize: number;
phantom: boolean;
font: string | void;
fontFamily: string | void;
fontWeight: string;
fontShape: string;
sizeMultiplier: number;
maxSize: number;
_fontMetrics: FontMetrics | void;
@@ -75,7 +81,9 @@ class Options {
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._fontMetrics = undefined;
@@ -92,7 +100,9 @@ class Options {
textSize: this.textSize,
color: this.color,
phantom: this.phantom,
font: this.font,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
fontShape: this.fontShape,
maxSize: this.maxSize,
};
@@ -183,9 +193,27 @@ class Options {
/**
* Create a new options objects with the give font.
*/
withFont(font: ?string): Options {
withFontFamily(fontFamily: ?string): Options {
return this.extend({
font: font || this.font,
fontFamily: fontFamily || this.fontFamily,
});
}
/**
* Creates a new options object with the given font weight
*/
withFontWeight(fontWeight: string): Options {
return this.extend({
fontWeight,
});
}
/**
* Creates a new options object with the given font weight
*/
withFontShape(fontShape: string): Options {
return this.extend({
fontShape,
});
}

View File

@@ -115,7 +115,7 @@ const mathsym = function(
// text ordinal and is therefore not present as a symbol in the symbols
// table for text, as well as a special case for boldsymbol because it
// can be used for bold + and -
if ((options && options.font && options.font === "boldsymbol") &&
if ((options && options.fontFamily && options.fontFamily === "boldsymbol") &&
lookupSymbol(value, "Main-Bold", mode).metrics) {
return makeSymbol(value, "Main-Bold", mode, options,
classes.concat(["mathbf"]));
@@ -144,12 +144,17 @@ const mathDefault = function(
} else if (type === "textord") {
const font = symbols[mode][value] && symbols[mode][value].font;
if (font === "ams") {
const fontName = retrieveTextFontName("amsrm", options.fontWeight,
options.fontShape);
return makeSymbol(
value, "AMS-Regular", mode, options, classes.concat(["amsrm"]));
value, fontName, mode, options,
classes.concat("amsrm", options.fontWeight, options.fontShape));
} else { // if (font === "main") {
const fontName = retrieveTextFontName("textrm", options.fontWeight,
options.fontShape);
return makeSymbol(
value, "Main-Regular", mode, options,
classes.concat(["mathrm"]));
value, fontName, mode, options,
classes.concat(options.fontWeight, options.fontShape));
}
} else {
throw new Error("unexpected type: " + type + " in mathDefault");
@@ -224,19 +229,31 @@ const makeOrd = function(
const classes = ["mord"];
const font = options.font;
if (font) {
let fontLookup;
if (font === "boldsymbol") {
fontLookup = boldsymbol(value, mode, options, classes);
} else if (font === "mathit" || utils.contains(mainitLetters, value)) {
fontLookup = mathit(value, mode, options, classes);
const fontFamily = options.fontFamily;
if (fontFamily) {
let fontName;
let fontClasses;
if (fontFamily === "boldsymbol") {
const fontData = boldsymbol(value, mode, options, classes);
fontName = fontData.fontName;
fontClasses = [fontData.fontClass];
} else if (fontFamily === "mathit" ||
utils.contains(mainitLetters, value)) {
const fontData = mathit(value, mode, options, classes);
fontName = fontData.fontName;
fontClasses = [fontData.fontClass];
} else if (fontFamily.includes("math") || mode === "math") {
// To support old font functions (i.e. \rm \sf etc.) or math mode.
fontName = fontMap[fontFamily].fontName;
fontClasses = [fontFamily];
} else {
fontLookup = fontMap[font];
fontName = retrieveTextFontName(fontFamily, options.fontWeight,
options.fontShape);
fontClasses = [fontFamily, options.fontWeight, options.fontShape];
}
if (lookupSymbol(value, fontLookup.fontName, mode).metrics) {
return makeSymbol(value, fontLookup.fontName, mode, options,
classes.concat([fontLookup.fontClass || font]));
if (lookupSymbol(value, fontName, mode).metrics) {
return makeSymbol(value, fontName, mode, options,
classes.concat(fontClasses));
} else {
return mathDefault(value, mode, options, classes, type);
}
@@ -570,6 +587,52 @@ const makeVerb = function(group: ParseNode, options: Options): string {
return text;
};
// Takes an Options object, and returns the appropriate fontLookup
const retrieveTextFontName = function(
fontFamily: string,
fontWeight: string,
fontShape: string,
): string {
const baseFontName = retrieveBaseFontName(fontFamily);
const fontStylesName = retrieveFontStylesName(fontWeight, fontShape);
return `${baseFontName}-${fontStylesName}`;
};
const retrieveBaseFontName = function(font: string): string {
let baseFontName = "";
switch (font) {
case "amsrm":
baseFontName = "AMS";
break;
case "textrm":
baseFontName = "Main";
break;
case "textsf":
baseFontName = "SansSerif";
break;
case "texttt":
baseFontName = "Typewriter";
break;
default:
throw new Error(`Invalid font provided: ${font}`);
}
return baseFontName;
};
const retrieveFontStylesName = function(
fontWeight?: string,
fontShape?: string,
): string {
let fontStylesName = '';
if (fontWeight === "textbf") {
fontStylesName += "Bold";
}
if (fontShape === "textit") {
fontStylesName += "Italic";
}
return fontStylesName || "Regular";
};
// A map of spacing functions to their attributes, like size and corresponding
// CSS class
const spacingFunctions: {[string]: {| size: string, className: string |}} = {

View File

@@ -591,7 +591,7 @@ groupTypes.styling = function(group, options) {
groupTypes.font = function(group, options) {
const font = group.value.font;
return buildGroup(group.value.body, options.withFont(font));
return buildGroup(group.value.body, options.withFontFamily(font));
};
groupTypes.verb = function(group, options) {

View File

@@ -31,7 +31,7 @@ export const makeText = function(text, mode) {
* Returns the math variant as a string or null if none is required.
*/
const getVariant = function(group, options) {
const font = options.font;
const font = options.fontFamily;
if (!font) {
return null;
}
@@ -54,7 +54,7 @@ const getVariant = function(group, options) {
const fontName = buildCommon.fontMap[font].fontName;
if (fontMetrics.getCharacterMetrics(value, fontName)) {
return buildCommon.fontMap[options.font].variant;
return buildCommon.fontMap[font].variant;
}
return null;
@@ -268,7 +268,7 @@ groupTypes.spacing = function(group) {
groupTypes.font = function(group, options) {
const font = group.value.font;
return buildGroup(group.value.body, options.withFont(font));
return buildGroup(group.value.body, options.withFontFamily(font));
};
groupTypes.styling = function(group, options) {

View File

@@ -246,6 +246,9 @@ const getCharacterMetrics = function(
character: string,
font: string,
): ?CharacterMetrics {
if (!metricMap[font]) {
throw new Error(`Font metrics not found for font: ${font}.`);
}
let ch = character.charCodeAt(0);
if (character[0] in extraCharacterMap) {
ch = extraCharacterMap[character[0]].charCodeAt(0);

View File

@@ -1,5 +1,4 @@
// @flow
const fontMetricsData = {
module.exports = {
"AMS-Regular": {
"65": [0, 0.68889, 0, 0],
"66": [0, 0.68889, 0, 0],
@@ -1331,6 +1330,244 @@ const fontMetricsData = {
"1009": [0.19444, 0.43056, 0, 0.08334],
"1013": [0, 0.43056, 0, 0.05556],
},
"SansSerif-Bold": {
"33": [0, 0.69444, 0, 0],
"34": [0, 0.69444, 0, 0],
"35": [0.19444, 0.69444, 0, 0],
"36": [0.05556, 0.75, 0, 0],
"37": [0.05556, 0.75, 0, 0],
"38": [0, 0.69444, 0, 0],
"39": [0, 0.69444, 0, 0],
"40": [0.25, 0.75, 0, 0],
"41": [0.25, 0.75, 0, 0],
"42": [0, 0.75, 0, 0],
"43": [0.11667, 0.61667, 0, 0],
"44": [0.10556, 0.13056, 0, 0],
"45": [0, 0.45833, 0, 0],
"46": [0, 0.13056, 0, 0],
"47": [0.25, 0.75, 0, 0],
"48": [0, 0.69444, 0, 0],
"49": [0, 0.69444, 0, 0],
"50": [0, 0.69444, 0, 0],
"51": [0, 0.69444, 0, 0],
"52": [0, 0.69444, 0, 0],
"53": [0, 0.69444, 0, 0],
"54": [0, 0.69444, 0, 0],
"55": [0, 0.69444, 0, 0],
"56": [0, 0.69444, 0, 0],
"57": [0, 0.69444, 0, 0],
"58": [0, 0.45833, 0, 0],
"59": [0.10556, 0.45833, 0, 0],
"61": [-0.09375, 0.40625, 0, 0],
"63": [0, 0.69444, 0, 0],
"64": [0, 0.69444, 0, 0],
"65": [0, 0.69444, 0, 0],
"66": [0, 0.69444, 0, 0],
"67": [0, 0.69444, 0, 0],
"68": [0, 0.69444, 0, 0],
"69": [0, 0.69444, 0, 0],
"70": [0, 0.69444, 0, 0],
"71": [0, 0.69444, 0, 0],
"72": [0, 0.69444, 0, 0],
"73": [0, 0.69444, 0, 0],
"74": [0, 0.69444, 0, 0],
"75": [0, 0.69444, 0, 0],
"76": [0, 0.69444, 0, 0],
"77": [0, 0.69444, 0, 0],
"78": [0, 0.69444, 0, 0],
"79": [0, 0.69444, 0, 0],
"80": [0, 0.69444, 0, 0],
"81": [0.10556, 0.69444, 0, 0],
"82": [0, 0.69444, 0, 0],
"83": [0, 0.69444, 0, 0],
"84": [0, 0.69444, 0, 0],
"85": [0, 0.69444, 0, 0],
"86": [0, 0.69444, 0.01528, 0],
"87": [0, 0.69444, 0.01528, 0],
"88": [0, 0.69444, 0, 0],
"89": [0, 0.69444, 0.0275, 0],
"90": [0, 0.69444, 0, 0],
"91": [0.25, 0.75, 0, 0],
"93": [0.25, 0.75, 0, 0],
"94": [0, 0.69444, 0, 0],
"95": [0.35, 0.10833, 0.03056, 0],
"97": [0, 0.45833, 0, 0],
"98": [0, 0.69444, 0, 0],
"99": [0, 0.45833, 0, 0],
"100": [0, 0.69444, 0, 0],
"101": [0, 0.45833, 0, 0],
"102": [0, 0.69444, 0.07639, 0],
"103": [0.19444, 0.45833, 0.01528, 0],
"104": [0, 0.69444, 0, 0],
"105": [0, 0.69444, 0, 0],
"106": [0.19444, 0.69444, 0, 0],
"107": [0, 0.69444, 0, 0],
"108": [0, 0.69444, 0, 0],
"109": [0, 0.45833, 0, 0],
"110": [0, 0.45833, 0, 0],
"111": [0, 0.45833, 0, 0],
"112": [0.19444, 0.45833, 0, 0],
"113": [0.19444, 0.45833, 0, 0],
"114": [0, 0.45833, 0.01528, 0],
"115": [0, 0.45833, 0, 0],
"116": [0, 0.58929, 0, 0],
"117": [0, 0.45833, 0, 0],
"118": [0, 0.45833, 0.01528, 0],
"119": [0, 0.45833, 0.01528, 0],
"120": [0, 0.45833, 0, 0],
"121": [0.19444, 0.45833, 0.01528, 0],
"122": [0, 0.45833, 0, 0],
"126": [0.35, 0.34444, 0, 0],
"305": [0, 0.45833, 0, 0],
"567": [0.19444, 0.45833, 0, 0],
"768": [0, 0.69444, 0, 0],
"769": [0, 0.69444, 0, 0],
"770": [0, 0.69444, 0, 0],
"771": [0, 0.69444, 0, 0],
"772": [0, 0.63778, 0, 0],
"774": [0, 0.69444, 0, 0],
"775": [0, 0.69444, 0, 0],
"776": [0, 0.69444, 0, 0],
"778": [0, 0.69444, 0, 0],
"779": [0, 0.69444, 0, 0],
"780": [0, 0.63542, 0, 0],
"915": [0, 0.69444, 0, 0],
"916": [0, 0.69444, 0, 0],
"920": [0, 0.69444, 0, 0],
"923": [0, 0.69444, 0, 0],
"926": [0, 0.69444, 0, 0],
"928": [0, 0.69444, 0, 0],
"931": [0, 0.69444, 0, 0],
"933": [0, 0.69444, 0, 0],
"934": [0, 0.69444, 0, 0],
"936": [0, 0.69444, 0, 0],
"937": [0, 0.69444, 0, 0],
"8211": [0, 0.45833, 0.03056, 0],
"8212": [0, 0.45833, 0.03056, 0],
"8216": [0, 0.69444, 0, 0],
"8217": [0, 0.69444, 0, 0],
"8220": [0, 0.69444, 0, 0],
"8221": [0, 0.69444, 0, 0],
},
"SansSerif-Italic": {
"33": [0, 0.69444, 0.05733, 0],
"34": [0, 0.69444, 0.00316, 0],
"35": [0.19444, 0.69444, 0.05087, 0],
"36": [0.05556, 0.75, 0.11156, 0],
"37": [0.05556, 0.75, 0.03126, 0],
"38": [0, 0.69444, 0.03058, 0],
"39": [0, 0.69444, 0.07816, 0],
"40": [0.25, 0.75, 0.13164, 0],
"41": [0.25, 0.75, 0.02536, 0],
"42": [0, 0.75, 0.11775, 0],
"43": [0.08333, 0.58333, 0.02536, 0],
"44": [0.125, 0.08333, 0, 0],
"45": [0, 0.44444, 0.01946, 0],
"46": [0, 0.08333, 0, 0],
"47": [0.25, 0.75, 0.13164, 0],
"48": [0, 0.65556, 0.11156, 0],
"49": [0, 0.65556, 0.11156, 0],
"50": [0, 0.65556, 0.11156, 0],
"51": [0, 0.65556, 0.11156, 0],
"52": [0, 0.65556, 0.11156, 0],
"53": [0, 0.65556, 0.11156, 0],
"54": [0, 0.65556, 0.11156, 0],
"55": [0, 0.65556, 0.11156, 0],
"56": [0, 0.65556, 0.11156, 0],
"57": [0, 0.65556, 0.11156, 0],
"58": [0, 0.44444, 0.02502, 0],
"59": [0.125, 0.44444, 0.02502, 0],
"61": [-0.13, 0.37, 0.05087, 0],
"63": [0, 0.69444, 0.11809, 0],
"64": [0, 0.69444, 0.07555, 0],
"65": [0, 0.69444, 0, 0],
"66": [0, 0.69444, 0.08293, 0],
"67": [0, 0.69444, 0.11983, 0],
"68": [0, 0.69444, 0.07555, 0],
"69": [0, 0.69444, 0.11983, 0],
"70": [0, 0.69444, 0.13372, 0],
"71": [0, 0.69444, 0.11983, 0],
"72": [0, 0.69444, 0.08094, 0],
"73": [0, 0.69444, 0.13372, 0],
"74": [0, 0.69444, 0.08094, 0],
"75": [0, 0.69444, 0.11983, 0],
"76": [0, 0.69444, 0, 0],
"77": [0, 0.69444, 0.08094, 0],
"78": [0, 0.69444, 0.08094, 0],
"79": [0, 0.69444, 0.07555, 0],
"80": [0, 0.69444, 0.08293, 0],
"81": [0.125, 0.69444, 0.07555, 0],
"82": [0, 0.69444, 0.08293, 0],
"83": [0, 0.69444, 0.09205, 0],
"84": [0, 0.69444, 0.13372, 0],
"85": [0, 0.69444, 0.08094, 0],
"86": [0, 0.69444, 0.1615, 0],
"87": [0, 0.69444, 0.1615, 0],
"88": [0, 0.69444, 0.13372, 0],
"89": [0, 0.69444, 0.17261, 0],
"90": [0, 0.69444, 0.11983, 0],
"91": [0.25, 0.75, 0.15942, 0],
"93": [0.25, 0.75, 0.08719, 0],
"94": [0, 0.69444, 0.0799, 0],
"95": [0.35, 0.09444, 0.08616, 0],
"97": [0, 0.44444, 0.00981, 0],
"98": [0, 0.69444, 0.03057, 0],
"99": [0, 0.44444, 0.08336, 0],
"100": [0, 0.69444, 0.09483, 0],
"101": [0, 0.44444, 0.06778, 0],
"102": [0, 0.69444, 0.21705, 0],
"103": [0.19444, 0.44444, 0.10836, 0],
"104": [0, 0.69444, 0.01778, 0],
"105": [0, 0.67937, 0.09718, 0],
"106": [0.19444, 0.67937, 0.09162, 0],
"107": [0, 0.69444, 0.08336, 0],
"108": [0, 0.69444, 0.09483, 0],
"109": [0, 0.44444, 0.01778, 0],
"110": [0, 0.44444, 0.01778, 0],
"111": [0, 0.44444, 0.06613, 0],
"112": [0.19444, 0.44444, 0.0389, 0],
"113": [0.19444, 0.44444, 0.04169, 0],
"114": [0, 0.44444, 0.10836, 0],
"115": [0, 0.44444, 0.0778, 0],
"116": [0, 0.57143, 0.07225, 0],
"117": [0, 0.44444, 0.04169, 0],
"118": [0, 0.44444, 0.10836, 0],
"119": [0, 0.44444, 0.10836, 0],
"120": [0, 0.44444, 0.09169, 0],
"121": [0.19444, 0.44444, 0.10836, 0],
"122": [0, 0.44444, 0.08752, 0],
"126": [0.35, 0.32659, 0.08826, 0],
"305": [0, 0.44444, 0.04169, 0],
"567": [0.19444, 0.44444, 0.04169, 0],
"768": [0, 0.69444, 0, 0],
"769": [0, 0.69444, 0.09205, 0],
"770": [0, 0.69444, 0.0799, 0],
"771": [0, 0.67659, 0.08826, 0],
"772": [0, 0.60889, 0.08776, 0],
"774": [0, 0.69444, 0.09483, 0],
"775": [0, 0.67937, 0.07774, 0],
"776": [0, 0.67937, 0.06385, 0],
"778": [0, 0.69444, 0, 0],
"779": [0, 0.69444, 0.09205, 0],
"780": [0, 0.63194, 0.08432, 0],
"915": [0, 0.69444, 0.13372, 0],
"916": [0, 0.69444, 0, 0],
"920": [0, 0.69444, 0.07555, 0],
"923": [0, 0.69444, 0, 0],
"926": [0, 0.69444, 0.12816, 0],
"928": [0, 0.69444, 0.08094, 0],
"931": [0, 0.69444, 0.11983, 0],
"933": [0, 0.69444, 0.09031, 0],
"934": [0, 0.69444, 0.04603, 0],
"936": [0, 0.69444, 0.09031, 0],
"937": [0, 0.69444, 0.08293, 0],
"8211": [0, 0.44444, 0.08616, 0],
"8212": [0, 0.44444, 0.08616, 0],
"8216": [0, 0.69444, 0.07816, 0],
"8217": [0, 0.69444, 0.07816, 0],
"8220": [0, 0.69444, 0.14205, 0],
"8221": [0, 0.69444, 0.00316, 0],
},
"SansSerif-Regular": {
"33": [0, 0.69444, 0, 0],
"34": [0, 0.69444, 0, 0],
@@ -1746,13 +1983,9 @@ const fontMetricsData = {
"934": [0, 0.61111, 0, 0],
"936": [0, 0.61111, 0, 0],
"937": [0, 0.61111, 0, 0],
"2018": [0, 0.61111, 0, 0],
"2019": [0, 0.61111, 0, 0],
"8216": [0, 0.61111, 0, 0],
"8217": [0, 0.61111, 0, 0],
"8242": [0, 0.61111, 0, 0],
"9251": [0.11111, 0.21944, 0, 0],
},
};
export default fontMetricsData;

View File

@@ -7,17 +7,28 @@ import * as html from "../buildHTML";
import * as mml from "../buildMathML";
// Non-mathy text, possibly in a font
const textFunctionFonts = {
"\\text": undefined, "\\textrm": "mathrm", "\\textsf": "mathsf",
"\\texttt": "mathtt", "\\textnormal": "mathrm", "\\textbf": "mathbf",
const textFontFamilies = {
"\\text": undefined, "\\textrm": "textrm", "\\textsf": "textsf",
"\\texttt": "texttt", "\\textnormal": "textrm",
};
const textFontWeights = {
"\\textbf": "textbf",
};
const textFontShapes = {
"\\textit": "textit",
};
defineFunction({
type: "text",
names: [
// Font families
"\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal",
"\\textbf", "\\textit",
// Font weights
"\\textbf",
// Font Shapes
"\\textit",
],
props: {
numArgs: 1,
@@ -30,11 +41,20 @@ defineFunction({
return {
type: "text",
body: ordargument(body),
font: textFunctionFonts[context.funcName],
font: context.funcName,
};
},
htmlBuilder(group, options) {
const newOptions = options.withFont(group.value.font);
const font = group.value.font;
// Checks if the argument is a font family or a font style.
let newOptions;
if (textFontFamilies[font]) {
newOptions = options.withFontFamily(textFontFamilies[font]);
} else if (textFontWeights[font]) {
newOptions = options.withFontWeight(textFontWeights[font]);
} else {
newOptions = options.withFontShape(textFontShapes[font]);
}
const inner = html.buildExpression(group.value.body, newOptions, true);
buildCommon.tryCombineChars(inner);
return buildCommon.makeSpan(["mord", "text"],