@@ -4,14 +4,23 @@ function loadchar(face::FTFont, c::Char)
4
4
check_error (err, " Could not load char to render." )
5
5
end
6
6
7
- function renderface (face:: FTFont , c:: Char , pixelsize:: Integer )
7
+ function loadglyph (face:: FTFont , c:: Char , pixelsize:: Integer )
8
8
set_pixelsize (face, pixelsize)
9
9
loadchar (face, c)
10
10
glyph = unsafe_load (face. glyph)
11
11
@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)
12
17
return glyphbitmap (glyph. bitmap), FontExtent (glyph. metrics)
13
18
end
14
19
20
+ function extents (face:: FTFont , c:: Char , pixelsize:: Integer )
21
+ return FontExtent (loadglyph (face, c, pixelsize). metrics)
22
+ end
23
+
15
24
function glyphbitmap (bitmap:: FreeType.FT_Bitmap )
16
25
@assert bitmap. pixel_mode == FreeType. FT_PIXEL_MODE_GRAY
17
26
bmp = Matrix {UInt8} (undef, bitmap. width, bitmap. rows)
@@ -27,102 +36,172 @@ function glyphbitmap(bitmap::FreeType.FT_Bitmap)
27
36
return bmp
28
37
end
29
38
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
31
42
32
43
"""
33
44
renderstring!(img::AbstractMatrix, str::String, face, pixelsize, y0, x0;
34
45
fcolor=one_or_typemax(T), bcolor=zero(T), halign=:hleft, valign=:vbaseline) -> Matrix
35
46
36
47
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
37
49
38
50
# Arguments
39
51
* `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
42
55
* `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
44
61
"""
45
62
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 ,
49
75
) where T<: Union{Real,Colorant}
50
76
51
77
if pixelsize isa Tuple
52
78
@warn " using tuple for pixelsize is deprecated, please use one integer"
53
79
pixelsize = pixelsize[1 ]
54
80
end
55
-
56
81
set_pixelsize (face, pixelsize)
57
82
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)
62
91
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
68
98
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)
76
102
end
77
103
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 )
79
109
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
82
112
)
83
113
84
- bitmapmax = typemax (eltype (bitmaps[1 ]))
85
-
86
- imgh, imgw = size (img)
87
114
if bcolor != = nothing
88
115
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)
91
118
] .= bcolor
92
119
end
93
120
94
121
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
98
128
99
- if istr == 1
129
+ if i == 1
100
130
prev_char = char
101
131
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))
103
133
px += kx
104
134
end
105
135
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
+
106
143
# 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 )
115
152
end
116
153
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
123
184
end
124
185
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
126
205
end
127
206
return img
128
207
end
0 commit comments