/* 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 #include #include #include #include #include #include #include #include #include #include #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(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 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(); 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 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(element); if(shape) { CanvasItem *item = shape->item(); if(item) { SVGImageElementImpl *image = dynamic_cast(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 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 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(m_viewportWidth * zoomFactor); int newHeight = static_cast(m_viewportHeight * zoomFactor); setRenderBufferSize(newWidth, newHeight); } else fill(); // reset clip paths TQDictIterator 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 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 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 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