UNPKG

8.69 kBJavaScriptView Raw
1// @flow
2import {supportedCodepoint} from "./unicodeScripts";
3
4import type {Mode} from "./types";
5
6/**
7 * This file contains metrics regarding fonts and individual symbols. The sigma
8 * and xi variables, as well as the metricMap map contain data extracted from
9 * TeX, TeX font metrics, and the TTF files. These data are then exposed via the
10 * `metrics` variable and the getCharacterMetrics function.
11 */
12
13// In TeX, there are actually three sets of dimensions, one for each of
14// textstyle (size index 5 and higher: >=9pt), scriptstyle (size index 3 and 4:
15// 7-8pt), and scriptscriptstyle (size index 1 and 2: 5-6pt). These are
16// provided in the the arrays below, in that order.
17//
18// The font metrics are stored in fonts cmsy10, cmsy7, and cmsy5 respsectively.
19// This was determined by running the following script:
20//
21// latex -interaction=nonstopmode \
22// '\documentclass{article}\usepackage{amsmath}\begin{document}' \
23// '$a$ \expandafter\show\the\textfont2' \
24// '\expandafter\show\the\scriptfont2' \
25// '\expandafter\show\the\scriptscriptfont2' \
26// '\stop'
27//
28// The metrics themselves were retreived using the following commands:
29//
30// tftopl cmsy10
31// tftopl cmsy7
32// tftopl cmsy5
33//
34// The output of each of these commands is quite lengthy. The only part we
35// care about is the FONTDIMEN section. Each value is measured in EMs.
36const sigmasAndXis = {
37 slant: [0.250, 0.250, 0.250], // sigma1
38 space: [0.000, 0.000, 0.000], // sigma2
39 stretch: [0.000, 0.000, 0.000], // sigma3
40 shrink: [0.000, 0.000, 0.000], // sigma4
41 xHeight: [0.431, 0.431, 0.431], // sigma5
42 quad: [1.000, 1.171, 1.472], // sigma6
43 extraSpace: [0.000, 0.000, 0.000], // sigma7
44 num1: [0.677, 0.732, 0.925], // sigma8
45 num2: [0.394, 0.384, 0.387], // sigma9
46 num3: [0.444, 0.471, 0.504], // sigma10
47 denom1: [0.686, 0.752, 1.025], // sigma11
48 denom2: [0.345, 0.344, 0.532], // sigma12
49 sup1: [0.413, 0.503, 0.504], // sigma13
50 sup2: [0.363, 0.431, 0.404], // sigma14
51 sup3: [0.289, 0.286, 0.294], // sigma15
52 sub1: [0.150, 0.143, 0.200], // sigma16
53 sub2: [0.247, 0.286, 0.400], // sigma17
54 supDrop: [0.386, 0.353, 0.494], // sigma18
55 subDrop: [0.050, 0.071, 0.100], // sigma19
56 delim1: [2.390, 1.700, 1.980], // sigma20
57 delim2: [1.010, 1.157, 1.420], // sigma21
58 axisHeight: [0.250, 0.250, 0.250], // sigma22
59
60 // These font metrics are extracted from TeX by using tftopl on cmex10.tfm;
61 // they correspond to the font parameters of the extension fonts (family 3).
62 // See the TeXbook, page 441. In AMSTeX, the extension fonts scale; to
63 // match cmex7, we'd use cmex7.tfm values for script and scriptscript
64 // values.
65 defaultRuleThickness: [0.04, 0.049, 0.049], // xi8; cmex7: 0.049
66 bigOpSpacing1: [0.111, 0.111, 0.111], // xi9
67 bigOpSpacing2: [0.166, 0.166, 0.166], // xi10
68 bigOpSpacing3: [0.2, 0.2, 0.2], // xi11
69 bigOpSpacing4: [0.6, 0.611, 0.611], // xi12; cmex7: 0.611
70 bigOpSpacing5: [0.1, 0.143, 0.143], // xi13; cmex7: 0.143
71
72 // The \sqrt rule width is taken from the height of the surd character.
73 // Since we use the same font at all sizes, this thickness doesn't scale.
74 sqrtRuleThickness: [0.04, 0.04, 0.04],
75
76 // This value determines how large a pt is, for metrics which are defined
77 // in terms of pts.
78 // This value is also used in katex.less; if you change it make sure the
79 // values match.
80 ptPerEm: [10.0, 10.0, 10.0],
81
82 // The space between adjacent `|` columns in an array definition. From
83 // `\showthe\doublerulesep` in LaTeX. Equals 2.0 / ptPerEm.
84 doubleRuleSep: [0.2, 0.2, 0.2],
85};
86
87// This map contains a mapping from font name and character code to character
88// metrics, including height, depth, italic correction, and skew (kern from the
89// character to the corresponding \skewchar)
90// This map is generated via `make metrics`. It should not be changed manually.
91import metricMap from "../submodules/katex-fonts/fontMetricsData";
92
93// These are very rough approximations. We default to Times New Roman which
94// should have Latin-1 and Cyrillic characters, but may not depending on the
95// operating system. The metrics do not account for extra height from the
96// accents. In the case of Cyrillic characters which have both ascenders and
97// descenders we prefer approximations with ascenders, primarily to prevent
98// the fraction bar or root line from intersecting the glyph.
99// TODO(kevinb) allow union of multiple glyph metrics for better accuracy.
100const extraCharacterMap = {
101 // Latin-1
102 'Å': 'A',
103 'Ç': 'C',
104 'Ð': 'D',
105 'Þ': 'o',
106 'å': 'a',
107 'ç': 'c',
108 'ð': 'd',
109 'þ': 'o',
110
111 // Cyrillic
112 'А': 'A',
113 'Б': 'B',
114 'В': 'B',
115 'Г': 'F',
116 'Д': 'A',
117 'Е': 'E',
118 'Ж': 'K',
119 'З': '3',
120 'И': 'N',
121 'Й': 'N',
122 'К': 'K',
123 'Л': 'N',
124 'М': 'M',
125 'Н': 'H',
126 'О': 'O',
127 'П': 'N',
128 'Р': 'P',
129 'С': 'C',
130 'Т': 'T',
131 'У': 'y',
132 'Ф': 'O',
133 'Х': 'X',
134 'Ц': 'U',
135 'Ч': 'h',
136 'Ш': 'W',
137 'Щ': 'W',
138 'Ъ': 'B',
139 'Ы': 'X',
140 'Ь': 'B',
141 'Э': '3',
142 'Ю': 'X',
143 'Я': 'R',
144 'а': 'a',
145 'б': 'b',
146 'в': 'a',
147 'г': 'r',
148 'д': 'y',
149 'е': 'e',
150 'ж': 'm',
151 'з': 'e',
152 'и': 'n',
153 'й': 'n',
154 'к': 'n',
155 'л': 'n',
156 'м': 'm',
157 'н': 'n',
158 'о': 'o',
159 'п': 'n',
160 'р': 'p',
161 'с': 'c',
162 'т': 'o',
163 'у': 'y',
164 'ф': 'b',
165 'х': 'x',
166 'ц': 'n',
167 'ч': 'n',
168 'ш': 'w',
169 'щ': 'w',
170 'ъ': 'a',
171 'ы': 'm',
172 'ь': 'a',
173 'э': 'e',
174 'ю': 'm',
175 'я': 'r',
176};
177
178export type CharacterMetrics = {
179 depth: number;
180 height: number;
181 italic: number;
182 skew: number;
183 width: number;
184};
185
186export type MetricMap = {
187 [string]: number[]
188}
189
190/**
191 * This function adds new font metrics to default metricMap
192 * It can also override existing metrics
193 */
194export function setFontMetrics(fontName: string, metrics: MetricMap) {
195 metricMap[fontName] = metrics;
196}
197
198/**
199 * This function is a convenience function for looking up information in the
200 * metricMap table. It takes a character as a string, and a font.
201 *
202 * Note: the `width` property may be undefined if fontMetricsData.js wasn't
203 * built using `Make extended_metrics`.
204 */
205export function getCharacterMetrics(
206 character: string,
207 font: string,
208 mode: Mode,
209): ?CharacterMetrics {
210 if (!metricMap[font]) {
211 throw new Error(`Font metrics not found for font: ${font}.`);
212 }
213 let ch = character.charCodeAt(0);
214 let metrics = metricMap[font][ch];
215 if (!metrics && character[0] in extraCharacterMap) {
216 ch = extraCharacterMap[character[0]].charCodeAt(0);
217 metrics = metricMap[font][ch];
218 }
219
220 if (!metrics && mode === 'text') {
221 // We don't typically have font metrics for Asian scripts.
222 // But since we support them in text mode, we need to return
223 // some sort of metrics.
224 // So if the character is in a script we support but we
225 // don't have metrics for it, just use the metrics for
226 // the Latin capital letter M. This is close enough because
227 // we (currently) only care about the height of the glpyh
228 // not its width.
229 if (supportedCodepoint(ch)) {
230 metrics = metricMap[font][77]; // 77 is the charcode for 'M'
231 }
232 }
233
234 if (metrics) {
235 return {
236 depth: metrics[0],
237 height: metrics[1],
238 italic: metrics[2],
239 skew: metrics[3],
240 width: metrics[4],
241 };
242 }
243}
244
245type FontSizeIndex = 0 | 1 | 2;
246export type FontMetrics = {
247 cssEmPerMu: number,
248 [string]: number,
249};
250
251const fontMetricsBySizeIndex: {[FontSizeIndex]: FontMetrics} = {};
252
253/**
254 * Get the font metrics for a given size.
255 */
256export function getGlobalMetrics(size: number): FontMetrics {
257 let sizeIndex: FontSizeIndex;
258 if (size >= 5) {
259 sizeIndex = 0;
260 } else if (size >= 3) {
261 sizeIndex = 1;
262 } else {
263 sizeIndex = 2;
264 }
265 if (!fontMetricsBySizeIndex[sizeIndex]) {
266 const metrics = fontMetricsBySizeIndex[sizeIndex] = {
267 cssEmPerMu: sigmasAndXis.quad[sizeIndex] / 18,
268 };
269 for (const key in sigmasAndXis) {
270 if (sigmasAndXis.hasOwnProperty(key)) {
271 metrics[key] = sigmasAndXis[key][sizeIndex];
272 }
273 }
274 }
275 return fontMetricsBySizeIndex[sizeIndex];
276}