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