1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qcssutil_p.h"
41 #include "private/qcssparser_p.h"
42 #include "qpainter.h"
43 #include <qmath.h>
44 
45 #ifndef QT_NO_CSSPARSER
46 
47 QT_BEGIN_NAMESPACE
48 
49 using namespace QCss;
50 
qPenFromStyle(const QBrush & b,qreal width,BorderStyle s)51 static QPen qPenFromStyle(const QBrush& b, qreal width, BorderStyle s)
52 {
53     Qt::PenStyle ps = Qt::NoPen;
54 
55     switch (s) {
56     case BorderStyle_Dotted:
57         ps  = Qt::DotLine;
58         break;
59     case BorderStyle_Dashed:
60         ps = width == 1 ? Qt::DotLine : Qt::DashLine;
61         break;
62     case BorderStyle_DotDash:
63         ps = Qt::DashDotLine;
64         break;
65     case BorderStyle_DotDotDash:
66         ps = Qt::DashDotDotLine;
67         break;
68     case BorderStyle_Inset:
69     case BorderStyle_Outset:
70     case BorderStyle_Solid:
71         ps = Qt::SolidLine;
72         break;
73     default:
74         break;
75     }
76 
77     return QPen(b, width, ps, Qt::FlatCap);
78 }
79 
qDrawRoundedCorners(QPainter * p,qreal x1,qreal y1,qreal x2,qreal y2,const QSizeF & r1,const QSizeF & r2,Edge edge,BorderStyle s,QBrush c)80 void qDrawRoundedCorners(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2,
81                          const QSizeF& r1, const QSizeF& r2,
82                          Edge edge, BorderStyle s, QBrush c)
83 {
84     const qreal pw = (edge == TopEdge || edge == BottomEdge) ? y2-y1 : x2-x1;
85     if (s == BorderStyle_Double) {
86         qreal wby3 = pw/3;
87         switch (edge) {
88         case TopEdge:
89         case BottomEdge:
90             qDrawRoundedCorners(p, x1, y1, x2, y1+wby3, r1, r2, edge, BorderStyle_Solid, c);
91             qDrawRoundedCorners(p, x1, y2-wby3, x2, y2, r1, r2, edge, BorderStyle_Solid, c);
92             break;
93         case LeftEdge:
94             qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, LeftEdge, BorderStyle_Solid, c);
95             qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, LeftEdge, BorderStyle_Solid, c);
96             break;
97         case RightEdge:
98             qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, RightEdge, BorderStyle_Solid, c);
99             qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, RightEdge, BorderStyle_Solid, c);
100             break;
101         default:
102             break;
103         }
104         return;
105     } else if (s == BorderStyle_Ridge || s == BorderStyle_Groove) {
106         BorderStyle s1, s2;
107         if (s == BorderStyle_Groove) {
108             s1 = BorderStyle_Inset;
109             s2 = BorderStyle_Outset;
110         } else {
111             s1 = BorderStyle_Outset;
112             s2 = BorderStyle_Inset;
113         }
114         int pwby2 = qRound(pw/2);
115         switch (edge) {
116         case TopEdge:
117             qDrawRoundedCorners(p, x1, y1, x2, y1 + pwby2, r1, r2, TopEdge, s1, c);
118             qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, TopEdge, s2, c);
119             break;
120         case BottomEdge:
121             qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, BottomEdge, s1, c);
122             qDrawRoundedCorners(p, x1, y1, x2, y2-pwby2, r1, r2, BottomEdge, s2, c);
123             break;
124         case LeftEdge:
125             qDrawRoundedCorners(p, x1, y1, x1 + pwby2, y2, r1, r2, LeftEdge, s1, c);
126             qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, LeftEdge, s2, c);
127             break;
128         case RightEdge:
129             qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, RightEdge, s1, c);
130             qDrawRoundedCorners(p, x1, y1, x2 - pwby2, y2, r1, r2, RightEdge, s2, c);
131             break;
132         default:
133             break;
134         }
135     } else if ((s == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge))
136             || (s == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge)))
137             c = c.color().lighter();
138 
139     p->save();
140     qreal pwby2 = pw/2;
141     p->setBrush(Qt::NoBrush);
142     QPen pen = qPenFromStyle(c, pw, s);
143     pen.setCapStyle(Qt::SquareCap); // this eliminates the offby1 errors that we might hit below
144     p->setPen(pen);
145     switch (edge) {
146     case TopEdge:
147         if (!r1.isEmpty())
148             p->drawArc(QRectF(x1 - r1.width() + pwby2, y1 + pwby2,
149                               2*r1.width() - pw, 2*r1.height() - pw), 135*16, -45*16);
150         if (!r2.isEmpty())
151             p->drawArc(QRectF(x2 - r2.width() + pwby2, y1 + pwby2,
152                        2*r2.width() - pw, 2*r2.height() - pw), 45*16, 45*16);
153         break;
154     case BottomEdge:
155         if (!r1.isEmpty())
156             p->drawArc(QRectF(x1 - r1.width() + pwby2, y2 - 2*r1.height() + pwby2,
157                               2*r1.width() - pw, 2*r1.height() - pw), -90 * 16, -45 * 16);
158         if (!r2.isEmpty())
159             p->drawArc(QRectF(x2 - r2.width() + pwby2, y2 - 2*r2.height() + pwby2,
160                        2*r2.width() - pw, 2*r2.height() - pw), -90 * 16, 45 * 16);
161         break;
162     case LeftEdge:
163         if (!r1.isEmpty())
164             p->drawArc(QRectF(x1 + pwby2, y1 - r1.height() + pwby2,
165                        2*r1.width() - pw, 2*r1.height() - pw), 135*16, 45*16);
166         if (!r2.isEmpty())
167             p->drawArc(QRectF(x1 + pwby2, y2 - r2.height() + pwby2,
168                        2*r2.width() - pw, 2*r2.height() - pw), 180*16, 45*16);
169         break;
170     case RightEdge:
171         if (!r1.isEmpty())
172             p->drawArc(QRectF(x2 - 2*r1.width() + pwby2, y1 - r1.height() + pwby2,
173                        2*r1.width() - pw, 2*r1.height() - pw), 45*16, -45*16);
174         if (!r2.isEmpty())
175             p->drawArc(QRectF(x2 - 2*r2.width() + pwby2, y2 - r2.height() + pwby2,
176                        2*r2.width() - pw, 2*r2.height() - pw), 315*16, 45*16);
177         break;
178     default:
179         break;
180     }
181     p->restore();
182 }
183 
184 
qDrawEdge(QPainter * p,qreal x1,qreal y1,qreal x2,qreal y2,qreal dw1,qreal dw2,QCss::Edge edge,QCss::BorderStyle style,QBrush c)185 void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, qreal dw2,
186                QCss::Edge edge, QCss::BorderStyle style, QBrush c)
187 {
188     p->save();
189     const qreal width = (edge == TopEdge || edge == BottomEdge) ? (y2-y1) : (x2-x1);
190 
191     if (width <= 2 && style == BorderStyle_Double)
192         style = BorderStyle_Solid;
193 
194     switch (style) {
195     case BorderStyle_Inset:
196     case BorderStyle_Outset:
197         if ((style == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge))
198             || (style == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge)))
199             c = c.color().lighter();
200         Q_FALLTHROUGH();
201     case BorderStyle_Solid: {
202         p->setPen(Qt::NoPen);
203         p->setBrush(c);
204         if (width == 1 || (dw1 == 0 && dw2 == 0)) {
205             p->drawRect(QRectF(x1, y1, x2-x1, y2-y1));
206         } else { // draw trapezoid
207             QPolygonF quad;
208             switch (edge) {
209             case TopEdge:
210                 quad << QPointF(x1, y1) << QPointF(x1 + dw1, y2)
211                      << QPointF(x2 - dw2, y2) << QPointF(x2, y1);
212                 break;
213             case BottomEdge:
214                 quad << QPointF(x1 + dw1, y1) << QPointF(x1, y2)
215                      << QPointF(x2, y2) << QPointF(x2 - dw2, y1);
216                 break;
217             case LeftEdge:
218                 quad << QPointF(x1, y1) << QPointF(x1, y2)
219                      << QPointF(x2, y2 - dw2) << QPointF(x2, y1 + dw1);
220                 break;
221             case RightEdge:
222                 quad << QPointF(x1, y1 + dw1) << QPointF(x1, y2 - dw2)
223                      << QPointF(x2, y2) << QPointF(x2, y1);
224                 break;
225             default:
226                 break;
227             }
228             p->drawConvexPolygon(quad);
229         }
230         break;
231     }
232     case BorderStyle_Dotted:
233     case BorderStyle_Dashed:
234     case BorderStyle_DotDash:
235     case BorderStyle_DotDotDash:
236         p->setPen(qPenFromStyle(c, width, style));
237         if (width == 1)
238             p->drawLine(QLineF(x1, y1, x2 - 1, y2 - 1));
239         else if (edge == TopEdge || edge == BottomEdge)
240             p->drawLine(QLineF(x1 + width/2, (y1 + y2)/2, x2 - width/2, (y1 + y2)/2));
241         else
242             p->drawLine(QLineF((x1+x2)/2, y1 + width/2, (x1+x2)/2, y2 - width/2));
243         break;
244 
245     case BorderStyle_Double: {
246         int wby3 = qRound(width/3);
247         int dw1by3 = qRound(dw1/3);
248         int dw2by3 = qRound(dw2/3);
249         switch (edge) {
250         case TopEdge:
251             qDrawEdge(p, x1, y1, x2, y1 + wby3, dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c);
252             qDrawEdge(p, x1 + dw1 - dw1by3, y2 - wby3, x2 - dw2 + dw1by3, y2,
253                       dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c);
254             break;
255         case LeftEdge:
256             qDrawEdge(p, x1, y1, x1 + wby3, y2, dw1by3, dw2by3, LeftEdge, BorderStyle_Solid, c);
257             qDrawEdge(p, x2 - wby3, y1 + dw1 - dw1by3, x2, y2 - dw2 + dw2by3, dw1by3, dw2by3,
258                       LeftEdge, BorderStyle_Solid, c);
259             break;
260         case BottomEdge:
261             qDrawEdge(p, x1 + dw1 - dw1by3, y1, x2 - dw2 + dw2by3, y1 + wby3, dw1by3, dw2by3,
262                       BottomEdge, BorderStyle_Solid, c);
263             qDrawEdge(p, x1, y2 - wby3, x2, y2, dw1by3, dw2by3, BottomEdge, BorderStyle_Solid, c);
264             break;
265         case RightEdge:
266             qDrawEdge(p, x2 - wby3, y1, x2, y2, dw1by3, dw2by3, RightEdge, BorderStyle_Solid, c);
267             qDrawEdge(p, x1, y1 + dw1 - dw1by3, x1 + wby3, y2 - dw2 + dw2by3, dw1by3, dw2by3,
268                       RightEdge, BorderStyle_Solid, c);
269             break;
270         default:
271             break;
272         }
273         break;
274     }
275     case BorderStyle_Ridge:
276     case BorderStyle_Groove: {
277         BorderStyle s1, s2;
278         if (style == BorderStyle_Groove) {
279             s1 = BorderStyle_Inset;
280             s2 = BorderStyle_Outset;
281         } else {
282             s1 = BorderStyle_Outset;
283             s2 = BorderStyle_Inset;
284         }
285         int dw1by2 = qFloor(dw1/2), dw2by2 = qFloor(dw2/2);
286         int wby2 = qRound(width/2);
287         switch (edge) {
288         case TopEdge:
289             qDrawEdge(p, x1, y1, x2, y1 + wby2, dw1by2, dw2by2, TopEdge, s1, c);
290             qDrawEdge(p, x1 + dw1by2, y1 + wby2, x2 - dw2by2, y2, dw1by2, dw2by2, TopEdge, s2, c);
291             break;
292         case BottomEdge:
293             qDrawEdge(p, x1, y1 + wby2, x2, y2, dw1by2, dw2by2, BottomEdge, s1, c);
294             qDrawEdge(p, x1 + dw1by2, y1, x2 - dw2by2, y1 + wby2, dw1by2, dw2by2, BottomEdge, s2, c);
295             break;
296         case LeftEdge:
297             qDrawEdge(p, x1, y1, x1 + wby2, y2, dw1by2, dw2by2, LeftEdge, s1, c);
298             qDrawEdge(p, x1 + wby2, y1 + dw1by2, x2, y2 - dw2by2, dw1by2, dw2by2, LeftEdge, s2, c);
299             break;
300         case RightEdge:
301             qDrawEdge(p, x1 + wby2, y1, x2, y2, dw1by2, dw2by2, RightEdge, s1, c);
302             qDrawEdge(p, x1, y1 + dw1by2, x1 + wby2, y2 - dw2by2, dw1by2, dw2by2, RightEdge, s2, c);
303             break;
304         default:
305             break;
306         }
307     }
308     default:
309         break;
310     }
311     p->restore();
312 }
313 
qNormalizeRadii(const QRect & br,const QSize * radii,QSize * tlr,QSize * trr,QSize * blr,QSize * brr)314 void qNormalizeRadii(const QRect &br, const QSize *radii,
315                      QSize *tlr, QSize *trr, QSize *blr, QSize *brr)
316 {
317     *tlr = radii[0].expandedTo(QSize(0, 0));
318     *trr = radii[1].expandedTo(QSize(0, 0));
319     *blr = radii[2].expandedTo(QSize(0, 0));
320     *brr = radii[3].expandedTo(QSize(0, 0));
321     if (tlr->width() + trr->width() > br.width())
322         *tlr = *trr = QSize(0, 0);
323     if (blr->width() + brr->width() > br.width())
324         *blr = *brr = QSize(0, 0);
325     if (tlr->height() + blr->height() > br.height())
326         *tlr = *blr = QSize(0, 0);
327     if (trr->height() + brr->height() > br.height())
328         *trr = *brr = QSize(0, 0);
329 }
330 
331 // Determines if Edge e1 draws over Edge e2. Depending on this trapezoids or rectangles are drawn
paintsOver(const QCss::BorderStyle * styles,const QBrush * colors,QCss::Edge e1,QCss::Edge e2)332 static bool paintsOver(const QCss::BorderStyle *styles, const QBrush *colors, QCss::Edge e1, QCss::Edge e2)
333 {
334     QCss::BorderStyle s1 = styles[e1];
335     QCss::BorderStyle s2 = styles[e2];
336 
337     if (s2 == BorderStyle_None || colors[e2] == Qt::transparent)
338         return true;
339 
340     if ((s1 == BorderStyle_Solid && s2 == BorderStyle_Solid) && (colors[e1] == colors[e2])
341             && colors[e1].isOpaque()) {
342         return true;
343     }
344 
345     return false;
346 }
347 
qDrawBorder(QPainter * p,const QRect & rect,const QCss::BorderStyle * styles,const int * borders,const QBrush * colors,const QSize * radii)348 void qDrawBorder(QPainter *p, const QRect &rect, const QCss::BorderStyle *styles,
349                  const int *borders, const QBrush *colors, const QSize *radii)
350 {
351     const QRectF br(rect);
352     QSize tlr, trr, blr, brr;
353     qNormalizeRadii(rect, radii, &tlr, &trr, &blr, &brr);
354 
355     // Drawn in increasing order of precendence
356     if (styles[BottomEdge] != BorderStyle_None && borders[BottomEdge] > 0) {
357         qreal dw1 = (blr.width() || paintsOver(styles, colors, BottomEdge, LeftEdge)) ? 0 : borders[LeftEdge];
358         qreal dw2 = (brr.width() || paintsOver(styles, colors, BottomEdge, RightEdge)) ? 0 : borders[RightEdge];
359         qreal x1 = br.x() + blr.width();
360         qreal y1 = br.y() + br.height() - borders[BottomEdge];
361         qreal x2 = br.x() + br.width() - brr.width();
362         qreal y2 = br.y() + br.height() ;
363 
364         qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, BottomEdge, styles[BottomEdge], colors[BottomEdge]);
365         if (blr.width() || brr.width())
366             qDrawRoundedCorners(p, x1, y1, x2, y2, blr, brr, BottomEdge, styles[BottomEdge], colors[BottomEdge]);
367     }
368     if (styles[RightEdge] != BorderStyle_None && borders[RightEdge] > 0) {
369         qreal dw1 = (trr.height() || paintsOver(styles, colors, RightEdge, TopEdge)) ? 0 : borders[TopEdge];
370         qreal dw2 = (brr.height() || paintsOver(styles, colors, RightEdge, BottomEdge)) ? 0 : borders[BottomEdge];
371         qreal x1 = br.x() + br.width() - borders[RightEdge];
372         qreal y1 = br.y() + trr.height();
373         qreal x2 = br.x() + br.width();
374         qreal y2 = br.y() + br.height() - brr.height();
375 
376         qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, RightEdge, styles[RightEdge], colors[RightEdge]);
377         if (trr.height() || brr.height())
378             qDrawRoundedCorners(p, x1, y1, x2, y2, trr, brr, RightEdge, styles[RightEdge], colors[RightEdge]);
379     }
380     if (styles[LeftEdge] != BorderStyle_None && borders[LeftEdge] > 0) {
381         qreal dw1 = (tlr.height() || paintsOver(styles, colors, LeftEdge, TopEdge)) ? 0 : borders[TopEdge];
382         qreal dw2 = (blr.height() || paintsOver(styles, colors, LeftEdge, BottomEdge)) ? 0 : borders[BottomEdge];
383         qreal x1 = br.x();
384         qreal y1 = br.y() + tlr.height();
385         qreal x2 = br.x() + borders[LeftEdge];
386         qreal y2 = br.y() + br.height() - blr.height();
387 
388         qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, LeftEdge, styles[LeftEdge], colors[LeftEdge]);
389         if (tlr.height() || blr.height())
390             qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, blr, LeftEdge, styles[LeftEdge], colors[LeftEdge]);
391     }
392     if (styles[TopEdge] != BorderStyle_None && borders[TopEdge] > 0) {
393         qreal dw1 = (tlr.width() || paintsOver(styles, colors, TopEdge, LeftEdge)) ? 0 : borders[LeftEdge];
394         qreal dw2 = (trr.width() || paintsOver(styles, colors, TopEdge, RightEdge)) ? 0 : borders[RightEdge];
395         qreal x1 = br.x() + tlr.width();
396         qreal y1 = br.y();
397         qreal x2 = br.left() + br.width() - trr.width();
398         qreal y2 = br.y() + borders[TopEdge];
399 
400         qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, TopEdge, styles[TopEdge], colors[TopEdge]);
401         if (tlr.width() || trr.width())
402             qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, trr, TopEdge, styles[TopEdge], colors[TopEdge]);
403     }
404 }
405 
406 #endif //QT_NO_CSSPARSER
407 
408 QT_END_NAMESPACE
409