gdkpango.c 13.5 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GDK - The GIMP Drawing Kit
2 3 4
 * Copyright (C) 2000 Red Hat, Inc. 
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6 7 8 9 10 11
 * 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
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardon's avatar
Javier Jardon committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17
 */

18
#include "config.h"
19

20
#include "gdkpango.h"
21

22
#include "gdkscreen.h"
23
#include "gdkintl.h"
24

25 26 27
#include <math.h>
#include <pango/pangocairo.h>

28 29 30 31 32 33 34 35 36 37 38 39 40

/**
 * SECTION:pango_interaction
 * @Short_description: Using Pango in GDK
 * @Title: Pango Interaction
 *
 * Pango is the text layout system used by GDK and GTK+. The functions
 * and types in this section are used to obtain clip regions for
 * #PangoLayouts, and to get #PangoContexts that can be used with
 * GDK.
 *
 * Creating a #PangoLayout object is the first step in rendering text,
 * and requires getting a handle to a #PangoContext. For GTK+ programs,
41
 * you’ll usually want to use gtk_widget_get_pango_context(), or
42 43 44 45 46
 * gtk_widget_create_pango_layout(), rather than using the lowlevel
 * gdk_pango_context_get_for_screen(). Once you have a #PangoLayout, you
 * can set the text and attributes of it with Pango functions like
 * pango_layout_set_text() and get its size with pango_layout_get_size().
 * (Note that Pango uses a fixed point system internally, so converting
47 48
 * between Pango units and pixels using [PANGO_SCALE][PANGO-SCALE-CAPS]
 * or the PANGO_PIXELS() macro.)
49 50 51
 *
 * Rendering a Pango layout is done most simply with pango_cairo_show_layout();
 * you can also draw pieces of the layout with pango_cairo_show_layout_line().
52 53 54
 *
 * ## Draw transformed text with Pango and cairo ## {#rotated-example}
 *
55
 * |[<!-- language="C" -->
56 57 58 59 60 61 62 63 64 65 66 67
 * #define RADIUS 100
 * #define N_WORDS 10
 * #define FONT "Sans Bold 18"
 *
 * PangoContext *context;
 * PangoLayout *layout;
 * PangoFontDescription *desc;
 *
 * double radius;
 * int width, height;
 * int i;
 *
68 69 70
 * // Set up a transformation matrix so that the user space coordinates for
 * // where we are drawing are [-RADIUS, RADIUS], [-RADIUS, RADIUS]
 * // We first center, then change the scale
71 72 73 74 75 76 77 78 79 80
 *
 * width = gdk_window_get_width (window);
 * height = gdk_window_get_height (window);
 * radius = MIN (width, height) / 2.;
 *
 * cairo_translate (cr,
 *                  radius + (width - 2 * radius) / 2,
 *                  radius + (height - 2 * radius) / 2);
 *                  cairo_scale (cr, radius / RADIUS, radius / RADIUS);
 *
81
 * // Create a PangoLayout, set the font and text
82 83 84 85 86 87 88
 * context = gdk_pango_context_get_for_screen (screen);
 * layout = pango_layout_new (context);
 * pango_layout_set_text (layout, "Text", -1);
 * desc = pango_font_description_from_string (FONT);
 * pango_layout_set_font_description (layout, desc);
 * pango_font_description_free (desc);
 *
89
 * // Draw the layout N_WORDS times in a circle
90 91 92 93 94 95 96
 * for (i = 0; i < N_WORDS; i++)
 *   {
 *     double red, green, blue;
 *     double angle = 2 * G_PI * i / n_words;
 *
 *     cairo_save (cr);
 *
97
 *     // Gradient from red at angle == 60 to blue at angle == 300
98 99 100 101 102 103 104
 *     red = (1 + cos (angle - 60)) / 2;
 *     green = 0;
 *     blue = 1 - red;
 *
 *     cairo_set_source_rgb (cr, red, green, blue);
 *     cairo_rotate (cr, angle);
 *
105
 *     // Inform Pango to re-layout the text with the new transformation matrix
106 107 108 109 110 111 112 113 114 115 116 117
 *     pango_cairo_update_layout (cr, layout);
 *
 *     pango_layout_get_size (layout, &width, &height);
 *
 *     cairo_move_to (cr, - width / 2 / PANGO_SCALE, - DEFAULT_TEXT_RADIUS);
 *     pango_cairo_show_layout (cr, layout);
 *
 *     cairo_restore (cr);
 *   }
 *
 * g_object_unref (layout);
 * g_object_unref (context);
118
 * ]|
119
 *
120
 * ## Output of the [example][rotated-example] above.
121 122
 *
 * ![](rotated-text.png)
123 124
 */

125 126 127 128 129
/* Get a clip region to draw only part of a layout. index_ranges
 * contains alternating range starts/stops. The region is the
 * region which contains the given ranges, i.e. if you draw with the
 * region as clip, only the given ranges are drawn.
 */
130
static cairo_region_t*
131 132 133
layout_iter_get_line_clip_region (PangoLayoutIter *iter,
				  gint             x_origin,
				  gint             y_origin,
134
				  const gint      *index_ranges,
135 136 137
				  gint             n_ranges)
{
  PangoLayoutLine *line;
138
  cairo_region_t *clip_region;
139 140 141 142
  PangoRectangle logical_rect;
  gint baseline;
  gint i;

143
  line = pango_layout_iter_get_line_readonly (iter);
144

Benjamin Otte's avatar
Benjamin Otte committed
145
  clip_region = cairo_region_create ();
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

  pango_layout_iter_get_line_extents (iter, NULL, &logical_rect);
  baseline = pango_layout_iter_get_baseline (iter);

  i = 0;
  while (i < n_ranges)
    {  
      gint *pixel_ranges = NULL;
      gint n_pixel_ranges = 0;
      gint j;

      /* Note that get_x_ranges returns layout coordinates
       */
      if (index_ranges[i*2+1] >= line->start_index &&
	  index_ranges[i*2] < line->start_index + line->length)
	pango_layout_line_get_x_ranges (line,
					index_ranges[i*2],
					index_ranges[i*2+1],
					&pixel_ranges, &n_pixel_ranges);
  
      for (j = 0; j < n_pixel_ranges; j++)
        {
          GdkRectangle rect;
	  int x_off, y_off;
          
          x_off = PANGO_PIXELS (pixel_ranges[2*j] - logical_rect.x);
	  y_off = PANGO_PIXELS (baseline - logical_rect.y);

          rect.x = x_origin + x_off;
          rect.y = y_origin - y_off;
          rect.width = PANGO_PIXELS (pixel_ranges[2*j + 1] - logical_rect.x) - x_off;
          rect.height = PANGO_PIXELS (baseline - logical_rect.y + logical_rect.height) - y_off;

Benjamin Otte's avatar
Benjamin Otte committed
179
          cairo_region_union_rectangle (clip_region, &rect);
180 181 182 183 184 185 186
        }

      g_free (pixel_ranges);
      ++i;
    }
  return clip_region;
}
187 188

/**
189
 * gdk_pango_layout_line_get_clip_region: (skip)
190 191 192
 * @line: a #PangoLayoutLine 
 * @x_origin: X pixel where you intend to draw the layout line with this clip
 * @y_origin: baseline pixel where you intend to draw the layout line with this clip
193 194 195
 * @index_ranges: (array): array of byte indexes into the layout,
 *     where even members of array are start indexes and odd elements
 *     are end indexes
196 197
 * @n_ranges: number of ranges in @index_ranges, i.e. half the size of @index_ranges
 * 
198
 * Obtains a clip region which contains the areas where the given
199 200
 * ranges of text would be drawn. @x_origin and @y_origin are the top left
 * position of the layout. @index_ranges
201
 * should contain ranges of bytes in the layout’s text. The clip
202 203 204 205 206
 * region will include space to the left or right of the line (to the
 * layout bounding box) if you have indexes above or below the indexes
 * contained inside the line. This is to draw the selection all the way
 * to the side of the layout. However, the clip region is in line coordinates,
 * not layout coordinates.
207 208 209 210 211
 *
 * Note that the regions returned correspond to logical extents of the text
 * ranges, not ink extents. So the drawn line may in fact touch areas out of
 * the clip region.  The clip region is mainly useful for highlightling parts
 * of text, such as when text is selected.
212
 * 
213
 * Returns: a clip region containing the given ranges
214
 **/
215
cairo_region_t*
216 217 218
gdk_pango_layout_line_get_clip_region (PangoLayoutLine *line,
                                       gint             x_origin,
                                       gint             y_origin,
219
                                       const gint      *index_ranges,
220 221
                                       gint             n_ranges)
{
222
  cairo_region_t *clip_region;
223
  PangoLayoutIter *iter;
224 225 226 227
  
  g_return_val_if_fail (line != NULL, NULL);
  g_return_val_if_fail (index_ranges != NULL, NULL);
  
228
  iter = pango_layout_get_iter (line->layout);
229
  while (pango_layout_iter_get_line_readonly (iter) != line)
230 231
    pango_layout_iter_next_line (iter);
  
232
  clip_region = layout_iter_get_line_clip_region(iter, x_origin, y_origin, index_ranges, n_ranges);
233

234
  pango_layout_iter_free (iter);
235 236 237 238 239

  return clip_region;
}

/**
240
 * gdk_pango_layout_get_clip_region: (skip)
241 242 243 244 245 246 247
 * @layout: a #PangoLayout 
 * @x_origin: X pixel where you intend to draw the layout with this clip
 * @y_origin: Y pixel where you intend to draw the layout with this clip
 * @index_ranges: array of byte indexes into the layout, where even members of array are start indexes and odd elements are end indexes
 * @n_ranges: number of ranges in @index_ranges, i.e. half the size of @index_ranges
 * 
 * Obtains a clip region which contains the areas where the given ranges
248 249
 * of text would be drawn. @x_origin and @y_origin are the top left point
 * to center the layout. @index_ranges should contain
250
 * ranges of bytes in the layout’s text.
251
 * 
252
 * Note that the regions returned correspond to logical extents of the text
Behdad Esfahbod's avatar
Behdad Esfahbod committed
253
 * ranges, not ink extents. So the drawn layout may in fact touch areas out of
254 255 256
 * the clip region.  The clip region is mainly useful for highlightling parts
 * of text, such as when text is selected.
 * 
257
 * Returns: a clip region containing the given ranges
258
 **/
259
cairo_region_t*
260 261 262
gdk_pango_layout_get_clip_region (PangoLayout *layout,
                                  gint         x_origin,
                                  gint         y_origin,
263
                                  const gint  *index_ranges,
264 265 266
                                  gint         n_ranges)
{
  PangoLayoutIter *iter;  
267
  cairo_region_t *clip_region;
268 269 270 271
  
  g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL);
  g_return_val_if_fail (index_ranges != NULL, NULL);
  
Benjamin Otte's avatar
Benjamin Otte committed
272
  clip_region = cairo_region_create ();
273 274 275 276 277 278
  
  iter = pango_layout_get_iter (layout);
  
  do
    {
      PangoRectangle logical_rect;
279
      cairo_region_t *line_region;
280 281 282 283 284
      gint baseline;
      
      pango_layout_iter_get_line_extents (iter, NULL, &logical_rect);
      baseline = pango_layout_iter_get_baseline (iter);      

285
      line_region = layout_iter_get_line_clip_region(iter, 
286 287
						     x_origin + PANGO_PIXELS (logical_rect.x),
						     y_origin + PANGO_PIXELS (baseline),
288 289
						     index_ranges,
						     n_ranges);
290

Benjamin Otte's avatar
Benjamin Otte committed
291 292
      cairo_region_union (clip_region, line_region);
      cairo_region_destroy (line_region);
293 294 295 296 297 298 299
    }
  while (pango_layout_iter_next_line (iter));

  pango_layout_iter_free (iter);

  return clip_region;
}
300 301 302 303 304 305

/**
 * gdk_pango_context_get:
 * 
 * Creates a #PangoContext for the default GDK screen.
 *
306
 * The context must be freed when you’re finished with it.
307 308 309 310 311
 * 
 * When using GTK+, normally you should use gtk_widget_get_pango_context()
 * instead of this function, to get the appropriate context for
 * the widget you intend to render text onto.
 * 
312 313 314 315
 * The newly created context will have the default font options (see
 * #cairo_font_options_t) for the default screen; if these options
 * change it will not be updated. Using gtk_widget_get_pango_context()
 * is more convenient if you want to keep a context around and track
316
 * changes to the screen’s font rendering settings.
317
 *
318
 * Returns: (transfer full): a new #PangoContext for the default display
319 320 321 322
 **/
PangoContext *
gdk_pango_context_get (void)
{
323
  return gdk_pango_context_get_for_display (gdk_display_get_default ());
324
}
325

326 327 328 329 330 331
/**
 * gdk_pango_context_get_for_screen:
 * @screen: the #GdkScreen for which the context is to be created.
 * 
 * Creates a #PangoContext for @screen.
 *
332
 * The context must be freed when you’re finished with it.
333 334 335 336 337
 * 
 * When using GTK+, normally you should use gtk_widget_get_pango_context()
 * instead of this function, to get the appropriate context for
 * the widget you intend to render text onto.
 * 
338 339 340 341
 * The newly created context will have the default font options
 * (see #cairo_font_options_t) for the screen; if these options
 * change it will not be updated. Using gtk_widget_get_pango_context()
 * is more convenient if you want to keep a context around and track
342
 * changes to the screen’s font rendering settings.
343
 * 
344
 * Returns: (transfer full): a new #PangoContext for @screen
345 346 347 348 349 350 351
 *
 * Since: 2.2
 **/
PangoContext *
gdk_pango_context_get_for_screen (GdkScreen *screen)
{
  PangoFontMap *fontmap;
352 353
  PangoContext *context;
  const cairo_font_options_t *options;
354
  double dpi;
355

356 357
  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

358
  fontmap = pango_cairo_font_map_get_default ();
359
  context = pango_font_map_create_context (fontmap);
360

361
  options = gdk_screen_get_font_options (screen);
362 363
  pango_cairo_context_set_font_options (context, options);

364 365 366
  dpi = gdk_screen_get_resolution (screen);
  pango_cairo_context_set_resolution (context, dpi);

367
  return context;
368
}
369

Matthias Clasen's avatar
Matthias Clasen committed
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
/**
 * gdk_pango_context_get_for_display:
 * @display: the #GdkDisplay for which the context is to be created
 *
 * Creates a #PangoContext for @display.
 *
 * The context must be freed when you’re finished with it.
 *
 * When using GTK+, normally you should use gtk_widget_get_pango_context()
 * instead of this function, to get the appropriate context for
 * the widget you intend to render text onto.
 *
 * The newly created context will have the default font options
 * (see #cairo_font_options_t) for the display; if these options
 * change it will not be updated. Using gtk_widget_get_pango_context()
 * is more convenient if you want to keep a context around and track
 * changes to the font rendering settings.
 *
 * Returns: (transfer full): a new #PangoContext for @display
 *
 * Since: 3.22
 */
392 393 394 395 396 397 398
PangoContext *
gdk_pango_context_get_for_display (GdkDisplay *display)
{
  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);

  return gdk_pango_context_get_for_screen (gdk_display_get_default_screen (display));
}