Skip to content

Commit c45cfbe

Browse files
authored
Merge pull request #45 from JuliaGraphics/sd/em_units
fixes + add more needed functionality
2 parents 2804aae + 5a24237 commit c45cfbe

File tree

4 files changed

+77
-40
lines changed

4 files changed

+77
-40
lines changed

src/layout.jl

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ iter_or_array(x::AbstractArray) = x
55
iter_or_array(x::StaticArray) = repeated(x)
66

77

8-
function metrics_bb(char::Char, font::FTFont, scale)
9-
extent = get_extent(font, char) .* Vec2f0(scale)
8+
function metrics_bb(char::Char, font::FTFont, pixel_size)
9+
extent = get_extent(font, char) .* Vec2f0(pixel_size)
1010
mini = bearing(extent)
1111
return Rect2D(mini, Vec2f0(extent.scale)), extent
1212
end
1313

14+
function boundingbox(char::Char, font::FTFont, pixel_size)
15+
bb, extent = metrics_bb(char, font, pixel_size)
16+
return bb
17+
end
18+
19+
function glyph_ink_size(char::Char, font::FTFont, pixel_size)
20+
bb, extent = metrics_bb(char, font, pixel_size)
21+
return widths(bb)
22+
end
23+
1424
"""
1525
iterate_extents(f, line::AbstractString, fonts, scales)
1626
Iterates over the extends of the characters (glyphs) in line!
@@ -41,7 +51,6 @@ function iterate_extents(f, line::AbstractString, fonts, scales)
4151
end
4252
end
4353

44-
4554
function glyph_rects(line::AbstractString, fonts, scales)
4655
rects = Rect2D[]
4756
iterate_extents(line, fonts, scales) do char, box, advance
@@ -53,3 +62,19 @@ end
5362
function boundingbox(line::AbstractString, fonts, scales)
5463
return reduce(union, glyph_rects(line, fonts, scales))
5564
end
65+
66+
function inkboundingbox(ext::FontExtent)
67+
l = leftinkbound(ext)
68+
r = rightinkbound(ext)
69+
b = bottominkbound(ext)
70+
t = topinkbound(ext)
71+
return FRect2D((l, b), (r - l, t - b))
72+
end
73+
74+
function height_insensitive_boundingbox(ext::FontExtent, font::FTFont)
75+
l = leftinkbound(ext)
76+
r = rightinkbound(ext)
77+
b = descender(font)
78+
t = ascender(font)
79+
return FRect2D((l, b), (r - l, t - b))
80+
end

src/rendering.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11

2-
function renderface(face::FTFont, c::Char,
3-
pixelsize::Integer=get_pixelsize(face))
2+
function loadchar(face::FTFont, c::Char)
3+
err = FT_Load_Char(face, c, FT_LOAD_RENDER)
4+
check_error(err, "Could not load char to render.")
5+
end
6+
7+
function renderface(face::FTFont, c::Char, pixelsize::Integer)
48
set_pixelsize(face, pixelsize)
59
loadchar(face, c)
610
glyph = face.glyph
711
@assert glyph.format == FreeType.FT_GLYPH_FORMAT_BITMAP
8-
return glyphbitmap(glyph.bitmap), get_extent(face, c)
12+
return glyphbitmap(glyph.bitmap), FontExtent(glyph.metrics)
913
end
1014

1115
function glyphbitmap(bitmap::FreeType.FT_Bitmap)
@@ -46,17 +50,17 @@ function renderstring!(
4650

4751
if pixelsize isa Tuple
4852
@warn "using tuple for pixelsize is deprecated, please use one integer"
49-
set_pixelsize(face, pixelsize[1])
50-
else
51-
set_pixelsize(face, pixelsize)
53+
pixelsize = pixelsize[1]
5254
end
5355

56+
set_pixelsize(face, pixelsize)
57+
5458
bitmaps = Vector{Matrix{UInt8}}(undef, lastindex(str))
5559
metrics = Vector{FontExtent{Int}}(undef, lastindex(str))
5660
ymin = ymax = sumadvancex = 0
5761

5862
for (istr, char) = enumerate(str)
59-
bitmap, metric_float = renderface(face, char)
63+
bitmap, metric_float = renderface(face, char, pixelsize)
6064
metric = round.(Int, metric_float)
6165
bitmaps[istr] = bitmap
6266
metrics[istr] = metric

src/types.jl

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ struct FontExtent{T}
3535
scale::Vec{2, T}
3636
end
3737

38+
hadvance(ext::FontExtent) = ext.advance[1]
39+
inkwidth(ext::FontExtent) = ext.scale[1]
40+
inkheight(ext::FontExtent) = ext.scale[2]
41+
hbearing_ori_to_left(ext::FontExtent) = ext.horizontal_bearing[1]
42+
hbearing_ori_to_top(ext::FontExtent) = ext.horizontal_bearing[2]
43+
leftinkbound(ext::FontExtent) = hbearing_ori_to_left(ext)
44+
rightinkbound(ext::FontExtent) = leftinkbound(ext) + inkwidth(ext)
45+
bottominkbound(ext::FontExtent) = hbearing_ori_to_top(ext) - inkheight(ext)
46+
topinkbound(ext::FontExtent) = hbearing_ori_to_top(ext)
47+
3848
BroadcastStyle(::Type{<: FontExtent}) = Style{FontExtent}()
3949
BroadcastStyle(::Style{FontExtent}, x) = Style{FontExtent}()
4050
BroadcastStyle(x, ::Style{FontExtent}) = Style{FontExtent}()
@@ -110,20 +120,18 @@ end
110120

111121
mutable struct FTFont
112122
ft_ptr::FreeType.FT_Face
113-
current_pixelsize::Base.RefValue{Int}
114123
use_cache::Bool
115-
cache::Dict{Tuple{Int, Char}, FontExtent{Float32}}
116-
function FTFont(ft_ptr::FreeType.FT_Face, pixel_size::Int=64, use_cache::Bool=true)
117-
cache = Dict{Tuple{Int, Char}, FontExtent{Float32}}()
118-
face = new(ft_ptr, Ref(pixel_size), use_cache, cache)
124+
extent_cache::Dict{Char, FontExtent{Float32}}
125+
function FTFont(ft_ptr::FreeType.FT_Face, use_cache::Bool=true)
126+
extent_cache = Dict{Tuple{Int, Char}, FontExtent{Float32}}()
127+
face = new(ft_ptr, use_cache, extent_cache)
119128
finalizer(safe_free, face)
120-
FT_Set_Pixel_Sizes(face, pixel_size, 0);
121129
return face
122130
end
123131
end
124132

125133
use_cache(face::FTFont) = getfield(face, :use_cache)
126-
get_cache(face::FTFont) = getfield(face, :cache)
134+
get_cache(face::FTFont) = getfield(face, :extent_cache)
127135

128136
function FTFont(path::String)
129137
return FTFont(newface(path))
@@ -159,13 +167,9 @@ end
159167
# Allow broadcasting over fonts
160168
Base.Broadcast.broadcastable(ft::FTFont) = Ref(ft)
161169

162-
get_pixelsize(face::FTFont) = getfield(face, :current_pixelsize)[]
163-
164170
function set_pixelsize(face::FTFont, size::Integer)
165-
get_pixelsize(face) == size && return size
166171
err = FT_Set_Pixel_Sizes(face, size, size)
167172
check_error(err, "Couldn't set pixelsize")
168-
getfield(face, :current_pixelsize)[] = size
169173
return size
170174
end
171175

@@ -181,14 +185,9 @@ function kerning(c1::Char, c2::Char, face::FTFont)
181185
return Vec2f0(kerning2d[].x / divisor, kerning2d[].y / divisor)
182186
end
183187

184-
function loadchar(face::FTFont, c::Char)
185-
err = FT_Load_Char(face, c, FT_LOAD_RENDER)
186-
check_error(err, "Could not load char to render.")
187-
end
188-
189188
function get_extent(face::FTFont, char::Char)
190189
if use_cache(face)
191-
get!(get_cache(face), (get_pixelsize(face), char)) do
190+
get!(get_cache(face), char) do
192191
return internal_get_extent(face, char)
193192
end
194193
else
@@ -197,9 +196,20 @@ function get_extent(face::FTFont, char::Char)
197196
end
198197

199198
function internal_get_extent(face::FTFont, char::Char)
200-
err = FT_Load_Char(face, char, FT_LOAD_DEFAULT)
201-
check_error(err, "Could not load char to get extend.")
202-
metrics = face.glyph.metrics
203-
# 64 since metrics are in 1/64 units (units to 26.6 fractional pixels)
204-
return FontExtent(metrics, Float32(64))
205-
end
199+
#=
200+
Load chars without scaling. This leaves all glyph metrics that can be
201+
retrieved in font units, which can be normalized by dividing with the
202+
font's units_per_EM. This is more robust than relying on extents
203+
that are only valid with a specific pixelsize, because a font's
204+
pixelsize can be silently changed by third parties, such as Cairo.
205+
If that happens, all glyph metrics are incorrect. We avoid this by using the normalized space.
206+
=#
207+
err = FT_Load_Char(face, char, FT_LOAD_NO_SCALE)
208+
check_error(err, "Could not load char to get extent.")
209+
# This gives us the font metrics in normalized units (0, 1), with negative
210+
# numbers interpreted as an offset
211+
return FontExtent(face.glyph.metrics, Float32(face.units_per_EM))
212+
end
213+
214+
descender(font) = font.descender / font.units_per_EM
215+
ascender(font) = font.ascender / font.units_per_EM

test/runtests.jl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
using FreeTypeAbstraction, Colors, ColorVectorSpace, GeometryBasics
22
using Test
3-
using FreeTypeAbstraction: boundingbox, Vec, glyph_rects, get_extent, FTFont, kerning
3+
using FreeTypeAbstraction: boundingbox, Vec, glyph_rects, get_extent, FTFont, kerning, glyph_ink_size
44
using FreeType
55

66
face = FreeTypeAbstraction.findfont("hack"; additional_fonts=@__DIR__)
77

8-
bb = boundingbox("asdasd", face, 1.0)
9-
@test bb == Rect(4.0, -1.0, 224.0, 50.0)
8+
bb = boundingbox("asdasd", face, 64)
9+
@test round.(Int, minimum(bb)) == Vec(4, -1)
10+
@test round.(Int, widths(bb)) == Vec2(221, 50)
1011

1112
FA = FreeTypeAbstraction
1213

1314
FA.set_pixelsize(face, 64) # should be the default
14-
img, extent = renderface(face, 'C')
15+
img, extent = renderface(face, 'C', 64)
1516
@test size(img) == (30, 49)
1617
@test typeof(img) == Array{UInt8,2}
17-
@test extent == FontExtent{Float32}(Float32[-15.0, 6.0], Float32[4.0, 48.0], Float32[39.0, 62.0], Float32[30.0, 49.0])
18-
img, extent = renderface(face, 'C', 200)
19-
@test round.(Int, widths(FA.boundingbox(extent))) == Vec(size(img))
2018

2119
a = renderstring!(zeros(UInt8, 20, 100), "helgo", face, 10, 10, 10)
2220

0 commit comments

Comments
 (0)