NanoMichael / MicroTeX

A dynamic, cross-platform, and embeddable LaTeX rendering library
MIT License
418 stars 69 forks source link

Wrong main font selection #105

Closed NanoMichael closed 2 years ago

NanoMichael commented 2 years ago

I also found a bug (I think), in the way non math font families are handled. I've tried to put NoteKit's fontfamily "Charter" (https://github.com/blackhole89/notekit/tree/master/data/fonts) into the search dir, and the font senser also found all of them and added all of them with a single LaTeX::addMainFont invocation as "Charter" family. However, after calling LaTeX::setDefaultMainFont("Charter") instead of picking the correct style for \text{} it instead used the one that was in the last position of the FontSrcList, which happened to be "Charter Italic".

See #99

sp1ritCS commented 2 years ago

So I did some digging, and believe I now know why this is the case. When implementing the fontsense algorithm, I was unaware of the importance of the FontSrc "name" in order to do styling. In lib/unimath/uni_font.cpp: FontFamily::fontStyleOf the "name" gets matched with a static map to determine the font style.

https://github.com/NanoMichael/cLaTeXMath/blob/a6a87221afbdbd2e8f106045f37cdd722a3d385c/lib/unimath/uni_font.cpp#L30

since the name determined by fontsense is "Charter {Regular,Bold,Italic,Bold Italic}" (or whatever, applies to every font) the returned FontStyle is always FontStyle::none. Instead of using names for styles, I think it'd be better to get and include the correct style in the clm file and then match against that.

If I understand the fontforge python doc correct, the only way of doing this is taking the <font>.macstyle parameter, which seems to be a bitflag style short.However the docs doesn't seem to specify whether its big or low endian 🙃 .

sp1ritCS commented 2 years ago

Hmm, at least neither Charter nor Latin Modern Math supports macstyle (just returns -1). Fontforge itself is able to detect the correct style under Font Information > OS/2 > Misc. > Style Map, however I fail to understand how to access that from python :/

sp1ritCS commented 2 years ago

Ok, it seems to not be documented, but <font>.os2_stylemap could work: similar to this: fontforge/sfd.c:8112

but I don't know what the best way forward is with including this in the clm file, given that preferably we unify macstyle/os2_stylemap

NanoMichael commented 2 years ago

Well, I've also noticed this problem...

A modern font has these meta properties:

For convenience, we put these properties together into one flag:

https://github.com/NanoMichael/cLaTeXMath/blob/a6a87221afbdbd2e8f106045f37cdd722a3d385c/lib/graphic/font_style.h#L10

We can read its style and weight via font.sfnt_names or font.os2_stylemap (not documented though), the problem is it seems we can not get its generic family. A solution is to let users specify these properties when generate clm data, e.g. bf, bfit, sfbf, sfbfit, and if not specified we treat it as a serif font.

sp1ritCS commented 2 years ago

Would something like this work?

diff --git a/prebuilt/otf2clm.py b/prebuilt/otf2clm.py
index ce325e4..53d487f 100755
--- a/prebuilt/otf2clm.py
+++ b/prebuilt/otf2clm.py
@@ -587,7 +587,62 @@ def write_glyphs(f, glyphs, glyph_name_id_map, is_math_font, have_glyph_path):
             write_path(glyph[3])

-def parse_otf(file_path, have_glyph_path, output_file_path):
+def parse_fontstyle(font, userstyle):
+    NONE = 0b00000000
+    RM =   0b00000001
+    BF =   0b00000010
+    IT =   0b00000100
+    CAL =  0b00001000
+    FRAK = 0b00010000
+    BB =   0b00100000
+    SF =   0b01000000
+    TT =   0b10000000
+
+    style = NONE
+
+    if (userstyle == None or userstyle == ""):
+        if (font.os2_weight >= 700):
+            style |= BF
+
+        if (font.os2_stylemap == 0x40):
+            style |= RM
+        if (font.os2_stylemap == 0x21):
+            style |= (BF | IT)
+        if (font.os2_stylemap == 0x20):
+            style |= BF
+        if (font.os2_stylemap == 0x01):
+            style |= IT
+
+        if (font.macstyle != -1):
+            if (font.macstyle & 0b01 == 0b01):
+                style |= BF
+            if (font.macstyle & 0b10 == 0b10):
+                style |= IT
+
+        if (font.fullname.upper().__contains__("MONO")):
+            style |= TT
+    else:
+        userstyles = userstyle.split(",")
+        if ("rm" in userstyles):
+            style |= RM
+        if ("bf" in userstyles):
+            style |= BF
+        if ("it" in userstyles):
+            style |= IT
+        if ("cal" in userstyles):
+            style |= CAL
+        if ("frak" in userstyles):
+            style |= FRAK
+        if ("bb" in userstyles):
+            style |= BB
+        if ("sf" in userstyles):
+            style |= SF
+        if ("tt" in userstyles):
+            style |= TT
+
+    return style
+
+def parse_otf(file_path, have_glyph_path, output_file_path, userstyle = ""):
     print("parsing font " + file_path + ", please wait...")
     font = fontforge.open(file_path)

@@ -633,6 +688,7 @@ def parse_otf(file_path, have_glyph_path, output_file_path):

     name = font.fullname
     family = font.familyname
+    style = parse_fontstyle(font, userstyle)
     em = font.em
     xheight = font.xHeight
     ascent = font.ascent
@@ -649,6 +705,7 @@ def parse_otf(file_path, have_glyph_path, output_file_path):
         f.write(struct.pack(str(len(name)+1) + 's', bytes(name, 'utf-8')))
         f.write(struct.pack(str(len(family)+1) + 's', bytes(family, 'utf-8')))
         f.write(struct.pack('?', is_math_font))
+        f.write(struct.pack('!H', style))
         f.write(struct.pack('!H', em))
         f.write(struct.pack('!H', int(xheight)))
         f.write(struct.pack('!H', ascent))
@@ -710,10 +767,14 @@ def main():
             sys.argv[4]
         )
     else:
+        userstyle = ""
+        if (len(sys.argv) > 5):
+            userstyle = sys.argv[5]
         parse_otf(
             sys.argv[2],
             sys.argv[3] == 'true',
-            sys.argv[4]
+            sys.argv[4],
+            userstyle
         )
         print("The generated clm data file was saved into file: " + sys.argv[4])
NanoMichael commented 2 years ago

Would something like this work?

Excellent 🎉 🎉 🎉 , that is exactly what I mean. Could you please make a PR for this?