Home | History | Annotate | Download | only in qt
      1 /*
      2  * Copyright (C) 2006 Zack Rusin   <zack (at) kde.org>
      3  *               2006 Rob Buis     <buis (at) kde.org>
      4  *               2009, 2010 Dirk Schulze <krit (at) webkit.org>
      5  *
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     25  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #include "config.h"
     31 #include "Path.h"
     32 
     33 #include "AffineTransform.h"
     34 #include "FloatRect.h"
     35 #include "GraphicsContext.h"
     36 #include "ImageBuffer.h"
     37 #include "PlatformString.h"
     38 #include "StrokeStyleApplier.h"
     39 #include <QPainterPath>
     40 #include <QTransform>
     41 #include <QString>
     42 #include <wtf/OwnPtr.h>
     43 
     44 #define _USE_MATH_DEFINES
     45 #include <math.h>
     46 
     47 #ifndef M_PI
     48 #   define M_PI 3.14159265358979323846
     49 #endif
     50 
     51 namespace WebCore {
     52 
     53 Path::Path()
     54 {
     55 }
     56 
     57 Path::~Path()
     58 {
     59 }
     60 
     61 Path::Path(const Path& other)
     62     : m_path(other.m_path)
     63 {
     64 }
     65 
     66 Path& Path::operator=(const Path& other)
     67 {
     68     m_path = other.m_path;
     69     return *this;
     70 }
     71 
     72 bool Path::contains(const FloatPoint& point, WindRule rule) const
     73 {
     74     Qt::FillRule savedRule = m_path.fillRule();
     75     const_cast<QPainterPath*>(&m_path)->setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
     76 
     77     bool contains = m_path.contains(point);
     78 
     79     const_cast<QPainterPath*>(&m_path)->setFillRule(savedRule);
     80     return contains;
     81 }
     82 
     83 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
     84 {
     85     ASSERT(applier);
     86 
     87     // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
     88     // on each call.
     89     OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1));
     90     GraphicsContext* gc = scratchImage->context();
     91     QPainterPathStroker stroke;
     92     applier->strokeStyle(gc);
     93 
     94     QPen pen = gc->pen();
     95     stroke.setWidth(pen.widthF());
     96     stroke.setCapStyle(pen.capStyle());
     97     stroke.setJoinStyle(pen.joinStyle());
     98     stroke.setMiterLimit(pen.miterLimit());
     99     stroke.setDashPattern(pen.dashPattern());
    100     stroke.setDashOffset(pen.dashOffset());
    101 
    102     return stroke.createStroke(m_path).contains(point);
    103 }
    104 
    105 void Path::translate(const FloatSize& size)
    106 {
    107     QTransform matrix;
    108     matrix.translate(size.width(), size.height());
    109     m_path = m_path * matrix;
    110 }
    111 
    112 FloatRect Path::boundingRect() const
    113 {
    114     return m_path.boundingRect();
    115 }
    116 
    117 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier)
    118 {
    119     // FIXME: We should try to use a 'shared Context' instead of creating a new ImageBuffer
    120     // on each call.
    121     OwnPtr<ImageBuffer> scratchImage = ImageBuffer::create(IntSize(1, 1));
    122     GraphicsContext* gc = scratchImage->context();
    123     QPainterPathStroker stroke;
    124     if (applier) {
    125         applier->strokeStyle(gc);
    126 
    127         QPen pen = gc->pen();
    128         stroke.setWidth(pen.widthF());
    129         stroke.setCapStyle(pen.capStyle());
    130         stroke.setJoinStyle(pen.joinStyle());
    131         stroke.setMiterLimit(pen.miterLimit());
    132         stroke.setDashPattern(pen.dashPattern());
    133         stroke.setDashOffset(pen.dashOffset());
    134     }
    135     return stroke.createStroke(m_path).boundingRect();
    136 }
    137 
    138 void Path::moveTo(const FloatPoint& point)
    139 {
    140     m_path.moveTo(point);
    141 }
    142 
    143 void Path::addLineTo(const FloatPoint& p)
    144 {
    145     m_path.lineTo(p);
    146 }
    147 
    148 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p)
    149 {
    150     m_path.quadTo(cp, p);
    151 }
    152 
    153 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p)
    154 {
    155     m_path.cubicTo(cp1, cp2, p);
    156 }
    157 
    158 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
    159 {
    160     FloatPoint p0(m_path.currentPosition());
    161 
    162     if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) {
    163         m_path.lineTo(p1);
    164         return;
    165     }
    166 
    167     FloatPoint p1p0((p0.x() - p1.x()), (p0.y() - p1.y()));
    168     FloatPoint p1p2((p2.x() - p1.x()), (p2.y() - p1.y()));
    169     float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y());
    170     float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y());
    171 
    172     double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length);
    173     // all points on a line logic
    174     if (cos_phi == -1) {
    175         m_path.lineTo(p1);
    176         return;
    177     }
    178     if (cos_phi == 1) {
    179         // add infinite far away point
    180         unsigned int max_length = 65535;
    181         double factor_max = max_length / p1p0_length;
    182         FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y()));
    183         m_path.lineTo(ep);
    184         return;
    185     }
    186 
    187     float tangent = radius / tan(acos(cos_phi) / 2);
    188     float factor_p1p0 = tangent / p1p0_length;
    189     FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));
    190 
    191     FloatPoint orth_p1p0(p1p0.y(), -p1p0.x());
    192     float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y());
    193     float factor_ra = radius / orth_p1p0_length;
    194 
    195     // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
    196     double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length);
    197     if (cos_alpha < 0.f)
    198         orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
    199 
    200     FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
    201 
    202     // calculate angles for addArc
    203     orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
    204     float sa = acos(orth_p1p0.x() / orth_p1p0_length);
    205     if (orth_p1p0.y() < 0.f)
    206         sa = 2 * piDouble - sa;
    207 
    208     // anticlockwise logic
    209     bool anticlockwise = false;
    210 
    211     float factor_p1p2 = tangent / p1p2_length;
    212     FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
    213     FloatPoint orth_p1p2((t_p1p2.x() - p.x()), (t_p1p2.y() - p.y()));
    214     float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y());
    215     float ea = acos(orth_p1p2.x() / orth_p1p2_length);
    216     if (orth_p1p2.y() < 0)
    217         ea = 2 * piDouble - ea;
    218     if ((sa > ea) && ((sa - ea) < piDouble))
    219         anticlockwise = true;
    220     if ((sa < ea) && ((ea - sa) > piDouble))
    221         anticlockwise = true;
    222 
    223     m_path.lineTo(t_p1p0);
    224 
    225     addArc(p, radius, sa, ea, anticlockwise);
    226 }
    227 
    228 void Path::closeSubpath()
    229 {
    230     m_path.closeSubpath();
    231 }
    232 
    233 #define DEGREES(t) ((t) * 180.0 / M_PI)
    234 void Path::addArc(const FloatPoint& p, float r, float sar, float ear, bool anticlockwise)
    235 {
    236     qreal xc = p.x();
    237     qreal yc = p.y();
    238     qreal radius = r;
    239 
    240 
    241     //### HACK
    242     // In Qt we don't switch the coordinate system for degrees
    243     // and still use the 0,0 as bottom left for degrees so we need
    244     // to switch
    245     sar = -sar;
    246     ear = -ear;
    247     anticlockwise = !anticlockwise;
    248     //end hack
    249 
    250     float sa = DEGREES(sar);
    251     float ea = DEGREES(ear);
    252 
    253     double span = 0;
    254 
    255     double xs = xc - radius;
    256     double ys = yc - radius;
    257     double width  = radius*2;
    258     double height = radius*2;
    259 
    260     if (!anticlockwise && (ea < sa))
    261         span += 360;
    262     else if (anticlockwise && (sa < ea))
    263         span -= 360;
    264 
    265     // this is also due to switched coordinate system
    266     // we would end up with a 0 span instead of 360
    267     if (!(qFuzzyCompare(span + (ea - sa) + 1, 1.0) &&
    268           qFuzzyCompare(qAbs(span), 360.0))) {
    269         span += ea - sa;
    270     }
    271 
    272     m_path.moveTo(QPointF(xc + radius  * cos(sar),
    273                           yc - radius  * sin(sar)));
    274 
    275     m_path.arcTo(xs, ys, width, height, sa, span);
    276 }
    277 
    278 void Path::addRect(const FloatRect& r)
    279 {
    280     m_path.addRect(r.x(), r.y(), r.width(), r.height());
    281 }
    282 
    283 void Path::addEllipse(const FloatRect& r)
    284 {
    285     m_path.addEllipse(r.x(), r.y(), r.width(), r.height());
    286 }
    287 
    288 void Path::clear()
    289 {
    290     m_path = QPainterPath();
    291 }
    292 
    293 bool Path::isEmpty() const
    294 {
    295     // Don't use QPainterPath::isEmpty(), as that also returns true if there's only
    296     // one initial MoveTo element in the path.
    297     return !m_path.elementCount();
    298 }
    299 
    300 bool Path::hasCurrentPoint() const
    301 {
    302     return !isEmpty();
    303 }
    304 
    305 String Path::debugString() const
    306 {
    307     QString ret;
    308     for (int i = 0; i < m_path.elementCount(); ++i) {
    309         const QPainterPath::Element &cur = m_path.elementAt(i);
    310 
    311         switch (cur.type) {
    312             case QPainterPath::MoveToElement:
    313                 ret += QString(QLatin1String("M%1,%2 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2);
    314                 break;
    315             case QPainterPath::LineToElement:
    316                 ret += QString(QLatin1String("L%1,%2 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2);
    317                 break;
    318             case QPainterPath::CurveToElement:
    319             {
    320                 const QPainterPath::Element &c1 = m_path.elementAt(i + 1);
    321                 const QPainterPath::Element &c2 = m_path.elementAt(i + 2);
    322 
    323                 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
    324                 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
    325 
    326                 ret += QString(QLatin1String("C%1,%2,%3,%4,%5,%6 ")).arg(cur.x, 0, 'f', 2).arg(cur.y, 0, 'f', 2).arg(c1.x, 0, 'f', 2)
    327                                                                     .arg(c1.y, 0, 'f', 2).arg(c2.x, 0, 'f', 2).arg(c2.y, 0, 'f', 2);
    328                 i += 2;
    329                 break;
    330             }
    331             case QPainterPath::CurveToDataElement:
    332                 Q_ASSERT(false);
    333                 break;
    334         }
    335     }
    336 
    337     return ret.trimmed();
    338 }
    339 
    340 void Path::apply(void* info, PathApplierFunction function) const
    341 {
    342     PathElement pelement;
    343     FloatPoint points[3];
    344     pelement.points = points;
    345     for (int i = 0; i < m_path.elementCount(); ++i) {
    346         const QPainterPath::Element& cur = m_path.elementAt(i);
    347 
    348         switch (cur.type) {
    349             case QPainterPath::MoveToElement:
    350                 pelement.type = PathElementMoveToPoint;
    351                 pelement.points[0] = QPointF(cur);
    352                 function(info, &pelement);
    353                 break;
    354             case QPainterPath::LineToElement:
    355                 pelement.type = PathElementAddLineToPoint;
    356                 pelement.points[0] = QPointF(cur);
    357                 function(info, &pelement);
    358                 break;
    359             case QPainterPath::CurveToElement:
    360             {
    361                 const QPainterPath::Element& c1 = m_path.elementAt(i + 1);
    362                 const QPainterPath::Element& c2 = m_path.elementAt(i + 2);
    363 
    364                 Q_ASSERT(c1.type == QPainterPath::CurveToDataElement);
    365                 Q_ASSERT(c2.type == QPainterPath::CurveToDataElement);
    366 
    367                 pelement.type = PathElementAddCurveToPoint;
    368                 pelement.points[0] = QPointF(cur);
    369                 pelement.points[1] = QPointF(c1);
    370                 pelement.points[2] = QPointF(c2);
    371                 function(info, &pelement);
    372 
    373                 i += 2;
    374                 break;
    375             }
    376             case QPainterPath::CurveToDataElement:
    377                 Q_ASSERT(false);
    378         }
    379     }
    380 }
    381 
    382 void Path::transform(const AffineTransform& transform)
    383 {
    384     m_path = QTransform(transform).map(m_path);
    385 }
    386 
    387 }
    388 
    389 // vim: ts=4 sw=4 et
    390