void text_layout::add_line(text_line & line) { if (lines_.empty()) { line.set_first_line(true); } height_ += line.height(); glyphs_count_ += line.size(); width_ = std::max(width_, line.width()); lines_.push_back(line); }
static void shape_text(text_line & line, text_itemizer & itemizer, std::map<unsigned,double> & width_map, face_manager_freetype & font_manager, double scale_factor) { unsigned start = line.first_char(); unsigned end = line.last_char(); std::size_t length = end - start; if (!length) return; std::list<text_item> const& list = itemizer.itemize(start, end); line.reserve(length); auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);}; const std::unique_ptr<hb_buffer_t, decltype(hb_buffer_deleter)> buffer(hb_buffer_create(),hb_buffer_deleter); hb_buffer_pre_allocate(buffer.get(), safe_cast<int>(length)); mapnik::value_unicode_string const& text = itemizer.text(); for (auto const& text_item : list) { face_set_ptr face_set = font_manager.get_face_set(text_item.format_->face_name, text_item.format_->fontset); double size = text_item.format_->text_size * scale_factor; face_set->set_unscaled_character_sizes(); std::size_t num_faces = face_set->size(); std::size_t pos = 0; font_feature_settings const& ff_settings = text_item.format_->ff_settings; int ff_count = safe_cast<int>(ff_settings.count()); for (auto const& face : *face_set) { ++pos; hb_buffer_clear_contents(buffer.get()); hb_buffer_add_utf16(buffer.get(), uchar_to_utf16(text.getBuffer()), text.length(), text_item.start, static_cast<int>(text_item.end - text_item.start)); hb_buffer_set_direction(buffer.get(), (text_item.dir == UBIDI_RTL)?HB_DIRECTION_RTL:HB_DIRECTION_LTR); hb_buffer_set_script(buffer.get(), _icu_script_to_script(text_item.script)); hb_font_t *font(hb_ft_font_create(face->get_face(), nullptr)); // https://github.com/mapnik/test-data-visual/pull/25 #if HB_VERSION_MAJOR > 0 #if HB_VERSION_ATLEAST(1, 0 , 5) hb_ft_font_set_load_flags(font,FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING); #endif #endif hb_shape(font, buffer.get(), ff_settings.get_features(), ff_count); hb_font_destroy(font); unsigned num_glyphs = hb_buffer_get_length(buffer.get()); hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), nullptr); hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), nullptr); bool font_has_all_glyphs = true; // Check if all glyphs are valid. for (unsigned i=0; i<num_glyphs; ++i) { if (!glyphs[i].codepoint) { font_has_all_glyphs = false; break; } } if (!font_has_all_glyphs && (pos < num_faces)) { //Try next font in fontset continue; } double max_glyph_height = 0; for (unsigned i=0; i<num_glyphs; ++i) { auto const& gpos = positions[i]; auto const& glyph = glyphs[i]; unsigned char_index = glyph.cluster; glyph_info g(glyph.codepoint,char_index,text_item.format_); if (face->glyph_dimensions(g)) { g.face = face; g.scale_multiplier = size / face->get_face()->units_per_EM; //Overwrite default advance with better value provided by HarfBuzz g.unscaled_advance = gpos.x_advance; g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier); double tmp_height = g.height(); if (tmp_height > max_glyph_height) max_glyph_height = tmp_height; width_map[char_index] += g.advance(); line.add_glyph(std::move(g), scale_factor); } } line.update_max_char_height(max_glyph_height); break; //When we reach this point the current font had all glyphs. } } }
static void shape_text(text_line & line, text_itemizer & itemizer, std::map<unsigned,double> & width_map, face_manager_freetype & font_manager, double scale_factor ) { unsigned start = line.first_char(); unsigned end = line.last_char(); mapnik::value_unicode_string const& text = itemizer.text(); size_t length = end - start; if (!length) return; line.reserve(length); std::list<text_item> const& list = itemizer.itemize(start, end); UErrorCode err = U_ZERO_ERROR; mapnik::value_unicode_string shaped; mapnik::value_unicode_string reordered; for (auto const& text_item : list) { face_set_ptr face_set = font_manager.get_face_set(text_item.format->face_name, text_item.format->fontset); double size = text_item.format->text_size * scale_factor; face_set->set_unscaled_character_sizes(); for (auto const& face : *face_set) { UBiDi *bidi = ubidi_openSized(length, 0, &err); ubidi_setPara(bidi, text.getBuffer(), length, UBIDI_DEFAULT_LTR, 0, &err); ubidi_writeReordered(bidi, reordered.getBuffer(length), length, UBIDI_DO_MIRRORING, &err); ubidi_close(bidi); reordered.releaseBuffer(length); int32_t num_char = u_shapeArabic(reordered.getBuffer(), length, shaped.getBuffer(length), length, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR | U_SHAPE_TEXT_DIRECTION_VISUAL_LTR, &err); if (num_char < 0) { MAPNIK_LOG_ERROR(icu_shaper) << " u_shapeArabic returned negative num_char " << num_char; } std::size_t num_chars = static_cast<std::size_t>(num_char); shaped.releaseBuffer(length); bool shaped_status = true; if (U_SUCCESS(err) && (num_chars == length)) { U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped); unsigned i = 0; for (iter.setToStart(); iter.hasNext();) { UChar ch = iter.nextPostInc(); glyph_info tmp; tmp.offset.clear(); tmp.char_index = i; tmp.glyph_index = FT_Get_Char_Index(face->get_face(), ch); if (tmp.glyph_index == 0) { shaped_status = false; break; } tmp.face = face; tmp.format = text_item.format; face->glyph_dimensions(tmp); tmp.scale_multiplier = size / face->get_face()->units_per_EM; width_map[i] += tmp.advance(); line.add_glyph(std::move(tmp), scale_factor); ++i; } } if (!shaped_status) continue; line.update_max_char_height(face->get_char_height(size)); return; } } }
// In the Unicode string characters are always stored in logical order. // This makes line breaking easy. One word is added to the current line at a time. Once the line is too long // we either go back one step or inset the line break at the current position (depending on "wrap_before" setting). // At the end everything that is left over is added as the final line. void text_layout::break_line(text_line & line, double wrap_width, unsigned text_ratio, bool wrap_before) { shape_text(line); if (!wrap_width || line.width() < wrap_width) { add_line(line); return; } if (text_ratio) { double wrap_at; double string_width = line.width(); double string_height = line.line_height(); for (double i = 1.0; ((wrap_at = string_width/i)/(string_height*i)) > text_ratio && (string_width/i) > wrap_width; i += 1.0) ; wrap_width = wrap_at; } mapnik::value_unicode_string const& text = itemizer_.text(); Locale locale; // TODO: Is the default constructor correct? UErrorCode status = U_ZERO_ERROR; BreakIterator *breakitr = BreakIterator::createLineInstance(locale, status); // Not breaking the text if an error occurs is probably the best thing we can do. // https://github.com/mapnik/mapnik/issues/2072 if (!U_SUCCESS(status)) { add_line(line); MAPNIK_LOG_ERROR(text_layout) << " could not create BreakIterator: " << u_errorName(status); return; } breakitr->setText(text); double current_line_length = 0; int last_break_position = static_cast<int>(line.first_char()); for (unsigned i=line.first_char(); i < line.last_char(); ++i) { // TODO: character_spacing std::map<unsigned, double>::const_iterator width_itr = width_map_.find(i); if (width_itr != width_map_.end()) { current_line_length += width_itr->second; } if (current_line_length <= wrap_width) continue; int break_position = wrap_before ? breakitr->preceding(i) : breakitr->following(i); // following() returns a break position after the last word. So DONE should only be returned // when calling preceding. if (break_position <= last_break_position || break_position == static_cast<int>(BreakIterator::DONE)) { // A single word is longer than the maximum line width. // Violate line width requirement and choose next break position break_position = breakitr->following(i); if (break_position == static_cast<int>(BreakIterator::DONE)) { break_position = line.last_char(); MAPNIK_LOG_ERROR(text_layout) << "Unexpected result in break_line. Trying to recover...\n"; } } // Break iterator operates on the whole string, while we only look at one line. So we need to // clamp break values. if (break_position < static_cast<int>(line.first_char())) { break_position = line.first_char(); } if (break_position > static_cast<int>(line.last_char())) { break_position = line.last_char(); } text_line new_line(last_break_position, break_position); clear_cluster_widths(last_break_position, break_position); shape_text(new_line); add_line(new_line); last_break_position = break_position; i = break_position - 1; current_line_length = 0; } if (last_break_position == static_cast<int>(line.first_char())) { // No line breaks => no reshaping required add_line(line); } else if (last_break_position != static_cast<int>(line.last_char())) { text_line new_line(last_break_position, line.last_char()); clear_cluster_widths(last_break_position, line.last_char()); shape_text(new_line); add_line(new_line); } }
void text_layout::break_line(text_line & line, char wrap_char, double wrap_width, unsigned text_ratio, bool wrap_before, bool repeat_wrap_char) { shape_text(line); if (!wrap_width || line.width() < wrap_width) { add_line(line); return; } if (text_ratio) { double wrap_at; double string_width = line.width(); double string_height = line.line_height(); for (double i = 1.0; ((wrap_at = string_width/i)/(string_height*i)) > text_ratio && (string_width/i) > wrap_width; i += 1.0) ; wrap_width = wrap_at; } mapnik::value_unicode_string const& text = itemizer_.text(); line_breaker breaker(text, wrap_char); double current_line_length = 0; int last_break_position = static_cast<int>(line.first_char()); for (unsigned i=line.first_char(); i < line.last_char(); ++i) { std::map<unsigned, double>::const_iterator width_itr = width_map_.find(i); if (width_itr != width_map_.end()) { current_line_length += width_itr->second; } if (current_line_length <= wrap_width) continue; int break_position = wrap_before ? breaker.preceding(i) : breaker.following(i); if (break_position <= last_break_position || break_position == static_cast<int>(BreakIterator::DONE)) { break_position = breaker.following(i); if (break_position == static_cast<int>(BreakIterator::DONE)) { break_position = line.last_char(); } } if (break_position < static_cast<int>(line.first_char())) { break_position = line.first_char(); } if (break_position > static_cast<int>(line.last_char())) { break_position = line.last_char(); } text_line new_line(adjust_last_break_position(last_break_position, repeat_wrap_char_), break_position); clear_cluster_widths(adjust_last_break_position(last_break_position, repeat_wrap_char_), break_position); shape_text(new_line); add_line(new_line); last_break_position = break_position; i = break_position - 1; current_line_length = 0; } if (last_break_position == static_cast<int>(line.first_char())) { add_line(line); } else if (last_break_position != static_cast<int>(line.last_char())) { text_line new_line(adjust_last_break_position(last_break_position, repeat_wrap_char_), line.last_char()); clear_cluster_widths(adjust_last_break_position(last_break_position, repeat_wrap_char_), line.last_char()); shape_text(new_line); add_line(new_line); } }