Make a Thicker Latin Modern Roman
All free fonts with unicode-math support are imperfect. For my taste, Latin Modern is the most polished free OTF font with unicode math, except its thin and sharp strokes for the "modern" laser printers and the lack of lowercase script math. I spent a few nights to figure out how to automatically create a thicker version of the font.
2019-11-02 2020-02-11

Why Latin Modern

As mentioned in the previous post, there are multiply reasons to migrate to XeTeX and LuaTeX. In particular, the possibility to easily use OpenType fonts is such a good feature that I would never look back to the classical TeX if I could choose.

The following fonts are the available ones with unicode-math support on my operating system:

All of them are imperfect. Here are some points one can easily realize: Latin Modern (LM) is in my taste the most polished font in this list except the thinness and the lack of lowercase script letters. Hence, I started to figure out an automatic way to increase its thickness. The results should not be very professional, but should save my eyes when shown on the monitors and should not have broken strokes when printed

Demonstration of the Final Result

Excluded Solutions

The Original Computer Modern

In the T3 computer modern, one can adjust the blacker value to make the glyphs thicker. However, that will not be well displayed by a low-DPI monitor. Typically the T3 texts would be rendered as if they were misaligned, even with a high rasterizing DPI. Besides, who would use a bitmap font nowadays?

To Rebuid CM-Super or Latin Modern

Rebuilding cm-super from a blacker T3 CM bitmaps does not seem to be a time-saving solution. On the other hand, the sources of LM Roman and its math symbols are not completely obtainable. Thus, this approach has also to be excluded.

Solution 1: Fakebold by Fontspec

TLDR, conclusion: visual effects depend on the PDF reader software

The fake bold works in both XeLaTeX and LuaLaTeX. In XeLaTeX it is written as

\setmainfont[%
  FakeBold=1,
  SmallCapsFont={* Caps},
  SlantedFont={* Slanted},
]{Latin Modern Roman}
and for the current LuaLaTeX (works in XeLaTeX as well)
\setmainfont[%
  RawFeature={embolden=1.0}
  SmallCapsFont={* Caps},
  SlantedFont={* Slanted},
]{Latin Modern Roman}
A fakebold factor of 1.0 as in the above settings makes the document look quite nice in Adobe Reader, SumatraPDF and in the Firefox browser, too. However, the visual effect depends on the PDF reader. Okular renders fakebolds imperfectly, but still acceptable; while the current evince or other readers based on poppler cairo will make the font much bolder, which becomes full bolds. Hence, this solution works only for limited PDF readers.

Solution 2: Modify the OTF

Starting from the following structure for the fontforge operations:
(my doc folder)$ tree fakebold/
fakebold/
├── adjust-lm.sh
├── adjust-lm-stroke.pe
├── adjust-lm-weight.pe
└── origin
    ├── latinmodern-math.otf
    ├── lmroman10-bolditalic.otf
    ├── lmroman10-bold.otf
    ├── lmroman10-italic.otf
    ├── lmroman10-regular.otf
    ├── lmroman12-bold.otf
    ├── lmroman12-italic.otf
    ├── lmroman12-regular.otf
    ├── lmroman17-regular.otf
    ├── lmroman5-bold.otf
    ├── lmroman5-regular.otf
    ├── lmroman6-bold.otf
    ├── lmroman6-regular.otf
    ├── lmroman7-bold.otf
    ├── lmroman7-italic.otf
    ├── lmroman7-regular.otf
    ├── lmroman8-bold.otf
    ├── lmroman8-italic.otf
    ├── lmroman8-regular.otf
    ├── lmroman9-bold.otf
    ├── lmroman9-italic.otf
    ├── lmroman9-regular.otf
    ├── lmromancaps10-oblique.otf
    ├── lmromancaps10-regular.otf
    ├── lmromandunh10-oblique.otf
    ├── lmromandunh10-regular.otf
    ├── lmromanslant10-bold.otf
    ├── lmromanslant10-regular.otf
    ├── lmromanslant12-regular.otf
    ├── lmromanslant17-regular.otf
    ├── lmromanslant8-regular.otf
    ├── lmromanslant9-regular.otf
    └── lmromanunsl10-regular.otf

1 directory, 37 files
Although the Python API of fontforge exposures more features than the native script language, those additional features will not be needed here. In addition, the advantage of the native script is that the work can be easily parallelized by launching a shell process for each OTF file as you will see later.

Approach a: Change Weight

This approach works well on the text fonts. However, I could not find a way to tune the weight of math font, without triggering the warning of (only) Adobe PDF reader. The script calls the ChangeWeight() function of fontforge. After the weight is enlarged, each glyph also becomes slightly wider. Therefore, a rescaling is worth to be considered. Since the vertical scaling will misalign the baseline of the letters, a horizontal shrinking is applied here.

There are two additional points to be taken into account:

  1. One needs to simplify the glyphs before scaling, otherwise, the shapes of some symbols like will be destroyed, as visualized in the follow three figures

    Fig. 1a:the original multiply symbol

    Fig. 1b:directly increasing the weight by 15

    Fig. 1c:increasing weight by 15 after simplification
  2. Some glyphs will have distorted shapes after changing the weight, as shown in the next figure.
    Fig. 2:Left side is the original glyph, right side is the one with an increased weight
    The solution is to accumulatively increase the weight step by step, at which this distortion does not appear.
For this approach, the adjust-lm-weight.pe is
 1 #!/user/bin/fontforge
 2 
 3 Open("origin/"+$1)
 4 SelectAll()
 5 Simplify()
 6 i = 0
 7 while(i < Strtol($2))
 8     ChangeWeight(Strtod($3))
 9     ++i
10 endloop
11 if($4 == "true")
12     Scale(95, 100)
13 endif
14 RemoveOverlap()
15 RemoveHints()
16 Generate($1)
17 Close()
and adjust-lm-weight.sh is:
1 #!/bin/sh
2 
3 for i in origin/lmroman*.otf ;  do
4     fontforge adjust-lm.pe "${i#origin/}" 3 5.0 true &
5 done
6 
7 fontforge adjust-lm.pe latinmodern-math.otf  1 15.0 false &
8 
9 wait

Approach b: Expand Stroke

This might be the best approach so far. Compared to the previous one, expanding the strokes will also work for the LM-Math font. This approach does not have the first issue of increasing weight, but it is still bothered by the second issue, which can be solved in the same way.

The script adjust-lm-stroke.pe is now

 1 #!/user/bin/fontforge
 2 
 3 Open("origin/"+$1)
 4 SelectAll()
 5 joinstyle = 0
 6 i = 0
 7 while (i < Strtod($2))
 8     ExpandStroke(Strtol($3), 1, joinstyle, 0, 1)
 9     ++i
10 endloop
11 RemoveOverlap()
12 ClearHints()
13 Simplify()
14 RoundToInt()
15 Generate($1)
16 Close()
and adjust-lm-weight.sh:
1 #!/bin/sh
2 for i in origin/lmroman*.otf ;  do
3     fontforge adjust-lm-stroke.pe "${i#origin/}" 3 6 &
4 done
5 # \symscr{L} would have problem with 3*6, switch to 1*18 for math
6 fontforge adjust-lm-stroke.pe latinmodern-math.otf 1 18 &
7 wait
The hints should be removed, as the original ones are no more optimal and AutoHint() from fontforge is not clever enough.

Settings for fontspec

 1 % Extract the fonts in 'fakebold/'
 2 \usepackage{fontspec}
 3 \usepackage[warnings-off={mathtools-colon,mathtools-overbracket}]{unicode-math}
 4 \setmainfont[%
 5   Path={fakebold/},
 6   %
 7   UprightFont={*-regular.otf},
 8   UprightFeatures={%
 9     SmallCapsFont={lmromancaps10-regular.otf},
10     SizeFeatures={%
11       {Size=     -5.5,  Font={lmroman5-regular.otf}},%
12       {Size=  5.5-6.5,  Font={lmroman6-regular.otf}},%
13       {Size=  6.5-7.5,  Font={lmroman7-regular.otf}},%
14       {Size=  7.5-8.5,  Font={lmroman8-regular.otf}},%
15       {Size=  8.5-9.5,  Font={lmroman9-regular.otf}},%
16       {Size=  9.5-11.5, Font={lmroman10-regular.otf}},%
17       {Size= 11.5-14.5, Font={lmroman12-regular.otf}},%
18       {Size= 14.5-    , Font={lmroman17-regular.otf}}%
19     }%
20   },%
21   %
22   BoldFont={lmroman10-bold.otf},
23   BoldFeatures={%
24     SizeFeatures={%
25       {Size=     -5.5,  Font={lmroman5-bold.otf}},%
26       {Size=  5.5-6.5,  Font={lmroman6-bold.otf}},%
27       {Size=  6.5-7.5,  Font={lmroman7-bold.otf}},%
28       {Size=  7.5-8.5,  Font={lmroman8-bold.otf}},%
29       {Size=  8.5-9.5,  Font={lmroman9-bold.otf}},%
30       {Size=  9.5-11.5, Font={lmroman10-bold.otf}},%
31       {Size= 11.5-    , Font={lmroman12-bold.otf}},%
32     }%
33   },%
34   %
35   ItalicFont={lmroman10-italic.otf},
36   ItalicFeatures={%
37     SizeFeatures={%
38       {Size=     -7.5,  Font={lmroman7-italic.otf}},%
39       {Size=  7.5-8.5,  Font={lmroman8-italic.otf}},%
40       {Size=  8.5-9.5,  Font={lmroman9-italic.otf}},%
41       {Size=  9.5-11.5, Font={lmroman10-italic.otf}},%
42       {Size= 11.5-    , Font={lmroman12-italic.otf}}%
43     }%
44   },%
45   BoldItalicFont={lmroman10-bolditalic.otf},%
46   %
47   SlantedFont={lmromanslant10-regular.otf},
48   SlantedFeatures={%
49     SmallCapsFont={lmromancaps10-oblique.otf},
50     SizeFeatures={%
51       {Size=     -8.5,  Font={lmromanslant8-regular.otf}},%
52       {Size=  8.5-9.5,  Font={lmromanslant9-regular.otf}},%
53       {Size=  9.5-11.5, Font={lmromanslant10-regular.otf}},%
54       {Size= 11.5-14.5, Font={lmromanslant12-regular.otf}},%
55       {Size= 14.5-    , Font={lmromanslant17-regular.otf}}%
56     }%
57   },%
58   BoldSlantedFont={lmromanslant10-bold.otf},%
59   %
60   Ligatures={TeX}
61 ]{lmroman10}%
62 \newfontfamily\upit[Path={fakebold/}]{lmromanunsl10-regular.otf}
63 \newcommand{\textupit}[1]{{\upit#1}}
64 \newcommand{\textitup}[1]{{\upit#1}}
65 \newfontfamily\dhstyle[Path={fakebold/},Scale=MatchLowercase,UprightFont={*-regular.otf},SlantedFont={*-oblique.otf}]{lmromandunh10}
66 \newcommand{\textdh}[1]{{\dhstyle#1}}
67 %
68 \setsansfont[%
69   Scale=MatchUppercase,
70   Ligatures=TeX
71 ]{Latin Modern Sans}
72 \newfontfamily{\dcstyle}[%
73   ItalicFont={lmsansdemicond10-oblique.otf},%
74   SlantedFont={lmsansdemicond10-oblique.otf},%
75 ]{lmsansdemicond10-regular.otf}
76 \newcommand{\textdc}[1]{{\dcstyle#1}}
77 %
78 \setmonofont[%
79   Scale=MatchUppercase
80 ]{Latin Modern Mono}
81 %
82 \setmathfont[%
83   Path={fakebold/},
84   bold-style=ISO,
85   partial=upright
86 ]{latinmodern-math.otf}
87 %
88 \setmathfontface\symupit[%
89   Path={fakebold/},
90 ]{lmromanunsl10-regular.otf}
91 %
92 \setmathfontface\symitup[%
93   Path={fakebold/},
94 ]{lmromanunsl10-regular.otf}

Known Problems

Download the Modified Font

thick-lm-otf.zip

Edit 2020-02-21: update download link, update sample TeX settings.