HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
AttributedGlyphLine.hpp
1// Copyright 2020 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Text/AttributedGlyph.hpp"
7#include <vector>
8#include <optional>
9
10namespace tt {
11
14 using iterator = vector_type::iterator;
15 using const_iterator = vector_type::const_iterator;
16 using value_type = vector_type::value_type;
17
18 vector_type line;
19 float width;
20 float ascender;
21 float descender;
22 float lineGap;
23 float capHeight;
24 float xHeight;
25 float y;
26
29 AttributedGlyphLine(iterator first, iterator last) noexcept :
30 line(), width(0.0f), ascender(0.0f), descender(0.0f), lineGap(0.0f), capHeight(0.0f), xHeight(0.0f)
31 {
32 tt_assume(std::distance(first, last) > 0);
33
34 line.reserve(std::distance(first, last));
35 std::move(first, last, std::back_inserter(line));
36 calculateLineMetrics();
37 }
38
39 [[nodiscard]] bool shouldWrap(float maximum_width) noexcept {
40 tt_assume(ssize(line) >= 1);
41 return
42 width > maximum_width &&
43 ssize(line) >= (line.back().isParagraphSeparator() ? 3 : 2);
44 }
45
46 [[nodiscard]] AttributedGlyphLine wrap(float maximum_width) noexcept {
47 tt_assume(shouldWrap(maximum_width));
48
49 auto word_end = line.begin();
50 auto line_width = 0.0f;
51 auto line_valid_width = 0.0f;
52
53 auto i = line.begin();
54 for (; i != line.end(); ++i) {
55 line_width += i->metrics.advance.x();
56 if (i->isVisible()) {
57 line_valid_width = line_width;
58 }
59
60 if (line_valid_width > maximum_width) {
61 // Found position where to wrap.
62 break;
63
64 } else if (i->isWhiteSpace()) {
65 // Include the whitespace in the word, as it should belong at the end of the line.
66 word_end = i + 1;
67 }
68 }
69
70 auto split_position =
71 (word_end != line.begin()) ? word_end : // Wrap at word boundary
72 (i != line.begin()) ? i : // Wrap at character boundary
73 i + 1; // Include at least one character.
74
75 auto reset_of_line = AttributedGlyphLine(split_position, line.end());
76 line.erase(split_position, line.cend());
77 calculateLineMetrics();
78 return reset_of_line;
79 }
80
81 [[nodiscard]] aarect boundingBox() const noexcept {
82 tt_assume(ssize(line) >= 1);
83
84 ttlet p0 = vec::point(
85 line.front().position.x(),
86 line.front().position.y() - descender
87 );
88
89 ttlet p3 = vec::point(
90 line.back().position.x() + line.back().metrics.advance.x(),
91 line.back().position.y() + ascender
92 );
93
94 return aarect::p0p3(p0, p3);
95 }
96
97 [[nodiscard]] bool contains(vec coordinate) const noexcept {
98 return boundingBox().contains(coordinate);
99 }
100
101 [[nodiscard]] const_iterator find(vec coordinate) const noexcept {
102 auto bbox = boundingBox();
103
104 if (coordinate.y() < bbox.y() || coordinate.y() > bbox.p3().y()) {
105 return cend();
106 }
107
108 if (coordinate.x() < bbox.x()) {
109 return cbegin();
110 }
111
112 if (coordinate.x() > bbox.p3().x()) {
113 return cend() - 1;
114 }
115
116 return std::lower_bound(cbegin(), cend(), coordinate.x(), [](ttlet &a, ttlet &b) {
117 return (a.position.x() + a.metrics.advance.x()) < b;
118 });
119 }
120
121 [[nodiscard]] size_t size() const noexcept { return line.size(); }
122
123 [[nodiscard]] iterator begin() noexcept { return line.begin(); }
124 [[nodiscard]] const_iterator begin() const noexcept { return line.cbegin(); }
125 [[nodiscard]] const_iterator cbegin() const noexcept { return line.cbegin(); }
126
127 [[nodiscard]] iterator end() noexcept { return line.end(); }
128 [[nodiscard]] const_iterator end() const noexcept { return line.cend(); }
129 [[nodiscard]] const_iterator cend() const noexcept { return line.cend(); }
130
131 void positionGlyphs(vec position) noexcept {
132 tt_assume(position.is_point());
133 y = position.y();
134 for (auto &&g: line) {
135 g.position = position;
136 position += g.metrics.advance;
137 }
138 }
139
140private:
141 void calculateLineMetrics() noexcept {
142 ascender = 0.0f;
143 descender = 0.0f;
144 lineGap = 0.0f;
145 capHeight = 0.0f;
146 xHeight = 0.0f;
147
148 auto totalWidth = 0.0f;
149 auto validWidth = 0.0f;
150 for (ttlet &g: line) {
151 totalWidth += g.metrics.advance.x();
152 ascender = std::max(ascender, g.metrics.ascender);
153 descender = std::max(descender, g.metrics.descender);
154 lineGap = std::max(lineGap, g.metrics.lineGap);
155 capHeight += g.metrics.capHeight;
156 xHeight += g.metrics.xHeight;
157
158 if (g.isVisible()) {
159 // Don't include trailing whitespace in the width.
160 validWidth = totalWidth;
161 }
162 }
163 capHeight /= numeric_cast<float>(ssize(line));
164 xHeight /= numeric_cast<float>(ssize(line));
165
166 width = validWidth;
167 }
168
169};
170
171}
bool contains(vec const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition aarect.hpp:221
static tt_force_inline aarect p0p3(vec const &v) noexcept
Create aarect from packed p0p3 coordinates.
Definition aarect.hpp:75
static tt_force_inline vec point(float x=0.0f, float y=0.0f, float z=0.0f) noexcept
Create a point out of 2 to 4 values.
Definition vec.hpp:180
Definition AttributedGlyphLine.hpp:12
AttributedGlyphLine(iterator first, iterator last) noexcept
This constructor will move the data from first to last.
Definition AttributedGlyphLine.hpp:29
T back(T... args)
T back_inserter(T... args)
T begin(T... args)
T distance(T... args)
T end(T... args)
T erase(T... args)
T front(T... args)
T lower_bound(T... args)
T max(T... args)
T move(T... args)
T reserve(T... args)
T size(T... args)