18 using iterator = std::vector<text_shaper_char>::iterator;
19 using const_iterator = std::vector<text_shaper_char>::const_iterator;
92 hi::font_metrics
const& metrics) noexcept :
93 first(first), last(last), columns(), metrics(metrics), line_nr(line_nr), y(0.0f), width(width), last_category()
95 auto last_visible_it = first;
96 for (
auto it = first; it != last; ++it) {
98 it->is_trailing_white_space =
false;
102 if (is_visible(it->general_category)) {
103 this->metrics = max(metrics, it->font_metrics());
104 last_visible_it = it;
110 for (
auto it = last_visible_it + 1; it != last; ++it) {
111 it->is_trailing_white_space =
true;
114 last_category = (last - 1)->general_category;
116 last_category = unicode_general_category::Cn;
120 [[nodiscard]]
constexpr size_t size() const noexcept
122 return columns.
size();
125 [[nodiscard]]
constexpr iterator front() const noexcept
127 return columns.
front();
130 [[nodiscard]]
constexpr iterator back() const noexcept
132 return columns.
back();
135 iterator operator[](
size_t index)
const noexcept
137 hi_assert_bounds(index, columns);
138 return columns[index];
141 void layout(horizontal_alignment alignment,
float min_x,
float max_x,
float sub_pixel_width)
noexcept
144 advance_glyphs(columns, y);
147 hilet[visible_width, num_internal_white_space] = calculate_precise_width(columns, paragraph_direction);
150 align_glyphs(columns, alignment, paragraph_direction, max_x - min_x, visible_width, num_internal_white_space);
153 move_glyphs(columns, min_x);
156 round_glyph_positions(columns, sub_pixel_width);
162 if (columns.
empty()) {
175 if (columns.
empty()) {
177 return {last,
false};
181 return char_it->rectangle.right() < x;
183 if (column_it == columns.
end()) {
184 column_it = columns.
end() - 1;
187 auto char_it = *column_it;
188 if (is_Zp_or_Zl(char_it->general_category)) {
190 if (paragraph_direction == unicode_bidi_class::L) {
191 if (column_it != columns.
begin()) {
192 char_it = *--column_it;
195 return {char_it,
false};
198 if (column_it + 1 != columns.
end()) {
199 char_it = *++column_it;
202 return {char_it,
false};
207 hilet after = (char_it->direction == unicode_bidi_class::L) == position.x() > char_it->rectangle.center();
208 return {char_it, after};
212 static void advance_glyphs_run(
214 text_shaper_line::column_vector::iterator first,
215 text_shaper_line::column_vector::iterator last)
noexcept
217 hi_axiom(first != last);
219 hilet char_it = *first;
220 hilet&
font = *char_it->glyphs.font;
221 hilet script = char_it->script;
224 auto run = gstring{};
226 for (
auto it = first; it != last; ++it) {
227 run += (*it)->grapheme;
230 auto result = font.shape_run(language, script, run);
231 result.scale_and_offset(char_it->scale);
232 hi_axiom(result.advances.size() == run.size());
233 hi_axiom(result.glyph_count.size() == run.size());
235 auto grapheme_index = 0_uz;
236 for (
auto it = first; it != last; ++it, ++grapheme_index) {
239 p += vector2{result.advances[grapheme_index], 0.0f};
245 static void advance_glyphs(text_shaper_line::column_vector& columns,
float y)
noexcept
247 if (columns.
empty()) {
251 auto p = point2{0.0f, y};
253 auto run_start = columns.
begin();
254 for (
auto it = run_start + 1; it != columns.
end(); ++it) {
255 hilet start_char_it = *run_start;
258 hilet same_font = start_char_it->glyphs.font == char_it->glyphs.font;
259 hilet same_style = start_char_it->style == char_it->style;
260 hilet same_size = start_char_it->scale == char_it->scale;
261 hilet same_language =
true;
262 hilet same_script = start_char_it->script == char_it->script;
264 if (not(same_font and same_style and same_size and same_language and same_script)) {
265 advance_glyphs_run(p, run_start, it);
269 advance_glyphs_run(p, run_start, columns.
end());
273 calculate_precise_width(text_shaper_line::column_vector& columns, unicode_bidi_class paragraph_direction)
275 if (columns.empty()) {
279 auto it = columns.begin();
280 for (; it != columns.end(); ++it) {
281 if (not(*it)->is_trailing_white_space) {
285 hilet left_x = (*it)->position.x();
287 auto right_x = left_x;
288 auto num_white_space = 0_uz;
289 for (; it != columns.end(); ++it) {
290 if ((*it)->is_trailing_white_space) {
295 right_x = (*it)->position.x() + (*it)->metrics.advance;
296 if (not is_visible((*it)->general_category)) {
301 hilet width = right_x - left_x;
304 for (
auto& char_it : columns) {
305 char_it->position.x() -= left_x;
308 return {width, num_white_space};
311 static void move_glyphs(text_shaper_line::column_vector& columns,
float offset)
noexcept
313 for (hilet& char_it : columns) {
314 char_it->position.x() += offset;
318 [[nodiscard]]
static bool align_glyphs_justified(
319 text_shaper_line::column_vector& columns,
320 float max_line_width,
322 size_t num_internal_white_space)
noexcept
324 if (num_internal_white_space == 0) {
328 hilet extra_space = max_line_width - visible_width;
329 if (extra_space > max_line_width * 0.25f) {
333 hilet extra_space_per_whitespace = extra_space / num_internal_white_space;
335 for (hilet& char_it : columns) {
336 char_it->position.x() += offset;
340 if (not char_it->is_trailing_white_space and not is_visible(char_it->general_category)) {
341 offset += extra_space_per_whitespace;
348 static void align_glyphs(
349 text_shaper_line::column_vector& columns,
350 horizontal_alignment alignment,
351 unicode_bidi_class paragraph_direction,
352 float max_line_width,
354 size_t num_internal_white_space)
noexcept
356 if (alignment == horizontal_alignment::justified) {
357 if (align_glyphs_justified(columns, max_line_width, visible_width, num_internal_white_space)) {
362 if (alignment == horizontal_alignment::flush or alignment == horizontal_alignment::justified) {
363 alignment = paragraph_direction == unicode_bidi_class::R ? horizontal_alignment::right : horizontal_alignment::left;
368 alignment == horizontal_alignment::left ? 0.0f :
369 alignment == horizontal_alignment::right ? max_line_width - visible_width :
370 (max_line_width - visible_width) * 0.5f;
373 return move_glyphs(columns, offset);
376 static void round_glyph_positions(text_shaper_line::column_vector& columns,
float sub_pixel_width)
noexcept
378 hilet rcp_sub_pixel_width = 1.0f / sub_pixel_width;
379 for (
auto it : columns) {
380 it->position.x() =
std::round(it->position.x() * rcp_sub_pixel_width) * sub_pixel_width;
385 create_bounding_rectangles(text_shaper_line::column_vector& columns,
float y,
float ascender,
float descender)
noexcept
387 for (
auto it = columns.begin(); it != columns.end(); ++it) {
388 hilet next_it = it + 1;
390 if (next_it == columns.end()) {
391 char_it->rectangle = {
392 point2{char_it->position.x(), y - descender},
393 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
395 hilet next_char_it = *next_it;
397 if (next_char_it->position.x() <= char_it->position.x()) {
399 char_it->rectangle = {
400 point2{char_it->position.x(), y - descender},
401 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
403 char_it->rectangle = {
404 point2{char_it->position.x(), y - descender}, point2{next_char_it->position.x(), y + ascender}};