rsvg-text.c 23.5 KB
Newer Older
1 2
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 sts=4 expandtab: */
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
   rsvg-text.c: Text handling routines for RSVG

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: Raph Levien <raph@artofcode.com>
*/

#include <string.h>

#include "rsvg-private.h"
30
#include "rsvg-styles.h"
31 32 33 34 35
#include "rsvg-text.h"
#include "rsvg-css.h"

#include "rsvg-shapes.h"

36 37 38
/* what we use for text rendering depends on what cairo has to offer */
#include <pango/pangocairo.h>

Caleb Michael Moore's avatar
Caleb Michael Moore committed
39 40 41
typedef struct _RsvgNodeText RsvgNodeText;

struct _RsvgNodeText {
42 43 44 45
    RsvgLength x, y;
    gboolean x_specified;
    gboolean y_specified;
    RsvgLength dx, dy;
Caleb Michael Moore's avatar
Caleb Michael Moore committed
46 47 48 49 50
};

typedef struct _RsvgNodeTref RsvgNodeTref;

struct _RsvgNodeTref {
51
    char *link;
Caleb Michael Moore's avatar
Caleb Michael Moore committed
52
};
53

54
char *
55
rsvg_make_valid_utf8 (const char *str, int len)
56
{
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    GString *string;
    const char *remainder, *invalid;
    int remaining_bytes, valid_bytes;

    string = NULL;
    remainder = str;

    if (len < 0)
        remaining_bytes = strlen (str);
    else
        remaining_bytes = len;

    while (remaining_bytes != 0) {
        if (g_utf8_validate (remainder, remaining_bytes, &invalid))
            break;
        valid_bytes = invalid - remainder;

        if (string == NULL)
            string = g_string_sized_new (remaining_bytes);

        g_string_append_len (string, remainder, valid_bytes);
        g_string_append_c (string, '?');

        remaining_bytes -= valid_bytes + 1;
        remainder = invalid + 1;
    }

    if (string == NULL)
        return len < 0 ? g_strndup (str, len) : g_strdup (str);

    g_string_append (string, remainder);

    return g_string_free (string, FALSE);
90 91
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
92
static GString *
93
_rsvg_text_chomp (RsvgState *state, GString * in, gboolean * lastwasspace)
94 95 96 97 98
{
    GString *out;
    guint i;
    out = g_string_new (in->str);

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    if (!state->space_preserve) {
        for (i = 0; i < out->len;) {
            if (out->str[i] == '\n')
                g_string_erase (out, i, 1);
            else
                i++;
        }

        for (i = 0; i < out->len; i++)
            if (out->str[i] == '\t')
                out->str[i] = ' ';

        for (i = 0; i < out->len;) {
            if (out->str[i] == ' ' && *lastwasspace)
                g_string_erase (out, i, 1);
            else {
                if (out->str[i] == ' ')
                    *lastwasspace = TRUE;
                else
                    *lastwasspace = FALSE;
                i++;
            }
        }
    }
123 124

    return out;
125 126
}

127
static void
128
set_text_common_atts (RsvgNodeText *text, RsvgPropertyBag *atts)
129 130 131 132
{
    const char *value;

    if ((value = rsvg_property_bag_lookup (atts, "x"))) {
133
        text->x = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
134 135 136
        text->x_specified = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "y"))) {
137
        text->y = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
138 139 140
        text->y_specified = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "dx")))
141
        text->dx = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
142
    if ((value = rsvg_property_bag_lookup (atts, "dy")))
143
        text->dy = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
144 145
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
146

Caleb Michael Moore's avatar
Caleb Michael Moore committed
147
static void
148
rsvg_node_text_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
149
{
150
    RsvgNodeText *text = impl;
151

152
    set_text_common_atts (text, atts);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
153
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
154

155
static void rsvg_text_render_text (RsvgDrawingCtx * ctx, const char *text, gdouble * x, gdouble * y);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
156

157
static void
158 159 160
_rsvg_node_text_type_children (RsvgNode * self, RsvgDrawingCtx * ctx,
                               gdouble * x, gdouble * y, gboolean * lastwasspace,
                               gboolean usetextonly);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
161

Caleb Michael Moore's avatar
Caleb Michael Moore committed
162
static void
163 164
_rsvg_node_text_type_tspan (RsvgNode *node, RsvgNodeText *self, RsvgDrawingCtx *ctx,
                            gdouble *x, gdouble *y, gboolean *lastwasspace,
165
                            gboolean usetextonly);
166

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
static void
_rsvg_node_text_type_tref (RsvgNodeTref * self, RsvgDrawingCtx * ctx,
                           gdouble * x, gdouble * y, gboolean * lastwasspace,
                           gboolean usetextonly);

typedef struct {
    RsvgDrawingCtx *ctx;
    gdouble *x;
    gdouble *y;
    gboolean *lastwasspace;
    gboolean usetextonly;
} DrawTextClosure;

static gboolean
draw_text_child (RsvgNode *node, gpointer data)
{
    DrawTextClosure *closure;
184
    RsvgNodeType type = rsvg_node_get_type (node);
185 186 187 188

    closure = data;

    if (type == RSVG_NODE_TYPE_CHARS) {
189
        RsvgNodeChars *chars = rsvg_rust_cnode_get_impl (node);
190 191 192 193 194 195 196 197 198 199 200 201 202
        GString *str = _rsvg_text_chomp (rsvg_current_state (closure->ctx), chars->contents, closure->lastwasspace);
        rsvg_text_render_text (closure->ctx, str->str, closure->x, closure->y);
        g_string_free (str, TRUE);
    } else {
        if (closure->usetextonly) {
            _rsvg_node_text_type_children (node,
                                           closure->ctx,
                                           closure->x,
                                           closure->y,
                                           closure->lastwasspace,
                                           closure->usetextonly);
        } else {
            if (type == RSVG_NODE_TYPE_TSPAN) {
203
                RsvgNodeText *tspan = rsvg_rust_cnode_get_impl (node);
204
                rsvg_state_push (closure->ctx);
205 206
                _rsvg_node_text_type_tspan (node,
                                            tspan,
207 208 209 210 211 212 213
                                            closure->ctx,
                                            closure->x,
                                            closure->y,
                                            closure->lastwasspace,
                                            closure->usetextonly);
                rsvg_state_pop (closure->ctx);
            } else if (type == RSVG_NODE_TYPE_TREF) {
214
                RsvgNodeTref *tref = rsvg_rust_cnode_get_impl (node);
215 216 217 218 219 220 221 222 223 224 225 226 227
                _rsvg_node_text_type_tref (tref,
                                           closure->ctx,
                                           closure->x,
                                           closure->y,
                                           closure->lastwasspace,
                                           closure->usetextonly);
            }
        }
    }

    return TRUE;
}

228
/* This function is responsible of selecting render for a text element including its children and giving it the drawing context */
229
static void
230
_rsvg_node_text_type_children (RsvgNode * self, RsvgDrawingCtx * ctx,
231 232
                               gdouble * x, gdouble * y, gboolean * lastwasspace,
                               gboolean usetextonly)
233
{
234
    DrawTextClosure closure;
235 236

    rsvg_push_discrete_layer (ctx);
237 238 239 240 241 242 243 244 245

    closure.ctx = ctx;
    closure.x = x;
    closure.y = y;
    closure.lastwasspace = lastwasspace;
    closure.usetextonly = usetextonly;

    rsvg_node_foreach_child (self, draw_text_child, &closure);

246
    rsvg_pop_discrete_layer (ctx);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
247 248
}

249 250 251 252 253 254 255 256 257 258 259
static gboolean
_rsvg_node_text_length_children (RsvgNode * self, RsvgDrawingCtx * ctx,
                                 gdouble * length, gboolean * lastwasspace,
                                 gboolean usetextonly);

static gboolean
_rsvg_node_text_length_tref (RsvgNodeTref * self, RsvgDrawingCtx * ctx,
                             gdouble * x, gboolean * lastwasspace,
                             gboolean usetextonly);

static gboolean
260 261 262 263 264
_rsvg_node_text_length_tspan (RsvgNode *node,
                              RsvgNodeText *self,
                              RsvgDrawingCtx *ctx,
                              gdouble *x,
                              gboolean *lastwasspace,
265
                              gboolean usetextonly);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
266

267
static gdouble rsvg_text_length_text_as_string (RsvgDrawingCtx * ctx, const char *text);
268

269 270 271 272 273 274 275 276 277 278
typedef struct {
    RsvgDrawingCtx *ctx;
    gdouble *length;
    gboolean *lastwasspace;
    gboolean usetextonly;
    gboolean done;
} ChildrenLengthClosure;

static gboolean
compute_child_length (RsvgNode *node, gpointer data)
279
{
280
    ChildrenLengthClosure *closure;
281
    RsvgNodeType type = rsvg_node_get_type (node);
282 283 284 285 286 287 288 289 290
    gboolean done;

    closure = data;
    done = FALSE;

    rsvg_state_push (closure->ctx);
    rsvg_state_reinherit_top (closure->ctx, rsvg_node_get_state (node), 0);

    if (type == RSVG_NODE_TYPE_CHARS) {
291
        RsvgNodeChars *chars = rsvg_rust_cnode_get_impl (node);
292 293 294 295 296 297 298 299 300 301
        GString *str = _rsvg_text_chomp (rsvg_current_state (closure->ctx), chars->contents, closure->lastwasspace);
        *closure->length += rsvg_text_length_text_as_string (closure->ctx, str->str);
        g_string_free (str, TRUE);
    } else {
        if (closure->usetextonly) {
            done = _rsvg_node_text_length_children (node,
                                                    closure->ctx,
                                                    closure->length,
                                                    closure->lastwasspace,
                                                    closure->usetextonly);
302
        } else {
303
            if (type == RSVG_NODE_TYPE_TSPAN) {
304 305 306
                RsvgNodeText *tspan = rsvg_rust_cnode_get_impl (node);
                done = _rsvg_node_text_length_tspan (node,
                                                     tspan,
307 308 309 310 311
                                                     closure->ctx,
                                                     closure->length,
                                                     closure->lastwasspace,
                                                     closure->usetextonly);
            } else if (type == RSVG_NODE_TYPE_TREF) {
312
                RsvgNodeTref *tref = rsvg_rust_cnode_get_impl (node);
313 314 315 316 317
                done = _rsvg_node_text_length_tref (tref,
                                                    closure->ctx,
                                                    closure->length,
                                                    closure->lastwasspace,
                                                    closure->usetextonly);
318
            }
319 320
        }
    }
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

    rsvg_state_pop (closure->ctx);

    closure->done = done;
    return !done;
}

static gboolean
_rsvg_node_text_length_children (RsvgNode * self, RsvgDrawingCtx * ctx,
                                 gdouble * length, gboolean * lastwasspace,
                                 gboolean usetextonly)
{
    ChildrenLengthClosure closure;

    closure.ctx = ctx;
    closure.length = length;
    closure.lastwasspace = lastwasspace;
    closure.usetextonly = usetextonly;
    closure.done = FALSE;

    rsvg_node_foreach_child (self, compute_child_length, &closure);

    return closure.done;
344 345
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
346 347

static void
348
rsvg_node_text_draw (RsvgNode *node, gpointer impl, RsvgDrawingCtx *ctx, int dominate)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
349
{
350
    RsvgNodeText *text = impl;
351
    double x, y, dx, dy, length = 0;
352
    gboolean lastwasspace = TRUE;
353 354

    rsvg_state_reinherit_top (ctx, rsvg_node_get_state (node), dominate);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
355

356 357 358 359
    x = rsvg_length_normalize (&text->x, ctx);
    y = rsvg_length_normalize (&text->y, ctx);
    dx = rsvg_length_normalize (&text->dx, ctx);
    dy = rsvg_length_normalize (&text->dy, ctx);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
360

361
    if (rsvg_current_state (ctx)->text_anchor != TEXT_ANCHOR_START) {
362
        _rsvg_node_text_length_children (node, ctx, &length, &lastwasspace, FALSE);
363 364 365 366 367 368 369
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
            length /= 2;
    }
    if (PANGO_GRAVITY_IS_VERTICAL (rsvg_current_state (ctx)->text_gravity)) {
        y -= length;
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
            dy /= 2;
370
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_END)
371 372 373
            dy = 0;
    } else {
        x -= length;
374
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
375 376 377
            dx /= 2;
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_END)
            dx = 0;
378
    }
379 380
    x += dx;
    y += dy;
Caleb Michael Moore's avatar
Caleb Michael Moore committed
381

382
    lastwasspace = TRUE;
383
    _rsvg_node_text_type_children (node, ctx, &x, &y, &lastwasspace, FALSE);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
384 385 386
}

RsvgNode *
387
rsvg_new_text (const char *element_name, RsvgNode *parent)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
388
{
389
    RsvgNodeText *text;
390

391
    text = g_new0 (RsvgNodeText, 1);
392
    text->x = text->y = text->dx = text->dy = rsvg_length_parse ("0", LENGTH_DIR_BOTH);
393 394 395 396 397 398 399 400

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_TEXT,
                                parent,
                                rsvg_state_new (),
                                text,
                                rsvg_node_text_set_atts,
                                rsvg_node_text_draw,
                                g_free);                                
Caleb Michael Moore's avatar
Caleb Michael Moore committed
401
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
402

Caleb Michael Moore's avatar
Caleb Michael Moore committed
403
static void
404 405
_rsvg_node_text_type_tspan (RsvgNode *node, RsvgNodeText *self, RsvgDrawingCtx *ctx,
                            gdouble *x, gdouble *y, gboolean *lastwasspace,
406
                            gboolean usetextonly)
407
{
408
    double dx, dy, length = 0;
409 410

    rsvg_state_reinherit_top (ctx, rsvg_node_get_state (node), 0);
411

412 413
    dx = rsvg_length_normalize (&self->dx, ctx);
    dy = rsvg_length_normalize (&self->dy, ctx);
414 415 416

    if (rsvg_current_state (ctx)->text_anchor != TEXT_ANCHOR_START) {
        gboolean lws = *lastwasspace;
417
        _rsvg_node_text_length_children (node, ctx, &length, &lws, usetextonly);
418 419 420 421
        if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
            length /= 2;
    }

422
    if (self->x_specified) {
423
        *x = rsvg_length_normalize (&self->x, ctx);
424 425
        if (!PANGO_GRAVITY_IS_VERTICAL (rsvg_current_state (ctx)->text_gravity)) {
            *x -= length;
426
            if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
427 428 429
                dx /= 2;
            if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_END)
                dx = 0;
430 431
        }
    }
432 433
    *x += dx;

434
    if (self->y_specified) {
435
        *y = rsvg_length_normalize (&self->y, ctx);
436 437 438 439 440 441 442 443 444
        if (PANGO_GRAVITY_IS_VERTICAL (rsvg_current_state (ctx)->text_gravity)) {
            *y -= length;
            if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_MIDDLE)
                dy /= 2;
            if (rsvg_current_state (ctx)->text_anchor == TEXT_ANCHOR_END)
                dy = 0;
        }
    }
    *y += dy;
445
    _rsvg_node_text_type_children (node, ctx, x, y, lastwasspace, usetextonly);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
446 447
}

448
static gboolean
449 450 451 452 453 454
_rsvg_node_text_length_tspan (RsvgNode *node,
                              RsvgNodeText *self,
                              RsvgDrawingCtx *ctx,
                              gdouble *length,
                              gboolean *lastwasspace,
                              gboolean usetextonly)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
455
{
456
    if (self->x_specified || self->y_specified)
457
        return TRUE;
458 459

    if (PANGO_GRAVITY_IS_VERTICAL (rsvg_current_state (ctx)->text_gravity))
460
        *length += rsvg_length_normalize (&self->dy, ctx);
461
    else
462
        *length += rsvg_length_normalize (&self->dx, ctx);
463

464 465
    return _rsvg_node_text_length_children (node, ctx, length,
                                            lastwasspace, usetextonly);
466 467
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
468
static void
469
rsvg_node_tspan_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
470
{
471
    RsvgNodeText *text = impl;
472

473
    set_text_common_atts (text, atts);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
474
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
475

476 477 478 479 480 481
static void
rsvg_node_tspan_draw (RsvgNode *node, gpointer impl, RsvgDrawingCtx *ctx, int dominate)
{
    /* nothing */
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
482
RsvgNode *
483
rsvg_new_tspan (const char *element_name, RsvgNode *parent)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
484
{
485
    RsvgNodeText *text;
486

487
    text = g_new0 (RsvgNodeText, 1);
488
    text->dx = text->dy = rsvg_length_parse ("0", LENGTH_DIR_BOTH);
489 490 491 492 493 494 495 496

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_TSPAN,
                                parent,
                                rsvg_state_new (),
                                text,
                                rsvg_node_tspan_set_atts,
                                rsvg_node_tspan_draw,
                                g_free);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
497
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
498

Caleb Michael Moore's avatar
Caleb Michael Moore committed
499
static void
500
_rsvg_node_text_type_tref (RsvgNodeTref * self, RsvgDrawingCtx * ctx,
501 502
                           gdouble * x, gdouble * y, gboolean * lastwasspace,
                           gboolean usetextonly)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
503
{
504 505 506 507
    RsvgNode *link;

    if (self->link == NULL)
      return;
508
    link = rsvg_drawing_ctx_acquire_node (ctx, self->link);
509 510 511
    if (link == NULL)
      return;

512
    _rsvg_node_text_type_children (link, ctx, x, y, lastwasspace, TRUE);
513

514
    rsvg_drawing_ctx_release_node (ctx, link);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
515
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
516

517
static gboolean
518
_rsvg_node_text_length_tref (RsvgNodeTref * self, RsvgDrawingCtx * ctx, gdouble * x,
519
                             gboolean * lastwasspace, gboolean usetextonly)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
520
{
521 522 523 524 525
    gboolean result;
    RsvgNode *link;

    if (self->link == NULL)
      return FALSE;
526
    link = rsvg_drawing_ctx_acquire_node (ctx, self->link);
527 528 529 530 531
    if (link == NULL)
      return FALSE;

    result = _rsvg_node_text_length_children (link, ctx, x, lastwasspace, TRUE);

532
    rsvg_drawing_ctx_release_node (ctx, link);
533 534 535 536 537

    return result;
}

static void
538
rsvg_node_tref_free (gpointer impl)
539
{
540 541
    RsvgNodeTref *self = impl;

542
    g_free (self->link);
543
    g_free (self);
Caleb Michael Moore's avatar
Caleb Michael Moore committed
544
}
Caleb Michael Moore's avatar
Caleb Michael Moore committed
545

Caleb Michael Moore's avatar
Caleb Michael Moore committed
546
static void
547
rsvg_node_tref_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
548
{
549
    RsvgNodeTref *text = impl;
550
    const char *value;
Caleb Michael Moore's avatar
Caleb Michael Moore committed
551

552 553 554
    if ((value = rsvg_property_bag_lookup (atts, "xlink:href"))) {
        g_free (text->link);
        text->link = g_strdup (value);
555
    }
Caleb Michael Moore's avatar
Caleb Michael Moore committed
556 557
}

558 559 560 561 562 563
static void
rsvg_node_tref_draw (RsvgNode *node, gpointer impl, RsvgDrawingCtx *ctx, int dominate)
{
    /* nothing */
}

Caleb Michael Moore's avatar
Caleb Michael Moore committed
564
RsvgNode *
565
rsvg_new_tref (const char *element_name, RsvgNode *parent)
Caleb Michael Moore's avatar
Caleb Michael Moore committed
566
{
567
    RsvgNodeTref *text;
568

569
    text = g_new0 (RsvgNodeTref, 1);
570
    text->link = NULL;
571 572 573 574 575 576 577 578

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_TREF,
                                parent,
                                rsvg_state_new (),
                                text,
                                rsvg_node_tref_set_atts,
                                rsvg_node_tref_draw,
                                rsvg_node_tref_free);
579
}
580 581 582

typedef struct _RsvgTextLayout RsvgTextLayout;

583 584 585 586 587
struct _RsvgTextLayout {
    PangoLayout *layout;
    RsvgDrawingCtx *ctx;
    TextAnchor anchor;
    gdouble x, y;
588 589 590
};

static void
591
rsvg_text_layout_free (RsvgTextLayout * layout)
592
{
593
    g_object_unref (layout->layout);
594
    g_free (layout);
595 596
}

597
static PangoLayout *
598
rsvg_text_create_layout (RsvgDrawingCtx * ctx, const char *text, PangoContext * context)
599
{
600
    RsvgState *state;
601 602
    PangoFontDescription *font_desc;
    PangoLayout *layout;
603 604
    PangoAttrList *attr_list;
    PangoAttribute *attribute;
605
    double dpi_y;
606

607 608
    state = rsvg_current_state (ctx);

609 610 611 612 613 614
    if (state->lang)
        pango_context_set_language (context, pango_language_from_string (state->lang));

    if (state->unicode_bidi == UNICODE_BIDI_OVERRIDE || state->unicode_bidi == UNICODE_BIDI_EMBED)
        pango_context_set_base_dir (context, state->text_dir);

615 616 617
    if (PANGO_GRAVITY_IS_VERTICAL (state->text_gravity))
        pango_context_set_base_gravity (context, state->text_gravity);

618 619 620 621 622 623 624 625 626
    font_desc = pango_font_description_copy (pango_context_get_font_description (context));

    if (state->font_family)
        pango_font_description_set_family_static (font_desc, state->font_family);

    pango_font_description_set_style (font_desc, state->font_style);
    pango_font_description_set_variant (font_desc, state->font_variant);
    pango_font_description_set_weight (font_desc, state->font_weight);
    pango_font_description_set_stretch (font_desc, state->font_stretch);
627 628

    rsvg_drawing_ctx_get_dpi (ctx, NULL, &dpi_y);
629
    pango_font_description_set_size (font_desc,
630
                                     rsvg_drawing_ctx_get_normalized_font_size (ctx) * PANGO_SCALE / dpi_y * 72);
631 632 633 634 635

    layout = pango_layout_new (context);
    pango_layout_set_font_description (layout, font_desc);
    pango_font_description_free (font_desc);

636
    attr_list = pango_attr_list_new ();
637
    attribute = pango_attr_letter_spacing_new (rsvg_length_normalize (&state->letter_spacing, ctx) * PANGO_SCALE);
638 639
    attribute->start_index = 0;
    attribute->end_index = G_MAXINT;
640
    pango_attr_list_insert (attr_list, attribute);
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656

    if (state->has_font_decor && text) {
        if (state->font_decor & TEXT_UNDERLINE) {
            attribute = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
            attribute->start_index = 0;
            attribute->end_index = -1;
            pango_attr_list_insert (attr_list, attribute);
        }
	if (state->font_decor & TEXT_STRIKE) {
            attribute = pango_attr_strikethrough_new (TRUE);
            attribute->start_index = 0;
            attribute->end_index = -1;
            pango_attr_list_insert (attr_list, attribute);
	}
    }

657 658 659
    pango_layout_set_attributes (layout, attr_list);
    pango_attr_list_unref (attr_list);

660 661 662 663 664
    if (text)
        pango_layout_set_text (layout, text, -1);
    else
        pango_layout_set_text (layout, NULL, 0);

665
    pango_layout_set_alignment (layout, (state->text_dir == PANGO_DIRECTION_LTR) ?
666 667 668
                                PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT);

    return layout;
669 670 671 672
}


static RsvgTextLayout *
673
rsvg_text_layout_new (RsvgDrawingCtx * ctx, const char *text)
674
{
675
    RsvgState *state;
676 677
    RsvgTextLayout *layout;

678 679
    state = rsvg_current_state (ctx);

680 681
    if (ctx->pango_context == NULL)
        ctx->pango_context = ctx->render->create_pango_context (ctx);
682 683 684

    layout = g_new0 (RsvgTextLayout, 1);

685
    layout->layout = rsvg_text_create_layout (ctx, text, ctx->pango_context);
686
    layout->ctx = ctx;
687

688
    layout->anchor = state->text_anchor;
689

690
    return layout;
691 692
}

693 694
void
rsvg_text_render_text (RsvgDrawingCtx * ctx, const char *text, gdouble * x, gdouble * y)
695
{
696 697
    PangoContext *context;
    PangoLayout *layout;
698 699
    PangoLayoutIter *iter;
    RsvgState *state;
700 701
    gint w, h;
    double offset_x, offset_y, offset;
702

703
    state = rsvg_current_state (ctx);
704

705 706 707
    /* Do not render the text if the font size is zero. See bug #581491. */
    if (state->font_size.length == 0)
        return;
708

709
    context = ctx->render->create_pango_context (ctx);
710
    layout = rsvg_text_create_layout (ctx, text, context);
711 712
    pango_layout_get_size (layout, &w, &h);
    iter = pango_layout_get_iter (layout);
713 714
    offset = pango_layout_iter_get_baseline (iter) / (double) PANGO_SCALE;
    offset += _rsvg_css_accumulate_baseline_shift (state, ctx);
715
    if (PANGO_GRAVITY_IS_VERTICAL (state->text_gravity)) {
716 717
        offset_x = -offset;
        offset_y = 0;
718
    } else {
719 720
        offset_x = 0;
        offset_y = offset;
721
    }
722
    pango_layout_iter_free (iter);
723
    ctx->render->render_pango_layout (ctx, layout, *x - offset_x, *y - offset_y);
724 725 726 727 728
    if (PANGO_GRAVITY_IS_VERTICAL (state->text_gravity))
        *y += w / (double)PANGO_SCALE;
    else
        *x += w / (double)PANGO_SCALE;

729 730
    g_object_unref (layout);
    g_object_unref (context);
731 732
}

733
static gdouble
734
rsvg_text_layout_width (RsvgTextLayout * layout)
735
{
736
    gint width;
737

738
    pango_layout_get_size (layout->layout, &width, NULL);
739

740
    return width / (double)PANGO_SCALE;
741 742
}

743 744
static gdouble
rsvg_text_length_text_as_string (RsvgDrawingCtx * ctx, const char *text)
745
{
746 747
    RsvgTextLayout *layout;
    gdouble x;
748

749
    layout = rsvg_text_layout_new (ctx, text);
750
    layout->x = layout->y = 0;
751

752
    x = rsvg_text_layout_width (layout);
753

754 755
    rsvg_text_layout_free (layout);
    return x;
756
}