You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/ksvg/core/KSVGCanvas.cpp

787 lines
18 KiB

/*
Copyright (C) 2001-2003 KSVG Team
This file is part of the KDE project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
aint with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "CanvasItem.h"
#include "CanvasItems.h"
#include "KSVGCanvas.moc"
#include "SVGRectImpl.h"
#include "SVGSVGElementImpl.h"
#include "SVGStringListImpl.h"
#include "SVGClipPathElementImpl.h"
#include "SVGImageElementImpl.h"
#include "SVGDocumentImpl.h"
#include <kdebug.h>
#include <kglobal.h>
#include <tqstring.h>
#include <tqdatetime.h>
#include <tqpaintdevicemetrics.h>
#include <tqwmatrix.h>
#include <X11/Xlib.h>
#include <math.h>
#include <libs/xrgbrender/gdk-pixbuf-xlibrgb.h>
#include <Font.h>
#include <fontconfig/fontconfig.h>
#define USE_TIMER
using namespace KSVG;
KSVGCanvas::KSVGCanvas(unsigned int width, unsigned int height) : m_viewportWidth(width), m_viewportHeight(height), m_width(width), m_height(height)
{
m_fontContext = 0;
m_items.setAutoDelete(true);
m_chunkSizeVer = CHUNK_SIZE_VERTICAL;
m_chunkSizeHor = CHUNK_SIZE_HORIZONTAL;
m_zoom = 1;
m_buffer = 0;
m_backgroundColor = TQColor(250, 250, 250);
m_immediateUpdate = false;
}
void KSVGCanvas::setup(TQPaintDevice *drawWindow, TQPaintDevice *directWindow)
{
m_drawWindow = drawWindow;
m_directWindow = directWindow;
m_buffer = 0;
m_nrChannels = 3;
setRenderBufferSize(m_width, m_height);
xlib_rgb_init_with_depth(m_drawWindow->x11Display(), XScreenOfDisplay(m_drawWindow->x11Display(), m_drawWindow->x11Screen()), m_drawWindow->x11Depth());
m_gc = XCreateGC(m_drawWindow->x11Display(), m_drawWindow->handle(), 0, 0);
}
void KSVGCanvas::setViewportDimension(unsigned int w, unsigned int h)
{
m_viewportWidth = w;
m_viewportHeight = h;
setRenderBufferSize(w, h);
}
void KSVGCanvas::setup(unsigned char *buffer, unsigned int width, unsigned int height)
{
setBuffer(buffer);
m_drawWindow = 0;
m_directWindow = 0;
m_nrChannels = 4;
if(height > 0)
{
m_width = width;
m_height = height;
}
setRenderBufferSize(m_width, m_height);
m_gc = 0;
}
void KSVGCanvas::setBuffer(unsigned char *buffer)
{
m_buffer = buffer;
}
KSVGCanvas::~KSVGCanvas()
{
if(m_fontContext)
delete m_fontContext;
if(m_buffer && m_gc)
delete []m_buffer;
if(m_gc)
XFreeGC(m_drawWindow->x11Display(), m_gc);
reset();
}
void KSVGCanvas::retune(unsigned int csh, unsigned int csv)
{
m_chunkSizeHor = csh;
m_chunkSizeVer = csv;
}
void KSVGCanvas::resize(unsigned int w, unsigned int h)
{
if(m_buffer && (m_width != int(w) || m_height != int(h)))
{
unsigned char *oldbuffer = m_buffer;
m_buffer = new unsigned char[w * h * m_nrChannels];
int minw = kMin(int(w), m_width);
int minh = kMin(int(h), m_height);
int origstride = m_width * m_nrChannels;
int newstride = w * m_nrChannels;
// Redraw new areas, if any
int diffw = w - m_width;
int diffh = h - m_height;
TQRect r(m_width, 0, diffw, m_height + diffh);
TQRect r3(0, m_height, m_width + diffw, diffh);
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
m_width = w;
m_height = h;
setBuffer(m_buffer);
fill();
if(diffw > 0 || diffh > 0)
{
CanvasItemList drawables;
if(diffw > 0)
{
TQRect r2 = mtx.invert().map(r);
// Recalc items
for(int j = r2.top() / int(m_chunkSizeVer); j <= r2.bottom() / int(m_chunkSizeVer); j++)
{
for(int i = r2.left() / int(m_chunkSizeHor); i <= r2.right() / int(m_chunkSizeHor); i++)
{
CanvasChunk *chunk = m_chunkManager.getChunk(i, j);
if(chunk)
{
for(CanvasItemList::ConstIterator it = chunk->list().begin(); it != chunk->list().end(); ++it)
{
if(!drawables.contains(*it))
drawables.append(*it);
}
}
}
}
}
if(diffh > 0)
{
TQRect r4 = mtx.invert().map(r3);
// Recalc items
for(int j = r4.top() / int(m_chunkSizeVer); j <= r4.bottom() / int(m_chunkSizeVer); j++)
{
for(int i = r4.left() / int(m_chunkSizeHor); i <= r4.right() / int(m_chunkSizeHor); i++)
{
CanvasChunk *chunk = m_chunkManager.getChunk(i, j);
if(chunk)
{
for(CanvasItemList::ConstIterator it = chunk->list().begin(); it != chunk->list().end(); ++it)
{
if(!drawables.contains(*it))
drawables.append(*it);
}
}
}
}
}
drawables.sort();
for(CanvasItemList::Iterator it = drawables.begin(); it != drawables.end(); ++it)
(*it)->draw();
}
for(int y = 0; y < minh; y++)
memcpy(m_buffer + y * newstride, oldbuffer + y * origstride, minw * m_nrChannels);
delete []oldbuffer;
}
}
void KSVGCanvas::setRenderBufferSize(int w, int h)
{
kdDebug(26005) << k_funcinfo << endl;
if(m_drawWindow)
{
bool needsRedraw = (!m_buffer) || (m_width != w || m_height != h);
if(needsRedraw)
{
TQPaintDeviceMetrics metrics(m_drawWindow);
m_width = kMin(int(w), metrics.width());
m_height = kMin(int(h), metrics.height());
if(m_buffer)
delete []m_buffer;
m_buffer = new unsigned char[m_width * m_height * m_nrChannels];
}
}
fill();
}
void KSVGCanvas::clear(const TQRect &r)
{
TQRect r2 = r & TQRect(0, 0, m_width, m_height);
if(!r2.isEmpty() && m_buffer)
{
for(int i = 0; i < r2.height(); i++)
memset(m_buffer + int(r2.x() * m_nrChannels) + int((r2.y() + i) * (m_width * m_nrChannels)), tqRgba(250, 250, 250, 250), r2.width() * m_nrChannels);
}
}
void KSVGCanvas::fill()
{
if(m_buffer)
{
unsigned char r = m_backgroundColor.red();
unsigned char g = m_backgroundColor.green();
unsigned char b = m_backgroundColor.blue();
if(m_nrChannels == 3)
{
if(r == g && r == b)
memset(m_buffer, r, m_width * m_height * m_nrChannels);
else
{
unsigned char *p = m_buffer;
for(int i = 0; i < m_width * m_height; i++)
{
*p++ = r;
*p++ = g;
*p++ = b;
}
}
}
else
{
TQ_UINT32 *p = reinterpret_cast<TQ_UINT32 *>(m_buffer);
unsigned char a = tqAlpha(m_backgroundColor.rgb());
#if X_BYTE_ORDER == X_LITTLE_ENDIAN
TQ_UINT32 rgba = (a << 24) | (b << 16) | (g << 8) | r;
#else
TQ_UINT32 rgba = (r << 24) | (g << 16) | (b << 8) | a;
#endif
for(int i = 0; i < m_width * m_height; i++)
*p++ = rgba;
}
}
}
// Clipping
void KSVGCanvas::clipToBuffer(int &x0, int &y0, int &x1, int &y1) const
{
// clamp to viewport
x0 = TQMAX(x0, 0);
x0 = TQMIN(x0, int(m_width - 1));
y0 = TQMAX(y0, 0);
y0 = TQMIN(y0, int(m_height - 1));
x1 = TQMAX(x1, 0);
x1 = TQMIN(x1, int(m_width - 1));
y1 = TQMAX(y1, 0);
y1 = TQMIN(y1, int(m_height - 1));
}
T2P::FontVisualParams *KSVGCanvas::fontVisualParams(SVGStylableImpl *style) const
{
T2P::FontVisualParams *fontVisualParams = new T2P::FontVisualParams();
// Calc weight & slant
int weight = 0, slant = 0;
EFontStyle fontStyle = style->getFontStyle();
TQString fontWeight = style->getFontWeight();
if(fontWeight.contains("bold"))
weight |= FC_WEIGHT_DEMIBOLD;
if(fontWeight.contains("bolder"))
weight |= FC_WEIGHT_BOLD;
if(fontWeight.contains("lighter"))
weight |= FC_WEIGHT_LIGHT;
bool ok = true;
int weightNumber = fontWeight.toInt(&ok);
if(ok)
weight = weightNumber;
if(fontStyle == FSNORMAL)
slant |= FC_SLANT_ROMAN;
else if(fontStyle == ITALIC)
slant |= FC_SLANT_ITALIC;
else if(fontStyle == OBLIQUE)
slant |= FC_SLANT_OBLIQUE;
// Calc font names
SVGStringListImpl *fontList = style->getFontFamily();
for(unsigned int i = 0; i <= fontList->numberOfItems(); i++)
{
DOM::DOMString *string = fontList->getItem(i);
if(string)
fontVisualParams->fontList().push_back(string->string().latin1());
}
fontVisualParams->setWeight(weight);
fontVisualParams->setSlant(slant);
fontVisualParams->setSize(style->getFontSize());
return fontVisualParams;
}
void KSVGCanvas::invalidate(CanvasItem *item, bool recalc)
{
if(m_chunksByItem.find(item) != m_chunksByItem.end())
{
if(recalc)
{
removeFromChunks(item);
addToChunks(item);
}
TQPtrListIterator<CanvasChunk> it = m_chunksByItem[item];
for(it.toFirst(); it.current(); ++it)
{
(*it)->setDirty();
if(!m_dirtyChunks.contains(*it))
m_dirtyChunks.append(*it);
}
}
else
addToChunks(item);
}
void KSVGCanvas::insert(CanvasItem *item, int z)
{
if(z == -1)
{
item->setZIndex(m_chunksByItem.size());
m_chunksByItem[item] = TQPtrList<CanvasChunk>();
addToChunks(item);
m_items.append(item);
bool visible = item->isVisible();
if(visible)
invalidate(item, false);
if(m_immediateUpdate)
{
if(visible)
{
item->draw();
TQRect bbox = item->bbox();
blit(bbox, true);
}
}
}
else
{
// make some space
for(unsigned int i = z; i < m_items.count(); i++)
m_items.at(i)->setZIndex(m_items.at(i)->zIndex() + 1);
item->setZIndex(z);
}
}
void KSVGCanvas::removeItem(CanvasItem *item)
{
removeFromChunks(item);
m_items.remove(item);
}
void KSVGCanvas::removeFromChunks(CanvasItem *item)
{
TQPtrListIterator<CanvasChunk> it = m_chunksByItem[item];
for(it.toFirst(); it.current(); ++it)
{
(*it)->remove(item);
if(!m_dirtyChunks.contains(*it))
m_dirtyChunks.append(*it);
}
m_chunksByItem.remove(item);
}
void KSVGCanvas::addToChunks(CanvasItem *item)
{
TQRect bbox = item->bbox();
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
bbox = mtx.invert().map(bbox);
for(int j = bbox.top() / m_chunkSizeVer; j <= (bbox.bottom() / m_chunkSizeVer); j++)
{
for(int i = bbox.left() / int(m_chunkSizeHor); i <= (bbox.right() / m_chunkSizeHor); i++)
{
CanvasChunk *chunk = m_chunkManager.getChunk(i, j);
if(!chunk)
{
chunk = new CanvasChunk(i, j);
m_chunkManager.addChunk(chunk);
}
chunk->add(item);
m_chunksByItem[item].append(chunk);
}
}
}
unsigned int KSVGCanvas::setElementItemZIndexRecursive(SVGElementImpl *element, unsigned int z)
{
SVGShapeImpl *shape = dynamic_cast<SVGShapeImpl *>(element);
if(shape)
{
CanvasItem *item = shape->item();
if(item)
{
SVGImageElementImpl *image = dynamic_cast<SVGImageElementImpl *>(shape);
if(image && image->svgImageRootElement())
{
// Set the z for all items in the svg image, since they live in the
// same canvas.
z = setElementItemZIndexRecursive(image->svgImageRootElement(), z);
}
else
{
item->setZIndex(z);
invalidate(item, false);
z++;
}
}
}
for(DOM::Node node = element->firstChild(); !node.isNull(); node = node.nextSibling())
{
SVGElementImpl *e = element->ownerDoc()->getElementFromHandle(node.handle());
if(e)
z = setElementItemZIndexRecursive(e, z);
}
return z;
}
void KSVGCanvas::update(const TQPoint &panPoint, bool erase)
{
#ifdef USE_TIMER
TQTime t;
t.start();
#endif
int dx = panPoint.x() - m_pan.x();
int dy = panPoint.y() - m_pan.y();
m_pan = panPoint;
if(erase)
fill();
// reset clip paths
TQDictIterator<CanvasClipPath> itr(m_clipPaths);
for(; itr.current(); ++itr)
(*itr)->update(UPDATE_TRANSFORM);
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
TQRect r(0, 0, m_width, m_height);
TQRect r2 = mtx.invert().map(r);
// pan all items
for(unsigned int i = 0; i < m_items.count(); i++)
m_items.at(i)->update(UPDATE_PAN, dx, dy);
// recalc items
CanvasItemList drawables;
TQPtrListIterator<CanvasItem> it = m_items;
for(int j = r2.top() / m_chunkSizeVer; j <= (r2.bottom() / m_chunkSizeVer); j++)
{
for(int i = r2.left() / m_chunkSizeHor; i <= (r2.right() / m_chunkSizeHor); i++)
{
CanvasChunk *chunk = m_chunkManager.getChunk(i, j);
if(chunk)
{
for(CanvasItemList::ConstIterator it = chunk->list().begin(); it != chunk->list().end(); ++it)
{
if(!drawables.contains(*it))
drawables.append(*it);
}
}
}
}
drawables.sort();
for(CanvasItemList::Iterator it = drawables.begin(); it != drawables.end(); ++it)
(*it)->draw();
if(m_drawWindow)
blit(TQRect(0, 0, m_width, m_height), false);
m_dirtyChunks.clear();
#ifdef USE_TIMER
kdDebug(26000) << k_funcinfo << " Total time: " << t.elapsed() << endl;
#endif
}
void KSVGCanvas::update(float zoomFactor)
{
#ifdef USE_TIMER
TQTime t;
t.start();
#endif
if(zoomFactor >= 1)
{
int newWidth = static_cast<int>(m_viewportWidth * zoomFactor);
int newHeight = static_cast<int>(m_viewportHeight * zoomFactor);
setRenderBufferSize(newWidth, newHeight);
}
else
fill();
// reset clip paths
TQDictIterator<CanvasClipPath> itr(m_clipPaths);
for(; itr.current(); ++itr)
(*itr)->update(UPDATE_TRANSFORM);
m_zoom = zoomFactor;
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
TQRect r(0, 0, m_width, m_height);
TQRect r2 = mtx.invert().map(r);
// zoom all items
for(unsigned int i = 0; i < m_items.count(); i++)
m_items.at(i)->update(UPDATE_ZOOM);
// recalc items
CanvasItemList drawables;
TQPtrListIterator<CanvasItem> it = m_items;
for(int j = r2.top() / m_chunkSizeVer; j <= (r2.bottom() / m_chunkSizeVer); j++)
{
for(int i = r2.left() / m_chunkSizeHor; i <= (r2.right() / m_chunkSizeHor); i++)
{
CanvasChunk *chunk = m_chunkManager.getChunk(i, j);
if(chunk)
{
for(CanvasItemList::ConstIterator it = chunk->list().begin(); it != chunk->list().end(); ++it)
{
if(!drawables.contains(*it))
drawables.append(*it);
}
}
}
}
drawables.sort();
for(CanvasItemList::Iterator it = drawables.begin(); it != drawables.end(); ++it)
(*it)->draw();
if(m_drawWindow)
blit(TQRect(0, 0, m_width, m_height), false);
m_dirtyChunks.clear();
#ifdef USE_TIMER
kdDebug(26000) << k_funcinfo << " Total time: " << t.elapsed() << endl;
#endif
}
void KSVGCanvas::reset()
{
m_items.clear();
m_chunkManager.clear();
m_chunksByItem.clear();
m_dirtyChunks.clear();
m_pan.setX(0);
m_pan.setY(0);
m_zoom = 1;
}
void KSVGCanvas::update()
{
#ifdef USE_TIMER
TQTime t;
t.start();
#endif
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
// Process dirty chunks
TQPtrList<CanvasChunk> chunkList;
CanvasItemList drawables;
for(unsigned int i = 0; i < m_dirtyChunks.count(); i++)
{
CanvasChunk *chunk = m_dirtyChunks[i];
Q_ASSERT(chunk->isDirty());
TQRect r = chunk->bbox();
TQRect chunkbox(mtx.map(r.topLeft()), mtx.map(r.bottomRight()));
clear(chunkbox);
chunkList.append(chunk);
for(CanvasItemList::ConstIterator it = chunk->list().begin(); it != chunk->list().end(); ++it)
{
// kdDebug(26005) << k_funcinfo << " Checking: " << *it << endl;
if(!drawables.contains(*it))
{
// kdDebug(26005) << k_funcinfo << " Yes, appending to update list!" << endl;
drawables.append(*it);
}
}
chunk->unsetDirty();
}
drawables.sort();
// Draw dirty chunks
for(CanvasItemList::Iterator it = drawables.begin(); it != drawables.end(); ++it)
{
// kdDebug(26005) << " Need to redraw dirty : " << (*it) << " with z : " << (*it)->zIndex() << endl;
(*it)->draw();
}
// Blit dirty chunks
TQPtrListIterator<CanvasChunk> it = chunkList;
for(it.toFirst(); it.current(); ++it)
{
TQRect r = (*it)->bbox();
TQRect chunkbox(mtx.map(r.topLeft()), mtx.map(r.bottomRight()));
blit(chunkbox, false);
}
m_dirtyChunks.clear();
#ifdef USE_TIMER
kdDebug(26005) << k_funcinfo << " Total time: " << t.elapsed() << endl;
#endif
}
CanvasItemList KSVGCanvas::collisions(const TQPoint &p, bool exact) const
{
TQWMatrix mtx;
mtx.translate(m_pan.x(), m_pan.y());
mtx.scale(m_zoom, m_zoom);
TQPoint p2 = mtx.invert().map(p);
if(p2.x() < 0 || p2.y() < 0)
return CanvasItemList();
unsigned int x = p2.x() / int(m_chunkSizeHor);
unsigned int y = p2.y() / int(m_chunkSizeVer);
CanvasItemList result;
CanvasChunk *chunk = m_chunkManager.getChunk(x, y);
if(!chunk)
return result;
CanvasItemList list = chunk->list();
if(exact)
{
for(CanvasItemList::Iterator it = list.begin(); it != list.end(); ++it)
{
if((*it)->fillContains(p) || (*it)->strokeContains(p) || (*it)->bbox().contains(p))
result.append(*it);
}
return result;
}
else
return list;
}
void KSVGCanvas::blit(const TQRect &rect, bool direct)
{
if(m_drawWindow && m_width && m_height)
{
// clamp to viewport
int x0 = rect.x();
x0 = TQMAX(x0, 0);
x0 = TQMIN(x0, int(m_width - 1));
int y0 = rect.y();
y0 = TQMAX(y0, 0);
y0 = TQMIN(y0, int(m_height - 1));
int x1 = rect.x() + rect.width() + 1;
x1 = TQMAX(x1, 0);
x1 = TQMIN(x1, int(m_width));
int y1 = rect.y() + rect.height() + 1;
y1 = TQMAX(y1, 0);
y1 = TQMIN(y1, int(m_height));
xlib_draw_rgb_image(direct ? m_directWindow->handle() : m_drawWindow->handle(), m_gc, x0, y0, x1 - x0, y1 - y0, XLIB_RGB_DITHER_NONE, m_buffer + (m_width * y0 + x0) * m_nrChannels, m_width * m_nrChannels);
}
}
void KSVGCanvas::blit()
{
return blit(TQRect(0, 0, m_width, m_height), false);
}
void KSVGCanvas::ChunkManager::addChunk(CanvasChunk *chunk)
{
TQString key = TQString("%1 %2").arg(chunk->x()).arg(chunk->y());
// kdDebug(26005) << k_funcinfo << "Adding chunk : " << chunk << endl;
m_chunks.insert(key, chunk);
}
CanvasChunk *KSVGCanvas::ChunkManager::getChunk(short x, short y) const
{
// kdDebug(26005) << k_funcinfo << "getting chunk from : " << x << ", " << y << endl;
TQString key = TQString("%1 %2").arg(x).arg(y);
return m_chunks[key];
}
void KSVGCanvas::ChunkManager::clear()
{
m_chunks.clear();
}
// vim:ts=4:noet