void text_layout::break_line(std::pair<unsigned, unsigned> && line_limits) { text_line line(line_limits.first, line_limits.second); shape_text(line); double scaled_wrap_width = wrap_width_ * scale_factor_; if (!scaled_wrap_width || line.width() < scaled_wrap_width) { add_line(std::move(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) > scaled_wrap_width; i += 1.0) ; scaled_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 <= scaled_wrap_width) continue; int break_position = wrap_before_ ? breaker.preceding(i + 1) : 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(std::move(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(std::move(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(std::move(new_line)); } }
static av_cold int init(AVFilterContext *ctx) { int err; DrawTextContext *s = ctx->priv; Glyph *glyph; if (!s->fontfile && !CONFIG_LIBFONTCONFIG) { av_log(ctx, AV_LOG_ERROR, "No font filename provided\n"); return AVERROR(EINVAL); } if (s->textfile) { if (s->text) { av_log(ctx, AV_LOG_ERROR, "Both text and text file provided. Please provide only one\n"); return AVERROR(EINVAL); } if ((err = load_textfile(ctx)) < 0) return err; } if (s->reload && !s->textfile) av_log(ctx, AV_LOG_WARNING, "No file to reload\n"); if (s->tc_opt_string) { int ret = av_timecode_init_from_string(&s->tc, s->tc_rate, s->tc_opt_string, ctx); if (ret < 0) return ret; if (s->tc24hmax) s->tc.flags |= AV_TIMECODE_FLAG_24HOURSMAX; if (!s->text) s->text = av_strdup(""); } if (!s->text) { av_log(ctx, AV_LOG_ERROR, "Either text, a valid file or a timecode must be provided\n"); return AVERROR(EINVAL); } #if CONFIG_LIBFRIBIDI if (s->text_shaping) if ((err = shape_text(ctx)) < 0) return err; #endif if ((err = FT_Init_FreeType(&(s->library)))) { av_log(ctx, AV_LOG_ERROR, "Could not load FreeType: %s\n", FT_ERRMSG(err)); return AVERROR(EINVAL); } err = load_font(ctx); if (err) return err; if (!s->fontsize) s->fontsize = 16; if ((err = FT_Set_Pixel_Sizes(s->face, 0, s->fontsize))) { av_log(ctx, AV_LOG_ERROR, "Could not set font size to %d pixels: %s\n", s->fontsize, FT_ERRMSG(err)); return AVERROR(EINVAL); } if (s->borderw) { if (FT_Stroker_New(s->library, &s->stroker)) { av_log(ctx, AV_LOG_ERROR, "Coult not init FT stroker\n"); return AVERROR_EXTERNAL; } FT_Stroker_Set(s->stroker, s->borderw << 6, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); } s->use_kerning = FT_HAS_KERNING(s->face); /* load the fallback glyph with code 0 */ load_glyph(ctx, NULL, 0); /* set the tabsize in pixels */ if ((err = load_glyph(ctx, &glyph, ' ')) < 0) { av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n"); return err; } s->tabsize *= glyph->advance; if (s->exp_mode == EXP_STRFTIME && (strchr(s->text, '%') || strchr(s->text, '\\'))) av_log(ctx, AV_LOG_WARNING, "expansion=strftime is deprecated.\n"); av_bprint_init(&s->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&s->expanded_fontcolor, 0, AV_BPRINT_SIZE_UNLIMITED); return 0; }
// 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 insert 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_icu(std::pair<unsigned, unsigned> && line_limits) { text_line line(line_limits.first, line_limits.second); shape_text(line); double scaled_wrap_width = wrap_width_ * scale_factor_; if (scaled_wrap_width <= 0 || line.width() < scaled_wrap_width) { add_line(std::move(line)); return; } if (text_ratio_ > 0) { 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) > scaled_wrap_width; i += 1.0) ; scaled_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; std::unique_ptr<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(std::move(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 <= scaled_wrap_width) continue; int break_position = wrap_before_ ? breakitr->preceding(i + 1) : 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(); } bool adjust_for_space_character = break_position > 0 && text[break_position - 1] == 0x0020; text_line new_line(last_break_position, adjust_for_space_character ? break_position - 1 : break_position); clear_cluster_widths(last_break_position, adjust_for_space_character ? break_position - 1 : break_position); shape_text(new_line); add_line(std::move(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(std::move(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(std::move(new_line)); } }