nr-arena-glyphs.cpp revision 1f67c2bae7e121b1ed8cc11a201d6fabdbdd467f
#define __NR_ARENA_GLYPHS_C__
/*
* RGBA display list system for inkscape
*
* Author:
* Lauris Kaplinski <lauris@kaplinski.com>
*
* Copyright (C) 2002 Lauris Kaplinski
*
* Released under GNU GPL
*
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "libnr/nr-blit.h"
#include "libnr/nr-convert2geom.h"
#include <2geom/matrix.h>
#include "style.h"
#include "display/nr-arena.h"
#include "display/nr-arena-glyphs.h"
#include <cairo.h>
#include "display/cairo-utils.h"
#include "helper/geom.h"
#ifdef test_glyph_liv
#include "../display/canvas-bpath.h"
#include "libnrtype/font-instance.h"
// defined in nr-arena-shape.cpp
void nr_pixblock_render_shape_mask_or(NRPixBlock &m, Shape *theS);
#endif
#ifdef ENABLE_SVG_FONTS
#include "nr-svgfonts.h"
#endif //#ifdef ENABLE_SVG_FONTS
static void nr_arena_glyphs_class_init(NRArenaGlyphsClass *klass);
static void nr_arena_glyphs_init(NRArenaGlyphs *glyphs);
static void nr_arena_glyphs_finalize(NRObject *object);
static guint nr_arena_glyphs_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
static guint nr_arena_glyphs_clip(cairo_t *ct, NRArenaItem *item, NRRectL *area);
static NRArenaItem *nr_arena_glyphs_pick(NRArenaItem *item, Geom::Point p, double delta, unsigned int sticky);
static NRArenaItemClass *glyphs_parent_class;
NRType
nr_arena_glyphs_get_type(void)
{
static NRType type = 0;
if (!type) {
type = nr_object_register_type(NR_TYPE_ARENA_ITEM,
"NRArenaGlyphs",
sizeof(NRArenaGlyphsClass),
sizeof(NRArenaGlyphs),
(void (*)(NRObjectClass *)) nr_arena_glyphs_class_init,
(void (*)(NRObject *)) nr_arena_glyphs_init);
}
return type;
}
static void
nr_arena_glyphs_class_init(NRArenaGlyphsClass *klass)
{
NRObjectClass *object_class;
NRArenaItemClass *item_class;
object_class = (NRObjectClass *) klass;
item_class = (NRArenaItemClass *) klass;
glyphs_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
object_class->finalize = nr_arena_glyphs_finalize;
object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaGlyphs>;
item_class->update = nr_arena_glyphs_update;
item_class->clip = nr_arena_glyphs_clip;
item_class->pick = nr_arena_glyphs_pick;
}
static void
nr_arena_glyphs_init(NRArenaGlyphs *glyphs)
{
glyphs->g_transform.setIdentity();
glyphs->font = NULL;
glyphs->glyph = 0;
glyphs->x = glyphs->y = 0.0;
}
static void
nr_arena_glyphs_finalize(NRObject *object)
{
NRArenaGlyphs *glyphs = static_cast<NRArenaGlyphs *>(object);
if (glyphs->font) {
glyphs->font->Unref();
glyphs->font=NULL;
}
((NRObjectClass *) glyphs_parent_class)->finalize(object);
}
static guint
nr_arena_glyphs_update(NRArenaItem *item, NRRectL */*area*/, NRGC *gc, guint /*state*/, guint /*reset*/)
{
NRArenaGlyphs *glyphs = NR_ARENA_GLYPHS(item);
NRArenaGlyphsGroup *ggroup = NR_ARENA_GLYPHS_GROUP(item->parent);
if (!glyphs->font || !ggroup->style)
return NR_ARENA_ITEM_STATE_ALL;
if (ggroup->nrstyle.fill.type == NRStyle::PAINT_NONE && ggroup->nrstyle.stroke.type == NRStyle::PAINT_NONE)
return NR_ARENA_ITEM_STATE_ALL;
Geom::OptRect b;
Geom::Matrix t = glyphs->g_transform * gc->transform;
glyphs->x = t[4];
glyphs->y = t[5];
b = bounds_exact_transformed(*glyphs->font->PathVector(glyphs->glyph), t);
if (b && ggroup->nrstyle.stroke.type != NRStyle::PAINT_NONE) {
float width, scale;
scale = gc->transform.descrim();
width = MAX(0.125, ggroup->nrstyle.stroke_width * scale);
if ( fabs(ggroup->nrstyle.stroke_width * scale) > 0.01 ) { // FIXME: this is always true
b->expandBy(width);
}
// those pesky miters, now
float miterMax = width * ggroup->nrstyle.miter_limit;
if ( miterMax > 0.01 ) {
// grunt mode. we should compute the various miters instead
// (one for each point on the curve)
b->expandBy(miterMax);
}
}
if (b) {
item->bbox.x0 = static_cast<NR::ICoord>(floor(b->left()));
item->bbox.y0 = static_cast<NR::ICoord>(floor(b->top()));
item->bbox.x1 = static_cast<NR::ICoord>(ceil (b->right()));
item->bbox.y1 = static_cast<NR::ICoord>(ceil (b->bottom()));
} else {
item->bbox.x0 = 0;
item->bbox.y0 = 0;
item->bbox.x1 = -1;
item->bbox.y1 = -1;
}
return NR_ARENA_ITEM_STATE_ALL;
}
static guint
nr_arena_glyphs_clip(cairo_t *ct, NRArenaItem *item, NRRectL */*area*/)
{
NRArenaGlyphs *glyphs;
glyphs = NR_ARENA_GLYPHS(item);
if (!glyphs->font) return item->state;
/* TODO : render to greyscale pixblock provided for clipping */
return item->state;
}
static NRArenaItem *
nr_arena_glyphs_pick(NRArenaItem *item, Geom::Point p, gdouble delta, unsigned int /*sticky*/)
{
NRArenaGlyphs *glyphs;
glyphs = NR_ARENA_GLYPHS(item);
if (!glyphs->font ) return NULL;
double const x = p[Geom::X];
double const y = p[Geom::Y];
/* With text we take a simple approach: pick if the point is in a characher bbox */
if ((x + delta >= item->bbox.x0) && (y + delta >= item->bbox.y0) && (x - delta <= item->bbox.x1) && (y - delta <= item->bbox.y1)) return item;
return NULL;
}
void
nr_arena_glyphs_set_path(NRArenaGlyphs *glyphs, SPCurve */*curve*/, unsigned int /*lieutenant*/, font_instance *font, gint glyph, Geom::Matrix const *transform)
{
nr_return_if_fail(glyphs != NULL);
nr_return_if_fail(NR_IS_ARENA_GLYPHS(glyphs));
nr_arena_item_request_render(NR_ARENA_ITEM(glyphs));
if (transform) {
glyphs->g_transform = *transform;
} else {
glyphs->g_transform.setIdentity();
}
if (font) font->Ref();
if (glyphs->font) glyphs->font->Unref();
glyphs->font=font;
glyphs->glyph = glyph;
nr_arena_item_request_update(NR_ARENA_ITEM(glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE);
}
static void nr_arena_glyphs_group_class_init(NRArenaGlyphsGroupClass *klass);
static void nr_arena_glyphs_group_init(NRArenaGlyphsGroup *group);
static void nr_arena_glyphs_group_finalize(NRObject *object);
static guint nr_arena_glyphs_group_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
static unsigned int nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
static unsigned int nr_arena_glyphs_group_clip(cairo_t *ct, NRArenaItem *item, NRRectL *area);
static NRArenaItem *nr_arena_glyphs_group_pick(NRArenaItem *item, Geom::Point p, gdouble delta, unsigned int sticky);
static NRArenaGroupClass *group_parent_class;
NRType
nr_arena_glyphs_group_get_type(void)
{
static NRType type = 0;
if (!type) {
type = nr_object_register_type(NR_TYPE_ARENA_GROUP,
"NRArenaGlyphsGroup",
sizeof(NRArenaGlyphsGroupClass),
sizeof(NRArenaGlyphsGroup),
(void (*)(NRObjectClass *)) nr_arena_glyphs_group_class_init,
(void (*)(NRObject *)) nr_arena_glyphs_group_init);
}
return type;
}
static void
nr_arena_glyphs_group_class_init(NRArenaGlyphsGroupClass *klass)
{
NRObjectClass *object_class;
NRArenaItemClass *item_class;
object_class = (NRObjectClass *) klass;
item_class = (NRArenaItemClass *) klass;
group_parent_class = (NRArenaGroupClass *) ((NRObjectClass *) klass)->parent;
object_class->finalize = nr_arena_glyphs_group_finalize;
object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaGlyphsGroup>;
item_class->update = nr_arena_glyphs_group_update;
item_class->render = nr_arena_glyphs_group_render;
item_class->clip = nr_arena_glyphs_group_clip;
item_class->pick = nr_arena_glyphs_group_pick;
}
static void
nr_arena_glyphs_group_init(NRArenaGlyphsGroup *group)
{
group->style = NULL;
group->paintbox.x0 = group->paintbox.y0 = 0.0F;
group->paintbox.x1 = group->paintbox.y1 = -1.0F;
}
static void
nr_arena_glyphs_group_finalize(NRObject *object)
{
NRArenaGlyphsGroup *group=static_cast<NRArenaGlyphsGroup *>(object);
if (group->style) {
sp_style_unref(group->style);
group->style = NULL;
}
((NRObjectClass *) group_parent_class)->finalize(object);
}
static guint
nr_arena_glyphs_group_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset)
{
NRArenaGlyphsGroup *group = NR_ARENA_GLYPHS_GROUP(item);
group->nrstyle.update();
if (((NRArenaItemClass *) group_parent_class)->update)
return ((NRArenaItemClass *) group_parent_class)->update(item, area, gc, state, reset);
return NR_ARENA_ITEM_STATE_ALL;
}
static unsigned int
nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int /*flags*/)
{
NRArenaItem *child;
NRArenaGroup *group = NR_ARENA_GROUP(item);
NRArenaGlyphsGroup *ggroup = NR_ARENA_GLYPHS_GROUP(item);
if (!ct) return item->state;
if (item->arena->rendermode == Inkscape::RENDERMODE_OUTLINE) {
guint32 rgba = item->arena->outlinecolor;
// FIXME: we use RGBA buffers but cairo writes BGRA (on i386), so we must cheat
// by setting color channels in the "wrong" order
cairo_set_source_rgba(ct, SP_RGBA32_B_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_R_F(rgba), SP_RGBA32_A_F(rgba));
cairo_set_tolerance(ct, 1.25); // low quality, but good enough for outline mode
NRRect temp(area->x0, area->y0, area->x1, area->y1);
Geom::OptRect area_2geom = temp.upgrade_2geom();
for (child = group->children; child != NULL; child = child->next) {
NRArenaGlyphs *g = NR_ARENA_GLYPHS(child);
Geom::PathVector const * pathv = g->font->PathVector(g->glyph);
cairo_new_path(ct);
Geom::Matrix transform = g->g_transform * group->ctm;
feed_pathvector_to_cairo (ct, *pathv, transform, area_2geom, false, 0);
cairo_fill(ct);
}
return item->state;
}
// NOTE: this is very similar to nr-arena-shape.cpp; the only difference is path feeding
bool needs_opacity = ((1.0 - ggroup->nrstyle.opacity) >= 0.01);
bool has_stroke, has_fill;
cairo_save(ct);
cairo_translate(ct, -area->x0, -area->y0);
ink_cairo_transform(ct, ggroup->ctm);
has_fill = ggroup->nrstyle.prepareFill(ct, &ggroup->paintbox);
has_stroke = ggroup->nrstyle.prepareStroke(ct, &ggroup->paintbox);
if (has_fill || has_stroke) {
if (needs_opacity) {
cairo_push_group(ct);
}
for (NRArenaItem *child = ggroup->children; child != NULL; child = child->next) {
NRArenaGlyphs *g = NR_ARENA_GLYPHS(child);
Geom::PathVector const &pathv = *g->font->PathVector(g->glyph);
cairo_save(ct);
ink_cairo_transform(ct, g->g_transform);
feed_pathvector_to_cairo(ct, pathv);
cairo_restore(ct);
}
if (has_fill) {
ggroup->nrstyle.applyFill(ct);
cairo_fill_preserve(ct);
}
if (has_stroke) {
ggroup->nrstyle.applyStroke(ct);
cairo_stroke_preserve(ct);
}
cairo_new_path(ct); // clear path
if (needs_opacity) {
cairo_pop_group_to_source(ct);
cairo_paint_with_alpha(ct, ggroup->nrstyle.opacity);
}
} // has fill or stroke pattern
cairo_restore(ct);
return item->state;
}
static unsigned int
nr_arena_glyphs_group_clip(cairo_t *ct, NRArenaItem *item, NRRectL *area)
{
//NRArenaGroup *group = NR_ARENA_GROUP(item);
guint ret = item->state;
/* Render children fill mask */
/*
for (NRArenaItem *child = group->children; child != NULL; child = child->next) {
ret = nr_arena_glyphs_fill_mask(NR_ARENA_GLYPHS(child), area, pb);
if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) return ret;
}*/
return ret;
}
static NRArenaItem *
nr_arena_glyphs_group_pick(NRArenaItem *item, Geom::Point p, gdouble delta, unsigned int sticky)
{
NRArenaItem *picked = NULL;
if (((NRArenaItemClass *) group_parent_class)->pick)
picked = ((NRArenaItemClass *) group_parent_class)->pick(item, p, delta, sticky);
if (picked) picked = item;
return picked;
}
void
nr_arena_glyphs_group_clear(NRArenaGlyphsGroup *sg)
{
NRArenaGroup *group = NR_ARENA_GROUP(sg);
nr_arena_item_request_render(NR_ARENA_ITEM(group));
while (group->children) {
nr_arena_item_remove_child(NR_ARENA_ITEM(group), group->children);
}
}
void
nr_arena_glyphs_group_add_component(NRArenaGlyphsGroup *sg, font_instance *font, int glyph, Geom::Matrix const &transform)
{
NRArenaGroup *group;
group = NR_ARENA_GROUP(sg);
Geom::PathVector const * pathv = ( font
? font->PathVector(glyph)
: NULL );
if ( pathv ) {
nr_arena_item_request_render(NR_ARENA_ITEM(group));
NRArenaItem *new_arena = NRArenaGlyphs::create(group->arena);
nr_arena_item_append_child(NR_ARENA_ITEM(group), new_arena);
nr_arena_item_unref(new_arena);
nr_arena_glyphs_set_path(NR_ARENA_GLYPHS(new_arena), NULL, FALSE, font, glyph, &transform);
}
}
void
nr_arena_glyphs_group_set_style(NRArenaGlyphsGroup *sg, SPStyle *style)
{
nr_return_if_fail(sg != NULL);
nr_return_if_fail(NR_IS_ARENA_GLYPHS_GROUP(sg));
if (style) sp_style_ref(style);
if (sg->style) sp_style_unref(sg->style);
sg->style = style;
sg->nrstyle.set(style);
nr_arena_item_request_update(NR_ARENA_ITEM(sg), NR_ARENA_ITEM_STATE_ALL, FALSE);
}
void
nr_arena_glyphs_group_set_paintbox(NRArenaGlyphsGroup *gg, NRRect const *pbox)
{
nr_return_if_fail(gg != NULL);
nr_return_if_fail(NR_IS_ARENA_GLYPHS_GROUP(gg));
nr_return_if_fail(pbox != NULL);
if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) {
gg->paintbox.x0 = pbox->x0;
gg->paintbox.y0 = pbox->y0;
gg->paintbox.x1 = pbox->x1;
gg->paintbox.y1 = pbox->y1;
} else {
gg->paintbox.x0 = gg->paintbox.y0 = 0.0F;
gg->paintbox.x1 = gg->paintbox.y1 = -1.0F;
}
nr_arena_item_request_update(NR_ARENA_ITEM(gg), NR_ARENA_ITEM_STATE_ALL, FALSE);
}
/*
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 :