Repository: KaTeX/KaTeX (js)

Issue:
Warning: No character metrics for '中' in style 'Main-Regular'
When I write Chinese characters in a math code block:

```latex
$$
f(x) = 测试中文
$$
```

They will be rendered properly but I still got a warning like this in the console:

```node
katex.js:5308 No character metrics for '中' in style 'Main-Regular'
```

This is a screenshot:

![image](https://user-images.githubusercontent.com/24741764/50574395-feeafa00-0e22-11e9-820a-194588842254.png)

Follow the call stack, I can see the warning was logged at: https://github.com/KaTeX/KaTeX/blob/8346294bf30b438379bea17f5e626fb546f55d62/src/buildCommon.js#L82-L85

I have tried setting `strict` option to `false`. But it only disables `LaTeX-incompatible` warning, the problem above still exists. Could you please explain what does this mean, and how can I avoid it?

Pull request:
Unicode characters in math render in text mode
Improve #2031 by rendering all supported Unicode text characters (via `supportedCodepoints`) in text mode, roughly mimicking wrapping them in `\text`, thereby using metrics of letter M as usual.

I confirmed that the tests only work with the new (one-line) change.

Here's what something like #2031's example looks like now; the remaining overlap is because the characters are actually taller than an M:

```latex
F_e=\dfrac{可改进部分占用时间}{\text{MMM}改进前总时间}\\
F_e=\dfrac{\text{M}可改进部分占用时间}{改进前总时间}
```

![image](https://user-images.githubusercontent.com/2218736/61189509-63053b00-a65c-11e9-91a1-11b7b8f91de1.png)

The Ms are just for illustrative purposes; with this PR, they don't affect the vertical layout:

![image](https://user-images.githubusercontent.com/2218736/61189541-e45ccd80-a65c-11e9-802c-d0d708c050e3.png)

Here's the without-Ms version with currently published KaTeX: substantially more overlap.

![image](https://user-images.githubusercontent.com/2218736/61189550-fa6a8e00-a65c-11e9-8823-68a20927e236.png)

This PR will also fix #1817.

Patch:
diff --git a/docs/supported.md b/docs/supported.md
index 49526229..74c745d5 100644
--- a/docs/supported.md
+++ b/docs/supported.md
@@ -180,9 +180,14 @@ Direct Input: $∂ ∇ ℑ Ⅎ ℵ ℶ ℷ ℸ ⅁ ℏ ð$
 
 **Unicode**
 
-The letters listed above will render in any KaTeX rendering mode.
+The letters listed above will render properly in any KaTeX rendering mode.
 
-If the KaTeX rendering mode is set to `strict: false` or `strict:"warn"` (default), then KaTeX will accept all Unicode letters. The letters not listed above will be rendered from system fonts, not KaTeX-supplied fonts, so their typography may clash. They may also cause small vertical alignment issues. KaTeX has detailed metrics for glyphs in Latin, Greek, and Cyrillic, but other glyphs are treated as if they are each as tall as the letter M.
+In addition, Brahmic, Georgian, Chinese, Japanese, and Korean glyphs are always accepted in text mode. However, these glyphs will be rendered from system fonts (not KaTeX-supplied fonts) so their typography may clash.
+You can provide rules for CSS classes `.latin-fallback`, `.cyrillic-fallback`, `.brahmic-fallback`, `.georgian-fallback`, `.cjk-fallback`, and `.hangul-fallback` to provide fallback fonts for these languages.
+Use of these glyphs may cause small vertical alignment issues: KaTeX has detailed metrics for listed symbols and most Latin, Greek, and Cyrillic letters, but other accepted glyphs are treated as if they are each as tall as the letter M in the current KaTeX font.
+
+If the KaTeX rendering mode is set to `strict: false` or `strict: "warn"` (default), then KaTeX will accept all Unicode letters in both text and math mode.
+All unrecognized characters will be treated as if they appeared in text mode, and are subject to the same issues of using system fonts and possibly using incorrect vertical alignment.
 
 For Persian composite characters, a user-supplied [plug-in](https://github.com/HosseinAgha/persian-katex-plugin) is under development.
 
diff --git a/src/Parser.js b/src/Parser.js
index a8efaac9..ba6ab750 100644
--- a/src/Parser.js
+++ b/src/Parser.js
@@ -971,9 +971,16 @@ export default class Parser {
                         nucleus);
                 }
             }
+            // All nonmathematical Unicode characters are rendered as if they
+            // are in text mode (wrapped in \text) because that's what it
+            // takes to render them in LaTeX.  Setting `mode: this.mode` is
+            // another natural choice (the user requested math mode), but
+            // this makes it more difficult for getCharacterMetrics() to
+            // distinguish Unicode characters without metrics and those for
+            // which we want to simulate the letter M.
             symbol = {
                 type: "textord",
-                mode: this.mode,
+                mode: "text",
                 loc: SourceLocation.range(nucleus),
                 text,
             };
diff --git a/src/buildCommon.js b/src/buildCommon.js
index e690fef8..4622b6ce 100644
--- a/src/buildCommon.js
+++ b/src/buildCommon.js
@@ -80,9 +80,8 @@ const makeSymbol = function(
             metrics.width, classes);
     } else {
         // TODO(emily): Figure out a good way to only print this in development
-        typeof console !== "undefined" && console.warn(
-            "No character metrics for '" + value + "' in style '" +
-                fontName + "'");
+        typeof console !== "undefined" && console.warn("No character metrics " +
+            `for '${value}' in style '${fontName}' and mode '${mode}'`);
         symbolNode = new SymbolNode(value, 0, 0, 0, 0, 0, classes);
     }
 


Test patch:
diff --git a/test/unicode-spec.js b/test/unicode-spec.js
index e6fea9b8..eabc457d 100644
--- a/test/unicode-spec.js
+++ b/test/unicode-spec.js
@@ -9,7 +9,7 @@ import {strictSettings, nonstrictSettings} from "./helpers";
 describe("unicode", function() {
     it("should parse Latin-1 inside \\text{}", function() {
         expect`\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿÆÇÐØÞßæçðøþ}`
-            .toParse();
+            .toBuild();
     });
 
     it("should not parse Latin-1 outside \\text{} with strict", function() {
@@ -21,19 +21,23 @@ describe("unicode", function() {
 
     it("should parse Latin-1 outside \\text{}", function() {
         expect`ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿÇÐÞçðþ`
-            .toParse(nonstrictSettings);
+            .toBuild(nonstrictSettings);
     });
 
     it("should parse all lower case Greek letters", function() {
-        expect`αβγδεϵζηθϑικλμνξοπϖρϱςστυφϕχψω`.toParse();
+        expect`αβγδεϵζηθϑικλμνξοπϖρϱςστυφϕχψω`.toBuild();
     });
 
     it("should parse math upper case Greek letters", function() {
-        expect`ΓΔΘΛΞΠΣΥΦΨΩ`.toParse();
+        expect`ΓΔΘΛΞΠΣΥΦΨΩ`.toBuild();
     });
 
     it("should parse Cyrillic inside \\text{}", function() {
-        expect`\text{БГДЖЗЙЛФЦШЫЮЯ}`.toParse();
+        expect`\text{БГДЖЗЙЛФЦШЫЮЯ}`.toBuild();
+    });
+
+    it("should parse Cyrillic outside \\text{}", function() {
+        expect`БГДЖЗЙЛФЦШЫЮЯ`.toBuild(nonstrictSettings);
     });
 
     it("should not parse Cyrillic outside \\text{} with strict", function() {
@@ -41,8 +45,13 @@ describe("unicode", function() {
     });
 
     it("should parse CJK inside \\text{}", function() {
-        expect`\text{私はバナナです}`.toParse();
-        expect`\text{여보세요}`.toParse();
+        expect`\text{私はバナナです}`.toBuild();
+        expect`\text{여보세요}`.toBuild();
+    });
+
+    it("should parse CJK outside \\text{}", function() {
+        expect`私はバナナです`.toBuild(nonstrictSettings);
+        expect`여보세요`.toBuild(nonstrictSettings);
     });
 
     it("should not parse CJK outside \\text{} with strict", function() {
@@ -51,7 +60,11 @@ describe("unicode", function() {
     });
 
     it("should parse Devangari inside \\text{}", function() {
-        expect`\text{नमस्ते}`.toParse();
+        expect`\text{नमस्ते}`.toBuild();
+    });
+
+    it("should parse Devangari outside \\text{}", function() {
+        expect`नमस्ते`.toBuild(nonstrictSettings);
     });
 
     it("should not parse Devangari outside \\text{} with strict", function() {
@@ -59,7 +72,11 @@ describe("unicode", function() {
     });
 
     it("should parse Georgian inside \\text{}", function() {
-        expect`\text{გამარჯობა}`.toParse();
+        expect`\text{გამარჯობა}`.toBuild();
+    });
+
+    it("should parse Georgian outside \\text{}", function() {
+        expect`გამარჯობა`.toBuild(nonstrictSettings);
     });
 
     it("should not parse Georgian outside \\text{} with strict", function() {
@@ -67,7 +84,7 @@ describe("unicode", function() {
     });
 
     it("should parse extended Latin characters inside \\text{}", function() {
-        expect`\text{ěščřžůřťďňőİı}`.toParse();
+        expect`\text{ěščřžůřťďňőİı}`.toBuild();
     });
 
     it("should not parse extended Latin outside \\text{} with strict", function() {