1 #pragma once 2 3 #include <cairo/cairo-xcb.h> 4 5 #include <algorithm> 6 #include <cmath> 7 #include <deque> 8 9 #include "cairo/font.hpp" 10 #include "cairo/surface.hpp" 11 #include "cairo/types.hpp" 12 #include "cairo/utils.hpp" 13 #include "common.hpp" 14 #include "components/logger.hpp" 15 #include "components/types.hpp" 16 #include "errors.hpp" 17 #include "utils/color.hpp" 18 #include "utils/string.hpp" 19 20 POLYBAR_NS 21 22 namespace cairo { 23 /** 24 * \brief Cairo context 25 */ 26 class context { 27 public: context(const surface & surface,const logger & log)28 explicit context(const surface& surface, const logger& log) : m_c(cairo_create(surface)), m_log(log) { 29 auto status = cairo_status(m_c); 30 if (status != CAIRO_STATUS_SUCCESS) { 31 throw application_error(sstream() << "cairo_status(): " << cairo_status_to_string(status)); 32 } 33 cairo_set_antialias(m_c, CAIRO_ANTIALIAS_GOOD); 34 } 35 ~context()36 virtual ~context() { 37 cairo_destroy(m_c); 38 } 39 operator cairo_t*() const40 operator cairo_t*() const { 41 return m_c; 42 } 43 operator <<(const surface & s)44 context& operator<<(const surface& s) { 45 cairo_set_source_surface(m_c, s, 0.0, 0.0); 46 return *this; 47 } 48 operator <<(cairo_operator_t o)49 context& operator<<(cairo_operator_t o) { 50 cairo_set_operator(m_c, o); 51 return *this; 52 } 53 operator <<(cairo_pattern_t * s)54 context& operator<<(cairo_pattern_t* s) { 55 cairo_set_source(m_c, s); 56 return *this; 57 } 58 operator <<(const abspos & p)59 context& operator<<(const abspos& p) { 60 if (p.clear) { 61 cairo_new_path(m_c); 62 } 63 cairo_move_to(m_c, p.x, p.y); 64 return *this; 65 } 66 operator <<(const relpos & p)67 context& operator<<(const relpos& p) { 68 cairo_rel_move_to(m_c, p.x, p.y); 69 return *this; 70 } 71 operator <<(const rgba & f)72 context& operator<<(const rgba& f) { 73 cairo_set_source_rgba(m_c, f.red_d(), f.green_d(), f.blue_d(), f.alpha_d()); 74 return *this; 75 } 76 operator <<(const rect & f)77 context& operator<<(const rect& f) { 78 cairo_rectangle(m_c, f.x, f.y, f.w, f.h); 79 return *this; 80 } 81 operator <<(const line & l)82 context& operator<<(const line& l) { 83 struct line p { 84 l.x1, l.y1, l.x2, l.y2, l.w 85 }; 86 snap(&p.x1, &p.y1); 87 snap(&p.x2, &p.y2); 88 cairo_move_to(m_c, p.x1, p.y1); 89 cairo_line_to(m_c, p.x2, p.y2); 90 cairo_set_line_width(m_c, p.w); 91 cairo_stroke(m_c); 92 return *this; 93 } 94 operator <<(const translate & d)95 context& operator<<(const translate& d) { 96 cairo_translate(m_c, d.x, d.y); 97 return *this; 98 } 99 operator <<(const linear_gradient & l)100 context& operator<<(const linear_gradient& l) { 101 auto stops = l.steps.size(); 102 if (stops >= 2) { 103 auto pattern = cairo_pattern_create_linear(l.x1, l.y1, l.x2, l.y2); 104 auto step = 1.0 / (stops - 1); 105 auto offset = 0.0; 106 for (auto&& color : l.steps) { 107 // clang-format off 108 cairo_pattern_add_color_stop_rgba(pattern, offset, color.red_d(), color.green_d(), color.blue_d(), color.alpha_d()); 109 // clang-format on 110 offset += step; 111 } 112 *this << pattern; 113 cairo_pattern_destroy(pattern); 114 } 115 return *this; 116 } 117 operator <<(const rounded_corners & c)118 context& operator<<(const rounded_corners& c) { 119 double d = M_PI / 180.0; 120 cairo_new_sub_path(m_c); 121 cairo_arc(m_c, c.x + c.w - c.radius.top, c.y + c.radius.top, c.radius.top, -90 * d, 0 * d); 122 cairo_arc(m_c, c.x + c.w - c.radius.bottom, c.y + c.h - c.radius.bottom, c.radius.bottom, 0 * d, 90 * d); 123 cairo_arc(m_c, c.x + c.radius.bottom, c.y + c.h - c.radius.bottom, c.radius.bottom, 90 * d, 180 * d); 124 cairo_arc(m_c, c.x + c.radius.top, c.y + c.radius.top, c.radius.top, 180 * d, 270 * d); 125 cairo_close_path(m_c); 126 return *this; 127 } 128 operator <<(const textblock & t)129 context& operator<<(const textblock& t) { 130 double x, y; 131 position(&x, &y); 132 133 // Prioritize the preferred font 134 vector<shared_ptr<font>> fns(m_fonts.begin(), m_fonts.end()); 135 136 if (t.font > 0 && t.font <= std::distance(fns.begin(), fns.end())) { 137 std::iter_swap(fns.begin(), fns.begin() + t.font - 1); 138 } 139 140 string utf8 = string(t.contents); 141 utils::unicode_charlist chars; 142 utils::utf8_to_ucs4((const unsigned char*)utf8.c_str(), chars); 143 144 while (!chars.empty()) { 145 auto remaining = chars.size(); 146 for (auto&& f : fns) { 147 unsigned int matches = 0; 148 149 // Match as many glyphs as possible if the default/preferred font 150 // is being tested. Otherwise test one glyph at a time against 151 // the remaining fonts. Roll back to the top of the font list 152 // when a glyph has been found. 153 if (f == fns.front() && (matches = f->match(chars)) == 0) { 154 continue; 155 } else if (f != fns.front() && (matches = f->match(chars.front())) == 0) { 156 continue; 157 } 158 159 string subset; 160 auto end = chars.begin(); 161 while (matches-- && end != chars.end()) { 162 subset += utf8.substr(end->offset, end->length); 163 end++; 164 } 165 166 // Use the font 167 f->use(); 168 169 // Get subset extents 170 cairo_text_extents_t extents; 171 f->textwidth(subset, &extents); 172 173 // Draw the background 174 if (t.bg_rect.h != 0.0) { 175 save(); 176 cairo_set_operator(m_c, t.bg_operator); 177 *this << t.bg; 178 cairo_rectangle(m_c, t.bg_rect.x + *t.x_advance, t.bg_rect.y + *t.y_advance, 179 t.bg_rect.w + extents.x_advance, t.bg_rect.h); 180 cairo_fill(m_c); 181 restore(); 182 } 183 184 // Render subset 185 auto fontextents = f->extents(); 186 f->render(subset, x, y - (fontextents.descent / 2 - fontextents.height / 4) + f->offset()); 187 188 // Get updated position 189 position(&x, nullptr); 190 191 // Increase position 192 *t.x_advance += extents.x_advance; 193 *t.y_advance += extents.y_advance; 194 195 chars.erase(chars.begin(), end); 196 break; 197 } 198 199 if (chars.empty()) { 200 break; 201 } else if (remaining != chars.size()) { 202 continue; 203 } 204 205 char unicode[6]{'\0'}; 206 utils::ucs4_to_utf8(unicode, chars.begin()->codepoint); 207 m_log.warn("Dropping unmatched character %s (U+%04x) in '%s'", unicode, chars.begin()->codepoint, t.contents); 208 utf8.erase(chars.begin()->offset, chars.begin()->length); 209 for (auto&& c : chars) { 210 c.offset -= chars.begin()->length; 211 } 212 chars.erase(chars.begin(), ++chars.begin()); 213 } 214 215 return *this; 216 } 217 operator <<(shared_ptr<font> && f)218 context& operator<<(shared_ptr<font>&& f) { 219 m_fonts.emplace_back(forward<decltype(f)>(f)); 220 return *this; 221 } 222 save(bool save_point=false)223 context& save(bool save_point = false) { 224 if (save_point) { 225 m_points.emplace_front(make_pair<double, double>(0.0, 0.0)); 226 position(&m_points.front().first, &m_points.front().second); 227 } 228 m_activegroups++; 229 cairo_save(m_c); 230 return *this; 231 } 232 restore(bool restore_point=false)233 context& restore(bool restore_point = false) { 234 if (!m_activegroups) { 235 throw application_error("Unmatched calls to save/restore"); 236 } 237 m_activegroups--; 238 cairo_restore(m_c); 239 if (restore_point && !m_points.empty()) { 240 *this << abspos{m_points.front().first, m_points.front().first}; 241 m_points.pop_front(); 242 } 243 return *this; 244 } 245 paint()246 context& paint() { 247 cairo_paint(m_c); 248 return *this; 249 } 250 paint(double alpha)251 context& paint(double alpha) { 252 cairo_paint_with_alpha(m_c, alpha); 253 return *this; 254 } 255 fill(bool preserve=false)256 context& fill(bool preserve = false) { 257 if (preserve) { 258 cairo_fill_preserve(m_c); 259 } else { 260 cairo_fill(m_c); 261 } 262 return *this; 263 } 264 mask(cairo_pattern_t * pattern)265 context& mask(cairo_pattern_t* pattern) { 266 cairo_mask(m_c, pattern); 267 return *this; 268 } 269 pop(cairo_pattern_t ** pattern)270 context& pop(cairo_pattern_t** pattern) { 271 *pattern = cairo_pop_group(m_c); 272 return *this; 273 } 274 push()275 context& push() { 276 cairo_push_group(m_c); 277 return *this; 278 } 279 destroy(cairo_pattern_t ** pattern)280 context& destroy(cairo_pattern_t** pattern) { 281 cairo_pattern_destroy(*pattern); 282 *pattern = nullptr; 283 return *this; 284 } 285 clear(bool paint=true)286 context& clear(bool paint = true) { 287 cairo_save(m_c); 288 cairo_set_operator(m_c, CAIRO_OPERATOR_CLEAR); 289 if (paint) { 290 cairo_paint(m_c); 291 } else { 292 cairo_fill_preserve(m_c); 293 } 294 cairo_restore(m_c); 295 return *this; 296 } 297 clip(bool preserve=false)298 context& clip(bool preserve = false) { 299 if (preserve) { 300 cairo_clip_preserve(m_c); 301 } else { 302 cairo_clip(m_c); 303 cairo_new_path(m_c); 304 } 305 return *this; 306 } 307 clip(const rect & r)308 context& clip(const rect& r) { 309 *this << r; 310 return clip(); 311 } 312 reset_clip()313 context& reset_clip() { 314 cairo_reset_clip(m_c); 315 return *this; 316 } 317 position(double * x,double * y=nullptr)318 context& position(double* x, double* y = nullptr) { 319 if (cairo_has_current_point(m_c)) { 320 double x_, y_; 321 x = x != nullptr ? x : &x_; 322 y = y != nullptr ? y : &y_; 323 cairo_get_current_point(m_c, x, y); 324 } 325 return *this; 326 } 327 snap(double * x,double * y)328 context& snap(double* x, double* y) { 329 cairo_user_to_device(m_c, x, y); 330 *x = static_cast<int>(*x + 0.5); 331 *y = static_cast<int>(*y + 0.5); 332 return *this; 333 } 334 335 protected: 336 cairo_t* m_c; 337 const logger& m_log; 338 vector<shared_ptr<font>> m_fonts; 339 std::deque<pair<double, double>> m_points; 340 int m_activegroups{0}; 341 }; 342 } // namespace cairo 343 344 POLYBAR_NS_END 345