1 | // @flow
|
2 | import {supportedCodepoint} from "./unicodeScripts";
|
3 |
|
4 | import 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.
|
36 | const 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.
|
91 | import 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.
|
100 | const 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 |
|
178 | export type CharacterMetrics = {
|
179 | depth: number;
|
180 | height: number;
|
181 | italic: number;
|
182 | skew: number;
|
183 | width: number;
|
184 | };
|
185 |
|
186 | export 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 | */
|
194 | export 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 | */
|
205 | export 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 |
|
245 | type FontSizeIndex = 0 | 1 | 2;
|
246 | export type FontMetrics = {
|
247 | cssEmPerMu: number,
|
248 | [string]: number,
|
249 | };
|
250 |
|
251 | const fontMetricsBySizeIndex: {[FontSizeIndex]: FontMetrics} = {};
|
252 |
|
253 | /**
|
254 | * Get the font metrics for a given size.
|
255 | */
|
256 | export 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 | }
|