/** \file
* Rendering with Cairo.
*/
/*
* Author:
* Miklos Erdelyi <erdelyim@gmail.com>
* Jon A. Cruz <jon@joncruz.org>
* Abhishek Sharma
*
* 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 <signal.h>
#include <errno.h>
#include <glib.h>
#include "display/canvas-bpath.h"
#include "display/cairo-utils.h"
#include "sp-item.h"
#include "sp-item-group.h"
#include "style.h"
#include "sp-hatch.h"
#include "sp-linear-gradient.h"
#include "sp-radial-gradient.h"
#include "sp-mesh.h"
#include "sp-pattern.h"
#include "sp-mask.h"
#include "sp-clippath.h"
#ifdef WIN32
#include "libnrtype/FontFactory.h" // USE_PANGO_WIN32
#endif
#include "cairo-render-context.h"
#include "cairo-renderer.h"
#include "svg/stringstream.h"
#include <cairo.h>
// include support for only the compiled-in surface types
#ifdef CAIRO_HAS_PDF_SURFACE
#include <cairo-pdf.h>
#endif
#ifdef CAIRO_HAS_PS_SURFACE
#include <cairo-ps.h>
#endif
#ifdef CAIRO_HAS_FT_FONT
#include <cairo-ft.h>
#endif
#ifdef CAIRO_HAS_WIN32_FONT
#include <pango/pangowin32.h>
#include <cairo-win32.h>
#endif
#include <pango/pangofc-fontmap.h>
//#define TRACE(_args) g_printf _args
//#define TRACE(_args) g_message _args
//#define TEST(_args) _args
// FIXME: expose these from sp-clippath/mask.cpp
/*struct SPClipPathView {
SPClipPathView *next;
unsigned int key;
Inkscape::DrawingItem *arenaitem;
Geom::OptRect bbox;
};
struct SPMaskView {
SPMaskView *next;
unsigned int key;
Inkscape::DrawingItem *arenaitem;
Geom::OptRect bbox;
};*/
namespace Inkscape {
namespace Extension {
namespace Internal {
static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length);
_dpi(72),
_pdf_level(1),
_ps_level(1),
_eps(false),
_bitmapresolution(72),
{
}
CairoRenderContext::~CairoRenderContext(void)
{
for (std::map<gpointer, cairo_font_face_t *>::const_iterator iter = font_table.begin(); iter != font_table.end(); ++iter)
}
{
if (font_face) {
}
}
{
return _renderer;
}
{
return _state;
}
{
// if this is the root node just return it
return _state;
} else {
}
}
{
// only opacity & overflow is stored for now
// disable rendering of opacity if there's a stroke on the fill
if (_state->merge_opacity
}
/**
* \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
*/
{
cairo_surface_t *surface = cairo_surface_create_similar(cairo_get_target(_cr), CAIRO_CONTENT_COLOR_ALPHA,
return new_context;
}
{
}
{
// 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:
return true;
break;
default:
break;
}
return false;
}
{
#ifndef CAIRO_HAS_PDF_SURFACE
return false;
#else
#endif
/* 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 == '|') {
fn += 1;
#ifndef WIN32
#else
#endif
if (!osp) {
return false;
}
} else if (*fn == '>') {
fn += 1;
if (!osf) {
return false;
}
} else {
/* put cwd stuff in here */
: g_strdup("lpr") );
#ifndef WIN32
#else
#endif
if (!osp) {
return false;
}
}
}
if (_stream) {
/* fixme: this is kinda icky */
#endif
}
return true;
}
{
#ifndef CAIRO_HAS_PS_SURFACE
return false;
#else
#endif
/* 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 == '|') {
fn += 1;
#ifndef WIN32
#else
#endif
if (!osp) {
return false;
}
} else if (*fn == '>') {
fn += 1;
if (!osf) {
return false;
}
} else {
/* put cwd stuff in here */
: g_strdup("lpr") );
#ifndef WIN32
#else
#endif
if (!osp) {
return false;
}
}
}
if (_stream) {
/* fixme: this is kinda icky */
#endif
}
return true;
}
{
}
{
}
{
return _ps_level;
}
{
_pdf_level = level;
}
{
}
{
}
{
return _is_omittext;
}
{
}
{
return _is_filtertobitmap;
}
{
}
{
return _bitmapresolution;
}
{
return _surface;
}
bool
{
if (status)
return false;
else
return true;
}
void
{
switch (mode) {
case RENDER_MODE_NORMAL:
case RENDER_MODE_CLIP:
_render_mode = mode;
break;
default:
break;
}
}
{
return _render_mode;
}
void
{
switch (mode) {
case CLIP_MODE_PATH: // Clip is rendered as a path for vector output
case CLIP_MODE_MASK: // Clip is rendered as a bitmap for raster output.
_clip_mode = mode;
break;
default:
break;
}
}
{
return _clip_mode;
}
{
return state;
}
{
TRACE(("--pushLayer\n"));
// clear buffer
if (!_vector_based_target) {
}
}
void
{
/*
At this point, the Cairo source is ready. A Cairo mask must be created if required.
Care must be taken of transformatons as Cairo, like PS and PDF, treats clip paths and
masks independently of the objects they effect while in SVG the clip paths and masks
are defined relative to the objects they are attached to.
Notes:
1. An SVG object may have both a clip path and a mask!
2. An SVG clip path can be composed of an object with a clip path. This is not handled properly.
3. An SVG clipped or masked object may be first drawn off the page and then translated onto
the page (document). This is also not handled properly.
4. The code converts all SVG masks to bitmaps. This shouldn't be necessary.
5. Cairo expects a mask to use only the alpha channel. SVG masks combine the RGB luminance with
alpha. This is handled here by doing a pixel by pixel conversion.
*/
// Apply any clip path first
if (clip_path) {
TRACE((" Applying clip\n"));
if (_render_mode == RENDER_MODE_CLIP)
if (_vector_based_target) {
if (!mask) {
if (opacity == 1.0)
else
} else {
// the clipPath will be applied before masking
}
} else {
// setup a new rendering context
// This code ties the clipping to the document coordinates. It doesn't allow
// for a clipped object intially drawn off the page and then translated onto
// the page.
TRACE(("clip: setupSurface failed\n"));
return;
}
// clear buffer
// If a mask won't be applied set opacity too. (The clip is represented by a solid Cairo mask.)
if (!mask)
else
// copy over the correct CTM
// It must be stored in item_transform of current state after pushState.
if (_state->parent_has_userspace)
else
// apply the clip path
if (!mask) {
}
}
}
// Apply any mask second
if (mask) {
TRACE((" Applying mask\n"));
// create rendering context for mask
// Fix Me: This is a kludge. PDF and PS output is set to 72 dpi but the
// Cairo surface is expecting the mask to be 96 dpi.
if( _vector_based_target ) {
}
TRACE(("mask: setupSurface failed\n"));
return;
}
// Mask should start black, but it is created white.
// set rendering mode to normal
// copy the correct CTM to mask context
/*
if (_state->parent_has_userspace)
mask_ctx->setTransform(getParentState()->transform);
else
mask_ctx->setTransform(_state->transform);
*/
// This is probably not correct... but it seems to do the trick.
// render mask contents to mask_ctx
// composite with clip mask
}
// In SVG, the rgb channels as well as the alpha channel is used in masking.
// In Cairo, only the alpha channel is used thus requiring this conversion.
// SVG specifies that RGB be converted to alpha using luminance-to-alpha.
// Notes: This calculation assumes linear RGB values. VERIFY COLOR SPACE!
// The incoming pixel values already include alpha, fill-opacity, etc.,
// however, opacity must still be applied.
for (int i = 0 ; i < width; i++) {
// lum_alpha can be slightly greater than 1 due to rounding errors...
// but this should be OK since it doesn't matter what the lower
// six hexadecimal numbers of *pixel are.
}
}
if (_clip_mode == CLIP_MODE_PATH) {
// we have to do the clipping after cairo_pop_group_to_source
}
// apply the mask onto the layer
}
} else {
// No clip path or mask
if (opacity == 1.0)
else
}
}
void
{
// here it should be checked whether the current clip winding changed
// so we could switch back to masked clipping
} else {
}
}
void
{
}
bool
{
// Is the surface already set up?
if (_is_valid)
return true;
return false;
os_bbox << "%%BoundingBox: 0 0 " << (int)ceil(width) << (int)ceil(height); // apparently, the numbers should be integers. (see bug 380501)
switch (_target) {
case CAIRO_SURFACE_TYPE_IMAGE:
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);
return FALSE;
}
// Cairo calculates the bounding box itself, however we want to override this. See Launchpad bug #380501
// cairo_ps_dsc_comment(surface, os_bbox.str().c_str());
// cairo_ps_dsc_begin_page(surface);
// cairo_ps_dsc_comment(surface, os_pagebbox.str().c_str());
#endif
break;
#endif
default:
return false;
break;
}
}
bool
{
return false;
if (ret)
return ret;
}
bool
{
return false;
}
return false;
}
return false;
}
if (ctm)
if (_vector_based_target) {
cairo_scale(_cr, Inkscape::Util::Quantity::convert(1, "px", "pt"), Inkscape::Util::Quantity::convert(1, "px", "pt"));
// set background color on non-alpha surfaces
// TODO: bgcolor should be derived from SPDocument
}
return true;
}
bool
{
if (_vector_based_target)
if (_layout)
if (_vector_based_target && _stream) {
/* Flush stream to be sure. */
}
if (status == CAIRO_STATUS_SUCCESS)
return true;
else
return false;
}
void
{
// store new CTM
}
void
{
}
{
return ret;
}
{
return parent_state->transform;
}
{
// copy current state's transform
}
{
}
{
bool hasItems = false;
if (SP_IS_ITEM (child)) {
hasItems = true;
}
}
return hasItems;
}
CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox)
{
double x = pat->x();
double y = pat->y();
double bbox_width_scaler;
double bbox_height_scaler;
} else {
bbox_width_scaler = 1.0;
bbox_height_scaler = 1.0;
ps2user[4] = x;
ps2user[5] = y;
}
// apply pattern transformation
// create pattern contents coordinate system
if (pat->viewBox_set) {
double x, y, w, h;
x = 0;
y = 0;
w = width * bbox_width_scaler;
h = height * bbox_height_scaler;
//calculatePreserveAspectRatio(pat->aspect_align, pat->aspect_clip, view_width, view_height, &x, &y, &w, &h);
}
// Calculate the size of the surface which has to be created
// Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel.
// Multiply by SUBPIX_SCALE to allow for less than a pixel precision
// create new rendering context
// adjust the size of the painted pattern to fit exactly the created surface
}
pattern_ctx->pushState();
// create drawing and group
// show items and render them
if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
if (SP_IS_ITEM(child)) {
}
}
break; // do not go further up the chain if children are found
}
}
pattern_ctx->popState();
// setup a cairo_pattern_t
// set pattern transformation
delete pattern_ctx;
// hide all items
if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
if (SP_IS_ITEM(child)) {
}
}
break; // do not go further up the chain if children are found
}
}
return result;
}
CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) {
// create drawing and group
// TODO need to refactor 'evil' referenced code for const correctness.
// Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel.
// Multiply by SUBPIX_SCALE to allow for less than a pixel precision
Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height());
//The rendering of hatch overflow is implemented by repeated drawing
//of hatch paths over one strip. Within each iteration paths are moved by pitch value.
//The movement progresses from right to left. This gives the same result
//as drawing whole strips in left-to-right order.
}
pattern_ctx->pushState();
for (int i = 0; i < overflow_steps; i++) {
for (std::vector<SPHatchPath *>::iterator iter = children.begin(); iter != children.end(); ++iter) {
}
}
pattern_ctx->popState();
// setup a cairo_pattern_t
delete pattern_ctx;
return result;
}
{
if (SP_IS_LINEARGRADIENT (paintserver)) {
// convert to userspace
}
// create linear gradient pattern
// add stops
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)) {
apply_bbox2user = true;
// create radial gradient pattern
// add stops
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_MESH (paintserver)) {
} else if (SP_IS_PATTERN (paintserver)) {
} else if ( dynamic_cast<SPHatch const *>(paintserver) ) {
} else {
return NULL;
}
// set extend type
switch (spread) {
case SP_GRADIENT_SPREAD_REPEAT: {
break;
}
case SP_GRADIENT_SPREAD_REFLECT: { // not supported by cairo-pdf yet
break;
}
case SP_GRADIENT_SPREAD_PAD: { // not supported by cairo-pdf yet
break;
}
default: {
break;
}
}
if (g->gradientTransform_set) {
// apply gradient transformation
} else {
}
if (apply_bbox2user) {
// convert to userspace
}
}
return pattern;
}
void
{
if (_state->merge_opacity) {
}
if (pattern) {
}
} else { // unset fill is black
}
}
void
{
if (_state->merge_opacity)
if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) {
} else {
cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha);
if (pattern) {
}
}
{
for( unsigned i = 0; i < ndashes; ++i ) {
}
} else {
}
// set line join type
case SP_STROKE_LINEJOIN_MITER:
break;
case SP_STROKE_LINEJOIN_ROUND:
break;
case SP_STROKE_LINEJOIN_BEVEL:
break;
}
// set line cap type
case SP_STROKE_LINECAP_BUTT:
break;
case SP_STROKE_LINECAP_ROUND:
break;
case SP_STROKE_LINECAP_SQUARE:
break;
}
}
void
{
// Only PDFLaTeX supports importing a single page of a graphics file,
if (_omittext_state == NEW_PAGE_ON_GRAPHIC)
}
}
void
{
// Only PDFLaTeX supports importing a single page of a graphics file,
if (_omittext_state == GRAPHIC_ON_TOP)
}
}
/* We need CairoPaintOrder as markers are rendered in a separate step and may be rendered
* inbetween fill and stroke.
*/
bool
CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order)
{
if (_render_mode == RENDER_MODE_CLIP) {
if (_clip_mode == CLIP_MODE_PATH) {
} else {
} else {
}
}
return true;
}
order == STROKE_ONLY;
return true;
if (!need_layer)
else
pushLayer();
if (!no_fill) {
} else {
}
}
if (no_stroke)
else
}
if (!no_stroke) {
else
}
}
if (need_layer)
popLayer();
else
return true;
}
{
if (_render_mode == RENDER_MODE_CLIP) {
return true;
}
// TODO: reenable merge_opacity if useful
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;
}
// scaling by width & height is not needed because it will be done by Cairo
// set clip region so that the pattern will not be repeated (bug in Cairo-PDF)
if (_vector_based_target) {
cairo_rectangle(_cr, 0, 0, w, h);
}
// See cairo-pdf-surface.c
if (style) {
// See: http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
// http://www.w3.org/TR/css4-images/#the-image-rendering
// Do nothing
break;
break;
default:
break;
}
}
return true;
}
// TODO investigate why the font is being ignored:
unsigned int CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont * /*font*/, std::vector<CairoGlyphInfo> const &glyphtext, bool path)
{
if (num_glyphs > GLYPH_ARRAY_SIZE) {
return 0;
}
}
unsigned int num_invalid_glyphs = 0;
unsigned int i = 0; // is a counter for indexing the glyphs array, only counts the valid glyphs
for (std::vector<CairoGlyphInfo>::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
TRACE(("INVALID GLYPH found\n"));
g_message("Invalid glyph found, continuing...");
continue;
}
i++;
}
if (path) {
} else {
}
if (num_glyphs > GLYPH_ARRAY_SIZE) {
}
return num_glyphs - num_invalid_glyphs;
}
bool
{
if (_is_omittext)
return true;
// create a cairo_font_face from PangoFont
// double size = style->font_size.computed; /// \fixme why is this variable never used?
#ifdef USE_PANGO_WIN32
# ifdef CAIRO_HAS_WIN32_FONT
MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE);
}
# endif
#else
# ifdef CAIRO_HAS_FT_FONT
}
# endif
#endif
//if (fc_pattern && FcPatternGetDouble(fc_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch)
// size = 12.0;
// set the given font matrix
if (_render_mode == RENDER_MODE_CLIP) {
if (_clip_mode == CLIP_MODE_MASK) {
} else {
}
} else {
// just add the glyph paths to the current context
}
} else {
bool fill = false;
fill = true;
}
bool stroke = false;
stroke = true;
}
// Text never has markers
bool stroke_over_fill = true;
stroke_over_fill = false;
}
bool have_path = false;
if (fill && stroke_over_fill) {
if (_is_texttopath) {
if (stroke) {
have_path = true;
} else {
}
} else {
}
}
if (stroke) {
if (!have_path) {
}
have_path = true;
} else {
}
}
if (fill && !stroke_over_fill) {
if (_is_texttopath) {
if (!have_path) {
// Could happen if both 'stroke' and 'stroke_over_fill' are false
}
} else {
}
}
}
// if (font_face)
// cairo_font_face_destroy(font_face);
return true;
}
/* Helper functions */
void
{
}
void
{
}
void
CairoRenderContext::_concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0)
{
}
void
{
}
void
{
}
static cairo_status_t
{
return CAIRO_STATUS_SUCCESS;
else
return CAIRO_STATUS_WRITE_ERROR;
}
#include "clear-n_.h"
} /* namespace Internal */
} /* namespace Extension */
} /* namespace Inkscape */
/* 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:fileencoding=utf-8:textwidth=99 :