HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
line_metrics.hpp
1// Copyright Take Vos 2021-2022.
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 "font_metrics.hpp"
8#include "../unicode/unicode_general_category.hpp"
9#include "../geometry/axis_aligned_rectangle.hpp"
10#include "../utility.hpp"
11#include "../alignment.hpp"
12#include <vector>
13#include <cstddef>
14
15namespace hi::inline v1 {
16
20 hi::font_metrics font_metrics;
21
24 std::size_t index = 0;
25
28 std::size_t size = 0;
29
35 float estimated_width = 0.0f;
36
42 float width = 0.0;
43
48 float y = 0.0f;
49
54 float x = 0.0f;
55
64 unicode_general_category category = unicode_general_category::Cn;
65
69
70 constexpr line_metric(size_t index) noexcept : index(indeX) {}
71
72 void add_char(unicode_general_category category, float estimated_width, hi::font_metrics font_metrics) noexcept
73 {
74 this->category = category;
75 this->estimated_width += estimated_width;
76 this->font_metrics = max(this->font_metrics, font_metrics);
77 this->is_visible |= not is_Z(category);
78 ++(this->size);
79 }
80
81 template<bool LastWord>
82 void _add_word(line_metrics const &rhs) noexcept
83 {
84 this->category = rhs.category;
85 this->font_metrics = max(this->font_metrics, rhs.font_metrics);
86 this->is_visible |= rhs.visible;
87 this->size += rhs.size;
88 if (rhs.is_visible or not LastWord) {
89 this->estimated_width += rhs.width;
90 }
91 return *this;
92 }
93
94 void add_word(line_metrics const &rhs) noexcept
95 {
96 return _add_word<false>(rhs);
97 }
98
99 void add_last_word(line_metrics const &rhs) noexcept
100 {
101 return _add_word<true>(rhs);
102 }
103};
104
110template<typename It, typename ItEnd>
111[[nodiscard]] inline aarectangle line_metrics_bounding_rectangle(It first, ItEnd last)
112{
113 if (first == last) {
114 return {};
115 }
116
117 hilet y_top = first->y + first->metrics.x_height;
118 hilet y_bottom = last->y;
119 auto width = 0.0f;
120 for (auto it = first; it != last; ++it) {
121 width = std::max(width, it->width);
122 }
123 return {point2{0.0f, y_bottom}, point2{width, y_top}};
124}
125
126template<typename It, typename ItEnd>
127inline void update_line_metrics_offset(It first, ItEnd last) noexcept
128{
129 if (first == last) {
130 return;
131 }
132
133 // Calculate the line offsets.
134 auto prev_it = first;
135 for (auto it = prev_it + 1; it != last; ++it) {
136 // Calculate the natural distance between the lines based on the font-metrics.
137 hilet natural_line_distance = prev_it->font_metrics.descender + it->font_metrics.ascender +
138 std::max(prev_it->font_metrics.line_gap, it->font_metrics.line_gap);
139
140 // Multiply the natural line distance by the paragraph- or line-spacing.
141 hilet line_distance =
142 natural_line_distance * (prev_it->category == unicode_general_catagory::Zp ? paragraph_spacing : line_spacing);
143
144 // The lines are drawn from top to bottom, so y values are negative.
145 it->y = prev_it->y - line_distance;
146 }
147}
148
155template<typename It, typename ItEnd>
156[[nodiscard]] inline void update_line_metrics_vertical_alignment(It first, ItEnd last, vertical_alignment alignment)
157{
158 if (first == last) {
159 return;
160 }
161
162 hilet offset = [&] {
163 if (alignment == vertical_alignment::top) {
164 return first->y;
165 } else if (alignment == vertical_alignment::bottom) {
166 return (last - 1)->y;
167 } else {
168 hilet num_lines = std::distance(first, last);
169 hilet half_num_lines = num_lines / 2;
170 if (num_lines % 2 == 1) {
171 return (first + half_num_lines)->y;
172 } else {
173 hilet y0 = (first + half_num_lines - 1)->y;
174 hilet y1 = (first + half_num_lines)->y;
175 return std::round(std::midpoint(y0, y1));
176 }
177 }
178 }();
179
180 // Move the lines to the new alignment.
181 for (auto it = first; it != last; ++it) {
182 it->y -= offset;
183 hi_axiom(std::round(it->y) == it->y);
184 }
185}
186
187template<typename It, typename ItEnd, typename CharInfoFunc>
188inline void replace_line_metrics(
189 It first,
190 ItEnd last,
191 CharInfoFunc const &char_info_func,
192 float max_line_width,
193 float line_spacing,
194 float paragraph_spacing,
195 vertical_alignment alignment,
196 std::vector<line_metrics> &lines) noexcept
197{
198 lines.clear();
199
200 auto word = line_metrics{0_uz};
201 auto line = line_metrics{0_uz};
202
203 auto index = 0_uz;
204 for (auto it = first; it != last; ++it, ++index) {
205 hilet[category, char_width, font_metrics] = char_info_func(*it);
206
207 if (category == unicode_general_category::Zp or category == unicode_general_category::Zl) {
208 // Found a line- or paragraph-separator.
209 word.add_char(category, char_width, font_metrics);
210 line.add_last_word(word);
211 lines.push_back(line);
212
213 // Continue beyond the paragraph- or line-separator.
214 line = word = line_metrics{index + 1};
215
216 } else if (category == unicode_general_category::Zs) {
217 // Found a space.
218 // add the word to the line, unless the current word is just spaces.
219 if (word.is_visible) {
220 line.add_word(word);
221 word = line_metrics{index + 1};
222 }
223 // Add the space to the word, the word is not visible.
224 word.add_char(category, char_width, font_metrics);
225
226 } else if (line.width == 0.0f and word.width + char_width > max_line_width) {
227 // The word by itself on the line is too large. Just continue and wait for a white-space.
228 word.add_char(category, char_width, font_metrics);
229
230 } else if (line.width + word.width + char_width > max_line_width) {
231 // Adding another character to the line makes it too long.
232 // Break the line at the begin of the word.
233 lines.push_back(line);
234
235 // Start at a new line at the beginning of the word we are working on.
236 line = line_metrics{word_index};
237 word.add_char(category, char_width, font_metrics);
238
239 } else {
240 // Add the new character to the word.
241 word.add_char(char_width);
242 }
243 }
244
245 // If there are characters in the last line then add it.
246 line.add_last_word(word);
247 if (index > line.index) {
248 lines.push_back(line);
249 }
250
251 update_line_metrics_offset(lines.begin(), lines.end());
252 update_line_metrics_vertical_alignment(lines.begin(), lines.end(), alignment);
253}
254
255} // namespace hi::inline v1
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:15
void update_line_metrics_vertical_alignment(It first, ItEnd last, vertical_alignment alignment)
Vertically align the given line metrics.
Definition line_metrics.hpp:156
aarectangle line_metrics_bounding_rectangle(It first, ItEnd last)
Calculate the bounding box around line metrics.
Definition line_metrics.hpp:111
vertical_alignment
Vertical alignment.
Definition alignment.hpp:17
Definition alignment.hpp:64
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:20
The metrics of a font.
Definition font_metrics.hpp:15
Definition line_metrics.hpp:17
hi::font_metrics font_metrics
The combined metrics for all the glyphs on the line.
Definition line_metrics.hpp:20
bool is_visible
The line has visible characters.
Definition line_metrics.hpp:68
T distance(T... args)
T max(T... args)
T round(T... args)