Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/engine/fonts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ A set of font for LaTeX rendering.
(for example necessary to access the big integral glyph).
- `slant_angle` the angle by which the italic fonts are slanted, in degree.
- `thickness` the thickness of the lines associated to the font.
- `text_italics_correction` is a reference to a Boolean flag to enable or disable
an italics correction heuristic for text.\n
Defaults to `Ref(false)`.
- `math_italics_correction` is a reference to a Boolean flag to enable or disable
an italics correction heuristic in math expressions.\n
Defaults to `Ref(true)`.
- `italics_correction_up_to_it_spacing` is a reference to a space in font units
inserted when switching from upright to italic glyphs.\n
Defaults to `Ref(0f0)`.
"""
struct FontFamily
fonts::Dict{Symbol, String}
Expand All @@ -82,14 +91,33 @@ struct FontFamily
special_chars::Dict{Char, Tuple{String, Int}}
slant_angle::Float64
thickness::Float64
text_italics_correction::Base.RefValue{Bool}
math_italics_correction::Base.RefValue{Bool}
italics_correction_up_to_it_spacing::Base.RefValue{Float32}
end

function FontFamily(fonts ;
font_mapping = _default_font_mapping,
font_modifiers = _default_font_modifiers,
special_chars = Dict{Char, Tuple{String, Int}}(),
slant_angle = 13,
thickness = 0.0375)
thickness = 0.0375,
text_italics_correction=Ref(false),
math_italics_correction=Ref(true),
italics_correction_up_to_it_spacing=Ref(0f0)
)

if !(text_italics_correction isa Ref)
text_italics_correction = Ref(text_italics_correction)
end

if !(math_italics_correction isa Ref)
math_italics_correction = Ref(math_italics_correction)
end

if !(italics_correction_up_to_it_spacing isa Ref)
italics_correction_up_to_it_spacing = Ref(italics_correction_up_to_it_spacing)
end

fonts = merge(_default_fonts, Dict(fonts))

Expand All @@ -99,7 +127,10 @@ function FontFamily(fonts ;
font_modifiers,
special_chars,
slant_angle,
thickness
thickness,
text_italics_correction,
math_italics_correction,
italics_correction_up_to_it_spacing
)
end

Expand Down Expand Up @@ -257,7 +288,7 @@ get_fontpath(fontstyle::Symbol) = get_fontpath(FontFamily(), fontstyle)

function is_slanted(font_family, char_type)
font_id = font_family.font_mapping[char_type]
return font_id == :italic
return font_id == :italic || font_id == :bolditalic
end

slant_angle(font_family) = font_family.slant_angle * π / 180
Expand Down
138 changes: 115 additions & 23 deletions src/engine/layout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ function tex_layout(expr, state)
head = expr.head
args = [expr.args...]
shrink = 0.6

italics_correction = if state.tex_mode == :inline_math
font_family.math_italics_correction[]
else
font_family.text_italics_correction[]
end
up_to_it_space = font_family.italics_correction_up_to_it_spacing[]

try
if isleaf(expr) # :char, :delimiter, :digit, :punctuation, :symbol
Expand Down Expand Up @@ -54,7 +61,8 @@ function tex_layout(expr, state)
(0, 0),
(x + hmid(core) - hmid(accent), y)
],
[1, 1]
[1, 1],
is_slanted(core)
)
elseif head == :decorated
core, sub, super = tex_layout.(args, state)
Expand All @@ -80,31 +88,29 @@ function tex_layout(expr, state)
-0.2
),
( super_x, super_y)],
[1, shrink, super_shrink]
[1, shrink, super_shrink],
is_slanted(core) || is_slanted(super)
)
elseif head == :delimited
elements = tex_layout.(args, state)
left, content, right = elements

height = inkheight(content)
left_scale = max(1, height / inkheight(left))
right_scale = max(1, height / inkheight(right))
scales = [left_scale, 1, right_scale]

dxs = hadvance.(elements) .* scales
xs = [0, cumsum(dxs[1:end-1])...]

# TODO Height calculation for the parenthesis looks wrong
# TODO Check what the algorithm should be there
# Center the delimiters in the middle of the bot and top baselines ?
return Group(elements,
Point2f[
(xs[1], -bottominkbound(left) + bottominkbound(content)),
(xs[2], 0),
(xs[3], -bottominkbound(right) + bottominkbound(content))
],
scales
)
_elements = [
Group([left,], Point2f[(xs[1], -bottominkbound(left) + bottominkbound(content))], [left_scale], is_slanted(left))
Group([content,], Point2f[(xs[2], 0)], [1,], is_slanted(content))
Group([right,], Point2f[(xs[3], -bottominkbound(right) + bottominkbound(content))], [right_scale], is_slanted(right))
]
return horizontal_layout(_elements; italics_correction, up_to_it_space)
elseif head == :font
modifier, content = args
return tex_layout(content, add_font_modifier(state, modifier))
Expand Down Expand Up @@ -133,12 +139,13 @@ function tex_layout(expr, state)

return Group(
[line, numerator, denominator],
Point2f[(0, y0), (x1, ytop), (x2, ybottom)]
Point2f[(0, y0), (x1, ytop), (x2, ybottom)];
slanted = is_slanted(numerator) || is_slanted(denominator)
)
elseif head == :function
name = args[1]
elements = TeXChar.(collect(name), state, Ref(:function))
return horizontal_layout(elements)
return horizontal_layout(elements; italics_correction)
elseif head == :glyph
font_id, glyph_id = argument_as_string.(args)
font_id = Symbol(font_id)
Expand All @@ -151,7 +158,13 @@ function tex_layout(expr, state)
if isempty(elements)
return Space(0.0)
end
return horizontal_layout(elements)
italics_correction = if mode == :inline_math
font_family.math_italics_correction[]
else
font_family.text_italics_correction[]
end

return horizontal_layout(elements; italics_correction, up_to_it_space)
elseif head == :integral
pad = 0.1
int, sub, super = tex_layout.(args, state)
Expand All @@ -169,7 +182,8 @@ function tex_layout(expr, state)
topinkbound(int) + pad
)
],
[1, shrink, shrink]
[1, shrink, shrink],
is_slanted(int) # TODO consider upper limit as well?
)
elseif head == :lines
length(args) == 1 && return tex_layout(only(args), state)
Expand All @@ -195,16 +209,17 @@ function tex_layout(expr, state)
Point2f[
(0.25, y + lw/2 + 0.2),
(0, 0)
]
];
slanted = is_slanted(content)
)
elseif head == :primes
primes = [TeXExpr(:char, ''') for _ in 1:only(args)]
return horizontal_layout(tex_layout.(primes, state))
return horizontal_layout(tex_layout.(primes, state); italics_correction, up_to_it_space)
elseif head == :space
return Space(args[1])
elseif head == :spaced
sym = tex_layout(args[1], state)
return horizontal_layout([Space(0.2), sym, Space(0.2)])
return horizontal_layout([Space(0.2), sym, Space(0.2)]; italics_correction, up_to_it_space)
elseif head == :sqrt
content = tex_layout(args[1], state)
h = inkheight(content)
Expand Down Expand Up @@ -262,7 +277,8 @@ function tex_layout(expr, state)
(x0 + dxsub, y0 + under_offset),
(x0 + dxsuper, y0 + over_offset)
],
[1, shrink, shrink]
[1, shrink, shrink],
is_slanted(core)
)
elseif head == :unicode
font_id, glyph_id = argument_as_string.(args)
Expand All @@ -287,11 +303,14 @@ tex_layout(::Nothing, state) = Space(0)

Layout the elements horizontally, like normal text.
"""
function horizontal_layout(elements)
function horizontal_layout(elements; italics_correction=false, kwargs...)
if italics_correction
elements = _italics_correction(elements; kwargs...)
end
dxs = hadvance.(elements)
xs = [0, cumsum(dxs[1:end-1])...]

return Group(elements, Point2f.(xs, 0))
return Group(elements, Point2f.(xs, 0); slanted = is_slanted(last(elements)))
end

function layout_text(string, font_family)
Expand All @@ -301,6 +320,79 @@ function layout_text(string, font_family)
return horizontal_layout(elements)
end

function _italics_correction(
elements;
up_to_it_space=0,
scales=1
)

@assert isa(scales, Number) || length(elements) == length(scales)
elems = vcat(Space(0), elements)
j = 1

for (i, elem) in enumerate(elements)
i == 1 && continue
elem isa Space && continue
prev = elements[i-1]
prev isa Space && continue

scale_elem = _get_scale(scales, i)
scale_prev = _get_scale(scales, i-1)

if is_slanted(prev) != is_slanted(elem)
offset = 0
#=
glyph metrics defined in `sile/justenough/justenoughharfbuzz.c`;
`height` (== `y_bearing`) ⇔ `topinkbound`
`tHeight` ⇔ `- inkheight`
`width` (== `x_advance`) ⇔ `hadvance`
`x_bearing` ⇔ `leftinkbound`
`glyphWidth` (== `width`) ⇔ `inkwidth`
=#
height_prev = topinkbound(prev) * scale_prev
if is_slanted(prev) && !is_slanted(elem) && height_prev > 0
# `fromItalicCorrection` in `sile/typesetters/base.lua`
## if previous glyph was slanted and printed width `d` is greater
## than hadvance, then add difference to bearing of upright glyph
width_prev = hadvance(prev) * scale_prev
glyph_width_prev = inkwidth(prev) * scale_prev
bearing_x_prev = leftinkbound(prev) * scale_prev
d = glyph_width_prev + bearing_x_prev
delta = d > width_prev ? d - width_prev : 0
height_elem = topinkbound(elem) * scale_elem
offset = height_prev <= height_elem ? delta : delta * height_elem / height_prev
elseif !is_slanted(prev) && is_slanted(elem)
# inspired by `toItalicCorrection` in `sile/typesetters/base.lua`
d = leftinkbound(elem) * scale_elem
depth_prev = (inkheight(prev) - topinkbound(prev)) * scale_prev
depth_elem = (inkheight(elem) - topinkbound(elem)) * scale_elem
delta = -d
if d < 0 && depth_prev > 0
# `sile` formula
## if previous glyph was upright and goes beyond baseline,
## if and current glyph has a negative bearing,
## then increase bearing by flipping sign of bearing distance
offset = depth_prev >= depth_elem ? delta : delta * depth_prev / depth_elem
elseif d >= 0
## but also remove/reduce positive bearing or reduce it to some
## minimum spacing value
offset = up_to_it_space * scale_elem + delta
end
end
if offset != 0
insert!(elems, i+j, Space(offset))
j+=1
end
end
end
popfirst!(elems)
return elems
end

_get_scale(scales::Number, i)=scales
_get_scale(scales, i)=scales[i]


"""
unravel(element::TeXElement, pos, scale)

Expand Down
7 changes: 6 additions & 1 deletion src/engine/layout_context.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ function add_font_modifier(state::LayoutState, modifier)
end

function get_font(state::LayoutState, char_type)
font_id = get_font_identifier(state, char_type)
return get_font(font_family, font_id)
end

function get_font_identifier(state::LayoutState, char_type)
if state.tex_mode == :text
char_type = :text
end
Expand All @@ -40,5 +45,5 @@ function get_font(state::LayoutState, char_type)
end
end

return get_font(font_family, font_id)
return font_id
end
19 changes: 13 additions & 6 deletions src/engine/texelements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,24 +157,27 @@ function TeXChar(char::Char, state::LayoutState, char_type)
return TeXChar(id, font, font_family, false, char)
end

font = get_font(state, char_type)
font_id = get_font_identifier(state, char_type)
font = get_font(font_family, font_id)

return TeXChar(
glyph_index(font, char),
font,
font_family,
is_slanted(state.font_family, char_type),
font_id == :italic || font_id == :bolditalic, # previously: `is_slanted(state.font_family, char_type)`
char)
end

function TeXChar(name::AbstractString, state::LayoutState, char_type ; represented='?')
font_family = state.font_family
font = get_font(state, char_type)
font_id = get_font_identifier(state, char_type)
font = get_font(font_family, font_id)

return TeXChar(
glyph_index(font, name),
font,
font_family,
is_slanted(state.font_family, char_type),
font_id == :italic || font_id == :bolditalic, # previously: `is_slanted(state.font_family, char_type)`
represented)
end

Expand Down Expand Up @@ -289,9 +292,13 @@ struct Group{T} <: TeXElement
elements::Vector{<:TeXElement}
positions::Vector{Point2f}
scales::Vector{T}
slanted::Bool
end

Group(elements, positions) = Group(elements, positions, ones(length(elements)))
Group(elements, positions, scales; slanted=false) = Group(elements, positions, scales, slanted)
Group(elements, positions; slanted=false) = Group(elements, positions, ones(length(elements)); slanted)

is_slanted(g::Group) = g.slanted

xpositions(g::Group) = [p[1] for p in g.positions]
ypositions(g::Group) = [p[2] for p in g.positions]
Expand Down Expand Up @@ -334,4 +341,4 @@ end
xheight(g::Group) = maximum(xheight.(g.elements) .* g.scales)

leftmost_glyph(g::Group) = leftmost_glyph(first(g.elements))
rightmost_glyph(g::Group) = rightmost_glyph(last(glyph))
rightmost_glyph(g::Group) = rightmost_glyph(last(g.elements))
2 changes: 1 addition & 1 deletion src/parser/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end
function Base.showerror(io::IO, e::TeXParseError)
println(io, "TeXParseError: ", e.msg)
show_state(io, e.stack, e.position, e.tex)
show_tokenization(io, tex)
show_tokenization(io, e.tex)
end

function show_tokenization(io, tex)
Expand Down
Loading