// @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]; }