From 2b7bd252e4404dd7c7e8a13439f005c4333e2519 Mon Sep 17 00:00:00 2001 From: Twan van Laarhoven Date: Thu, 14 May 2020 02:37:15 +0200 Subject: [PATCH] Added
  • , and tags --- CHANGES.txt | 3 + doc/type/tagged_string.txt | 4 +- src/render/text/compound.cpp | 38 ++++++- src/render/text/element.cpp | 186 +++++++++++++++++++---------------- src/render/text/element.hpp | 116 +++++++++++----------- src/render/text/viewer.cpp | 108 ++++++++++++-------- src/render/text/viewer.hpp | 5 +- src/script/scriptable.cpp | 3 +- src/util/alignment.cpp | 4 +- src/util/alignment.hpp | 2 + 10 files changed, 274 insertions(+), 195 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8f0c0b1c..acc90bff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,9 @@ Bug fixes: Template features: * Added tag to change the font inside a text field. + * Added tag to change the margins of a block of text. + * Added tag to change the horizontal alignment of a block of text. + * Added
  • tag for list bullet points. * Colors can now be written using hex notation, #rrggbb / #rrggbbaa, and short hex notation (#rgb / #rgba) Scripting: diff --git a/doc/type/tagged_string.txt b/doc/type/tagged_string.txt index 4bbd7f31..4d1158dd 100644 --- a/doc/type/tagged_string.txt +++ b/doc/type/tagged_string.txt @@ -18,6 +18,9 @@ This is written as the character with code 1 in files. | @@ The text inside the tag is rendered with the given [[type:color]]. | @@ The text inside the tag is rendered with the given font size in points, for example @"text"@ makes the text 12 points. The text is scaled down proportionally when it does not fit in a text box and the @scale down to@ attribute allows it. | @@ The text inside the tag is rendered with the given font family. +| @@ The block inside the tag is aligned with the given horizontal [[type:alignment]] +| @@ The block inside the tag has additional left and right (optional) margins of the specified size in pixels. +| @
  • @ The text inside the tag is treated as a list marker, meaning that if the line wraps it will be indented to match the content of the @
  • @ tag. | @@ Line breaks inside this tag use the [[prop:style:line height line]], and they show a horizontal line. | @@ Line breaks inside this tag use the [[prop:style:soft line height]]. | @@ An atomic piece of text. The cursor can never be inside it; it is selected as a whole. @@ -26,7 +29,6 @@ This is written as the character with code 1 in files. | @@ The text inside the tag is highlighted as a keyword in source code. | @@ The text inside the tag is highlighted as a string in source code. - --Other tags-- ! Tag Description | @@ Indicates that the text inside it is a keyword. This tag is automatically inserted by diff --git a/src/render/text/compound.cpp b/src/render/text/compound.cpp index aed61715..27027e7a 100644 --- a/src/render/text/compound.cpp +++ b/src/render/text/compound.cpp @@ -12,16 +12,44 @@ // ----------------------------------------------------------------------------- : CompoundTextElement void CompoundTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const { - elements.draw(dc, scale, rect, xs, what, start, end); + for (auto const& e : children) { + size_t start_ = max(start, e->start); + size_t end_ = min(end, e->end); + if (start_ < end_) { + e->draw(dc, scale, + RealRect(rect.x + xs[start_ - start] - xs[0], rect.y, + xs[end_ - start] - xs[start_ - start], rect.height), + xs + start_ - start, what, start_, end_); + } + if (end <= e->end) return; // nothing can be after this + } } + void CompoundTextElement::getCharInfo(RotatedDC& dc, double scale, vector& out) const { - elements.getCharInfo(dc, scale, start, end, out); + for (auto const& e : children) { + // characters before this element, after the previous + assert(e->start >= out.size()); + out.resize(e->start); + e->getCharInfo(dc, scale, out); + } + assert(end >= out.size()); + out.resize(end); } + double CompoundTextElement::minScale() const { - return elements.minScale(); + double m = 0.0001; + for (auto const& e : children) { + m = max(m, e->minScale()); + } + return m; } + double CompoundTextElement::scaleStep() const { - return elements.scaleStep(); + double m = 1; + for (auto const& e : children) { + m = min(m, e->scaleStep()); + } + return m; } // ----------------------------------------------------------------------------- : AtomTextElement @@ -29,7 +57,7 @@ double CompoundTextElement::scaleStep() const { void AtomTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const { if (what & DRAW_ACTIVE) { dc.SetPen(*wxTRANSPARENT_PEN); - dc.SetBrush(Color(210,210,210)); + dc.SetBrush(background_color); dc.DrawRectangle(rect); } CompoundTextElement::draw(dc, scale, rect, xs, what, start, end); diff --git a/src/render/text/element.cpp b/src/render/text/element.cpp index 9b9ae386..f9331bc4 100644 --- a/src/render/text/element.cpp +++ b/src/render/text/element.cpp @@ -16,48 +16,6 @@ DECLARE_POINTER_TYPE(FontTextElement); // ----------------------------------------------------------------------------- : TextElements -void TextElements::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const { - FOR_EACH_CONST(e, elements) { - size_t start_ = max(start, e->start); - size_t end_ = min(end, e->end); - if (start_ < end_) { - e->draw(dc, scale, - RealRect(rect.x + xs[start_-start] - xs[0], rect.y, - xs[end_-start] - xs[start_-start], rect.height), - xs + start_ - start, what, start_, end_); - } - if (end <= e->end) return; // nothing can be after this - } -} - -void TextElements::getCharInfo(RotatedDC& dc, double scale, size_t start, size_t end, vector& out) const { - FOR_EACH_CONST(e, elements) { - // characters before this element, after the previous - while (out.size() < e->start) { - out.push_back(CharInfo()); - } - e->getCharInfo(dc, scale, out); - } - while (out.size() < end) { - out.push_back(CharInfo()); - } -} - -double TextElements::minScale() const { - double m = 0.0001; - FOR_EACH_CONST(e, elements) { - m = max(m, e->minScale()); - } - return m; -} -double TextElements::scaleStep() const { - double m = 1; - FOR_EACH_CONST(e, elements) { - m = min(m, e->scaleStep()); - } - return m; -} - // Colors for tags Color param_colors[] = { Color(0,170,0) @@ -72,25 +30,34 @@ const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0]) // Helper class for TextElements::fromString, to allow persistent formating state accross recusive calls struct TextElementsFromString { // What formatting is enabled? - int bold, italic, symbol; - int soft, kwpph, param, line, soft_line; - int code, code_kw, code_string, param_ref, error; - int param_id; + int bold = 0, italic = 0, symbol = 0; + int soft = 0, kwpph = 0, param = 0, line = 0, soft_line = 0; + int code = 0, code_kw = 0, code_string = 0, param_ref = 0; + int param_id = 0; vector colors; vector sizes; vector fonts; - /// put angle brackets around the text? - bool bracket; - - TextElementsFromString() - : bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0), soft_line(0) - , code(0), code_kw(0), code_string(0), param_ref(0), error(0) - , param_id(0), bracket(false) {} + vector> margins; + vector aligns; + + const TextStyle& style; + Context& ctx; + vector& paragraphs; + TextElementsFromString(TextElements& out, const String& text, const TextStyle& style, Context& ctx) + : style(style), ctx(ctx), paragraphs(out.paragraphs) + { + out.start = 0; + out.end = text.size(); + paragraphs.emplace_back(); + paragraphs.back().start = 0; + fromString(out.children, text, 0, text.size()); + paragraphs.back().end = text.size(); + } + +private: // read TextElements from a string - void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) { - te.elements.clear(); - end = min(end, text.size()); + void fromString(vector& elements, const String& text, size_t start, size_t end) { size_t text_start = start; // for each character... for (size_t pos = start ; pos < end ; ) { @@ -98,7 +65,8 @@ struct TextElementsFromString { if (c == _('<')) { if (text_start < pos) { // text element before this tag? - addText(te, text, text_start, pos, style, ctx); + addText(elements, text, text_start, pos); + addParagraphs(text, text_start, pos); } // a (formatting) tag size_t tag_start = pos; @@ -175,18 +143,54 @@ struct TextElementsFromString { else if (is_substr(text, tag_start, _(" e(new AtomTextElement(pos, end_tag)); - fromString(e->elements, text, pos, end_tag, style, ctx); - te.elements.push_back(e); + intrusive_ptr e = make_intrusive(pos, end_tag, color); + fromString(e->children, text, pos, end_tag); + elements.push_back(e); pos = skip_tag(text, end_tag); } else if (is_substr(text, tag_start, _( " e(new ErrorTextElement(pos, end_tag)); - fromString(e->elements, text, pos, end_tag, style, ctx); - te.elements.push_back(e); + intrusive_ptr e = make_intrusive(pos, end_tag); + fromString(e->children, text, pos, end_tag); + elements.push_back(e); pos = skip_tag(text, end_tag); + } else if (is_substr(text, tag_start, _(":"), tag_start); + if (colon < pos - 1 && text.GetChar(colon) == _(':')) { + size_t colon2 = text.find_first_of(_(">:"), colon+1); + double margin_left = 0., margin_right = 0.; + text.substr(colon + 1, colon2 - colon - 2).ToDouble(&margin_left); + text.substr(colon2 + 1, pos - colon2 - 2).ToDouble(&margin_right); + if (!margins.empty()) { + margin_left += margins.back().first; + margin_right += margins.back().second; + } + margins.emplace_back(margin_left, margin_right); + paragraphs.back().margin_left = margin_left; + paragraphs.back().margin_right = margin_right; + } + } else if (is_substr(text, tag_start, _(":"), tag_start); + if (colon < pos - 1 && text.GetChar(colon) == _(':')) { + Alignment align = alignment_from_string(text.substr(colon+1, pos-colon-2)); + aligns.push_back(align); + paragraphs.back().alignment = align; + } + } else if (is_substr(text, tag_start, _("& elements, const String& text, size_t start, size_t end) { String content = untag(text.substr(start, end - start)); assert(content.size() == end-start); // use symbol font? if (symbol > 0 && style.symbol_font.valid()) { - te.elements.push_back(make_intrusive(content, start, end, style.symbol_font, &ctx)); + elements.push_back(make_intrusive(content, start, end, style.symbol_font, &ctx)); } else { // text, possibly mixed with symbols DrawWhat what = soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL; @@ -221,8 +226,7 @@ struct TextElementsFromString { content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET; start -= 1; end += 1; - } - if (style.always_symbol && style.symbol_font.valid()) { + } else if (style.always_symbol && style.symbol_font.valid()) { // mixed symbols/text, autodetected by symbol font size_t text_pos = 0; size_t pos = 0; @@ -233,9 +237,9 @@ struct TextElementsFromString { if (text_pos < pos) { // text before it? if (!font) font = makeFont(style); - te.elements.push_back(make_intrusive(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break)); + elements.push_back(make_intrusive(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break)); } - te.elements.push_back(make_intrusive(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx)); + elements.push_back(make_intrusive(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx)); text_pos = pos += n; } else { ++pos; @@ -243,10 +247,30 @@ struct TextElementsFromString { } if (text_pos < pos) { if (!font) font = makeFont(style); - te.elements.push_back(make_intrusive(content.substr(text_pos), start+text_pos, end, font, what, line_break)); + elements.push_back(make_intrusive(content.substr(text_pos), start+text_pos, end, font, what, line_break)); } } else { - te.elements.push_back(make_intrusive(content, start, end, makeFont(style), what, line_break)); + elements.push_back(make_intrusive(content, start, end, makeFont(style), what, line_break)); + } + } + } + // Find paragraph breaks in text + void addParagraphs(const String& text, size_t start, size_t end) { + if (line == 0 && soft_line > 0) return; + for (size_t i = start; i < end; ++i) { + wxUniChar c = text.GetChar(i); + if (c == '\n') { + paragraphs.back().end = i + 1; + paragraphs.emplace_back(); + paragraphs.back().start = i + 1; + paragraphs.back().margin_end_char = i + 1; + if (!margins.empty()) { + paragraphs.back().margin_left = margins.back().first; + paragraphs.back().margin_right = margins.back().second; + } + if (!aligns.empty()) { + paragraphs.back().alignment = aligns.back(); + } } } } @@ -271,18 +295,12 @@ struct TextElementsFromString { } }; -void TextElements::fromString(const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) { - TextElementsFromString f; - f.fromString(*this, text, start, end, style, ctx); +void TextElements::clear() { + children.clear(); + paragraphs.clear(); } -/* -// ----------------------------------------------------------------------------- : CompoundTextElement -void CompoundTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, DrawWhat what, size_t start, size_t end) const { - elements.draw(dc, scale, rect, what, start, end); +void TextElements::fromString(const String& text, const TextStyle& style, Context& ctx) { + clear(); + TextElementsFromString f(*this, text, style, ctx); } -RealSize CompoundTextElement::charSize(RotatedDC& dc, double scale, size_t index) const { - return elements.charSize(rot, scale, index); -} - -*/ diff --git a/src/render/text/element.hpp b/src/render/text/element.hpp index 4a69c38a..76646976 100644 --- a/src/render/text/element.hpp +++ b/src/render/text/element.hpp @@ -27,7 +27,7 @@ enum class LineBreak { NO, // no line break ever MAYBE, // break here when in "direction:vertical" mode SPACE, // optional line break (' ') - SOFT, // always a line break, spacing as a soft break + SOFT, // always a line break, spacing as a soft break, doesn't end paragraphs HARD, // always a line break ('\n') LINE, // line break with a separator line () }; @@ -58,7 +58,7 @@ class TextElement : public IntrusivePtrBase { /// Draw a subsection section of the text in the given rectangle /** xs give the x coordinates for each character * this->start <= start < end <= this->end <= text.size() */ - virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const = 0; + virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const = 0; /// Get information on all characters in the range [start...end) and store them in out virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const = 0; /// Return the minimum scale factor allowed (starts at 1) @@ -67,75 +67,41 @@ class TextElement : public IntrusivePtrBase { virtual double scaleStep() const = 0; }; -// ----------------------------------------------------------------------------- : TextElements - -/// A list of text elements -class TextElements { -public: - /// Draw all the elements (as need to show the range start..end) - void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; - // Get information on all characters in the range [start...end) and store them in out - void getCharInfo(RotatedDC& dc, double scale, size_t start, size_t end, vector& out) const; - /// Return the minimum scale factor allowed by all elements - double minScale() const; - /// Return the steps the scale factor should take - double scaleStep() const; - - /// The actual elements - /** They must be in order of positions and not overlap, i.e. - * i < j ==> elements[i].end <= elements[j].start - */ - vector elements; - - /// Find the element that contains the given index, if there is any - vector::const_iterator findByIndex(size_t index) const; - - /// Read the elements from a string - void fromString(const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx); -}; - // ----------------------------------------------------------------------------- : SimpleTextElement -/// A text element that just shows text -class SimpleTextElement : public TextElement { -public: - SimpleTextElement(const String& content, size_t start, size_t end) - : TextElement(start, end), content(content) - {} - String content; ///< Text to show -}; - /// A text element that uses a normal font -class FontTextElement : public SimpleTextElement { +class FontTextElement : public TextElement { public: FontTextElement(const String& content, size_t start, size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style) - : SimpleTextElement(content, start, end) + : TextElement(start, end), content(content) , font(font), draw_as(draw_as), break_style(break_style) {} - virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; - virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; - virtual double minScale() const; - virtual double scaleStep() const; + void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override; + void getCharInfo(RotatedDC& dc, double scale, vector& out) const override; + double minScale() const override; + double scaleStep() const override; private: + String content; ///< Text to show FontP font; DrawWhat draw_as; LineBreak break_style; }; /// A text element that uses a symbol font -class SymbolTextElement : public SimpleTextElement { +class SymbolTextElement : public TextElement { public: SymbolTextElement(const String& content, size_t start, size_t end, const SymbolFontRef& font, Context* ctx) - : SimpleTextElement(content, start, end) + : TextElement(start, end), content(content) , font(font), ctx(*ctx) {} - virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; - virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; - virtual double minScale() const; - virtual double scaleStep() const; + void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override; + void getCharInfo(RotatedDC& dc, double scale, vector& out) const override; + double minScale() const override; + double scaleStep() const override; private: + String content; const SymbolFontRef& font; // owned by TextStyle Context& ctx; }; @@ -147,20 +113,26 @@ class CompoundTextElement : public TextElement { public: CompoundTextElement(size_t start, size_t end) : TextElement(start, end) {} - virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; - virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; - virtual double minScale() const; - virtual double scaleStep() const; - - TextElements elements; ///< the elements + void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override; + void getCharInfo(RotatedDC& dc, double scale, vector& out) const override; + double minScale() const override; + double scaleStep() const override; + + /// Children of this element + /** They must be in order of positions and not overlap, i.e. + * i < j ==> elements[i].end <= elements[j].start + */ + vector children; }; -/// A TextElement drawn using a grey background +/// A TextElement drawn using a colored background class AtomTextElement : public CompoundTextElement { public: - AtomTextElement(size_t start, size_t end) : CompoundTextElement(start, end) {} + AtomTextElement(size_t start, size_t end, Color background_color) : CompoundTextElement(start, end), background_color(background_color) {} - virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; + void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override; +private: + Color background_color; }; /// A TextElement drawn using a red wavy underline @@ -168,6 +140,30 @@ class ErrorTextElement : public CompoundTextElement { public: ErrorTextElement(size_t start, size_t end) : CompoundTextElement(start, end) {} - virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; + void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override; +}; + +// ----------------------------------------------------------------------------- : TextElements + +class TextParagraph { +public: + optional alignment; + double margin_left = 0., margin_right = 0.; + //double margin_top = 0., margin_bottom = 0.; // TODO: more margin options? + size_t start = String::npos, end = String::npos; + size_t margin_end_char = 0; // end position of characters that are added to the margin (i.e. bullet points) }; +/// A list of text elements extracted from a string +class TextElements : public CompoundTextElement { +public: + TextElements() : CompoundTextElement(String::npos,String::npos) {} + + /// Information on the paragraphs/blocks in the string + /// Text segments separated by newlines are considered paragraphs + vector paragraphs; + + void clear(); + /// Read the elements from a string + void fromString(const String& text, const TextStyle& style, Context& ctx); +}; diff --git a/src/render/text/viewer.cpp b/src/render/text/viewer.cpp index e75a00d7..ecd01360 100644 --- a/src/render/text/viewer.cpp +++ b/src/render/text/viewer.cpp @@ -19,8 +19,10 @@ struct TextViewer::Line { double top; ///< y position of (the top of) this line double line_height; ///< The height of this line in pixels LineBreak break_after; ///< Is there a saparator after this line? - Alignment alignment; ///< Alignment of this line + optional alignment; ///< Alignment of this line bool justifying; ///< Is the text justified? Only true when *really* justifying. + double margin_left; ///< Left margin + double margin_right;///< Rightmargin Line() : start(0), end_or_soft(0), top(0), line_height(0) @@ -162,7 +164,7 @@ bool TextViewer::prepare(RotatedDC& dc, const String& text, TextStyle& style, Co } } void TextViewer::reset(bool related) { - elements.elements.clear(); + elements.clear(); lines.clear(); if (!related) scale = 1.0; } @@ -338,7 +340,7 @@ void TextViewer::setExactScrollPosition(double pos) { // ----------------------------------------------------------------------------- : Elements void TextViewer::prepareElements(const String& text, const TextStyle& style, Context& ctx) { - elements.fromString(text, 0, text.size(), style, ctx); + elements.fromString(text, style, ctx); } @@ -447,8 +449,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const // Is there any scaling (common case is: no) if (min_scale >= 1.0) { scale = 1.0; - elements.getCharInfo(dc, scale, 0, text.size(), chars); - prepareLinesScale(dc, chars, style, false, lines); + elements.getCharInfo(dc, scale, chars); + prepareLinesAtScale(dc, chars, style, false, lines); return; } @@ -469,8 +471,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const // - change max_scale // Try the layout at the previous scale, this could give a quick upper bound - elements.getCharInfo(dc, scale, 0, text.size(), chars); - bool fits = prepareLinesScale(dc, chars, style, false, lines); + elements.getCharInfo(dc, scale, chars); + bool fits = prepareLinesAtScale(dc, chars, style, false, lines); if (fits) { min_scale = scale; max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale)); @@ -480,8 +482,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const scale += scale_step; vector lines_before; vector chars_before; - elements.getCharInfo(dc, scale, 0, text.size(), chars_before); - fits = prepareLinesScale(dc, chars_before, style, false, lines_before); + elements.getCharInfo(dc, scale, chars_before); + fits = prepareLinesAtScale(dc, chars_before, style, false, lines_before); if (fits) { // too bad swap(lines, lines_before); @@ -499,8 +501,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const // ensure invariant d (below) best_scale = scale = min_scale; chars.clear(); - elements.getCharInfo(dc, scale, 0, text.size(), chars); - prepareLinesScale(dc, chars, style, false, lines); + elements.getCharInfo(dc, scale, chars); + prepareLinesAtScale(dc, chars, style, false, lines); max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale)); } @@ -517,8 +519,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const scale = (min_scale + max_scale) / 2; vector lines_try; vector chars_try; - elements.getCharInfo(dc, scale, 0, text.size(), chars_try); - fits = prepareLinesScale(dc, chars_try, style, false, lines_try); + elements.getCharInfo(dc, scale, chars_try); + fits = prepareLinesAtScale(dc, chars_try, style, false, lines_try); if (fits) { min_scale = scale; max_scale = min(max_scale, bound_on_max_scale(dc,style,lines_try,scale)); @@ -535,35 +537,51 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const // we'd better update lines, e doesn't hold scale = min_scale; chars.clear(); - elements.getCharInfo(dc, scale, 0, text.size(), chars); - fits = prepareLinesScale(dc, chars, style, false, lines); + elements.getCharInfo(dc, scale, chars); + fits = prepareLinesAtScale(dc, chars, style, false, lines); } scale = min_scale; } +// Try to fit a blank line in the masked image, move down until it fits +RealSize TextViewer::fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const { + RealSize line_size(line.margin_left + lineLeft(dc, style, line.top), 0); + while (line.top < style.height && line_size.width + 1 >= style.width - style.padding_right - line.margin_right) { + // nothing fits on this line, move down one pixel + line.top += 1; + line_size.width = line.margin_left + lineLeft(dc, style, line.top); + } + return line_size; +} -bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector& chars, const TextStyle& style, bool stop_if_too_long, vector& lines) const { +bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector& chars, const TextStyle& style, bool stop_if_too_long, vector& lines) const { // Try to layout the text at the current scale - // first line lines.clear(); + + // The current "paragraph" in the input string + size_t i_para = 0; + assert(elements.paragraphs.size() > 0); + + // first line Line line; line.top = style.padding_top; + line.margin_left = elements.paragraphs[0].margin_left; + line.margin_right = elements.paragraphs[0].margin_right; + line.alignment = elements.paragraphs[0].alignment; // size of the line so far - RealSize line_size(lineLeft(dc, style, 0), 0); - while (line.top < style.height && line_size.width + 1 >= style.width - style.padding_right) { - // nothing fits on this line, move down one pixel - line.top += 1; - line_size.width = lineLeft(dc, style, line.top); - } + RealSize line_size = fitLineWidth(line, dc, style); line.positions.push_back(line_size.width); + // The word we are currently reading RealSize word_size; vector positions_word; // positios for this word size_t word_end_or_soft = 0; size_t word_start = 0; // For each character ... - for(size_t i = 0 ; i < chars.size() ; ++i) { + for (size_t i = 0 ; i < chars.size() ; ++i) { const CharInfo& c = chars[i]; + assert(i_para < elements.paragraphs.size()); + assert(c.size.width == 0 || elements.paragraphs[i_para].start <= i && i < elements.paragraphs[i_para].end); // Should we break? bool word_too_long = false; bool break_now = false; @@ -590,6 +608,9 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector& chars, } positions_word.push_back(word_size.width); if (!c.soft) word_end_or_soft = i + 1; + if (i < elements.paragraphs[i_para].margin_end_char) { + line.margin_left += c.size.width; // character in left margin + } // Did the word become too long? if (!break_now) { double max_width = lineRight(dc, style, line.top); @@ -661,14 +682,20 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector& chars, line.start = word_start; line.positions.clear(); if (line.break_after == LineBreak::LINE) line.line_height = 0; + if (line.break_after >= LineBreak::HARD) { + // end of paragraph + // look at next paragraph + assert(elements.paragraphs[i_para].end == i + 1); + assert(i_para + 1 < elements.paragraphs.size()); + if (i_para+1 < elements.paragraphs.size()) ++i_para; + assert(elements.paragraphs[i_para].start == i + 1); + line.margin_left = elements.paragraphs[i_para].margin_left; + line.margin_right = elements.paragraphs[i_para].margin_right; + line.alignment = elements.paragraphs[i_para].alignment; + } line.break_after = LineBreak::NO; // reset line_size - line_size = RealSize(lineLeft(dc, style, line.top), 0); - while (line.top < style.height && line_size.width + 1 >= style.width - style.padding_right) { - // nothing fits on this line, move down one pixel - line.top += 1; - line_size.width = lineLeft(dc, style, line.top); - } + line_size = fitLineWidth(line, dc, style); line.positions.push_back(line_size.width); // start position } } @@ -797,37 +824,38 @@ void TextViewer::alignParagraph(size_t start_line, size_t end_line, const vector Line& l = lines[li]; l.top += vdelta; // amount to shift all characters horizontally - l.alignment = style.alignment; // TODO: set at another place l.alignHorizontal(chars, style, s); } // TODO : work well with mask } void TextViewer::Line::alignHorizontal(const vector& chars, const TextStyle& style, const RealRect& s) { - double width = this->width(); - bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > s.width : true) + double width = this->width() - margin_left; + double target_width = s.width - margin_left - margin_right; + Alignment alignment = this->alignment.value_or(style.alignment); + bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > target_width : true) && (alignment & ALIGN_IF_SOFTBREAK ? break_after == LineBreak::SOFT || !style.field().multi_line : true); if ((alignment & ALIGN_JUSTIFY_ALL) && should_fill) { // justify text, by characters justifying = true; - double hdelta = s.width - width; // amount of space to distribute + double hdelta = target_width - width; // amount of space to distribute int count = (int)(end_or_soft - start); // distribute it among this many characters if (count <= 0) count = 1; // prevent div by 0 int i = 0; - FOR_EACH(c, positions) { + for (auto& c : positions) { c += s.x + hdelta * i++ / count; } } else if ((alignment & ALIGN_JUSTIFY_WORDS) && should_fill) { // justify text, by words justifying = true; - double hdelta = s.width - width; // amount of space to distribute - int count = 0; // distribute it among this many word breaks + double hdelta = target_width - width; // amount of space to distribute + int count = 0; // distribute it among this many word breaks for (size_t k = start + 1 ; k < end_or_soft - 1 ; ++k) { if (chars[k].break_after == LineBreak::SPACE) ++count; } if (count == 0) count = 1; // prevent div by 0 int i = 0; size_t j = start; - FOR_EACH(c, positions) { + for (auto& c : positions) { c += s.x + hdelta * i / count; if (j < end_or_soft && chars[j++].break_after == LineBreak::SPACE) i++; } @@ -837,8 +865,8 @@ void TextViewer::Line::alignHorizontal(const vector& chars, const Text } else { // simple alignment justifying = false; - double hdelta = s.x + align_delta_x(alignment, s.width, width); - FOR_EACH(c, positions) { + double hdelta = s.x + align_delta_x(alignment, target_width, width); + for (auto& c : positions) { c += hdelta; } } diff --git a/src/render/text/viewer.hpp b/src/render/text/viewer.hpp index 3865cd61..762e4a7c 100644 --- a/src/render/text/viewer.hpp +++ b/src/render/text/viewer.hpp @@ -139,7 +139,10 @@ class TextViewer { void prepareLinesTryScales(RotatedDC& dc, const String& text, const TextStyle& style, vector& chars_out); /// Prepare the lines, layout the text; at a specific scale /** Stores output in lines_out */ - bool prepareLinesScale(RotatedDC& dc, const vector& chars, const TextStyle& style, bool stop_if_too_long, vector& lines_out) const; + bool prepareLinesAtScale(RotatedDC& dc, const vector& chars, const TextStyle& style, bool stop_if_too_long, vector& lines_out) const; + /// Move the line down until it fits in the masked area + /// Return the line's size + RealSize fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const; /// Align the lines within the textbox void alignLines(RotatedDC& dc, const vector& chars, const TextStyle& style); /// Align the lines of a single paragraph (a set of lines) diff --git a/src/script/scriptable.cpp b/src/script/scriptable.cpp index 596cf962..f9b7fe3b 100644 --- a/src/script/scriptable.cpp +++ b/src/script/scriptable.cpp @@ -14,7 +14,6 @@ #include