Skip to content

Commit 0f3a87b

Browse files
committed
Define ParsedText to hold line parsing results
1 parent e5daee5 commit 0f3a87b

3 files changed

Lines changed: 58 additions & 38 deletions

File tree

src/OpenType.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ using .Meta: isexpr
1515

1616
const Vec = SVector
1717
const Vec2 = SVector{2,Float64}
18+
const Box2 = Box{2,Float64}
1819

1920
@reexport import GeometryExperiments: boundingelement
2021
@reexport using BinaryParsingTools: Tag, @tag_str, @tag2_str, @tag3_str, @tag4_str, Tag2, Tag3, Tag4
@@ -88,9 +89,8 @@ export GlyphID,
8889

8990
Line, LineSegment,
9091
ascender, descender,
91-
segment_geometry, segment_height, line_geometry, text_geometry,
92-
has_outlines,
93-
lines,
92+
segment_geometry, segment_height, line_geometry,
93+
has_outlines, ParsedText,
9494
GlyphStyle
9595

9696
end

src/text.jl

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,39 @@ end
250250

251251
Base.show(io::IO, line::Line) = print(io, Line, '(', length(line.glyphs), " glyphs, ", length(line.segments), " segments)")
252252

253+
struct ParsedText
254+
lines::Vector{Line}
255+
spacings::Vector{Float64}
256+
geometry::Optional{Box2}
257+
end
258+
259+
function ParsedText(text::Text, fonts)
260+
lines = parse_lines(text, fonts)
261+
spacings = compute_line_spacings(lines, text.options)
262+
geometry = compute_text_geometry(text, lines, spacings)
263+
return ParsedText(lines, spacings, geometry)
264+
end
265+
266+
function compute_line_spacings(lines, options::TextOptions)
267+
spacings = Float64[0.0]
268+
for i in eachindex(lines)[begin:(end - 1)]
269+
prev = lines[i]
270+
next = lines[i + 1]
271+
height = ascender(next) - descender(prev)
272+
spacing = height * options.line_spacing
273+
push!(spacings, spacing)
274+
end
275+
return spacings
276+
end
277+
253278
function segment_geometry(line::Line, segment::LineSegment)
254279
isempty(segment.indices) && return nothing
255280
ymin = descender(segment)
256281
ymax = ascender(segment)
257282
xmin, _ = line.positions[first(segment.indices)]
258283
width = sum(first, @view line.advances[segment.indices]; init = 0.0)
259284
xmax = xmin + width
260-
Box(Point2(xmin, ymin), Point2(xmax, ymax))
285+
return Box(Point2(xmin, ymin), Point2(xmax, ymax))
261286
end
262287

263288
function line_geometry(line::Line)
@@ -267,7 +292,7 @@ function line_geometry(line::Line)
267292
ymax = ascender(line)
268293
width = sum(first, line.advances; init = 0.0)
269294
xmax = xmin + width
270-
Box(Point2(xmin, ymin), Point2(xmax, ymax))
295+
return Box(Point2(xmin, ymin), Point2(xmax, ymax))
271296
end
272297

273298
function has_outlines(line::Line, segment::LineSegment)
@@ -277,29 +302,28 @@ function has_outlines(line::Line, segment::LineSegment)
277302
outlines = line.outlines[glyph]
278303
!isempty(outlines) && return true
279304
end
280-
false
305+
return false
281306
end
282307

283308
ascender(line::Line) = maximum(ascender, line.segments; init = 0.0)
284309
descender(line::Line) = maximum(descender, line.segments; init = 0.0)
285310

286-
function split_lines(text::Text)
311+
function split_text_into_lines(text::Text)
287312
newlines = findall(==('\n'), text.chars)
288313
push!(newlines, 1 + lastindex(text.chars))
289314
lines = SplitLine[]
290315
prev = 0
291316
for i in newlines
292317
range = (prev + 1):(i - 1)
293318
prev = i
294-
isempty(range) && continue
295319
push!(lines, SplitLine(text.chars[range], range.start, text.style_changes))
296320
end
297-
lines
321+
return lines
298322
end
299323

300-
function lines(text::Text, fonts::AbstractVector{Pair{OpenTypeFont, FontOptions}})
324+
function parse_lines(text::Text, fonts::AbstractVector{Pair{OpenTypeFont, FontOptions}})
301325
lines = Line[]
302-
for line in split_lines(text)
326+
for line in split_text_into_lines(text)
303327
glyph_indices = Dict{Pair{GlyphID, OpenTypeFont}, Int64}()
304328
line_glyphs, line_positions, line_advances = GlyphID[], Vec2[], Vec2[]
305329
outlines = Vector{GlyphOutline}[]
@@ -327,7 +351,7 @@ function lines(text::Text, fonts::AbstractVector{Pair{OpenTypeFont, FontOptions}
327351
end
328352
push!(lines, Line(line_glyphs, line_positions, line_advances, segments, outlines))
329353
end
330-
lines
354+
return lines
331355
end
332356

333357
function compute_positions(offsets, start, scale)
@@ -336,19 +360,15 @@ function compute_positions(offsets, start, scale)
336360
push!(positions, start .+ offset.origin .* scale)
337361
start = start .+ offset.advance .* scale
338362
end
339-
positions
363+
return positions
340364
end
341365

342-
text_geometry(text::Text, fonts) = text_geometry(text, lines(text, fonts))
343-
344-
function text_geometry(text::Text, lines::AbstractVector{Line})
366+
function compute_text_geometry(text::Text, lines::AbstractVector{Line}, spacings::Vector{Float64})
345367
result = nothing
346-
for line in lines
368+
for (line, spacing) in zip(lines, spacings)
347369
geometry = line_geometry(line)
348-
if !isnothing(result)
349-
geometry -= Vec2(0.0, text.options.line_spacing * result.height)
350-
end
351-
result = isnothing(result) ? geometry : boundingelement(result, geometry)
370+
geometry === nothing && continue
371+
result = result == nothing ? geometry : boundingelement(result, @set geometry -= Vec2(0, spacing))
352372
end
353-
result
373+
return result
354374
end

test/text.jl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,23 @@
6363
file = google_font_files["inter"][1]
6464
font = OpenTypeFont(file);
6565
options = FontOptions(ShapingOptions(tag"latn", tag"fra "), FontSize(1/10; reduce_to_fit = false))
66-
t = Text("The brown fox jumps over the lazy dog.", TextOptions())
67-
ls = lines(t, [font => options])
68-
@test length(ls) == 1
69-
line = ls[1]
70-
@test length(line.glyphs) == length(t.chars)
66+
text = Text("The brown fox jumps over the lazy dog.", TextOptions())
67+
parsed = ParsedText(text, [font => options])
68+
@test length(parsed.lines) == 1
69+
line = parsed.lines[1]
70+
@test length(line.glyphs) == length(text.chars)
7171
@test length(line.segments) == 1
7272
segment = line.segments[1]
7373
@test sprint(show, MIME"text/plain"(), segment) isa String
74-
@test segment.indices == eachindex(t.chars)
75-
box = text_geometry(t, [font => options])
76-
@test box.min === Point2(0, 0)
77-
@test 1.82 < box.max[1] < 1.83
78-
@test 0.096 < box.max[2] < 0.097
74+
@test segment.indices == eachindex(text.chars)
75+
@test parsed.geometry.min === Point2(0, 0)
76+
@test 1.82 < parsed.geometry.max[1] < 1.83
77+
@test 0.096 < parsed.geometry.max[2] < 0.097
7978

80-
t = Text(styled"The {bold:brown} {red:fox {italic:jumps}} over the {italic:lazy} dog.", TextOptions())
81-
box2 = text_geometry(t, [font => options])
82-
@test box == box2
83-
line = only(lines(t, [font => options]))
79+
text = Text(styled"The {bold:brown} {red:fox {italic:jumps}} over the {italic:lazy} dog.", TextOptions())
80+
parsed2 = ParsedText(text, [font => options])
81+
@test parsed.geometry == parsed2.geometry
82+
line = only(parsed2.lines)
8483
@test length(line.segments) == 8
8584
a, b, c, d, e, f, g, h = line.segments
8685
test_style_equals(x, y) = for prop in fieldnames(GlyphStyle); prop :size && @test getproperty(x, prop) == getproperty(y, prop); end
@@ -98,8 +97,9 @@
9897

9998
file = google_font_files["spacemono"][1]
10099
font = OpenTypeFont(file);
101-
t = Text(styled"{size=20:Some{size=100: big }text.}", TextOptions())
102-
line = only(lines(t, [font => options]))
100+
text = Text(styled"{size=20:Some{size=100: big }text.}", TextOptions())
101+
parsed = ParsedText(text, [font => options])
102+
line = only(parsed.lines)
103103
@test length(line.segments) == 3
104104
a, b, c = line.segments
105105
@test a.style.size === c.style.size

0 commit comments

Comments
 (0)