// -*- mode: c++; c-basic-offset: 2; -*- #include "common.hh" #include #include #include #include #include "gui_htmlattrtext.hh" #include "observers.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(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("
"); 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((color >> 16) & 0xff), static_cast((color >> 8) & 0xff), static_cast(color & 0xff))); } else { out->append(tmp, snprintf(tmp, sizeof(tmp), "rgba(%u, %u, %u, %u)", static_cast((color >> 16) & 0xff), static_cast((color >> 8) & 0xff), static_cast(color & 0xff), static_cast(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); notify_changed(); } 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_); notify_changed(); return; } ranges_.insert(it, r); notify_changed(); } 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; } notify_changed(); } std::string html() const override { std::string ret; std::deque 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_; } void reset() override { text_.clear(); ranges_.clear(); notify_changed(); } void add_listener(Listener* listener) override { observers_.insert(listener); } void remove_listener(Listener* listener) override { observers_.erase(listener); } 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; } void notify_changed() { auto it = observers_.notify(); while (it.has_next()) { it.next()->changed(this); } } static void start_tag(std::string* out, Attribute const& attr) { out->append("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&) { out->append(""); } std::string text_; std::vector ranges_; Observers observers_; }; } // namespace // static HtmlAttributedText* HtmlAttributedText::create() { return new HtmlAttributedTextImpl(); }