Skip to content

Commit 0789347

Browse files
committed
Merge branch 'master' into sd/no-sarray
2 parents e63bef5 + a8fecea commit 0789347

File tree

6 files changed

+423
-281
lines changed

6 files changed

+423
-281
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "FreeTypeAbstraction"
22
uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
3-
version = "0.9.5"
3+
version = "0.9.8"
44

55
[deps]
66
ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4"

src/findfonts.jl

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
if Sys.isapple()
22
function _font_paths()
3-
[
3+
return [
44
"/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.
55
joinpath(homedir(), "Library/Fonts"), # Fonts specific to each user.
66
"/Network/Library/Fonts", # Fonts shared for users on a network
@@ -9,10 +9,12 @@ if Sys.isapple()
99
]
1010
end
1111
elseif Sys.iswindows()
12-
_font_paths() = [
13-
joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "Fonts"),
14-
joinpath(homedir(), "AppData", "Local", "Microsoft", "Windows", "Fonts"),
15-
]
12+
function _font_paths()
13+
return [
14+
joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "Fonts"),
15+
joinpath(homedir(), "AppData", "Local", "Microsoft", "Windows", "Fonts"),
16+
]
17+
end
1618
else
1719
function add_recursive(result, path)
1820
for p in readdir(path)
@@ -25,27 +27,26 @@ else
2527
end
2628
function _font_paths()
2729
result = String[]
28-
for p in ("/usr/share/fonts", joinpath(homedir(), ".fonts"), joinpath(homedir(), ".local/share/fonts"), "/usr/local/share/fonts",)
30+
for p in (
31+
"/usr/share/fonts",
32+
joinpath(homedir(), ".fonts"),
33+
joinpath(homedir(), ".local/share/fonts"),
34+
"/usr/local/share/fonts"
35+
)
2936
if isdir(p)
3037
push!(result, p)
3138
add_recursive(result, p)
3239
end
3340
end
34-
result
41+
return result
3542
end
3643
end
3744

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
45+
family_name(x::FTFont) = lowercase(x.family_name)
46+
style_name(x::FTFont) = lowercase(x.style_name)
4547

4648
const REGULAR_STYLES = ("regular", "normal", "medium", "standard", "roman", "book")
4749

48-
4950
"""
5051
Match a font using the user-specified search string. Each part of the search string
5152
is searched in the family name first which has to match once to include the font
@@ -79,7 +80,6 @@ Then this is how this function would match different search strings:
7980
- "arial" => no match
8081
"""
8182
function match_font(face::FTFont, searchparts)::Tuple{Int, Int, Bool, Int}
82-
8383
fname = family_name(face)
8484
sname = style_name(face)
8585
is_regular_style = any(occursin(s, sname) for s in REGULAR_STYLES)
@@ -108,7 +108,7 @@ function match_font(face::FTFont, searchparts)::Tuple{Int, Int, Bool, Int}
108108

109109
style_score = sum(length(part) for part in remaining_parts if occursin(part, sname))
110110

111-
(family_score, style_score, is_regular_style, fontlength_penalty)
111+
return (family_score, style_score, is_regular_style, fontlength_penalty)
112112
end
113113

114114
function try_load(fpath)
@@ -121,7 +121,6 @@ end
121121

122122
fontname(ft::FTFont) = "$(family_name(ft)) $(style_name(ft))"
123123

124-
125124
function findfont(
126125
searchstring::String;
127126
italic::Bool=false, # this is unused in the new implementation
@@ -170,5 +169,5 @@ function findfont(
170169
end
171170
end
172171

173-
best_font
172+
return best_font
174173
end

src/layout.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ iter_or_array(x::Union{Mat, StaticVector}) = 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)

src/rendering.jl

Lines changed: 132 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@ 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 glyph
13+
end
14+
15+
function renderface(face::FTFont, c::Char, pixelsize::Integer)
16+
glyph = loadglyph(face, c, pixelsize)
1217
return glyphbitmap(glyph.bitmap), FontExtent(glyph.metrics)
1318
end
1419

20+
function extents(face::FTFont, c::Char, pixelsize::Integer)
21+
return FontExtent(loadglyph(face, c, pixelsize).metrics)
22+
end
23+
1524
function glyphbitmap(bitmap::FreeType.FT_Bitmap)
1625
@assert bitmap.pixel_mode == FreeType.FT_PIXEL_MODE_GRAY
1726
bmp = Matrix{UInt8}(undef, bitmap.width, bitmap.rows)
@@ -27,102 +36,172 @@ function glyphbitmap(bitmap::FreeType.FT_Bitmap)
2736
return bmp
2837
end
2938

30-
one_or_typemax(::Type{T}) where {T<:Union{Real,Colorant}} = T<:Integer ? typemax(T) : oneunit(T)
39+
function one_or_typemax(::Type{T}) where {T<:Union{Real,Colorant}}
40+
return T<:Integer ? typemax(T) : oneunit(T)
41+
end
3142

3243
"""
3344
renderstring!(img::AbstractMatrix, str::String, face, pixelsize, y0, x0;
3445
fcolor=one_or_typemax(T), bcolor=zero(T), halign=:hleft, valign=:vbaseline) -> Matrix
3546
3647
Render `str` into `img` using the font `face` of size `pixelsize` at coordinates `y0,x0`.
48+
Uses the conventions of freetype.org/freetype2/docs/glyphs/glyphs-3.html
3749
3850
# Arguments
3951
* `y0,x0`: origin is in upper left with positive `y` going down
40-
* `fcolor`: foreground color; typemax(T) for T<:Integer, otherwise one(T)
41-
* `bcolor`: background color; set to `nothing` for transparent
52+
* `fcolor`: foreground color; AbstractVector{T}, typemax(T) for T<:Integer, otherwise one(T)
53+
* `gcolor`: background color; AbstractVector{T}, typemax(T) for T<:Integer, otherwise one(T)
54+
* `bcolor`: canvas background color; set to `nothing` for transparent
4255
* `halign`: :hleft, :hcenter, or :hright
43-
* `valign`: :vtop, :vcenter, :vbaseline, or :vbottom
56+
* `valign`: :vtop, :vcenter, :vbaseline, or :vbttom
57+
* `bbox_glyph`: glyph bounding box color (debugging)
58+
* `bbox`: bounding box color (debugging)
59+
* `gstr`: background string or array of chars (for background sizing)
60+
* `incx`: extra x spacing
4461
"""
4562
function renderstring!(
46-
img::AbstractMatrix{T}, str::String, face::FTFont, pixelsize::Union{Int, Tuple{Int, Int}}, y0, x0;
47-
fcolor::T = one_or_typemax(T), bcolor::Union{T,Nothing} = zero(T),
48-
halign::Symbol = :hleft, valign::Symbol = :vbaseline
63+
img::AbstractMatrix{T}, fstr::Union{AbstractVector{Char},String},
64+
face::FTFont, pixelsize::Union{Int, Tuple{Int, Int}}, y0, x0;
65+
fcolor::Union{AbstractVector{T},T} = one_or_typemax(T),
66+
gcolor::Union{AbstractVector{T},T,Nothing} = nothing,
67+
bcolor::Union{T,Nothing} = zero(T),
68+
halign::Symbol = :hleft,
69+
valign::Symbol = :vbaseline,
70+
bbox_glyph::Union{T,Nothing} = nothing,
71+
bbox::Union{T,Nothing} = nothing,
72+
gstr::Union{AbstractVector{Char},String,Nothing} = nothing,
73+
off_bg::Int = 0,
74+
incx::Int = 0,
4975
) where T<:Union{Real,Colorant}
5076

5177
if pixelsize isa Tuple
5278
@warn "using tuple for pixelsize is deprecated, please use one integer"
5379
pixelsize = pixelsize[1]
5480
end
55-
5681
set_pixelsize(face, pixelsize)
5782

58-
bitmaps = Vector{Matrix{UInt8}}(undef, lastindex(str))
59-
metrics = Vector{FontExtent{Int}}(undef, lastindex(str))
60-
# ymin and ymax w.r.t the baseline
61-
ymin = ymax = sumadvancex = 0
83+
fstr = fstr isa AbstractVector ? fstr : collect(fstr)
84+
if gstr !== nothing
85+
gstr = gstr isa AbstractVector ? gstr : collect(gstr)
86+
end
87+
88+
len = length(fstr)
89+
bitmaps = Vector{Matrix{UInt8}}(undef, len)
90+
metrics = Vector{FontExtent{Int}}(undef, len)
6291

63-
for (istr, char) = enumerate(str)
64-
bitmap, metric_float = renderface(face, char, pixelsize)
65-
metric = round.(Int, metric_float)
66-
bitmaps[istr] = bitmap
67-
metrics[istr] = metric
92+
y_min = y_max = sum_advance_x = 0 # y_min and y_max are w.r.t the baseline
93+
for (i, char) in enumerate(fstr)
94+
bitmap, metricf = renderface(face, char, pixelsize)
95+
metric = round.(Int, metricf)
96+
bitmaps[i] = bitmap
97+
metrics[i] = metric
6898

69-
# scale of glyph (size of bitmap)
70-
w, h = metric.scale
71-
# offset between glyph origin and bitmap top left corner
72-
bx, by = metric.horizontal_bearing
73-
ymin = min(ymin, by - h)
74-
ymax = max(ymax, by)
75-
sumadvancex += metric.advance[1]
99+
y_min = min(y_min, bottominkbound(metric))
100+
y_max = max(y_max, topinkbound(metric))
101+
sum_advance_x += hadvance(metric)
76102
end
77103

78-
px = x0 - (halign == :hright ? sumadvancex : halign == :hcenter ? sumadvancex >> 1 : 0)
104+
bitmap_max = bitmaps |> first |> eltype |> typemax
105+
imgh, imgw = size(img)
106+
107+
# initial pen position
108+
px = x0 - (halign == :hright ? sum_advance_x : halign == :hcenter ? sum_advance_x >> 1 : 0)
79109
py = y0 + (
80-
valign == :vtop ? ymax : valign == :vbottom ? ymin :
81-
valign == :vcenter ? (ymax - ymin) >> 1 + ymin : 0
110+
valign == :vtop ? y_max : valign == :vbottom ? y_min :
111+
valign == :vcenter ? (y_max - y_min) >> 1 + y_min : 0
82112
)
83113

84-
bitmapmax = typemax(eltype(bitmaps[1]))
85-
86-
imgh, imgw = size(img)
87114
if bcolor !== nothing
88115
img[
89-
clamp(py-ymax+1, 1, imgh) : clamp(py-ymin-1, 1, imgh),
90-
clamp(px-1, 1, imgw) : clamp(px+sumadvancex-1, 1, imgw)
116+
clamp(py - y_max, 1, imgh) : clamp(py - y_min, 1, imgh),
117+
clamp(px, 1, imgw) : clamp(px + sum_advance_x, 1, imgw)
91118
] .= bcolor
92119
end
93120

94121
local prev_char::Char
95-
for (istr, char) = enumerate(str)
96-
w, h = metrics[istr].scale
97-
bx, by = metrics[istr].horizontal_bearing
122+
for (i, char) in enumerate(fstr)
123+
bitmap = bitmaps[i]
124+
metric = metrics[i]
125+
bx, by = metric.horizontal_bearing
126+
ax, ay = metric.advance
127+
sx, sy = metric.scale
98128

99-
if istr == 1
129+
if i == 1
100130
prev_char = char
101131
else
102-
kx, ky = map(x-> round(Int, x), kerning(prev_char, char, face))
132+
kx, _ = map(x-> round(Int, x), kerning(prev_char, char, face))
103133
px += kx
104134
end
105135

136+
# glyph origin
137+
oy = py - by
138+
ox = px + bx
139+
140+
fcol = fcolor isa AbstractVector ? fcolor[i] : fcolor
141+
gcol = gcolor isa AbstractVector ? gcolor[i] : gcolor
142+
106143
# trim parts of glyph images that are outside the destination
107-
cliprowlo, cliprowhi = max(0, -(py-by)), max(0, py - by + h - imgh)
108-
clipcollo, clipcolhi = max(0, -bx-px), max(0, px + bx + w - imgw)
109-
110-
if bcolor === nothing
111-
for row = 1+cliprowlo : h-cliprowhi, col = 1+clipcollo : w-clipcolhi
112-
bitmaps[istr][col,row]==0 && continue
113-
c1 = bitmaps[istr][col,row] / bitmapmax * fcolor
114-
img[row+py-by, col+px+bx] = T <: Integer ? round(T, c1) : T(c1)
144+
row_lo, row_hi = 1 + max(0, -oy), sy - max(0, oy + sy - imgh)
145+
col_lo, col_hi = 1 + max(0, -ox), sx - max(0, ox + sx - imgw)
146+
147+
if gcol === nothing
148+
for r in row_lo:row_hi, c in col_lo:col_hi
149+
(bm = bitmap[c, r]) == 0 && continue
150+
color = bm / bitmap_max * fcol
151+
img[oy + r, ox + c] = T <: Integer ? round(T, color) : T(color)
115152
end
116153
else
117-
for row = 1+cliprowlo : h-cliprowhi, col = 1+clipcollo : w-clipcolhi
118-
bitmaps[istr][col, row] == 0 && continue
119-
w1 = bitmaps[istr][col, row] / bitmapmax
120-
c1 = w1 * fcolor
121-
c0 = (1.0 - w1) * bcolor
122-
img[row + py - by, col + px + bx] = T <: Integer ? round(T, c1 + c0) : T(c1 + c0)
154+
if gstr !== nothing
155+
gmetric = round.(Int, extents(face, gstr[i], pixelsize))
156+
y_min = bottominkbound(gmetric)
157+
y_max = topinkbound(gmetric)
158+
end
159+
160+
# fill background
161+
by1, by2 = py - y_max, py - y_min
162+
bx1, bx2 = px, px + ax
163+
r1, r2 = clamp(by1, 1, imgh), clamp(by2, 1, imgh)
164+
c1, c2 = clamp(bx1, 1, imgw), clamp(bx2, 1, imgw)
165+
for r in r1 + off_bg:r2 - off_bg, c in c1 + off_bg:c2 - off_bg
166+
img[r, c] = gcol
167+
end
168+
169+
# render character by drawing the corresponding glyph
170+
for r in row_lo:row_hi, c in col_lo:col_hi
171+
(bm = bitmap[c, r]) == 0 && continue
172+
w1 = bm / bitmap_max
173+
color0 = w1 * fcol
174+
color1 = (1.0 - w1) * gcol
175+
img[oy + r, ox + c] = T <: Integer ? round(T, color0 + color1) : T(color0 + color1)
176+
end
177+
178+
# draw background bounding box
179+
if bbox !== nothing && r2 > r1 && c2 > c1
180+
img[r1, c1:c2] .= bbox
181+
img[r2, c1:c2] .= bbox
182+
img[r1:r2, c1] .= bbox
183+
img[r1:r2, c2] .= bbox
123184
end
124185
end
125-
px += metrics[istr].advance[1]
186+
187+
# draw glyph bounding box
188+
if bbox_glyph !== nothing
189+
# rect = boundingbox(metric)
190+
# (mc, mr), (Mc, Mr) = extrema(rect)
191+
# r1, r2 = clamp(py + mr, 1, imgh), clamp(py + Mr, 1, imgh)
192+
# c1, c2 = clamp(px + mc, 1, imgw), clamp(px + Mc, 1, imgw)
193+
# ^^^ should be equivalent to vvv: see JuliaGraphics/FreeTypeAbstraction.jl/pull/71
194+
r1, r2 = clamp(oy + row_lo, 1, imgh), clamp(oy + row_hi, 1, imgh)
195+
c1, c2 = clamp(ox + col_lo, 1, imgw), clamp(ox + col_hi, 1, imgw)
196+
if r2 > r1 && c2 > c1
197+
img[r1, c1:c2] .= bbox_glyph
198+
img[r2, c1:c2] .= bbox_glyph
199+
img[r1:r2, c1] .= bbox_glyph
200+
img[r1:r2, c2] .= bbox_glyph
201+
end
202+
end
203+
204+
px += ax + incx
126205
end
127206
return img
128207
end

0 commit comments

Comments
 (0)