Skip to content

Commit c757b00

Browse files
authored
Merge pull request #67 from Kolaru/full_unicode_font
Good support for NewComputerModern font
2 parents ca57d2b + 8022c67 commit c757b00

File tree

13 files changed

+169
-115
lines changed

13 files changed

+169
-115
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
version:
18+
- '1.7'
1819
- '1.6'
19-
- '1.3'
2020
os:
2121
- ubuntu-latest
2222
arch:

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MathTeXEngine"
22
uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
33
authors = ["Benoît Richard <kolaru@hotmail.com>"]
4-
version = "0.4.3"
4+
version = "0.5.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -18,8 +18,8 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1818
AbstractTrees = "0.3, 0.4"
1919
Automa = "0.8"
2020
DataStructures = "0.18"
21-
FreeTypeAbstraction = "0.9"
21+
FreeTypeAbstraction = "0.10"
2222
GeometryBasics = "0.4.1, 0.4.2"
2323
LaTeXStrings = "1.2"
2424
RelocatableFolders = "0.1, 0.2, 0.3"
25-
julia = "1.3"
25+
julia = "1.6"

prototype/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[deps]
2+
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
23
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
34
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
5+
FreeType = "b38be410-82b0-50bf-ab77-7b57e271db43"
6+
FreeTypeAbstraction = "663a7486-cb36-511b-a19d-713bb74d65c9"
47
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
58
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
69
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

prototype/glyphtest.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Pkg
2+
3+
Pkg.activate("prototype")
4+
5+
using MathTeXEngine
6+
using CairoMakie
7+
8+
tex = L"\int dx \theta 2 x"
9+
text(0, 0 ; text = tex, textsize=20)
10+
11+
12+
elems = generate_tex_elements(tex)

prototype/prototype.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ draw_texelement!(args... ; size=64) = nothing
1717
function draw_texelement!(ax, texchar::TeXChar, position, scale ; size=64)
1818
x = position[1] * size
1919
y = position[2] * size
20-
text!(ax, string(texchar.char), font=texchar.font,
20+
# TODO This doesn't make sense anymore
21+
text!(ax, string(Char(texchar.represented_char)), font=texchar.font,
2122
position=Point2f(x, y),
2223
textsize=size*scale,
2324
space=:data,
@@ -151,9 +152,14 @@ begin # Quick test
151152
ax = Axis(fig[2, 1])
152153
hidedecorations!(ax)
153154
ax.aspect = DataAspect()
154-
tex = L"3\degree \partial \sum \lim_{L →\infty} \gamma A^\sqrt{A + j + 2 + 3 + 2 + L} z^2 = \sum_{k = 1}^N \vec{v}_{(a + \bar{a})_k} + \sqrt{j} x! \quad \mathrm{when} \quad \sqrt{\frac{\Omega-2}{4+a+x}} < \int_{0}^{2π} |\sin(\mu x)| dx"
155+
tex = L"\nabla 3\degree \partial L^3 \sum \lim_{L →\infty}
156+
\varphi \phi \varpi \pi \varepsilon \epsilon
157+
ℝ^\sqrt{A + j + 2 + 3} |x^2|^3 = \sum_{k = 1}^N
158+
\vec{v}_{(a' + \bar{a})_k} + \sqrt{T} x! \quad \mathrm{when} \quad
159+
\left[ \sqrt{\frac{\Omega-2}{a \langle c^\dagger \rangle b}} \right]^3_3
160+
< \int_{0}^{2π} |\sin(\mu x)| dx"
155161

156162
makie_tex!(ax, tex, debug=true, size=64)
157163
fig[3, 1] = Label(fig, tex, tellwidth=false, tellheight=false, textsize=40)
158164
fig
159-
end
165+
end

src/MathTeXEngine.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ using REPL.REPLCompletions: latex_symbols
1414
using RelocatableFolders
1515

1616
import FreeTypeAbstraction:
17-
ascender, boundingbox, descender, get_extent, hadvance, inkheight, inkwidth,
17+
ascender, boundingbox, descender, get_extent, glyph_index,
18+
hadvance, inkheight, inkwidth,
1819
height_insensitive_boundingbox, leftinkbound, rightinkbound,
1920
topinkbound, bottominkbound
2021

@@ -23,6 +24,7 @@ const re = Automa.RegExp
2324
export TeXExpr, texparse
2425
export TeXElement, TeXChar, VLine, HLine, generate_tex_elements
2526
export get_font, get_fontpath
27+
export glyph_index
2628

2729
# Reexport from LaTeXStrings
2830
export @L_str
@@ -33,6 +35,7 @@ include("parser/commands_registration.jl")
3335
include("parser/parser.jl")
3436

3537
include("engine/computer_modern_data.jl")
38+
include("engine/new_computer_modern_data.jl")
3639
include("engine/fonts.jl")
3740
include("engine/layout_context.jl")
3841
include("engine/texelements.jl")

src/engine/computer_modern_data.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,15 @@ _latex_to_computer_modern = Dict(
228228
)
229229

230230

231-
_symbol_to_computer_modern = Dict{Char, Tuple{String, Char}}()
231+
_symbol_to_computer_modern = Dict{Char, Tuple{String, Int}}()
232232

233-
for (symbol, (fontname, id)) in _latex_to_computer_modern
233+
for (symbol, (fontname, glyph_id)) in _latex_to_computer_modern
234234
if haskey(latex_symbols, symbol)
235235
symbol = latex_symbols[symbol][1]
236236
else
237237
symbol = symbol[1]
238238
end
239239

240240
fontpath = joinpath("ComputerModern", "$fontname.ttf")
241-
_symbol_to_computer_modern[symbol] = (fontpath, Char(id))
241+
_symbol_to_computer_modern[symbol] = (fontpath, glyph_id)
242242
end

src/engine/fonts.jl

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ end
2525
# Loading the font directly here lead to FreeTypeAbstraction to fail with error
2626
# code 35, because handles to fonts are C pointer that cannot be fully
2727
# serialized at compile time
28-
const _default_fonts = Dict(
28+
const _new_computer_modern_fonts = Dict(
29+
:regular => joinpath("NewComputerModern", "NewCMMath-Regular.otf"),
30+
:italic => joinpath("NewComputerModern", "NewCM10-Italic.otf"),
31+
:bold => joinpath("NewComputerModern", "NewCM10-Bold.otf"),
32+
:bolditalic => joinpath("NewComputerModern", "NewCM10-BoldItalic.otf"),
33+
:math => joinpath("NewComputerModern", "NewCMMath-Regular.otf")
34+
)
35+
36+
const _computer_modern_fonts = Dict(
2937
:regular => joinpath("ComputerModern", "cmr10.ttf"),
3038
:italic => joinpath("ComputerModern", "cmmi10.ttf"),
3139
:bold => joinpath("ComputerModern", "cmb10.ttf"),
@@ -69,19 +77,33 @@ struct FontFamily
6977
fonts::Dict{Symbol, String}
7078
font_mapping::Dict{Symbol, Symbol}
7179
font_modifiers::Dict{Symbol, Dict{Symbol, Symbol}}
72-
special_chars::Dict{Char, Tuple{String, Char}}
80+
special_chars::Dict{Char, Tuple{String, Int}}
7381
slant_angle::Float64
7482
thickness::Float64
7583
end
7684

77-
FontFamily(fonts) = FontFamily(
78-
fonts,
79-
_default_font_mapping,
80-
_default_font_modifiers,
81-
_symbol_to_computer_modern,
82-
15,
83-
0.0375)
84-
FontFamily() = FontFamily(_default_fonts)
85+
FontFamily() = FontFamily("NewComputerModern")
86+
FontFamily(fontname) = default_font_families[fontname]
87+
88+
# These two fonts internals are very different, despite their similar names
89+
# We only try to fully support NewComputerModern, the other is here as it may
90+
# sometime provide quickfix solution to bug
91+
const default_font_families = Dict(
92+
"NewComputerModern" => FontFamily(
93+
_new_computer_modern_fonts,
94+
_default_font_mapping,
95+
_default_font_modifiers,
96+
_symbol_to_new_computer_modern,
97+
13,
98+
0.0375),
99+
"ComputerModern" => FontFamily(
100+
_computer_modern_fonts,
101+
_default_font_mapping,
102+
_default_font_modifiers,
103+
_symbol_to_computer_modern,
104+
15,
105+
0.0375)
106+
)
85107

86108
"""
87109
get_font([font_family=FontFamily()], fontstyle)
@@ -118,19 +140,10 @@ The thickness of the underline for the given font set.
118140
"""
119141
thickness(font_family::FontFamily) = font_family.thickness
120142

121-
"""
122-
xheight(font::FTFont)
123-
124-
The height of the letter x in the given font, i.e. the height of the letters
125-
without neither ascender nor descender.
126-
"""
127-
xheight(font::FTFont) = inkheight(TeXChar('x', font))
128-
129-
130143
"""
131144
xheight(font::FontFamily)
132145
133146
The height of the letter x in the given font family, i.e. the height of the letters
134147
without neither ascender nor descender.
135148
"""
136-
xheight(font_family) = xheight(get_font(font_family, :regular))
149+
xheight(font_family) = inkheight(TeXChar('x', LayoutState(font_family), :text))

src/engine/layout.jl

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ function tex_layout(expr, state)
2727
if head in [:char, :delimiter, :digit, :punctuation, :symbol]
2828
char = args[1]
2929
texchar = TeXChar(char, state, head)
30-
31-
if char == '°'
32-
return Group(
33-
[texchar],
34-
[Point2f(-0.2, 0)],
35-
[1]
36-
)
37-
end
3830
return texchar
3931
elseif head == :combining_accent
4032
accent, core = tex_layout.(args, state)
@@ -52,21 +44,20 @@ function tex_layout(expr, state)
5244
[core, accent],
5345
Point2f[
5446
(0, 0),
55-
(x + hmid(core) - hmid(accent), y)
47+
(x + hmid(core) - hmid(accent), 0)
5648
],
5749
[1, 1]
5850
)
5951
elseif head == :decorated
6052
core, sub, super = tex_layout.(args, state)
61-
62-
core_width = hadvance(core)
6353

54+
# Should be a bit smarter for slanted font
6455
return Group(
6556
[core, sub, super],
6657
Point2f[
6758
(0, 0),
68-
(core_width, -0.2),
69-
(core_width, 0.8 * xheight(core))],
59+
(rightinkbound(core) + 0.1, -0.2),
60+
(rightinkbound(core) + 0.1, 0.9 * xheight(core))],
7061
[1, shrink, shrink]
7162
)
7263
elseif head == :delimited
@@ -129,26 +120,19 @@ function tex_layout(expr, state)
129120
return horizontal_layout(elements)
130121
elseif head == :integral
131122
pad = 0.1
132-
sub, super = tex_layout.(args[2:3], state)
133-
134-
# Always use ComputerModern fallback for the integral sign
135-
# as the Unicode LaTeX approach requires to use glyph variant
136-
# which is unlikely to be supported by backends
137-
intfont = load_font(joinpath("ComputerModern", "cmex10.ttf"))
138-
int = TeXChar(Char(0x5a), intfont)
139-
h = inkheight(int)
123+
int, sub, super = tex_layout.(args, state)
140124

141125
return Group(
142126
[int, sub, super],
143127
Point2f[
144-
(0, y_for_centered(font_family, int)),
128+
(0, 0),
145129
(
146130
0.15 - inkwidth(sub)*shrink/2,
147-
-h/2 + xheight(font_family)/2 - topinkbound(sub)*shrink - pad
131+
bottominkbound(int) - topinkbound(sub)*shrink - pad
148132
),
149133
(
150134
0.85 - inkwidth(super)*shrink/2,
151-
h/2 + xheight(font_family)/2 + pad
135+
topinkbound(int) + pad
152136
)
153137
],
154138
[1, shrink, shrink]
@@ -160,27 +144,24 @@ function tex_layout(expr, state)
160144
return horizontal_layout([Space(0.2), sym, Space(0.2)])
161145
elseif head == :sqrt
162146
content = tex_layout(args[1], state)
163-
sqrt = TeXChar('', state, :symbol)
164-
165-
relpad = 0.15
166-
167147
h = inkheight(content)
168-
ypad = relpad * h
169-
h += 2ypad
170-
171-
if h > inkheight(sqrt)
172-
sqrt = TeXChar('', state, :symbol)
173-
h = inkheight(sqrt)
174-
y0 = (topinkbound(sqrt) - bottominkbound(sqrt))/2 + xheight(font_family)/2
175-
else
176-
y0 = bottominkbound(content) - bottominkbound(sqrt) - 0.1
148+
sqrt = nothing
149+
150+
for name in ["radical.v1", "radical.v2", "radical.v3", "radical.v4"]
151+
sqrt = TeXChar(name, state, :symbol ; represented = '')
152+
if inkheight(sqrt) >= 1.05h
153+
pad = (inkheight(sqrt) - 1.05h) / 2
154+
break
155+
end
177156
end
178157

179-
lw = thickness(font_family)
158+
h = inkheight(sqrt)
180159

160+
lw = thickness(font_family)
161+
y0 = bottominkbound(content) - bottominkbound(sqrt) - pad
181162
y = y0 + topinkbound(sqrt) - lw
182163

183-
hline = HLine(inkwidth(content) + 0.1, lw)
164+
hline = HLine(inkwidth(content) + pad, lw)
184165

185166
return Group(
186167
[sqrt, hline, content, Space(1.2)],
@@ -204,14 +185,7 @@ function tex_layout(expr, state)
204185

205186
# The leftmost element must have x = 0
206187
x0 = -min(0, dxsub, dxsuper)
207-
208-
# Special case to deal with sum symbols and the like that do not
209-
# have their baseline properly set in the font
210-
if core isa TeXChar
211-
y0 = y_for_centered(font_family, core)
212-
else
213-
y0 = 0.0
214-
end
188+
y0 = 0.0
215189

216190
return Group(
217191
[core, sub, super],
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
_latex_to_new_computer_modern = Dict(
2+
raw"\int" => 5930,
3+
raw"\sum" => 5941,
4+
5+
raw"\partial" => 3377,
6+
raw"\varepsilon" => 3378,
7+
raw"\vartheta" => 3379,
8+
raw"\varkappa" => 3380,
9+
raw"\varphi" => 3381,
10+
raw"\varrho" => 3382,
11+
raw"\varpi" => 3383,
12+
13+
raw"\epsilon" => 3356,
14+
raw"\theta" => 3359,
15+
raw"\kappa" => 3361,
16+
raw"\phi" => 3373,
17+
raw"\rho" => 3368,
18+
raw"\pi" => 3367
19+
)
20+
21+
22+
_symbol_to_new_computer_modern = Dict{Char, Tuple{String, Int}}()
23+
cmmath_fontpath = joinpath("NewComputerModern", "NewCMMath-Regular.otf")
24+
25+
for (symbol, glyph_id) in _latex_to_new_computer_modern
26+
if haskey(latex_symbols, symbol)
27+
symbol = latex_symbols[symbol][1]
28+
else
29+
symbol = symbol[1]
30+
end
31+
32+
_symbol_to_new_computer_modern[symbol] = (cmmath_fontpath, glyph_id)
33+
end
34+
35+
# Standard hreek symbols : thin and italic
36+
for i in 0:24
37+
capital = 'Α' + i
38+
small = 'α' + i
39+
40+
if !haskey(_symbol_to_new_computer_modern, capital)
41+
_symbol_to_new_computer_modern[capital] = (cmmath_fontpath, 3326 + i)
42+
end
43+
if !haskey(_symbol_to_new_computer_modern, small)
44+
_symbol_to_new_computer_modern[small] = (cmmath_fontpath, 3352 + i)
45+
end
46+
end

0 commit comments

Comments
 (0)