blob: 952aa2bc4d0532e68de2190da249a89845820209 [file] [log] [blame]
/*
* Copyright (C) 2006 Dirk Mueller <mueller@kde.org>
* Copyright (C) 2006 Zack Rusin <zack@kde.org>
* Copyright (C) 2006 George Staikos <staikos@kde.org>
* Copyright (C) 2006 Simon Hausmann <hausmann@kde.org>
* Copyright (C) 2006 Allan Sandfeld Jensen <sandfeld@kde.org>
* Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 "config.h"
#include "GraphicsContext.h"
#include "Path.h"
#include <QPainter>
#include <QPainterPath>
#include <QPaintDevice>
#include <QStack>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#if SVG_SUPPORT
#include "KRenderingDeviceQt.h"
#endif
#define notImplemented() do { fprintf(stderr, "FIXME: UNIMPLEMENTED: %s:%d\n", __FILE__, __LINE__); } while(0)
namespace WebCore {
static QPainter::CompositionMode toQtCompositionMode(CompositeOperator op)
{
switch (op) {
case CompositeClear:
return QPainter::CompositionMode_Clear;
case CompositeCopy:
return QPainter::CompositionMode_Source;
case CompositeSourceOver:
return QPainter::CompositionMode_SourceOver;
case CompositeSourceIn:
return QPainter::CompositionMode_SourceIn;
case CompositeSourceOut:
return QPainter::CompositionMode_SourceOut;
case CompositeSourceAtop:
return QPainter::CompositionMode_SourceAtop;
case CompositeDestinationOver:
return QPainter::CompositionMode_DestinationOver;
case CompositeDestinationIn:
return QPainter::CompositionMode_DestinationIn;
case CompositeDestinationOut:
return QPainter::CompositionMode_DestinationOut;
case CompositeDestinationAtop:
return QPainter::CompositionMode_DestinationAtop;
case CompositeXOR:
return QPainter::CompositionMode_Xor;
case CompositePlusDarker:
return QPainter::CompositionMode_SourceOver;
case CompositeHighlight:
return QPainter::CompositionMode_SourceOver;
case CompositePlusLighter:
return QPainter::CompositionMode_SourceOver;
}
return QPainter::CompositionMode_SourceOver;
}
static Qt::PenCapStyle toQtLineCap(LineCap lc)
{
switch (lc) {
case ButtCap:
return Qt::FlatCap;
case RoundCap:
return Qt::RoundCap;
case SquareCap:
return Qt::SquareCap;
}
return Qt::FlatCap;
}
static Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
{
switch (lj) {
case MiterJoin:
return Qt::SvgMiterJoin;
case RoundJoin:
return Qt::RoundJoin;
case BevelJoin:
return Qt::BevelJoin;
}
return Qt::MiterJoin;
}
struct TransparencyLayer
{
TransparencyLayer(const QPainter& p, int width, int height)
{
pixmap = QPixmap(width, height);
painter = new QPainter(&pixmap);
painter->setPen(p.pen());
painter->setBrush(p.brush());
painter->setMatrix(p.matrix());
}
TransparencyLayer()
: painter(0)
{
}
void cleanup()
{
delete painter;
}
QPixmap pixmap;
QPainter* painter;
qreal opacity;
};
struct TextShadow
{
TextShadow()
: x(0)
, y(0)
, blur(0)
{
}
bool isNull() { return !x && !y && !blur; }
int x;
int y;
int blur;
Color color;
};
class GraphicsContextPlatformPrivate
{
public:
GraphicsContextPlatformPrivate(QPainter* painter);
~GraphicsContextPlatformPrivate();
QPainter& p()
{
if (layers.isEmpty()) {
if (redirect)
return *redirect;
return *painter;
} else
return *layers.top().painter;
}
QPaintDevice* device;
QStack<TransparencyLayer> layers;
QPainter* redirect;
IntRect focusRingClip;
TextShadow shadow;
private:
QPainter* painter;
};
GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
: device(p->device())
{
painter = p;
redirect = 0;
// FIXME: Maybe only enable in SVG mode?
painter->setRenderHint(QPainter::Antialiasing);
}
GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
{
}
GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
: m_common(createGraphicsContextPrivate())
, m_data(new GraphicsContextPlatformPrivate(context))
{
setPaintingDisabled(!context);
}
GraphicsContext::~GraphicsContext()
{
while(!m_data->layers.isEmpty())
endTransparencyLayer();
destroyGraphicsContextPrivate(m_common);
delete m_data;
}
PlatformGraphicsContext* GraphicsContext::platformContext() const
{
return &m_data->p();
}
void GraphicsContext::savePlatformState()
{
m_data->p().save();
}
void GraphicsContext::restorePlatformState()
{
m_data->p().restore();
}
/* FIXME: DISABLED WHILE MERGING BACK FROM UNITY
void GraphicsContext::drawTextShadow(const TextRun& run, const IntPoint& point, const TextStyle& style)
{
if (paintingDisabled())
return;
if (m_data->shadow.isNull())
return;
TextShadow* shadow = &m_data->shadow;
if (shadow->blur <= 0) {
Pen p = pen();
setPen(shadow->color);
font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
setPen(p);
} else {
const int thickness = shadow->blur;
// FIXME: OPTIMIZE: limit the area to only the actually painted area + 2*thickness
const int w = m_data->p().device()->width();
const int h = m_data->p().device()->height();
const QRgb color = qRgb(255, 255, 255);
const QRgb bgColor = qRgb(0, 0, 0);
QImage image(QSize(w, h), QImage::Format_ARGB32);
image.fill(bgColor);
QPainter p;
Pen curPen = pen();
p.begin(&image);
setPen(color);
m_data->redirect = &p;
font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
m_data->redirect = 0;
p.end();
setPen(curPen);
int md = thickness * thickness; // max-dist^2
// blur map/precalculated shadow-decay
float* bmap = (float*) alloca(sizeof(float) * (md + 1));
for (int n = 0; n <= md; n++) {
float f;
f = n / (float) (md + 1);
f = 1.0 - f * f;
bmap[n] = f;
}
float factor = 0.0; // maximal potential opacity-sum
for (int n = -thickness; n <= thickness; n++) {
for (int m = -thickness; m <= thickness; m++) {
int d = n * n + m * m;
if (d <= md)
factor += bmap[d];
}
}
// alpha map
float* amap = (float*) alloca(sizeof(float) * (h * w));
memset(amap, 0, h * w * (sizeof(float)));
for (int j = thickness; j<h-thickness; j++) {
for (int i = thickness; i<w-thickness; i++) {
QRgb col = image.pixel(i,j);
if (col == bgColor)
continue;
float g = qAlpha(col);
g = g / 255;
for (int n = -thickness; n <= thickness; n++) {
for (int m = -thickness; m <= thickness; m++) {
int d = n * n + m * m;
if (d > md)
continue;
float f = bmap[d];
amap[(i + m) + (j + n) * w] += (g * f);
}
}
}
}
QImage res(QSize(w,h),QImage::Format_ARGB32);
int r = shadow->color.red();
int g = shadow->color.green();
int b = shadow->color.blue();
int a1 = shadow->color.alpha();
// arbitratry factor adjustment to make shadows more solid.
factor = 1.333 / factor;
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
int a = (int) (amap[i + j * w] * factor * a1);
if (a > 255)
a = 255;
res.setPixel(i,j, qRgba(r, g, b, a));
}
}
m_data->p().drawImage(0, 0, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
}
}
*/
// Draws a filled rectangle with a stroked border.
void GraphicsContext::drawRect(const IntRect& rect)
{
if (paintingDisabled())
return;
m_data->p().drawRect(rect);
}
// FIXME: Now that this is refactored, it should be shared by all contexts.
static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth,
const Pen::PenStyle& penStyle)
{
// For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
// works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
// (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
// us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
if (p1.x() == p2.x()) {
p1.setY(p1.y() + strokeWidth);
p2.setY(p2.y() - strokeWidth);
} else {
p1.setX(p1.x() + strokeWidth);
p2.setX(p2.x() - strokeWidth);
}
}
if (((int) strokeWidth) % 2) {
if (p1.x() == p2.x()) {
// We're a vertical line. Adjust our x.
p1.setX(p1.x() + 0.5);
p2.setX(p2.x() + 0.5);
} else {
// We're a horizontal line. Adjust our y.
p1.setY(p1.y() + 0.5);
p2.setY(p2.y() + 0.5);
}
}
}
// This is only used to draw borders.
void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
{
if (paintingDisabled())
return;
FloatPoint p1 = point1;
FloatPoint p2 = point2;
adjustLineToPixelBounderies(p1, p2, pen().width(), pen().style());
m_data->p().drawLine(p1, p2);
}
// This method is only used to draw the little circles used in lists.
void GraphicsContext::drawEllipse(const IntRect& rect)
{
if (paintingDisabled())
return;
m_data->p().drawEllipse(rect);
}
void GraphicsContext::drawArc(const IntRect& rect, float thickness,
int startAngle, int angleSpan)
{
if (paintingDisabled())
return;
const QPen oldPen = m_data->p().pen();
QPen nPen = oldPen;
nPen.setWidthF(thickness);
m_data->p().setPen(nPen);
m_data->p().drawArc(rect, startAngle, angleSpan);
m_data->p().setPen(oldPen);
}
void GraphicsContext::drawConvexPolygon(size_t npoints, const IntPoint* points)
{
if (paintingDisabled())
return;
m_data->p().drawConvexPolygon(reinterpret_cast<const QPoint*>(points), npoints);
}
void GraphicsContext::fillRect(const IntRect& rect, const Color& c)
{
if (paintingDisabled())
return;
m_data->p().fillRect(rect, QColor(c.red(), c.green(), c.blue(), c.alpha()));
}
void GraphicsContext::fillRect(const FloatRect& rect, const Color& c)
{
if (paintingDisabled())
return;
m_data->p().fillRect(rect, QColor(c.red(), c.green(), c.blue(), c.alpha()));
}
void GraphicsContext::addClip(const IntRect& rect)
{
if (paintingDisabled())
return;
QPainterPath path;
path.addRect(QRectF(rect));
m_data->p().setClipPath(path, Qt::UniteClip);
}
void GraphicsContext::drawFocusRing(const Color& color)
{
if (paintingDisabled())
return;
notImplemented();
}
void GraphicsContext::setFocusRingClip(const IntRect& rect)
{
if (paintingDisabled())
return;
m_data->focusRingClip = rect;
}
void GraphicsContext::clearFocusRingClip()
{
if (paintingDisabled())
return;
m_data->focusRingClip = IntRect();
}
void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset,
int width, bool printing)
{
if (paintingDisabled())
return;
IntPoint origin = point + IntSize(0, yOffset + 1);
IntPoint endPoint = origin + IntSize(width, 0);
drawLine(origin, endPoint);
}
void GraphicsContext::drawLineForMisspelling(const IntPoint& point, int width)
{
if (paintingDisabled())
return;
notImplemented();
}
FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
{
QRectF rect(frect);
rect = m_data->p().deviceMatrix().mapRect(rect);
QRect result = rect.toRect(); //round it
return FloatRect(QRectF(result));
}
void GraphicsContext::setShadow(const IntSize& pos, int blur, const Color &color)
{
if (paintingDisabled())
return;
m_data->shadow.x = pos.width();
m_data->shadow.y = pos.height();
m_data->shadow.blur = blur;
m_data->shadow.color = color;
}
void GraphicsContext::clearShadow()
{
if (paintingDisabled())
return;
m_data->shadow = TextShadow();
}
void GraphicsContext::beginTransparencyLayer(float opacity)
{
if (paintingDisabled())
return;
TransparencyLayer layer(m_data->p(),
m_data->device->width(),
m_data->device->height());
layer.opacity = opacity;
m_data->layers.push(layer);
}
void GraphicsContext::endTransparencyLayer()
{
if (paintingDisabled())
return;
TransparencyLayer layer = m_data->layers.pop();
layer.painter->end();
m_data->p().save();
m_data->p().setOpacity(layer.opacity);
m_data->p().drawPixmap(0, 0, layer.pixmap);
m_data->p().restore();
m_data->p().end();
layer.cleanup();
}
void GraphicsContext::clearRect(const FloatRect& rect)
{
if (paintingDisabled())
return;
m_data->p().eraseRect(rect);
}
void GraphicsContext::strokeRect(const FloatRect& rect, float width)
{
if (paintingDisabled())
return;
QPainterPath path; path.addRect(rect);
QPen nPen = m_data->p().pen();
nPen.setWidthF(width);
m_data->p().strokePath(path, nPen);
}
void GraphicsContext::setLineWidth(float width)
{
if (paintingDisabled())
return;
QPen nPen = m_data->p().pen();
nPen.setWidthF(width);
m_data->p().setPen(nPen);
}
void GraphicsContext::setLineCap(LineCap lc)
{
if (paintingDisabled())
return;
QPen nPen = m_data->p().pen();
nPen.setCapStyle(toQtLineCap(lc));
m_data->p().setPen(nPen);
}
void GraphicsContext::setLineJoin(LineJoin lj)
{
if (paintingDisabled())
return;
QPen nPen = m_data->p().pen();
nPen.setJoinStyle(toQtLineJoin(lj));
m_data->p().setPen(nPen);
}
void GraphicsContext::setMiterLimit(float limit)
{
if (paintingDisabled())
return;
QPen nPen = m_data->p().pen();
nPen.setMiterLimit(limit);
m_data->p().setPen(nPen);
}
void GraphicsContext::setAlpha(float opacity)
{
if (paintingDisabled())
return;
m_data->p().setOpacity(opacity);
}
void GraphicsContext::setCompositeOperation(CompositeOperator op)
{
if (paintingDisabled())
return;
m_data->p().setCompositionMode(toQtCompositionMode(op));
}
void GraphicsContext::clip(const Path& path)
{
if (paintingDisabled())
return;
m_data->p().setClipPath(*path.platformPath());
}
void GraphicsContext::translate(const FloatSize& s)
{
if (paintingDisabled())
return;
m_data->p().scale(s.width(), s.height());
}
void GraphicsContext::rotate(float radians)
{
if (paintingDisabled())
return;
m_data->p().rotate(radians);
}
void GraphicsContext::scale(const FloatSize& s)
{
if (paintingDisabled())
return;
m_data->p().scale(s.width(), s.height());
}
void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
int thickness)
{
if (paintingDisabled())
return;
addClip(rect);
QPainterPath path;
// Add outer ellipse
path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
// Add inner ellipse.
path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
path.setFillRule(Qt::OddEvenFill);
m_data->p().setClipPath(path, Qt::IntersectClip);
}
void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft,
const IntSize& topRight, const IntSize& bottomLeft,
const IntSize& bottomRight)
{
if (paintingDisabled())
return;
// Need sufficient width and height to contain these curves. Sanity check our top/bottom
// values and our width/height values to make sure the curves can all fit.
int requiredWidth = qMax(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
if (requiredWidth > rect.width())
return;
int requiredHeight = qMax(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
if (requiredHeight > rect.height())
return;
// Clip to our rect.
addClip(rect);
// OK, the curves can fit.
QPainterPath path;
// Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
// not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
// normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
// be subsumed by the other).
path.addEllipse(QRectF(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
path.addEllipse(QRectF(rect.right() - topRight.width() * 2, rect.y(),
topRight.width() * 2, topRight.height() * 2));
path.addEllipse(QRectF(rect.x(), rect.bottom() - bottomLeft.height() * 2,
bottomLeft.width() * 2, bottomLeft.height() * 2));
path.addEllipse(QRectF(rect.right() - bottomRight.width() * 2,
rect.bottom() - bottomRight.height() * 2,
bottomRight.width() * 2, bottomRight.height() * 2));
int topLeftRightHeightMax = qMax(topLeft.height(), topRight.height());
int bottomLeftRightHeightMax = qMax(bottomLeft.height(), bottomRight.height());
int topBottomLeftWidthMax = qMax(topLeft.width(), bottomLeft.width());
int topBottomRightWidthMax = qMax(topRight.width(), bottomRight.width());
// Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
path.addRect(QRectF(rect.x() + topLeft.width(),
rect.y(),
rect.width() - topLeft.width() - topRight.width(),
topLeftRightHeightMax));
path.addRect(QRectF(rect.x() + bottomLeft.width(), rect.bottom() - bottomLeftRightHeightMax,
rect.width() - bottomLeft.width() - bottomRight.width(), bottomLeftRightHeightMax));
path.addRect(QRectF(rect.x(),
rect.y() + topLeft.height(),
topBottomLeftWidthMax,
rect.height() - topLeft.height() - bottomLeft.height()));
path.addRect(QRectF(rect.right() - topBottomRightWidthMax,
rect.y() + topRight.height(),
topBottomRightWidthMax,
rect.height() - topRight.height() - bottomRight.height()));
path.addRect(QRectF(rect.x() + topBottomLeftWidthMax,
rect.y() + topLeftRightHeightMax,
rect.width() - topBottomLeftWidthMax - topBottomRightWidthMax,
rect.height() - topLeftRightHeightMax - bottomLeftRightHeightMax));
path.setFillRule(Qt::WindingFill);
m_data->p().setClipPath(path, Qt::IntersectClip);
}
#if SVG_SUPPORT
KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
{
return new KRenderingDeviceContextQt(platformContext());
}
#endif
}
// vim: ts=4 sw=4 et