/* (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 "SVGImageRenderer.h" #include "CGContextProvider.h" #include "GdkXftContext.h" #include "GdkHelpers.h" #include "ImageRenderer.h" // This file uses librsvg to load SVG image representation, which can then be transformed to bitmap // and rendererd by ordinary ImageRenderer. SVG images can be loaded with gdkpixbuf loader too, it seems, // but then the scaling is bitmap scaling.. extern "C"{ static void size_prepared(gint *width,gint *height, gpointer user_data); } // SvgInfo is implicitly shared between copied objects // it countains the svg source, which can then be used to rerender the image in case // resizing is requested. this meanst that for each resize, parsing etc happens. // rsvg would be kind if it would provide handle to an intermediate format, ie. dom tree of // the svg document, so that we could operate on that and only relayouting would be needed // for each resize. class SvgInfo { int refCnt; public: GByteArray* source; SvgInfo() : refCnt(1), source(0) {} ~SvgInfo() { if (source) g_byte_array_free(source, true); }; void ref() { refCnt++;} void unref() { if (!(--refCnt)) delete this; } }; SVGImageRenderer::SVGImageRenderer() :pixbuf(0) ,loadOffset(0) ,svgHandle(0) ,svgHandleOpen(false) ,info(new SvgInfo()) ,rasterRenderer(0) { wantedSize.w = wantedSize.h = -1; } SVGImageRenderer::SVGImageRenderer(const SVGImageRenderer& other) :pixbuf(0) ,wantedSize(other.wantedSize) ,realSize(other.realSize) ,loadOffset(0) ,svgHandle(0) ,svgHandleOpen(false) ,info(other.info) ,rasterRenderer(0) { info->ref(); } SVGImageRenderer::~SVGImageRenderer() { flushRasterCache(); info->unref(); } bool SVGImageRenderer::incrementalLoadWithBytes(const void *bytes, unsigned length, bool isComplete) { if (!svgHandle) { svgHandle = rsvg_handle_new(); rsvg_handle_set_size_callback(svgHandle, ::size_prepared, this, NULL); svgHandleOpen = true; loadOffset = 0; } GError* err = NULL; if ((length - loadOffset) > 0) { bool succ = rsvg_handle_write(svgHandle, ((guchar*) bytes) + loadOffset, length - loadOffset, &err); if (succ) loadOffset += length - loadOffset; else { #if DEBUG g_printerr("error loading image incrementally. this:%x bytes %d, len:%d, isComplete: %d, loadOffset:%d", (int) this, (int) bytes,length, (int) isComplete, loadOffset); #endif } } if (isComplete) { err = NULL; svgHandleOpen = false; rsvg_handle_close(svgHandle, &err); // save the source bytes for resizing info->source = g_byte_array_sized_new(length); g_byte_array_append(info->source, (const guint8*)bytes, length); } invalidate(); pixbuf = rsvg_handle_get_pixbuf(svgHandle); return pixbuf; } void SVGImageRenderer::size(GdkRectangle* outSize) { if (!outSize) return; outSize->x = 0; outSize->y = 0; outSize->width = wantedSize.w; outSize->height = wantedSize.h; } void SVGImageRenderer::resize(GdkRectangle* s) { if (!s) return; assert(s->x == 0); assert(s->y == 0); assert(s->width > 0); assert(s->height > 0); flushRasterCache(); wantedSize.w = s->width; wantedSize.h = s->height; } GdkPixbuf* SVGImageRenderer::getCurrentPixbuf() { if (!pixbuf) { if (svgHandle) { pixbuf = rsvg_handle_get_pixbuf(svgHandle); } else if (info->source) { svgHandle = rsvg_handle_new(); rsvg_handle_set_size_callback(svgHandle, ::size_prepared, this, NULL); GError* err = NULL; bool succ = rsvg_handle_write(svgHandle, ((guchar*) info->source->data), info->source->len, &err); err = NULL; rsvg_handle_close(svgHandle, &err); if (succ) { pixbuf = rsvg_handle_get_pixbuf(svgHandle); } } else { pixbuf = 0; } } return pixbuf; } void SVGImageRenderer::cache() { if (rasterRenderer) return; rasterRenderer = new ImageRenderer(getCurrentPixbuf()); rasterRenderer->retain(); } void SVGImageRenderer::invalidate() { if (pixbuf) { g_object_unref(pixbuf); pixbuf = 0; } if (rasterRenderer) { rasterRenderer->release(); rasterRenderer = 0; } } void SVGImageRenderer::drawImageInRect(GdkRectangle* inRect, GdkRectangle* fromRect, NSCompositingOperation compositeOperator, CGContextRef context) { // scaling not supported assert(inRect->width == fromRect->width); assert(inRect->height == fromRect->height); if (isNull()) return; cache(); rasterRenderer->drawImageInRect(inRect, fromRect, compositeOperator, context); } void SVGImageRenderer::stopAnimation() { } void SVGImageRenderer::tileInRect(GdkRectangle* r, int sx, int sy, CGContextRef context) { assert(r); assert(context); if (isNull()) return; cache(); rasterRenderer->tileInRect(r, sx, sy, context); } bool SVGImageRenderer::isNull() { if (!info->source && !pixbuf) return true; return false; } void SVGImageRenderer::increaseUseCount() { } void SVGImageRenderer::decreaseUseCount() { } void SVGImageRenderer::flushRasterCache() { invalidate(); if (svgHandle) { if (svgHandleOpen) { GError *err = NULL; // flushed while loading rsvg_handle_close(svgHandle, &err); } rsvg_handle_free(svgHandle); svgHandle = 0; } } void SVGImageRenderer::sizePrepared(int *width, int *height) { realSize.w = *width; realSize.h = *height; if (wantedSize.w == -1 && wantedSize.h == -1) { wantedSize.w = *width; wantedSize.h = *height; return; } if (wantedSize.w == *width && wantedSize.h == *height) return; *width = wantedSize.w; *height = wantedSize.h; } SVGImageRenderer* SVGImageRenderer::copy() { SVGImageRenderer* r = new SVGImageRenderer(*this); return r; } GdkPixbuf* SVGImageRenderer::handle() const { return const_cast(this)->getCurrentPixbuf(); } extern "C"{ static void size_prepared(gint *width, gint *height, gpointer user_data) { SVGImageRenderer* d = static_cast(user_data); d->sizePrepared(width, height); } } // extern "C"