From 31e0850c421fcc777c452121eb5c68fcf2ce3cda Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Fri, 28 May 2004 22:53:24 +0000 Subject: Add PangoMatrix type for affine transforms. Fri May 28 11:39:39 2004 Owen Taylor * pango/pango-types.h pango/pango-utils.c: Add PangoMatrix type for affine transforms. * configure.in pango.pc.in pango/Makefile.am: Add a -lm dependency for PangoMatrix operations. * pango/pango-context.[ch]: Add pango_context_set/get_matrix(). * pango/pangoft2-render.c pango/pangoft2-private.h: Add code for drawing antialiased transformed rectangles and squiggly error underlines. * pango/pangoft2.[ch]: Add pango_ft2_render_transformed(), pango_ft2_render_layout_subpixel(), pango_ft2_render_layout_line_subpixel(), implement transformed rendering. * pango/pangofc-font.c: Pass any transformation matrix on to fontconfig when creating the pattern for a PangoFcFont. --- pango/Makefile.am | 3 +- pango/pango-context.c | 49 +++++ pango/pango-context.h | 4 + pango/pango-types.h | 57 ++++++ pango/pango-utils.c | 136 ++++++++++++++ pango/pangofc-fontmap.c | 87 ++++++--- pango/pangoft2-private.h | 13 ++ pango/pangoft2-render.c | 459 +++++++++++++++++++++++++++++++++++++++++++++++ pango/pangoft2.c | 359 ++++++++++++++++++++++++------------ pango/pangoft2.h | 41 +++-- 10 files changed, 1062 insertions(+), 146 deletions(-) create mode 100644 pango/pangoft2-render.c (limited to 'pango') diff --git a/pango/Makefile.am b/pango/Makefile.am index 5782a14b..14b3e039 100644 --- a/pango/Makefile.am +++ b/pango/Makefile.am @@ -40,7 +40,7 @@ endif lib_LTLIBRARIES = libpango-1.0.la libpango_1_0_la_LDFLAGS = -version-info $(LT_VERSION_INFO) $(no_undefined) -libpango_1_0_la_LIBADD = $(GLIB_LIBS) mini-fribidi/libmini-fribidi.la +libpango_1_0_la_LIBADD = $(GLIB_LIBS) mini-fribidi/libmini-fribidi.la -lm libpango_1_0_la_DEPENDENCIES = mini-fribidi/libmini-fribidi.la if OS_WIN32 @@ -189,6 +189,7 @@ libpangoft2_1_0_la_SOURCES = \ pangoft2.c \ pangoft2-private.h \ pangoft2-fontmap.c \ + pangoft2-render.c \ module-defs-fc.c if OS_WIN32 diff --git a/pango/pango-context.c b/pango/pango-context.c index 316245dd..9ebde1e1 100644 --- a/pango/pango-context.c +++ b/pango/pango-context.c @@ -37,6 +37,8 @@ struct _PangoContext PangoDirection base_dir; PangoFontDescription *font_desc; + PangoMatrix *matrix; + PangoFontMap *font_map; }; @@ -117,6 +119,8 @@ pango_context_finalize (GObject *object) g_object_unref (context->font_map); pango_font_description_free (context->font_desc); + if (context->matrix) + pango_matrix_free (context->matrix); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -151,6 +155,51 @@ pango_context_new (void) return context; } +/** + * pango_context_set_matrix: + * @context: a #PangoContext + * @matrix: a #PangoMatrix, or %NULL to unset any existing matrix. + * (No matrix set is the same as setting the identity matrix.) + * + * Sets the transformation matrix that will be applied when rendering + * with this context. Note that reported metrics are in the user space + * coordinates before the application of the matrix, not device-space + * coordiantes after the application of the matrix. So, they don't scale + * with the matrix, though they may change slightly for different + * matrices, depending on how the text is fit to the pixel grid. + **/ +void +pango_context_set_matrix (PangoContext *context, + PangoMatrix *matrix) +{ + g_return_if_fail (PANGO_IS_CONTEXT (context)); + + if (context->matrix) + pango_matrix_free (context->matrix); + if (matrix) + context->matrix = pango_matrix_copy (matrix); +} + +/** + * pango_context_get_matrix: + * @context: a #PangoContext + * + * Gets the transformation matrix that will be applied when + * rendering with this context. See pango_context_set_matrix(). + * + * Returns: the matrix, or %NULL if no matrix has been set + * (which is the same as the identity matrix). The returned + * matrix is owned by Pango and must not be modified or + * freed. + **/ +PangoMatrix * +pango_context_get_matrix (PangoContext *context) +{ + g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL); + + return context->matrix; +} + /** * pango_context_set_font_map: * @context: a #PangoContext diff --git a/pango/pango-context.h b/pango/pango-context.h index 878c3c28..6a6d28c8 100644 --- a/pango/pango-context.h +++ b/pango/pango-context.h @@ -78,6 +78,10 @@ void pango_context_set_base_dir (PangoContext PangoDirection direction); PangoDirection pango_context_get_base_dir (PangoContext *context); +void pango_context_set_matrix (PangoContext *context, + PangoMatrix *matrix); +PangoMatrix *pango_context_get_matrix (PangoContext *context); + /* Break a string of Unicode characters into segments with * consistent shaping/language engine and bidrectional level. * Returns a GList of PangoItem's diff --git a/pango/pango-types.h b/pango/pango-types.h index 6f515ead..55283fbd 100644 --- a/pango/pango-types.h +++ b/pango/pango-types.h @@ -33,6 +33,8 @@ typedef struct _PangoEngineLang PangoEngineLang; typedef struct _PangoEngineShape PangoEngineShape; typedef struct _PangoFont PangoFont; + +typedef struct _PangoMatrix PangoMatrix; typedef struct _PangoRectangle PangoRectangle; /* Dummy typedef - internally it's a 'const char *' */ @@ -53,6 +55,61 @@ struct _PangoRectangle int height; }; +/** + * PangoMatrix: + * @xx: 1st component of the transformation matrix + * @xy: 2nd component of the transformation matrix + * @yx: 3rd component of the transformation matrix + * @yy: 4th component of the transformation matrix + * @x0: x translation + * @y0: y translation + * + * A structure specifying a transformation between user-space + * coordinates and device coordinates. The transformation + * is given by + * + * + * x_device = x_user * matrix->xx + y_user * matrix->xy + matrix->x0; + * y_device = x_user * matrix->yx + y_user * matrix->yy + matrix->y0; + * + **/ +struct _PangoMatrix +{ + double xx; + double xy; + double yx; + double yy; + double x0; + double y0; +}; + +/** + * PANGO_MATRIX_INIT + * + * Constant that can be used to initialize a PangoMatrix to + * the identity transform. + * + * + * PangoMatrix matrix = PANGO_MATRIX_INIT; + * pango_matrix_rotate (&matrix, 45.); + * + **/ +#define PANGO_MATRIX_INIT { 1., 0., 0., 1., 0., 0. }; + +PangoMatrix *pango_matrix_copy (PangoMatrix *matrix); +void pango_matrix_free (PangoMatrix *matrix); + +void pango_matrix_translate (PangoMatrix *matrix, + double tx, + double ty); +void pango_matrix_scale (PangoMatrix *matrix, + double scale_x, + double scale_y); +void pango_matrix_rotate (PangoMatrix *matrix, + double degrees); +void pango_matrix_concat (PangoMatrix *matrix, + PangoMatrix *new); + #define PANGO_SCALE 1024 #define PANGO_PIXELS(d) (((d) >= 0) ? \ ((d) + PANGO_SCALE / 2) / PANGO_SCALE : \ diff --git a/pango/pango-utils.c b/pango/pango-utils.c index 68267772..6c75a3bf 100644 --- a/pango/pango-utils.c +++ b/pango/pango-utils.c @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -986,6 +987,141 @@ pango_parse_stretch (const char *str, return FALSE; } +/** + * pango_matrix_copy: + * @matrix: a #PangoMatrix + * + * Copies a #PangoMatrix. + * + * Return value: a copy of @matrix. The result must be freed with + * pango_matrix_free(). + **/ +PangoMatrix * +pango_matrix_copy (PangoMatrix *matrix) +{ + g_return_val_if_fail (matrix != NULL, NULL); + + return g_memdup (matrix, sizeof (PangoMatrix)); +} + +/** + * pango_matrix_free: + * @matrix: a #PangoMatrix + * + * Free a #PangoMatrix created with pango_matrix_copy(). + **/ +void +pango_matrix_free (PangoMatrix *matrix) +{ + g_return_if_fail (matrix != NULL); + + g_free (matrix); +} + +/** + * pango_matrix_translate: + * @matrix: a #PangoMatrix + * @tx: amount to translate in the X direction + * @ty: amount to translate in the Y direction + * + * Changes the transformation represented by @matrix to be the + * transformation given by first translating by (@tx, @ty) + * then applying the original transformation. + **/ +void +pango_matrix_translate (PangoMatrix *matrix, + double tx, + double ty) +{ + g_return_if_fail (matrix != NULL); + + matrix->x0 = matrix->xx * tx + matrix->xy * ty + matrix->x0; + matrix->y0 = matrix->yx * tx + matrix->yy * ty + matrix->y0; +} + +/** + * pango_matrix_scale: + * @matrix: a #PangoMatrix + * @scale_x: amount to scale by in X direction + * @scale_y: amount to scale by in Y direction + * + * Changes the transformation represented by @matrix to be the + * transformation given by first scaling by @sx in the X direction + * and @sy in the Y direction then applying the original + * transformation. + **/ +void +pango_matrix_scale (PangoMatrix *matrix, + double scale_x, + double scale_y) +{ + g_return_if_fail (matrix != NULL); + + matrix->xx *= scale_x; + matrix->xy *= scale_y; + matrix->yx *= scale_x; + matrix->yy *= scale_y; +} + +/** + * pango_matrix_rotate: + * @matrix: a #PangoMatrix + * @degrees: degrees to rotate counter-clockwise + * + * Changes the transformation represented by @matrix to be the + * transformation given by first rotating by @degrees degrees + * counter-clokwise then applying the original transformation. + **/ +void +pango_matrix_rotate (PangoMatrix *matrix, + double degrees) +{ + PangoMatrix tmp; + gdouble r, s, c; + + g_return_if_fail (matrix != NULL); + + r = degrees * (G_PI / 180.); + s = sin (r); + c = cos (r); + + tmp.xx = c; + tmp.xy = s; + tmp.yx = -s; + tmp.yy = c; + tmp.x0 = 0; + tmp.y0 = 0; + + pango_matrix_concat (matrix, &tmp); +} + +/** + * pango_matrix_concat: + * @matrix: a #PangoMatrix + * @new: a #PangoMatrix + * + * Changes the transformation represented by @matrix to be the + * transformation given by first applying transformation + * given by @new then applying the original transformation. + **/ +void +pango_matrix_concat (PangoMatrix *matrix, + PangoMatrix *new) +{ + PangoMatrix tmp; + + g_return_if_fail (matrix != NULL); + + tmp = *matrix; + + matrix->xx = tmp.xx * new->xx + tmp.xy * new->yx; + matrix->xy = tmp.xx * new->xy + tmp.xy * new->yy; + matrix->yx = tmp.yx * new->xx + tmp.yy * new->yx; + matrix->yy = tmp.yx * new->xy + tmp.yy * new->yy; + matrix->x0 = tmp.xx * new->x0 + tmp.xy * new->y0 + tmp.x0; + matrix->y0 = tmp.yx * new->y0 + tmp.yy * new->y0 + tmp.y0; +} + static const char canon_map[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/pango/pangofc-fontmap.c b/pango/pangofc-fontmap.c index 26129836..5321e587 100644 --- a/pango/pangofc-fontmap.c +++ b/pango/pangofc-fontmap.c @@ -359,7 +359,6 @@ pango_fc_font_map_add (PangoFcFontMap *fcfontmap, g_assert (fcfont->fontmap == NULL); - fcfont->fontmap = g_object_ref (fcfontmap); g_hash_table_insert (priv->fonts, fcfont->font_pattern, fcfont); @@ -588,10 +587,12 @@ pango_fc_make_pattern (const PangoFontDescription *description) static PangoFont * pango_fc_font_map_new_font (PangoFontMap *fontmap, - FcPattern *match) + PangoMatrix *pango_matrix, + FcPattern *match) { PangoFcFontMap *fcfontmap = (PangoFcFontMap *)fontmap; PangoFcFontMapPrivate *priv = fcfontmap->priv; + FcPattern *pattern; PangoFcFont *fcfont; /* Returning NULL here actually violates a contract @@ -604,14 +605,40 @@ pango_fc_font_map_new_font (PangoFontMap *fontmap, return NULL; /* Look up cache */ - fcfont = g_hash_table_lookup (priv->fonts, match); + if (!pango_matrix) + { + fcfont = g_hash_table_lookup (priv->fonts, match); - if (fcfont) - return g_object_ref (fcfont); + if (fcfont) + return g_object_ref (fcfont); + } + + if (pango_matrix) + { + FcMatrix fc_matrix; + + /* FontConfig has the Y axis pointing up, Pango, down. + */ + fc_matrix.xx = pango_matrix->xx; + fc_matrix.xy = - pango_matrix->xy; + fc_matrix.yx = - pango_matrix->yx; + fc_matrix.yy = pango_matrix->yy; + + pattern = FcPatternDuplicate (match); + FcPatternAddMatrix (pattern, FC_MATRIX, &fc_matrix); + } + else + pattern = match; + + fcfont = PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->new_font (fcfontmap, pattern); - fcfont = PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->new_font (fcfontmap, match); + if (!pango_matrix) + pango_fc_font_map_add (fcfontmap, fcfont); - pango_fc_font_map_add (fcfontmap, fcfont); + if (pango_matrix) + FcPatternDestroy (pattern); + + fcfont->fontmap = g_object_ref (fcfontmap); return (PangoFont *)fcfont; } @@ -736,15 +763,19 @@ pango_fc_font_map_get_patterns (PangoFontMap *fontmap, static PangoFont * pango_fc_font_map_load_font (PangoFontMap *fontmap, - PangoContext *context, - const PangoFontDescription *description) + PangoContext *context, + const PangoFontDescription *description) { PangoFcPatternSet *patterns = pango_fc_font_map_get_patterns (fontmap, context, description, NULL); if (!patterns) return NULL; if (patterns->n_patterns > 0) - return pango_fc_font_map_new_font (fontmap, patterns->patterns[0]); + { + return pango_fc_font_map_new_font (fontmap, + context ? pango_context_get_matrix (context) : NULL, + patterns->patterns[0]); + } return NULL; } @@ -809,35 +840,51 @@ pango_fc_font_map_load_fontset (PangoFontMap *fontmap, PangoFcPatternSet *patterns = pango_fc_font_map_get_patterns (fontmap, context, desc, language); PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (fontmap); PangoFcFontMapPrivate *priv = fcfontmap->priv; + PangoFontset *result; + PangoMatrix *matrix; int i; if (!patterns) return NULL; - if (!patterns->fontset) + if (context) + matrix = pango_context_get_matrix (context); + else + matrix = NULL; + + /* We never cache fontsets when a transformation is in place + */ + if (!patterns->fontset || matrix) { PangoFontsetSimple *simple; simple = pango_fontset_simple_new (language); + result = PANGO_FONTSET (simple); for (i = 0; i < patterns->n_patterns; i++) { - PangoFont *font = pango_fc_font_map_new_font (fontmap, patterns->patterns[i]); + PangoFont *font; + + font = pango_fc_font_map_new_font (fontmap, matrix, patterns->patterns[i]); if (font) pango_fontset_simple_append (simple, font); } - - patterns->fontset = PANGO_FONTSET (simple); - g_object_add_weak_pointer (G_OBJECT (patterns->fontset), - (gpointer *)&patterns->fontset); + + if (!matrix) + { + patterns->fontset = PANGO_FONTSET (simple); + g_object_add_weak_pointer (G_OBJECT (patterns->fontset), + (gpointer *)&patterns->fontset); + } } else - g_object_ref (patterns->fontset); + result = g_object_ref (patterns->fontset); - if (!patterns->cache_link || - patterns->cache_link != priv->fontset_cache->head) + if (!matrix && + (!patterns->cache_link || + patterns->cache_link != priv->fontset_cache->head)) pango_fc_font_map_cache_fontset (fcfontmap, patterns); - return patterns->fontset; + return result; } static void diff --git a/pango/pangoft2-private.h b/pango/pangoft2-private.h index 8caa28b7..29ab5b50 100644 --- a/pango/pangoft2-private.h +++ b/pango/pangoft2-private.h @@ -92,4 +92,17 @@ void pango_ft2_font_set_cache_glyph_data (PangoFont *font, void pango_ft2_font_set_glyph_cache_destroy (PangoFont *font, GDestroyNotify destroy_notify); +void _pango_ft2_draw_rect (FT_Bitmap *bitmap, + PangoMatrix *matrix, + int x, + int y, + int width, + int height); +void _pango_ft2_draw_error_underline (FT_Bitmap *bitmap, + PangoMatrix *matrix, + int x, + int y, + int width, + int height); + #endif /* __PANGOFT2_PRIVATE_H__ */ diff --git a/pango/pangoft2-render.c b/pango/pangoft2-render.c new file mode 100644 index 00000000..2b809fb7 --- /dev/null +++ b/pango/pangoft2-render.c @@ -0,0 +1,459 @@ +/* Pango + * pangoft2-render.c: Rendering routines to FT_Bitmap objects + * + * Copyright (C) 2004 Red Hat Software + * Copyright (C) 2000 Tor Lillqvist + * + * This library 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 library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include "pangoft2-private.h" + +typedef struct { + double y; + double x1; + double x2; +} Position; + +static void +draw_simple_trap (FT_Bitmap *bitmap, + Position *t, + Position *b) +{ + int iy = floor (t->y); + int x1, x2, x; + double dy = b->y - t->y; + guchar *dest; + + if (iy < 0 || iy >= bitmap->rows) + return; + dest = bitmap->buffer + iy * bitmap->pitch; + + if (t->x1 < b->x1) + x1 = floor (t->x1); + else + x1 = floor (b->x1); + + if (t->x2 > b->x2) + x2 = ceil (t->x2); + else + x2 = ceil (b->x2); + + x1 = CLAMP (x1, 0, bitmap->width); + x2 = CLAMP (x2, 0, bitmap->width); + + for (x = x1; x < x2; x++) + { + double top_left = MAX (t->x1, x); + double top_right = MIN (t->x2, x + 1); + double bottom_left = MAX (b->x1, x); + double bottom_right = MIN (b->x2, x + 1); + double c = 0.5 * dy * ((top_right - top_left) + (bottom_right - bottom_left)); + + /* When converting to [0,255], we round up. This is intended + * to prevent the problem of pixels that get divided into + * multiple slices not being fully black. + */ + int ic = c * 256; + + dest[x] = MIN (dest[x] + ic, 255); + } +} + +static void +interpolate_position (Position *result, + Position *top, + Position *bottom, + double val, + double val1, + double val2) +{ + result->y = (top->y * (val2 - val) + bottom->y * (val - val1)) / (val2 - val1); + result->x1 = (top->x1 * (val2 - val) + bottom->x1 * (val - val1)) / (val2 - val1); + result->x2 = (top->x2 * (val2 - val) + bottom->x2 * (val - val1)) / (val2 - val1); +} + +/* This draws a trapezoid with the parallel sides aligned with + * the X axis. We do this by subdividing the trapezoid vertically + * into thin slices (themselves trapezoids) where two edge sides are each + * contained within a single pixel and then rasterizing each + * slice. There are frequently multiple slices within a single + * line so we have to accumulate to get the final result. + */ +static void +draw_trap (FT_Bitmap *bitmap, + double y1, + double x11, + double x21, + double y2, + double x12, + double x22) +{ + Position pos; + Position t; + Position b; + gboolean done = FALSE; + + if (y1 == y2) + return; + + pos.y = t.y = y1; + pos.x1 = t.x1 = x11; + pos.x2 = t.x2 = x21; + b.y = y2; + b.x1 = x12; + b.x2 = x22; + + while (!done) + { + Position pos_next; + double y_next, x1_next, x2_next; + double ix1, ix2; + + /* The algorithm here is written to emphasize simplicity and + * numerical stability as opposed to speed. + * + * While the end result is slicing up the polygon vertically, + * conceptually we aren't walking in the X direction, rather we + * are walking along the edges. When we compute crossing of + * horizontal pixel boundaries, we use the X coordinate as the + * interpolating variable, when we compute crossing for vertical + * pixel boundaries, we use the Y coordinate. + * + * This allows us to handle almost exactly horizontal edges without + * running into difficulties. (Almost exactly horizontal edges + * come up frequently due to inexactness in computing, say, + * a 90 degree rotation transformation) + */ + + pos_next = b; + done = TRUE; + + /* Check for crossing vertical pixel boundaries */ + y_next = floor (pos.y) + 1; + if (y_next < pos_next.y) + { + interpolate_position (&pos_next, &t, &b, + y_next, t.y, b.y); + pos_next.y = y_next; + done = FALSE; + } + + /* Check left side for crossing horizontal pixel boundaries */ + ix1 = floor (pos.x1); + + if (b.x1 < t.x1) + { + if (ix1 == pos.x1) + x1_next = ix1 - 1; + else + x1_next = ix1; + + if (x1_next > pos_next.x1) + { + interpolate_position (&pos_next, &t, &b, + x1_next, t.x1, b.x1); + pos_next.x1 = x1_next; + done = FALSE; + } + } + else if (b.x1 > t.x1) + { + x1_next = ix1 + 1; + + if (x1_next < pos_next.x1) + { + interpolate_position (&pos_next, &t, &b, + x1_next, t.x1, b.x1); + pos_next.x1 = x1_next; + done = FALSE; + } + } + + /* Check right side for crossing horizontal pixel boundaries */ + ix2 = floor (pos.x2); + + if (b.x2 < t.x2) + { + if (ix2 == pos.x2) + x2_next = ix2 - 1; + else + x2_next = ix2; + + if (x2_next > pos_next.x2) + { + interpolate_position (&pos_next, &t, &b, + x2_next, t.x2, b.x2); + pos_next.x2 = x2_next; + done = FALSE; + } + } + else if (x22 > x21) + { + x2_next = ix2 + 1; + + if (x2_next < pos_next.x2) + { + interpolate_position (&pos_next, &t, &b, + x2_next, t.x2, b.x2); + pos_next.x2 = x2_next; + done = FALSE; + } + } + + draw_simple_trap (bitmap, &pos, &pos_next); + pos = pos_next; + } +} + +typedef struct +{ + double x, y; +} Point; + +static void +to_device (PangoMatrix *matrix, + double x, + double y, + Point *result) +{ + result->x = (x * matrix->xx + y * matrix->xy) / PANGO_SCALE + matrix->x0; + result->y = (x * matrix->yx + y * matrix->yy) / PANGO_SCALE + matrix->y0; +} + +int +compare_points (const void *a, + const void *b) +{ + const Point *pa = a; + const Point *pb = b; + + if (pa->y < pb->y) + return -1; + else if (pa->y > pb->y) + return 1; + else if (pa->x < pb->x) + return -1; + else if (pa->x > pb->x) + return 1; + else + return 0; +} + +/** + * _pango_ft2_draw_rect: + * @bitmap: a #FT_Bitmap + * @matrix: a #PangoMatrix giving the user to device transformation + * @x_offset: X offset in device coordinates to add onto transformation result + * @y_offset: Y offset in device coordinates to add onto transformation result + * @x: X coordinate of rectangle, in Pango units in user coordinate system + * @y: Y coordinate of rectangle, in Pango units in user coordinate system + * @width: width of rectangle, in Pango units in user coordinate system + * @height: height of rectangle, in Pango units in user coordinate system + * + * Render an axis aligned rectangle in user coordinates onto + * a bitmap after transformation by the given matrix. Rendering + * is done anti-aliased. + **/ +void +_pango_ft2_draw_rect (FT_Bitmap *bitmap, + PangoMatrix *matrix, + int x, + int y, + int width, + int height) +{ + Point points[4]; + + /* Convert the points to device coordinates, and sort + * in ascending Y order. (Ordering by X for ties) + */ + to_device (matrix, x, y, &points[0]); + to_device (matrix, x + width, y, &points[1]); + to_device (matrix, x, y + height, &points[2]); + to_device (matrix, x + width, y + height, &points[3]); + + qsort (points, 4, sizeof (Point), compare_points); + + /* There are essentially three cases. (There is a fourth + * case where trapezoid B is degenerate and we just have + * two triangles, but we don't need to handle it separately.) + * + * 1 2 3 + * + * ______ /\ /\ + * / / /A \ /A \ + * / B / /____\ /____\ + * /_____/ / B / \ B \ + * /_____/ \_____\ + * \ C / \ C / + * \ / \ / + * \/ \/ + */ + if (points[0].y == points[1].y) + { + /* Case 1 (pure shear) */ + draw_trap (bitmap, /* B */ + points[0].y, points[0].x, points[1].x, + points[2].y, points[2].x, points[3].x); + } + else if (points[1].x < points[2].x) + { + /* Case 2 */ + double tmp_width = ((points[2].x - points[0].x) * (points[1].y - points[0].y)) / (points[2].y - points[0].y); + double base_width = tmp_width + points[0].x - points[1].x; + + draw_trap (bitmap, /* A */ + points[0].y, points[0].x, points[0].x, + points[1].y, points[1].x, points[1].x + base_width); + draw_trap (bitmap, + points[1].y, points[1].x, points[1].x + base_width, /* B */ + points[2].y, points[2].x - base_width, points[2].x); + draw_trap (bitmap, + points[2].y, points[2].x - base_width, points[2].x, /* C */ + points[3].y, points[3].x, points[3].x); + } + else + { + /* case 3 */ + double tmp_width = ((points[0].x - points[2].x) * (points[1].y - points[0].y)) / (points[2].y - points[0].y); + double base_width = tmp_width + points[1].x - points[0].x; + + draw_trap (bitmap, /* A */ + points[0].y, points[0].x, points[0].x, + points[1].y, points[1].x - base_width, points[1].x); + draw_trap (bitmap, + points[1].y, points[1].x - base_width, points[1].x, /* B */ + points[2].y, points[2].x, points[2].x + base_width); + draw_trap (bitmap, + points[2].y, points[2].x, points[2].x + base_width, /* C */ + points[3].y, points[3].x, points[3].x); + } +} + +/* We are drawing an error underline that looks like one of: + * + * /\ /\ /\ /\ /\ - + * / \ / \ / \ / \ / \ | + * \ \ /\ \ / / \ \ /\ \ | + * \ \/B \ \/ C / \ \/B \ \ | height = HEIGHT_SQUARES * square + * \ A \ /\ A \ / \ A \ /\ A \ | + * \ \/ \ \/ \ \/ \ \ | + * \ / \ / \ / \ / | + * \/ \/ \/ \/ - + * + * |----| + * unit_width = (HEIGHT_SQUARES - 1) * square + * + * To do this conveniently, we work in a coordinate system where A,B,C + * are axis aligned rectangles. (If fonts were square, the diagrams + * would be clearer) + * + * (0,0) + * /\ /\ + * / \ / \ + * /\ /\ /\ / + * / \/ \/ \/ + * / \ /\ / + * Y axis \/ \/ + * \ /\ + * \/ \ + * \ X axis + * + * Note that the long side in this coordinate system is HEIGHT_SQUARES + 1 + * units long + * + * The diagrams above are shown with HEIGHT_SQUARES an integer, but + * that is actually incidental; the value 2.5 below seems better than + * either HEIGHT_SQUARES=3 (a little long and skinny) or + * HEIGHT_SQUARES=2 (a bit short and stubby) + */ + +#define HEIGHT_SQUARES 2.5 + +static void +get_total_matrix (PangoMatrix *total, + PangoMatrix *global, + int x, + int y, + int square) +{ + PangoMatrix local; + gdouble scale = 0.5 * square; + + /* The local matrix translates from the axis aligned coordinate system + * to the original user space coordinate system. + */ + local.xx = scale; + local.xy = - scale; + local.yx = scale; + local.yy = scale; + local.x0 = 0; + local.y0 = 0; + + *total = *global; + pango_matrix_concat (total, &local); + + total->x0 = (global->xx * x + global->xy * y) / PANGO_SCALE + global->x0; + total->y0 = (global->yx * x + global->yy * y) / PANGO_SCALE + global->y0; +} + +void +_pango_ft2_draw_error_underline (FT_Bitmap *bitmap, + PangoMatrix *matrix, + int x, + int y, + int width, + int height) +{ + + int square = height / HEIGHT_SQUARES; + int unit_width = (HEIGHT_SQUARES - 1) * square; + int width_units = (width + unit_width / 2) / unit_width; + + x += (width - width_units * unit_width) / 2; + width = width_units * unit_width; + + while (TRUE) + { + PangoMatrix total; + get_total_matrix (&total, matrix, x, y, square); + + _pango_ft2_draw_rect (bitmap, &total, /* A */ + 0, 0, + HEIGHT_SQUARES * 2 - 1, 1); + + if (width_units > 2) + { + _pango_ft2_draw_rect (bitmap, &total, + HEIGHT_SQUARES * 2 - 2, - (HEIGHT_SQUARES * 2 - 3), + 1, HEIGHT_SQUARES * 2 - 3); /* B */ + width_units -= 2; + x += unit_width * 2; + } + else if (width_units == 2) + { + _pango_ft2_draw_rect (bitmap, &total, + HEIGHT_SQUARES * 2 - 2, - (HEIGHT_SQUARES * 2 - 2), + 1, HEIGHT_SQUARES * 2 - 2); /* C */ + break; + } + else + break; + } +} diff --git a/pango/pangoft2.c b/pango/pangoft2.c index 4693a9f3..5de9ca7e 100644 --- a/pango/pangoft2.c +++ b/pango/pangoft2.c @@ -158,6 +158,25 @@ load_fallback_face (PangoFT2Font *ft2font, FcPatternDestroy (matched); } +static void +set_transform (PangoFT2Font *ft2font) +{ + PangoFcFont *fcfont = (PangoFcFont *)ft2font; + FcMatrix *fc_matrix; + + if (FcPatternGetMatrix (fcfont->font_pattern, FC_MATRIX, 0, &fc_matrix) == FcResultMatch) + { + FT_Matrix ft_matrix; + + ft_matrix.xx = 0x10000L * fc_matrix->xx; + ft_matrix.yy = 0x10000L * fc_matrix->yy; + ft_matrix.xy = 0x10000L * fc_matrix->xy; + ft_matrix.yx = 0x10000L * fc_matrix->yx; + + FT_Set_Transform (ft2font->face, &ft_matrix, NULL); + } +} + /** * pango_ft2_font_get_face: * @font: a #PangoFont @@ -228,6 +247,8 @@ pango_ft2_font_get_face (PangoFont *font) g_assert (ft2font->face); + set_transform (ft2font); + error = FT_Set_Char_Size (ft2font->face, PANGO_PIXELS_26_6 (ft2font->size), PANGO_PIXELS_26_6 (ft2font->size), @@ -337,23 +358,45 @@ pango_ft2_font_render_glyph (PangoFont *font, return rendered; } +static void +transform_point (PangoMatrix *matrix, + int x, + int y, + int *x_out_pixels, + int *y_out_pixels) +{ + double x_out = (matrix->xx * x + matrix->xy * y) / PANGO_SCALE + matrix->x0; + double y_out = (matrix->yx * x + matrix->yy * y) / PANGO_SCALE + matrix->y0; + + *x_out_pixels = floor (x_out + 0.5); + *y_out_pixels = floor (y_out + 0.5); +} /** - * pango_ft2_render: + * pango_ft2_render_transformed: * @bitmap: the FreeType2 bitmap onto which to draw the string * @font: the font in which to draw the string + * @matrix: a #PangoMatrix, or %NULL to use an identity transformation * @glyphs: the glyph string to draw - * @x: the x position of the start of the string (in pixels) - * @y: the y position of the baseline (in pixels) + * @x: the x position of the start of the string (in Pango + * units in user space coordinates) + * @y: the y position of the baseline (in Pango units + * in user space coordinates) * - * Renders a PangoGlyphString onto a FreeType2 bitmap. + * Renders a #PangoGlyphString onto a FreeType2 bitmap, possibly + * transforming the layed-out coordinates through a transformation + * matrix. Note that the transformation matrix for @font is not + * changed, so to produce correct rendering results, the @font + * must have been loaded using a #PangoContext with an identical + * transformation matrix to that passed in to this function. **/ void -pango_ft2_render (FT_Bitmap *bitmap, - PangoFont *font, - PangoGlyphString *glyphs, - int x, - int y) +pango_ft2_render_transformed (FT_Bitmap *bitmap, + PangoMatrix *matrix, + PangoFont *font, + PangoGlyphString *glyphs, + int x, + int y) { FT_UInt glyph_index; int i; @@ -384,9 +427,19 @@ pango_ft2_render (FT_Bitmap *bitmap, rendered_glyph = pango_ft2_font_render_glyph (font, glyph_index); add_glyph_to_cache = TRUE; } - - ixoff = x + PANGO_PIXELS (x_position + gi->geometry.x_offset); - iyoff = y + PANGO_PIXELS (gi->geometry.y_offset); + + if (matrix) + { + transform_point (matrix, + x + x_position + gi->geometry.x_offset, + y + gi->geometry.y_offset, + &ixoff, &iyoff); + } + else + { + ixoff = PANGO_PIXELS (x + x_position + gi->geometry.x_offset); + iyoff = PANGO_PIXELS (y + gi->geometry.y_offset); + } x_start = MAX (0, - (ixoff + rendered_glyph->bitmap_left)); x_limit = MIN (rendered_glyph->bitmap.width, @@ -483,6 +536,26 @@ pango_ft2_render (FT_Bitmap *bitmap, } } +/** + * pango_ft2_render: + * @bitmap: the FreeType2 bitmap onto which to draw the string + * @font: the font in which to draw the string + * @glyphs: the glyph string to draw + * @x: the x position of the start of the string (in pixels) + * @y: the y position of the baseline (in pixels) + * + * Renders a #PangoGlyphString onto a FreeType2 bitmap. + **/ +void +pango_ft2_render (FT_Bitmap *bitmap, + PangoFont *font, + PangoGlyphString *glyphs, + int x, + int y) +{ + pango_ft2_render_transformed (bitmap, NULL, font, glyphs, x * PANGO_SCALE, y * PANGO_SCALE); +} + static FT_Glyph_Metrics * pango_ft2_get_per_char (PangoFont *font, guint32 glyph_index) @@ -715,59 +788,113 @@ pango_ft2_get_unknown_glyph (PangoFont *font) return 0; } - static void -pango_ft2_draw_hline (FT_Bitmap *bitmap, - int y, - int start, - int end) +draw_underline (FT_Bitmap *bitmap, + PangoMatrix *matrix, + PangoFontMetrics *metrics, + PangoUnderline uline, + int x, + int width, + int base_y, + int descent) { - unsigned char *p; - int ix; + int underline_thickness = pango_font_metrics_get_underline_thickness (metrics); + int underline_position = pango_font_metrics_get_underline_position (metrics); + int y_off = 0; /* Quiet GCC */ - if (y < 0 || y >= bitmap->rows) - return; + switch (uline) + { + case PANGO_UNDERLINE_NONE: + g_assert_not_reached(); + break; + case PANGO_UNDERLINE_SINGLE: + y_off = - underline_position; + break; + case PANGO_UNDERLINE_DOUBLE: + y_off = - underline_position; + break; + case PANGO_UNDERLINE_LOW: + y_off = underline_thickness + descent; + break; + case PANGO_UNDERLINE_ERROR: + { + _pango_ft2_draw_error_underline (bitmap, matrix, + x, + base_y - underline_position, + width, + 3 * underline_thickness); + return; + } + } - if (end <= 0 || start >= bitmap->width) - return; - - if (start < 0) - start = 0; + _pango_ft2_draw_rect (bitmap, matrix, + x, + base_y + y_off, + width, + underline_thickness); - if (end >= bitmap->width) - end = bitmap->width; + if (uline == PANGO_UNDERLINE_DOUBLE) + { + y_off += 2 * underline_thickness; + + _pango_ft2_draw_rect (bitmap, matrix, + x, + base_y + y_off, + width, + underline_thickness); + } +} - p = bitmap->buffer + y * bitmap->pitch + start; - - for (ix = 0; ix < end - start; ix++) - *p++ = 0xff; +static void +draw_strikethrough (FT_Bitmap *bitmap, + PangoMatrix *matrix, + PangoFontMetrics *metrics, + int x, + int width, + int base_y) +{ + int strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness (metrics); + int strikethrough_position = pango_font_metrics_get_strikethrough_position (metrics); + + _pango_ft2_draw_rect (bitmap, matrix, + x, + base_y - strikethrough_position, + width, + strikethrough_thickness); } /** - * pango_ft2_render_layout_line: + * pango_ft2_render_layout_line_subpixel: * @bitmap: a FT_Bitmap to render the line onto * @line: a #PangoLayoutLine - * @x: the x position of start of string (in pixels) - * @y: the y position of baseline (in pixels) + * @x: the x position of start of string (in pango units) + * @y: the y position of baseline (in pango units) * - * Render a #PangoLayoutLine onto a FreeType2 bitmap + * Render a #PangoLayoutLine onto a FreeType2 bitmap, with he + * location specified in fixed-point pango units rather than + * pixels. (Using this will avoid extra inaccuracies from + * rounding to integer pixels multiple times, even if the + * final glyph positions are integers.) */ void -pango_ft2_render_layout_line (FT_Bitmap *bitmap, - PangoLayoutLine *line, - int x, - int y) +pango_ft2_render_layout_line_subpixel (FT_Bitmap *bitmap, + PangoLayoutLine *line, + int x, + int y) { GSList *tmp_list = line->runs; PangoRectangle logical_rect; PangoRectangle ink_rect; int x_off = 0; + PangoMatrix *matrix; + + matrix = pango_context_get_matrix (pango_layout_get_context (line->layout)); while (tmp_list) { PangoUnderline uline = PANGO_UNDERLINE_NONE; gboolean strike, shape_set; - gint rise, risen_y; + gint rise; PangoLayoutRun *run = tmp_list->data; tmp_list = tmp_list->next; @@ -776,92 +903,84 @@ pango_ft2_render_layout_line (FT_Bitmap *bitmap, &uline, &strike, &rise, &shape_set, &ink_rect, &logical_rect); - risen_y = y - PANGO_PIXELS (rise); - if (!shape_set) { - if (uline == PANGO_UNDERLINE_NONE) + if (uline == PANGO_UNDERLINE_NONE && !strike) pango_glyph_string_extents (run->glyphs, run->item->analysis.font, NULL, &logical_rect); else pango_glyph_string_extents (run->glyphs, run->item->analysis.font, &ink_rect, &logical_rect); + + pango_ft2_render_transformed (bitmap, matrix, + run->item->analysis.font, run->glyphs, + x + x_off, y - rise); - pango_ft2_render (bitmap, run->item->analysis.font, run->glyphs, - x + PANGO_PIXELS (x_off), risen_y); - } + if (uline != PANGO_UNDERLINE_NONE || strike) + { + PangoFontMetrics *metrics = pango_font_get_metrics (run->item->analysis.font, + run->item->analysis.language); + + if (uline != PANGO_UNDERLINE_NONE) + draw_underline (bitmap, matrix, metrics, + uline, + x_off + ink_rect.x, + ink_rect.width, + y - rise, + ink_rect.y + ink_rect.height); + + if (strike) + draw_strikethrough (bitmap, matrix, metrics, + x_off + ink_rect.x, + ink_rect.width, + y - rise); - switch (uline) - { - case PANGO_UNDERLINE_NONE: - break; - case PANGO_UNDERLINE_DOUBLE: - pango_ft2_draw_hline (bitmap, - risen_y + 4, - x + PANGO_PIXELS (x_off + ink_rect.x), - x + PANGO_PIXELS (x_off + ink_rect.x + ink_rect.width)); - /* Fall through */ - case PANGO_UNDERLINE_SINGLE: - pango_ft2_draw_hline (bitmap, - risen_y + 2, - x + PANGO_PIXELS (x_off + ink_rect.x), - x + PANGO_PIXELS (x_off + ink_rect.x + ink_rect.width)); - break; - case PANGO_UNDERLINE_ERROR: - { - int point_x; - int counter = 0; - int end_x = x + PANGO_PIXELS (x_off + ink_rect.x + ink_rect.width); - - for (point_x = x + PANGO_PIXELS (x_off + ink_rect.x) - 1; - point_x <= end_x; - point_x += 2) - { - if (counter) - pango_ft2_draw_hline (bitmap, - risen_y + 2, - point_x, MIN (point_x + 1, end_x)); - else - pango_ft2_draw_hline (bitmap, - risen_y + 3, - point_x, MIN (point_x + 1, end_x)); - - counter = (counter + 1) % 2; - } - } - break; - case PANGO_UNDERLINE_LOW: - pango_ft2_draw_hline (bitmap, - risen_y + PANGO_PIXELS (ink_rect.y + ink_rect.height), - x + PANGO_PIXELS (x_off + ink_rect.x), - x + PANGO_PIXELS (x_off + ink_rect.x + ink_rect.width)); - break; + pango_font_metrics_unref (metrics); + } } - if (strike) - pango_ft2_draw_hline (bitmap, - risen_y + PANGO_PIXELS (logical_rect.y + logical_rect.height / 2), - x + PANGO_PIXELS (x_off + logical_rect.x), - x + PANGO_PIXELS (x_off + logical_rect.x + logical_rect.width)); - x_off += logical_rect.width; } } + /** - * pango_ft2_render_layout: + * pango_ft2_render_layout_line: + * @bitmap: a FT_Bitmap to render the line onto + * @line: a #PangoLayoutLine + * @x: the x position of start of string (in pixels) + * @y: the y position of baseline (in pixels) + * + * Render a #PangoLayoutLine onto a FreeType2 bitmap + */ +void +pango_ft2_render_layout_line (FT_Bitmap *bitmap, + PangoLayoutLine *line, + int x, + int y) +{ + pango_ft2_render_layout_line_subpixel (bitmap, line, x * PANGO_SCALE, y * PANGO_SCALE); +} + + +/** + * pango_ft2_render_layout_subpixel: * @bitmap: a FT_Bitmap to render the layout onto * @layout: a #PangoLayout - * @x: the X position of the left of the layout (in pixels) - * @y: the Y position of the top of the layout (in pixels) + * @x: the X position of the left of the layout (in Pango units) + * @y: the Y position of the top of the layout (in Pango units) * - * Render a #PangoLayout onto a FreeType2 bitmap + * Render a #PangoLayout onto a FreeType2 bitmap, with he + * location specified in fixed-point pango units rather than + * pixels. (Using this will avoid extra inaccuracies from + * rounding to integer pixels multiple times, even if the + * final glyph positions are integers.) */ void -pango_ft2_render_layout (FT_Bitmap *bitmap, - PangoLayout *layout, - int x, - int y) +pango_ft2_render_layout_subpixel (FT_Bitmap *bitmap, + PangoLayout *layout, + int x, + int y) { PangoLayoutIter *iter; @@ -880,17 +999,35 @@ pango_ft2_render_layout (FT_Bitmap *bitmap, pango_layout_iter_get_line_extents (iter, NULL, &logical_rect); baseline = pango_layout_iter_get_baseline (iter); - - pango_ft2_render_layout_line (bitmap, - line, - x + PANGO_PIXELS (logical_rect.x), - y + PANGO_PIXELS (baseline)); + + pango_ft2_render_layout_line_subpixel (bitmap, + line, + x + logical_rect.x, + y + baseline); } while (pango_layout_iter_next_line (iter)); pango_layout_iter_free (iter); } +/** + * pango_ft2_render_layout: + * @bitmap: a FT_Bitmap to render the layout onto + * @layout: a #PangoLayout + * @x: the X position of the left of the layout (in pixels) + * @y: the Y position of the top of the layout (in pixels) + * + * Render a #PangoLayout onto a FreeType2 bitmap + */ +void +pango_ft2_render_layout (FT_Bitmap *bitmap, + PangoLayout *layout, + int x, + int y) +{ + pango_ft2_render_layout_subpixel (bitmap, layout, x * PANGO_SCALE, y * PANGO_SCALE); +} + /* This utility function is duplicated here and in pango-layout.c; should it be * public? Trouble is - what is the appropriate set of properties? */ diff --git a/pango/pangoft2.h b/pango/pangoft2.h index e8ba820f..16386c6d 100644 --- a/pango/pangoft2.h +++ b/pango/pangoft2.h @@ -45,20 +45,33 @@ typedef void (*PangoFT2SubstituteFunc) (FcPattern *pattern, /* Calls for applications */ - -void pango_ft2_render (FT_Bitmap *bitmap, - PangoFont *font, - PangoGlyphString *glyphs, - gint x, - gint y); -void pango_ft2_render_layout_line (FT_Bitmap *bitmap, - PangoLayoutLine *line, - int x, - int y); -void pango_ft2_render_layout (FT_Bitmap *bitmap, - PangoLayout *layout, - int x, - int y); +void pango_ft2_render (FT_Bitmap *bitmap, + PangoFont *font, + PangoGlyphString *glyphs, + gint x, + gint y); +void pango_ft2_render_transformed (FT_Bitmap *bitmap, + PangoMatrix *matrix, + PangoFont *font, + PangoGlyphString *glyphs, + int x, + int y); +void pango_ft2_render_layout_line (FT_Bitmap *bitmap, + PangoLayoutLine *line, + int x, + int y); +void pango_ft2_render_layout_line_subpixel (FT_Bitmap *bitmap, + PangoLayoutLine *line, + int x, + int y); +void pango_ft2_render_layout (FT_Bitmap *bitmap, + PangoLayout *layout, + int x, + int y); +void pango_ft2_render_layout_subpixel (FT_Bitmap *bitmap, + PangoLayout *layout, + int x, + int y); GType pango_ft2_font_map_get_type (void); -- cgit v1.2.1