void DrawingText::_clipItem(DrawingContext &ct, Geom::IntRect const &/*area*/) { Inkscape::DrawingContext::Save save(ct); // handle clip-rule if (_style) { if (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) { ct.setFillRule(CAIRO_FILL_RULE_EVEN_ODD); } else { ct.setFillRule(CAIRO_FILL_RULE_WINDING); } } for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&*i); if (!g) { throw InvalidItemException(); } Inkscape::DrawingContext::Save save(ct); ct.transform(g->_ctm); ct.path(*g->_font->PathVector(g->_glyph)); } ct.fill(); }
unsigned DrawingText::_renderItem(DrawingContext &ct, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/) { if (_drawing.outline()) { guint32 rgba = _drawing.outlinecolor; Inkscape::DrawingContext::Save save(ct); ct.setSource(rgba); ct.setTolerance(0.5); // low quality, but good enough for outline mode for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&*i); if (!g) throw InvalidItemException(); Inkscape::DrawingContext::Save save(ct); // skip glpyhs with singular transforms if (g->_ctm.isSingular()) continue; ct.transform(g->_ctm); ct.path(*g->_font->PathVector(g->_glyph)); ct.fill(); } return RENDER_OK; } // NOTE: this is very similar to drawing-shape.cpp; the only difference is in path feeding bool has_stroke, has_fill; has_fill = _nrstyle.prepareFill(ct, _item_bbox); has_stroke = _nrstyle.prepareStroke(ct, _item_bbox); if (has_fill || has_stroke) { for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&*i); if (!g) throw InvalidItemException(); Inkscape::DrawingContext::Save save(ct); if (g->_ctm.isSingular()) continue; ct.transform(g->_ctm); ct.path(*g->_font->PathVector(g->_glyph)); } Inkscape::DrawingContext::Save save(ct); ct.transform(_ctm); if (has_fill) { _nrstyle.applyFill(ct); ct.fillPreserve(); } if (has_stroke) { _nrstyle.applyStroke(ct); ct.strokePreserve(); } ct.newPath(); // clear path } return RENDER_OK; }
/** * Paints the clean area from cache and modifies the @a area * parameter to the bounds of the region that must be repainted. */ void DrawingCache::paintFromCache(DrawingContext &dc, Geom::OptIntRect &area) { if (!area) return; // We subtract the clean region from the area, then get the bounds // of the resulting region. This is the area that needs to be repainted // by the item. // Then we subtract the area that needs to be repainted from the // original area and paint the resulting region from cache. cairo_rectangle_int_t area_c = _convertRect(*area); cairo_region_t *dirty_region = cairo_region_create_rectangle(&area_c); cairo_region_t *cache_region = cairo_region_copy(dirty_region); cairo_region_subtract(dirty_region, _clean_region); if (cairo_region_is_empty(dirty_region)) { area = Geom::OptIntRect(); } else { cairo_rectangle_int_t to_repaint; cairo_region_get_extents(dirty_region, &to_repaint); area = _convertRect(to_repaint); cairo_region_subtract_rectangle(cache_region, &to_repaint); } cairo_region_destroy(dirty_region); if (!cairo_region_is_empty(cache_region)) { int nr = cairo_region_num_rectangles(cache_region); cairo_rectangle_int_t tmp; for (int i = 0; i < nr; ++i) { cairo_region_get_rectangle(cache_region, i, &tmp); dc.rectangle(_convertRect(tmp)); } dc.setSource(this); dc.fill(); } cairo_region_destroy(cache_region); }
/** * Rasterize items. * This method submits the drawing opeartions required to draw this item * to the supplied DrawingContext, restricting drawing the specified area. * * This method does some common tasks and calls the item-specific rendering * function, _renderItem(), to render e.g. paths or bitmaps. * * @param flags Rendering options. This deals mainly with cache control. */ unsigned DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at) { bool outline = _drawing.outline(); bool render_filters = _drawing.renderFilters(); // stop_at is handled in DrawingGroup, but this check is required to handle the case // where a filtered item with background-accessing filter has enable-background: new if (this == stop_at) return RENDER_STOP; // If we are invisible, return immediately if (!_visible) return RENDER_OK; if (_ctm.isSingular(1e-18)) return RENDER_OK; // TODO convert outline rendering to a separate virtual function if (outline) { _renderOutline(dc, area, flags); return RENDER_OK; } // carea is the area to paint Geom::OptIntRect carea = Geom::intersect(area, _drawbox); if (!carea) return RENDER_OK; if (_antialias) { cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_DEFAULT); } else { cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_NONE); } // render from cache if possible if (_cached) { if (_cache) { _cache->prepare(); set_cairo_blend_operator( dc, _mix_blend_mode ); _cache->paintFromCache(dc, carea); if (!carea) return RENDER_OK; } else { // There is no cache. This could be because caching of this item // was just turned on after the last update phase, or because // we were previously outside of the canvas. Geom::OptIntRect cl = _drawing.cacheLimit(); cl.intersectWith(_drawbox); if (cl) { _cache = new DrawingCache(*cl); } } } else { // if our caching was turned off after the last update, it was already // deleted in setCached() } // determine whether this shape needs intermediate rendering. bool needs_intermediate_rendering = false; bool &nir = needs_intermediate_rendering; bool needs_opacity = (_opacity < 0.995); // this item needs an intermediate rendering if: nir |= (_clip != NULL); // 1. it has a clipping path nir |= (_mask != NULL); // 2. it has a mask nir |= (_filter != NULL && render_filters); // 3. it has a filter nir |= needs_opacity; // 4. it is non-opaque nir |= (_cache != NULL); // 5. it is cached nir |= (_mix_blend_mode != SP_CSS_BLEND_NORMAL); // 6. Blend mode not normal nir |= (_isolation == SP_CSS_ISOLATION_ISOLATE); // 7. Explicit isolatiom /* How the rendering is done. * * Clipping, masking and opacity are done by rendering them to a surface * and then compositing the object's rendering onto it with the IN operator. * The object itself is rendered to a group. * * Opacity is done by rendering the clipping path with an alpha * value corresponding to the opacity. If there is no clipping path, * the entire intermediate surface is painted with alpha corresponding * to the opacity value. */ // Short-circuit the simple case. // We also use this path for filter background rendering, because masking, clipping, // filters and opacity do not apply when rendering the ancestors of the filtered // element if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) { return _renderItem(dc, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at); } // iarea is the bounding box for intermediate rendering // Note 1: Pixels inside iarea but outside carea are invalid // (incomplete filter dependence region). // Note 2: We only need to render carea of clip and mask, but // iarea of the object. Geom::OptIntRect iarea = carea; // expand carea to contain the dependent area of filters. if (_filter && render_filters) { _filter->area_enlarge(*iarea, this); iarea.intersectWith(_drawbox); } DrawingSurface intermediate(*iarea); DrawingContext ict(intermediate); unsigned render_result = RENDER_OK; // 1. Render clipping path with alpha = opacity. ict.setSource(0,0,0,_opacity); // Since clip can be combined with opacity, the result could be incorrect // for overlapping clip children. To fix this we use the SOURCE operator // instead of the default OVER. ict.setOperator(CAIRO_OPERATOR_SOURCE); ict.paint(); if (_clip) { ict.pushGroup(); _clip->clip(ict, *carea); // fixme: carea or area? ict.popGroupToSource(); ict.setOperator(CAIRO_OPERATOR_IN); ict.paint(); } ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default // 2. Render the mask if present and compose it with the clipping path + opacity. if (_mask) { ict.pushGroup(); _mask->render(ict, *carea, flags); cairo_surface_t *mask_s = ict.rawTarget(); // Convert mask's luminance to alpha ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha()); ict.popGroupToSource(); ict.setOperator(CAIRO_OPERATOR_IN); ict.paint(); ict.setOperator(CAIRO_OPERATOR_OVER); } // 3. Render object itself ict.pushGroup(); render_result = _renderItem(ict, *iarea, flags, stop_at); // 4. Apply filter. if (_filter && render_filters) { bool rendered = false; if (_filter->uses_background() && _background_accumulate) { DrawingItem *bg_root = this; for (; bg_root; bg_root = bg_root->_parent) { if (bg_root->_background_new) break; } if (bg_root) { DrawingSurface bg(*iarea); DrawingContext bgdc(bg); bg_root->render(bgdc, *iarea, flags | RENDER_FILTER_BACKGROUND, this); _filter->render(this, ict, &bgdc); rendered = true; } } if (!rendered) { _filter->render(this, ict, NULL); } // Note that because the object was rendered to a group, // the internals of the filter need to use cairo_get_group_target() // instead of cairo_get_target(). } // 5. Render object inside the composited mask + clip ict.popGroupToSource(); ict.setOperator(CAIRO_OPERATOR_IN); ict.paint(); // 6. Paint the completed rendering onto the base context (or into cache) if (_cached && _cache) { DrawingContext cachect(*_cache); cachect.rectangle(*carea); cachect.setOperator(CAIRO_OPERATOR_SOURCE); cachect.setSource(&intermediate); cachect.fill(); _cache->markClean(*carea); } dc.rectangle(*carea); dc.setSource(&intermediate); set_cairo_blend_operator( dc, _mix_blend_mode ); dc.fill(); dc.setSource(0,0,0,0); // the call above is to clear a ref on the intermediate surface held by dc return render_result; }