283 lines
8.7 KiB
JavaScript
283 lines
8.7 KiB
JavaScript
|
// @flow
|
|||
|
import {supportedCodepoint} from "./unicodeScripts";
|
|||
|
|
|||
|
import type {Mode} from "./types";
|
|||
|
|
|||
|
/**
|
|||
|
* This file contains metrics regarding fonts and individual symbols. The sigma
|
|||
|
* and xi variables, as well as the metricMap map contain data extracted from
|
|||
|
* TeX, TeX font metrics, and the TTF files. These data are then exposed via the
|
|||
|
* `metrics` variable and the getCharacterMetrics function.
|
|||
|
*/
|
|||
|
|
|||
|
// In TeX, there are actually three sets of dimensions, one for each of
|
|||
|
// textstyle (size index 5 and higher: >=9pt), scriptstyle (size index 3 and 4:
|
|||
|
// 7-8pt), and scriptscriptstyle (size index 1 and 2: 5-6pt). These are
|
|||
|
// provided in the arrays below, in that order.
|
|||
|
//
|
|||
|
// The font metrics are stored in fonts cmsy10, cmsy7, and cmsy5 respectively.
|
|||
|
// This was determined by running the following script:
|
|||
|
//
|
|||
|
// latex -interaction=nonstopmode \
|
|||
|
// '\documentclass{article}\usepackage{amsmath}\begin{document}' \
|
|||
|
// '$a$ \expandafter\show\the\textfont2' \
|
|||
|
// '\expandafter\show\the\scriptfont2' \
|
|||
|
// '\expandafter\show\the\scriptscriptfont2' \
|
|||
|
// '\stop'
|
|||
|
//
|
|||
|
// The metrics themselves were retrieved using the following commands:
|
|||
|
//
|
|||
|
// tftopl cmsy10
|
|||
|
// tftopl cmsy7
|
|||
|
// tftopl cmsy5
|
|||
|
//
|
|||
|
// The output of each of these commands is quite lengthy. The only part we
|
|||
|
// care about is the FONTDIMEN section. Each value is measured in EMs.
|
|||
|
const sigmasAndXis = {
|
|||
|
slant: [0.250, 0.250, 0.250], // sigma1
|
|||
|
space: [0.000, 0.000, 0.000], // sigma2
|
|||
|
stretch: [0.000, 0.000, 0.000], // sigma3
|
|||
|
shrink: [0.000, 0.000, 0.000], // sigma4
|
|||
|
xHeight: [0.431, 0.431, 0.431], // sigma5
|
|||
|
quad: [1.000, 1.171, 1.472], // sigma6
|
|||
|
extraSpace: [0.000, 0.000, 0.000], // sigma7
|
|||
|
num1: [0.677, 0.732, 0.925], // sigma8
|
|||
|
num2: [0.394, 0.384, 0.387], // sigma9
|
|||
|
num3: [0.444, 0.471, 0.504], // sigma10
|
|||
|
denom1: [0.686, 0.752, 1.025], // sigma11
|
|||
|
denom2: [0.345, 0.344, 0.532], // sigma12
|
|||
|
sup1: [0.413, 0.503, 0.504], // sigma13
|
|||
|
sup2: [0.363, 0.431, 0.404], // sigma14
|
|||
|
sup3: [0.289, 0.286, 0.294], // sigma15
|
|||
|
sub1: [0.150, 0.143, 0.200], // sigma16
|
|||
|
sub2: [0.247, 0.286, 0.400], // sigma17
|
|||
|
supDrop: [0.386, 0.353, 0.494], // sigma18
|
|||
|
subDrop: [0.050, 0.071, 0.100], // sigma19
|
|||
|
delim1: [2.390, 1.700, 1.980], // sigma20
|
|||
|
delim2: [1.010, 1.157, 1.420], // sigma21
|
|||
|
axisHeight: [0.250, 0.250, 0.250], // sigma22
|
|||
|
|
|||
|
// These font metrics are extracted from TeX by using tftopl on cmex10.tfm;
|
|||
|
// they correspond to the font parameters of the extension fonts (family 3).
|
|||
|
// See the TeXbook, page 441. In AMSTeX, the extension fonts scale; to
|
|||
|
// match cmex7, we'd use cmex7.tfm values for script and scriptscript
|
|||
|
// values.
|
|||
|
defaultRuleThickness: [0.04, 0.049, 0.049], // xi8; cmex7: 0.049
|
|||
|
bigOpSpacing1: [0.111, 0.111, 0.111], // xi9
|
|||
|
bigOpSpacing2: [0.166, 0.166, 0.166], // xi10
|
|||
|
bigOpSpacing3: [0.2, 0.2, 0.2], // xi11
|
|||
|
bigOpSpacing4: [0.6, 0.611, 0.611], // xi12; cmex7: 0.611
|
|||
|
bigOpSpacing5: [0.1, 0.143, 0.143], // xi13; cmex7: 0.143
|
|||
|
|
|||
|
// The \sqrt rule width is taken from the height of the surd character.
|
|||
|
// Since we use the same font at all sizes, this thickness doesn't scale.
|
|||
|
sqrtRuleThickness: [0.04, 0.04, 0.04],
|
|||
|
|
|||
|
// This value determines how large a pt is, for metrics which are defined
|
|||
|
// in terms of pts.
|
|||
|
// This value is also used in katex.less; if you change it make sure the
|
|||
|
// values match.
|
|||
|
ptPerEm: [10.0, 10.0, 10.0],
|
|||
|
|
|||
|
// The space between adjacent `|` columns in an array definition. From
|
|||
|
// `\showthe\doublerulesep` in LaTeX. Equals 2.0 / ptPerEm.
|
|||
|
doubleRuleSep: [0.2, 0.2, 0.2],
|
|||
|
|
|||
|
// The width of separator lines in {array} environments. From
|
|||
|
// `\showthe\arrayrulewidth` in LaTeX. Equals 0.4 / ptPerEm.
|
|||
|
arrayRuleWidth: [0.04, 0.04, 0.04],
|
|||
|
|
|||
|
// Two values from LaTeX source2e:
|
|||
|
fboxsep: [0.3, 0.3, 0.3], // 3 pt / ptPerEm
|
|||
|
fboxrule: [0.04, 0.04, 0.04], // 0.4 pt / ptPerEm
|
|||
|
};
|
|||
|
|
|||
|
// This map contains a mapping from font name and character code to character
|
|||
|
// metrics, including height, depth, italic correction, and skew (kern from the
|
|||
|
// character to the corresponding \skewchar)
|
|||
|
// This map is generated via `make metrics`. It should not be changed manually.
|
|||
|
import metricMap from "./fontMetricsData";
|
|||
|
|
|||
|
// These are very rough approximations. We default to Times New Roman which
|
|||
|
// should have Latin-1 and Cyrillic characters, but may not depending on the
|
|||
|
// operating system. The metrics do not account for extra height from the
|
|||
|
// accents. In the case of Cyrillic characters which have both ascenders and
|
|||
|
// descenders we prefer approximations with ascenders, primarily to prevent
|
|||
|
// the fraction bar or root line from intersecting the glyph.
|
|||
|
// TODO(kevinb) allow union of multiple glyph metrics for better accuracy.
|
|||
|
const extraCharacterMap = {
|
|||
|
// Latin-1
|
|||
|
'Å': 'A',
|
|||
|
'Ð': 'D',
|
|||
|
'Þ': 'o',
|
|||
|
'å': 'a',
|
|||
|
'ð': 'd',
|
|||
|
'þ': 'o',
|
|||
|
|
|||
|
// Cyrillic
|
|||
|
'А': 'A',
|
|||
|
'Б': 'B',
|
|||
|
'В': 'B',
|
|||
|
'Г': 'F',
|
|||
|
'Д': 'A',
|
|||
|
'Е': 'E',
|
|||
|
'Ж': 'K',
|
|||
|
'З': '3',
|
|||
|
'И': 'N',
|
|||
|
'Й': 'N',
|
|||
|
'К': 'K',
|
|||
|
'Л': 'N',
|
|||
|
'М': 'M',
|
|||
|
'Н': 'H',
|
|||
|
'О': 'O',
|
|||
|
'П': 'N',
|
|||
|
'Р': 'P',
|
|||
|
'С': 'C',
|
|||
|
'Т': 'T',
|
|||
|
'У': 'y',
|
|||
|
'Ф': 'O',
|
|||
|
'Х': 'X',
|
|||
|
'Ц': 'U',
|
|||
|
'Ч': 'h',
|
|||
|
'Ш': 'W',
|
|||
|
'Щ': 'W',
|
|||
|
'Ъ': 'B',
|
|||
|
'Ы': 'X',
|
|||
|
'Ь': 'B',
|
|||
|
'Э': '3',
|
|||
|
'Ю': 'X',
|
|||
|
'Я': 'R',
|
|||
|
'а': 'a',
|
|||
|
'б': 'b',
|
|||
|
'в': 'a',
|
|||
|
'г': 'r',
|
|||
|
'д': 'y',
|
|||
|
'е': 'e',
|
|||
|
'ж': 'm',
|
|||
|
'з': 'e',
|
|||
|
'и': 'n',
|
|||
|
'й': 'n',
|
|||
|
'к': 'n',
|
|||
|
'л': 'n',
|
|||
|
'м': 'm',
|
|||
|
'н': 'n',
|
|||
|
'о': 'o',
|
|||
|
'п': 'n',
|
|||
|
'р': 'p',
|
|||
|
'с': 'c',
|
|||
|
'т': 'o',
|
|||
|
'у': 'y',
|
|||
|
'ф': 'b',
|
|||
|
'х': 'x',
|
|||
|
'ц': 'n',
|
|||
|
'ч': 'n',
|
|||
|
'ш': 'w',
|
|||
|
'щ': 'w',
|
|||
|
'ъ': 'a',
|
|||
|
'ы': 'm',
|
|||
|
'ь': 'a',
|
|||
|
'э': 'e',
|
|||
|
'ю': 'm',
|
|||
|
'я': 'r',
|
|||
|
};
|
|||
|
|
|||
|
export type CharacterMetrics = {
|
|||
|
depth: number;
|
|||
|
height: number;
|
|||
|
italic: number;
|
|||
|
skew: number;
|
|||
|
width: number;
|
|||
|
};
|
|||
|
|
|||
|
export type MetricMap = {
|
|||
|
[string]: number[]
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* This function adds new font metrics to default metricMap
|
|||
|
* It can also override existing metrics
|
|||
|
*/
|
|||
|
export function setFontMetrics(fontName: string, metrics: MetricMap) {
|
|||
|
metricMap[fontName] = metrics;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* This function is a convenience function for looking up information in the
|
|||
|
* 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`.
|
|||
|
*/
|
|||
|
export function getCharacterMetrics(
|
|||
|
character: string,
|
|||
|
font: string,
|
|||
|
mode: Mode,
|
|||
|
): ?CharacterMetrics {
|
|||
|
if (!metricMap[font]) {
|
|||
|
throw new Error(`Font metrics not found for font: ${font}.`);
|
|||
|
}
|
|||
|
let ch = character.charCodeAt(0);
|
|||
|
let metrics = metricMap[font][ch];
|
|||
|
if (!metrics && character[0] in extraCharacterMap) {
|
|||
|
ch = extraCharacterMap[character[0]].charCodeAt(0);
|
|||
|
metrics = metricMap[font][ch];
|
|||
|
}
|
|||
|
|
|||
|
if (!metrics && mode === 'text') {
|
|||
|
// We don't typically have font metrics for Asian scripts.
|
|||
|
// But since we support them in text mode, we need to return
|
|||
|
// some sort of metrics.
|
|||
|
// So if the character is in a script we support but we
|
|||
|
// don't have metrics for it, just use the metrics for
|
|||
|
// the Latin capital letter M. This is close enough because
|
|||
|
// we (currently) only care about the height of the glyph
|
|||
|
// not its width.
|
|||
|
if (supportedCodepoint(ch)) {
|
|||
|
metrics = metricMap[font][77]; // 77 is the charcode for 'M'
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (metrics) {
|
|||
|
return {
|
|||
|
depth: metrics[0],
|
|||
|
height: metrics[1],
|
|||
|
italic: metrics[2],
|
|||
|
skew: metrics[3],
|
|||
|
width: metrics[4],
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
type FontSizeIndex = 0 | 1 | 2;
|
|||
|
export type FontMetrics = {
|
|||
|
cssEmPerMu: number,
|
|||
|
[string]: number,
|
|||
|
};
|
|||
|
|
|||
|
const fontMetricsBySizeIndex: {[FontSizeIndex]: FontMetrics} = {};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the font metrics for a given size.
|
|||
|
*/
|
|||
|
export function getGlobalMetrics(size: number): FontMetrics {
|
|||
|
let sizeIndex: FontSizeIndex;
|
|||
|
if (size >= 5) {
|
|||
|
sizeIndex = 0;
|
|||
|
} else if (size >= 3) {
|
|||
|
sizeIndex = 1;
|
|||
|
} else {
|
|||
|
sizeIndex = 2;
|
|||
|
}
|
|||
|
if (!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];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return fontMetricsBySizeIndex[sizeIndex];
|
|||
|
}
|