#define __SP_CAIRO_RENDER_CONTEXT_C__ /** \file * Rendering with Cairo. */ /* * Author: * Miklos Erdelyi * * Copyright (C) 2006 Miklos Erdelyi * * Licensed under GNU GPL */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifndef PANGO_ENABLE_BACKEND #define PANGO_ENABLE_BACKEND #endif #ifndef PANGO_ENABLE_ENGINE #define PANGO_ENABLE_ENGINE #endif #include #include #include #include #include #include #include #include #include #include #include "display/nr-arena.h" #include "display/nr-arena-item.h" #include "display/nr-arena-group.h" #include "display/curve.h" #include "display/canvas-bpath.h" #include "sp-item.h" #include "sp-item-group.h" #include "style.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" #include "sp-pattern.h" #include "sp-mask.h" #include "sp-clippath.h" #ifdef WIN32 #include "libnrtype/FontFactory.h" // USE_PANGO_WIN32 #endif #include #include "cairo-render-context.h" #include "cairo-renderer.h" #include "extension/system.h" #include "io/sys.h" #include // include support for only the compiled-in surface types #ifdef CAIRO_HAS_PDF_SURFACE #include #endif #ifdef CAIRO_HAS_PS_SURFACE #include #endif #ifdef CAIRO_HAS_FT_FONT #include #endif #ifdef CAIRO_HAS_WIN32_FONT #include #include #endif #include //#define TRACE(_args) g_printf _args #define TRACE(_args) //#define TEST(_args) _args #define TEST(_args) // FIXME: expose these from sp-clippath/mask.cpp struct SPClipPathView { SPClipPathView *next; unsigned int key; NRArenaItem *arenaitem; NRRect bbox; }; struct SPMaskView { SPMaskView *next; unsigned int key; NRArenaItem *arenaitem; NRRect bbox; }; namespace Inkscape { namespace Extension { namespace Internal { static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length); CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _dpi(72), _pdf_level(0), _ps_level(1), _is_texttopath(FALSE), _is_filtertobitmap(FALSE), _bitmapresolution(72), _stream(NULL), _is_valid(FALSE), _vector_based_target(FALSE), _cr(NULL), _surface(NULL), _target(CAIRO_SURFACE_TYPE_IMAGE), _target_format(CAIRO_FORMAT_ARGB32), _layout(NULL), _state(NULL), _renderer(parent), _render_mode(RENDER_MODE_NORMAL), _clip_mode(CLIP_MODE_MASK) {} CairoRenderContext::~CairoRenderContext(void) { if (_cr) cairo_destroy(_cr); if (_surface) cairo_surface_destroy(_surface); if (_layout) g_object_unref(_layout); } CairoRenderer* CairoRenderContext::getRenderer(void) const { return _renderer; } CairoRenderState* CairoRenderContext::getCurrentState(void) const { return _state; } CairoRenderState* CairoRenderContext::getParentState(void) const { // if this is the root node just return it if (g_slist_length(_state_stack) == 1) { return _state; } else { return (CairoRenderState *)g_slist_nth_data(_state_stack, 1); } } void CairoRenderContext::setStateForStyle(SPStyle const *style) { // only opacity & overflow is stored for now _state->opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); _state->has_overflow = (style->overflow.set && style->overflow.value != SP_CSS_OVERFLOW_VISIBLE); _state->has_filtereffect = (style->filter.set != 0) ? TRUE : FALSE; if (style->fill.isPaintserver() || style->stroke.isPaintserver()) _state->merge_opacity = FALSE; // disable rendering of opacity if there's a stroke on the fill if (_state->merge_opacity && !style->fill.isNone() && !style->stroke.isNone()) _state->merge_opacity = FALSE; } /** * \brief Creates a new render context which will be compatible with the given context's Cairo surface * * \param width width of the surface to be created * \param height height of the surface to be created */ CairoRenderContext* CairoRenderContext::cloneMe(double width, double height) const { g_assert( _is_valid ); g_assert( width > 0.0 && height > 0.0 ); CairoRenderContext *new_context = _renderer->createContext(); cairo_surface_t *surface = cairo_surface_create_similar(cairo_get_target(_cr), CAIRO_CONTENT_COLOR_ALPHA, (int)ceil(width), (int)ceil(height)); new_context->_cr = cairo_create(surface); new_context->_surface = surface; new_context->_is_valid = TRUE; return new_context; } CairoRenderContext* CairoRenderContext::cloneMe(void) const { g_assert( _is_valid ); return cloneMe(_width, _height); } bool CairoRenderContext::setImageTarget(cairo_format_t format) { // format cannot be set on an already initialized surface if (_is_valid) return false; switch (format) { case CAIRO_FORMAT_ARGB32: case CAIRO_FORMAT_RGB24: case CAIRO_FORMAT_A8: case CAIRO_FORMAT_A1: _target_format = format; _target = CAIRO_SURFACE_TYPE_IMAGE; return true; break; default: break; } return false; } bool CairoRenderContext::setPdfTarget(gchar const *utf8_fn) { #ifndef CAIRO_HAS_PDF_SURFACE return false; #else _target = CAIRO_SURFACE_TYPE_PDF; _vector_based_target = TRUE; #endif FILE *osf = NULL; FILE *osp = NULL; gsize bytesRead = 0; gsize bytesWritten = 0; GError *error = NULL; gchar *local_fn = g_filename_from_utf8(utf8_fn, -1, &bytesRead, &bytesWritten, &error); gchar const *fn = local_fn; /* TODO: Replace the below fprintf's with something that does the right thing whether in * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the * return code. */ if (fn != NULL) { if (*fn == '|') { fn += 1; while (isspace(*fn)) fn += 1; #ifndef WIN32 osp = popen(fn, "w"); #else osp = _popen(fn, "w"); #endif if (!osp) { fprintf(stderr, "inkscape: popen(%s): %s\n", fn, strerror(errno)); return false; } _stream = osp; } else if (*fn == '>') { fn += 1; while (isspace(*fn)) fn += 1; Inkscape::IO::dump_fopen_call(fn, "K"); osf = Inkscape::IO::fopen_utf8name(fn, "w+"); if (!osf) { fprintf(stderr, "inkscape: fopen(%s): %s\n", fn, strerror(errno)); return false; } _stream = osf; } else { /* put cwd stuff in here */ gchar *qn = ( *fn ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */ : g_strdup("lpr") ); #ifndef WIN32 osp = popen(qn, "w"); #else osp = _popen(qn, "w"); #endif if (!osp) { fprintf(stderr, "inkscape: popen(%s): %s\n", qn, strerror(errno)); return false; } g_free(qn); _stream = osp; } } g_free(local_fn); if (_stream) { /* fixme: this is kinda icky */ #if !defined(_WIN32) && !defined(__WIN32__) (void) signal(SIGPIPE, SIG_IGN); #endif } return true; } bool CairoRenderContext::setPsTarget(gchar const *utf8_fn) { #ifndef CAIRO_HAS_PS_SURFACE return false; #else _target = CAIRO_SURFACE_TYPE_PS; _vector_based_target = TRUE; #endif FILE *osf = NULL; FILE *osp = NULL; gsize bytesRead = 0; gsize bytesWritten = 0; GError *error = NULL; gchar *local_fn = g_filename_from_utf8(utf8_fn, -1, &bytesRead, &bytesWritten, &error); gchar const *fn = local_fn; /* TODO: Replace the below fprintf's with something that does the right thing whether in * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the * return code. */ if (fn != NULL) { if (*fn == '|') { fn += 1; while (isspace(*fn)) fn += 1; #ifndef WIN32 osp = popen(fn, "w"); #else osp = _popen(fn, "w"); #endif if (!osp) { fprintf(stderr, "inkscape: popen(%s): %s\n", fn, strerror(errno)); return false; } _stream = osp; } else if (*fn == '>') { fn += 1; while (isspace(*fn)) fn += 1; Inkscape::IO::dump_fopen_call(fn, "K"); osf = Inkscape::IO::fopen_utf8name(fn, "w+"); if (!osf) { fprintf(stderr, "inkscape: fopen(%s): %s\n", fn, strerror(errno)); return false; } _stream = osf; } else { /* put cwd stuff in here */ gchar *qn = ( *fn ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */ : g_strdup("lpr") ); #ifndef WIN32 osp = popen(qn, "w"); #else osp = _popen(qn, "w"); #endif if (!osp) { fprintf(stderr, "inkscape: popen(%s): %s\n", qn, strerror(errno)); return false; } g_free(qn); _stream = osp; } } g_free(local_fn); if (_stream) { /* fixme: this is kinda icky */ #if !defined(_WIN32) && !defined(__WIN32__) (void) signal(SIGPIPE, SIG_IGN); #endif } return true; } void CairoRenderContext::setPSLevel(unsigned int level) { _ps_level = level; } unsigned int CairoRenderContext::getPSLevel(void) { return _ps_level; } void CairoRenderContext::setPDFLevel(unsigned int level) { _pdf_level = level; } void CairoRenderContext::setTextToPath(bool texttopath) { _is_texttopath = texttopath; } void CairoRenderContext::setFilterToBitmap(bool filtertobitmap) { _is_filtertobitmap = filtertobitmap; } bool CairoRenderContext::getFilterToBitmap(void) { return _is_filtertobitmap; } void CairoRenderContext::setBitmapResolution(int resolution) { _bitmapresolution = resolution; } int CairoRenderContext::getBitmapResolution(void) { return _bitmapresolution; } cairo_surface_t* CairoRenderContext::getSurface(void) { g_assert( _is_valid ); return _surface; } bool CairoRenderContext::saveAsPng(const char *file_name) { cairo_status_t status = cairo_surface_write_to_png(_surface, file_name); if (status) return false; else return true; } void CairoRenderContext::setRenderMode(CairoRenderMode mode) { switch (mode) { case RENDER_MODE_NORMAL: case RENDER_MODE_CLIP: _render_mode = mode; break; default: _render_mode = RENDER_MODE_NORMAL; break; } } CairoRenderContext::CairoRenderMode CairoRenderContext::getRenderMode(void) const { return _render_mode; } void CairoRenderContext::setClipMode(CairoClipMode mode) { switch (mode) { case CLIP_MODE_PATH: case CLIP_MODE_MASK: _clip_mode = mode; break; default: _clip_mode = CLIP_MODE_PATH; break; } } CairoRenderContext::CairoClipMode CairoRenderContext::getClipMode(void) const { return _clip_mode; } CairoRenderState* CairoRenderContext::_createState(void) { CairoRenderState *state = (CairoRenderState*)g_malloc(sizeof(CairoRenderState)); g_assert( state != NULL ); state->has_filtereffect = FALSE; state->merge_opacity = TRUE; state->opacity = 1.0; state->need_layer = FALSE; state->has_overflow = FALSE; state->parent_has_userspace = FALSE; state->clip_path = NULL; state->mask = NULL; return state; } void CairoRenderContext::pushLayer(void) { g_assert( _is_valid ); TRACE(("--pushLayer\n")); cairo_push_group(_cr); // clear buffer if (!_vector_based_target) { cairo_save(_cr); cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR); cairo_paint(_cr); cairo_restore(_cr); } } void CairoRenderContext::popLayer(void) { g_assert( _is_valid ); float opacity = _state->opacity; TRACE(("--popLayer w/ %f\n", opacity)); // apply clipPath or mask if present SPClipPath *clip_path = _state->clip_path; SPMask *mask = _state->mask; if (clip_path || mask) { CairoRenderContext *clip_ctx = 0; cairo_surface_t *clip_mask = 0; if (clip_path) { if (_render_mode == RENDER_MODE_CLIP) mask = NULL; // disable mask when performing nested clipping if (_vector_based_target) { setClipMode(CLIP_MODE_PATH); if (!mask) { cairo_pop_group_to_source(_cr); _renderer->applyClipPath(this, clip_path); if (opacity == 1.0) cairo_paint(_cr); else cairo_paint_with_alpha(_cr, opacity); } else { // the clipPath will be applied before masking } } else { // setup a new rendering context clip_ctx = _renderer->createContext(); clip_ctx->setImageTarget(CAIRO_FORMAT_A8); clip_ctx->setClipMode(CLIP_MODE_MASK); if (!clip_ctx->setupSurface(_width, _height)) { TRACE(("setupSurface failed\n")); _renderer->destroyContext(clip_ctx); return; } // clear buffer cairo_save(clip_ctx->_cr); cairo_set_operator(clip_ctx->_cr, CAIRO_OPERATOR_CLEAR); cairo_paint(clip_ctx->_cr); cairo_restore(clip_ctx->_cr); // if a mask won't be applied set opacity too if (!mask) cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, opacity); else cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, 1.0); // copy over the correct CTM if (_state->parent_has_userspace) clip_ctx->setTransform(&getParentState()->transform); else clip_ctx->setTransform(&_state->transform); // apply the clip path clip_ctx->pushState(); _renderer->applyClipPath(clip_ctx, clip_path); clip_ctx->popState(); clip_mask = clip_ctx->getSurface(); TEST(clip_ctx->saveAsPng("clip_mask.png")); if (!mask) { cairo_pop_group_to_source(_cr); cairo_mask_surface(_cr, clip_mask, 0, 0); _renderer->destroyContext(clip_ctx); } } } if (mask) { // create rendering context for mask CairoRenderContext *mask_ctx = _renderer->createContext(); mask_ctx->setupSurface(_width, _height); // set rendering mode to normal setRenderMode(RENDER_MODE_NORMAL); // copy the correct CTM to mask context if (_state->parent_has_userspace) mask_ctx->setTransform(&getParentState()->transform); else mask_ctx->setTransform(&_state->transform); // render mask contents to mask_ctx _renderer->applyMask(mask_ctx, mask); TEST(mask_ctx->saveAsPng("mask.png")); // composite with clip mask if (clip_path && _clip_mode == CLIP_MODE_MASK) { cairo_mask_surface(mask_ctx->_cr, clip_mask, 0, 0); _renderer->destroyContext(clip_ctx); } cairo_surface_t *mask_image = mask_ctx->getSurface(); int width = cairo_image_surface_get_width(mask_image); int height = cairo_image_surface_get_height(mask_image); int stride = cairo_image_surface_get_stride(mask_image); unsigned char *pixels = cairo_image_surface_get_data(mask_image); // premultiply with opacity if (_state->opacity != 1.0) { TRACE(("premul w/ %f\n", opacity)); guint8 int_opacity = (guint8)(255 * opacity); for (int row = 0 ; row < height; row++) { unsigned char *row_data = pixels + (row * stride); for (int i = 0 ; i < width; i++) { guint32 *pixel = (guint32 *)row_data + i; *pixel = ((((*pixel & 0x00ff0000) >> 16) * 13817 + ((*pixel & 0x0000ff00) >> 8) * 46518 + ((*pixel & 0x000000ff) ) * 4688) * int_opacity); } } } cairo_pop_group_to_source(_cr); if (_clip_mode == CLIP_MODE_PATH) { // we have to do the clipping after cairo_pop_group_to_source _renderer->applyClipPath(this, clip_path); } // apply the mask onto the layer cairo_mask_surface(_cr, mask_image, 0, 0); _renderer->destroyContext(mask_ctx); } } else { cairo_pop_group_to_source(_cr); if (opacity == 1.0) cairo_paint(_cr); else cairo_paint_with_alpha(_cr, opacity); } } void CairoRenderContext::addClipPath(NArtBpath const *bp, SPIEnum const *fill_rule) { g_assert( _is_valid ); // here it should be checked whether the current clip winding changed // so we could switch back to masked clipping if (fill_rule->value == SP_WIND_RULE_EVENODD) { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); } else { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); } addBpath(bp); } void CairoRenderContext::addClippingRect(double x, double y, double width, double height) { g_assert( _is_valid ); cairo_rectangle(_cr, x, y, width, height); cairo_clip(_cr); } bool CairoRenderContext::setupSurface(double width, double height) { // Is the surface already set up? if (_is_valid) return true; if (_vector_based_target && _stream == NULL) return false; cairo_surface_t *surface = NULL; switch (_target) { case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(_target_format, (int)ceil(width), (int)ceil(height)); break; #ifdef CAIRO_HAS_PDF_SURFACE case CAIRO_SURFACE_TYPE_PDF: surface = cairo_pdf_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); break; #endif #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); #if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { return FALSE; } cairo_ps_surface_restrict_to_level (surface, (cairo_ps_level_t)_ps_level); #endif break; #endif default: return false; break; } return _finishSurfaceSetup (surface); } bool CairoRenderContext::setSurfaceTarget(cairo_surface_t *surface, bool is_vector) { if (_is_valid || !surface) return false; _vector_based_target = is_vector; bool ret = _finishSurfaceSetup (surface); if (ret) cairo_surface_reference (surface); return ret; } bool CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface) { if(surface == NULL) { return FALSE; } if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { return FALSE; } _cr = cairo_create(surface); _surface = surface; if (_vector_based_target) { cairo_scale(_cr, PT_PER_PX, PT_PER_PX); } else if (cairo_surface_get_content(_surface) != CAIRO_CONTENT_ALPHA) { // set background color on non-alpha surfaces // TODO: bgcolor should be derived from SPDocument cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); cairo_rectangle(_cr, 0, 0, _width, _height); cairo_fill(_cr); } _is_valid = TRUE; return true; } bool CairoRenderContext::finish(void) { g_assert( _is_valid ); if (_vector_based_target) cairo_show_page(_cr); cairo_destroy(_cr); cairo_surface_destroy(_surface); _cr = NULL; _surface = NULL; if (_layout) g_object_unref(_layout); _is_valid = FALSE; if (_vector_based_target && _stream) { /* Flush stream to be sure. */ (void) fflush(_stream); fclose(_stream); _stream = NULL; } return true; } void CairoRenderContext::transform(NR::Matrix const *transform) { g_assert( _is_valid ); cairo_matrix_t matrix; _initCairoMatrix(&matrix, transform); cairo_transform(_cr, &matrix); // store new CTM getTransform(&_state->transform); } void CairoRenderContext::setTransform(NR::Matrix const *transform) { g_assert( _is_valid ); cairo_matrix_t matrix; _initCairoMatrix(&matrix, transform); cairo_set_matrix(_cr, &matrix); _state->transform = *transform; } void CairoRenderContext::getTransform(NR::Matrix *copy) const { g_assert( _is_valid ); cairo_matrix_t ctm; cairo_get_matrix(_cr, &ctm); (*copy)[0] = ctm.xx; (*copy)[1] = ctm.yx; (*copy)[2] = ctm.xy; (*copy)[3] = ctm.yy; (*copy)[4] = ctm.x0; (*copy)[5] = ctm.y0; } void CairoRenderContext::getParentTransform(NR::Matrix *copy) const { g_assert( _is_valid ); CairoRenderState *parent_state = getParentState(); memcpy(copy, &parent_state->transform, sizeof(NR::Matrix)); } void CairoRenderContext::pushState(void) { g_assert( _is_valid ); cairo_save(_cr); CairoRenderState *new_state = _createState(); // copy current state's transform new_state->transform = _state->transform; _state_stack = g_slist_prepend(_state_stack, new_state); _state = new_state; } void CairoRenderContext::popState(void) { g_assert( _is_valid ); cairo_restore(_cr); g_free(_state_stack->data); _state_stack = g_slist_remove_link(_state_stack, _state_stack); _state = (CairoRenderState*)_state_stack->data; g_assert( g_slist_length(_state_stack) > 0 ); } static bool pattern_hasItemChildren (SPPattern *pat) { for (SPObject *child = sp_object_first_child(SP_OBJECT(pat)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { if (SP_IS_ITEM (child)) { return true; } } return false; } cairo_pattern_t* CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver, NRRect const *pbox) { g_assert( SP_IS_PATTERN(paintserver) ); SPPattern *pat = SP_PATTERN (paintserver); NR::Matrix ps2user, pcs2dev; ps2user.set_identity(); pcs2dev.set_identity(); double x = pattern_x(pat); double y = pattern_y(pat); double width = pattern_width(pat); double height = pattern_height(pat); double bbox_width_scaler; double bbox_height_scaler; TRACE(("%f x %f pattern\n", width, height)); if (pbox && pattern_patternUnits(pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) { //NR::Matrix bbox2user (pbox->x1 - pbox->x0, 0.0, 0.0, pbox->y1 - pbox->y0, pbox->x0, pbox->y0); bbox_width_scaler = pbox->x1 - pbox->x0; bbox_height_scaler = pbox->y1 - pbox->y0; ps2user[4] = x * bbox_width_scaler + pbox->x0; ps2user[5] = y * bbox_height_scaler + pbox->y0; } else { bbox_width_scaler = 1.0; bbox_height_scaler = 1.0; ps2user[4] = x; ps2user[5] = y; } // apply pattern transformation NR::Matrix pattern_transform(pattern_patternTransform(pat)); ps2user *= pattern_transform; // create pattern contents coordinate system if (pat->viewBox_set) { NRRect *view_box = pattern_viewBox(pat); double x, y, w, h; double view_width, view_height; x = 0; y = 0; w = width * bbox_width_scaler; h = height * bbox_height_scaler; view_width = view_box->x1 - view_box->x0; view_height = view_box->y1 - view_box->y0; //calculatePreserveAspectRatio(pat->aspect_align, pat->aspect_clip, view_width, view_height, &x, &y, &w, &h); pcs2dev[0] = w / view_width; pcs2dev[3] = h / view_height; pcs2dev[4] = x - view_box->x0 * pcs2dev[0]; pcs2dev[5] = y - view_box->y0 * pcs2dev[3]; } else if (pbox && pattern_patternContentUnits(pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) { pcs2dev[0] = pbox->x1 - pbox->x0; pcs2dev[3] = pbox->y1 - pbox->y0; } // calculate the size of the surface which has to be created // the scaling needs to be taken into account in the ctm after the pattern transformation NR::Matrix temp; temp = pattern_transform * _state->transform; double width_scaler = sqrt(temp[0] * temp[0] + temp[2] * temp[2]); double height_scaler = sqrt(temp[1] * temp[1] + temp[3] * temp[3]); if (_vector_based_target) { // eliminate PT_PER_PX mul from these width_scaler *= 1.25; height_scaler *= 1.25; } double surface_width = ceil(bbox_width_scaler * width_scaler * width); double surface_height = ceil(bbox_height_scaler * height_scaler * height); TRACE(("surface size: %f x %f\n", surface_width, surface_height)); // create new rendering context CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height); // adjust the size of the painted pattern to fit exactly the created surface // this has to be done because of the rounding to obtain an integer pattern surface width/height double scale_width = surface_width / (bbox_width_scaler * width); double scale_height = surface_height / (bbox_height_scaler * height); if (scale_width != 1.0 || scale_height != 1.0 || _vector_based_target) { TRACE(("needed to scale with %f %f\n", scale_width, scale_height)); pcs2dev *= NR::scale(1.0 / scale_width, 1.0 / scale_height); ps2user *= NR::scale(scale_width, scale_height); } pattern_ctx->setTransform(&pcs2dev); pattern_ctx->pushState(); // create arena and group NRArena *arena = NRArena::create(); unsigned dkey = sp_item_display_key_new(1); // show items and render them for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { if (SP_IS_ITEM (child)) { sp_item_invoke_show (SP_ITEM (child), arena, dkey, SP_ITEM_REFERENCE_FLAGS); _renderer->renderItem(pattern_ctx, SP_ITEM (child)); } } break; // do not go further up the chain if children are found } } pattern_ctx->popState(); // setup a cairo_pattern_t cairo_surface_t *pattern_surface = pattern_ctx->getSurface(); TEST(pattern_ctx->saveAsPng("pattern.png")); cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface); cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT); // set pattern transformation cairo_matrix_t pattern_matrix; _initCairoMatrix(&pattern_matrix, &ps2user); cairo_matrix_invert(&pattern_matrix); cairo_pattern_set_matrix(result, &pattern_matrix); delete pattern_ctx; // hide all items for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { if (SP_IS_ITEM (child)) { sp_item_invoke_hide (SP_ITEM (child), dkey); } } break; // do not go further up the chain if children are found } } return result; } cairo_pattern_t* CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const paintserver, NRRect const *pbox, float alpha) { cairo_pattern_t *pattern = NULL; bool apply_bbox2user = FALSE; if (SP_IS_LINEARGRADIENT (paintserver)) { SPLinearGradient *lg=SP_LINEARGRADIENT(paintserver); sp_gradient_ensure_vector(SP_GRADIENT(lg)); // when exporting from commandline, vector is not built NR::Point p1 (lg->x1.computed, lg->y1.computed); NR::Point p2 (lg->x2.computed, lg->y2.computed); if (pbox && SP_GRADIENT(lg)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { // convert to userspace NR::Matrix bbox2user(pbox->x1 - pbox->x0, 0, 0, pbox->y1 - pbox->y0, pbox->x0, pbox->y0); p1 *= bbox2user; p2 *= bbox2user; } // create linear gradient pattern pattern = cairo_pattern_create_linear(p1[NR::X], p1[NR::Y], p2[NR::X], p2[NR::Y]); // add stops for (gint i = 0; unsigned(i) < lg->vector.stops.size(); i++) { float rgb[3]; sp_color_get_rgb_floatv(&lg->vector.stops[i].color, rgb); cairo_pattern_add_color_stop_rgba(pattern, lg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], lg->vector.stops[i].opacity * alpha); } } else if (SP_IS_RADIALGRADIENT (paintserver)) { SPRadialGradient *rg=SP_RADIALGRADIENT(paintserver); sp_gradient_ensure_vector(SP_GRADIENT(rg)); // when exporting from commandline, vector is not built NR::Point c (rg->cx.computed, rg->cy.computed); NR::Point f (rg->fx.computed, rg->fy.computed); double r = rg->r.computed; if (pbox && SP_GRADIENT(rg)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) apply_bbox2user = true; // create radial gradient pattern pattern = cairo_pattern_create_radial(f[NR::X], f[NR::Y], 0, c[NR::X], c[NR::Y], r); // add stops for (gint i = 0; unsigned(i) < rg->vector.stops.size(); i++) { float rgb[3]; sp_color_get_rgb_floatv(&rg->vector.stops[i].color, rgb); cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha); } } else if (SP_IS_PATTERN (paintserver)) { pattern = _createPatternPainter(paintserver, pbox); } else { return NULL; } if (pattern && SP_IS_GRADIENT (paintserver)) { SPGradient *g = SP_GRADIENT(paintserver); // set extend type SPGradientSpread spread = sp_gradient_get_spread(g); switch (spread) { case SP_GRADIENT_SPREAD_REPEAT: { cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); break; } case SP_GRADIENT_SPREAD_REFLECT: { // not supported by cairo-pdf yet cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REFLECT); break; } case SP_GRADIENT_SPREAD_PAD: { // not supported by cairo-pdf yet cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); break; } default: { cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE); break; } } cairo_matrix_t pattern_matrix; if (g->gradientTransform_set) { // apply gradient transformation cairo_matrix_init(&pattern_matrix, g->gradientTransform[0], g->gradientTransform[1], g->gradientTransform[2], g->gradientTransform[3], g->gradientTransform[4], g->gradientTransform[5]); } else { cairo_matrix_init_identity (&pattern_matrix); } if (apply_bbox2user) { // convert to userspace cairo_matrix_t bbox2user; cairo_matrix_init (&bbox2user, pbox->x1 - pbox->x0, 0, 0, pbox->y1 - pbox->y0, pbox->x0, pbox->y0); cairo_matrix_multiply (&pattern_matrix, &bbox2user, &pattern_matrix); } cairo_matrix_invert(&pattern_matrix); // because Cairo expects a userspace->patternspace matrix cairo_pattern_set_matrix(pattern, &pattern_matrix); } return pattern; } void CairoRenderContext::_setFillStyle(SPStyle const *const style, NRRect const *pbox) { g_return_if_fail( style->fill.isColor() || style->fill.isPaintserver() ); float alpha = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); if (_state->merge_opacity) { alpha *= _state->opacity; TRACE(("merged op=%f\n", alpha)); } if (style->fill.isColor()) { float rgb[3]; sp_color_get_rgb_floatv(&style->fill.value.color, rgb); cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); } else { g_assert( style->fill.isPaintserver() || SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style)) ); cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_FILL_SERVER(style), pbox, alpha); if (pattern) { cairo_set_source(_cr, pattern); cairo_pattern_destroy(pattern); } } } void CairoRenderContext::_setStrokeStyle(SPStyle const *style, NRRect const *pbox) { float alpha = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); if (_state->merge_opacity) alpha *= _state->opacity; if (style->stroke.isColor()) { float rgb[3]; sp_color_get_rgb_floatv(&style->stroke.value.color, rgb); cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); } else { g_assert( style->fill.isPaintserver() || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) ); cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha); if (pattern) { cairo_set_source(_cr, pattern); cairo_pattern_destroy(pattern); } } if (style->stroke_dash.n_dash && style->stroke_dash.dash ) { cairo_set_dash(_cr, style->stroke_dash.dash, style->stroke_dash.n_dash, style->stroke_dash.offset); } else { cairo_set_dash(_cr, NULL, 0, 0.0); // disable dashing } cairo_set_line_width(_cr, style->stroke_width.computed); // set line join type cairo_line_join_t join = CAIRO_LINE_JOIN_MITER; switch (style->stroke_linejoin.computed) { case SP_STROKE_LINEJOIN_MITER: join = CAIRO_LINE_JOIN_MITER; break; case SP_STROKE_LINEJOIN_ROUND: join = CAIRO_LINE_JOIN_ROUND; break; case SP_STROKE_LINEJOIN_BEVEL: join = CAIRO_LINE_JOIN_BEVEL; break; } cairo_set_line_join(_cr, join); // set line cap type cairo_line_cap_t cap = CAIRO_LINE_CAP_BUTT; switch (style->stroke_linecap.computed) { case SP_STROKE_LINECAP_BUTT: cap = CAIRO_LINE_CAP_BUTT; break; case SP_STROKE_LINECAP_ROUND: cap = CAIRO_LINE_CAP_ROUND; break; case SP_STROKE_LINECAP_SQUARE: cap = CAIRO_LINE_CAP_SQUARE; break; } cairo_set_line_cap(_cr, cap); cairo_set_miter_limit(_cr, MAX(1, style->stroke_miterlimit.value)); } bool CairoRenderContext::renderPath(const_NRBPath const *bpath, SPStyle const *style, NRRect const *pbox) { g_assert( _is_valid ); if (_render_mode == RENDER_MODE_CLIP) { if (_clip_mode == CLIP_MODE_PATH) { addClipPath(bpath->path, &style->fill_rule); } else { setBpath(bpath->path); if (style->fill_rule.value == SP_WIND_RULE_EVENODD) { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); } else { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); } cairo_fill(_cr); TEST(cairo_surface_write_to_png (_surface, "pathmask.png")); } return true; } if (style->fill.isNone() && style->stroke.isNone()) return true; bool need_layer = ( !_state->merge_opacity && !_state->need_layer && ( _state->opacity != 1.0 || _state->clip_path != NULL || _state->mask != NULL ) ); if (!need_layer) cairo_save(_cr); else pushLayer(); if (!style->fill.isNone()) { _setFillStyle(style, pbox); setBpath(bpath->path); if (style->fill_rule.value == SP_WIND_RULE_EVENODD) { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); } else { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); } if (style->stroke.isNone()) cairo_fill(_cr); else cairo_fill_preserve(_cr); } if (!style->stroke.isNone()) { _setStrokeStyle(style, pbox); if (style->fill.isNone()) setBpath(bpath->path); cairo_stroke(_cr); } if (need_layer) popLayer(); else cairo_restore(_cr); return true; } bool CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsigned int rs, NR::Matrix const *image_transform, SPStyle const *style) { g_assert( _is_valid ); if (_render_mode == RENDER_MODE_CLIP) return true; guchar* px_rgba = (guchar*)g_malloc(4 * w * h); if (!px_rgba) return false; float opacity; if (_state->merge_opacity) opacity = _state->opacity; else opacity = 1.0; // make a copy of the original pixbuf with premultiplied alpha // if we pass the original pixbuf it will get messed up for (unsigned i = 0; i < h; i++) { for (unsigned j = 0; j < w; j++) { guchar const *src = px + i * rs + j * 4; guint32 *dst = (guint32 *)(px_rgba + i * rs + j * 4); guchar r, g, b, alpha_dst; // calculate opacity-modified alpha alpha_dst = src[3]; if (opacity != 1.0 && _vector_based_target) alpha_dst = (guchar)ceil((float)alpha_dst * opacity); // premul alpha (needed because this will be undone by cairo-pdf) r = src[0]*alpha_dst/255; g = src[1]*alpha_dst/255; b = src[2]*alpha_dst/255; *dst = (((alpha_dst) << 24) | (((r)) << 16) | (((g)) << 8) | (b)); } } cairo_surface_t *image_surface = cairo_image_surface_create_for_data(px_rgba, CAIRO_FORMAT_ARGB32, w, h, w * 4); if (cairo_surface_status(image_surface)) { TRACE(("Image surface creation failed:\n%s\n", cairo_status_to_string(cairo_surface_status(image_surface)))); return false; } // setup automatic freeing of the image data when destroying the surface static cairo_user_data_key_t key; cairo_surface_set_user_data(image_surface, &key, px_rgba, (cairo_destroy_func_t)g_free); cairo_save(_cr); // scaling by width & height is not needed because it will be done by Cairo if (image_transform) transform(image_transform); cairo_set_source_surface(_cr, image_surface, 0.0, 0.0); // set clip region so that the pattern will not be repeated (bug in Cairo-PDF) if (_vector_based_target) { cairo_new_path(_cr); cairo_rectangle(_cr, 0, 0, w, h); cairo_clip(_cr); } if (_vector_based_target) cairo_paint(_cr); else cairo_paint_with_alpha(_cr, opacity); cairo_restore(_cr); cairo_surface_destroy(image_surface); return true; } #define GLYPH_ARRAY_SIZE 64 unsigned int CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector const &glyphtext, bool is_stroke) { cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE]; cairo_glyph_t *glyphs = glyph_array; unsigned int num_glyphs = glyphtext.size(); if (num_glyphs > GLYPH_ARRAY_SIZE) glyphs = (cairo_glyph_t*)g_malloc(sizeof(cairo_glyph_t) * num_glyphs); unsigned int num_invalid_glyphs = 0; unsigned int i = 0; for (std::vector::const_iterator it_info = glyphtext.begin() ; it_info != glyphtext.end() ; it_info++) { // skip glyphs which are PANGO_GLYPH_EMPTY (0x0FFFFFFF) // or have the PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) set if (it_info->index == 0x0FFFFFFF || it_info->index & 0x10000000) { TRACE(("INVALID GLYPH found\n")); num_invalid_glyphs++; continue; } glyphs[i - num_invalid_glyphs].index = it_info->index; glyphs[i - num_invalid_glyphs].x = it_info->x; glyphs[i - num_invalid_glyphs].y = it_info->y; i++; } if (is_stroke || _is_texttopath) cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs); else cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs); if (num_glyphs > GLYPH_ARRAY_SIZE) g_free(glyphs); return num_glyphs - num_invalid_glyphs; } bool CairoRenderContext::renderGlyphtext(PangoFont *font, NR::Matrix const *font_matrix, std::vector const &glyphtext, SPStyle const *style) { // create a cairo_font_face from PangoFont double size = style->font_size.computed; cairo_font_face_t *font_face = NULL; FcPattern *fc_pattern = NULL; #ifdef USE_PANGO_WIN32 # ifdef CAIRO_HAS_WIN32_FONT LOGFONTA *lfa = pango_win32_font_logfont(font); LOGFONTW lfw; ZeroMemory(&lfw, sizeof(LOGFONTW)); memcpy(&lfw, lfa, sizeof(LOGFONTA)); MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE); font_face = cairo_win32_font_face_create_for_logfontw(&lfw); # endif #else # ifdef CAIRO_HAS_FT_FONT PangoFcFont *fc_font = PANGO_FC_FONT(font); fc_pattern = fc_font->font_pattern; font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); # endif #endif cairo_save(_cr); cairo_set_font_face(_cr, font_face); if (fc_pattern && FcPatternGetDouble(fc_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch) size = 12.0; // set the given font matrix cairo_matrix_t matrix; _initCairoMatrix(&matrix, font_matrix); cairo_set_font_matrix(_cr, &matrix); if (_render_mode == RENDER_MODE_CLIP) { if (_clip_mode == CLIP_MODE_MASK) { if (style->fill_rule.value == SP_WIND_RULE_EVENODD) { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); } else { cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); } _showGlyphs(_cr, font, glyphtext, FALSE); } else { // just add the glyph paths to the current context _showGlyphs(_cr, font, glyphtext, TRUE); } } else { if (style->fill.isColor() || style->fill.isPaintserver()) { // set fill style _setFillStyle(style, NULL); _showGlyphs(_cr, font, glyphtext, FALSE); } if (style->stroke.isColor() || style->stroke.isPaintserver()) { // set stroke style _setStrokeStyle(style, NULL); // paint stroke _showGlyphs(_cr, font, glyphtext, TRUE); cairo_stroke(_cr); } } cairo_restore(_cr); if (font_face) cairo_font_face_destroy(font_face); return true; } /* Helper functions */ void CairoRenderContext::addBpath(NArtBpath const *bp) { bool closed = false; while (bp->code != NR_END) { switch (bp->code) { case NR_MOVETO: if (closed) { cairo_close_path(_cr); } closed = true; cairo_move_to(_cr, bp->x3, bp->y3); break; case NR_MOVETO_OPEN: if (closed) { cairo_close_path(_cr); } closed = false; cairo_move_to(_cr, bp->x3, bp->y3); break; case NR_LINETO: cairo_line_to(_cr, bp->x3, bp->y3); break; case NR_CURVETO: cairo_curve_to(_cr, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3); break; default: break; } bp += 1; } if (closed) { cairo_close_path(_cr); } } void CairoRenderContext::setBpath(NArtBpath const *bp) { cairo_new_path(_cr); if (bp) addBpath(bp); } void CairoRenderContext::_concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0) { cairo_matrix_t matrix; cairo_matrix_init(&matrix, xx, yx, xy, yy, x0, y0); cairo_transform(cr, &matrix); } void CairoRenderContext::_initCairoMatrix(cairo_matrix_t *matrix, NR::Matrix const *transform) { matrix->xx = (*transform)[0]; matrix->yx = (*transform)[1]; matrix->xy = (*transform)[2]; matrix->yy = (*transform)[3]; matrix->x0 = (*transform)[4]; matrix->y0 = (*transform)[5]; } void CairoRenderContext::_concatTransform(cairo_t *cr, NR::Matrix const *transform) { _concatTransform(cr, (*transform)[0], (*transform)[1], (*transform)[2], (*transform)[3], (*transform)[4], (*transform)[5]); } static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length) { size_t written; FILE *file = (FILE*)closure; written = fwrite (data, 1, length, file); if (written == length) return CAIRO_STATUS_SUCCESS; else return CAIRO_STATUS_WRITE_ERROR; } #include "clear-n_.h" } /* namespace Internal */ } /* namespace Extension */ } /* namespace Inkscape */ #undef TRACE #undef TEST /* End of GNU GPL code */ /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :