diff options
Diffstat (limited to 'src/gui_htmlattrtext.cc')
| -rw-r--r-- | src/gui_htmlattrtext.cc | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/src/gui_htmlattrtext.cc b/src/gui_htmlattrtext.cc new file mode 100644 index 0000000..24380af --- /dev/null +++ b/src/gui_htmlattrtext.cc @@ -0,0 +1,218 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" + +#include <algorithm> +#include <deque> +#include <string.h> +#include <vector> + +#include "gui_htmlattrtext.hh" + +namespace { + +char const* ESCAPE = "&<>\"\n "; + +void append_escaped(std::string* out, char const* in, size_t len) { + size_t last = 0; + for (size_t i = 0; i < len; ++i) { + auto tmp = reinterpret_cast<char const*>(memchr(ESCAPE, in[i], 6)); + if (!tmp) continue; + if (last < i) { + out->append(in + last, i - last); + } + last = i + 1; + switch (tmp - ESCAPE) { + case 0: + out->append("&"); + break; + case 1: + out->append("<"); + break; + case 2: + out->append(">"); + break; + case 3: + out->append("""); + break; + case 4: + out->append("<br>"); + break; + case 5: + out->append(" "); + break; + } + } + if (last < len) { + out->append(in + last, len - last); + } +} + +void color(std::string* out, uint32_t color) { + char tmp[30]; + if (color >> 24 == 0xff) { + out->append(tmp, snprintf(tmp, sizeof(tmp), "rgb(%u, %u, %u)", + static_cast<unsigned>((color >> 16) & 0xff), + static_cast<unsigned>((color >> 8) & 0xff), + static_cast<unsigned>(color & 0xff))); + } else { + out->append(tmp, snprintf(tmp, sizeof(tmp), "rgba(%u, %u, %u, %u)", + static_cast<unsigned>((color >> 16) & 0xff), + static_cast<unsigned>((color >> 8) & 0xff), + static_cast<unsigned>(color & 0xff), + static_cast<unsigned>(color >> 24))); + } +} + +class HtmlAttributedTextImpl : public HtmlAttributedText { +public: + void append(const char* str, size_t len, Attribute const& attr, size_t start, size_t length) override { + if (!str || start >= len) return; + length = std::min(len - start, length); + if (length == 0) return; + auto offset = text_.size(); + text_.append(str + start, length); + if (attr != EMPTY) ranges_.emplace_back(attr, offset, offset + length); + } + + void add(Attribute const& attr, size_t start, size_t length) override { + if (attr == EMPTY) return; + auto end = check_end(start, length); + if (end == 0) return; + Range r(attr, start, end); + auto it = std::lower_bound(ranges_.begin(), ranges_.end(), r); + if (it != ranges_.end() && it->start_ == r.start_ && it->end_ == r.end_) { + it->attr_.add(r.attr_); + return; + } + ranges_.insert(it, r); + } + void set(Attribute const& attr, size_t start, size_t length) override { + clear(start, length); + add(attr, start, length); + } + void clear(size_t start, size_t length) override { + auto end = check_end(start, length); + if (end == 0) return; + auto it = ranges_.begin(); + while (it != ranges_.end()) { + if (it->start_ >= end) break; + if (it->end_ <= start) { + ++it; + continue; + } + if (it->start_ == start && it->end_ == end) { + it = ranges_.erase(it); + continue; + } + if (start > it->start_ && end < it->end_) { + auto const e = it->end_; + it->end_ = start; + it = ranges_.emplace(it + 1, it->attr_, end, e); + } else if (start <= it->start_) { + it->start_ = end; + } else { + assert(end >= it->end_); + it->end_ = start; + } + ++it; + } + } + + std::string html() const override { + std::string ret; + std::deque<Range const*> stack; + size_t last = 0; + for (auto const& range : ranges_) { + while (!stack.empty() && stack.front()->end_ <= range.start_) { + if (stack.front()->end_ > last) { + append_escaped(&ret, text_.data() + last, stack.front()->end_ - last); + last = stack.front()->end_; + } + end_tag(&ret, stack.front()->attr_); + stack.pop_front(); + } + if (range.start_ > last) { + append_escaped(&ret, text_.data() + last, range.start_ - last); + last = range.start_; + } + start_tag(&ret, range.attr_); + auto it = std::lower_bound(stack.begin(), stack.end(), &range, [](Range const* r1, Range const* r2) -> bool { + return r1->end_ < r2->end_; + }); + stack.emplace(it, &range); + } + while (!stack.empty()) { + if (stack.front()->end_ > last) { + append_escaped(&ret, text_.data() + last, stack.front()->end_ - last); + last = stack.front()->end_; + } + end_tag(&ret, stack.front()->attr_); + stack.pop_front(); + } + if (last < text_.size()) { + append_escaped(&ret, text_.data() + last, text_.size() - last); + } + return ret; + } + + std::string text() const override { + return text_; + } + +private: + struct Range { + size_t start_; + size_t end_; + Attribute attr_; + + Range(Attribute const& attr, size_t start, size_t end) + : start_(start), end_(end), attr_(attr) { + assert(start_ < end_); + } + + bool operator<(Range const& range) const { + return start_ < range.start_; + } + }; + + size_t check_end(size_t start, size_t length) const { + if (start >= text_.size()) return 0; + length = std::min(text_.size() - start, length); + if (length == 0) return 0; + return start + length; + } + + static void start_tag(std::string* out, Attribute const& attr) { + out->append("<span style=\""); + if (attr.bold()) out->append("font-weight: bold; "); + if (attr.italic()) out->append("font-style: italic; "); + if (attr.underline()) out->append("text-decoration: underline; "); + if (attr.strike()) out->append("text-decoration: line-through; "); + if (attr.has_foreground()) { + out->append("color: "); + color(out, attr.foreground()); + out->append("; "); + } + if (attr.has_background()) { + out->append("background-color: "); + color(out, attr.background()); + out->append("; "); + } + out->append("\">"); + } + + static void end_tag(std::string* out, Attribute const& UNUSED(attr)) { + out->append("</span>"); + } + + std::string text_; + std::vector<Range> ranges_; +}; + +} // namespace + +// static +HtmlAttributedText* HtmlAttributedText::create() { + return new HtmlAttributedTextImpl(); +} |
