20 using iterator = std::vector<text_shaper_char>::iterator;
21 using const_iterator = std::vector<text_shaper_char>::const_iterator;
95 first(first), last(last), columns(), metrics(metrics), line_nr(line_nr), y(0.0f), width(width), last_category()
97 auto last_visible_it = first;
98 for (
auto it = first; it != last; ++it) {
100 it->is_trailing_white_space =
false;
104 if (is_visible(it->general_category)) {
105 this->metrics = max(metrics, it->font_metrics());
106 last_visible_it = it;
112 for (
auto it = last_visible_it + 1; it != last; ++it) {
113 it->is_trailing_white_space =
true;
116 last_category = (last - 1)->general_category;
118 last_category = unicode_general_category::Cn;
122 [[nodiscard]]
constexpr size_t size() const noexcept
124 return columns.
size();
127 [[nodiscard]]
constexpr iterator front() const noexcept
129 return columns.
front();
132 [[nodiscard]]
constexpr iterator back() const noexcept
134 return columns.
back();
137 iterator operator[](
size_t index)
const noexcept
139 hi_assert_bounds(index, columns);
140 return columns[index];
143 void layout(horizontal_alignment alignment,
float min_x,
float max_x,
float sub_pixel_width)
noexcept
146 advance_glyphs(columns, y);
149 auto const[visible_width, num_internal_white_space] = calculate_precise_width(columns, paragraph_direction);
152 align_glyphs(columns, alignment, paragraph_direction, max_x - min_x, visible_width, num_internal_white_space);
155 move_glyphs(columns, min_x);
158 round_glyph_positions(columns, sub_pixel_width);
161 create_bounding_rectangles(columns, y, metrics.
ascender.in(pixels), metrics.
descender.in(pixels));
164 if (columns.
empty()) {
177 if (columns.
empty()) {
179 return {last,
false};
182 auto column_it =
std::lower_bound(columns.
begin(), columns.
end(), position.x(), [](
auto const& char_it,
auto const& x) {
183 return char_it->rectangle.right() < x;
185 if (column_it == columns.
end()) {
186 column_it = columns.
end() - 1;
189 auto char_it = *column_it;
190 if (is_Zp_or_Zl(char_it->general_category)) {
192 if (paragraph_direction == unicode_bidi_class::L) {
193 if (column_it != columns.
begin()) {
194 char_it = *--column_it;
197 return {char_it,
false};
200 if (column_it + 1 != columns.
end()) {
201 char_it = *++column_it;
204 return {char_it,
false};
209 auto const after = (char_it->direction == unicode_bidi_class::L) == position.x() > char_it->rectangle.center();
210 return {char_it, after};
214 static void advance_glyphs_run(
216 text_shaper_line::column_vector::iterator first,
217 text_shaper_line::column_vector::iterator last)
noexcept
219 hi_axiom(first != last);
221 auto const char_it = *first;
222 auto const&
font = *char_it->glyphs.font;
223 auto const script = char_it->script;
224 auto const language =
iso_639{};
226 auto run = gstring{};
228 for (
auto it = first; it != last; ++it) {
229 run += (*it)->grapheme;
232 auto result = font.shape_run(language, script, run);
233 result.scale_and_offset(char_it->font_size.in(pixels_per_em));
234 hi_axiom(result.advances.size() == run.size());
235 hi_axiom(result.glyph_count.size() == run.size());
237 auto grapheme_index = 0_uz;
238 for (
auto it = first; it != last; ++it, ++grapheme_index) {
241 p += vector2{result.advances[grapheme_index], 0.0f};
247 static void advance_glyphs(text_shaper_line::column_vector& columns,
float y)
noexcept
249 if (columns.
empty()) {
253 auto p = point2{0.0f, y};
255 auto run_start = columns.
begin();
256 for (
auto it = run_start + 1; it != columns.
end(); ++it) {
257 auto const start_char_it = *run_start;
258 auto const char_it = *it;
260 auto const same_font = start_char_it->glyphs.font == char_it->glyphs.font;
261 auto const same_style = start_char_it->style == char_it->style;
262 auto const same_size = start_char_it->font_size == char_it->font_size;
263 auto const same_language =
true;
264 auto const same_script = start_char_it->script == char_it->script;
266 if (not(same_font and same_style and same_size and same_language and same_script)) {
267 advance_glyphs_run(p, run_start, it);
271 advance_glyphs_run(p, run_start, columns.
end());
275 calculate_precise_width(text_shaper_line::column_vector& columns, unicode_bidi_class paragraph_direction)
277 if (columns.empty()) {
281 auto it = columns.begin();
282 for (; it != columns.end(); ++it) {
283 if (not(*it)->is_trailing_white_space) {
287 auto const left_x = (*it)->position.x();
289 auto right_x = left_x;
290 auto num_white_space = 0_uz;
291 for (; it != columns.end(); ++it) {
292 if ((*it)->is_trailing_white_space) {
297 right_x = (*it)->position.x() + (*it)->metrics.advance;
298 if (not is_visible((*it)->general_category)) {
303 auto const width = right_x - left_x;
306 for (
auto& char_it : columns) {
307 char_it->position.x() -= left_x;
310 return {width, num_white_space};
313 static void move_glyphs(text_shaper_line::column_vector& columns,
float offset)
noexcept
315 for (
auto const& char_it : columns) {
316 char_it->position.x() += offset;
320 [[nodiscard]]
static bool align_glyphs_justified(
321 text_shaper_line::column_vector& columns,
322 float max_line_width,
324 size_t num_internal_white_space)
noexcept
326 if (num_internal_white_space == 0) {
330 auto const extra_space = max_line_width - visible_width;
331 if (extra_space > max_line_width * 0.25f) {
335 auto const extra_space_per_whitespace = extra_space / num_internal_white_space;
337 for (
auto const& char_it : columns) {
338 char_it->position.x() += offset;
342 if (not char_it->is_trailing_white_space and not is_visible(char_it->general_category)) {
343 offset += extra_space_per_whitespace;
350 static void align_glyphs(
351 text_shaper_line::column_vector& columns,
352 horizontal_alignment alignment,
353 unicode_bidi_class paragraph_direction,
354 float max_line_width,
356 size_t num_internal_white_space)
noexcept
358 if (alignment == horizontal_alignment::justified) {
359 if (align_glyphs_justified(columns, max_line_width, visible_width, num_internal_white_space)) {
364 if (alignment == horizontal_alignment::flush or alignment == horizontal_alignment::justified) {
365 alignment = paragraph_direction == unicode_bidi_class::R ? horizontal_alignment::right : horizontal_alignment::left;
370 alignment == horizontal_alignment::left ? 0.0f :
371 alignment == horizontal_alignment::right ? max_line_width - visible_width :
372 (max_line_width - visible_width) * 0.5f;
375 return move_glyphs(columns, offset);
378 static void round_glyph_positions(text_shaper_line::column_vector& columns,
float sub_pixel_width)
noexcept
380 auto const rcp_sub_pixel_width = 1.0f / sub_pixel_width;
381 for (
auto it : columns) {
382 it->position.x() =
std::round(it->position.x() * rcp_sub_pixel_width) * sub_pixel_width;
387 create_bounding_rectangles(text_shaper_line::column_vector& columns,
float y,
float ascender,
float descender)
noexcept
389 for (
auto it = columns.begin(); it != columns.end(); ++it) {
390 auto const next_it = it + 1;
391 auto const char_it = *it;
392 if (next_it == columns.end()) {
393 char_it->rectangle = {
394 point2{char_it->position.x(), y - descender},
395 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
397 auto const next_char_it = *next_it;
399 if (next_char_it->position.x() <= char_it->position.x()) {
401 char_it->rectangle = {
402 point2{char_it->position.x(), y - descender},
403 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
405 char_it->rectangle = {
406 point2{char_it->position.x(), y - descender}, point2{next_char_it->position.x(), y + ascender}};