Skip to content

Commit

Permalink
Merge pull request #67 from Kolaru/full_unicode_font
Browse files Browse the repository at this point in the history
Good support for NewComputerModern font
  • Loading branch information
Kolaru authored Jul 22, 2022
2 parents ca57d2b + 8022c67 commit c757b00
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
fail-fast: false
matrix:
version:
- '1.7'
- '1.6'
- '1.3'
os:
- ubuntu-latest
arch:
Expand Down
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MathTeXEngine"
uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
authors = ["Benoît Richard <[email protected]>"]
version = "0.4.3"
version = "0.5.0"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
Expand All @@ -18,8 +18,8 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
AbstractTrees = "0.3, 0.4"
Automa = "0.8"
DataStructures = "0.18"
FreeTypeAbstraction = "0.9"
FreeTypeAbstraction = "0.10"
GeometryBasics = "0.4.1, 0.4.2"
LaTeXStrings = "1.2"
RelocatableFolders = "0.1, 0.2, 0.3"
julia = "1.3"
julia = "1.6"
3 changes: 3 additions & 0 deletions prototype/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[deps]
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
FreeType = "b38be410-82b0-50bf-ab77-7b57e271db43"
FreeTypeAbstraction = "663a7486-cb36-511b-a19d-713bb74d65c9"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
Expand Down
12 changes: 12 additions & 0 deletions prototype/glyphtest.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Pkg

Pkg.activate("prototype")

using MathTeXEngine
using CairoMakie

tex = L"\int dx \theta 2 x"
text(0, 0 ; text = tex, textsize=20)


elems = generate_tex_elements(tex)
12 changes: 9 additions & 3 deletions prototype/prototype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ draw_texelement!(args... ; size=64) = nothing
function draw_texelement!(ax, texchar::TeXChar, position, scale ; size=64)
x = position[1] * size
y = position[2] * size
text!(ax, string(texchar.char), font=texchar.font,
# TODO This doesn't make sense anymore
text!(ax, string(Char(texchar.represented_char)), font=texchar.font,
position=Point2f(x, y),
textsize=size*scale,
space=:data,
Expand Down Expand Up @@ -151,9 +152,14 @@ begin # Quick test
ax = Axis(fig[2, 1])
hidedecorations!(ax)
ax.aspect = DataAspect()
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"
tex = L"\nabla 3\degree \partial L^3 \sum \lim_{L →\infty}
\varphi \phi \varpi \pi \varepsilon \epsilon
ℝ^\sqrt{A + j + 2 + 3} |x^2|^3 = \sum_{k = 1}^N
\vec{v}_{(a' + \bar{a})_k} + \sqrt{T} x! \quad \mathrm{when} \quad
\left[ \sqrt{\frac{\Omega-2}{a \langle c^\dagger \rangle b}} \right]^3_3
< \int_{0}^{2π} |\sin(\mu x)| dx"

makie_tex!(ax, tex, debug=true, size=64)
fig[3, 1] = Label(fig, tex, tellwidth=false, tellheight=false, textsize=40)
fig
end
end
5 changes: 4 additions & 1 deletion src/MathTeXEngine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ using REPL.REPLCompletions: latex_symbols
using RelocatableFolders

import FreeTypeAbstraction:
ascender, boundingbox, descender, get_extent, hadvance, inkheight, inkwidth,
ascender, boundingbox, descender, get_extent, glyph_index,
hadvance, inkheight, inkwidth,
height_insensitive_boundingbox, leftinkbound, rightinkbound,
topinkbound, bottominkbound

Expand All @@ -23,6 +24,7 @@ const re = Automa.RegExp
export TeXExpr, texparse
export TeXElement, TeXChar, VLine, HLine, generate_tex_elements
export get_font, get_fontpath
export glyph_index

# Reexport from LaTeXStrings
export @L_str
Expand All @@ -33,6 +35,7 @@ include("parser/commands_registration.jl")
include("parser/parser.jl")

include("engine/computer_modern_data.jl")
include("engine/new_computer_modern_data.jl")
include("engine/fonts.jl")
include("engine/layout_context.jl")
include("engine/texelements.jl")
Expand Down
6 changes: 3 additions & 3 deletions src/engine/computer_modern_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,15 @@ _latex_to_computer_modern = Dict(
)


_symbol_to_computer_modern = Dict{Char, Tuple{String, Char}}()
_symbol_to_computer_modern = Dict{Char, Tuple{String, Int}}()

for (symbol, (fontname, id)) in _latex_to_computer_modern
for (symbol, (fontname, glyph_id)) in _latex_to_computer_modern
if haskey(latex_symbols, symbol)
symbol = latex_symbols[symbol][1]
else
symbol = symbol[1]
end

fontpath = joinpath("ComputerModern", "$fontname.ttf")
_symbol_to_computer_modern[symbol] = (fontpath, Char(id))
_symbol_to_computer_modern[symbol] = (fontpath, glyph_id)
end
53 changes: 33 additions & 20 deletions src/engine/fonts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ end
# Loading the font directly here lead to FreeTypeAbstraction to fail with error
# code 35, because handles to fonts are C pointer that cannot be fully
# serialized at compile time
const _default_fonts = Dict(
const _new_computer_modern_fonts = Dict(
:regular => joinpath("NewComputerModern", "NewCMMath-Regular.otf"),
:italic => joinpath("NewComputerModern", "NewCM10-Italic.otf"),
:bold => joinpath("NewComputerModern", "NewCM10-Bold.otf"),
:bolditalic => joinpath("NewComputerModern", "NewCM10-BoldItalic.otf"),
:math => joinpath("NewComputerModern", "NewCMMath-Regular.otf")
)

const _computer_modern_fonts = Dict(
:regular => joinpath("ComputerModern", "cmr10.ttf"),
:italic => joinpath("ComputerModern", "cmmi10.ttf"),
:bold => joinpath("ComputerModern", "cmb10.ttf"),
Expand Down Expand Up @@ -69,19 +77,33 @@ struct FontFamily
fonts::Dict{Symbol, String}
font_mapping::Dict{Symbol, Symbol}
font_modifiers::Dict{Symbol, Dict{Symbol, Symbol}}
special_chars::Dict{Char, Tuple{String, Char}}
special_chars::Dict{Char, Tuple{String, Int}}
slant_angle::Float64
thickness::Float64
end

FontFamily(fonts) = FontFamily(
fonts,
_default_font_mapping,
_default_font_modifiers,
_symbol_to_computer_modern,
15,
0.0375)
FontFamily() = FontFamily(_default_fonts)
FontFamily() = FontFamily("NewComputerModern")
FontFamily(fontname) = default_font_families[fontname]

# These two fonts internals are very different, despite their similar names
# We only try to fully support NewComputerModern, the other is here as it may
# sometime provide quickfix solution to bug
const default_font_families = Dict(
"NewComputerModern" => FontFamily(
_new_computer_modern_fonts,
_default_font_mapping,
_default_font_modifiers,
_symbol_to_new_computer_modern,
13,
0.0375),
"ComputerModern" => FontFamily(
_computer_modern_fonts,
_default_font_mapping,
_default_font_modifiers,
_symbol_to_computer_modern,
15,
0.0375)
)

"""
get_font([font_family=FontFamily()], fontstyle)
Expand Down Expand Up @@ -118,19 +140,10 @@ The thickness of the underline for the given font set.
"""
thickness(font_family::FontFamily) = font_family.thickness

"""
xheight(font::FTFont)
The height of the letter x in the given font, i.e. the height of the letters
without neither ascender nor descender.
"""
xheight(font::FTFont) = inkheight(TeXChar('x', font))


"""
xheight(font::FontFamily)
The height of the letter x in the given font family, i.e. the height of the letters
without neither ascender nor descender.
"""
xheight(font_family) = xheight(get_font(font_family, :regular))
xheight(font_family) = inkheight(TeXChar('x', LayoutState(font_family), :text))
68 changes: 21 additions & 47 deletions src/engine/layout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ function tex_layout(expr, state)
if head in [:char, :delimiter, :digit, :punctuation, :symbol]
char = args[1]
texchar = TeXChar(char, state, head)

if char == '°'
return Group(
[texchar],
[Point2f(-0.2, 0)],
[1]
)
end
return texchar
elseif head == :combining_accent
accent, core = tex_layout.(args, state)
Expand All @@ -52,21 +44,20 @@ function tex_layout(expr, state)
[core, accent],
Point2f[
(0, 0),
(x + hmid(core) - hmid(accent), y)
(x + hmid(core) - hmid(accent), 0)
],
[1, 1]
)
elseif head == :decorated
core, sub, super = tex_layout.(args, state)

core_width = hadvance(core)

# Should be a bit smarter for slanted font
return Group(
[core, sub, super],
Point2f[
(0, 0),
(core_width, -0.2),
(core_width, 0.8 * xheight(core))],
(rightinkbound(core) + 0.1, -0.2),
(rightinkbound(core) + 0.1, 0.9 * xheight(core))],
[1, shrink, shrink]
)
elseif head == :delimited
Expand Down Expand Up @@ -129,26 +120,19 @@ function tex_layout(expr, state)
return horizontal_layout(elements)
elseif head == :integral
pad = 0.1
sub, super = tex_layout.(args[2:3], state)

# Always use ComputerModern fallback for the integral sign
# as the Unicode LaTeX approach requires to use glyph variant
# which is unlikely to be supported by backends
intfont = load_font(joinpath("ComputerModern", "cmex10.ttf"))
int = TeXChar(Char(0x5a), intfont)
h = inkheight(int)
int, sub, super = tex_layout.(args, state)

return Group(
[int, sub, super],
Point2f[
(0, y_for_centered(font_family, int)),
(0, 0),
(
0.15 - inkwidth(sub)*shrink/2,
-h/2 + xheight(font_family)/2 - topinkbound(sub)*shrink - pad
bottominkbound(int) - topinkbound(sub)*shrink - pad
),
(
0.85 - inkwidth(super)*shrink/2,
h/2 + xheight(font_family)/2 + pad
topinkbound(int) + pad
)
],
[1, shrink, shrink]
Expand All @@ -160,27 +144,24 @@ function tex_layout(expr, state)
return horizontal_layout([Space(0.2), sym, Space(0.2)])
elseif head == :sqrt
content = tex_layout(args[1], state)
sqrt = TeXChar('', state, :symbol)

relpad = 0.15

h = inkheight(content)
ypad = relpad * h
h += 2ypad

if h > inkheight(sqrt)
sqrt = TeXChar('', state, :symbol)
h = inkheight(sqrt)
y0 = (topinkbound(sqrt) - bottominkbound(sqrt))/2 + xheight(font_family)/2
else
y0 = bottominkbound(content) - bottominkbound(sqrt) - 0.1
sqrt = nothing

for name in ["radical.v1", "radical.v2", "radical.v3", "radical.v4"]
sqrt = TeXChar(name, state, :symbol ; represented = '')
if inkheight(sqrt) >= 1.05h
pad = (inkheight(sqrt) - 1.05h) / 2
break
end
end

lw = thickness(font_family)
h = inkheight(sqrt)

lw = thickness(font_family)
y0 = bottominkbound(content) - bottominkbound(sqrt) - pad
y = y0 + topinkbound(sqrt) - lw

hline = HLine(inkwidth(content) + 0.1, lw)
hline = HLine(inkwidth(content) + pad, lw)

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

# The leftmost element must have x = 0
x0 = -min(0, dxsub, dxsuper)

# Special case to deal with sum symbols and the like that do not
# have their baseline properly set in the font
if core isa TeXChar
y0 = y_for_centered(font_family, core)
else
y0 = 0.0
end
y0 = 0.0

return Group(
[core, sub, super],
Expand Down
46 changes: 46 additions & 0 deletions src/engine/new_computer_modern_data.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
_latex_to_new_computer_modern = Dict(
raw"\int" => 5930,
raw"\sum" => 5941,

raw"\partial" => 3377,
raw"\varepsilon" => 3378,
raw"\vartheta" => 3379,
raw"\varkappa" => 3380,
raw"\varphi" => 3381,
raw"\varrho" => 3382,
raw"\varpi" => 3383,

raw"\epsilon" => 3356,
raw"\theta" => 3359,
raw"\kappa" => 3361,
raw"\phi" => 3373,
raw"\rho" => 3368,
raw"\pi" => 3367
)


_symbol_to_new_computer_modern = Dict{Char, Tuple{String, Int}}()
cmmath_fontpath = joinpath("NewComputerModern", "NewCMMath-Regular.otf")

for (symbol, glyph_id) in _latex_to_new_computer_modern
if haskey(latex_symbols, symbol)
symbol = latex_symbols[symbol][1]
else
symbol = symbol[1]
end

_symbol_to_new_computer_modern[symbol] = (cmmath_fontpath, glyph_id)
end

# Standard hreek symbols : thin and italic
for i in 0:24
capital = 'Α' + i
small = 'α' + i

if !haskey(_symbol_to_new_computer_modern, capital)
_symbol_to_new_computer_modern[capital] = (cmmath_fontpath, 3326 + i)
end
if !haskey(_symbol_to_new_computer_modern, small)
_symbol_to_new_computer_modern[small] = (cmmath_fontpath, 3352 + i)
end
end
Loading

5 comments on commit c757b00

@jkrumbiegel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want to register this? :)

@Kolaru
Copy link
Owner Author

@Kolaru Kolaru commented on c757b00 Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkrumbiegel I would very much like to, but Registrator (through JuliaHub -- which I use normally) don't let me :(

I have been told that the problem should be resolved very soon.

@jkrumbiegel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you could try JuliaRegistrator, it's what we use for Makie too

@Kolaru
Copy link
Owner Author

@Kolaru Kolaru commented on c757b00 Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/65062

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.0 -m "<description of version>" c757b00145f06b572f11d880a434379f86423925
git push origin v0.5.0

Please sign in to comment.