After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 329547 - pango_layout_get_pixel_extents and pango_layout_get_pixel_size cause off-by-one metrics
pango_layout_get_pixel_extents and pango_layout_get_pixel_size cause off-by-o...
Status: RESOLVED FIXED
Product: pango
Classification: Platform
Component: general
unspecified
Other All
: Normal minor
: ---
Assigned To: pango-maint
pango-maint
Depends on:
Blocks:
 
 
Reported: 2006-02-02 01:06 UTC by Behdad Esfahbod
Modified: 2006-04-29 19:40 UTC
See Also:
GNOME target: ---
GNOME version: ---



Description Behdad Esfahbod 2006-02-02 01:06:20 UTC
Here is the current code for these two functions:

void
pango_layout_get_pixel_extents (PangoLayout *layout,
                                PangoRectangle *ink_rect,
                                PangoRectangle *logical_rect)
{
  g_return_if_fail (PANGO_IS_LAYOUT (layout));

  pango_layout_get_extents (layout, ink_rect, logical_rect);

  if (ink_rect)
    {
      ink_rect->width = (ink_rect->width + PANGO_SCALE / 2) / PANGO_SCALE;
      ink_rect->height = (ink_rect->height + PANGO_SCALE / 2) / PANGO_SCALE;

      ink_rect->x = PANGO_PIXELS (ink_rect->x);
      ink_rect->y = PANGO_PIXELS (ink_rect->y);
    }

  if (logical_rect)
    {
      logical_rect->width = (logical_rect->width + PANGO_SCALE / 2) / PANGO_SCALE;
      logical_rect->height = (logical_rect->height + PANGO_SCALE / 2) / PANGO_SCALE;

      logical_rect->x = PANGO_PIXELS (logical_rect->x);
      logical_rect->y = PANGO_PIXELS (logical_rect->y);
    }
}

void
pango_layout_get_pixel_size (PangoLayout *layout,
                             int         *width,
                             int         *height)
{
  PangoRectangle logical_rect;

  pango_layout_get_extents (layout, NULL, &logical_rect);

  if (width)
    *width = (logical_rect.width + PANGO_SCALE / 2) / PANGO_SCALE;
  if (height)
    *height = (logical_rect.height + PANGO_SCALE / 2) / PANGO_SCALE;
}


First, I'm not sure how rounding makes sense here.  It may cause off-by-one metrics in itself, but more importantly is how pango_layout_get_pixel_extents is implemented: it floors x and y, and rounds width and height all separately.  Something like this will be more robust:

      x_res = ink_rect->x % PANGO_SCALE;
      y_res = ink_rect->y % PANGO_SCALE;

      ink_rect->x = PANGO_PIXELS (ink_rect->x);
      ink_rect->y = PANGO_PIXELS (ink_rect->y);

      ink_rect->width = (x_res + ink_rect->width + PANGO_SCALE / 2) / PANGO_SCALE;
      ink_rect->height = (y_res + ink_rect->height + PANGO_SCALE / 2) / PANGO_SCALE;


(and of course, the rounding can be done using PANGO_PIXELS)
Comment 1 Morten Welinder 2006-04-27 20:15:14 UTC
Rounding to nearest does not make sense.  Callers like
gtkcellrenderertext.c's get_size expect rounding up, at least for width
and height.

(This becomes obvious if ones changes

  pango_layout_get_pixel_extents (layout, NULL, &rect);

to

  pango_layout_get_pixel_extents (layout, &rect, NULL);

in get_size.)
Comment 2 Behdad Esfahbod 2006-04-28 01:57:43 UTC
A few points:

 1) We should use the same function (floor, round, ceil) on both x and x+width, or we will see gaps between neighbouring runs, or overlaps.

 2) With pixel devices, we are really talking about hinted metrics only, so the only difference that the conversion function makes is for cases that a rounding error has been introduced somewhere, like the eclipse bug I fixed for example:

  https://bugs.freedesktop.org/show_bug.cgi?id=5200

for this reason I think rounding makes more sense.
Comment 3 Morten Welinder 2006-04-28 13:19:54 UTC
I'm not sure I understand you correctly re 2.  If you try the gtk+ change
I mentioned, you will see that occasionally a letter gets its bottom cut
off.  If you round height up [this place cares about width and height only]
then that never happens.

Are you saying that gtk+ has a separate bug here?

I fear that there are really two different classes of users of the _pixel_
functions:

1. Those who want to know the width of the layout in the sense that all
   the ink will fall inside the rectangle.  [round up]

2. Those who want the width in the sense that here+width is where the next
   item should be drawn.  [round nearest]
Comment 4 Behdad Esfahbod 2006-04-28 15:28:16 UTC
1) What if you try with my patch?

2) Do you have hinting off by any chance?  There's a separate bug that with unhinted fonts metrics are not hinted correctly.  That can cause you see a difference if you round up.



As for your two usecases, note that these two functions are pure convenience.  One can call pango_layout_get_extents and round how they like it.  I had the same vision as you, but came up to the conclusion that your two cases should always give the same result, and that is true given that we know all metrics are always hinted for pixel display (oh oh, except for ink extents of course, causing what you observe.)
Comment 5 Morten Welinder 2006-04-28 15:47:43 UTC
1. My ->x and ->y always seem to be N*1024.  So no difference.

2. Clearly on.  But this is a self-compiled bastard system, so who knows
   what kind of problems have been introduced.
Comment 6 Owen Taylor 2006-04-28 17:12:29 UTC
I would agree with Morten that ink metrics should be expanded, not
rounded. 

The guarantee that we make is that you can render a layout onto a
pixmap the size of its ink metrics and it won't get clipped.

Overlap isn't an issue, because ink metrics for adjacent elements
can and do overlap already.

For logical metrics, rounding is probably more sensible.
Comment 7 Behdad Esfahbod 2006-04-29 19:40:45 UTC
Ok, now we have:

#define PANGO_SCALE 1024
#define PANGO_PIXELS(d) (((int)(d) + 512) >> 10)
#define PANGO_PIXELS_FLOOR(d) (((int)(d)) >> 10)
#define PANGO_PIXELS_CEIL(d) (((int)(d) + 1023) >> 10)

and:

void
pango_layout_get_pixel_extents (PangoLayout *layout,
                                PangoRectangle *ink_rect,
                                PangoRectangle *logical_rect)
{
  g_return_if_fail (PANGO_IS_LAYOUT (layout));

  pango_layout_get_extents (layout, ink_rect, logical_rect);

  if (ink_rect)
    {
      int orig_x = ink_rect->x;
      int orig_y = ink_rect->y;

      ink_rect->x = PANGO_PIXELS_FLOOR (ink_rect->x);
      ink_rect->y = PANGO_PIXELS_FLOOR (ink_rect->y);

      ink_rect->width  = PANGO_PIXELS_CEIL (orig_x + ink_rect->width ) - ink_rect->x;
      ink_rect->height = PANGO_PIXELS_CEIL (orig_y + ink_rect->height) - ink_rect->y;
    }

  if (logical_rect)
    {
      int orig_x = logical_rect->x;
      int orig_y = logical_rect->y;

      logical_rect->x = PANGO_PIXELS (logical_rect->x);
      logical_rect->y = PANGO_PIXELS (logical_rect->y);

      logical_rect->width  = PANGO_PIXELS (orig_x + logical_rect->width ) - logical_rect->x;
      logical_rect->height = PANGO_PIXELS (orig_y + logical_rect->height) - logical_rect->y;
    }
}

How does it look?

I'm a bit concerned about what may happen in the sums overflow.  Not sure if it matters.

2006-04-29  Behdad Esfahbod  <behdad@gnome.org>

        Bug 329547 – pango_layout_get_pixel_extents and
        pango_layout_get_pixel_size cause off-by-one metrics

        * docs/pango-sections.txt:
        * docs/tmpl/glyphs.sgml:
        * pango/pango-types.h: Define PANGO_PIXELS_FLOOR and PANGO_PIXELS_CEIL.

        * pango/pango-layout.c (pango_layout_get_pixel_extents),
        (pango_layout_get_pixel_size): Make sure logical rects are
        consistent and ink rects are inclusive.


Only on HEAD.  Please test.