From 09dbe543681e838d10f721072809349ac0fde908 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Tue, 19 Mar 2024 10:54:35 +0100 Subject: [PATCH 01/11] Rewrite shaper to not maintain a fixed last_shape_info struct --- src/string_metrics.cpp | 57 ++++--- src/string_shape.cpp | 367 ++++++++++++++++++++++------------------- src/string_shape.h | 46 ++++-- 3 files changed, 264 insertions(+), 206 deletions(-) diff --git a/src/string_metrics.cpp b/src/string_metrics.cpp index e2c7432..7be5988 100644 --- a/src/string_metrics.cpp +++ b/src/string_metrics.cpp @@ -268,18 +268,23 @@ int ts_string_width(const char* string, FontSettings font_info, double size, double res, int include_bearing, double* width) { BEGIN_CPP11 HarfBuzzShaper& shaper = get_hb_shaper(); - bool success = shaper.single_line_shape( + shaper.error_code = 0; + const ShapeInfo string_shape = shaper.shape_text_run( string, font_info, size, res ); - if (!success) { + if (shaper.error_code != 0) { return shaper.error_code; } - int32_t width_tmp = shaper.last_shape_info.width; + int32_t width_tmp = 0; + for (size_t i = 0; i < string_shape.glyph_id.size(); ++i) { + width_tmp += string_shape.x_advance[i]; + } + if (!include_bearing) { - width_tmp -= shaper.last_shape_info.left_bearing; - width_tmp -= shaper.last_shape_info.right_bearing; + width_tmp -= string_shape.x_bear[0]; + width_tmp -= string_shape.x_advance.back() - string_shape.x_bear.back() - string_shape.width.back(); } *width = double(width_tmp) / 64.0; @@ -294,31 +299,35 @@ int ts_string_shape(const char* string, FontSettings font_info, double size, std::vector& fallback_scaling) { BEGIN_CPP11 HarfBuzzShaper& shaper = get_hb_shaper(); - bool success = shaper.single_line_shape( + shaper.error_code = 0; + const ShapeInfo string_shape = shaper.shape_text_run( string, font_info, size, res ); - if (!success) { + + if (shaper.error_code != 0) { return shaper.error_code; } - int n_glyphs = shaper.last_shape_info.x_pos.size(); + + size_t n_glyphs = string_shape.glyph_id.size(); loc.clear(); - if (n_glyphs == 0) { - id.clear(); - font.clear(); - fallbacks.clear(); - fallback_scaling.clear(); - } else { - for (int i = 0; i < n_glyphs; ++i) { - loc.push_back({ - double(shaper.last_shape_info.x_pos[i]) / 64.0, - 0.0 - }); - } - id.assign(shaper.last_shape_info.glyph_id.begin(), shaper.last_shape_info.glyph_id.end()); - font.assign(shaper.last_shape_info.font.begin(), shaper.last_shape_info.font.end()); - fallbacks.assign(shaper.last_shape_info.fallbacks.begin(), shaper.last_shape_info.fallbacks.end()); - fallback_scaling.assign(shaper.last_shape_info.fallback_scaling.begin(), shaper.last_shape_info.fallback_scaling.end()); + id.clear(); + font.clear(); + fallbacks.clear(); + fallback_scaling.clear(); + int32_t x = 0; + int32_t y = 0; + for (int i = 0; i < n_glyphs; ++i) { + loc.push_back({ + double(x + string_shape.x_offset[i]) / 64.0, + double(y + string_shape.y_offset[i]) / 64.0 + }); + x += string_shape.x_advance[i]; + y += string_shape.y_advance[i]; } + id.assign(string_shape.glyph_id.begin(), string_shape.glyph_id.end()); + font.assign(string_shape.font.begin(), string_shape.font.end()); + fallbacks.assign(string_shape.fallbacks.begin(), string_shape.fallbacks.end()); + fallback_scaling.assign(string_shape.fallback_scaling.begin(), string_shape.fallback_scaling.end()); END_CPP11_NO_RETURN return 0; diff --git a/src/string_shape.cpp b/src/string_shape.cpp index 52071c3..604e22a 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -27,9 +27,7 @@ std::vector HarfBuzzShaper::descenders = {}; std::vector HarfBuzzShaper::may_break = {}; std::vector HarfBuzzShaper::must_break = {}; std::vector HarfBuzzShaper::may_stretch = {}; -ShapeID HarfBuzzShaper::last_shape_id = {}; -ShapeID HarfBuzzShaper::temp_shape_id = {}; -ShapeInfo HarfBuzzShaper::last_shape_info = {}; +std::vector HarfBuzzShaper::shape_infos = {}; bool HarfBuzzShaper::shape_string(const char* string, const char* fontfile, int index, double size, double res, double lineheight, @@ -355,24 +353,61 @@ bool HarfBuzzShaper::finish_string() { return true; } -bool HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_info, - double size, double res) { +void HarfBuzzShaper::reset() { + glyph_id.clear(); + glyph_cluster.clear(); + string_id.clear(); + x_pos.clear(); + y_pos.clear(); + x_mid.clear(); + x_advance.clear(); + x_offset.clear(); + left_bear.clear(); + right_bear.clear(); + top_extend.clear(); + bottom_extend.clear(); + line_left_bear.clear(); + line_right_bear.clear(); + line_width.clear(); + line_id.clear(); + ascenders.clear(); + descenders.clear(); + may_break.clear(); + must_break.clear(); + may_stretch.clear(); + shape_infos.clear(); + + pen_x = 0; + pen_y = 0; + + top = 0; + bottom = 0; + ascend = 0; + descend = 0; + + left_bearing = 0; + right_bearing = 0; + width = 0; + height = 0; + top_border = 0; + left_border = 0; + + cur_string = 0; +} + +ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_info, + double size, double res) { int n_features = font_info.n_features; std::vector features(n_features); + ShapeInfo text_run; + ShapeID run_id; if (n_features == 0) { - temp_shape_id.string.assign(string); - temp_shape_id.font.assign(font_info.file); - temp_shape_id.index = font_info.index; - temp_shape_id.size = size * res; - if (temp_shape_id == last_shape_id) { - return true; - } - if (shape_cache.get(temp_shape_id, last_shape_info)) { - last_shape_id.string.swap(temp_shape_id.string); - last_shape_id.font.swap(temp_shape_id.font); - last_shape_id.index = temp_shape_id.index; - last_shape_id.size = temp_shape_id.size; - return true; + run_id.string.assign(string); + run_id.font.assign(font_info.file); + run_id.index = font_info.index; + run_id.size = size * res; + if (shape_cache.get(run_id, text_run)) { + return text_run; } } else { for (int i = 0; i < n_features; ++i) { @@ -382,13 +417,10 @@ bool HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_inf features[i].start = 0; features[i].end = -1; } - // Reset temp id so we don't haphazardly use a shaping with features - temp_shape_id.string.clear(); - temp_shape_id.font.clear(); - temp_shape_id.index = 0; - temp_shape_id.size = 0; } + text_run.fallbacks.push_back(font_info); + int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); @@ -404,14 +436,6 @@ bool HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_inf embeddings.push_back(0); } - last_shape_info.x_pos.clear(); - last_shape_info.glyph_id.clear(); - last_shape_info.width = 0; - last_shape_info.font.clear(); - last_shape_info.fallbacks.clear(); - last_shape_info.fallbacks.push_back(font_info); - last_shape_info.fallback_scaling.clear(); - bool may_have_emoji = false; for (int i = 0; i < n_chars; ++i) { if (utc_string[i] >= 8205) { @@ -428,7 +452,7 @@ bool HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_inf if (emoji_embeddings[i] == 1) { embeddings[i] = 2; if (!emoji_font_added) { - last_shape_info.fallbacks.push_back(locate_font_with_features("emoji", 0, 0)); + text_run.fallbacks.push_back(locate_font_with_features("emoji", 0, 0)); emoji_font_added = true; } } @@ -438,59 +462,16 @@ bool HarfBuzzShaper::single_line_shape(const char* string, FontSettings font_inf size_t embedding_start = 0; for (size_t i = 1; i <= embeddings.size(); ++i) { if (i == embeddings.size() || embeddings[i] != embeddings[i - 1]) { - shape_embedding(utc_string, embedding_start, i, n_chars, size, res, features, embeddings[embedding_start] == 2); + shape_embedding(utc_string, embedding_start, i, n_chars, size, res, features, embeddings[embedding_start] == 2, text_run); embedding_start = i; } } - shape_cache.add(temp_shape_id, last_shape_info); - last_shape_id.string.swap(temp_shape_id.string); - last_shape_id.font.swap(temp_shape_id.font); - last_shape_id.index = temp_shape_id.index; - last_shape_id.size = temp_shape_id.size; + if (n_features == 0) { + shape_cache.add(run_id, text_run); + } //FT_Done_Face(face); - return true; -} - -void HarfBuzzShaper::reset() { - glyph_id.clear(); - glyph_cluster.clear(); - string_id.clear(); - x_pos.clear(); - y_pos.clear(); - x_mid.clear(); - x_advance.clear(); - x_offset.clear(); - left_bear.clear(); - right_bear.clear(); - top_extend.clear(); - bottom_extend.clear(); - line_left_bear.clear(); - line_right_bear.clear(); - line_width.clear(); - line_id.clear(); - ascenders.clear(); - descenders.clear(); - may_break.clear(); - must_break.clear(); - may_stretch.clear(); - - pen_x = 0; - pen_y = 0; - - top = 0; - bottom = 0; - ascend = 0; - descend = 0; - - left_bearing = 0; - right_bearing = 0; - width = 0; - height = 0; - top_border = 0; - left_border = 0; - - cur_string = 0; + return text_run; } bool HarfBuzzShaper::shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars) { @@ -561,7 +542,7 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, double size, double res, std::vector& features, - bool emoji) { + bool emoji, ShapeInfo& shape_info) { unsigned int embedding_size = end - start; if (embedding_size < 1) { @@ -569,36 +550,36 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, } int error = 0; - FT_Face face = get_cached_face(last_shape_info.fallbacks[0].file, - last_shape_info.fallbacks[0].index, + FT_Face face = get_cached_face(shape_info.fallbacks[0].file, + shape_info.fallbacks[0].index, size, res, &error); if (error != 0) { - Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[0].file, last_shape_info.fallbacks[0].index); + Rprintf("Failed to get face: %s, %i\n", shape_info.fallbacks[0].file, shape_info.fallbacks[0].index); error_code = error; return false; } - if (last_shape_info.fallback_scaling.empty()) { + if (shape_info.fallback_scaling.empty()) { double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; scaling *= family_scaling(face->family_name); - last_shape_info.fallback_scaling.push_back(scaling); + shape_info.fallback_scaling.push_back(scaling); } if (emoji) { - face = get_cached_face(last_shape_info.fallbacks[1].file, - last_shape_info.fallbacks[1].index, + face = get_cached_face(shape_info.fallbacks[1].file, + shape_info.fallbacks[1].index, size, res, &error); if (error != 0) { - Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[1].file, last_shape_info.fallbacks[1].index); + Rprintf("Failed to get emoji face: %s, %i\n", shape_info.fallbacks[1].file, shape_info.fallbacks[1].index); error_code = error; return false; } - if (last_shape_info.fallback_scaling.size() == 1) { + if (shape_info.fallback_scaling.size() == 1) { double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; scaling *= family_scaling(face->family_name); - last_shape_info.fallback_scaling.push_back(scaling); + shape_info.fallback_scaling.push_back(scaling); } } @@ -620,12 +601,12 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, return true; } - bool ltr = true; + shape_info.ltr = true; for (unsigned int i = 1; i < n_glyphs; ++i) { if (glyph_info[i - 1].cluster == glyph_info[i].cluster) { continue; } - ltr = glyph_info[i - 1].cluster < glyph_info[i].cluster; + shape_info.ltr = glyph_info[i - 1].cluster < glyph_info[i].cluster; break; } @@ -633,13 +614,16 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, std::vector char_font(embedding_size, current_font); bool needs_fallback = false; bool any_resolved = false; - annotate_fallbacks(current_font + 1, 0, char_font, glyph_info, n_glyphs, needs_fallback, any_resolved, ltr, start); + annotate_fallbacks(current_font + 1, 0, char_font, glyph_info, n_glyphs, needs_fallback, any_resolved, shape_info.ltr, start); if (!needs_fallback) { // Short route - use existing shaping glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); + fill_glyph_info(string, start, embedding_size, shape_info); hb_font_destroy(font); return true; + } else { + } hb_font_destroy(font); @@ -659,13 +643,13 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, } int error = 0; bool using_new = false; - font = load_fallback(current_font, string, start + fallback_start, start + fallback_end, error, size, res, using_new); + font = load_fallback(current_font, string, start + fallback_start, start + fallback_end, error, size, res, using_new, shape_info); if (!using_new) { // We don't need to have success if we are trying an existing font any_resolved = true; } if (error != 0) { - Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); + Rprintf("Failed to get face: %s, %i\n", shape_info.fallbacks[current_font].file, shape_info.fallbacks[current_font].index); error_code = error; return false; } @@ -679,7 +663,7 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, if (n_glyphs > 0) { bool needs_fallback_2 = false; bool any_resolved_2 = false; - annotate_fallbacks(current_font + 1, fallback_start, char_font, glyph_info, n_glyphs, needs_fallback_2, any_resolved_2, ltr, start); + annotate_fallbacks(current_font + 1, fallback_start, char_font, glyph_info, n_glyphs, needs_fallback_2, any_resolved_2, shape_info.ltr, start); if (needs_fallback_2) needs_fallback = true; if (any_resolved_2) any_resolved = true; } @@ -692,22 +676,22 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, // Make sure char_font does not point to non-existing fonts for (size_t i = 0; i < char_font.size(); ++i) { - if (char_font[i] >= last_shape_info.fallbacks.size()) { + if (char_font[i] >= shape_info.fallbacks.size()) { char_font[i] = 0; } } - if (ltr) { + if (shape_info.ltr) { current_font = char_font[0]; unsigned int text_run_start = 0; for (unsigned int i = 1; i <= embedding_size; ++i) { if (i == embedding_size || char_font[i] != current_font) { int error = 0; - FT_Face face = get_cached_face(last_shape_info.fallbacks[current_font].file, - last_shape_info.fallbacks[current_font].index, + FT_Face face = get_cached_face(shape_info.fallbacks[current_font].file, + shape_info.fallbacks[current_font].index, size, res, &error); if (error != 0) { - Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); + Rprintf("Failed to get face: %s, %i\n", shape_info.fallbacks[current_font].file, shape_info.fallbacks[current_font].index); error_code = error; return false; } @@ -720,12 +704,13 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); + fill_glyph_info(string, start + text_run_start, i - text_run_start, shape_info); hb_font_destroy(font); if (i < embedding_size) { current_font = char_font[i]; - if (current_font >= last_shape_info.fallbacks.size()) current_font = 0; + if (current_font >= shape_info.fallbacks.size()) current_font = 0; text_run_start = i; } } @@ -736,11 +721,11 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, for (int i = text_run_end - 1; i >= 0; --i) { if (i <= 0 || char_font[i - 1] != current_font) { int error = 0; - FT_Face face = get_cached_face(last_shape_info.fallbacks[current_font].file, - last_shape_info.fallbacks[current_font].index, + FT_Face face = get_cached_face(shape_info.fallbacks[current_font].file, + shape_info.fallbacks[current_font].index, size, res, &error); if (error != 0) { - Rprintf("Failed to get face: %s, %i\n", last_shape_info.fallbacks[current_font].file, last_shape_info.fallbacks[current_font].index); + Rprintf("Failed to get face: %s, %i\n", shape_info.fallbacks[current_font].file, shape_info.fallbacks[current_font].index); error_code = error; return false; } @@ -753,22 +738,70 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); + fill_glyph_info(string, start + i, text_run_end - i, shape_info); hb_font_destroy(font); if (i > 0) { current_font = char_font[i - 1]; - if (current_font >= last_shape_info.fallbacks.size()) current_font = 0; + if (current_font >= shape_info.fallbacks.size()) current_font = 0; text_run_end = i; } } } } - return true; } +hb_font_t* HarfBuzzShaper::load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, double size, double res, bool& new_added, ShapeInfo& shape_info) { + + new_added = false; + if (font >= shape_info.fallbacks.size()) { + int n_conv = 0; + const char* fallback_string = utf_converter.convert_to_utf(string + start, end - start, n_conv); + shape_info.fallbacks.push_back( + get_fallback(fallback_string, + shape_info.fallbacks[0].file, + shape_info.fallbacks[0].index) + ); + new_added = true; + } + FT_Face face = get_cached_face(shape_info.fallbacks[font].file, + shape_info.fallbacks[font].index, + size, res, &error); + + if (error != 0) { + return NULL; + } + + if (font >= shape_info.fallback_scaling.size()) { + double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; + scaling *= family_scaling(face->family_name); + shape_info.fallback_scaling.push_back(scaling); + } + + return hb_ft_font_create(face, NULL); +} + +bool HarfBuzzShaper::fallback_cluster(unsigned int font, std::vector& char_font, unsigned int from, unsigned int& start, unsigned int& end) { + bool has_cluster = false; + for (unsigned int i = from; i < char_font.size(); ++i) { + if (char_font[i] == font) { + start = i; + has_cluster = true; + break; + } + } + for (unsigned int i = start + 1; i <= char_font.size(); ++i) { + if (i == char_font.size() || char_font[i] != font) { + end = i; + break; + } + } + return has_cluster; +} + void HarfBuzzShaper::annotate_fallbacks(unsigned int font, unsigned int offset, std::vector& char_font, hb_glyph_info_t* glyph_info, @@ -811,73 +844,75 @@ void HarfBuzzShaper::annotate_fallbacks(unsigned int font, unsigned int offset, void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, unsigned int n_glyphs, hb_font_t* font, - unsigned int font_id) { - double scaling = last_shape_info.fallback_scaling[font_id]; + unsigned int font_id, ShapeInfo& shape_info) { + double scaling = shape_info.fallback_scaling[font_id]; if (scaling < 0) scaling = 1.0; + + +#if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 + ascend = 0; + descend = 0; +#else + hb_font_extents_t fextent; + hb_font_get_h_extents(font, &fextent); + + ascend = fextent.ascender; + descend = fextent.descender; +#endif + hb_glyph_extents_t extent; - int32_t x = last_shape_info.width; + + int new_size = shape_info.glyph_id.size() + n_glyphs; + shape_info.glyph_id.reserve(new_size); + shape_info.glyph_cluster.reserve(new_size); + shape_info.x_offset.reserve(new_size); + shape_info.y_offset.reserve(new_size); + shape_info.x_advance.reserve(new_size); + shape_info.y_advance.reserve(new_size); + shape_info.x_bear.reserve(new_size); + shape_info.y_bear.reserve(new_size); + shape_info.width.reserve(new_size); + shape_info.height.reserve(new_size); + shape_info.ascenders.reserve(new_size); + shape_info.descenders.reserve(new_size); + shape_info.font.reserve(new_size); + for (unsigned int i = 0; i < n_glyphs; ++i) { - if (last_shape_info.x_pos.empty()) { - hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); - last_shape_info.left_bearing = extent.x_bearing * scaling; - } - last_shape_info.x_pos.push_back(x + (glyph_pos[i].x_offset) * scaling); - last_shape_info.glyph_id.push_back(glyph_info[i].codepoint); - last_shape_info.font.push_back(font_id); - x += glyph_pos[i].x_advance * scaling; - } - last_shape_info.width = x; - hb_font_get_glyph_extents(font, glyph_info[n_glyphs - 1].codepoint, &extent); - last_shape_info.right_bearing = glyph_pos[n_glyphs - 1].x_advance - (extent.x_bearing + extent.width); - last_shape_info.right_bearing *= scaling; -} + shape_info.glyph_id.push_back(glyph_info[i].codepoint); + shape_info.glyph_cluster.push_back(glyph_info[i].cluster); + shape_info.x_offset.push_back(glyph_pos[i].x_offset * scaling); + shape_info.y_offset.push_back(glyph_pos[i].y_offset * scaling); + shape_info.x_advance.push_back(glyph_pos[i].x_advance * scaling); + shape_info.y_advance.push_back(glyph_pos[i].y_advance * scaling); -hb_font_t* HarfBuzzShaper::load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, double size, double res, bool& new_added) { + hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); + shape_info.x_bear.push_back(extent.x_bearing * scaling); + shape_info.y_bear.push_back(extent.y_bearing * scaling); + shape_info.width.push_back(extent.width * scaling); + shape_info.height.push_back(extent.height * scaling); - new_added = false; - if (font >= last_shape_info.fallbacks.size()) { - int n_conv = 0; - const char* fallback_string = utf_converter.convert_to_utf(string + start, end - start, n_conv); - last_shape_info.fallbacks.push_back( - get_fallback(fallback_string, - last_shape_info.fallbacks[0].file, - last_shape_info.fallbacks[0].index) - ); - new_added = true; - } - FT_Face face = get_cached_face(last_shape_info.fallbacks[font].file, - last_shape_info.fallbacks[font].index, - size, res, &error); + shape_info.ascenders.push_back(ascend); + shape_info.descenders.push_back(descend); - if (error != 0) { - return NULL; - } + shape_info.font.push_back(font_id); - if (font >= last_shape_info.fallback_scaling.size()) { - double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; - scaling *= family_scaling(face->family_name); - last_shape_info.fallback_scaling.push_back(scaling); } - - return hb_ft_font_create(face, NULL); } -bool HarfBuzzShaper::fallback_cluster(unsigned int font, std::vector& char_font, unsigned int from, unsigned int& start, unsigned int& end) { - bool has_cluster = false; - for (unsigned int i = from; i < char_font.size(); ++i) { - if (char_font[i] == font) { - start = i; - has_cluster = true; - break; - } - } - for (unsigned int i = start + 1; i <= char_font.size(); ++i) { - if (i == char_font.size() || char_font[i] != font) { - end = i; - break; +void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, unsigned start, + unsigned length, ShapeInfo& shape_info) { + for (size_t i = shape_info.must_break.size(); i < shape_info.glyph_cluster.size(); ++i) { + int32_t cluster = shape_info.glyph_cluster[i]; + if (cluster < length) { + shape_info.must_break.push_back(glyph_is_linebreak(string[cluster])); + shape_info.may_break.push_back(glyph_is_breaker(string[cluster])); + shape_info.may_stretch.push_back(glyph_may_stretch(string[cluster])); + } else { + shape_info.must_break.push_back(false); + shape_info.may_break.push_back(false); + shape_info.may_stretch.push_back(false); } } - return has_cluster; } #endif diff --git a/src/string_shape.h b/src/string_shape.h index d5722ff..c293c86 100644 --- a/src/string_shape.h +++ b/src/string_shape.h @@ -38,13 +38,24 @@ struct ShapeID { }; struct ShapeInfo { std::vector glyph_id; - std::vector x_pos; + std::vector glyph_cluster; + std::vector x_advance; + std::vector y_advance; + std::vector x_offset; + std::vector y_offset; + std::vector x_bear; + std::vector y_bear; + std::vector width; + std::vector height; + std::vector ascenders; + std::vector descenders; + std::vector may_break; + std::vector must_break; + std::vector may_stretch; std::vector font; std::vector fallbacks; std::vector fallback_scaling; - int32_t width; - int32_t left_bearing; - int32_t right_bearing; + bool ltr; }; namespace std { template <> @@ -104,6 +115,7 @@ class HarfBuzzShaper { static std::vector x_pos; static std::vector y_pos; static std::vector x_mid; + static std::vector y_mid; int32_t width; int32_t height; int32_t left_bearing; @@ -114,7 +126,7 @@ class HarfBuzzShaper { int32_t left_border; int32_t pen_x; int32_t pen_y; - static ShapeInfo last_shape_info; + static std::vector shape_infos; int error_code; @@ -127,16 +139,13 @@ class HarfBuzzShaper { double size, double tracking); bool finish_string(); - - bool single_line_shape(const char* string, FontSettings font_info, double size, - double res); + ShapeInfo shape_text_run(const char* string, FontSettings font_info, double size, + double res); private: static UTF_UCS utf_converter; static LRU_Cache > bidi_cache; static LRU_Cache shape_cache; - static ShapeID last_shape_id; - static ShapeID temp_shape_id; hb_buffer_t *buffer; double cur_lineheight; int cur_align; @@ -175,10 +184,12 @@ class HarfBuzzShaper { bool shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars); bool shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, double size, double res, - std::vector& features, bool emoji); + std::vector& features, bool emoji, + ShapeInfo& shape_info); hb_font_t* load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, - double size, double res, bool& new_added); + double size, double res, bool& new_added, + ShapeInfo& shape_info); bool fallback_cluster(unsigned int font, std::vector& char_font, unsigned int from, unsigned int& start, unsigned int& end); void annotate_fallbacks(unsigned int font, unsigned int offset, @@ -187,7 +198,10 @@ class HarfBuzzShaper { bool& needs_fallback, bool& any_resolved, bool ltr, unsigned int string_offset); void fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, - unsigned int n_glyphs, hb_font_t* font, unsigned int font_id); + unsigned int n_glyphs, hb_font_t* font, unsigned int font_id, + ShapeInfo& shape_info); + void fill_glyph_info(const uint32_t* string, unsigned start, unsigned length, + ShapeInfo& shape_info); inline double family_scaling(const char* family) { if (strcmp("Apple Color Emoji", family) == 0) { @@ -197,7 +211,7 @@ class HarfBuzzShaper { } return 1; } - inline bool glyph_is_linebreak(int id) { + inline bool glyph_is_linebreak(int32_t id) { switch (id) { case 10: return true; case 11: return true; @@ -210,7 +224,7 @@ class HarfBuzzShaper { return false; } - inline bool glyph_is_breaker(int id) { + inline bool glyph_is_breaker(int32_t id) { switch (id) { case 9: return true; case 32: return true; @@ -235,7 +249,7 @@ class HarfBuzzShaper { return false; } - inline bool glyph_may_stretch(int id) { + inline bool glyph_may_stretch(int32_t id) { switch (id) { case 32: return true; } From 9effbf03f2f1c98ae599c4d65d82089f49445e1c Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Tue, 16 Apr 2024 21:29:00 +0200 Subject: [PATCH 02/11] Huge update to the shaping engine in shape_text() --- DESCRIPTION | 3 +- NAMESPACE | 3 + R/cpp11.R | 4 +- R/shape_text.R | 100 +-- R/textshaping-package.R | 3 +- man/figures/lifecycle-archived.svg | 22 +- man/figures/lifecycle-defunct.svg | 22 +- man/figures/lifecycle-deprecated.svg | 22 +- man/figures/lifecycle-experimental.svg | 22 +- man/figures/lifecycle-maturing.svg | 22 +- man/figures/lifecycle-questioning.svg | 22 +- man/figures/lifecycle-soft-deprecated.svg | 21 + man/figures/lifecycle-stable.svg | 30 +- man/figures/lifecycle-superseded.svg | 22 +- man/get_font_features.Rd | 6 +- man/shape_text.Rd | 25 +- man/text_width.Rd | 6 +- src/cpp11.cpp | 8 +- src/string_metrics.cpp | 160 ++--- src/string_metrics.h | 6 +- src/string_shape.cpp | 712 +++++++++++----------- src/string_shape.h | 150 +++-- 22 files changed, 830 insertions(+), 561 deletions(-) create mode 100644 man/figures/lifecycle-soft-deprecated.svg diff --git a/DESCRIPTION b/DESCRIPTION index 401820f..641432c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,6 +17,7 @@ BugReports: https://github.com/r-lib/textshaping/issues Depends: R (>= 3.2.0) Imports: + lifecycle, systemfonts (>= 1.0.0) Suggests: covr, @@ -29,5 +30,5 @@ VignetteBuilder: knitr Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 SystemRequirements: freetype2, harfbuzz, fribidi diff --git a/NAMESPACE b/NAMESPACE index ef8c4c8..4d9a3ee 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,9 @@ export(get_font_features) export(shape_text) export(text_width) +importFrom(lifecycle,deprecated) +importFrom(systemfonts,font_feature) importFrom(systemfonts,match_font) +importFrom(systemfonts,match_fonts) importFrom(systemfonts,system_fonts) useDynLib(textshaping, .registration = TRUE) diff --git a/R/cpp11.R b/R/cpp11.R index 7f44f6e..f10c8e8 100644 --- a/R/cpp11.R +++ b/R/cpp11.R @@ -4,8 +4,8 @@ get_face_features_c <- function(path, index) { .Call(`_textshaping_get_face_features_c`, path, index) } -get_string_shape_c <- function(string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) { - .Call(`_textshaping_get_string_shape_c`, string, id, path, index, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) +get_string_shape_c <- function(string, id, path, index, features, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) { + .Call(`_textshaping_get_string_shape_c`, string, id, path, index, features, size, res, lineheight, align, hjust, vjust, width, tracking, indent, hanging, space_before, space_after) } get_line_width_c <- function(string, path, index, size, res, include_bearing) { diff --git a/R/shape_text.R b/R/shape_text.R index 16599d6..882dc43 100644 --- a/R/shape_text.R +++ b/R/shape_text.R @@ -61,6 +61,7 @@ #' } #' #' @export +#' @importFrom systemfonts font_feature match_fonts #' #' @examples #' string <- "This is a long string\nLook; It spans multiple lines\nand all" @@ -78,10 +79,11 @@ #' shape_text(string, id = c(1, 1, 1), size = c(12, 24, 12)) #' shape_text <- function(strings, id = NULL, family = '', italic = FALSE, - bold = FALSE, size = 12, res = 72, lineheight = 1, - align = 'left', hjust = 0, vjust = 0, width = NA, - tracking = 0, indent = 0, hanging = 0, space_before = 0, - space_after = 0, path = NULL, index = 0) { + weight = 'normal', width = 'normal', features = font_feature(), + size = 12, res = 72, lineheight = 1, align = 'left', + hjust = 0, vjust = 0, max_width = NA, tracking = 0, + indent = 0, hanging = 0, space_before = 0, space_after = 0, + path = NULL, index = 0, bold = deprecated()) { n_strings = length(strings) if (is.null(id)) id <- seq_len(n_strings) id <- rep_len(id, n_strings) @@ -93,62 +95,72 @@ shape_text <- function(strings, id = NULL, family = '', italic = FALSE, id <- id[ido] strings <- as.character(strings)[ido] + if (lifecycle::is_present(bold)) { + lifecycle::deprecate_soft("0.4.0", "shape_text(bold)", "shape_text(weight='bold')") + weight <- ifelse(bold, "bold", "normal") + } + + if (inherits(features, 'font_feature')) features <- list(features) + features <- rep_len(features, n_strings) + if (is.null(path)) { - if (all(c(length(family), length(italic), length(bold)) == 1)) { - loc <- systemfonts::match_font(family, italic, bold) - path <- loc$path - index <- loc$index - } else { - family <- rep_len(family, n_strings) - italic <- rep_len(italic, n_strings) - bold <- rep_len(bold, n_strings) - loc <- Map(systemfonts::match_font, family = family, italic = italic, bold = bold) - path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE)[ido] - index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE)[ido] - } + family <- rep_len(family, n_strings) + italic <- rep_len(italic, n_strings) + weight <- rep_len(weight, n_strings) + width <- rep_len(width, n_strings) + loc <- match_fonts(family, italic, weight, width) + path <- loc$path[ido] + index <- loc$index[ido] + features <- Map(c, loc$features, features)[ido] } else { - if (!all(c(length(path), length(index)) == 1)) { - path <- rep_len(path, n_strings)[ido] - index <- rep_len(index, n_strings)[ido] - } + path <- rep_len(path, n_strings)[ido] + index <- rep_len(index, n_strings)[ido] + features <- features[ido] } - if (length(size) != 1) size <- rep_len(size, n_strings)[ido] - if (length(res) != 1) res <- rep_len(res, n_strings)[ido] - if (length(lineheight) != 1) lineheight <- rep_len(lineheight, n_strings)[ido] - align <- match.arg(align, c('left', 'center', 'right', 'justified', 'distributed'), TRUE) - align <- match(align, c('left', 'center', 'right', 'justified', 'distributed')) - if (length(align) != 1) align <- rep_len(align, n_strings)[ido] - if (length(hjust) != 1) hjust <- rep_len(hjust, n_strings)[ido] - if (length(vjust) != 1) vjust <- rep_len(vjust, n_strings)[ido] - if (length(width) != 1) width <- rep_len(width, n_strings)[ido] - width[is.na(width)] <- -1 - if (length(tracking) != 1) tracking <- rep_len(tracking, n_strings)[ido] - if (length(indent) != 1) indent <- rep_len(indent, n_strings)[ido] - if (length(hanging) != 1) hanging <- rep_len(hanging, n_strings)[ido] - if (length(space_before) != 1) space_before <- rep_len(space_before, n_strings)[ido] - if (length(space_after) != 1) space_after <- rep_len(space_after, n_strings)[ido] + size <- rep_len(size, n_strings)[ido] + res <- rep_len(res, n_strings)[ido] + lineheight <- rep_len(lineheight, n_strings)[ido] + align <- match.arg(align, c('left', 'center', 'right', 'justified-left', 'justified-center', 'justified-right', 'distributed'), TRUE) + align <- match(align, c('left', 'center', 'right', 'justified-left', 'justified-center', 'justified-right', 'distributed')) + align <- rep_len(align, n_strings)[ido] + hjust <- rep_len(hjust, n_strings)[ido] + vjust <- rep_len(vjust, n_strings)[ido] + max_width <- rep_len(max_width, n_strings)[ido] + max_width[is.na(max_width)] <- -1 + tracking <- rep_len(tracking, n_strings)[ido] + indent <- rep_len(indent, n_strings)[ido] + hanging <- rep_len(hanging, n_strings)[ido] + space_before <- rep_len(space_before, n_strings)[ido] + space_after <- rep_len(space_after, n_strings)[ido] - width <- width * res + max_width <- max_width * res + tracking <- tracking * res indent <- indent * res hanging <- hanging * res + space_before <- space_before * res / 72 + space_after <- space_after * res / 72 + if (!all(file.exists(path))) stop("path must point to a valid file", call. = FALSE) shape <- get_string_shape_c( - strings, id, path, as.integer(index), as.numeric(size), as.numeric(res), - as.numeric(lineheight), as.integer(align) - 1L, as.numeric(hjust), - as.numeric(vjust), as.numeric(width), as.numeric(tracking), + strings, id, path, as.integer(index), features, as.numeric(size), + as.numeric(res), as.numeric(lineheight), as.integer(align) - 1L, + as.numeric(hjust), as.numeric(vjust), as.numeric(max_width), as.numeric(tracking), as.numeric(indent), as.numeric(hanging), as.numeric(space_before), as.numeric(space_after) ) if (nrow(shape$shape) == 0) return(shape) shape$metrics$string <- vapply(split(strings, id), paste, character(1), collapse = '') - shape$shape$string_id <- ido[shape$shape$string_id] + shape$metrics[-1] <- lapply(shape$metrics[-1], function(x) x * 72 / res[!duplicated(id)]) + + shape$shape$string_id <- ido[(cumsum(c(0, rle(id)$lengths)) + 1)[shape$shape$metric_id] + shape$shape$string_id - 1] shape$shape <- shape$shape[order(shape$shape$string_id), , drop = FALSE] - #shape$shape$glyph <- intToUtf8(shape$shape$glyph, multiple = TRUE) - shape$shape$x_offset <- shape$shape$x_offset * (72 / res) - shape$shape$y_offset <- shape$shape$y_offset * (72 / res) - shape$shape$x_midpoint <- shape$shape$x_midpoint * (72 / res) + shape$shape$x_offset <- shape$shape$x_offset * (72 / res[shape$shape$string_id]) + shape$shape$y_offset <- shape$shape$y_offset * (72 / res[shape$shape$string_id]) + shape$shape$advance <- shape$shape$advance * (72 / res[shape$shape$string_id]) + shape$shape$ascender <- shape$shape$ascender * (72 / res[shape$shape$string_id]) + shape$shape$descender <- shape$shape$descender * (72 / res[shape$shape$string_id]) shape } #' Calculate the width of a string, ignoring new-lines diff --git a/R/textshaping-package.R b/R/textshaping-package.R index ab8a9e3..3720fc0 100644 --- a/R/textshaping-package.R +++ b/R/textshaping-package.R @@ -4,7 +4,8 @@ # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start -#' @useDynLib textshaping, .registration = TRUE +#' @importFrom lifecycle deprecated #' @importFrom systemfonts system_fonts +#' @useDynLib textshaping, .registration = TRUE ## usethis namespace: end NULL diff --git a/man/figures/lifecycle-archived.svg b/man/figures/lifecycle-archived.svg index 48f72a6..745ab0c 100644 --- a/man/figures/lifecycle-archived.svg +++ b/man/figures/lifecycle-archived.svg @@ -1 +1,21 @@ - lifecyclelifecyclearchivedarchived \ No newline at end of file + + lifecycle: archived + + + + + + + + + + + + + + + lifecycle + + archived + + diff --git a/man/figures/lifecycle-defunct.svg b/man/figures/lifecycle-defunct.svg index 01452e5..d5c9559 100644 --- a/man/figures/lifecycle-defunct.svg +++ b/man/figures/lifecycle-defunct.svg @@ -1 +1,21 @@ -lifecyclelifecycledefunctdefunct \ No newline at end of file + + lifecycle: defunct + + + + + + + + + + + + + + + lifecycle + + defunct + + diff --git a/man/figures/lifecycle-deprecated.svg b/man/figures/lifecycle-deprecated.svg index 4baaee0..b61c57c 100644 --- a/man/figures/lifecycle-deprecated.svg +++ b/man/figures/lifecycle-deprecated.svg @@ -1 +1,21 @@ -lifecyclelifecycledeprecateddeprecated \ No newline at end of file + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/man/figures/lifecycle-experimental.svg b/man/figures/lifecycle-experimental.svg index d1d060e..5d88fc2 100644 --- a/man/figures/lifecycle-experimental.svg +++ b/man/figures/lifecycle-experimental.svg @@ -1 +1,21 @@ -lifecyclelifecycleexperimentalexperimental \ No newline at end of file + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/man/figures/lifecycle-maturing.svg b/man/figures/lifecycle-maturing.svg index df71310..897370e 100644 --- a/man/figures/lifecycle-maturing.svg +++ b/man/figures/lifecycle-maturing.svg @@ -1 +1,21 @@ -lifecyclelifecyclematuringmaturing \ No newline at end of file + + lifecycle: maturing + + + + + + + + + + + + + + + lifecycle + + maturing + + diff --git a/man/figures/lifecycle-questioning.svg b/man/figures/lifecycle-questioning.svg index 08ee0c9..7c1721d 100644 --- a/man/figures/lifecycle-questioning.svg +++ b/man/figures/lifecycle-questioning.svg @@ -1 +1,21 @@ -lifecyclelifecyclequestioningquestioning \ No newline at end of file + + lifecycle: questioning + + + + + + + + + + + + + + + lifecycle + + questioning + + diff --git a/man/figures/lifecycle-soft-deprecated.svg b/man/figures/lifecycle-soft-deprecated.svg new file mode 100644 index 0000000..9c166ff --- /dev/null +++ b/man/figures/lifecycle-soft-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: soft-deprecated + + + + + + + + + + + + + + + lifecycle + + soft-deprecated + + diff --git a/man/figures/lifecycle-stable.svg b/man/figures/lifecycle-stable.svg index e015dc8..9bf21e7 100644 --- a/man/figures/lifecycle-stable.svg +++ b/man/figures/lifecycle-stable.svg @@ -1 +1,29 @@ -lifecyclelifecyclestablestable \ No newline at end of file + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/man/figures/lifecycle-superseded.svg b/man/figures/lifecycle-superseded.svg index 75f24f5..db8d757 100644 --- a/man/figures/lifecycle-superseded.svg +++ b/man/figures/lifecycle-superseded.svg @@ -1 +1,21 @@ - lifecyclelifecyclesupersededsuperseded \ No newline at end of file + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + diff --git a/man/get_font_features.Rd b/man/get_font_features.Rd index 9b57b33..2898796 100644 --- a/man/get_font_features.Rd +++ b/man/get_font_features.Rd @@ -13,11 +13,11 @@ get_font_features( ) } \arguments{ -\item{family}{The name of the font family} +\item{family}{The name of the font families to match} -\item{italic}{logicals indicating the font style} +\item{italic}{logical indicating the font slant} -\item{bold}{logicals indicating the font style} +\item{bold}{logical indicating whether the font weight} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} diff --git a/man/shape_text.Rd b/man/shape_text.Rd index 5449709..1a7f458 100644 --- a/man/shape_text.Rd +++ b/man/shape_text.Rd @@ -9,21 +9,24 @@ shape_text( id = NULL, family = "", italic = FALSE, - bold = FALSE, + weight = "normal", + width = "normal", + features = font_feature(), size = 12, res = 72, lineheight = 1, align = "left", hjust = 0, vjust = 0, - width = NA, + max_width = NA, tracking = 0, indent = 0, hanging = 0, space_before = 0, space_after = 0, path = NULL, - index = 0 + index = 0, + bold = deprecated() ) } \arguments{ @@ -32,11 +35,12 @@ shape_text( \item{id}{A vector grouping the strings together. If strings share an id the shaping will continue between strings} -\item{family}{The name of the font family} +\item{family}{The name of the font families to match} -\item{italic}{logicals indicating the font style} +\item{italic}{logical indicating the font slant} -\item{bold}{logicals indicating the font style} +\item{width}{The requested with of the string in inches. Setting this to +something other than \code{NA} will turn on word wrapping.} \item{size}{The pointsize of the font to use for size related measures} @@ -44,14 +48,11 @@ shaping will continue between strings} \item{lineheight}{A multiplier for the lineheight} -\item{align}{Within text box alignment, either \code{'left'}, \code{'center'}, or -\code{'right'}} +\item{align}{Within text box alignment, either \code{'left'}, \code{'center'}, \code{'right'}, +\code{'justified'}, or \code{'distributed'}} \item{hjust, vjust}{The justification of the textbox surrounding the text} -\item{width}{The requested with of the string in inches. Setting this to -something other than \code{NA} will turn on word wrapping.} - \item{tracking}{Tracking of the glyphs (space adjustment) measured in 1/1000 em.} @@ -65,6 +66,8 @@ measured in points} \item{path, index}{path an index of a font file to circumvent lookup based on family and style} + +\item{bold}{logical indicating whether the font weight} } \value{ A list with two element: \code{shape} contains the position of each glyph, diff --git a/man/text_width.Rd b/man/text_width.Rd index 8f4bfd9..6e7b042 100644 --- a/man/text_width.Rd +++ b/man/text_width.Rd @@ -19,11 +19,11 @@ text_width( \arguments{ \item{strings}{A character vector of strings} -\item{family}{The name of the font family} +\item{family}{The name of the font families to match} -\item{italic}{logicals indicating the font style} +\item{italic}{logical indicating the font slant} -\item{bold}{logicals indicating the font style} +\item{bold}{logical indicating whether the font weight} \item{size}{The pointsize of the font to use for size related measures} diff --git a/src/cpp11.cpp b/src/cpp11.cpp index e69c9e0..6d22ea2 100644 --- a/src/cpp11.cpp +++ b/src/cpp11.cpp @@ -13,10 +13,10 @@ extern "C" SEXP _textshaping_get_face_features_c(SEXP path, SEXP index) { END_CPP11 } // string_metrics.h -list get_string_shape_c(strings string, integers id, strings path, integers index, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after); -extern "C" SEXP _textshaping_get_string_shape_c(SEXP string, SEXP id, SEXP path, SEXP index, SEXP size, SEXP res, SEXP lineheight, SEXP align, SEXP hjust, SEXP vjust, SEXP width, SEXP tracking, SEXP indent, SEXP hanging, SEXP space_before, SEXP space_after) { +list get_string_shape_c(strings string, integers id, strings path, integers index, list_of features, doubles size, doubles res, doubles lineheight, integers align, doubles hjust, doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after); +extern "C" SEXP _textshaping_get_string_shape_c(SEXP string, SEXP id, SEXP path, SEXP index, SEXP features, SEXP size, SEXP res, SEXP lineheight, SEXP align, SEXP hjust, SEXP vjust, SEXP width, SEXP tracking, SEXP indent, SEXP hanging, SEXP space_before, SEXP space_after) { BEGIN_CPP11 - return cpp11::as_sexp(get_string_shape_c(cpp11::as_cpp>(string), cpp11::as_cpp>(id), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(lineheight), cpp11::as_cpp>(align), cpp11::as_cpp>(hjust), cpp11::as_cpp>(vjust), cpp11::as_cpp>(width), cpp11::as_cpp>(tracking), cpp11::as_cpp>(indent), cpp11::as_cpp>(hanging), cpp11::as_cpp>(space_before), cpp11::as_cpp>(space_after))); + return cpp11::as_sexp(get_string_shape_c(cpp11::as_cpp>(string), cpp11::as_cpp>(id), cpp11::as_cpp>(path), cpp11::as_cpp>(index), cpp11::as_cpp>>(features), cpp11::as_cpp>(size), cpp11::as_cpp>(res), cpp11::as_cpp>(lineheight), cpp11::as_cpp>(align), cpp11::as_cpp>(hjust), cpp11::as_cpp>(vjust), cpp11::as_cpp>(width), cpp11::as_cpp>(tracking), cpp11::as_cpp>(indent), cpp11::as_cpp>(hanging), cpp11::as_cpp>(space_before), cpp11::as_cpp>(space_after))); END_CPP11 } // string_metrics.h @@ -31,7 +31,7 @@ extern "C" { static const R_CallMethodDef CallEntries[] = { {"_textshaping_get_face_features_c", (DL_FUNC) &_textshaping_get_face_features_c, 2}, {"_textshaping_get_line_width_c", (DL_FUNC) &_textshaping_get_line_width_c, 6}, - {"_textshaping_get_string_shape_c", (DL_FUNC) &_textshaping_get_string_shape_c, 16}, + {"_textshaping_get_string_shape_c", (DL_FUNC) &_textshaping_get_string_shape_c, 17}, {NULL, NULL, 0} }; } diff --git a/src/string_metrics.cpp b/src/string_metrics.cpp index 7be5988..d252bb7 100644 --- a/src/string_metrics.cpp +++ b/src/string_metrics.cpp @@ -16,8 +16,9 @@ using namespace cpp11; #ifdef NO_HARFBUZZ_FRIBIDI list get_string_shape_c(strings string, integers id, strings path, integers index, - doubles size, doubles res, doubles lineheight, integers align, - doubles hjust, doubles vjust, doubles width, doubles tracking, + list_of features, doubles size, doubles res, + doubles lineheight, integers align, doubles hjust, + doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after) { Rprintf("textshaping has been compiled without HarfBuzz and/or Fribidi. Please install system dependencies and recompile\n"); @@ -90,43 +91,53 @@ int ts_string_shape_old(const char* string, FontSettings font_info, double size, #else +std::vector< std::vector > create_font_features(list_of features) { + std::vector< std::vector > res; + + for (R_xlen_t i = 0; i < features.size(); ++i) { + res.emplace_back(); + strings tags = as_cpp(features[i][0]); + integers vals = as_cpp(features[i][1]); + for (R_xlen_t j = 0; j < tags.size(); ++j) { + const char* f = Rf_translateCharUTF8(tags[j]); + res.back().push_back({{f[0], f[1], f[2], f[3]}, vals[j]}); + } + } + + return res; +} + +std::vector create_font_settings(strings path, integers index, std::vector< std::vector >& features) { + std::vector res; + + for (R_xlen_t i = 0; i < path.size(); ++i) { + res.emplace_back(); + strncpy(res.back().file, Rf_translateCharUTF8(path[i]), PATH_MAX); + res.back().file[PATH_MAX] = '\0'; + res.back().index = index[i]; + res.back().features = features[i].data(); + res.back().n_features = features[i].size(); + } + + return res; +} + list get_string_shape_c(strings string, integers id, strings path, integers index, - doubles size, doubles res, doubles lineheight, integers align, - doubles hjust, doubles vjust, doubles width, doubles tracking, + list_of features, doubles size, doubles res, + doubles lineheight, integers align, doubles hjust, + doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after) { int n_strings = string.size(); - bool one_path = path.size() == 1; - const char* first_path = Rf_translateCharUTF8(path[0]); - int first_index = index[0]; - bool one_size = size.size() == 1; - double first_size = size[0]; - bool one_res = res.size() == 1; - double first_res = res[0]; - bool one_lht = lineheight.size() == 1; - double first_lht = lineheight[0]; - bool one_align = align.size() == 1; - int first_align = align[0]; - bool one_hjust = hjust.size() == 1; - double first_hjust = hjust[0]; - bool one_vjust = vjust.size() == 1; - double first_vjust = vjust[0]; - bool one_width = width.size() == 1; - double first_width = width[0] * 64; - bool one_tracking = tracking.size() == 1; - double first_tracking = tracking[0]; - bool one_indent = indent.size() == 1; - double first_indent = indent[0] * 64; - bool one_hanging = hanging.size() == 1; - double first_hanging = hanging[0] * 64; - bool one_before = space_before.size() == 1; - double first_before = space_before[0] * 64; - bool one_after = space_after.size() == 1; - double first_after = space_after[0] * 64; + auto all_features = create_font_features(features); + auto fonts = create_font_settings(path, index, all_features); // Return Columns - writable::integers glyph, glyph_id, metric_id, string_id; - writable::doubles x_offset, y_offset, x_midpoint, widths, heights, left_bearings, right_bearings, top_bearings, bottom_bearings, left_border, top_border, pen_x, pen_y; + writable::integers glyph, glyph_id, metric_id, string_id, fontindex; + writable::doubles x_offset, y_offset, widths, heights, left_bearings, right_bearings, + top_bearings, bottom_bearings, left_border, top_border, pen_x, pen_y, fontsize, + advance, ascender, descender; + writable::strings fontpath; // Shape the text int cur_id = id[0] - 1; // make sure it differs from first @@ -137,31 +148,19 @@ list get_string_shape_c(strings string, integers id, strings path, integers inde const char* this_string = Rf_translateCharUTF8(string[i]); int this_id = id[i]; if (cur_id == this_id) { - success = shaper.add_string(this_string, - one_path ? first_path : Rf_translateCharUTF8(path[i]), - one_path ? first_index : index[i], - one_size ? first_size : size[i], - one_tracking ? first_tracking : tracking[i]); + success = shaper.add_string(this_string, fonts[i], size[i], tracking[i], cpp11::is_na(string[i])); + if (!success) { Rf_error("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(path[i]), shaper.error_code); } } else { cur_id = this_id; - success = shaper.shape_string(this_string, - one_path ? first_path : Rf_translateCharUTF8(path[i]), - one_path ? first_index : index[i], - one_size ? first_size : size[i], - one_res ? first_res : res[i], - one_lht ? first_lht : lineheight[i], - one_align ? first_align : align[i], - one_hjust ? first_hjust : hjust[i], - one_vjust ? first_vjust : vjust[i], - one_width ? first_width : width[i] * 64, - one_tracking ? first_tracking : tracking[i], - one_indent ? first_indent : indent[i] * 64, - one_hanging ? first_hanging : hanging[i] * 64, - one_before ? first_before : space_before[i] * 64, - one_after ? first_after : space_after[i] * 64); + success = shaper.shape_string(this_string, fonts[i], size[i], res[i], + lineheight[i], align[i], hjust[i], vjust[i], + width[i] * 64.0, tracking[i], indent[i] * 64.0, + hanging[i] * 64.0, space_before[i] * 64.0, + space_after[i] * 64.0, cpp11::is_na(string[i])); + if (!success) { Rf_error("Failed to shape string (%s) with font file (%s) with freetype error %i", this_string, Rf_translateCharUTF8(STRING_ELT(path, i)), shaper.error_code); } @@ -174,13 +173,19 @@ list get_string_shape_c(strings string, integers id, strings path, integers inde } int n_glyphs = shaper.glyph_id.size(); for (int j = 0; j < n_glyphs; j++) { - glyph.push_back((int) shaper.glyph_cluster[j]); + if (shaper.must_break[j]) continue; // Don't add any linebreak chars as they often map to null glyph + glyph.push_back((int) shaper.glyph_cluster[j] + 1); glyph_id.push_back((int) shaper.glyph_id[j]); metric_id.push_back(pen_x.size() + 1); string_id.push_back(shaper.string_id[j] + 1); x_offset.push_back(double(shaper.x_pos[j]) / 64.0); y_offset.push_back(double(shaper.y_pos[j]) / 64.0); - x_midpoint.push_back(double(shaper.x_mid[j]) / 64.0); + fontpath.push_back(shaper.fontfile[j]); + fontindex.push_back((int) shaper.fontindex[j]); + fontsize.push_back(shaper.fontsize[j]); + advance.push_back(double(shaper.advance[j]) / 64.0); + ascender.push_back(double(shaper.ascender[j]) / 64.0); + descender.push_back(double(shaper.descender[j]) / 64.0); } widths.push_back(double(shaper.width) / 64.0); heights.push_back(double(shaper.height) / 64.0); @@ -197,28 +202,33 @@ list get_string_shape_c(strings string, integers id, strings path, integers inde writable::strings str(pen_x.size()); writable::data_frame string_df({ - "string"_nm = (SEXP) str, - "width"_nm = (SEXP) widths, - "height"_nm = (SEXP) heights, - "left_bearing"_nm = (SEXP) left_bearings, - "right_bearing"_nm = (SEXP) right_bearings, - "top_bearing"_nm = (SEXP) top_bearings, - "bottom_bearing"_nm = (SEXP) bottom_bearings, - "left_border"_nm = (SEXP) left_border, - "top_border"_nm = (SEXP) top_border, - "pen_x"_nm = (SEXP) pen_x, - "pen_y"_nm = (SEXP) pen_y + "string"_nm = str, + "width"_nm = widths, + "height"_nm = heights, + "left_bearing"_nm = left_bearings, + "right_bearing"_nm = right_bearings, + "top_bearing"_nm = top_bearings, + "bottom_bearing"_nm = bottom_bearings, + "left_border"_nm = left_border, + "top_border"_nm = top_border, + "pen_x"_nm = pen_x, + "pen_y"_nm = pen_y }); string_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); writable::data_frame info_df({ - "glyph"_nm = (SEXP) glyph, - "index"_nm = (SEXP) glyph_id, - "metric_id"_nm = (SEXP) metric_id, - "string_id"_nm = (SEXP) string_id, - "x_offset"_nm = (SEXP) x_offset, - "y_offset"_nm = (SEXP) y_offset, - "x_midpoint"_nm = (SEXP) x_midpoint + "glyph"_nm = glyph, + "index"_nm = glyph_id, + "metric_id"_nm = metric_id, + "string_id"_nm = string_id, + "x_offset"_nm = x_offset, + "y_offset"_nm = y_offset, + "font_path"_nm = fontpath, + "font_index"_nm = fontindex, + "font_size"_nm = fontsize, + "advance"_nm = advance, + "ascender"_nm = ascender, + "descender"_nm = descender }); info_df.attr("class") = writable::strings({"tbl_df", "tbl", "data.frame"}); @@ -270,7 +280,7 @@ int ts_string_width(const char* string, FontSettings font_info, double size, HarfBuzzShaper& shaper = get_hb_shaper(); shaper.error_code = 0; const ShapeInfo string_shape = shaper.shape_text_run( - string, font_info, size, res + string, font_info, size, res, 0 ); if (shaper.error_code != 0) { @@ -301,7 +311,7 @@ int ts_string_shape(const char* string, FontSettings font_info, double size, HarfBuzzShaper& shaper = get_hb_shaper(); shaper.error_code = 0; const ShapeInfo string_shape = shaper.shape_text_run( - string, font_info, size, res + string, font_info, size, res, 0 ); if (shaper.error_code != 0) { diff --git a/src/string_metrics.h b/src/string_metrics.h index 6b42dd3..2f77fd7 100644 --- a/src/string_metrics.h +++ b/src/string_metrics.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -22,8 +23,9 @@ struct Point { [[cpp11::register]] list get_string_shape_c(strings string, integers id, strings path, integers index, - doubles size, doubles res, doubles lineheight, integers align, - doubles hjust, doubles vjust, doubles width, doubles tracking, + list_of features, doubles size, doubles res, + doubles lineheight, integers align, doubles hjust, + doubles vjust, doubles width, doubles tracking, doubles indent, doubles hanging, doubles space_before, doubles space_after); diff --git a/src/string_shape.cpp b/src/string_shape.cpp index 604e22a..84a2382 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -10,61 +10,19 @@ UTF_UCS HarfBuzzShaper::utf_converter = UTF_UCS(); LRU_Cache > HarfBuzzShaper::bidi_cache = {1000}; LRU_Cache HarfBuzzShaper::shape_cache = {1000}; -std::vector HarfBuzzShaper::glyph_id = {}; -std::vector HarfBuzzShaper::glyph_cluster = {}; -std::vector HarfBuzzShaper::string_id = {}; -std::vector HarfBuzzShaper::x_pos = {}; -std::vector HarfBuzzShaper::y_pos = {}; -std::vector HarfBuzzShaper::x_mid = {}; -std::vector HarfBuzzShaper::x_advance = {}; -std::vector HarfBuzzShaper::x_offset = {}; -std::vector HarfBuzzShaper::left_bear = {}; -std::vector HarfBuzzShaper::right_bear = {}; -std::vector HarfBuzzShaper::top_extend = {}; -std::vector HarfBuzzShaper::bottom_extend = {}; -std::vector HarfBuzzShaper::ascenders = {}; -std::vector HarfBuzzShaper::descenders = {}; -std::vector HarfBuzzShaper::may_break = {}; -std::vector HarfBuzzShaper::must_break = {}; -std::vector HarfBuzzShaper::may_stretch = {}; -std::vector HarfBuzzShaper::shape_infos = {}; - -bool HarfBuzzShaper::shape_string(const char* string, const char* fontfile, - int index, double size, double res, double lineheight, + +bool HarfBuzzShaper::shape_string(const char* string, FontSettings& font_info, + double size, double res, double lineheight, int align, double hjust, double vjust, double width, double tracking, double ind, double hang, double before, - double after) { + double after, bool spacer) { reset(); - int error = 0; - FT_Face face = get_cached_face(fontfile, index, size, res, &error); - if (error != 0) { - error_code = error; - return false; - } - hb_font_t *font = hb_ft_font_create(face, NULL); - - int n_chars = 0; - const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); - - std::vector embeddings = {}; - - if (n_chars > 1) { - std::string key(string); - if (!bidi_cache.get(key, embeddings)) { - embeddings = get_bidi_embeddings(utc_string, n_chars); - bidi_cache.add(key, embeddings); - } - } else { - embeddings.push_back(0); - } - max_width = width; indent = ind; hanging = hang; space_before = before; space_after = after; - cur_tracking = tracking; cur_res = res; cur_lineheight = lineheight; @@ -72,212 +30,205 @@ bool HarfBuzzShaper::shape_string(const char* string, const char* fontfile, cur_hjust = hjust; cur_vjust = vjust; - int start = 0; - for (size_t i = 0; i < embeddings.size(); ++i) { - if (i == embeddings.size() - 1 || embeddings[i] != embeddings[i + 1]) { - hb_buffer_reset(buffer); - hb_buffer_add_utf32(buffer, utc_string, n_chars, start, i - start + 1); - hb_buffer_guess_segment_properties(buffer); - - bool success = shape_glyphs(font, utc_string + start, i - start + 1); - if (!success) { - return false; - } - - start = i + 1; - } - } - - hb_font_destroy(font); - //FT_Done_Face(face); - return true; + return add_string(string, font_info, size, tracking, spacer); } -bool HarfBuzzShaper::add_string(const char* string, const char* fontfile, - int index, double size, double tracking) { - cur_string++; - int error = 0; - FT_Face face = get_cached_face(fontfile, index, size, cur_res, &error); - if (error != 0) { - error_code = error; - return false; - } - hb_font_t *font = hb_ft_font_create(face, NULL); - - int n_chars = 0; - const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); - - std::vector embeddings = {}; - - if (n_chars > 1) { - std::string key(string); - if (!bidi_cache.get(key, embeddings)) { - embeddings = get_bidi_embeddings(utc_string, n_chars); - bidi_cache.add(key, embeddings); - } - } else { - embeddings.push_back(0); +bool HarfBuzzShaper::add_string(const char* string, FontSettings& font_info, + double size, double tracking, bool spacer) { + if (spacer) { + return add_spacer(size, tracking); } - cur_tracking = tracking; + error_code = 0; + shape_infos.push_back(shape_text_run(string, font_info, size, cur_res, tracking)); - int start = 0; - for (size_t i = 0; i < embeddings.size(); ++i) { - if (i == embeddings.size() - 1 || embeddings[i] != embeddings[i + 1]) { - hb_buffer_reset(buffer); - hb_buffer_add_utf32(buffer, utc_string, n_chars, start, i - start + 1); - hb_buffer_guess_segment_properties(buffer); - - bool success = shape_glyphs(font, utc_string + start, i - start + 1); - if (!success) { - return false; - } - - start = i + 1; - } + if (error_code != 0) { + shape_infos.pop_back(); + return false; } - - hb_font_destroy(font); //FT_Done_Face(face); return true; } +bool HarfBuzzShaper::add_spacer(double height, double width) { + width *= 64.0 / 72.0; + double space_height = height * 64.0 * cur_res / 72.0; + shape_infos.push_back({ + {0}, // glyph_id + {0}, // glyph_cluster + {int32_t(width)}, // x_advance + {0}, // y_advance + {0}, // x_offset + {0}, // y_offset + {0}, // x_bear + {int32_t(space_height)}, // y_bear + {int32_t(width)}, // width + {int32_t(space_height)}, // height + {int32_t(space_height)}, // ascenders + {0}, // descenders + {false}, // may_break + {false}, // must_break + {false}, // may_stretch + {0}, // font + {{"", 0, NULL, 0}}, // fallbacks + {height}, // fallback_size + {-1}, // fallback_scaling + true // ltr + }); + return true; +} bool HarfBuzzShaper::finish_string() { - if (glyph_id.empty()) { + if (shape_infos.empty()) { return true; } + bool vertical = false; + + if (vertical) { + pen_y = indent; + pen_x = 0; + } else { + pen_x = indent; + pen_y = 0; + } + bool first_char = true; bool first_line = true; - pen_x += indent; - int last_space = -1; - int32_t last_nonspace_width = 0; - int32_t last_nonspace_bear = 0; int cur_line = 0; - double line_height = 0; - size_t glyph_counter = 0; int32_t max_descend = 0; int32_t max_ascend = 0; int32_t max_top_extend = 0; int32_t max_bottom_extend = 0; int32_t last_max_descend = 0; - bool no_break_last = true; - - for (unsigned int i = 0; i < glyph_id.size(); ++i) { - bool linebreak = must_break[i]; - bool might_break = may_break[i]; - bool last = i == glyph_id.size() - 1; - - bool soft_wrap = false; - - if (might_break || linebreak) { - last_space = i; - if (no_break_last) { - last_nonspace_width = pen_x; - last_nonspace_bear = i == 0 ? 0 : right_bear[i - 1]; + int breaktype = 0; + int32_t cur_linewidth = 0; + int32_t cur_left_bear = 0; + int32_t cur_right_bear = 0; + size_t last_line_start = 0; + + for (size_t j = 0; j < shape_infos.size(); ++j) { + ShapeInfo& cur_shape = shape_infos[j]; + bool last_shape = j == shape_infos.size() - 1; + size_t start = cur_shape.ltr ? 0 : cur_shape.glyph_id.size(); + + while (true) { + size_t end; + bool last; + if (cur_shape.glyph_id.size() == 0) { + end = 0; + last = last_shape; + max_ascend = std::max(max_ascend, cur_shape.ascenders[0]); + max_descend = std::min(max_descend, cur_shape.descenders[0]); + } else { + end = fill_out_width(start, max_width - pen_x, j, breaktype); + last = last_shape && end == (cur_shape.ltr ? cur_shape.glyph_id.size() : 0); + for (size_t i = std::min(start, end); i < std::max(start, end); ++i) { + glyph_id.push_back(cur_shape.glyph_id[i]); + glyph_cluster.push_back(cur_shape.glyph_cluster[i]); + fontfile.push_back(cur_shape.fallbacks[cur_shape.font[i]].file); + fontindex.push_back(cur_shape.fallbacks[cur_shape.font[i]].index); + fontsize.push_back(cur_shape.fallback_size[cur_shape.font[i]]); + advance.push_back(cur_shape.x_advance[i]); + ascender.push_back(cur_shape.ascenders[i]); + descender.push_back(cur_shape.descenders[i]); + + string_id.push_back(j); + line_id.push_back(cur_line); + x_pos.push_back(pen_x + cur_shape.x_offset[i]); + y_pos.push_back(pen_y + cur_shape.y_offset[i]); + + must_break.push_back(cur_shape.must_break[i]); + may_stretch.push_back(cur_shape.may_stretch[i]); + + if (vertical) { + pen_y += cur_shape.y_advance[i]; + if (first_char) cur_left_bear = cur_shape.y_bear[i]; + if (!cur_shape.may_break[i] || (cur_shape.ltr && first_char)) { + cur_linewidth += pen_y; + cur_right_bear = cur_shape.y_bear[i] + cur_shape.height[i]; + } + + // TODO: Awaiting knowledge on glyph metrics returned with vertical layouts + max_ascend = std::max(max_ascend, cur_shape.ascenders[i]); + max_top_extend = std::max(max_top_extend, cur_shape.y_bear[i]); + max_descend = std::max(max_descend, cur_shape.descenders[i]); + max_bottom_extend = std::max(max_bottom_extend, cur_shape.height[i] + cur_shape.y_bear[i]); + + + } else { + pen_x += cur_shape.x_advance[i]; + if (first_char) cur_left_bear = cur_shape.x_bear[i]; + if ((!cur_shape.may_break[i] && !cur_shape.must_break[i]) || (cur_shape.ltr && first_char)) { + cur_linewidth = pen_x; + cur_right_bear = cur_shape.x_advance[i] - cur_shape.x_bear[i] - cur_shape.width[i]; + } + + max_ascend = std::max(max_ascend, cur_shape.ascenders[i]); + max_top_extend = std::max(max_top_extend, cur_shape.y_bear[i]); + max_descend = std::min(max_descend, cur_shape.descenders[i]); + max_bottom_extend = std::min(max_bottom_extend, cur_shape.height[i] + cur_shape.y_bear[i]); + } + + if (!cur_shape.may_break[i]) first_char = false; + } } - } - no_break_last = !might_break; - - // Calculate top and bottom extend and ascender/descender + if (breaktype != 0 || last) { + line_width.push_back(cur_linewidth); + line_left_bear.push_back(cur_left_bear); + line_right_bear.push_back(cur_right_bear); - // Soft wrapping? - if (max_width > 0 && !first_char && pen_x + x_advance[i] > max_width && !might_break && !linebreak) { - // Rewind to last breaking char and set the soft_wrap flag - i = last_space >= 0 ? last_space : i - 1; - x_pos.resize(i + 1); - x_mid.resize(i + 1); - line_id.resize(i + 1); - soft_wrap = true; - last = false; - } else { - // No soft wrap, record pen position - x_pos.push_back(pen_x + x_offset[i]); - x_mid.push_back(x_advance[i] / 2); - line_id.push_back(cur_line); - } - - // If last char update terminal line info - if (last && !linebreak) { - last_nonspace_width = pen_x + x_advance[i]; - last_nonspace_bear = right_bear[i]; - } - if (first_char) { - line_left_bear.push_back(left_bear[i]); - pen_y -= space_before; - } - - // Handle new lines - if (linebreak || soft_wrap || last) { - // Record and reset line dim info - line_right_bear.push_back(last_nonspace_bear); - line_width.push_back(last_nonspace_width); - last_nonspace_bear = 0; - last_nonspace_width = 0; - last_space = -1; - no_break_last = true; - - // Calculate line dimensions - for (size_t j = glyph_counter; j < x_pos.size(); ++j) { - if (max_ascend < ascenders[j]) { - max_ascend = ascenders[j]; - } - if (max_top_extend < top_extend[j]) { - max_top_extend = top_extend[j]; - } - if (max_descend > descenders[j]) { - max_descend = descenders[j]; + if (first_line) { + top_border = max_ascend + space_before; + top_bearing = top_border - max_top_extend; } - if (max_bottom_extend > bottom_extend[j]) { - max_bottom_extend = bottom_extend[j]; + + double line_height = first_line ? 0 : (max_ascend - last_max_descend) * cur_lineheight; + for (size_t k = last_line_start; k < y_pos.size(); ++k) { + if (vertical) { + x_pos[k] -= line_height; + } else { + y_pos[k] -= line_height; + } } - } + if (vertical) { + pen_x -= line_height + (breaktype == 2 ? space_after + (last ? 0 : space_before) : 0); + pen_y = last ? (breaktype != 0 ? 0 : pen_y) : (breaktype == 1 ? hanging : indent); - // Move pen based on indent and line height - line_height = (max_ascend - last_max_descend) * cur_lineheight; - if (last) { - pen_x = (linebreak || soft_wrap) ? 0 : pen_x + x_advance[i]; - } else { - pen_x = soft_wrap ? hanging : indent; - } - pen_y -= first_line ? 0 : line_height; - bottom -= line_height; - // Fill up y_pos based on calculated pen position - for (; glyph_counter < x_pos.size(); ++glyph_counter) { - y_pos.push_back(pen_y); - } - // Move pen_y further down based on paragraph spacing - // TODO: Add per string paragraph spacing - if (linebreak) { - pen_y -= space_after; - if (last) { - pen_y -= line_height; - bottom -= line_height; + } else { + pen_y -= line_height + (breaktype == 2 ? space_after + (last ? 0 : space_before) : 0); + pen_x = last ? (breaktype != 0 ? 0 : pen_x) : (breaktype == 1 ? hanging : indent); } - } - if (first_line) { - top_border = max_ascend; - top_bearing = top_border - max_top_extend; - } - // Reset flags and counters - last_max_descend = max_descend; - if (!last) { - max_ascend = 0; - max_descend = 0; - max_top_extend = 0; - max_bottom_extend = 0; + + last_max_descend = max_descend; + cur_linewidth = 0; + cur_left_bear = 0; + cur_right_bear = 0; first_line = false; cur_line++; first_char = true; + if (!last) { // We need this for calculating box dimensions + max_ascend = 0; + max_descend = 0; + max_top_extend = 0; + max_bottom_extend = 0; + } else if (breaktype == 2) { + pen_y -= (max_ascend - last_max_descend) * cur_lineheight + space_before; + max_bottom_extend = 0; + } } - } else { - // No line break - advance the pen - pen_x += x_advance[i]; - first_char = false; + if (breaktype != 0) { + last_line_start = y_pos.size(); + } + if (end == 0 || end == cur_shape.glyph_id.size()) { + break; + } + start = end; } } - height = top_border - pen_y - max_descend + space_after; - bottom_bearing = max_bottom_extend - max_descend; + + int32_t bottom = pen_y + max_descend - space_after; + height = top_border - bottom; + bottom_bearing = max_bottom_extend - max_descend + space_after; int max_width_ind = std::max_element(line_width.begin(), line_width.end()) - line_width.begin(); width = max_width < 0 ? line_width[max_width_ind] : max_width; if (cur_align == 1 || cur_align == 2) { @@ -286,13 +237,14 @@ bool HarfBuzzShaper::finish_string() { int32_t lwd = line_width[index]; x_pos[i] = cur_align == 1 ? x_pos[i] + width/2 - lwd/2 : x_pos[i] + width - lwd; } + pen_x = cur_align == 1 ? pen_x + width/2 - line_width.back()/2 : pen_x + width - line_width.back(); } - if (cur_align == 3) { + if (cur_align == 3 || cur_align == 4 || cur_align == 5) { std::vector n_stretches(line_width.size(), 0); std::vector no_stretch(line_width.size(), false); for (unsigned int i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; - no_stretch[index] = no_stretch[index] || must_break[i]; + no_stretch[index] = no_stretch[index] || index == line_width.size() - 1 || must_break[i]; if (may_stretch[i] && i-1 < x_pos.size() && index == line_id[i+1]) { n_stretches[index]++; } @@ -300,24 +252,49 @@ bool HarfBuzzShaper::finish_string() { int32_t cum_move = 0; for (unsigned int i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; - if (no_stretch[index]) continue; + int32_t lwd = line_width[index]; + if (no_stretch[index]) { + if (cur_align == 4) { + x_pos[i] = x_pos[i] + width/2 - lwd/2; + } else if (cur_align == 5) { + x_pos[i] = x_pos[i] + width - lwd; + } + continue; + } if (i == 0 || line_id[i-1] != index) { cum_move = 0; } x_pos[i] += cum_move; + if (n_stretches[index] == 0) { + if (cur_align == 4) { + x_pos[i] = x_pos[i] + width/2 - lwd/2; + } else if (cur_align == 5) { + x_pos[i] = x_pos[i] + width - lwd; + } + } if (may_stretch[i]) { cum_move += (width - line_width[index]) / n_stretches[index]; } } + pen_x += cum_move; + if (no_stretch.back() || n_stretches.back() == 0) { + if (cur_align == 4) { + pen_x += width/2 - line_width.back()/2; + } else if (cur_align == 5) { + pen_x += width - line_width.back(); + } + } + for (size_t i = 0; i < line_width.size(); ++i) { + if (!no_stretch[i] && n_stretches[i] != 0) line_width[i] = width; + } } - if (cur_align == 4) { + if (cur_align == 6) { std::vector n_glyphs(line_width.size(), 0); for (unsigned int i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; - if (!must_break[i] && i-1 < x_pos.size() && index == line_id[i+1]) { + if (!must_break[i] && (i == x_pos.size()-1 || index == line_id[i+1])) { n_glyphs[index]++; } - if (i == x_pos.size()-1) n_glyphs[index]++; } int32_t cum_move = 0; for (unsigned int i = 0; i < x_pos.size(); ++i) { @@ -328,51 +305,47 @@ bool HarfBuzzShaper::finish_string() { x_pos[i] += cum_move; cum_move += (width - line_width[index]) / (n_glyphs[index]-1); } + pen_x += cum_move; + for (size_t i = 0; i < line_width.size(); ++i) { + if (n_glyphs[i] != 0) line_width[i] = width; + } } double width_diff = width - line_width[max_width_ind]; if (cur_align == 1) { width_diff /= 2; } - left_bearing = cur_align == 0 ? *std::min_element(line_left_bear.begin(), line_left_bear.end()) : line_left_bear[max_width_ind] + width_diff; - right_bearing = cur_align == 2 ? *std::min_element(line_right_bear.begin(), line_right_bear.end()) : line_right_bear[max_width_ind] + width_diff; - if (cur_hjust != 0.0) { - left_border = - cur_hjust * width; - pen_x += left_border; - for (unsigned int i = 0; i < x_pos.size(); ++i) { - x_pos[i] += left_border; - } + left_bearing = cur_align == 0 || cur_align == 3 || cur_align == 5 ? *std::min_element(line_left_bear.begin(), line_left_bear.end()) : line_left_bear[max_width_ind] + width_diff; + right_bearing = cur_align == 2 || cur_align == 4 || cur_align == 5 ? *std::min_element(line_right_bear.begin(), line_right_bear.end()) : line_right_bear[max_width_ind] + width_diff; + + left_border = - cur_hjust * width; + pen_x += left_border; + for (unsigned int i = 0; i < x_pos.size(); ++i) { + x_pos[i] += left_border; } - if (cur_vjust != 1.0) { - int32_t just_height = top_border - pen_y; - for (unsigned int i = 0; i < x_pos.size(); ++i) { - y_pos[i] += - pen_y - cur_vjust * just_height; - } - top_border += - pen_y - cur_vjust * just_height; - pen_y += - pen_y - cur_vjust * just_height; + for (unsigned int i = 0; i < x_pos.size(); ++i) { + y_pos[i] += - bottom - cur_vjust * height; } + top_border += - bottom - cur_vjust * height; + pen_y += - bottom - cur_vjust * height; return true; } void HarfBuzzShaper::reset() { glyph_id.clear(); glyph_cluster.clear(); + fontfile.clear(); + fontindex.clear(); + fontsize.clear(); string_id.clear(); x_pos.clear(); y_pos.clear(); - x_mid.clear(); - x_advance.clear(); - x_offset.clear(); - left_bear.clear(); - right_bear.clear(); - top_extend.clear(); - bottom_extend.clear(); + advance.clear(); + ascender.clear(); + descender.clear(); line_left_bear.clear(); line_right_bear.clear(); line_width.clear(); line_id.clear(); - ascenders.clear(); - descenders.clear(); - may_break.clear(); must_break.clear(); may_stretch.clear(); shape_infos.clear(); @@ -387,16 +360,33 @@ void HarfBuzzShaper::reset() { left_bearing = 0; right_bearing = 0; + top_bearing = 0; + bottom_bearing = 0; width = 0; height = 0; top_border = 0; left_border = 0; cur_string = 0; + + error_code = 0; + + cur_lineheight = 0.0; + cur_align = 0; + cur_hjust = 0.0; + cur_vjust = 0.0; + cur_res = 0.0; + top = 0; + bottom = 0; + max_width = 0; + indent = 0; + hanging = 0; + space_before = 0; + space_after = 0; } -ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_info, - double size, double res) { +ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings& font_info, + double size, double res, double tracking) { int n_features = font_info.n_features; std::vector features(n_features); ShapeInfo text_run; @@ -406,6 +396,7 @@ ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_i run_id.font.assign(font_info.file); run_id.index = font_info.index; run_id.size = size * res; + run_id.tracking = tracking; if (shape_cache.get(run_id, text_run)) { return text_run; } @@ -424,6 +415,26 @@ ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_i int n_chars = 0; const uint32_t* utc_string = utf_converter.convert_to_ucs(string, n_chars); + if (n_chars == 0) { + text_run.ascenders.push_back(0); + text_run.descenders.push_back(0); +#if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 +#else + int error = 0; + hb_font_t *font = hb_ft_font_create(get_cached_face(font_info.file, font_info.index, size, res, &error), NULL); + if (error != 0) { + Rprintf("Failed to get face: %s, %i\n", font_info.file, font_info.index); + error_code = error; + } else { + hb_font_extents_t fextent; + hb_font_get_h_extents(font, &fextent); + text_run.ascenders[0] = fextent.ascender; + text_run.descenders[0] = fextent.descender; + } +#endif + return text_run; + } + std::vector embeddings = {}; if (n_chars > 1) { @@ -462,7 +473,8 @@ ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_i size_t embedding_start = 0; for (size_t i = 1; i <= embeddings.size(); ++i) { if (i == embeddings.size() || embeddings[i] != embeddings[i - 1]) { - shape_embedding(utc_string, embedding_start, i, n_chars, size, res, features, embeddings[embedding_start] == 2, text_run); + bool success = shape_embedding(utc_string, embedding_start, i, n_chars, size, res, tracking, features, embeddings[embedding_start] == 2, text_run); + if (!success) return ShapeInfo(); embedding_start = i; } } @@ -474,73 +486,9 @@ ShapeInfo HarfBuzzShaper::shape_text_run(const char* string, FontSettings font_i return text_run; } -bool HarfBuzzShaper::shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars) { - hb_shape(font, buffer, NULL, 0); - - unsigned int n_glyphs = 0; - hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); - hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - - if (n_glyphs == 0) return true; - - glyph_id.reserve(n_glyphs); - glyph_cluster.reserve(n_glyphs); - string_id.reserve(n_glyphs); - x_pos.reserve(n_glyphs); - y_pos.reserve(n_glyphs); - -#if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 - ascend = 0; - descend = 0; -#else - hb_font_extents_t extent; - hb_font_get_h_extents(font, &extent); - - ascend = extent.ascender; - descend = extent.descender; -#endif - - for (unsigned int i = 0; i < n_glyphs; ++i) { - unsigned int cluster = glyph_info[i].cluster; - glyph_cluster.push_back(cluster); - glyph_id.push_back(glyph_info[i].codepoint); - - if (cluster < n_chars) { - may_break.push_back(glyph_is_breaker(string[cluster])); - must_break.push_back(glyph_is_linebreak(string[cluster])); - may_stretch.push_back(glyph_may_stretch(string[cluster])); - } else { - may_break.push_back(false); - must_break.push_back(false); - may_stretch.push_back(false); - } - - string_id.push_back(cur_string); - - hb_glyph_extents_t extent; - hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); - - int32_t x_off = glyph_pos[i].x_offset; - int32_t x_adv = glyph_pos[i].x_advance; - x_advance.push_back(x_adv + cur_tracking); - left_bear.push_back(extent.x_bearing); - right_bear.push_back(x_adv - (extent.x_bearing + extent.width)); - top_extend.push_back(extent.y_bearing); - bottom_extend.push_back(extent.height + extent.y_bearing); - ascenders.push_back(ascend); - descenders.push_back(descend); - if (i == 0) { - x_offset.push_back(0); - } else { - x_offset.push_back(x_off); - } - } - return true; -} - bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, - double size, double res, + double size, double res, double tracking, std::vector& features, bool emoji, ShapeInfo& shape_info) { unsigned int embedding_size = end - start; @@ -561,9 +509,10 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, } if (shape_info.fallback_scaling.empty()) { - double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; - scaling *= family_scaling(face->family_name); - shape_info.fallback_scaling.push_back(scaling); + double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 * res / 72.0 / face->size->metrics.height; + double fscaling = family_scaling(face->family_name); + shape_info.fallback_scaling.push_back(scaling * fscaling); + shape_info.fallback_size.push_back(size * fscaling); } if (emoji) { @@ -577,9 +526,10 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, } if (shape_info.fallback_scaling.size() == 1) { - double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; - scaling *= family_scaling(face->family_name); - shape_info.fallback_scaling.push_back(scaling); + double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 * res / 72.0 / face->size->metrics.height; + double fscaling = family_scaling(face->family_name); + shape_info.fallback_scaling.push_back(scaling * fscaling); + shape_info.fallback_size.push_back(size * fscaling); } } @@ -589,10 +539,11 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, hb_buffer_reset(buffer); hb_buffer_add_utf32(buffer, string, string_length, start, embedding_size); hb_buffer_guess_segment_properties(buffer); + hb_glyph_info_t *glyph_info = NULL; + glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); hb_shape(font, buffer, features.data(), features.size()); - hb_glyph_info_t *glyph_info = NULL; hb_glyph_position_t *glyph_pos = NULL; glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); @@ -618,12 +569,10 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, if (!needs_fallback) { // Short route - use existing shaping glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); - fill_glyph_info(string, start, embedding_size, shape_info); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, start, shape_info, tracking); + fill_glyph_info(string, start + embedding_size, shape_info); hb_font_destroy(font); return true; - } else { - } hb_font_destroy(font); @@ -704,8 +653,8 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); - fill_glyph_info(string, start + text_run_start, i - text_run_start, shape_info); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, start + text_run_start, shape_info, tracking); + fill_glyph_info(string, start + i, shape_info); hb_font_destroy(font); if (i < embedding_size) { @@ -738,8 +687,8 @@ bool HarfBuzzShaper::shape_embedding(const uint32_t* string, unsigned start, hb_shape(font, buffer, features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &n_glyphs); glyph_pos = hb_buffer_get_glyph_positions(buffer, &n_glyphs); - fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, shape_info); - fill_glyph_info(string, start + i, text_run_end - i, shape_info); + fill_shape_info(glyph_info, glyph_pos, n_glyphs, font, current_font, start + i, shape_info, tracking); + fill_glyph_info(string, start + text_run_end, shape_info); hb_font_destroy(font); if (i > 0) { @@ -776,9 +725,10 @@ hb_font_t* HarfBuzzShaper::load_fallback(unsigned int font, const uint32_t* str } if (font >= shape_info.fallback_scaling.size()) { - double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 / face->size->metrics.height; - scaling *= family_scaling(face->family_name); - shape_info.fallback_scaling.push_back(scaling); + double scaling = FT_IS_SCALABLE(face) ? -1 : size * 64.0 * res / 72.0 / face->size->metrics.height; + double fscaling = family_scaling(face->family_name); + shape_info.fallback_scaling.push_back(scaling * fscaling); + shape_info.fallback_size.push_back(size * fscaling); } return hb_ft_font_create(face, NULL); @@ -844,10 +794,14 @@ void HarfBuzzShaper::annotate_fallbacks(unsigned int font, unsigned int offset, void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, unsigned int n_glyphs, hb_font_t* font, - unsigned int font_id, ShapeInfo& shape_info) { + unsigned int font_id, + unsigned int cluster_offset, + ShapeInfo& shape_info, int32_t tracking) { double scaling = shape_info.fallback_scaling[font_id]; if (scaling < 0) scaling = 1.0; + tracking *= shape_info.fallback_size[font_id] / 1000; + #if HB_VERSION_MAJOR < 2 && HB_VERSION_MINOR < 2 ascend = 0; @@ -882,7 +836,7 @@ void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, shape_info.glyph_cluster.push_back(glyph_info[i].cluster); shape_info.x_offset.push_back(glyph_pos[i].x_offset * scaling); shape_info.y_offset.push_back(glyph_pos[i].y_offset * scaling); - shape_info.x_advance.push_back(glyph_pos[i].x_advance * scaling); + shape_info.x_advance.push_back(glyph_pos[i].x_advance * scaling + tracking); shape_info.y_advance.push_back(glyph_pos[i].y_advance * scaling); hb_font_get_glyph_extents(font, glyph_info[i].codepoint, &extent); @@ -891,19 +845,19 @@ void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, shape_info.width.push_back(extent.width * scaling); shape_info.height.push_back(extent.height * scaling); - shape_info.ascenders.push_back(ascend); - shape_info.descenders.push_back(descend); + shape_info.ascenders.push_back(ascend * scaling); + shape_info.descenders.push_back(descend * scaling); shape_info.font.push_back(font_id); } } -void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, unsigned start, - unsigned length, ShapeInfo& shape_info) { +void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, unsigned end, + ShapeInfo& shape_info) { for (size_t i = shape_info.must_break.size(); i < shape_info.glyph_cluster.size(); ++i) { int32_t cluster = shape_info.glyph_cluster[i]; - if (cluster < length) { + if (cluster < end) { shape_info.must_break.push_back(glyph_is_linebreak(string[cluster])); shape_info.may_break.push_back(glyph_is_breaker(string[cluster])); shape_info.may_stretch.push_back(glyph_may_stretch(string[cluster])); @@ -915,4 +869,78 @@ void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, unsigned start, } } +size_t HarfBuzzShaper::fill_out_width(size_t from, int32_t max, + size_t shape, int& breaktype) { + int32_t w = 0; + size_t last_possible_break = from; + bool has_break = false; + breaktype = 0; + if (shape_infos[shape].ltr) { + if (max < 0) return shape_infos[shape].glyph_id.size(); + for (size_t i = from; i < shape_infos[shape].glyph_id.size(); ++i) { + if (shape_infos[shape].must_break[i]) { + breaktype = 2; + return i + 1; + } + if (shape_infos[shape].may_break[i]) { + last_possible_break = i; + has_break = true; + } + w += shape_infos[shape].x_advance[i]; + if (w > max) { + breaktype = 1; + return has_break ? last_possible_break + 1 : i; + } + } + size_t next_shape = shape + 1; + while (next_shape < shape_infos.size()) { + for (size_t i = 0; i < shape_infos[next_shape].glyph_id.size(); ++i) { + if (shape_infos[next_shape].must_break[i] || shape_infos[next_shape].may_break[i]) { + return shape_infos[shape].glyph_id.size(); + } + w += shape_infos[next_shape].x_advance[i]; + if (w > max) { + breaktype = has_break ? 1 : 0; + return has_break ? last_possible_break + 1 : shape_infos[shape].glyph_id.size(); + } + } + next_shape++; + } + last_possible_break = shape_infos[shape].glyph_id.size(); + } else { + if (max < 0) return 0; + for (size_t i = from - 1; i >= 0; --i) { + if (shape_infos[shape].must_break[i]) { + breaktype = 2; + return i + 1; + } + if (shape_infos[shape].may_break[i]) { + last_possible_break = i; + has_break = true; + } + w += shape_infos[shape].x_advance[i]; + if (w > max) { + breaktype = 1; + return has_break ? last_possible_break : i + 1; + } + } + size_t next_shape = shape + 1; + while (next_shape < shape_infos.size()) { + for (size_t i = shape_infos[next_shape].glyph_id.size() - 1; i >= 0; --i) { + if (shape_infos[next_shape].must_break[i] || shape_infos[shape].may_break[i]) { + return 0; + } + w += shape_infos[next_shape].x_advance[i]; + if (w > max) { + breaktype = has_break ? 1 : 0; + return has_break ? last_possible_break : 0; + } + } + next_shape++; + } + last_possible_break = 0; + } + return last_possible_break; +} + #endif diff --git a/src/string_shape.h b/src/string_shape.h index c293c86..9d2a26e 100644 --- a/src/string_shape.h +++ b/src/string_shape.h @@ -16,24 +16,28 @@ struct ShapeID { std::string font; unsigned int index; double size; + double tracking; - inline ShapeID() : string(""), font(""), index(0), size(0.0) {} - inline ShapeID(std::string _string, std::string _font, unsigned int _index, double _size) : + inline ShapeID() : string(""), font(""), index(0), size(0.0), tracking(0.0) {} + inline ShapeID(std::string _string, std::string _font, unsigned int _index, double _size, double _tracking) : string(_string), font(_font), index(_index), - size(_size) {} + size(_size), + tracking(_tracking) {} inline ShapeID(const ShapeID& shape) : string(shape.string), font(shape.font), index(shape.index), - size(shape.size) {} + size(shape.size), + tracking(shape.tracking) {} inline bool operator==(const ShapeID &other) const { return (index == other.index && size == other.size && string == other.string && - font == other.font); + font == other.font) && + tracking == other.tracking; } }; struct ShapeInfo { @@ -54,6 +58,7 @@ struct ShapeInfo { std::vector may_stretch; std::vector font; std::vector fallbacks; + std::vector fallback_size; std::vector fallback_scaling; bool ltr; }; @@ -64,7 +69,8 @@ struct hash { return std::hash()(x.string) ^ std::hash()(x.font) ^ std::hash()(x.index) ^ - std::hash()(x.size); + std::hash()(x.size) ^ + std::hash()(x.tracking); } }; } @@ -72,6 +78,19 @@ struct hash { class HarfBuzzShaper { public: HarfBuzzShaper() : + // Public + glyph_id(), + glyph_cluster(), + fontfile(), + fontindex(), + fontsize(), + string_id(), + x_pos(), + y_pos(), + advance(), + ascender(), + descender(), + must_break(), width(0), height(0), left_bearing(0), @@ -83,12 +102,15 @@ class HarfBuzzShaper { pen_x(0), pen_y(0), error_code(0), + // Private cur_lineheight(0.0), cur_align(0), cur_string(0), cur_hjust(0.0), cur_vjust(0.0), cur_res(0.0), + shape_infos(), + may_stretch(), line_left_bear(), line_right_bear(), line_width(), @@ -109,13 +131,18 @@ class HarfBuzzShaper { hb_buffer_destroy(buffer); }; - static std::vector glyph_id; - static std::vector glyph_cluster; - static std::vector string_id; - static std::vector x_pos; - static std::vector y_pos; - static std::vector x_mid; - static std::vector y_mid; + std::vector glyph_id; + std::vector glyph_cluster; + std::vector fontfile; + std::vector fontindex; + std::vector fontsize; + std::vector string_id; + std::vector x_pos; + std::vector y_pos; + std::vector advance; + std::vector ascender; + std::vector descender; + std::vector must_break; int32_t width; int32_t height; int32_t left_bearing; @@ -126,21 +153,21 @@ class HarfBuzzShaper { int32_t left_border; int32_t pen_x; int32_t pen_y; - static std::vector shape_infos; int error_code; - bool shape_string(const char* string, const char* fontfile, int index, + bool shape_string(const char* string, FontSettings& font_info, double size, double res, double lineheight, int align, double hjust, double vjust, double width, double tracking, double ind, double hang, double before, - double after); - bool add_string(const char* string, const char* fontfile, int index, - double size, double tracking); + double after, bool spacer); + bool add_string(const char* string, FontSettings& font_info, + double size, double tracking, bool spacer); + bool add_spacer(double height, double width); bool finish_string(); - ShapeInfo shape_text_run(const char* string, FontSettings font_info, double size, - double res); + ShapeInfo shape_text_run(const char* string, FontSettings& font_info, double size, + double res, double tracking); private: static UTF_UCS utf_converter; @@ -153,18 +180,8 @@ class HarfBuzzShaper { double cur_hjust; double cur_vjust; double cur_res; - double cur_tracking; - static std::vector x_advance; - static std::vector x_offset; - static std::vector left_bear; - static std::vector right_bear; - static std::vector top_extend; - static std::vector bottom_extend; - static std::vector ascenders; - static std::vector descenders; - static std::vector may_break; - static std::vector must_break; - static std::vector may_stretch; + std::vector shape_infos; + std::vector may_stretch; std::vector line_left_bear; std::vector line_right_bear; std::vector line_width; @@ -181,11 +198,10 @@ class HarfBuzzShaper { int32_t space_after; void reset(); - bool shape_glyphs(hb_font_t *font, const uint32_t *string, unsigned int n_chars); bool shape_embedding(const uint32_t* string, unsigned start, unsigned end, unsigned int string_length, double size, double res, - std::vector& features, bool emoji, - ShapeInfo& shape_info); + double tracking, std::vector& features, + bool emoji, ShapeInfo& shape_info); hb_font_t* load_fallback(unsigned int font, const uint32_t* string, unsigned int start, unsigned int end, int& error, double size, double res, bool& new_added, @@ -199,9 +215,10 @@ class HarfBuzzShaper { unsigned int string_offset); void fill_shape_info(hb_glyph_info_t* glyph_info, hb_glyph_position_t* glyph_pos, unsigned int n_glyphs, hb_font_t* font, unsigned int font_id, - ShapeInfo& shape_info); - void fill_glyph_info(const uint32_t* string, unsigned start, unsigned length, - ShapeInfo& shape_info); + unsigned int cluster_offset, ShapeInfo& shape_info, + int32_t tracking); + void fill_glyph_info(const uint32_t* string, unsigned end, ShapeInfo& shape_info); + size_t fill_out_width(size_t from, int32_t max, size_t shape, int& breaktype); inline double family_scaling(const char* family) { if (strcmp("Apple Color Emoji", family) == 0) { @@ -213,45 +230,48 @@ class HarfBuzzShaper { } inline bool glyph_is_linebreak(int32_t id) { switch (id) { - case 10: return true; - case 11: return true; - case 12: return true; - case 13: return true; - case 133: return true; - case 8232: return true; - case 8233: return true; + case 10: return true; // Line feed + case 11: return true; // Vertical tab + case 12: return true; // Form feed + case 13: return true; // Cariage return + case 133: return true; // Next line + case 8232: return true; // Line Separator + case 8233: return true; // Paragraph Separator } return false; } inline bool glyph_is_breaker(int32_t id) { switch (id) { - case 9: return true; - case 32: return true; - case 5760: return true; - case 6158: return true; - case 8192: return true; - case 8193: return true; - case 8194: return true; - case 8195: return true; - case 8196: return true; - case 8197: return true; - case 8198: return true; - case 8200: return true; - case 8201: return true; - case 8202: return true; - case 8203: return true; - case 8204: return true; - case 8205: return true; - case 8287: return true; - case 12288: return true; + case 9: return true; // Horizontal tab + case 32: return true; // Space + case 45: return true; // Hyphen + case 173: return true; // Soft hyphen + case 5760: return true; // Ogham Space Mark + case 6158: return true; // Mongolian Vowel Separator + case 8192: return true; // En Quad + case 8193: return true; // Em Quad + case 8194: return true; // En Space + case 8195: return true; // Em Space + case 8196: return true; // Three-Per-Em Space + case 8197: return true; // Four-Per-Em Space + case 8198: return true; // Six-Per-Em Space + case 8200: return true; // Punctuation Space + case 8201: return true; // Thin Space + case 8202: return true; // Hair Space + case 8203: return true; // Zero Width Space + case 8204: return true; // Zero Width Non-Joiner + case 8205: return true; // Zero Width Joiner + case 8208: return true; // Hyphen + case 8287: return true; // Medium Mathematical Space + case 12288: return true; // Ideographic Space } return false; } inline bool glyph_may_stretch(int32_t id) { switch (id) { - case 32: return true; + case 32: return true; // Space } return false; } From e160db71d32dc0ef43d144481ccec676f2f957bd Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Tue, 16 Apr 2024 21:36:35 +0200 Subject: [PATCH 03/11] fix a few compiler warnings --- src/string_metrics.cpp | 2 +- src/string_shape.cpp | 4 ++-- src/string_shape.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/string_metrics.cpp b/src/string_metrics.cpp index d252bb7..18ed6d2 100644 --- a/src/string_metrics.cpp +++ b/src/string_metrics.cpp @@ -326,7 +326,7 @@ int ts_string_shape(const char* string, FontSettings font_info, double size, fallback_scaling.clear(); int32_t x = 0; int32_t y = 0; - for (int i = 0; i < n_glyphs; ++i) { + for (size_t i = 0; i < n_glyphs; ++i) { loc.push_back({ double(x + string_shape.x_offset[i]) / 64.0, double(y + string_shape.y_offset[i]) / 64.0 diff --git a/src/string_shape.cpp b/src/string_shape.cpp index 84a2382..5f1971a 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -243,7 +243,7 @@ bool HarfBuzzShaper::finish_string() { std::vector n_stretches(line_width.size(), 0); std::vector no_stretch(line_width.size(), false); for (unsigned int i = 0; i < x_pos.size(); ++i) { - int index = line_id[i]; + size_t index = line_id[i]; no_stretch[index] = no_stretch[index] || index == line_width.size() - 1 || must_break[i]; if (may_stretch[i] && i-1 < x_pos.size() && index == line_id[i+1]) { n_stretches[index]++; @@ -853,7 +853,7 @@ void HarfBuzzShaper::fill_shape_info(hb_glyph_info_t* glyph_info, } } -void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, unsigned end, +void HarfBuzzShaper::fill_glyph_info(const uint32_t* string, int end, ShapeInfo& shape_info) { for (size_t i = shape_info.must_break.size(); i < shape_info.glyph_cluster.size(); ++i) { int32_t cluster = shape_info.glyph_cluster[i]; diff --git a/src/string_shape.h b/src/string_shape.h index 9d2a26e..e83f508 100644 --- a/src/string_shape.h +++ b/src/string_shape.h @@ -217,7 +217,7 @@ class HarfBuzzShaper { unsigned int n_glyphs, hb_font_t* font, unsigned int font_id, unsigned int cluster_offset, ShapeInfo& shape_info, int32_t tracking); - void fill_glyph_info(const uint32_t* string, unsigned end, ShapeInfo& shape_info); + void fill_glyph_info(const uint32_t* string, int end, ShapeInfo& shape_info); size_t fill_out_width(size_t from, int32_t max, size_t shape, int& breaktype); inline double family_scaling(const char* family) { From 898cc5341cc6fbf1cc1cff551544b60be3469f75 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Tue, 16 Apr 2024 21:58:10 +0200 Subject: [PATCH 04/11] depend on dev systemfonts --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 641432c..d150145 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,3 +32,4 @@ Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 SystemRequirements: freetype2, harfbuzz, fribidi +Remotes: r-lib/systemfonts From e430da486b58f4e09f3d5daa0b7511a520e230a0 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Thu, 18 Apr 2024 09:55:30 +0200 Subject: [PATCH 05/11] Fix docs for shape_text --- R/shape_text.R | 11 ++++++++--- man/shape_text.Rd | 26 +++++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/R/shape_text.R b/R/shape_text.R index 882dc43..786ec64 100644 --- a/R/shape_text.R +++ b/R/shape_text.R @@ -12,12 +12,17 @@ #' @param strings A character vector of strings to shape #' @param id A vector grouping the strings together. If strings share an id the #' shaping will continue between strings -#' @inheritParams systemfonts::font_info +#' @inheritParams systemfonts::match_fonts +#' @param features A [systemfonts::font_feature()] object or a list of them, +#' giving the OpenType font features to set +#' @param size The size in points to use for the font +#' @param res The resolution to use when doing the shaping. Should optimally +#' match the resolution used when rendering the glyphs. #' @param lineheight A multiplier for the lineheight #' @param align Within text box alignment, either `'left'`, `'center'`, `'right'`, -#' `'justified'`, or `'distributed'` +#' `'justified-left'`, `'justified-right'`, `'justified-center'`, or `'distributed'` #' @param hjust,vjust The justification of the textbox surrounding the text -#' @param width The requested with of the string in inches. Setting this to +#' @param max_width The requested with of the string in inches. Setting this to #' something other than `NA` will turn on word wrapping. #' @param tracking Tracking of the glyphs (space adjustment) measured in 1/1000 #' em. diff --git a/man/shape_text.Rd b/man/shape_text.Rd index 1a7f458..2e66906 100644 --- a/man/shape_text.Rd +++ b/man/shape_text.Rd @@ -39,20 +39,36 @@ shaping will continue between strings} \item{italic}{logical indicating the font slant} -\item{width}{The requested with of the string in inches. Setting this to -something other than \code{NA} will turn on word wrapping.} +\item{weight}{The weight to query for, either in numbers (\code{0}, \code{100}, \code{200}, +\code{300}, \code{400}, \code{500}, \code{600}, \code{700}, \code{800}, or \code{900}) or strings (\code{"undefined"}, +\code{"thin"}, \code{"ultralight"}, \code{"light"}, \code{"normal"}, \code{"medium"}, \code{"semibold"}, +\code{"bold"}, \code{"ultrabold"}, or \code{"heavy"}). \code{NA} will be interpreted as +\code{"undefined"}/\code{0}} + +\item{width}{The width to query for either in numbers (\code{0}, \code{1}, \code{2}, +\code{3}, \code{4}, \code{5}, \code{6}, \code{7}, \code{8}, or \code{9}) or strings (\code{"undefined"}, +\code{"ultracondensed"}, \code{"extracondensed"}, \code{"condensed"}, \code{"semicondensed"}, +\code{"normal"}, \code{"semiexpanded"}, \code{"expanded"}, \code{"extraexpanded"}, or +\code{"ultraexpanded"}). \code{NA} will be interpreted as \code{"undefined"}/\code{0}} + +\item{features}{A \code{\link[systemfonts:font_feature]{systemfonts::font_feature()}} object or a list of them, +giving the OpenType font features to set} -\item{size}{The pointsize of the font to use for size related measures} +\item{size}{The size in points to use for the font} -\item{res}{The ppi of the size related mesures} +\item{res}{The resolution to use when doing the shaping. Should optimally +match the resolution used when rendering the glyphs.} \item{lineheight}{A multiplier for the lineheight} \item{align}{Within text box alignment, either \code{'left'}, \code{'center'}, \code{'right'}, -\code{'justified'}, or \code{'distributed'}} +\code{'justified-left'}, \code{'justified-right'}, \code{'justified-center'}, or \code{'distributed'}} \item{hjust, vjust}{The justification of the textbox surrounding the text} +\item{max_width}{The requested with of the string in inches. Setting this to +something other than \code{NA} will turn on word wrapping.} + \item{tracking}{Tracking of the glyphs (space adjustment) measured in 1/1000 em.} From 3fe4e21cfa19d3a19a14ddb44b3bee68857636a3 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Thu, 18 Apr 2024 09:55:48 +0200 Subject: [PATCH 06/11] Remove all use of deprecated match_font --- NAMESPACE | 1 - R/font_features.R | 21 ++++++++------------- R/shape_text.R | 20 ++++++++------------ 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4d9a3ee..5d9c6c5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,7 +5,6 @@ export(shape_text) export(text_width) importFrom(lifecycle,deprecated) importFrom(systemfonts,font_feature) -importFrom(systemfonts,match_font) importFrom(systemfonts,match_fonts) importFrom(systemfonts,system_fonts) useDynLib(textshaping, .registration = TRUE) diff --git a/R/font_features.R b/R/font_features.R index 8961aa0..8e165dc 100644 --- a/R/font_features.R +++ b/R/font_features.R @@ -9,7 +9,7 @@ #' @return A list with an element for each of the input fonts containing the #' supported feature tags for that font. #' -#' @importFrom systemfonts match_font +#' @importFrom systemfonts match_fonts #' @export #' #' @examples @@ -24,18 +24,13 @@ get_font_features <- function(family = '', italic = FALSE, bold = FALSE, path = NULL, index = 0) { if (is.null(path)) { full_length <- max(length(family), length(italic), length(bold)) - if (full_length == 1) { - loc <- match_font(family, italic, bold) - path <- loc$path - index <- loc$index - } else { - family <- rep_len(family, full_length) - italic <- rep_len(italic, full_length) - bold <- rep_len(bold, full_length) - loc <- Map(match_font, family = family, italic = italic, bold = bold) - path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) - index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) - } + fonts <- match_fonts( + rep_len(family, full_length), + rep_len(italic, full_length), + ifelse(rep_len(bold, full_length), "bold", "normal") + ) + path <- fonts$path + index <- fonts$index } else { full_length <- max(length(path), length(index)) path <- rep_len(path, full_length) diff --git a/R/shape_text.R b/R/shape_text.R index 786ec64..842af5a 100644 --- a/R/shape_text.R +++ b/R/shape_text.R @@ -184,6 +184,7 @@ shape_text <- function(strings, id = NULL, family = '', italic = FALSE, #' provided `res` value to convert it into absolute values. #' #' @export +#' @importFrom systemfonts match_fonts #' #' @examples #' strings <- c('A short string', 'A very very looong string') @@ -194,18 +195,13 @@ text_width <- function(strings, family = '', italic = FALSE, bold = FALSE, index = 0) { n_strings <- length(strings) if (is.null(path)) { - if (all(c(length(family), length(italic), length(bold)) == 1)) { - loc <- systemfonts::match_font(family, italic, bold) - path <- loc$path - index <- loc$index - } else { - family <- rep_len(family, n_strings) - italic <- rep_len(italic, n_strings) - bold <- rep_len(bold, n_strings) - loc <- Map(systemfonts::match_font, family = family, italic = italic, bold = bold) - path <- vapply(loc, `[[`, character(1L), 1, USE.NAMES = FALSE) - index <- vapply(loc, `[[`, integer(1L), 2, USE.NAMES = FALSE) - } + fonts <- match_fonts( + rep_len(family, n_strings), + rep_len(italic, n_strings), + ifelse(rep_len(bold, n_strings), "bold", "normal") + ) + path <- fonts$path + index <- fonts$index } else { if (!all(c(length(path), length(index)) == 1)) { path <- rep_len(path, n_strings) From 3a22edbe6ec898fd6bc3379836a7843a680fddb5 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Thu, 18 Apr 2024 10:01:30 +0200 Subject: [PATCH 07/11] fix signedness comparison --- src/string_shape.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/string_shape.cpp b/src/string_shape.cpp index 5f1971a..f94bafd 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -232,7 +232,7 @@ bool HarfBuzzShaper::finish_string() { int max_width_ind = std::max_element(line_width.begin(), line_width.end()) - line_width.begin(); width = max_width < 0 ? line_width[max_width_ind] : max_width; if (cur_align == 1 || cur_align == 2) { - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; int32_t lwd = line_width[index]; x_pos[i] = cur_align == 1 ? x_pos[i] + width/2 - lwd/2 : x_pos[i] + width - lwd; @@ -242,15 +242,15 @@ bool HarfBuzzShaper::finish_string() { if (cur_align == 3 || cur_align == 4 || cur_align == 5) { std::vector n_stretches(line_width.size(), 0); std::vector no_stretch(line_width.size(), false); - for (unsigned int i = 0; i < x_pos.size(); ++i) { - size_t index = line_id[i]; + for (size_t i = 0; i < x_pos.size(); ++i) { + int index = line_id[i]; no_stretch[index] = no_stretch[index] || index == line_width.size() - 1 || must_break[i]; if (may_stretch[i] && i-1 < x_pos.size() && index == line_id[i+1]) { n_stretches[index]++; } } int32_t cum_move = 0; - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; int32_t lwd = line_width[index]; if (no_stretch[index]) { @@ -290,14 +290,14 @@ bool HarfBuzzShaper::finish_string() { } if (cur_align == 6) { std::vector n_glyphs(line_width.size(), 0); - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; if (!must_break[i] && (i == x_pos.size()-1 || index == line_id[i+1])) { n_glyphs[index]++; } } int32_t cum_move = 0; - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { int index = line_id[i]; if (i == 0 || line_id[i-1] != index) { cum_move = 0; @@ -319,10 +319,10 @@ bool HarfBuzzShaper::finish_string() { left_border = - cur_hjust * width; pen_x += left_border; - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { x_pos[i] += left_border; } - for (unsigned int i = 0; i < x_pos.size(); ++i) { + for (size_t i = 0; i < x_pos.size(); ++i) { y_pos[i] += - bottom - cur_vjust * height; } top_border += - bottom - cur_vjust * height; From dd40b93c7060697b20332b386fc3be163a131319 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Thu, 18 Apr 2024 10:05:59 +0200 Subject: [PATCH 08/11] fix compilation on old windows --- src/string_shape.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/string_shape.cpp b/src/string_shape.cpp index f94bafd..aceb8bf 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -52,7 +52,7 @@ bool HarfBuzzShaper::add_string(const char* string, FontSettings& font_info, bool HarfBuzzShaper::add_spacer(double height, double width) { width *= 64.0 / 72.0; double space_height = height * 64.0 * cur_res / 72.0; - shape_infos.push_back({ + ShapeInfo info = { {0}, // glyph_id {0}, // glyph_cluster {int32_t(width)}, // x_advance @@ -73,7 +73,8 @@ bool HarfBuzzShaper::add_spacer(double height, double width) { {height}, // fallback_size {-1}, // fallback_scaling true // ltr - }); + }; + shape_infos.push_back(info); return true; } From 45e7a3ea4afe9edcf8a5379eaddded8d41c1c209 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Thu, 18 Apr 2024 10:25:36 +0200 Subject: [PATCH 09/11] once more for windows --- src/string_shape.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/string_shape.cpp b/src/string_shape.cpp index aceb8bf..2589d57 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -52,6 +52,7 @@ bool HarfBuzzShaper::add_string(const char* string, FontSettings& font_info, bool HarfBuzzShaper::add_spacer(double height, double width) { width *= 64.0 / 72.0; double space_height = height * 64.0 * cur_res / 72.0; + FontSettings dummy_font = {"", 0, NULL, 0}; ShapeInfo info = { {0}, // glyph_id {0}, // glyph_cluster @@ -69,7 +70,7 @@ bool HarfBuzzShaper::add_spacer(double height, double width) { {false}, // must_break {false}, // may_stretch {0}, // font - {{"", 0, NULL, 0}}, // fallbacks + {dummy_font}, // fallbacks {height}, // fallback_size {-1}, // fallback_scaling true // ltr From 12be50dea3710ef625272e3808e768bcc4672fc1 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Wed, 24 Apr 2024 15:21:34 +0200 Subject: [PATCH 10/11] Fix rounding error bug when using width output as new width during shaping --- src/string_shape.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string_shape.cpp b/src/string_shape.cpp index 2589d57..3c37653 100644 --- a/src/string_shape.cpp +++ b/src/string_shape.cpp @@ -18,7 +18,7 @@ bool HarfBuzzShaper::shape_string(const char* string, FontSettings& font_info, double after, bool spacer) { reset(); - max_width = width; + max_width = width + 1; // To prevent rounding errors indent = ind; hanging = hang; space_before = before; From 08771eab4e7ddaa8c14daca961d1cf75aef87409 Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Mon, 29 Apr 2024 13:40:36 +0200 Subject: [PATCH 11/11] update news, but too much to write --- NEWS.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index a5e2982..323751e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,7 @@ # textshaping (development version) -* Fixed height calculation in `shape_text()` to avoid an extra line of height -* Fixed width calculation in `shape_text()` to ignore width of terminal - line-break characters -* Added `'justified'` and `'distributed'` options to `align` in `shape_text()` - to either expand spaces to fit line width to full width, or distribute all - glyphs to fit line width to full width +* Full rewrite of `shape_text()` to allow proper font-fallback, bidi text + support, support for font-features, spacers, new align settings, etc. # textshaping 0.3.7