mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Added <li>, <margin> and <align> tags
This commit is contained in:
@@ -19,6 +19,9 @@ Bug fixes:
|
|||||||
|
|
||||||
Template features:
|
Template features:
|
||||||
* Added <font:...> tag to change the font inside a text field.
|
* Added <font:...> tag to change the font inside a text field.
|
||||||
|
* Added <margin:...> tag to change the margins of a block of text.
|
||||||
|
* Added <align:...> tag to change the horizontal alignment of a block of text.
|
||||||
|
* Added <li> tag for list bullet points.
|
||||||
* Colors can now be written using hex notation, #rrggbb / #rrggbbaa, and short hex notation (#rgb / #rgba)
|
* Colors can now be written using hex notation, #rrggbb / #rrggbbaa, and short hex notation (#rgb / #rgba)
|
||||||
|
|
||||||
Scripting:
|
Scripting:
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ This is written as the character with code 1 in files.
|
|||||||
| @<color:???>@ The text inside the tag is rendered with the given [[type:color]].
|
| @<color:???>@ The text inside the tag is rendered with the given [[type:color]].
|
||||||
| @<size:???>@ The text inside the tag is rendered with the given font size in points, for example @"<size:12>text</size>"@ 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.
|
| @<size:???>@ The text inside the tag is rendered with the given font size in points, for example @"<size:12>text</size>"@ 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.
|
||||||
| @<font:???>@ The text inside the tag is rendered with the given font family.
|
| @<font:???>@ The text inside the tag is rendered with the given font family.
|
||||||
|
| @<align:???>@ The block inside the tag is aligned with the given horizontal [[type:alignment]]
|
||||||
|
| @<margin:??:??>@ The block inside the tag has additional left and right (optional) margins of the specified size in pixels.
|
||||||
|
| @<li>@ 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 @<li>@ tag.
|
||||||
| @<line>@ Line breaks inside this tag use the [[prop:style:line height line]], and they show a horizontal line.
|
| @<line>@ Line breaks inside this tag use the [[prop:style:line height line]], and they show a horizontal line.
|
||||||
| @<soft-line>@ Line breaks inside this tag use the [[prop:style:soft line height]].
|
| @<soft-line>@ Line breaks inside this tag use the [[prop:style:soft line height]].
|
||||||
| @<atom>@ An atomic piece of text. The cursor can never be inside it; it is selected as a whole.
|
| @<atom>@ 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.
|
|||||||
| @<code-kw>@ The text inside the tag is highlighted as a keyword in source code.
|
| @<code-kw>@ The text inside the tag is highlighted as a keyword in source code.
|
||||||
| @<code-str>@ The text inside the tag is highlighted as a string in source code.
|
| @<code-str>@ The text inside the tag is highlighted as a string in source code.
|
||||||
|
|
||||||
|
|
||||||
--Other tags--
|
--Other tags--
|
||||||
! Tag Description
|
! Tag Description
|
||||||
| @<kw-?>@ Indicates that the text inside it is a keyword. This tag is automatically inserted by
|
| @<kw-?>@ Indicates that the text inside it is a keyword. This tag is automatically inserted by
|
||||||
|
|||||||
@@ -12,16 +12,44 @@
|
|||||||
// ----------------------------------------------------------------------------- : CompoundTextElement
|
// ----------------------------------------------------------------------------- : CompoundTextElement
|
||||||
|
|
||||||
void CompoundTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
|
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<CharInfo>& out) const {
|
void CompoundTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& 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 {
|
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 {
|
double CompoundTextElement::scaleStep() const {
|
||||||
return elements.scaleStep();
|
double m = 1;
|
||||||
|
for (auto const& e : children) {
|
||||||
|
m = min(m, e->scaleStep());
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : AtomTextElement
|
// ----------------------------------------------------------------------------- : 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 {
|
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) {
|
if (what & DRAW_ACTIVE) {
|
||||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||||
dc.SetBrush(Color(210,210,210));
|
dc.SetBrush(background_color);
|
||||||
dc.DrawRectangle(rect);
|
dc.DrawRectangle(rect);
|
||||||
}
|
}
|
||||||
CompoundTextElement::draw(dc, scale, rect, xs, what, start, end);
|
CompoundTextElement::draw(dc, scale, rect, xs, what, start, end);
|
||||||
|
|||||||
+103
-85
@@ -16,48 +16,6 @@ DECLARE_POINTER_TYPE(FontTextElement);
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------- : TextElements
|
// ----------------------------------------------------------------------------- : 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<CharInfo>& 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 <atom-param> tags
|
// Colors for <atom-param> tags
|
||||||
Color param_colors[] =
|
Color param_colors[] =
|
||||||
{ Color(0,170,0)
|
{ 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
|
// Helper class for TextElements::fromString, to allow persistent formating state accross recusive calls
|
||||||
struct TextElementsFromString {
|
struct TextElementsFromString {
|
||||||
// What formatting is enabled?
|
// What formatting is enabled?
|
||||||
int bold, italic, symbol;
|
int bold = 0, italic = 0, symbol = 0;
|
||||||
int soft, kwpph, param, line, soft_line;
|
int soft = 0, kwpph = 0, param = 0, line = 0, soft_line = 0;
|
||||||
int code, code_kw, code_string, param_ref, error;
|
int code = 0, code_kw = 0, code_string = 0, param_ref = 0;
|
||||||
int param_id;
|
int param_id = 0;
|
||||||
vector<Color> colors;
|
vector<Color> colors;
|
||||||
vector<double> sizes;
|
vector<double> sizes;
|
||||||
vector<String> fonts;
|
vector<String> fonts;
|
||||||
/// put angle brackets around the text?
|
vector<pair<double,double>> margins;
|
||||||
bool bracket;
|
vector<Alignment> aligns;
|
||||||
|
|
||||||
TextElementsFromString()
|
const TextStyle& style;
|
||||||
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0), soft_line(0)
|
Context& ctx;
|
||||||
, code(0), code_kw(0), code_string(0), param_ref(0), error(0)
|
vector<TextParagraph>& paragraphs;
|
||||||
, param_id(0), bracket(false) {}
|
|
||||||
|
|
||||||
|
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
|
// read TextElements from a string
|
||||||
void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
|
void fromString(vector<TextElementP>& elements, const String& text, size_t start, size_t end) {
|
||||||
te.elements.clear();
|
|
||||||
end = min(end, text.size());
|
|
||||||
size_t text_start = start;
|
size_t text_start = start;
|
||||||
// for each character...
|
// for each character...
|
||||||
for (size_t pos = start ; pos < end ; ) {
|
for (size_t pos = start ; pos < end ; ) {
|
||||||
@@ -98,7 +65,8 @@ struct TextElementsFromString {
|
|||||||
if (c == _('<')) {
|
if (c == _('<')) {
|
||||||
if (text_start < pos) {
|
if (text_start < pos) {
|
||||||
// text element before this tag?
|
// 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
|
// a (formatting) tag
|
||||||
size_t tag_start = pos;
|
size_t tag_start = pos;
|
||||||
@@ -175,18 +143,54 @@ struct TextElementsFromString {
|
|||||||
else if (is_substr(text, tag_start, _("</atom-param"))) param -= 1;
|
else if (is_substr(text, tag_start, _("</atom-param"))) param -= 1;
|
||||||
else if (is_substr(text, tag_start, _("<atom"))) {
|
else if (is_substr(text, tag_start, _("<atom"))) {
|
||||||
// 'atomic' indicator
|
// 'atomic' indicator
|
||||||
|
#if 0
|
||||||
|
// it would be nice if we could have semi-transparent brushes
|
||||||
|
Color color = style.font.color; color.a /= 5;
|
||||||
|
#else
|
||||||
|
Color fg = style.font.color;
|
||||||
|
Color color = fg.r+fg.g+fg.b < 255*2 ? Color(210,210,210) : Color(60,60,60);
|
||||||
|
#endif
|
||||||
size_t end_tag = min(end, match_close_tag(text, tag_start));
|
size_t end_tag = min(end, match_close_tag(text, tag_start));
|
||||||
intrusive_ptr<AtomTextElement> e(new AtomTextElement(pos, end_tag));
|
intrusive_ptr<AtomTextElement> e = make_intrusive<AtomTextElement>(pos, end_tag, color);
|
||||||
fromString(e->elements, text, pos, end_tag, style, ctx);
|
fromString(e->children, text, pos, end_tag);
|
||||||
te.elements.push_back(e);
|
elements.push_back(e);
|
||||||
pos = skip_tag(text, end_tag);
|
pos = skip_tag(text, end_tag);
|
||||||
} else if (is_substr(text, tag_start, _( "<error"))) {
|
} else if (is_substr(text, tag_start, _( "<error"))) {
|
||||||
// error indicator
|
// error indicator
|
||||||
size_t end_tag = min(end, match_close_tag(text, tag_start));
|
size_t end_tag = min(end, match_close_tag(text, tag_start));
|
||||||
intrusive_ptr<ErrorTextElement> e(new ErrorTextElement(pos, end_tag));
|
intrusive_ptr<ErrorTextElement> e = make_intrusive<ErrorTextElement>(pos, end_tag);
|
||||||
fromString(e->elements, text, pos, end_tag, style, ctx);
|
fromString(e->children, text, pos, end_tag);
|
||||||
te.elements.push_back(e);
|
elements.push_back(e);
|
||||||
pos = skip_tag(text, end_tag);
|
pos = skip_tag(text, end_tag);
|
||||||
|
} else if (is_substr(text, tag_start, _("</li"))) {
|
||||||
|
// end of bullet point, set margin here
|
||||||
|
paragraphs.back().margin_end_char = pos;
|
||||||
|
} else if (is_substr(text, tag_start, _("<margin"))) {
|
||||||
|
size_t colon = text.find_first_of(_(">:"), 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, _("</margin"))) {
|
||||||
|
if (!margins.empty()) margins.pop_back();
|
||||||
|
} else if (is_substr(text, tag_start, _("<align"))) {
|
||||||
|
size_t colon = text.find_first_of(_(">:"), 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, _("</align"))) {
|
||||||
|
if (!aligns.empty()) aligns.pop_back();
|
||||||
} else {
|
} else {
|
||||||
// ignore other tags
|
// ignore other tags
|
||||||
}
|
}
|
||||||
@@ -199,18 +203,19 @@ struct TextElementsFromString {
|
|||||||
}
|
}
|
||||||
if (text_start < end) {
|
if (text_start < end) {
|
||||||
// remaining text at the end
|
// remaining text at the end
|
||||||
addText(te, text, text_start, end, style, ctx);
|
addText(elements, text, text_start, end);
|
||||||
|
addParagraphs(text, text_start, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Create a text element for a piece of text, text[start..end)
|
/// Create a text element for a piece of text, text[start..end)
|
||||||
void addText(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
|
void addText(vector<TextElementP>& elements, const String& text, size_t start, size_t end) {
|
||||||
String content = untag(text.substr(start, end - start));
|
String content = untag(text.substr(start, end - start));
|
||||||
assert(content.size() == end-start);
|
assert(content.size() == end-start);
|
||||||
// use symbol font?
|
// use symbol font?
|
||||||
if (symbol > 0 && style.symbol_font.valid()) {
|
if (symbol > 0 && style.symbol_font.valid()) {
|
||||||
te.elements.push_back(make_intrusive<SymbolTextElement>(content, start, end, style.symbol_font, &ctx));
|
elements.push_back(make_intrusive<SymbolTextElement>(content, start, end, style.symbol_font, &ctx));
|
||||||
} else {
|
} else {
|
||||||
// text, possibly mixed with symbols
|
// text, possibly mixed with symbols
|
||||||
DrawWhat what = soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL;
|
DrawWhat what = soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL;
|
||||||
@@ -221,8 +226,7 @@ private:
|
|||||||
content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET;
|
content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET;
|
||||||
start -= 1;
|
start -= 1;
|
||||||
end += 1;
|
end += 1;
|
||||||
}
|
} else if (style.always_symbol && style.symbol_font.valid()) {
|
||||||
if (style.always_symbol && style.symbol_font.valid()) {
|
|
||||||
// mixed symbols/text, autodetected by symbol font
|
// mixed symbols/text, autodetected by symbol font
|
||||||
size_t text_pos = 0;
|
size_t text_pos = 0;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@@ -233,9 +237,9 @@ private:
|
|||||||
if (text_pos < pos) {
|
if (text_pos < pos) {
|
||||||
// text before it?
|
// text before it?
|
||||||
if (!font) font = makeFont(style);
|
if (!font) font = makeFont(style);
|
||||||
te.elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break));
|
elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break));
|
||||||
}
|
}
|
||||||
te.elements.push_back(make_intrusive<SymbolTextElement>(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx));
|
elements.push_back(make_intrusive<SymbolTextElement>(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx));
|
||||||
text_pos = pos += n;
|
text_pos = pos += n;
|
||||||
} else {
|
} else {
|
||||||
++pos;
|
++pos;
|
||||||
@@ -243,10 +247,30 @@ private:
|
|||||||
}
|
}
|
||||||
if (text_pos < pos) {
|
if (text_pos < pos) {
|
||||||
if (!font) font = makeFont(style);
|
if (!font) font = makeFont(style);
|
||||||
te.elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos), start+text_pos, end, font, what, line_break));
|
elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos), start+text_pos, end, font, what, line_break));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
te.elements.push_back(make_intrusive<FontTextElement>(content, start, end, makeFont(style), what, line_break));
|
elements.push_back(make_intrusive<FontTextElement>(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 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void TextElements::fromString(const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
|
void TextElements::clear() {
|
||||||
TextElementsFromString f;
|
children.clear();
|
||||||
f.fromString(*this, text, start, end, style, ctx);
|
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);
|
|
||||||
}
|
|
||||||
RealSize CompoundTextElement::charSize(RotatedDC& dc, double scale, size_t index) const {
|
|
||||||
return elements.charSize(rot, scale, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
void TextElements::fromString(const String& text, const TextStyle& style, Context& ctx) {
|
||||||
|
clear();
|
||||||
|
TextElementsFromString f(*this, text, style, ctx);
|
||||||
|
}
|
||||||
|
|||||||
+56
-60
@@ -27,7 +27,7 @@ enum class LineBreak {
|
|||||||
NO, // no line break ever
|
NO, // no line break ever
|
||||||
MAYBE, // break here when in "direction:vertical" mode
|
MAYBE, // break here when in "direction:vertical" mode
|
||||||
SPACE, // optional line break (' ')
|
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')
|
HARD, // always a line break ('\n')
|
||||||
LINE, // line break with a separator line (<line>)
|
LINE, // line break with a separator line (<line>)
|
||||||
};
|
};
|
||||||
@@ -58,7 +58,7 @@ public:
|
|||||||
/// Draw a subsection section of the text in the given rectangle
|
/// Draw a subsection section of the text in the given rectangle
|
||||||
/** xs give the x coordinates for each character
|
/** xs give the x coordinates for each character
|
||||||
* this->start <= start < end <= this->end <= text.size() */
|
* 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
|
/// Get information on all characters in the range [start...end) and store them in out
|
||||||
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const = 0;
|
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const = 0;
|
||||||
/// Return the minimum scale factor allowed (starts at 1)
|
/// Return the minimum scale factor allowed (starts at 1)
|
||||||
@@ -67,75 +67,41 @@ public:
|
|||||||
virtual double scaleStep() const = 0;
|
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<CharInfo>& 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<TextElementP> elements;
|
|
||||||
|
|
||||||
/// Find the element that contains the given index, if there is any
|
|
||||||
vector<TextElementP>::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
|
// ----------------------------------------------------------------------------- : 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
|
/// A text element that uses a normal font
|
||||||
class FontTextElement : public SimpleTextElement {
|
class FontTextElement : public TextElement {
|
||||||
public:
|
public:
|
||||||
FontTextElement(const String& content, size_t start, size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style)
|
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)
|
, 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;
|
void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override;
|
||||||
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
|
void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const override;
|
||||||
virtual double minScale() const;
|
double minScale() const override;
|
||||||
virtual double scaleStep() const;
|
double scaleStep() const override;
|
||||||
private:
|
private:
|
||||||
|
String content; ///< Text to show
|
||||||
FontP font;
|
FontP font;
|
||||||
DrawWhat draw_as;
|
DrawWhat draw_as;
|
||||||
LineBreak break_style;
|
LineBreak break_style;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A text element that uses a symbol font
|
/// A text element that uses a symbol font
|
||||||
class SymbolTextElement : public SimpleTextElement {
|
class SymbolTextElement : public TextElement {
|
||||||
public:
|
public:
|
||||||
SymbolTextElement(const String& content, size_t start, size_t end, const SymbolFontRef& font, Context* ctx)
|
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)
|
, 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;
|
void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override;
|
||||||
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
|
void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const override;
|
||||||
virtual double minScale() const;
|
double minScale() const override;
|
||||||
virtual double scaleStep() const;
|
double scaleStep() const override;
|
||||||
private:
|
private:
|
||||||
|
String content;
|
||||||
const SymbolFontRef& font; // owned by TextStyle
|
const SymbolFontRef& font; // owned by TextStyle
|
||||||
Context& ctx;
|
Context& ctx;
|
||||||
};
|
};
|
||||||
@@ -147,20 +113,26 @@ class CompoundTextElement : public TextElement {
|
|||||||
public:
|
public:
|
||||||
CompoundTextElement(size_t start, size_t end) : TextElement(start, end) {}
|
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;
|
void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const override;
|
||||||
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
|
void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const override;
|
||||||
virtual double minScale() const;
|
double minScale() const override;
|
||||||
virtual double scaleStep() const;
|
double scaleStep() const override;
|
||||||
|
|
||||||
TextElements elements; ///< the elements
|
/// 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<TextElementP> children;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A TextElement drawn using a grey background
|
/// A TextElement drawn using a colored background
|
||||||
class AtomTextElement : public CompoundTextElement {
|
class AtomTextElement : public CompoundTextElement {
|
||||||
public:
|
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
|
/// A TextElement drawn using a red wavy underline
|
||||||
@@ -168,6 +140,30 @@ class ErrorTextElement : public CompoundTextElement {
|
|||||||
public:
|
public:
|
||||||
ErrorTextElement(size_t start, size_t end) : CompoundTextElement(start, end) {}
|
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> 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<TextParagraph> paragraphs;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
/// Read the elements from a string
|
||||||
|
void fromString(const String& text, const TextStyle& style, Context& ctx);
|
||||||
|
};
|
||||||
|
|||||||
+71
-43
@@ -19,8 +19,10 @@ struct TextViewer::Line {
|
|||||||
double top; ///< y position of (the top of) this line
|
double top; ///< y position of (the top of) this line
|
||||||
double line_height; ///< The height of this line in pixels
|
double line_height; ///< The height of this line in pixels
|
||||||
LineBreak break_after; ///< Is there a saparator after this line?
|
LineBreak break_after; ///< Is there a saparator after this line?
|
||||||
Alignment alignment; ///< Alignment of this line
|
optional<Alignment> alignment; ///< Alignment of this line
|
||||||
bool justifying; ///< Is the text justified? Only true when *really* justifying.
|
bool justifying; ///< Is the text justified? Only true when *really* justifying.
|
||||||
|
double margin_left; ///< Left margin
|
||||||
|
double margin_right;///< Rightmargin
|
||||||
|
|
||||||
Line()
|
Line()
|
||||||
: start(0), end_or_soft(0), top(0), line_height(0)
|
: 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) {
|
void TextViewer::reset(bool related) {
|
||||||
elements.elements.clear();
|
elements.clear();
|
||||||
lines.clear();
|
lines.clear();
|
||||||
if (!related) scale = 1.0;
|
if (!related) scale = 1.0;
|
||||||
}
|
}
|
||||||
@@ -338,7 +340,7 @@ void TextViewer::setExactScrollPosition(double pos) {
|
|||||||
// ----------------------------------------------------------------------------- : Elements
|
// ----------------------------------------------------------------------------- : Elements
|
||||||
|
|
||||||
void TextViewer::prepareElements(const String& text, const TextStyle& style, Context& ctx) {
|
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)
|
// Is there any scaling (common case is: no)
|
||||||
if (min_scale >= 1.0) {
|
if (min_scale >= 1.0) {
|
||||||
scale = 1.0;
|
scale = 1.0;
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars);
|
elements.getCharInfo(dc, scale, chars);
|
||||||
prepareLinesScale(dc, chars, style, false, lines);
|
prepareLinesAtScale(dc, chars, style, false, lines);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,8 +471,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const
|
|||||||
// - change max_scale
|
// - change max_scale
|
||||||
|
|
||||||
// Try the layout at the previous scale, this could give a quick upper bound
|
// Try the layout at the previous scale, this could give a quick upper bound
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars);
|
elements.getCharInfo(dc, scale, chars);
|
||||||
bool fits = prepareLinesScale(dc, chars, style, false, lines);
|
bool fits = prepareLinesAtScale(dc, chars, style, false, lines);
|
||||||
if (fits) {
|
if (fits) {
|
||||||
min_scale = scale;
|
min_scale = scale;
|
||||||
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,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;
|
scale += scale_step;
|
||||||
vector<Line> lines_before;
|
vector<Line> lines_before;
|
||||||
vector<CharInfo> chars_before;
|
vector<CharInfo> chars_before;
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars_before);
|
elements.getCharInfo(dc, scale, chars_before);
|
||||||
fits = prepareLinesScale(dc, chars_before, style, false, lines_before);
|
fits = prepareLinesAtScale(dc, chars_before, style, false, lines_before);
|
||||||
if (fits) {
|
if (fits) {
|
||||||
// too bad
|
// too bad
|
||||||
swap(lines, lines_before);
|
swap(lines, lines_before);
|
||||||
@@ -499,8 +501,8 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const
|
|||||||
// ensure invariant d (below)
|
// ensure invariant d (below)
|
||||||
best_scale = scale = min_scale;
|
best_scale = scale = min_scale;
|
||||||
chars.clear();
|
chars.clear();
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars);
|
elements.getCharInfo(dc, scale, chars);
|
||||||
prepareLinesScale(dc, chars, style, false, lines);
|
prepareLinesAtScale(dc, chars, style, false, lines);
|
||||||
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale));
|
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;
|
scale = (min_scale + max_scale) / 2;
|
||||||
vector<Line> lines_try;
|
vector<Line> lines_try;
|
||||||
vector<CharInfo> chars_try;
|
vector<CharInfo> chars_try;
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars_try);
|
elements.getCharInfo(dc, scale, chars_try);
|
||||||
fits = prepareLinesScale(dc, chars_try, style, false, lines_try);
|
fits = prepareLinesAtScale(dc, chars_try, style, false, lines_try);
|
||||||
if (fits) {
|
if (fits) {
|
||||||
min_scale = scale;
|
min_scale = scale;
|
||||||
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines_try,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
|
// we'd better update lines, e doesn't hold
|
||||||
scale = min_scale;
|
scale = min_scale;
|
||||||
chars.clear();
|
chars.clear();
|
||||||
elements.getCharInfo(dc, scale, 0, text.size(), chars);
|
elements.getCharInfo(dc, scale, chars);
|
||||||
fits = prepareLinesScale(dc, chars, style, false, lines);
|
fits = prepareLinesAtScale(dc, chars, style, false, lines);
|
||||||
}
|
}
|
||||||
scale = min_scale;
|
scale = min_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to fit a blank line in the masked image, move down until it fits
|
||||||
bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& lines) const {
|
RealSize TextViewer::fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const {
|
||||||
// Try to layout the text at the current scale
|
RealSize line_size(line.margin_left + lineLeft(dc, style, line.top), 0);
|
||||||
// first line
|
while (line.top < style.height && line_size.width + 1 >= style.width - style.padding_right - line.margin_right) {
|
||||||
lines.clear();
|
|
||||||
Line line;
|
|
||||||
line.top = style.padding_top;
|
|
||||||
// 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
|
// nothing fits on this line, move down one pixel
|
||||||
line.top += 1;
|
line.top += 1;
|
||||||
line_size.width = lineLeft(dc, style, line.top);
|
line_size.width = line.margin_left + lineLeft(dc, style, line.top);
|
||||||
}
|
}
|
||||||
|
return line_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& lines) const {
|
||||||
|
// Try to layout the text at the current scale
|
||||||
|
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 = fitLineWidth(line, dc, style);
|
||||||
line.positions.push_back(line_size.width);
|
line.positions.push_back(line_size.width);
|
||||||
|
|
||||||
// The word we are currently reading
|
// The word we are currently reading
|
||||||
RealSize word_size;
|
RealSize word_size;
|
||||||
vector<double> positions_word; // positios for this word
|
vector<double> positions_word; // positios for this word
|
||||||
size_t word_end_or_soft = 0;
|
size_t word_end_or_soft = 0;
|
||||||
size_t word_start = 0;
|
size_t word_start = 0;
|
||||||
// For each character ...
|
// 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];
|
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?
|
// Should we break?
|
||||||
bool word_too_long = false;
|
bool word_too_long = false;
|
||||||
bool break_now = false;
|
bool break_now = false;
|
||||||
@@ -590,6 +608,9 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
|
|||||||
}
|
}
|
||||||
positions_word.push_back(word_size.width);
|
positions_word.push_back(word_size.width);
|
||||||
if (!c.soft) word_end_or_soft = i + 1;
|
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?
|
// Did the word become too long?
|
||||||
if (!break_now) {
|
if (!break_now) {
|
||||||
double max_width = lineRight(dc, style, line.top);
|
double max_width = lineRight(dc, style, line.top);
|
||||||
@@ -661,14 +682,20 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
|
|||||||
line.start = word_start;
|
line.start = word_start;
|
||||||
line.positions.clear();
|
line.positions.clear();
|
||||||
if (line.break_after == LineBreak::LINE) line.line_height = 0;
|
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;
|
line.break_after = LineBreak::NO;
|
||||||
// reset line_size
|
// reset line_size
|
||||||
line_size = RealSize(lineLeft(dc, style, line.top), 0);
|
line_size = fitLineWidth(line, dc, style);
|
||||||
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.positions.push_back(line_size.width); // start position
|
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];
|
Line& l = lines[li];
|
||||||
l.top += vdelta;
|
l.top += vdelta;
|
||||||
// amount to shift all characters horizontally
|
// amount to shift all characters horizontally
|
||||||
l.alignment = style.alignment; // TODO: set at another place
|
|
||||||
l.alignHorizontal(chars, style, s);
|
l.alignHorizontal(chars, style, s);
|
||||||
}
|
}
|
||||||
// TODO : work well with mask
|
// TODO : work well with mask
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const TextStyle& style, const RealRect& s) {
|
void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const TextStyle& style, const RealRect& s) {
|
||||||
double width = this->width();
|
double width = this->width() - margin_left;
|
||||||
bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > s.width : true)
|
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);
|
&& (alignment & ALIGN_IF_SOFTBREAK ? break_after == LineBreak::SOFT || !style.field().multi_line : true);
|
||||||
if ((alignment & ALIGN_JUSTIFY_ALL) && should_fill) {
|
if ((alignment & ALIGN_JUSTIFY_ALL) && should_fill) {
|
||||||
// justify text, by characters
|
// justify text, by characters
|
||||||
justifying = true;
|
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
|
int count = (int)(end_or_soft - start); // distribute it among this many characters
|
||||||
if (count <= 0) count = 1; // prevent div by 0
|
if (count <= 0) count = 1; // prevent div by 0
|
||||||
int i = 0;
|
int i = 0;
|
||||||
FOR_EACH(c, positions) {
|
for (auto& c : positions) {
|
||||||
c += s.x + hdelta * i++ / count;
|
c += s.x + hdelta * i++ / count;
|
||||||
}
|
}
|
||||||
} else if ((alignment & ALIGN_JUSTIFY_WORDS) && should_fill) {
|
} else if ((alignment & ALIGN_JUSTIFY_WORDS) && should_fill) {
|
||||||
// justify text, by words
|
// justify text, by words
|
||||||
justifying = true;
|
justifying = true;
|
||||||
double hdelta = s.width - width; // amount of space to distribute
|
double hdelta = target_width - width; // amount of space to distribute
|
||||||
int count = 0; // distribute it among this many word breaks
|
int count = 0; // distribute it among this many word breaks
|
||||||
for (size_t k = start + 1 ; k < end_or_soft - 1 ; ++k) {
|
for (size_t k = start + 1 ; k < end_or_soft - 1 ; ++k) {
|
||||||
if (chars[k].break_after == LineBreak::SPACE) ++count;
|
if (chars[k].break_after == LineBreak::SPACE) ++count;
|
||||||
}
|
}
|
||||||
if (count == 0) count = 1; // prevent div by 0
|
if (count == 0) count = 1; // prevent div by 0
|
||||||
int i = 0; size_t j = start;
|
int i = 0; size_t j = start;
|
||||||
FOR_EACH(c, positions) {
|
for (auto& c : positions) {
|
||||||
c += s.x + hdelta * i / count;
|
c += s.x + hdelta * i / count;
|
||||||
if (j < end_or_soft && chars[j++].break_after == LineBreak::SPACE) i++;
|
if (j < end_or_soft && chars[j++].break_after == LineBreak::SPACE) i++;
|
||||||
}
|
}
|
||||||
@@ -837,8 +865,8 @@ void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const Text
|
|||||||
} else {
|
} else {
|
||||||
// simple alignment
|
// simple alignment
|
||||||
justifying = false;
|
justifying = false;
|
||||||
double hdelta = s.x + align_delta_x(alignment, s.width, width);
|
double hdelta = s.x + align_delta_x(alignment, target_width, width);
|
||||||
FOR_EACH(c, positions) {
|
for (auto& c : positions) {
|
||||||
c += hdelta;
|
c += hdelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,10 @@ private:
|
|||||||
void prepareLinesTryScales(RotatedDC& dc, const String& text, const TextStyle& style, vector<CharInfo>& chars_out);
|
void prepareLinesTryScales(RotatedDC& dc, const String& text, const TextStyle& style, vector<CharInfo>& chars_out);
|
||||||
/// Prepare the lines, layout the text; at a specific scale
|
/// Prepare the lines, layout the text; at a specific scale
|
||||||
/** Stores output in lines_out */
|
/** Stores output in lines_out */
|
||||||
bool prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& lines_out) const;
|
bool prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& 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
|
/// Align the lines within the textbox
|
||||||
void alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style);
|
void alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style);
|
||||||
/// Align the lines of a single paragraph (a set of lines)
|
/// Align the lines of a single paragraph (a set of lines)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <script/value.hpp>
|
#include <script/value.hpp>
|
||||||
#include <gfx/color.hpp>
|
#include <gfx/color.hpp>
|
||||||
|
|
||||||
Alignment from_string(const String&);
|
|
||||||
void parse_enum(const String&,Direction&);
|
void parse_enum(const String&,Direction&);
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : Store
|
// ----------------------------------------------------------------------------- : Store
|
||||||
@@ -26,7 +25,7 @@ void store(const ScriptValueP& val, bool& var) { var = val->toBoo
|
|||||||
void store(const ScriptValueP& val, Color& var) { var = val->toColor(); }
|
void store(const ScriptValueP& val, Color& var) { var = val->toColor(); }
|
||||||
void store(const ScriptValueP& val, Defaultable<String>& var) { var.assign(val->toString()); }
|
void store(const ScriptValueP& val, Defaultable<String>& var) { var.assign(val->toString()); }
|
||||||
void store(const ScriptValueP& val, Defaultable<Color>& var) { var.assign(val->toColor()); }
|
void store(const ScriptValueP& val, Defaultable<Color>& var) { var.assign(val->toColor()); }
|
||||||
void store(const ScriptValueP& val, Alignment& var) { var = from_string(val->toString()); }
|
void store(const ScriptValueP& val, Alignment& var) { var = alignment_from_string(val->toString()); }
|
||||||
void store(const ScriptValueP& val, Direction& var) { parse_enum(val->toString(),var); }
|
void store(const ScriptValueP& val, Direction& var) { parse_enum(val->toString(),var); }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : OptionalScript
|
// ----------------------------------------------------------------------------- : OptionalScript
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ RealPoint align_in_rect(Alignment align, const RealSize& to_align, const RealRec
|
|||||||
// ----------------------------------------------------------------------------- : Reflection stuff
|
// ----------------------------------------------------------------------------- : Reflection stuff
|
||||||
|
|
||||||
/// Convert a String to an Alignment
|
/// Convert a String to an Alignment
|
||||||
Alignment from_string(const String& s) {
|
Alignment alignment_from_string(const String& s) {
|
||||||
int al = ALIGN_TOP_LEFT;
|
int al = ALIGN_TOP_LEFT;
|
||||||
if (s.find(_("left")) !=String::npos) al = ALIGN_LEFT | (al & ~ALIGN_HORIZONTAL);
|
if (s.find(_("left")) !=String::npos) al = ALIGN_LEFT | (al & ~ALIGN_HORIZONTAL);
|
||||||
if (s.find(_("center")) !=String::npos) al = ALIGN_CENTER | (al & ~ALIGN_HORIZONTAL);
|
if (s.find(_("center")) !=String::npos) al = ALIGN_CENTER | (al & ~ALIGN_HORIZONTAL);
|
||||||
@@ -85,7 +85,7 @@ String to_string(Alignment align) {
|
|||||||
// we need custom io, because there can be both a horizontal and a vertical component
|
// we need custom io, because there can be both a horizontal and a vertical component
|
||||||
|
|
||||||
template <> void Reader::handle(Alignment& align) {
|
template <> void Reader::handle(Alignment& align) {
|
||||||
align = from_string(getValue());
|
align = alignment_from_string(getValue());
|
||||||
}
|
}
|
||||||
template <> void Writer::handle(const Alignment& align) {
|
template <> void Writer::handle(const Alignment& align) {
|
||||||
handle(to_string(align));
|
handle(to_string(align));
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ double align_delta_y(Alignment align, double box_height, double obj_height);
|
|||||||
*/
|
*/
|
||||||
RealPoint align_in_rect(Alignment align, const RealSize& to_align, const RealRect& outer);
|
RealPoint align_in_rect(Alignment align, const RealSize& to_align, const RealRect& outer);
|
||||||
|
|
||||||
|
Alignment alignment_from_string(const String&);
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : Direction
|
// ----------------------------------------------------------------------------- : Direction
|
||||||
|
|
||||||
/// Direction to place something in
|
/// Direction to place something in
|
||||||
|
|||||||
Reference in New Issue
Block a user