Skip to content

Commit 2b7bd25

Browse files
committed
Added <li>, <margin> and <align> tags
1 parent 6d4d973 commit 2b7bd25

File tree

10 files changed

+274
-195
lines changed

10 files changed

+274
-195
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Bug fixes:
1919

2020
Template features:
2121
* Added <font:...> tag to change the font inside a text field.
22+
* Added <margin:...> tag to change the margins of a block of text.
23+
* Added <align:...> tag to change the horizontal alignment of a block of text.
24+
* Added <li> tag for list bullet points.
2225
* Colors can now be written using hex notation, #rrggbb / #rrggbbaa, and short hex notation (#rgb / #rgba)
2326

2427
Scripting:

doc/type/tagged_string.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ This is written as the character with code 1 in files.
1818
| @<color:???>@ The text inside the tag is rendered with the given [[type:color]].
1919
| @<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.
2020
| @<font:???>@ The text inside the tag is rendered with the given font family.
21+
| @<align:???>@ The block inside the tag is aligned with the given horizontal [[type:alignment]]
22+
| @<margin:??:??>@ The block inside the tag has additional left and right (optional) margins of the specified size in pixels.
23+
| @<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.
2124
| @<line>@ Line breaks inside this tag use the [[prop:style:line height line]], and they show a horizontal line.
2225
| @<soft-line>@ Line breaks inside this tag use the [[prop:style:soft line height]].
2326
| @<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.
2629
| @<code-kw>@ The text inside the tag is highlighted as a keyword in source code.
2730
| @<code-str>@ The text inside the tag is highlighted as a string in source code.
2831

29-
3032
--Other tags--
3133
! Tag Description
3234
| @<kw-?>@ Indicates that the text inside it is a keyword. This tag is automatically inserted by

src/render/text/compound.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,52 @@
1212
// ----------------------------------------------------------------------------- : CompoundTextElement
1313

1414
void CompoundTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
15-
elements.draw(dc, scale, rect, xs, what, start, end);
15+
for (auto const& e : children) {
16+
size_t start_ = max(start, e->start);
17+
size_t end_ = min(end, e->end);
18+
if (start_ < end_) {
19+
e->draw(dc, scale,
20+
RealRect(rect.x + xs[start_ - start] - xs[0], rect.y,
21+
xs[end_ - start] - xs[start_ - start], rect.height),
22+
xs + start_ - start, what, start_, end_);
23+
}
24+
if (end <= e->end) return; // nothing can be after this
25+
}
1626
}
27+
1728
void CompoundTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const {
18-
elements.getCharInfo(dc, scale, start, end, out);
29+
for (auto const& e : children) {
30+
// characters before this element, after the previous
31+
assert(e->start >= out.size());
32+
out.resize(e->start);
33+
e->getCharInfo(dc, scale, out);
34+
}
35+
assert(end >= out.size());
36+
out.resize(end);
1937
}
38+
2039
double CompoundTextElement::minScale() const {
21-
return elements.minScale();
40+
double m = 0.0001;
41+
for (auto const& e : children) {
42+
m = max(m, e->minScale());
43+
}
44+
return m;
2245
}
46+
2347
double CompoundTextElement::scaleStep() const {
24-
return elements.scaleStep();
48+
double m = 1;
49+
for (auto const& e : children) {
50+
m = min(m, e->scaleStep());
51+
}
52+
return m;
2553
}
2654

2755
// ----------------------------------------------------------------------------- : AtomTextElement
2856

2957
void AtomTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
3058
if (what & DRAW_ACTIVE) {
3159
dc.SetPen(*wxTRANSPARENT_PEN);
32-
dc.SetBrush(Color(210,210,210));
60+
dc.SetBrush(background_color);
3361
dc.DrawRectangle(rect);
3462
}
3563
CompoundTextElement::draw(dc, scale, rect, xs, what, start, end);

src/render/text/element.cpp

Lines changed: 102 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,6 @@ DECLARE_POINTER_TYPE(FontTextElement);
1616

1717
// ----------------------------------------------------------------------------- : TextElements
1818

19-
void TextElements::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
20-
FOR_EACH_CONST(e, elements) {
21-
size_t start_ = max(start, e->start);
22-
size_t end_ = min(end, e->end);
23-
if (start_ < end_) {
24-
e->draw(dc, scale,
25-
RealRect(rect.x + xs[start_-start] - xs[0], rect.y,
26-
xs[end_-start] - xs[start_-start], rect.height),
27-
xs + start_ - start, what, start_, end_);
28-
}
29-
if (end <= e->end) return; // nothing can be after this
30-
}
31-
}
32-
33-
void TextElements::getCharInfo(RotatedDC& dc, double scale, size_t start, size_t end, vector<CharInfo>& out) const {
34-
FOR_EACH_CONST(e, elements) {
35-
// characters before this element, after the previous
36-
while (out.size() < e->start) {
37-
out.push_back(CharInfo());
38-
}
39-
e->getCharInfo(dc, scale, out);
40-
}
41-
while (out.size() < end) {
42-
out.push_back(CharInfo());
43-
}
44-
}
45-
46-
double TextElements::minScale() const {
47-
double m = 0.0001;
48-
FOR_EACH_CONST(e, elements) {
49-
m = max(m, e->minScale());
50-
}
51-
return m;
52-
}
53-
double TextElements::scaleStep() const {
54-
double m = 1;
55-
FOR_EACH_CONST(e, elements) {
56-
m = min(m, e->scaleStep());
57-
}
58-
return m;
59-
}
60-
6119
// Colors for <atom-param> tags
6220
Color param_colors[] =
6321
{ Color(0,170,0)
@@ -72,33 +30,43 @@ const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0])
7230
// Helper class for TextElements::fromString, to allow persistent formating state accross recusive calls
7331
struct TextElementsFromString {
7432
// What formatting is enabled?
75-
int bold, italic, symbol;
76-
int soft, kwpph, param, line, soft_line;
77-
int code, code_kw, code_string, param_ref, error;
78-
int param_id;
33+
int bold = 0, italic = 0, symbol = 0;
34+
int soft = 0, kwpph = 0, param = 0, line = 0, soft_line = 0;
35+
int code = 0, code_kw = 0, code_string = 0, param_ref = 0;
36+
int param_id = 0;
7937
vector<Color> colors;
8038
vector<double> sizes;
8139
vector<String> fonts;
82-
/// put angle brackets around the text?
83-
bool bracket;
84-
85-
TextElementsFromString()
86-
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0), soft_line(0)
87-
, code(0), code_kw(0), code_string(0), param_ref(0), error(0)
88-
, param_id(0), bracket(false) {}
40+
vector<pair<double,double>> margins;
41+
vector<Alignment> aligns;
42+
43+
const TextStyle& style;
44+
Context& ctx;
45+
vector<TextParagraph>& paragraphs;
8946

47+
TextElementsFromString(TextElements& out, const String& text, const TextStyle& style, Context& ctx)
48+
: style(style), ctx(ctx), paragraphs(out.paragraphs)
49+
{
50+
out.start = 0;
51+
out.end = text.size();
52+
paragraphs.emplace_back();
53+
paragraphs.back().start = 0;
54+
fromString(out.children, text, 0, text.size());
55+
paragraphs.back().end = text.size();
56+
}
57+
58+
private:
9059
// read TextElements from a string
91-
void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
92-
te.elements.clear();
93-
end = min(end, text.size());
60+
void fromString(vector<TextElementP>& elements, const String& text, size_t start, size_t end) {
9461
size_t text_start = start;
9562
// for each character...
9663
for (size_t pos = start ; pos < end ; ) {
9764
Char c = text.GetChar(pos);
9865
if (c == _('<')) {
9966
if (text_start < pos) {
10067
// text element before this tag?
101-
addText(te, text, text_start, pos, style, ctx);
68+
addText(elements, text, text_start, pos);
69+
addParagraphs(text, text_start, pos);
10270
}
10371
// a (formatting) tag
10472
size_t tag_start = pos;
@@ -175,18 +143,54 @@ struct TextElementsFromString {
175143
else if (is_substr(text, tag_start, _("</atom-param"))) param -= 1;
176144
else if (is_substr(text, tag_start, _("<atom"))) {
177145
// 'atomic' indicator
146+
#if 0
147+
// it would be nice if we could have semi-transparent brushes
148+
Color color = style.font.color; color.a /= 5;
149+
#else
150+
Color fg = style.font.color;
151+
Color color = fg.r+fg.g+fg.b < 255*2 ? Color(210,210,210) : Color(60,60,60);
152+
#endif
178153
size_t end_tag = min(end, match_close_tag(text, tag_start));
179-
intrusive_ptr<AtomTextElement> e(new AtomTextElement(pos, end_tag));
180-
fromString(e->elements, text, pos, end_tag, style, ctx);
181-
te.elements.push_back(e);
154+
intrusive_ptr<AtomTextElement> e = make_intrusive<AtomTextElement>(pos, end_tag, color);
155+
fromString(e->children, text, pos, end_tag);
156+
elements.push_back(e);
182157
pos = skip_tag(text, end_tag);
183158
} else if (is_substr(text, tag_start, _( "<error"))) {
184159
// error indicator
185160
size_t end_tag = min(end, match_close_tag(text, tag_start));
186-
intrusive_ptr<ErrorTextElement> e(new ErrorTextElement(pos, end_tag));
187-
fromString(e->elements, text, pos, end_tag, style, ctx);
188-
te.elements.push_back(e);
161+
intrusive_ptr<ErrorTextElement> e = make_intrusive<ErrorTextElement>(pos, end_tag);
162+
fromString(e->children, text, pos, end_tag);
163+
elements.push_back(e);
189164
pos = skip_tag(text, end_tag);
165+
} else if (is_substr(text, tag_start, _("</li"))) {
166+
// end of bullet point, set margin here
167+
paragraphs.back().margin_end_char = pos;
168+
} else if (is_substr(text, tag_start, _("<margin"))) {
169+
size_t colon = text.find_first_of(_(">:"), tag_start);
170+
if (colon < pos - 1 && text.GetChar(colon) == _(':')) {
171+
size_t colon2 = text.find_first_of(_(">:"), colon+1);
172+
double margin_left = 0., margin_right = 0.;
173+
text.substr(colon + 1, colon2 - colon - 2).ToDouble(&margin_left);
174+
text.substr(colon2 + 1, pos - colon2 - 2).ToDouble(&margin_right);
175+
if (!margins.empty()) {
176+
margin_left += margins.back().first;
177+
margin_right += margins.back().second;
178+
}
179+
margins.emplace_back(margin_left, margin_right);
180+
paragraphs.back().margin_left = margin_left;
181+
paragraphs.back().margin_right = margin_right;
182+
}
183+
} else if (is_substr(text, tag_start, _("</margin"))) {
184+
if (!margins.empty()) margins.pop_back();
185+
} else if (is_substr(text, tag_start, _("<align"))) {
186+
size_t colon = text.find_first_of(_(">:"), tag_start);
187+
if (colon < pos - 1 && text.GetChar(colon) == _(':')) {
188+
Alignment align = alignment_from_string(text.substr(colon+1, pos-colon-2));
189+
aligns.push_back(align);
190+
paragraphs.back().alignment = align;
191+
}
192+
} else if (is_substr(text, tag_start, _("</align"))) {
193+
if (!aligns.empty()) aligns.pop_back();
190194
} else {
191195
// ignore other tags
192196
}
@@ -199,18 +203,19 @@ struct TextElementsFromString {
199203
}
200204
if (text_start < end) {
201205
// remaining text at the end
202-
addText(te, text, text_start, end, style, ctx);
206+
addText(elements, text, text_start, end);
207+
addParagraphs(text, text_start, end);
203208
}
204209
}
205210

206211
private:
207212
/// Create a text element for a piece of text, text[start..end)
208-
void addText(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
213+
void addText(vector<TextElementP>& elements, const String& text, size_t start, size_t end) {
209214
String content = untag(text.substr(start, end - start));
210215
assert(content.size() == end-start);
211216
// use symbol font?
212217
if (symbol > 0 && style.symbol_font.valid()) {
213-
te.elements.push_back(make_intrusive<SymbolTextElement>(content, start, end, style.symbol_font, &ctx));
218+
elements.push_back(make_intrusive<SymbolTextElement>(content, start, end, style.symbol_font, &ctx));
214219
} else {
215220
// text, possibly mixed with symbols
216221
DrawWhat what = soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL;
@@ -221,8 +226,7 @@ struct TextElementsFromString {
221226
content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET;
222227
start -= 1;
223228
end += 1;
224-
}
225-
if (style.always_symbol && style.symbol_font.valid()) {
229+
} else if (style.always_symbol && style.symbol_font.valid()) {
226230
// mixed symbols/text, autodetected by symbol font
227231
size_t text_pos = 0;
228232
size_t pos = 0;
@@ -233,20 +237,40 @@ struct TextElementsFromString {
233237
if (text_pos < pos) {
234238
// text before it?
235239
if (!font) font = makeFont(style);
236-
te.elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break));
240+
elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break));
237241
}
238-
te.elements.push_back(make_intrusive<SymbolTextElement>(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx));
242+
elements.push_back(make_intrusive<SymbolTextElement>(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx));
239243
text_pos = pos += n;
240244
} else {
241245
++pos;
242246
}
243247
}
244248
if (text_pos < pos) {
245249
if (!font) font = makeFont(style);
246-
te.elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos), start+text_pos, end, font, what, line_break));
250+
elements.push_back(make_intrusive<FontTextElement>(content.substr(text_pos), start+text_pos, end, font, what, line_break));
247251
}
248252
} else {
249-
te.elements.push_back(make_intrusive<FontTextElement>(content, start, end, makeFont(style), what, line_break));
253+
elements.push_back(make_intrusive<FontTextElement>(content, start, end, makeFont(style), what, line_break));
254+
}
255+
}
256+
}
257+
// Find paragraph breaks in text
258+
void addParagraphs(const String& text, size_t start, size_t end) {
259+
if (line == 0 && soft_line > 0) return;
260+
for (size_t i = start; i < end; ++i) {
261+
wxUniChar c = text.GetChar(i);
262+
if (c == '\n') {
263+
paragraphs.back().end = i + 1;
264+
paragraphs.emplace_back();
265+
paragraphs.back().start = i + 1;
266+
paragraphs.back().margin_end_char = i + 1;
267+
if (!margins.empty()) {
268+
paragraphs.back().margin_left = margins.back().first;
269+
paragraphs.back().margin_right = margins.back().second;
270+
}
271+
if (!aligns.empty()) {
272+
paragraphs.back().alignment = aligns.back();
273+
}
250274
}
251275
}
252276
}
@@ -271,18 +295,12 @@ struct TextElementsFromString {
271295
}
272296
};
273297

274-
void TextElements::fromString(const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
275-
TextElementsFromString f;
276-
f.fromString(*this, text, start, end, style, ctx);
298+
void TextElements::clear() {
299+
children.clear();
300+
paragraphs.clear();
277301
}
278-
/*
279-
// ----------------------------------------------------------------------------- : CompoundTextElement
280302

281-
void CompoundTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, DrawWhat what, size_t start, size_t end) const {
282-
elements.draw(dc, scale, rect, what, start, end);
303+
void TextElements::fromString(const String& text, const TextStyle& style, Context& ctx) {
304+
clear();
305+
TextElementsFromString f(*this, text, style, ctx);
283306
}
284-
RealSize CompoundTextElement::charSize(RotatedDC& dc, double scale, size_t index) const {
285-
return elements.charSize(rot, scale, index);
286-
}
287-
288-
*/

0 commit comments

Comments
 (0)