blob: b6182cd04d37d4ed1a3e2c97b27bf299c951206f [file] [log] [blame]
/*
* Copyright (C) 2003 Apple Computer, Inc. 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.
*/
#import "KWQPainter.h"
#import "KWQWidget.h"
#import "KWQFontMetrics.h"
#import "KWQPixmap.h"
#import "KWQPtrStack.h"
#import "KWQPointArray.h"
#import "KWQPaintDevice.h"
#import "KWQPrinter.h"
#import "KWQAssertions.h"
#import "WebCoreImageRenderer.h"
#import "WebCoreTextRenderer.h"
#import "WebCoreTextRendererFactory.h"
struct QPState {
QPState() : paintingDisabled(false) { }
QFont font;
QPen pen;
QBrush brush;
QRegion clip;
bool paintingDisabled;
};
struct QPainterPrivate {
QPainterPrivate() : textRenderer(0) { }
~QPainterPrivate() { [textRenderer release]; }
QPState state;
QPtrStack<QPState> stack;
id <WebCoreTextRenderer> textRenderer;
QFont textRendererFont;
};
QPainter::QPainter() : data(new QPainterPrivate), _isForPrinting(false)
{
}
QPainter::QPainter(bool forPrinting) : data(new QPainterPrivate)
{
_isForPrinting = forPrinting;
}
QPainter::~QPainter()
{
delete data;
}
QPaintDevice *QPainter::device() const
{
static QPrinter printer;
static QPaintDevice screen;
return _isForPrinting ? &printer : &screen;
}
const QFont &QPainter::font() const
{
return data->state.font;
}
void QPainter::setFont(const QFont &aFont)
{
data->state.font = aFont;
}
QFontMetrics QPainter::fontMetrics() const
{
return data->state.font;
}
const QPen &QPainter::pen() const
{
return data->state.pen;
}
void QPainter::setPen(const QPen &pen)
{
data->state.pen = pen;
}
void QPainter::setPen(PenStyle style)
{
data->state.pen.setStyle(style);
data->state.pen.setColor(Qt::black);
data->state.pen.setWidth(0);
}
void QPainter::setPen(QRgb rgb)
{
data->state.pen.setStyle(SolidLine);
data->state.pen.setColor(rgb);
data->state.pen.setWidth(0);
}
void QPainter::setBrush(const QBrush &brush)
{
data->state.brush = brush;
}
void QPainter::setBrush(BrushStyle style)
{
data->state.brush.setStyle(style);
data->state.brush.setColor(Qt::black);
}
void QPainter::setBrush(QRgb rgb)
{
data->state.brush.setStyle(SolidPattern);
data->state.brush.setColor(rgb);
}
const QBrush &QPainter::brush() const
{
return data->state.brush;
}
QRect QPainter::xForm(const QRect &aRect) const
{
// No difference between device and model coords, so the identity transform is ok.
return aRect;
}
void QPainter::save()
{
data->stack.push(new QPState(data->state));
[NSGraphicsContext saveGraphicsState];
}
void QPainter::restore()
{
if (data->stack.isEmpty()) {
ERROR("ERROR void QPainter::restore() stack is empty");
return;
}
QPState *ps = data->stack.pop();
data->state = *ps;
delete ps;
[NSGraphicsContext restoreGraphicsState];
}
// Draws a filled rectangle with a stroked border.
void QPainter::drawRect(int x, int y, int w, int h)
{
if (data->state.paintingDisabled)
return;
if (data->state.brush.style() != NoBrush) {
_setColorFromBrush();
NSRectFillUsingOperation (NSMakeRect(x,y,w,h), NSCompositeSourceOver);
}
if (data->state.pen.style() != NoPen) {
_setColorFromPen();
NSFrameRect(NSMakeRect(x, y, w, h));
}
}
void QPainter::_setColorFromBrush()
{
[data->state.brush.color().getNSColor() set];
}
void QPainter::_setColorFromPen()
{
[data->state.pen.color().getNSColor() set];
}
// This is only used to draw borders.
void QPainter::drawLine(int x1, int y1, int x2, int y2)
{
if (data->state.paintingDisabled)
return;
PenStyle penStyle = data->state.pen.style();
if (penStyle == NoPen)
return;
float width = data->state.pen.width();
if (width < 1)
width = 1;
NSPoint p1 = NSMakePoint(x1, y1);
NSPoint p2 = NSMakePoint(x2, y2);
// 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 == DotLine || penStyle == DashLine) {
if (x1 == x2) {
p1.y += width;
p2.y -= width;
}
else {
p1.x += width;
p2.x -= width;
}
}
if (((int)width)%2) {
if (x1 == x2) {
// We're a vertical line. Adjust our x.
p1.x += 0.5;
p2.x += 0.5;
}
else {
// We're a horizontal line. Adjust our y.
p1.y += 0.5;
p2.y += 0.5;
}
}
NSBezierPath *path = [[NSBezierPath alloc] init];
[path setLineWidth:width];
int patWidth = 0;
switch (penStyle) {
case NoPen:
case SolidLine:
break;
case DotLine:
patWidth = (int)width;
break;
case DashLine:
patWidth = 3*(int)width;
break;
}
_setColorFromPen();
NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
BOOL flag = [graphicsContext shouldAntialias];
[graphicsContext setShouldAntialias: NO];
if (patWidth) {
// Do a rect fill of our endpoints. This ensures we always have the
// appearance of being a border. We then draw the actual dotted/dashed line.
if (x1 == x2) {
NSRectFill(NSMakeRect(p1.x-width/2, p1.y-width, width, width));
NSRectFill(NSMakeRect(p2.x-width/2, p2.y, width, width));
}
else {
NSRectFill(NSMakeRect(p1.x-width, p1.y-width/2, width, width));
NSRectFill(NSMakeRect(p2.x, p2.y-width/2, width, width));
}
// Example: 80 pixels with a width of 30 pixels.
// Remainder is 20. The maximum pixels of line we could paint
// will be 50 pixels.
int distance = ((x1 == x2) ? (y2 - y1) : (x2 - x1)) - 2*(int)width;
int remainder = distance%patWidth;
int coverage = distance-remainder;
int numSegments = coverage/patWidth;
float patternOffset = 0;
// Special case 1px dotted borders for speed.
if (patWidth == 1)
patternOffset = 1.0;
else {
bool evenNumberOfSegments = numSegments%2 == 0;
if (remainder)
evenNumberOfSegments = !evenNumberOfSegments;
if (evenNumberOfSegments) {
if (remainder) {
patternOffset += patWidth - remainder;
patternOffset += remainder/2;
}
else
patternOffset = patWidth/2;
}
else if (!evenNumberOfSegments) {
if (remainder)
patternOffset = (patWidth - remainder)/2;
}
}
const float dottedLine[2] = { patWidth, patWidth };
[path setLineDash:dottedLine count:2 phase:patternOffset];
}
[path moveToPoint:p1];
[path lineToPoint:p2];
[path stroke];
[path release];
[graphicsContext setShouldAntialias: flag];
}
// This method is only used to draw the little circles used in lists.
void QPainter::drawEllipse(int x, int y, int w, int h)
{
if (data->state.paintingDisabled)
return;
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect: NSMakeRect (x, y, w, h)];
if (data->state.brush.style() != NoBrush) {
_setColorFromBrush();
[path fill];
}
if (data->state.pen.style() != NoPen) {
_setColorFromPen();
[path stroke];
}
}
// Only supports arc on circles. That's all khtml needs.
void QPainter::drawArc (int x, int y, int w, int h, int a, int alen)
{
if (data->state.paintingDisabled)
return;
if (data->state.pen.style() != NoPen) {
if (w != h) {
ERROR("only supports drawing arcs on a circle");
}
NSBezierPath *path = [[NSBezierPath alloc] init];
float fa = (float)a / 16;
float falen = fa + (float)alen / 16;
[path appendBezierPathWithArcWithCenter:NSMakePoint(x + w / 2, y + h / 2)
radius:(float)w / 2
startAngle:-fa
endAngle:-falen
clockwise:YES];
_setColorFromPen();
[path stroke];
[path release];
}
}
void QPainter::drawLineSegments(const QPointArray &points, int index, int nlines)
{
if (data->state.paintingDisabled)
return;
_drawPoints (points, 0, index, nlines, false);
}
void QPainter::drawPolyline(const QPointArray &points, int index, int npoints)
{
_drawPoints (points, 0, index, npoints, false);
}
void QPainter::drawPolygon(const QPointArray &points, bool winding, int index,
int npoints)
{
_drawPoints (points, winding, index, npoints, true);
}
void QPainter::drawConvexPolygon(const QPointArray &points)
{
_drawPoints (points, false, 0, -1, true);
}
void QPainter::_drawPoints (const QPointArray &_points, bool winding, int index, int _npoints, bool fill)
{
if (data->state.paintingDisabled)
return;
int npoints = _npoints != -1 ? _npoints : _points.size()-index;
NSPoint points[npoints];
for (int i = 0; i < npoints; i++) {
points[i].x = _points[index+i].x();
points[i].y = _points[index+i].y();
}
NSBezierPath *path = [[NSBezierPath alloc] init];
[path appendBezierPathWithPoints:&points[0] count:npoints];
[path closePath]; // Qt always closes the path. Determined empirically.
if (fill && data->state.brush.style() != NoBrush) {
[path setWindingRule:winding ? NSNonZeroWindingRule : NSEvenOddWindingRule];
_setColorFromBrush();
[path fill];
}
if (data->state.pen.style() != NoPen) {
_setColorFromPen();
[path stroke];
}
[path release];
}
void QPainter::drawPixmap(const QPoint &p, const QPixmap &pix)
{
drawPixmap(p.x(), p.y(), pix);
}
void QPainter::drawPixmap(const QPoint &p, const QPixmap &pix, const QRect &r)
{
drawPixmap(p.x(), p.y(), pix, r.x(), r.y(), r.width(), r.height());
}
void QPainter::drawPixmap( int x, int y, const QPixmap &pixmap,
int sx, int sy, int sw, int sh )
{
if (data->state.paintingDisabled)
return;
if (sw == -1)
sw = pixmap.width();
if (sh == -1)
sh = pixmap.height();
[pixmap.imageRenderer beginAnimationInRect:NSMakeRect(x, y, sw, sh)
fromRect:NSMakeRect(sx, sy, sw, sh)];
}
void QPainter::drawTiledPixmap( int x, int y, int w, int h,
const QPixmap &pixmap, int sx, int sy )
{
if (data->state.paintingDisabled)
return;
[pixmap.imageRenderer tileInRect:NSMakeRect(x, y, w, h) fromPoint:NSMakePoint(sx, sy)];
}
void QPainter::_updateRenderer(NSString **families)
{
if (data->textRenderer == 0 || data->state.font != data->textRendererFont) {
data->textRendererFont = data->state.font;
id <WebCoreTextRenderer> oldRenderer = data->textRenderer;
data->textRenderer = [[[WebCoreTextRendererFactory sharedFactory]
rendererWithFont:data->textRendererFont.getNSFont()
usingPrinterFont:data->textRendererFont.isPrinterFont()] retain];
[oldRenderer release];
}
}
void QPainter::drawText(int x, int y, int, int, int alignmentFlags, const QString &qstring)
{
if (data->state.paintingDisabled)
return;
// Avoid allocations, use stack array to pass font families. Normally these
// css fallback lists are small <= 3.
CREATE_FAMILY_ARRAY(data->state.font, families);
_updateRenderer(families);
const UniChar* str = (const UniChar*)qstring.unicode();
if (alignmentFlags & Qt::AlignRight)
x -= ROUND_TO_INT([data->textRenderer floatWidthForCharacters:(const UniChar *)str stringLength:qstring.length() fromCharacterPosition:0 numberOfCharacters:qstring.length() withPadding: 0 applyRounding:YES attemptFontSubstitution: YES widths: 0 letterSpacing: 0 wordSpacing: 0 fontFamilies: families]);
[data->textRenderer drawCharacters:str stringLength:qstring.length()
fromCharacterPosition:0
toCharacterPosition:qstring.length()
atPoint:NSMakePoint(x, y)
withPadding: 0
withTextColor:data->state.pen.color().getNSColor()
backgroundColor:nil
rightToLeft: false
letterSpacing: 0
wordSpacing: 0
fontFamilies: families];
}
void QPainter::drawText(int x, int y, const QChar *str, int len, int from, int to, int toAdd, const QColor &backgroundColor, QPainter::TextDirection d, int letterSpacing, int wordSpacing)
{
if (data->state.paintingDisabled || len <= 0)
return;
// Avoid allocations, use stack array to pass font families. Normally these
// css fallback lists are small <= 3.
CREATE_FAMILY_ARRAY(data->state.font, families);
_updateRenderer(families);
[data->textRenderer
drawCharacters:(const UniChar *)str stringLength:len
fromCharacterPosition:from
toCharacterPosition:to
atPoint:NSMakePoint(x, y)
withPadding: toAdd
withTextColor:data->state.pen.color().getNSColor()
backgroundColor:backgroundColor.isValid() ? backgroundColor.getNSColor() : nil
rightToLeft: d == RTL ? true : false
letterSpacing: letterSpacing
wordSpacing: wordSpacing
fontFamilies: families];
}
void QPainter::drawLineForText(int x, int y, int yOffset, int width)
{
if (data->state.paintingDisabled)
return;
[data->textRenderer
drawLineForCharacters: NSMakePoint(x, y)
yOffset:(float)yOffset
withWidth: width
withColor:data->state.pen.color().getNSColor()];
}
QColor QPainter::selectedTextBackgroundColor()
{
NSColor *color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
return QColor((int)(255 * [color redComponent]), (int)(255 * [color greenComponent]), (int)(255 * [color blueComponent]));
}
void QPainter::fillRect(int x, int y, int w, int h, const QBrush &brush)
{
if (data->state.paintingDisabled)
return;
if (brush.style() == SolidPattern) {
[brush.color().getNSColor() set];
NSRectFill(NSMakeRect(x, y, w, h));
}
}
void QPainter::addClip(const QRect &rect)
{
[NSBezierPath clipRect:rect];
}
Qt::RasterOp QPainter::rasterOp() const
{
return CopyROP;
}
void QPainter::setRasterOp(RasterOp)
{
}
void QPainter::setPaintingDisabled(bool f)
{
data->state.paintingDisabled = f;
}
bool QPainter::paintingDisabled() const
{
return data->state.paintingDisabled;
}