20 using iterator = std::vector<text_shaper_char>::iterator;
21 using const_iterator = std::vector<text_shaper_char>::const_iterator;
102 font_metrics_px
const&
metrics) noexcept :
105 auto last_visible_it =
first;
106 for (
auto it =
first; it !=
last; ++it) {
108 it->is_trailing_white_space =
false;
112 if (is_visible(it->general_category)) {
113 this->metrics = max(
metrics, it->font_metrics());
114 this->line_spacing =
std::max(this->line_spacing, it->style.line_spacing());
115 this->paragraph_spacing =
std::max(this->paragraph_spacing, it->style.paragraph_spacing());
116 last_visible_it = it;
122 for (
auto it = last_visible_it + 1; it !=
last; ++it) {
123 it->is_trailing_white_space =
true;
132 [[nodiscard]]
constexpr size_t size() const noexcept
134 return columns.
size();
137 [[nodiscard]]
constexpr iterator front() const noexcept
139 return columns.
front();
142 [[nodiscard]]
constexpr iterator back() const noexcept
144 return columns.
back();
147 iterator operator[](
size_t index)
const noexcept
149 hi_assert_bounds(index, columns);
150 return columns[index];
153 void layout(horizontal_alignment alignment,
float min_x,
float max_x,
float sub_pixel_width)
noexcept
156 advance_glyphs(columns, y);
159 auto const[visible_width, num_internal_white_space] = calculate_precise_width(columns, paragraph_direction);
162 align_glyphs(columns, alignment, paragraph_direction, max_x - min_x, visible_width, num_internal_white_space);
165 move_glyphs(columns, min_x);
168 round_glyph_positions(columns, sub_pixel_width);
171 create_bounding_rectangles(columns, y, metrics.
ascender.in(unit::pixels), metrics.
descender.in(unit::pixels));
174 if (columns.
empty()) {
189 return {
last,
false};
193 return char_it->rectangle.right() < x;
195 if (column_it ==
columns.end()) {
199 auto char_it = *column_it;
200 if (is_Zp_or_Zl(char_it->general_category)) {
203 if (column_it !=
columns.begin()) {
204 char_it = *--column_it;
207 return {char_it,
false};
210 if (column_it + 1 !=
columns.end()) {
211 char_it = *++column_it;
214 return {char_it,
false};
219 auto const after = (char_it->direction == unicode_bidi_class::L) == position.x() > char_it->rectangle.center();
220 return {char_it, after};
224 static void advance_glyphs_run(
226 text_shaper_line::column_vector::iterator first,
227 text_shaper_line::column_vector::iterator last)
noexcept
229 hi_axiom(first != last);
231 auto const char_it = *first;
232 auto const font = char_it->glyphs.font;
233 auto const script = char_it->script;
234 auto const language =
iso_639{};
236 auto run = gstring{};
238 for (
auto it = first; it != last; ++it) {
239 run += (*it)->grapheme;
242 auto result = font->shape_run(language, script, run);
243 result.scale_and_offset(char_it->font_size.in(unit::pixels_per_em));
244 hi_axiom(result.advances.size() == run.size());
245 hi_axiom(result.glyph_count.size() == run.size());
247 auto grapheme_index = 0_uz;
248 for (
auto it = first; it != last; ++it, ++grapheme_index) {
251 p += vector2{result.advances[grapheme_index], 0.0f};
257 static void advance_glyphs(text_shaper_line::column_vector& columns,
float y)
noexcept
259 if (columns.
empty()) {
263 auto p = point2{0.0f, y};
265 auto run_start = columns.
begin();
266 for (
auto it = run_start + 1; it != columns.
end(); ++it) {
267 auto const start_char_it = *run_start;
268 auto const char_it = *it;
270 auto const same_font = start_char_it->glyphs.font == char_it->glyphs.font;
271 auto const same_style = start_char_it->style == char_it->style;
272 auto const same_size = start_char_it->font_size == char_it->font_size;
273 auto const same_language =
true;
274 auto const same_script = start_char_it->script == char_it->script;
276 if (not(same_font and same_style and same_size and same_language and same_script)) {
277 advance_glyphs_run(p, run_start, it);
281 advance_glyphs_run(p, run_start, columns.
end());
284 [[nodiscard]]
static std::pair<float, size_t>
285 calculate_precise_width(text_shaper_line::column_vector& columns, unicode_bidi_class paragraph_direction)
287 if (columns.empty()) {
291 auto it = columns.begin();
292 for (; it != columns.end(); ++it) {
293 if (not(*it)->is_trailing_white_space) {
297 auto const left_x = (*it)->position.x();
299 auto right_x = left_x;
300 auto num_white_space = 0_uz;
301 for (; it != columns.end(); ++it) {
302 if ((*it)->is_trailing_white_space) {
307 right_x = (*it)->position.x() + (*it)->metrics.advance;
308 if (not is_visible((*it)->general_category)) {
313 auto const width = right_x - left_x;
316 for (
auto& char_it : columns) {
317 char_it->position.x() -= left_x;
320 return {width, num_white_space};
323 static void move_glyphs(text_shaper_line::column_vector& columns,
float offset)
noexcept
325 for (
auto const& char_it : columns) {
326 char_it->position.x() += offset;
330 [[nodiscard]]
static bool align_glyphs_justified(
331 text_shaper_line::column_vector& columns,
332 float max_line_width,
334 size_t num_internal_white_space)
noexcept
336 if (num_internal_white_space == 0) {
340 auto const extra_space = max_line_width - visible_width;
341 if (extra_space > max_line_width * 0.25f) {
345 auto const extra_space_per_whitespace = extra_space / num_internal_white_space;
347 for (
auto const& char_it : columns) {
348 char_it->position.x() += offset;
352 if (not char_it->is_trailing_white_space and not is_visible(char_it->general_category)) {
353 offset += extra_space_per_whitespace;
360 static void align_glyphs(
361 text_shaper_line::column_vector& columns,
362 horizontal_alignment alignment,
363 unicode_bidi_class paragraph_direction,
364 float max_line_width,
366 size_t num_internal_white_space)
noexcept
368 if (alignment == horizontal_alignment::justified) {
369 if (align_glyphs_justified(columns, max_line_width, visible_width, num_internal_white_space)) {
374 if (alignment == horizontal_alignment::flush or alignment == horizontal_alignment::justified) {
375 alignment = paragraph_direction == unicode_bidi_class::R ? horizontal_alignment::right : horizontal_alignment::left;
380 alignment == horizontal_alignment::left ? 0.0f :
381 alignment == horizontal_alignment::right ? max_line_width - visible_width :
382 (max_line_width - visible_width) * 0.5f;
385 return move_glyphs(columns, offset);
388 static void round_glyph_positions(text_shaper_line::column_vector& columns,
float sub_pixel_width)
noexcept
390 auto const rcp_sub_pixel_width = 1.0f / sub_pixel_width;
391 for (
auto it : columns) {
392 it->position.x() =
std::round(it->position.x() * rcp_sub_pixel_width) * sub_pixel_width;
397 create_bounding_rectangles(text_shaper_line::column_vector& columns,
float y,
float ascender,
float descender)
noexcept
399 for (
auto it = columns.begin(); it != columns.end(); ++it) {
400 auto const next_it = it + 1;
401 auto const char_it = *it;
402 if (next_it == columns.end()) {
403 char_it->rectangle = {
404 point2{char_it->position.x(), y - descender},
405 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
407 auto const next_char_it = *next_it;
409 if (next_char_it->position.x() <= char_it->position.x()) {
411 char_it->rectangle = {
412 point2{char_it->position.x(), y - descender},
413 point2{char_it->position.x() + char_it->metrics.advance, y + ascender}};
415 char_it->rectangle = {
416 point2{char_it->position.x(), y - descender}, point2{next_char_it->position.x(), y + ascender}};