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