Skip to content

Commit d279078

Browse files
committed
various fixes
1 parent 53b4655 commit d279078

File tree

5 files changed

+414
-321
lines changed

5 files changed

+414
-321
lines changed

src/findfonts.jl

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
if Sys.isapple()
2-
function _font_paths()
3-
[
4-
"/Library/Fonts", # Additional fonts that can be used by all users. This is generally where fonts go if they are to be used by other applications.
5-
joinpath(homedir(), "Library/Fonts"), # Fonts specific to each user.
6-
"/Network/Library/Fonts", # Fonts shared for users on a network
7-
"/System/Library/Fonts", # System specific fonts
8-
"/System/Library/Fonts/Supplemental", # new location since Catalina
9-
]
10-
end
2+
_font_paths() = [
3+
"/Library/Fonts", # Additional fonts that can be used by all users. This is generally where fonts go if they are to be used by other applications.
4+
joinpath(homedir(), "Library/Fonts"), # Fonts specific to each user.
5+
"/Network/Library/Fonts", # Fonts shared for users on a network
6+
"/System/Library/Fonts", # System specific fonts
7+
"/System/Library/Fonts/Supplemental", # new location since Catalina
8+
]
119
elseif Sys.iswindows()
1210
_font_paths() = [
1311
joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "Fonts"),
@@ -25,7 +23,7 @@ else
2523
end
2624
function _font_paths()
2725
result = String[]
28-
for p in ("/usr/share/fonts", joinpath(homedir(), ".fonts"), joinpath(homedir(), ".local/share/fonts"), "/usr/local/share/fonts",)
26+
for p in ("/usr/share/fonts", joinpath(homedir(), ".fonts"), joinpath(homedir(), ".local/share/fonts"), "/usr/local/share/fonts",)
2927
if isdir(p)
3028
push!(result, p)
3129
add_recursive(result, p)
@@ -35,17 +33,11 @@ else
3533
end
3634
end
3735

38-
function family_name(x::FTFont)
39-
lowercase(x.family_name)
40-
end
41-
42-
function style_name(x::FTFont)
43-
lowercase(x.style_name)
44-
end
36+
family_name(x::FTFont) = lowercase(x.family_name)
37+
style_name(x::FTFont) = lowercase(x.style_name)
4538

4639
const REGULAR_STYLES = ("regular", "normal", "medium", "standard", "roman", "book")
4740

48-
4941
"""
5042
Match a font using the user-specified search string. Each part of the search string
5143
is searched in the family name first which has to match once to include the font
@@ -79,7 +71,6 @@ Then this is how this function would match different search strings:
7971
- "arial" => no match
8072
"""
8173
function match_font(face::FTFont, searchparts)::Tuple{Int, Int, Bool, Int}
82-
8374
fname = family_name(face)
8475
sname = style_name(face)
8576
is_regular_style = any(occursin(s, sname) for s in REGULAR_STYLES)
@@ -121,7 +112,6 @@ end
121112

122113
fontname(ft::FTFont) = "$(family_name(ft)) $(style_name(ft))"
123114

124-
125115
function findfont(
126116
searchstring::String;
127117
italic::Bool=false, # this is unused in the new implementation

src/layout.jl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ iter_or_array(x::StaticArray) = repeated(x)
77

88
function metrics_bb(char::Char, font::FTFont, pixel_size)
99
extent = get_extent(font, char) .* Vec2f(pixel_size)
10-
mini = bearing(extent)
11-
return Rect2(mini, Vec2f(extent.scale)), extent
10+
return boundingbox(extent), extent
1211
end
1312

1413
function boundingbox(char::Char, font::FTFont, pixel_size)
@@ -59,9 +58,7 @@ function glyph_rects(line::AbstractString, fonts, scales)
5958
return rects
6059
end
6160

62-
function boundingbox(line::AbstractString, fonts, scales)
63-
return reduce(union, glyph_rects(line, fonts, scales))
64-
end
61+
boundingbox(line::AbstractString, fonts, scales) = reduce(union, glyph_rects(line, fonts, scales))
6562

6663
function inkboundingbox(ext::FontExtent)
6764
l = leftinkbound(ext)

src/rendering.jl

Lines changed: 121 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ function loadchar(face::FTFont, c::Char)
44
check_error(err, "Could not load char to render.")
55
end
66

7-
function renderface(face::FTFont, c::Char, pixelsize::Integer)
7+
function loadglyph(face::FTFont, c::Char, pixelsize::Integer)
88
set_pixelsize(face, pixelsize)
99
loadchar(face, c)
1010
glyph = unsafe_load(face.glyph)
1111
@assert glyph.format == FreeType.FT_GLYPH_FORMAT_BITMAP
12-
return glyphbitmap(glyph.bitmap), FontExtent(glyph.metrics)
12+
glyph
13+
end
14+
15+
function renderface(face::FTFont, c::Char, pixelsize::Integer)
16+
glyph = loadglyph(face, c, pixelsize)
17+
glyphbitmap(glyph.bitmap), FontExtent(glyph.metrics)
1318
end
1419

20+
extents(face::FTFont, c::Char, pixelsize::Integer) =
21+
FontExtent(loadglyph(face, c, pixelsize).metrics)
22+
1523
function glyphbitmap(bitmap::FreeType.FT_Bitmap)
1624
@assert bitmap.pixel_mode == FreeType.FT_PIXEL_MODE_GRAY
1725
bmp = Matrix{UInt8}(undef, bitmap.width, bitmap.rows)
@@ -24,7 +32,7 @@ function glyphbitmap(bitmap::FreeType.FT_Bitmap)
2432
bmp[:, r] = src
2533
row += bitmap.pitch
2634
end
27-
return bmp
35+
bmp
2836
end
2937

3038
one_or_typemax(::Type{T}) where {T<:Union{Real,Colorant}} = T<:Integer ? typemax(T) : oneunit(T)
@@ -34,107 +42,163 @@ one_or_typemax(::Type{T}) where {T<:Union{Real,Colorant}} = T<:Integer ? typemax
3442
fcolor=one_or_typemax(T), bcolor=zero(T), halign=:hleft, valign=:vbaseline) -> Matrix
3543
3644
Render `str` into `img` using the font `face` of size `pixelsize` at coordinates `y0,x0`.
45+
Uses the conventions of freetype.org/freetype2/docs/glyphs/glyphs-3.html
3746
3847
# Arguments
3948
* `y0,x0`: origin is in upper left with positive `y` going down
4049
* `fcolor`: foreground color; AbstractVector{T}, typemax(T) for T<:Integer, otherwise one(T)
4150
* `gcolor`: background color; AbstractVector{T}, typemax(T) for T<:Integer, otherwise one(T)
4251
* `bcolor`: canvas background color; set to `nothing` for transparent
4352
* `halign`: :hleft, :hcenter, or :hright
44-
* `valign`: :vtop, :vcenter, :vbaseline, or :vbottom
53+
* `valign`: :vtop, :vcenter, :vbaseline, or :vbttom
54+
* `bbox_glyph`: glyph bounding box color (debugging)
55+
* `bbox`: bounding box color (debugging)
56+
* `gstr`: background string or array of chars (for background sizing)
57+
* `incx`: extra x spacing
4558
"""
4659
function renderstring!(
47-
img::AbstractMatrix{T}, str::Union{AbstractVector{Char},String}, face::FTFont, pixelsize::Union{Int, Tuple{Int, Int}}, y0, x0;
60+
img::AbstractMatrix{T}, fstr::Union{AbstractVector{Char},String},
61+
face::FTFont, pixelsize::Union{Int, Tuple{Int, Int}}, y0, x0;
4862
fcolor::Union{AbstractVector{T},T} = one_or_typemax(T),
4963
gcolor::Union{AbstractVector{T},T,Nothing} = nothing,
5064
bcolor::Union{T,Nothing} = zero(T),
51-
halign::Symbol = :hleft, valign::Symbol = :vbaseline
65+
halign::Symbol = :hleft,
66+
valign::Symbol = :vbaseline,
67+
bbox_glyph::Union{T,Nothing} = nothing,
68+
bbox::Union{T,Nothing} = nothing,
69+
gstr::Union{AbstractVector{Char},String,Nothing} = nothing,
70+
off_bg::Int = 0,
71+
incx::Int = 0,
5272
) where T<:Union{Real,Colorant}
5373

5474
if pixelsize isa Tuple
5575
@warn "using tuple for pixelsize is deprecated, please use one integer"
5676
pixelsize = pixelsize[1]
5777
end
58-
59-
str = str isa AbstractVector ? String(str) : str
60-
6178
set_pixelsize(face, pixelsize)
6279

63-
bitmaps = Vector{Matrix{UInt8}}(undef, lastindex(str))
64-
metrics = Vector{FontExtent{Int}}(undef, lastindex(str))
65-
# ymin and ymax w.r.t the baseline
66-
ymin = ymax = sumadvancex = 0
80+
fstr = fstr isa AbstractVector ? fstr : collect(fstr)
81+
if gstr !== nothing
82+
gstr = gstr isa AbstractVector ? gstr : collect(gstr)
83+
end
6784

68-
for (istr, char) = enumerate(str)
69-
bitmap, metric_float = renderface(face, char, pixelsize)
70-
metric = round.(Int, metric_float)
71-
bitmaps[istr] = bitmap
72-
metrics[istr] = metric
85+
len = length(fstr)
86+
bitmaps = Vector{Matrix{UInt8}}(undef, len)
87+
metrics = Vector{FontExtent{Int}}(undef, len)
7388

74-
# scale of glyph (size of bitmap)
75-
w, h = metric.scale
76-
# offset between glyph origin and bitmap top left corner
77-
bx, by = metric.horizontal_bearing
78-
ymin = min(ymin, by - h)
79-
ymax = max(ymax, by)
80-
sumadvancex += metric.advance[1]
89+
y_min = y_max = sum_advance_x = 0 # y_min and y_max are w.r.t the baseline
90+
for (i, char) in enumerate(fstr)
91+
bitmap, metricf = renderface(face, char, pixelsize)
92+
metric = round.(Int, metricf)
93+
bitmaps[i] = bitmap
94+
metrics[i] = metric
95+
96+
y_min = min(y_min, bottominkbound(metric))
97+
y_max = max(y_max, topinkbound(metric))
98+
sum_advance_x += hadvance(metric)
8199
end
82100

83-
px = x0 - (halign == :hright ? sumadvancex : halign == :hcenter ? sumadvancex >> 1 : 0)
101+
bitmap_max = bitmaps |> first |> eltype |> typemax
102+
imgh, imgw = size(img)
103+
104+
# initial pen position
105+
px = x0 - (halign == :hright ? sum_advance_x : halign == :hcenter ? sum_advance_x >> 1 : 0)
84106
py = y0 + (
85-
valign == :vtop ? ymax : valign == :vbottom ? ymin :
86-
valign == :vcenter ? (ymax - ymin) >> 1 + ymin : 0
107+
valign == :vtop ? y_max : valign == :vbottom ? y_min :
108+
valign == :vcenter ? (y_max - y_min) >> 1 + y_min : 0
87109
)
88110

89-
bitmapmax = typemax(eltype(bitmaps[1]))
90-
91-
imgh, imgw = size(img)
92111
if bcolor !== nothing
93112
img[
94-
clamp(py-ymax+1, 1, imgh) : clamp(py-ymin-1, 1, imgh),
95-
clamp(px-1, 1, imgw) : clamp(px+sumadvancex-1, 1, imgw)
113+
clamp(py - y_max, 1, imgh) : clamp(py - y_min, 1, imgh),
114+
clamp(px, 1, imgw) : clamp(px + sum_advance_x, 1, imgw)
96115
] .= bcolor
97116
end
98117

99118
local prev_char::Char
100-
for (istr, char) = enumerate(str)
101-
w, h = metrics[istr].scale
102-
bx, by = metrics[istr].horizontal_bearing
119+
for (i, char) in enumerate(fstr)
120+
bitmap = bitmaps[i]
121+
metric = metrics[i]
122+
bx, by = metric.horizontal_bearing
123+
ax, ay = metric.advance
124+
sx, sy = metric.scale
103125

104-
if istr == 1
126+
if i == 1
105127
prev_char = char
106128
else
107-
kx, ky = map(x-> round(Int, x), kerning(prev_char, char, face))
129+
kx, _ = map(x-> round(Int, x), kerning(prev_char, char, face))
108130
px += kx
109131
end
110132

111-
fcol = fcolor isa AbstractVector ? fcolor[istr] : fcolor
112-
gcol = gcolor isa AbstractVector ? gcolor[istr] : gcolor
133+
# glyph origin
134+
oy = py - by
135+
ox = px + bx
136+
137+
fcol = fcolor isa AbstractVector ? fcolor[i] : fcolor
138+
gcol = gcolor isa AbstractVector ? gcolor[i] : gcolor
113139

114140
# trim parts of glyph images that are outside the destination
115-
cliprowlo, cliprowhi = max(0, -(py-by)), max(0, py - by + h - imgh)
116-
clipcollo, clipcolhi = max(0, -bx-px), max(0, px + bx + w - imgw)
141+
row_lo, row_hi = 1 + max(0, -oy), sy - max(0, oy + sy - imgh)
142+
col_lo, col_hi = 1 + max(0, -ox), sx - max(0, ox + sx - imgw)
117143

118144
if gcol === nothing
119-
for row = 1+cliprowlo : h-cliprowhi, col = 1+clipcollo : w-clipcolhi
120-
bitmaps[istr][col,row] == 0 && continue
121-
c1 = bitmaps[istr][col,row] / bitmapmax * fcol
122-
img[row+py-by, col+px+bx] = T <: Integer ? round(T, c1) : T(c1)
145+
for r in row_lo:row_hi, c in col_lo:col_hi
146+
(bm = bitmap[c, r]) == 0 && continue
147+
color = bm / bitmap_max * fcol
148+
img[oy + r, ox + c] = T <: Integer ? round(T, color) : T(color)
123149
end
124150
else
125-
img[
126-
clamp(py-ymax+1, 1, imgh) : clamp(py-ymin-1, 1, imgh),
127-
clamp(px-1, 1, imgw) : clamp(px+sumadvancex-1, 1, imgw)
128-
] .= gcol
129-
for row = 1+cliprowlo : h-cliprowhi, col = 1+clipcollo : w-clipcolhi
130-
bitmaps[istr][col, row] == 0 && continue
131-
w1 = bitmaps[istr][col, row] / bitmapmax
132-
c1 = w1 * fcol
133-
c0 = (1.0 - w1) * gcol
134-
img[row + py - by, col + px + bx] = T <: Integer ? round(T, c1 + c0) : T(c1 + c0)
151+
if gstr !== nothing
152+
gmetric = round.(Int, extents(face, gstr[i], pixelsize))
153+
y_min = bottominkbound(gmetric)
154+
y_max = topinkbound(gmetric)
155+
end
156+
157+
# fill background
158+
by1, by2 = py - y_max, py - y_min
159+
bx1, bx2 = px, px + ax
160+
r1, r2 = clamp(by1, 1, imgh), clamp(by2, 1, imgh)
161+
c1, c2 = clamp(bx1, 1, imgw), clamp(bx2, 1, imgw)
162+
for r in r1 + off_bg:r2 - off_bg, c in c1 + off_bg:c2 - off_bg
163+
img[r, c] = gcol
164+
end
165+
166+
# render character by drawing the corresponding glyph
167+
for r in row_lo:row_hi, c in col_lo:col_hi
168+
(bm = bitmap[c, r]) == 0 && continue
169+
w1 = bm / bitmap_max
170+
color0 = w1 * fcol
171+
color1 = (1.0 - w1) * gcol
172+
img[oy + r, ox + c] = T <: Integer ? round(T, color0 + color1) : T(color0 + color1)
173+
end
174+
175+
# draw background bounding box
176+
if bbox !== nothing && r2 > r1 && c2 > c1
177+
img[r1, c1:c2] .= bbox
178+
img[r2, c1:c2] .= bbox
179+
img[r1:r2, c1] .= bbox
180+
img[r1:r2, c2] .= bbox
181+
end
182+
end
183+
184+
# draw glyph bounding box
185+
if bbox_glyph !== nothing
186+
rect = boundingbox(metric)
187+
(mc, mr), (Mc, Mr) = extrema(rect)
188+
r1, r2 = clamp(py + mr, 1, imgh), clamp(py + Mr, 1, imgh)
189+
c1, c2 = clamp(px + mc, 1, imgw), clamp(px + Mc, 1, imgw)
190+
# ^^^ should be equivalent to vvv
191+
# r1, r2 = clamp(oy + row_lo, 1, imgh), clamp(oy + row_hi, 1, imgh)
192+
# c1, c2 = clamp(ox + col_lo, 1, imgw), clamp(ox + col_hi, 1, imgw)
193+
if r2 > r1 && c2 > c1
194+
img[r1, c1:c2] .= bbox_glyph
195+
img[r2, c1:c2] .= bbox_glyph
196+
img[r1:r2, c1] .= bbox_glyph
197+
img[r1:r2, c2] .= bbox_glyph
135198
end
136199
end
137-
px += metrics[istr].advance[1]
200+
201+
px += ax + incx
138202
end
139203
return img
140204
end

0 commit comments

Comments
 (0)