//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make card games | //| Copyright: (C) Twan van Laarhoven and the other MSE developers | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include #include DECLARE_POINTER_TYPE(FontTextElement); // ----------------------------------------------------------------------------- : TextElements // Colors for tags Color param_colors[] = { Color(0,170,0) , Color(0,0,200) , Color(200,0,100) , Color(200,200,0) , Color(0,170,170) , Color(200,0,0) }; const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0]); struct Margins { double left, right, top; }; // Helper class for TextElements::fromString, to allow persistent formating state accross recusive calls struct TextElementsFromString { // What formatting is enabled? int bold = 0, italic = 0, underline = 0, strikethrough = 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, li = 0; vector colors; vector sizes; vector fonts; vector margins; vector aligns; const TextStyle& style; Context& ctx; vector& clauses; vector& paragraphs; TextElementsFromString(TextElements& out, const String& text, const TextStyle& style, Context& ctx) : style(style), ctx(ctx), clauses(out.clauses), paragraphs(out.paragraphs) { out.start = 0; out.end = text.size(); clauses.emplace_back(); clauses.back().start = 0; paragraphs.emplace_back(); paragraphs.back().start = 0; fromString(out.children, text, 0, text.size()); clauses.back().end = text.size(); paragraphs.back().end = text.size(); } private: // read TextElements from a string 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 ; ) { Char c = text.GetChar(pos); if (c == _('<')) { if (text_start < pos) { // text element before this tag? addText(elements, text, text_start, pos); addClausesAndParagraphs(text, text_start, pos); } // a (formatting) tag size_t tag_start = pos; pos = skip_tag(text, tag_start); if (is_tag(text, tag_start, _( ":"), tag_start); if (colon < pos - 1 && text.GetChar(colon) == _(':')) { auto c = parse_color(text.substr(colon+1, pos-colon-2)); if (c) { colors.push_back(*c); } else { queue_message(MESSAGE_WARNING, _("Invalid color in tagged string: ") + text.substr(colon + 1, pos - colon - 2)); colors.push_back(style.font.color); } } } else if (is_tag(text, tag_start, _(":"), tag_start); if (colon < pos - 1 && text.GetChar(colon) == _(':')) { fonts.push_back(text.substr(colon+1, pos-colon-2)); } } else if (is_tag(text, tag_start, _(":"), tag_start); if (colon < pos - 1 && text.GetChar(colon) == _(':')) { double size = style.font.size; String v = text.substr(colon+1, pos-colon-2); v.ToDouble(&size); sizes.push_back(size); } } else if (is_tag(text, tag_start, _(" if (pos != String::npos) { String ref = text.substr(tag_start + 10, pos - tag_start - 11); long ref_n; if (ref.ToLong(&ref_n)) { param_id = (ref_n - 1)%param_colors_count + param_colors_count; } } param_ref += 1; } else if (is_tag(text, tag_start, _(" 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_tag(text, tag_start, _( " 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_tag(text, tag_start, _(" outside
  • tag")); } } else if (is_tag(text, tag_start, _(" outside
  • tag")); } } else if (is_tag(text, tag_start, _(":"), tag_start); if (colon < pos - 1 && text.GetChar(colon) == _(':')) { size_t colon2 = text.find_first_of(_(">:"), colon + 1); size_t colon3 = colon2 < pos-1 ? text.find_first_of(_(">:"), colon2 + 1) : colon2; Margins m = {0.,0.,0.}; text.substr(colon + 1, colon2 - colon - 2).ToDouble(&m.left); text.substr(colon2 + 1, colon3 - colon2 - 2).ToDouble(&m.right); text.substr(colon3 + 1, pos - colon3 - 2).ToDouble(&m.top); if (!margins.empty()) { m.left += margins.back().left; m.right += margins.back().right; m.top += margins.back().top; } margins.emplace_back(m); clauses.back().margin_left = m.left; clauses.back().margin_right = m.right; clauses.back().margin_top = m.top; } } else if (is_tag(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_tag(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()) { 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; LineBreak line_break = line > 0 ? LineBreak::LINE : soft_line > 0 ? LineBreak::SOFT : LineBreak::HARD; if (kwpph > 0 || param > 0) { // bracket the text content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET; start -= 1; end += 1; } // render as symbols or as text? if (style.always_symbol && style.symbol_font.valid()) { // mixed symbols/text, autodetected by symbol font size_t text_pos = 0; size_t pos = 0; FontRefP font; while (pos < end-start) { if (size_t n = style.symbol_font.font->recognizePrefix(content,pos)) { // at 'pos' there are n symbol font characters if (text_pos < pos) { // text before it? if (!font) font = makeFont(style); 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(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx)); text_pos = pos += n; } else { ++pos; } } if (text_pos < pos) { if (!font) font = makeFont(style); elements.push_back(make_intrusive(content.substr(text_pos), start+text_pos, end, font, what, line_break)); } } else { elements.push_back(make_intrusive(content, start, end, makeFont(style), what, line_break)); } } } // Find clause and paragraph breaks in text void addClausesAndParagraphs(const String& text, size_t start, size_t end) { for (size_t i = start; i < end; ++i) { wxUniChar c = text.GetChar(i); if (c == '\n') { clauses.back().end = i + 1; clauses.emplace_back(); clauses.back().start = i + 1; if (!margins.empty()) { clauses.back().margin_left = margins.back().left; clauses.back().margin_right = margins.back().right; clauses.back().margin_top = margins.back().top; } if (line < 1 && soft_line > 0) continue; paragraphs.back().end = i + 1; paragraphs.emplace_back(); paragraphs.back().start = i + 1; paragraphs.back().margin_before_bullet = i + 1; paragraphs.back().margin_after_bullet = i + 1; if (!aligns.empty()) { paragraphs.back().alignment = aligns.back(); } } } } FontRefP makeFont(const TextStyle& style) { return style.font.make( (bold > 0 ? FONT_BOLD : FONT_NORMAL) | (italic > 0 ? FONT_ITALIC : FONT_NORMAL) | (soft > 0 ? FONT_SOFT : FONT_NORMAL) | (kwpph > 0 ? FONT_SOFT : FONT_NORMAL) | (code > 0 ? FONT_CODE : FONT_NORMAL) | (code_kw > 0 ? FONT_CODE_KW : FONT_NORMAL) | (code_string > 0 ? FONT_CODE_STRING : FONT_NORMAL) | (!fonts.empty() ? FONT_FROM_TAG : FONT_NORMAL), underline > 0, strikethrough > 0, fonts.empty() ? nullptr : &fonts.back(), param > 0 || param_ref > 0 ? ¶m_colors[(param_id++) % param_colors_count] : !colors.empty() ? &colors.back() : nullptr, !sizes.empty() ? &sizes.back() : nullptr ); } }; void TextElements::clear() { children.clear(); clauses.clear(); paragraphs.clear(); } void TextElements::fromString(const String& text, const TextStyle& style, Context& ctx) { clear(); TextElementsFromString f(*this, text, style, ctx); }