Home | History | Annotate | Download | only in cairo
      1 /*
      2     Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk (at) gmail.com>
      3     Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <wildfox (at) kde.org>
      4                   2004, 2005, 2006 Rob Buis <buis (at) kde.org>
      5                   2005, 2007 Apple Inc. All Rights reserved.
      6                   2007 Alp Toker <alp (at) atoker.com>
      7                   2008 Dirk Schulze <krit (at) webkit.org>
      8 
      9     This library is free software; you can redistribute it and/or
     10     modify it under the terms of the GNU Library General Public
     11     License as published by the Free Software Foundation; either
     12     version 2 of the License, or (at your option) any later version.
     13 
     14     This library is distributed in the hope that it will be useful,
     15     but WITHOUT ANY WARRANTY; without even the implied warranty of
     16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     17     Library General Public License for more details.
     18 
     19     You should have received a copy of the GNU Library General Public License
     20     aint with this library; see the file COPYING.LIB.  If not, write to
     21     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     22     Boston, MA 02110-1301, USA.
     23 */
     24 
     25 #include "config.h"
     26 #include "Path.h"
     27 
     28 #include "AffineTransform.h"
     29 #include "CairoPath.h"
     30 #include "FloatRect.h"
     31 #include "GraphicsContext.h"
     32 #include "PlatformString.h"
     33 #include "StrokeStyleApplier.h"
     34 
     35 #include <cairo.h>
     36 #include <math.h>
     37 #include <wtf/MathExtras.h>
     38 
     39 namespace WebCore {
     40 
     41 Path::Path()
     42     : m_path(new CairoPath())
     43 {
     44 }
     45 
     46 Path::~Path()
     47 {
     48     delete m_path;
     49 }
     50 
     51 Path::Path(const Path& other)
     52     : m_path(new CairoPath())
     53 {
     54     cairo_t* cr = platformPath()->m_cr;
     55     cairo_path_t* p = cairo_copy_path(other.platformPath()->m_cr);
     56     cairo_append_path(cr, p);
     57     cairo_path_destroy(p);
     58 }
     59 
     60 Path& Path::operator=(const Path& other)
     61 {
     62     if (&other == this)
     63         return *this;
     64 
     65     clear();
     66     cairo_t* cr = platformPath()->m_cr;
     67     cairo_path_t* p = cairo_copy_path(other.platformPath()->m_cr);
     68     cairo_append_path(cr, p);
     69     cairo_path_destroy(p);
     70     return *this;
     71 }
     72 
     73 void Path::clear()
     74 {
     75     cairo_t* cr = platformPath()->m_cr;
     76     cairo_new_path(cr);
     77 }
     78 
     79 bool Path::isEmpty() const
     80 {
     81     return !cairo_has_current_point(platformPath()->m_cr);
     82 }
     83 
     84 bool Path::hasCurrentPoint() const
     85 {
     86     return !isEmpty();
     87 }
     88 
     89 void Path::translate(const FloatSize& p)
     90 {
     91     cairo_t* cr = platformPath()->m_cr;
     92     cairo_translate(cr, p.width(), p.height());
     93 }
     94 
     95 void Path::moveTo(const FloatPoint& p)
     96 {
     97     cairo_t* cr = platformPath()->m_cr;
     98     cairo_move_to(cr, p.x(), p.y());
     99 }
    100 
    101 void Path::addLineTo(const FloatPoint& p)
    102 {
    103     cairo_t* cr = platformPath()->m_cr;
    104     cairo_line_to(cr, p.x(), p.y());
    105 }
    106 
    107 void Path::addRect(const FloatRect& rect)
    108 {
    109     cairo_t* cr = platformPath()->m_cr;
    110     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
    111 }
    112 
    113 /*
    114  * inspired by libsvg-cairo
    115  */
    116 void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& point)
    117 {
    118     cairo_t* cr = platformPath()->m_cr;
    119     double x, y;
    120     double x1 = controlPoint.x();
    121     double y1 = controlPoint.y();
    122     double x2 = point.x();
    123     double y2 = point.y();
    124     cairo_get_current_point(cr, &x, &y);
    125     cairo_curve_to(cr,
    126                    x  + 2.0 / 3.0 * (x1 - x),  y  + 2.0 / 3.0 * (y1 - y),
    127                    x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2),
    128                    x2, y2);
    129 }
    130 
    131 void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& controlPoint3)
    132 {
    133     cairo_t* cr = platformPath()->m_cr;
    134     cairo_curve_to(cr, controlPoint1.x(), controlPoint1.y(),
    135                    controlPoint2.x(), controlPoint2.y(),
    136                    controlPoint3.x(), controlPoint3.y());
    137 }
    138 
    139 void Path::addArc(const FloatPoint& p, float r, float sa, float ea, bool anticlockwise)
    140 {
    141     // http://bugs.webkit.org/show_bug.cgi?id=16449
    142     // cairo_arc() functions hang or crash when passed inf as radius or start/end angle
    143     if (!isfinite(r) || !isfinite(sa) || !isfinite(ea))
    144         return;
    145 
    146     cairo_t* cr = platformPath()->m_cr;
    147     if (anticlockwise)
    148         cairo_arc_negative(cr, p.x(), p.y(), r, sa, ea);
    149     else
    150         cairo_arc(cr, p.x(), p.y(), r, sa, ea);
    151 }
    152 
    153 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
    154 {
    155     if (isEmpty())
    156         return;
    157 
    158     cairo_t* cr = platformPath()->m_cr;
    159 
    160     double x0, y0;
    161     cairo_get_current_point(cr, &x0, &y0);
    162     FloatPoint p0(x0, y0);
    163     if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || radius == 0.f) {
    164         cairo_line_to(cr, p1.x(), p1.y());
    165         return;
    166     }
    167 
    168     FloatPoint p1p0((p0.x() - p1.x()),(p0.y() - p1.y()));
    169     FloatPoint p1p2((p2.x() - p1.x()),(p2.y() - p1.y()));
    170     float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y());
    171     float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y());
    172 
    173     double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length);
    174     // all points on a line logic
    175     if (cos_phi == -1) {
    176         cairo_line_to(cr, p1.x(), p1.y());
    177         return;
    178     }
    179     if (cos_phi == 1) {
    180         // add infinite far away point
    181         unsigned int max_length = 65535;
    182         double factor_max = max_length / p1p0_length;
    183         FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y()));
    184         cairo_line_to(cr, ep.x(), ep.y());
    185         return;
    186     }
    187 
    188     float tangent = radius / tan(acos(cos_phi) / 2);
    189     float factor_p1p0 = tangent / p1p0_length;
    190     FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));
    191 
    192     FloatPoint orth_p1p0(p1p0.y(), -p1p0.x());
    193     float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y());
    194     float factor_ra = radius / orth_p1p0_length;
    195 
    196     // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
    197     double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length);
    198     if (cos_alpha < 0.f)
    199         orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
    200 
    201     FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));
    202 
    203     // calculate angles for addArc
    204     orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y());
    205     float sa = acos(orth_p1p0.x() / orth_p1p0_length);
    206     if (orth_p1p0.y() < 0.f)
    207         sa = 2 * piDouble - sa;
    208 
    209     // anticlockwise logic
    210     bool anticlockwise = false;
    211 
    212     float factor_p1p2 = tangent / p1p2_length;
    213     FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
    214     FloatPoint orth_p1p2((t_p1p2.x() - p.x()),(t_p1p2.y() - p.y()));
    215     float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y());
    216     float ea = acos(orth_p1p2.x() / orth_p1p2_length);
    217     if (orth_p1p2.y() < 0)
    218         ea = 2 * piDouble - ea;
    219     if ((sa > ea) && ((sa - ea) < piDouble))
    220         anticlockwise = true;
    221     if ((sa < ea) && ((ea - sa) > piDouble))
    222         anticlockwise = true;
    223 
    224     cairo_line_to(cr, t_p1p0.x(), t_p1p0.y());
    225 
    226     addArc(p, radius, sa, ea, anticlockwise);
    227 }
    228 
    229 void Path::addEllipse(const FloatRect& rect)
    230 {
    231     cairo_t* cr = platformPath()->m_cr;
    232     cairo_save(cr);
    233     float yRadius = .5 * rect.height();
    234     float xRadius = .5 * rect.width();
    235     cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
    236     cairo_scale(cr, xRadius, yRadius);
    237     cairo_arc(cr, 0., 0., 1., 0., 2 * piDouble);
    238     cairo_restore(cr);
    239 }
    240 
    241 void Path::closeSubpath()
    242 {
    243     cairo_t* cr = platformPath()->m_cr;
    244     cairo_close_path(cr);
    245 }
    246 
    247 FloatRect Path::boundingRect() const
    248 {
    249     cairo_t* cr = platformPath()->m_cr;
    250     double x0, x1, y0, y1;
    251     cairo_path_extents(cr, &x0, &y0, &x1, &y1);
    252     return FloatRect(x0, y0, x1 - x0, y1 - y0);
    253 }
    254 
    255 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier)
    256 {
    257     cairo_t* cr = platformPath()->m_cr;
    258     if (applier) {
    259         GraphicsContext gc(cr);
    260         applier->strokeStyle(&gc);
    261     }
    262 
    263     double x0, x1, y0, y1;
    264     cairo_stroke_extents(cr, &x0, &y0, &x1, &y1);
    265     return FloatRect(x0, y0, x1 - x0, y1 - y0);
    266 }
    267 
    268 bool Path::contains(const FloatPoint& point, WindRule rule) const
    269 {
    270     if (!boundingRect().contains(point))
    271         return false;
    272 
    273     cairo_t* cr = platformPath()->m_cr;
    274     cairo_fill_rule_t cur = cairo_get_fill_rule(cr);
    275     cairo_set_fill_rule(cr, rule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
    276     bool contains = cairo_in_fill(cr, point.x(), point.y());
    277     cairo_set_fill_rule(cr, cur);
    278     return contains;
    279 }
    280 
    281 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
    282 {
    283     ASSERT(applier);
    284     cairo_t* cr = platformPath()->m_cr;
    285     GraphicsContext gc(cr);
    286     applier->strokeStyle(&gc);
    287 
    288     return cairo_in_stroke(cr, point.x(), point.y());
    289 }
    290 
    291 void Path::apply(void* info, PathApplierFunction function) const
    292 {
    293     cairo_t* cr = platformPath()->m_cr;
    294     cairo_path_t* path = cairo_copy_path(cr);
    295     cairo_path_data_t* data;
    296     PathElement pelement;
    297     FloatPoint points[3];
    298     pelement.points = points;
    299 
    300     for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
    301         data = &path->data[i];
    302         switch (data->header.type) {
    303         case CAIRO_PATH_MOVE_TO:
    304             pelement.type = PathElementMoveToPoint;
    305             pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
    306             function(info, &pelement);
    307             break;
    308         case CAIRO_PATH_LINE_TO:
    309             pelement.type = PathElementAddLineToPoint;
    310             pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
    311             function(info, &pelement);
    312             break;
    313         case CAIRO_PATH_CURVE_TO:
    314             pelement.type = PathElementAddCurveToPoint;
    315             pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y);
    316             pelement.points[1] = FloatPoint(data[2].point.x,data[2].point.y);
    317             pelement.points[2] = FloatPoint(data[3].point.x,data[3].point.y);
    318             function(info, &pelement);
    319             break;
    320         case CAIRO_PATH_CLOSE_PATH:
    321             pelement.type = PathElementCloseSubpath;
    322             function(info, &pelement);
    323             break;
    324         }
    325     }
    326     cairo_path_destroy(path);
    327 }
    328 
    329 void Path::transform(const AffineTransform& trans)
    330 {
    331     cairo_t* m_cr = platformPath()->m_cr;
    332     cairo_matrix_t c_matrix = cairo_matrix_t(trans);
    333     cairo_matrix_invert(&c_matrix);
    334     cairo_transform(m_cr, &c_matrix);
    335 }
    336 
    337 String Path::debugString() const
    338 {
    339     if (isEmpty())
    340         return String();
    341 
    342     String pathString;
    343     cairo_path_t* path = cairo_copy_path(platformPath()->m_cr);
    344     cairo_path_data_t* data;
    345 
    346     for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
    347         data = &path->data[i];
    348         switch (data->header.type) {
    349         case CAIRO_PATH_MOVE_TO:
    350             if (i < (path->num_data - path->data[i].header.length))
    351                 pathString += String::format("M%.2f,%.2f ",
    352                                       data[1].point.x, data[1].point.y);
    353             break;
    354         case CAIRO_PATH_LINE_TO:
    355             pathString += String::format("L%.2f,%.2f ",
    356                                       data[1].point.x, data[1].point.y);
    357             break;
    358         case CAIRO_PATH_CURVE_TO:
    359             pathString += String::format("C%.2f,%.2f,%.2f,%.2f,%.2f,%.2f ",
    360                                       data[1].point.x, data[1].point.y,
    361                                       data[2].point.x, data[2].point.y,
    362                                       data[3].point.x, data[3].point.y);
    363             break;
    364         case CAIRO_PATH_CLOSE_PATH:
    365             pathString += "Z ";
    366             break;
    367         }
    368     }
    369 
    370     cairo_path_destroy(path);
    371     return pathString.simplifyWhiteSpace();
    372 }
    373 
    374 } // namespace WebCore
    375