[cairo] PATCH #3 - text extents

John Ellson ellson at research.att.com
Mon Dec 8 19:44:53 PST 2003


John Ellson wrote:

> Soorya Kuloor wrote:
>
>> Hi John,
>>
>> I tried the patch that you provided for text extents and am having some
>> problems calculating the text bounding box from the extents. As long as
>> the text is horizontal the bounding box seems to fit the text exactly.
>> However, if the text is rotated the bounding box does not look right.
>> The attached test program reproduces the problem.
>>
>> Am I doing something stupid in my bounding box calculation? Is it wrong
>> to calculate bounding box from extents?
>>
>> Thanks for any help.
>>  
>>
>
> Soorya,
>
> Thanks.  I have compiled your test case and I can see the problem you
> describe.  I don't have a quick fix.  I'll work on it this weekend.
>
> John
>
OK, here is a third attempt at implementing text extents. These patches 
are against
CVS as of Mon Dec 08, 22.44 EST.

This one works as well as the last patch for straight text at different 
view scales,
and better for the rotated bounding rectangle test case provided by Soorya.

Its still not perfect.  The right side of the rectangle seems to show 
some variation
at different scales.    I've attached text-bounds2.c to illustrate.  
I'm wondering if the error is from the difference between the bitmap 
sizes and the
calculated extents  Anyone have any ideas?

John
-------------- next part --------------
? depcomp
? install-sh
? missing
? mkinstalldirs
Index: src/cairo.c
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo.c,v
retrieving revision 1.29
diff -r1.29 cairo.c
710d709
< /* XXX: NYI
734d732
< */
Index: src/cairo.h
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo.h,v
retrieving revision 1.32
diff -r1.32 cairo.h
399,401d398
< 
< /* XXX: NYI
< 
412a410,411
> /* XXX: NYI
> 
Index: src/cairo_ft_font.c
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo_ft_font.c,v
retrieving revision 1.7
diff -r1.7 cairo_ft_font.c
43,48c43,46
< #define DOUBLE_TO_26_6(d) ((FT_F26Dot6)((d) * 63.0))
< #define DOUBLE_FROM_26_6(t) (((double)((t) >> 6)) \
< 			     + ((double)((t) & 0x3F) / 63.0))
< #define DOUBLE_TO_16_16(d) ((FT_Fixed)((d) * 65535.0))
< #define DOUBLE_FROM_16_16(t) (((double)((t) >> 16)) \
< 			      + ((double)((t) & 0xFFFF) / 65535.0))
---
> #define DOUBLE_TO_26_6(d) ((FT_F26Dot6)((d) * 64.0))
> #define DOUBLE_FROM_26_6(t) ((double)(t) / 64.0)
> #define DOUBLE_TO_16_16(d) ((FT_Fixed)((d) * 65536.0))
> #define DOUBLE_FROM_16_16(t) ((double)(t) / 65536.0)
324c322
<     cairo_ft_font_t *ft;
---
>     FT_Face face;
332c330
<     ft = (cairo_ft_font_t *)font;
---
>     face = ((cairo_ft_font_t *)font)->face;
346c344
<     _install_font_matrix (&font->matrix, ft->face);
---
>     _install_font_matrix (&font->matrix, face);
350c348
<         (*glyphs)[i].index = FT_Get_Char_Index (ft->face, ucs4[i]);
---
>         (*glyphs)[i].index = FT_Get_Char_Index (face, ucs4[i]);
354c352
<         FT_Load_Glyph (ft->face, (*glyphs)[i].index, FT_LOAD_DEFAULT);
---
>         FT_Load_Glyph (face, (*glyphs)[i].index, FT_LOAD_DEFAULT);
356,357c354,355
<         x += DOUBLE_FROM_26_6 (ft->face->glyph->advance.x);
<         y -= DOUBLE_FROM_26_6 (ft->face->glyph->advance.y);
---
>         x += DOUBLE_FROM_26_6(face->glyph->advance.x);
>         y -= DOUBLE_FROM_26_6(face->glyph->advance.y);
368,370c366
<     double scale_x, scale_y;
<     cairo_ft_font_t *ft = (cairo_ft_font_t *)font;
<     cairo_status_t status = CAIRO_STATUS_SUCCESS;
---
>     FT_Face face; 
372c368
<     _get_scale_factors(&font->matrix, &scale_x, &scale_y);
---
>     face = ((cairo_ft_font_t *)font)->face;
374c370,374
< #define FONT_UNIT_TO_DEV(x) ((double)(x) / (double)(ft->face->units_per_EM))
---
>     extents->ascent =        DOUBLE_FROM_26_6(face->ascender);
>     extents->descent =       DOUBLE_FROM_26_6(face->descender);
>     extents->height =        DOUBLE_FROM_26_6(face->height);
>     extents->max_x_advance = DOUBLE_FROM_26_6(face->max_advance_width);
>     extents->max_y_advance = DOUBLE_FROM_26_6(face->max_advance_height);
376,381c376
<     extents->ascent = FONT_UNIT_TO_DEV(ft->face->ascender) * scale_y;
<     extents->descent = FONT_UNIT_TO_DEV(ft->face->descender) * scale_y;
<     extents->height = FONT_UNIT_TO_DEV(ft->face->height) * scale_y;
<     extents->max_x_advance = FONT_UNIT_TO_DEV(ft->face->max_advance_width) * scale_x;
<     extents->max_y_advance = FONT_UNIT_TO_DEV(ft->face->max_advance_height) * scale_y;
<     return status;
---
>     return CAIRO_STATUS_SUCCESS;
390,391c385,394
<     cairo_ft_font_t *ft;
<     cairo_status_t status = CAIRO_STATUS_SUCCESS;
---
>     FT_Face		face;
>     FT_GlyphSlot	glyphslot;
>     int                 i;
>     FT_F26Dot6		X, Y;
>     FT_F26Dot6		Left, Right, Top, Bottom;
>     FT_F26Dot6		overall_Left, overall_Right;
>     FT_F26Dot6		overall_Top, overall_Bottom;
>                                                                                           
>     if (font == NULL || glyphs == NULL || extents == NULL)
>         return CAIRO_STATUS_NO_MEMORY;
393c396,397
<     ft = (cairo_ft_font_t *)font;
---
>     face = ((cairo_ft_font_t *)font)->face;
>     glyphslot = face->glyph;
395c399,400
<     /* FIXME: lift code from xft to do this */
---
>     overall_Left = overall_Right = X = DOUBLE_TO_26_6(glyphs[0].x);
>     overall_Top = overall_Bottom = Y = DOUBLE_TO_26_6(glyphs[0].y);
397c402,457
<     return status;
---
> #define HORIZONTAL 1   /* FIXME  - get current vertical/horizontal mode from Fc???? */
>     if (HORIZONTAL)
>     {
>         for (i = 0; i < num_glyphs; i++)
>         {
>             FT_Load_Glyph (face, glyphs[i].index, FT_LOAD_DEFAULT);
>     
> 	    X = DOUBLE_TO_26_6(glyphs[i].x);
> 	    Y = DOUBLE_TO_26_6(glyphs[i].y);
>     
>             Left = X + glyphslot->metrics.horiBearingX;
>             Top = Y - glyphslot->metrics.horiBearingY;
>             Right = Left + glyphslot->metrics.width;
>             Bottom = Top + glyphslot->metrics.height;
>     
>             if (Left < overall_Left) overall_Left = Left;
>             if (Top < overall_Top) overall_Top = Top;
>             if (Right > overall_Right) overall_Right = Right;
>             if (Bottom > overall_Bottom) overall_Bottom = Bottom;
> 
> 	    X += glyphslot->metrics.horiAdvance;
>         }
>     }
>     else
>     {
>         for (i = 0; i < num_glyphs; i++)
>         {
>             FT_Load_Glyph (face, glyphs[i].index, FT_LOAD_DEFAULT);
>     
> 	    X = DOUBLE_TO_26_6(glyphs[i].x);
> 	    Y = DOUBLE_TO_26_6(glyphs[i].y);
>     
>             Left = X + glyphslot->metrics.vertBearingX;
>             Top = Y - glyphslot->metrics.vertBearingY;
>             Right = Left + glyphslot->metrics.width;
>             Bottom = Top + glyphslot->metrics.height;
>     
>             if (Left < overall_Left) overall_Left = Left;
>             if (Top < overall_Top) overall_Top = Top;
>             if (Right > overall_Right) overall_Right = Right;
>             if (Bottom > overall_Bottom) overall_Bottom = Bottom;
> 
> 	    Y += glyphslot->metrics.vertAdvance;
>         }
>     }
>     if (X > overall_Right) overall_Right = X;
>     if (Y > overall_Bottom) overall_Bottom = Y;
> 
>     extents->left_side_bearing  = DOUBLE_FROM_26_6(overall_Left);
>     extents->right_side_bearing = DOUBLE_FROM_26_6(overall_Right);
>     extents->ascent             = DOUBLE_FROM_26_6(overall_Top);
>     extents->descent            = DOUBLE_FROM_26_6(overall_Bottom);
>     extents->x_advance          = DOUBLE_FROM_26_6(X);
>     extents->y_advance          = DOUBLE_FROM_26_6(Y);
> 
>     return CAIRO_STATUS_SUCCESS;
Index: src/cairo_gstate.c
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo_gstate.c,v
retrieving revision 1.33
diff -r1.33 cairo_gstate.c
1594,1603c1594
<     cairo_int_status_t status;
<     cairo_matrix_t saved_font_matrix;
<     
<     cairo_matrix_copy (&saved_font_matrix, &gstate->font->matrix);
<     cairo_matrix_multiply (&gstate->font->matrix, &gstate->ctm, &gstate->font->matrix);
<     
<     status = _cairo_font_font_extents (gstate->font, extents);
< 
<     cairo_matrix_copy (&gstate->font->matrix, &saved_font_matrix);    
<     return status;
---
>     return _cairo_font_font_extents (gstate->font, extents);
1623,1633c1614
<     cairo_matrix_t saved_font_matrix;
<     cairo_status_t status;
< 
<     cairo_matrix_copy (&saved_font_matrix, &gstate->font->matrix);
<     cairo_matrix_multiply (&gstate->font->matrix, &gstate->ctm, &gstate->font->matrix);
< 
<     status = _cairo_font_text_extents (gstate->font,
< 				       utf8, extents);
< 
<     cairo_matrix_copy (&gstate->font->matrix, &saved_font_matrix);
<     return status;
---
>     return _cairo_font_text_extents (gstate->font, utf8, extents);
1642,1669c1623
<     cairo_status_t status;
<     int i;
<     cairo_glyph_t *transformed_glyphs = NULL;
<     cairo_matrix_t saved_font_matrix;
< 
<     transformed_glyphs = malloc (num_glyphs * sizeof(cairo_glyph_t));
<     if (transformed_glyphs == NULL)
< 	return CAIRO_STATUS_NO_MEMORY;
<     
<     for (i = 0; i < num_glyphs; ++i)
<     {
< 	transformed_glyphs[i] = glyphs[i];
< 	cairo_matrix_transform_point (&gstate->ctm, 
< 				      &(transformed_glyphs[i].x), 
< 				      &(transformed_glyphs[i].y));
<     }
< 
<     cairo_matrix_copy (&saved_font_matrix, &gstate->font->matrix);
<     cairo_matrix_multiply (&gstate->font->matrix, &gstate->ctm, &gstate->font->matrix);
< 
<     status = _cairo_font_glyph_extents (gstate->font,
< 					transformed_glyphs, num_glyphs,
< 					extents);
< 
<     cairo_matrix_copy (&gstate->font->matrix, &saved_font_matrix);
< 
<     free (transformed_glyphs);
<     return status;
---
>     return _cairo_font_glyph_extents (gstate->font, glyphs, num_glyphs, extents);
-------------- next part --------------
? autom4te.cache
? depcomp
Index: src/svg_cairo.c
===================================================================
RCS file: /cvs/xsvg/libsvg-cairo/src/svg_cairo.c,v
retrieving revision 1.22
diff -r1.22 svg_cairo.c
1122a1123,1125
>     cairo_text_extents_t extents;
> 
>     _svg_cairo_select_font (svg_cairo);
1126a1130,1142
>     if (svg_cairo->state->text_anchor != SVG_TEXT_ANCHOR_START) {
> 	cairo_text_extents (svg_cairo->cr, utf8, &extents);
> 
> 	if (svg_cairo->state->text_anchor == SVG_TEXT_ANCHOR_END) {
> 	    x -= extents.x_advance;
>             y -= extents.y_advance;
>         }
>         else if (svg_cairo->state->text_anchor == SVG_TEXT_ANCHOR_MIDDLE) {
>             x -= extents.x_advance / 2.0;
>             y -= extents.y_advance / 2.0;
>         }
>     }
> 
1137,1149d1152
< /* XXX: Temporarily disable since cairo_text_extents is currently not working
< 
<     if (svg_cairo->state->text_anchor != SVG_TEXT_ANCHOR_START) {
< 	double x, y, width, height, dx, dy;
< 	cairo_text_extents (svg_cairo->cr, utf8, &x, &y, &width, &height, &dx, &dy);
< 	if (svg_cairo->state->text_anchor == SVG_TEXT_ANCHOR_END)
< 	    cairo_rel_move_to (svg_cairo->cr, -dx, -dy);
< 	else if (svg_cairo->state->text_anchor == SVG_TEXT_ANCHOR_MIDDLE)
< 	    cairo_rel_move_to (svg_cairo->cr, -dx / 2, -dy / 2);
<     }
< */
< 
<     _svg_cairo_select_font (svg_cairo);
-------------- next part --------------
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/* Small example demonstrating emulating knockout-groups as in PDF-1.4
 * using cairo_set_operator().
 *
 * Owen Taylor,

 * v0.1  30 November  2002
 * v0.2   1 December  2002 - typo fixes from Keith Packard
 * v0.3  17 April     2003 - Tracking changes in Xr, (Removal of Xr{Push,Pop}Group)
 * v0.4  29 September 2003 - Use cairo_rectangle rather than private rect_path
 *			     Use cairo_arc for oval_path
 * Keeping log of changes in ChangeLog/CVS now. (2003-11-19) Carl Worth
 */
#include <X11/Xlib.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <math.h>
#include <stdio.h>

static void
draw (cairo_t *cr,
      int      width,
      int      height)
{
    double penwidth=4.0;
    char *text1 = "Hello - this is text..";
    char *text2 = "Hello, this is text #2 !!!";
    cairo_text_extents_t extents;
    
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 1.0, 1.0);
    cairo_rectangle(cr, 0, 0, width, height);
    cairo_fill(cr);
    cairo_restore(cr);

    /*
     * Draw text1 and its bounding box
     */
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 0.0, 0.0);
    cairo_scale(cr, 1.0, 1.0);
    cairo_move_to(cr, 50, 50);
    cairo_select_font(cr, "Arial",
                      CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_scale_font(cr, 15);
    cairo_text_extents(cr, text1, &extents);

    cairo_save(cr);
    cairo_set_line_width(cr, penwidth);
    cairo_set_rgb_color(cr, 0.0, 0.0, 1.0);
fprintf(stderr,"%g %g\n",extents.left_side_bearing,extents.right_side_bearing);
    cairo_rectangle(cr,
		50 + extents.left_side_bearing - penwidth/2.0,
		50 + extents.ascent - penwidth/2.0,
                extents.right_side_bearing - extents.left_side_bearing + penwidth,
		extents.descent - extents.ascent + penwidth);
    cairo_stroke(cr);
    cairo_restore(cr);
    
    cairo_show_text(cr, text1);

    cairo_restore(cr);

    /*
     * Draw text1 and its bounding box
     */
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 0.0, 0.0);
    cairo_scale(cr, 1.5, 1.5);
    cairo_move_to(cr, 50, 75);
    cairo_select_font(cr, "Arial",
                      CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_scale_font(cr, 15);
    cairo_text_extents(cr, text1, &extents);

    cairo_save(cr);
    cairo_set_line_width(cr, penwidth);
    cairo_set_rgb_color(cr, 0.0, 0.0, 1.0);
fprintf(stderr,"%g %g\n",extents.left_side_bearing,extents.right_side_bearing);
    cairo_rectangle(cr,
		50 + extents.left_side_bearing - penwidth/2.0,
		75 + extents.ascent - penwidth/2.0,
                extents.right_side_bearing - extents.left_side_bearing + penwidth,
		extents.descent - extents.ascent + penwidth);
    cairo_stroke(cr);
    cairo_restore(cr);
    
    cairo_show_text(cr, text1);

    cairo_restore(cr);

    /*
     * Draw text1 and its bounding box
     */
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 0.0, 0.0);
    cairo_scale(cr, 2.0, 2.0);
    cairo_move_to(cr, 50, 100);
    cairo_select_font(cr, "Arial",
                      CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_scale_font(cr, 15);
    cairo_text_extents(cr, text1, &extents);

    cairo_save(cr);
    cairo_set_line_width(cr, penwidth);
    cairo_set_rgb_color(cr, 0.0, 0.0, 1.0);
fprintf(stderr,"%g %g\n",extents.left_side_bearing,extents.right_side_bearing);
    cairo_rectangle(cr,
		50 + extents.left_side_bearing - penwidth/2.0,
		100 + extents.ascent - penwidth/2.0,
                extents.right_side_bearing - extents.left_side_bearing + penwidth,
		extents.descent - extents.ascent + penwidth);
    cairo_stroke(cr);
    cairo_restore(cr);
    
    cairo_show_text(cr, text1);

    cairo_restore(cr);

    /*
     * Draw text2 and its bounding box
     */
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 0.0, 0.0);
    cairo_scale(cr, 1.5, 1.5);
    cairo_rotate(cr, 0.3);
    cairo_move_to(cr, 150, 150);
    cairo_select_font(cr, "Arial",
                      CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_scale_font(cr, 15);
    cairo_text_extents(cr, text2, &extents);

    cairo_save(cr);
    cairo_set_line_width(cr, penwidth);
    cairo_set_rgb_color(cr, 0.0, 0.0, 1.0);
    cairo_rectangle(cr,
		150 + extents.left_side_bearing - penwidth/2.0,
		150 + extents.ascent - penwidth/2.0,
                extents.right_side_bearing - extents.left_side_bearing + penwidth,
		extents.descent - extents.ascent + penwidth);
    cairo_stroke(cr);
    cairo_restore(cr);

    cairo_show_text(cr, text2);
    
    cairo_restore(cr);

    /*
     * Draw text2 and its bounding box
     */
    cairo_save(cr);
    cairo_set_rgb_color(cr, 1.0, 0.0, 0.0);
    cairo_scale(cr, 1.5, 1.5);
    cairo_rotate(cr, 0.6);
    cairo_move_to(cr, 150, 150);
    cairo_select_font(cr, "Arial",
                      CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_scale_font(cr, 15);
    cairo_text_extents(cr, text2, &extents);

    cairo_save(cr);
    cairo_set_line_width(cr, penwidth);
    cairo_set_rgb_color(cr, 0.0, 0.0, 1.0);
    cairo_rectangle(cr,
		150 + extents.left_side_bearing - penwidth/2.0,
		150 + extents.ascent - penwidth/2.0,
                extents.right_side_bearing - extents.left_side_bearing + penwidth,
		extents.descent - extents.ascent + penwidth);
    cairo_stroke(cr);
    cairo_restore(cr);

    cairo_show_text(cr, text2);
    
    cairo_restore(cr);
}

int
main (int argc, char **argv)
{
  Display *dpy;
  int screen;
  Window w;
  Pixmap pixmap;
  char *title = "cairo: Knockout Groups";
  unsigned int quit_keycode;
  int needs_redraw;
  GC gc;
  XWMHints *wmhints;
  XSizeHints *normalhints;
  XClassHint *classhint;
  
  int width = 600;
  int height = 500;
  
  dpy = XOpenDisplay (NULL);
  screen = DefaultScreen (dpy);

  w = XCreateSimpleWindow (dpy, RootWindow (dpy, screen),
			   0, 0, width, height, 0,
			   BlackPixel (dpy, screen), WhitePixel (dpy, screen));

  normalhints = XAllocSizeHints ();
  normalhints->flags = 0;
  normalhints->x = 0;
  normalhints->y = 0;
  normalhints->width = width;
  normalhints->height = height;

  classhint = XAllocClassHint ();
  classhint->res_name = "cairo-knockout";
  classhint->res_class = "Cairo-knockout";
    
  wmhints = XAllocWMHints ();
  wmhints->flags = InputHint;
  wmhints->input = True;
    
  Xutf8SetWMProperties (dpy, w, title, "cairo-knockout", 0, 0, 
                        normalhints, wmhints, classhint);
  XFree (wmhints);
  XFree (classhint);
  XFree (normalhints);

  pixmap = XCreatePixmap (dpy, w, width, height, DefaultDepth (dpy, screen));
  gc = XCreateGC (dpy, pixmap, 0, NULL);

  quit_keycode = XKeysymToKeycode(dpy, XStringToKeysym("Q"));

  XSelectInput (dpy, w, ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask);
  XMapWindow (dpy, w);
  
  needs_redraw = 1;

  while (1) {
      XEvent xev;

      /* Only do the redraw if there are no events pending.  This
       * avoids us getting behind doing several redraws for several
       * consecutive resize events for example.
       */
      if (!XPending (dpy) && needs_redraw) {
          cairo_t *cr = cairo_create ();

          cairo_set_target_drawable (cr, dpy, pixmap);

          draw (cr, width, height);

          cairo_destroy (cr);

          XCopyArea (dpy, pixmap, w, gc,
                     0, 0,
                     width, height,
                     0, 0);

          needs_redraw = 0;
      }
      
      XNextEvent (dpy, &xev);

      switch (xev.xany.type) {
      case ButtonPress:
          /* A click on the canvas ends the program */
          goto DONE;
      case KeyPress:
          if (xev.xkey.keycode == quit_keycode)
              goto DONE;
          break;
      case ConfigureNotify:
          /* Note new size and create new pixmap. */
	  width = xev.xconfigure.width;
	  height = xev.xconfigure.height;
          XFreePixmap (dpy, pixmap);
          pixmap = XCreatePixmap (dpy, w, width, height, DefaultDepth (dpy, screen));
          needs_redraw = 1;
	  break;
      case Expose:
          XCopyArea (dpy, pixmap, w, gc,
                     xev.xexpose.x, xev.xexpose.y,
                     xev.xexpose.width, xev.xexpose.height,
                     xev.xexpose.x, xev.xexpose.y);
	  break;
      }
  }
  DONE:

  XFreeGC (dpy, gc);
  XCloseDisplay (dpy);

  return 0;
}


More information about the cairo mailing list