/* * Copyright (c) 2004 Nokia. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the * distribution. * * Neither the name of Nokia nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "GdkXftContext.h" #include "CGContextProvider.h" class PathElement; static void subpath_draw_stroke(gpointer data, gpointer user_data); static void subpath_draw_fill(gpointer data, gpointer user_data); static void path_point_element(gpointer data, gpointer user_data); class Path { public: Path() : subpaths(0), elements(0), subpath(0), sx(0), sy(0), closed(false) {} ~Path(); void beginPath(); /** Closes the topmost open subpath. Returns true, if the path iself was closed, false if one path had open subpath that was closed. */ void closePath(); void moveTo(int x, int y); /** Adds a line element from current position to given coordinates. */ void addLineElement(int x, int y); void addArcElement(int x, int y, float radius, float startAngle, float endAngle, int clockwise); void addArcToElement(int x1, int y1, int x2, int y2, float radius); void addCurveElement(int cp1x, int cp1y, int cp2x, int cp2y, int x, int y); void addQuadCurveElement(int cpx, int cpy, int x, int y); void addRectElement(const GdkRectangle * rect); void drawStroke(GdkXftContext * context); void drawFill(GdkXftContext * context); protected: /** Draws either filled or stroked element*/ void draw(GdkXftContext * context, bool fill); friend void subpath_draw_stroke(gpointer data, gpointer user_data); friend void subpath_draw_fill(gpointer data, gpointer user_data); private: Path(int x, int y) : subpaths(0), elements(0), subpath(0), sx(x), sy(y), closed(false) {} void addElement(PathElement* element); /** Removes all elements and subpaths. */ void clear(); /** Closes this subpath. */ void close(); GList* subpaths; GList* elements; /** current subpath, if any. */ Path * subpath; int sx, sy; bool closed; }; class PathElement { public: int startX() { return spX; } int startY() { return spY; } int endX() { return epX; } int endY() { return epY; } PathElement(int sx, int sy, int ex, int ey) : spX(sx), spY(sy), epX(ex), epY(ey), index(0) {} virtual ~PathElement() {} protected: /** Draws the individual path element to canvas. */ virtual void draw(GdkXftContext* context, bool fill) = 0; int spX; int spY; int epX; int epY; int index; friend void path_point_element(gpointer data, gpointer user_data); friend class Path; }; class LinePathElement : public PathElement { public: LinePathElement(int startX, int startY, int endX, int endY) : PathElement(startX, startY, endX, endY) {} protected: void draw(GdkXftContext * context, bool fill); friend class Path; }; class RectPathElement : public PathElement { public: RectPathElement(const GdkRectangle * rect) : PathElement(rect->x, rect->y, rect->width, rect->height) {} protected: void draw(GdkXftContext * context, bool fill); }; /* * invariants: (xftdraw != 0) == (xftGdkDraw != 0) * */ static void setXftClip(XftDraw* draw, GdkRegion* r, gint xoff, gint yoff) { assert(draw); assert(r); GdkRectangle* rects = 0; gint n_rects = 0; gdk_region_get_rectangles(r, &rects, &n_rects); XRectangle* xrects; if (n_rects) { xrects = g_new(XRectangle, n_rects); int i; for (i=0;ibeginPath(); } GdkXftContext::~GdkXftContext() { if (_clip) gdk_region_destroy(_clip); if (savedClip) gdk_region_destroy(savedClip); if (xftdraw) { XftDrawDestroy(xftdraw); g_object_unref(xftGdkDraw); } if (_fillColor) g_object_unref(_fillColor); if (path) { delete path; } g_object_unref(gc); g_object_unref(drawable); } void GdkXftContext::updateXftDraw() { assert(drawable); assert(gc); GdkDrawable *real_drawable = drawable; // if doublebuffering is on, drawable is actually window and internal // paint info contains the correct buffer. GdkWindow* window = GDK_IS_WINDOW(drawable) ? GDK_WINDOW(drawable) : 0; if (window) { gdk_window_get_internal_paint_info(window, &real_drawable, &xftxoff, &xftyoff); iswindow = true; } else { iswindow = false; xftxoff = xftyoff = 0; } g_object_ref(real_drawable); Colormap xcmap_new = GDK_COLORMAP_XCOLORMAP(gdk_drawable_get_colormap(real_drawable)); Visual *xvisual_new = GDK_VISUAL_XVISUAL(gdk_drawable_get_visual(real_drawable)); if (xftdraw && xcmap_new == xcmap && xvisual_new == xvisual) { XftDrawChange(xftdraw, GDK_DRAWABLE_XID(real_drawable)); g_object_unref(xftGdkDraw); } else { if (xftdraw) { XftDrawDestroy(xftdraw); g_object_unref(xftGdkDraw); } xcmap = xcmap_new; xvisual = xvisual_new; xftdraw = XftDrawCreate(GDK_DRAWABLE_XDISPLAY(real_drawable), GDK_DRAWABLE_XID(real_drawable), xvisual, xcmap); } xftGdkDraw = real_drawable; } GdkDrawable* GdkXftContext::realDrawable() { return xftGdkDraw; } void GdkXftContext::realTranslate(int *x, int *y) { assert(x); assert(y); if (iswindow) { gdk_window_get_internal_paint_info(drawable, NULL, &xftxoff, &xftyoff); *x += xftxoff; *y += xftyoff; } } void GdkXftContext::clearClip() { if (_clip) { gdk_gc_set_clip_region(gc, NULL); gdk_region_destroy(_clip); _clip = 0; } } void GdkXftContext::addClip(GdkRectangle* rect) { if (!_clip) { _clip = gdk_region_rectangle(rect); } else { gdk_region_union_with_rect(_clip, rect); } gdk_gc_set_clip_origin(gc, 0, 0); gdk_gc_set_clip_region(gc, _clip); setXftClip(xftdraw, _clip, xftxoff, xftyoff); } void GdkXftContext::saveGraphicsState() { assert(!savedClip); if (_clip) savedClip = gdk_region_copy(_clip); } void GdkXftContext::restoreGraphicsState() { if (_clip) gdk_region_destroy(_clip); _clip = savedClip; savedClip = 0; gdk_gc_set_clip_region(gc, _clip); } void GdkXftContext::regionExpiresAt(GTimeVal* moment, GdkRectangle* rect) { assert(provider); provider->regionExpiresAt(moment, rect, this); } #define LOG(message) g_printerr("%s: %s\n", __PRETTY_FUNCTION__, message) CGImageRef GdkXftContext::createImage() { return new CGImage(drawable); } void GdkXftContext::drawImage(const GdkRectangle * rect, const CGImageRef image) { gdk_draw_drawable(drawable, gc, image->image, rect->x, rect->y, 0, 0, rect->width, rect->height); } void GdkXftContext::flush() { LOG("KWIQ: NotImplemented GdkXftContext::flush()" ); } void GdkXftContext::setLineCap (CGLineCap cap) { lineCap = cap; GdkGCValues values; switch(lineCap) { case kCGLineCapButt: values.cap_style = GDK_CAP_BUTT; break; case kCGLineCapRound: values.cap_style = GDK_CAP_ROUND; break; case kCGLineCapSquare: values.cap_style = GDK_CAP_PROJECTING; break; } gdk_gc_set_values(gc, &values, GDK_GC_CAP_STYLE); } void GdkXftContext::setLineJoin (CGLineJoin join) { lineJoin = join; GdkGCValues values; switch(join) { case kCGLineJoinMiter: values.join_style = GDK_JOIN_MITER; break; case kCGLineJoinRound: values.join_style = GDK_JOIN_ROUND; break; case kCGLineJoinBevel: values.join_style = GDK_JOIN_BEVEL; break; } gdk_gc_set_values(gc, &values, GDK_GC_JOIN_STYLE); } void GdkXftContext::setLineWidth (float width) { lineWidth = width; GdkGCValues values; values.line_width = (int) width; gdk_gc_set_values(gc, &values, GDK_GC_LINE_WIDTH); } void GdkXftContext::setMiterLimit (float limit) { miterLimit = limit; } void GdkXftContext::setRGBStrokeColor(float red, float green, float blue, float alpha) { if (_strokeColor) g_object_unref(_strokeColor); _strokeColor = g_new0(GdkColor, 1); _strokeColor->red = (guint) (65536.0f * red); _strokeColor->green = (guint) (65536.0f * green); _strokeColor->blue = (guint) (65536.0f * blue); g_object_ref(_strokeColor); } void GdkXftContext::setRGBFillColor(float red, float green, float blue, float alpha) { if (_fillColor) g_object_unref(_fillColor); _fillColor = g_new0(GdkColor, 1); _fillColor->red = (guint) (65536.0f * red); _fillColor->green = (guint) (65536.0f * green); _fillColor->blue = (guint) (65536.0f * blue); g_object_ref(_fillColor); } void GdkXftContext::setGrayStrokeColor(float gray, float alpha) { setRGBStrokeColor(gray, gray, gray, alpha); } void GdkXftContext::setGrayFillColor(float gray, float alpha) { setRGBFillColor(gray, gray, gray, alpha); } void GdkXftContext::setCMYKStrokeColor(float c, float m, float y, float k, float a) { LOG("KWIQ: NotImplemented GdkXftContext::setCMYKStrokeColor(float, float, float, float, float)" ); } void GdkXftContext::setCMYKFillColor(float c, float m, float y, float k, float a) { LOG("KWIQ: NotImplemented GdkXftContext::setCMYKFillColor(float, float, float, float, float)" ); } void GdkXftContext::addArc (float x, float y, float radius, float startAngle, float endAngle, int clockwise) { path->addArcElement((int) x, (int) y, radius, startAngle, endAngle, clockwise); } void GdkXftContext::addArcToPoint (float x1, float y1, float x2, float y2, float radius) { path->addArcToElement((int) x1, (int) y1, (int) x2, (int) y2, radius); } void GdkXftContext::addCurveToPoint (float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) { path->addCurveElement((int) cp1x, (int) cp1y, (int) cp2x, (int) cp2y, (int) x, (int) y); } void GdkXftContext::addLineToPoint (float x, float y) { path->addLineElement((int) x, (int) y); } void GdkXftContext::addQuadCurveToPoint (float cpx, float cpy, float x, float y) { path->addQuadCurveElement((int) cpx, (int) cpy, (int) x, (int) y); } void GdkXftContext::addRect (const GdkRectangle * rect) { path->addRectElement(rect); } void GdkXftContext::beginPath() { path->beginPath(); } void GdkXftContext::closePath() { path->closePath(); } void GdkXftContext::moveToPoint (float x, float y) { path->moveTo((int) x, (int) y); } void GdkXftContext::clearRect (const GdkRectangle * rect) { LOG("KWIQ: NotImplemented GdkXftContext::clearRect(GdkRectangle *)" ); } void GdkXftContext::fillPath () { closePath(); path->drawFill(this); } void GdkXftContext::fillRect (const GdkRectangle * rect) { // fill rectangle if (_fillColor) { gdk_gc_set_rgb_fg_color(gc, _fillColor); gdk_draw_rectangle(drawable, gc, TRUE, rect->x+1, rect->y+1, rect->width-1, rect->height-1); } // draw outline strokeRect(rect); } void GdkXftContext::strokePath () { path->drawStroke(this); } void GdkXftContext::strokeRect (const GdkRectangle * rect) { // draw outline if (_strokeColor) { gdk_gc_set_rgb_fg_color(gc, _strokeColor); gdk_draw_rectangle(drawable, gc, FALSE, rect->x, rect->y, rect->width, rect->height); } } void GdkXftContext::strokeRectWithWidth (const GdkRectangle * rect, float width) { float oldW = lineWidth; setLineWidth(width); strokeRect(rect); setLineWidth(oldW); } void GdkXftContext::clip () { LOG("KWIQ: NotImplemented GdkXftContext::clip()" ); } void GdkXftContext::setAlpha (float a) { alpha = a; } void GdkXftContext::setShadow (CGSize offset, float blur) { LOG("KWIQ: NotImplemented GdkXftContext::setShadow(CGSize, float)" ); } void GdkXftContext::setShadowWithColor (CGSize offset, float blur, CGColorRef colorRef) { LOG("KWIQ: NotImplemented GdkXftContext::setShadowWithColor(CGSize, float, CGColorRef)" ); } void GdkXftContext::rotateCTM (float angle) { LOG("KWIQ: NotImplemented GdkXftContext::rotateCTM(float)" ); } void GdkXftContext::scaleCTM (float sx, float sy) { LOG("KWIQ: NotImplemented GdkXftContext::scaleCTM(float, float)" ); } void GdkXftContext::translateCTM (float tx, float ty) { LOG("KWIQ: NotImplemented GdkXftContext::translateCTM(float, float)" ); } Path::~Path() { GList * first; if (subpath) delete subpath; while(subpaths) { first = g_list_first(subpaths); subpaths = g_list_remove(subpaths, first->data); delete static_cast(first->data); } while(elements) { first = g_list_first(elements); elements = g_list_remove(elements, first->data); delete static_cast(first->data); } } void Path::beginPath() { // http://www.whatwg.org/specs/web-apps/current-work/ // section 6.1.1.8 // // The beginPath() method resets the list of subpaths to an empty // list, and calls moveTo() with the point (0,0). When the context // is created, a call to beginPath() is implied. GList * first; while(subpaths) { first = g_list_first(subpaths); subpaths = g_list_remove(subpaths, first->data); delete static_cast(first->data); } if (subpath) delete subpath; moveTo(0, 0); } void Path::closePath() { if (subpath && subpath->elements) { subpath->close(); subpaths = g_list_append(subpaths, subpath); } subpath = 0L; } /** Called only on subpaths. */ void Path::close() { // connect the beginning to end with an implicit line // not needed, since GdkPolygon does this internally. closed = true; } void Path::moveTo(int x, int y) { if (subpath) { closePath(); } subpath = new Path(x, y); } /** Adds a line element from current position to given coordinates. */ void Path::addLineElement(int x, int y) { if (subpath) { subpath->addLineElement(x, y); } else { // we're in subpath. addElement(new LinePathElement(sx, sy, x, y)); sx = x; sy = y; } } void Path::addArcElement(int x, int y, float radius, float startAngle, float endAngle, int clockwise) { LOG("KWIQ: NotImplemented Path::addArcToElement(int, int, float, float, float, int)" ); } void Path::addArcToElement(int x1, int y1, int x2, int y2, float radius) { LOG("KWIQ: NotImplemented Path::addArcToElement(int, int, int, int, float)" ); } void Path::addCurveElement(int cp1x, int cp1y, int cp2x, int cp2y, int x, int y) { LOG("KWIQ: NotImplemented Path::addCurveElement(int, int, int, int, int, int)" ); } void Path::addQuadCurveElement(int cpx, int cpy, int x, int y) { LOG("KWIQ: NotImplemented Path::addQuadCurveElement(int, int, int, int)" ); } void Path::addRectElement (const GdkRectangle * rect) { // this isn't actually in line with specifications, the result is the same. // Existing path is closed, a subpath containing rectangle is added and a // new subpath is opened at 0,0. moveTo(0, 0); Path * subpath = new Path(); // no need to close. subpath->addElement(new RectPathElement(rect)); subpaths = g_list_append(subpaths, subpath); } static void subpath_draw_stroke(gpointer data, gpointer user_data) { Path * path = static_cast(data); GdkXftContext * context = static_cast(user_data); path->draw(context, false); } static void subpath_draw_fill(gpointer data, gpointer user_data) { Path * path = static_cast(data); GdkXftContext * context = static_cast(user_data); path->draw(context, true); } static void path_point_element(gpointer data, gpointer user_data) { PathElement * elem = static_cast(data); GdkPoint * points = static_cast(user_data); if (elem->index == 0) { // first element needs to add start point as well points->x = elem->spX; points->y = elem->spY; } for(int i = 0;i <= elem->index; i++, ++points) {} points->x = elem->epX; points->y = elem->epY; } void Path::drawStroke(GdkXftContext * context) { if (subpaths || subpath) { closePath(); g_list_foreach(subpaths, subpath_draw_stroke, context); } else { draw(context, false); } } void Path::drawFill(GdkXftContext * context) { if (subpaths || subpath) { closePath(); g_list_foreach(subpaths, subpath_draw_fill, context); } else { draw(context, true); } } void Path::draw(GdkXftContext * context, bool fill) { if (elements && g_list_length(elements) == 1) { // special case -- rectangle. static_cast(elements->data)->draw(context, fill); } else { int len = g_list_length(elements) + 1; // fill in the points. GdkPoint points[len]; g_list_foreach(elements, path_point_element, &points); if (fill) gdk_gc_set_rgb_fg_color(context->gc, context->fillColor()); else gdk_gc_set_rgb_fg_color(context->gc, context->strokeColor()); gdk_draw_polygon(context->drawable, context->gc, fill, points, len); } } void Path::addElement(PathElement* element) { if (!closed) { element->index = g_list_length(elements); elements = g_list_append(elements, element); } } void LinePathElement::draw(GdkXftContext * context, bool fill) { // draw outline if (context->strokeColor()) { gdk_gc_set_rgb_fg_color(context->gc, context->strokeColor()); gdk_draw_line(context->drawable, context->gc, spX, spY, epX, epY); } } void RectPathElement::draw(GdkXftContext * context, bool fill) { // fill rectangle if (fill && context->fillColor()) { gdk_gc_set_rgb_fg_color(context->gc, context->fillColor()); gdk_draw_rectangle(context->drawable, context->gc, TRUE, spX+1, spY+1, epX-1, epY-1); } // draw outline if (context->strokeColor()) { gdk_gc_set_rgb_fg_color(context->gc, context->strokeColor()); gdk_draw_rectangle(context->drawable, context->gc, FALSE, spX, spY, epX, epY); } } #undef LOG