adg: avoid invalid access to private data
[adg.git] / src / adg / adg-text.c
blob0df0d43ead9c7db88831e9b59320848f40fbc870
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-text
23 * @short_description: A pango based text entity
25 * The #AdgText class is the main class for showing text. It works
26 * the same way as #AdgToyText but uses pango instead of the so
27 * called cairo "toy" API.
29 * The text entity is not subject to the local matrix, only its origin is.
31 * <note><para>
32 * By default, the #AdgEntity:local-mix property is set to
33 * #ADG_MIX_ANCESTORS_NORMALIZED on #AdgText entities.
34 * </para></note>
36 * Since: 1.0
37 **/
39 /**
40 * AdgText:
42 * All fields are privates and should not be used directly.
43 * Use its public methods instead.
45 * Since: 1.0
46 **/
49 #include "adg-internal.h"
50 #include <pango/pangocairo.h>
52 #include "adg-dress.h"
53 #include "adg-style.h"
54 #include "adg-font-style.h"
55 #include "adg-pango-style.h"
56 #include "adg-textual.h"
58 #include "adg-text.h"
59 #include "adg-text-private.h"
62 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_text_parent_class)
63 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_text_parent_class)
66 static void _adg_iface_init (AdgTextualIface *iface);
69 G_DEFINE_TYPE_WITH_CODE(AdgText, adg_text, ADG_TYPE_ENTITY,
70 G_IMPLEMENT_INTERFACE(ADG_TYPE_TEXTUAL, _adg_iface_init))
72 enum {
73 PROP_0,
74 PROP_FONT_DRESS,
75 PROP_TEXT
79 static void _adg_dispose (GObject *object);
80 static void _adg_finalize (GObject *object);
81 static void _adg_get_property (GObject *object,
82 guint param_id,
83 GValue *value,
84 GParamSpec *pspec);
85 static void _adg_set_property (GObject *object,
86 guint param_id,
87 const GValue *value,
88 GParamSpec *pspec);
89 static void _adg_global_changed (AdgEntity *entity);
90 static void _adg_local_changed (AdgEntity *entity);
91 static void _adg_invalidate (AdgEntity *entity);
92 static void _adg_arrange (AdgEntity *entity);
93 static void _adg_render (AdgEntity *entity,
94 cairo_t *cr);
95 static void _adg_set_font_dress (AdgTextual *textual,
96 AdgDress dress);
97 static AdgDress _adg_get_font_dress (AdgTextual *textual);
98 static void _adg_set_text (AdgTextual *textual,
99 const gchar *text);
100 static gchar * _adg_dup_text (AdgTextual *textual);
101 static void _adg_refresh_extents (AdgText *text);
102 static void _adg_clear_layout (AdgText *text);
105 static void
106 adg_text_class_init(AdgTextClass *klass)
108 GObjectClass *gobject_class;
109 AdgEntityClass *entity_class;
111 gobject_class = (GObjectClass *) klass;
112 entity_class = (AdgEntityClass *) klass;
114 g_type_class_add_private(klass, sizeof(AdgTextPrivate));
116 gobject_class->dispose = _adg_dispose;
117 gobject_class->finalize = _adg_finalize;
118 gobject_class->get_property = _adg_get_property;
119 gobject_class->set_property = _adg_set_property;
121 entity_class->global_changed = _adg_global_changed;
122 entity_class->local_changed = _adg_local_changed;
123 entity_class->invalidate = _adg_invalidate;
124 entity_class->arrange = _adg_arrange;
125 entity_class->render = _adg_render;
127 g_object_class_override_property(gobject_class, PROP_FONT_DRESS, "font-dress");
128 g_object_class_override_property(gobject_class, PROP_TEXT, "text");
131 static void
132 _adg_iface_init(AdgTextualIface *iface)
134 iface->set_font_dress = _adg_set_font_dress;
135 iface->get_font_dress = _adg_get_font_dress;
136 iface->set_text = _adg_set_text;
137 iface->dup_text = _adg_dup_text;
138 iface->text_changed = NULL;
141 static void
142 adg_text_init(AdgText *text)
144 AdgTextPrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(text, ADG_TYPE_TEXT,
145 AdgTextPrivate);
147 data->font_dress = ADG_DRESS_FONT_TEXT;
148 data->text = NULL;
149 data->layout = NULL;
151 text->data = data;
153 adg_entity_set_local_mix((AdgEntity *) text, ADG_MIX_ANCESTORS_NORMALIZED);
156 static void
157 _adg_dispose(GObject *object)
159 AdgText *text = (AdgText *) object;
161 _adg_clear_layout(text);
163 if (_ADG_OLD_OBJECT_CLASS->dispose)
164 _ADG_OLD_OBJECT_CLASS->dispose(object);
167 static void
168 _adg_finalize(GObject *object)
170 AdgText *text;
171 AdgTextPrivate *data;
173 text = (AdgText *) object;
174 data = text->data;
176 g_free(data->text);
178 if (_ADG_OLD_OBJECT_CLASS->finalize)
179 _ADG_OLD_OBJECT_CLASS->finalize(object);
182 static void
183 _adg_get_property(GObject *object, guint prop_id,
184 GValue *value, GParamSpec *pspec)
186 AdgTextPrivate *data = ((AdgText *) object)->data;
188 switch (prop_id) {
189 case PROP_FONT_DRESS:
190 g_value_set_enum(value, data->font_dress);
191 break;
192 case PROP_TEXT:
193 g_value_set_string(value, data->text);
194 break;
195 default:
196 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
197 break;
201 static void
202 _adg_set_property(GObject *object, guint prop_id,
203 const GValue *value, GParamSpec *pspec)
205 AdgText *text;
206 AdgTextPrivate *data;
208 text = (AdgText *) object;
209 data = text->data;
211 switch (prop_id) {
212 case PROP_FONT_DRESS:
213 data->font_dress = g_value_get_enum(value);
214 _adg_clear_layout(text);
215 break;
216 case PROP_TEXT:
217 g_free(data->text);
218 data->text = g_value_dup_string(value);
219 _adg_clear_layout(text);
220 break;
221 default:
222 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
223 break;
229 * adg_text_new:
230 * @text: the text
232 * Creates a new text entity using @text as its content.
234 * Returns: the newly created text entity
236 * Since: 1.0
238 AdgText *
239 adg_text_new(const gchar *text)
241 return g_object_new(ADG_TYPE_TEXT,
242 "text", text, NULL);
246 static void
247 _adg_global_changed(AdgEntity *entity)
249 if (_ADG_OLD_ENTITY_CLASS->global_changed != NULL)
250 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
252 _adg_refresh_extents((AdgText *) entity);
255 static void
256 _adg_local_changed(AdgEntity *entity)
258 if (_ADG_OLD_ENTITY_CLASS->local_changed != NULL)
259 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
261 _adg_refresh_extents((AdgText *) entity);
264 static void
265 _adg_invalidate(AdgEntity *entity)
267 _adg_clear_layout((AdgText *) entity);
269 if (_ADG_OLD_ENTITY_CLASS->invalidate)
270 _ADG_OLD_ENTITY_CLASS->invalidate(entity);
273 static void
274 _adg_arrange(AdgEntity *entity)
276 AdgText *text;
277 AdgTextPrivate *data;
278 PangoRectangle size;
280 text = (AdgText *) entity;
281 data = text->data;
283 if (adg_is_string_empty(data->text)) {
284 /* Undefined text */
285 CpmlExtents new_extents;
286 new_extents.is_defined = FALSE;
287 adg_entity_set_extents(entity, &new_extents);
288 _adg_clear_layout(text);
289 return;
290 } else if (data->layout != NULL) {
291 /* Cached result */
292 return;
295 if (data->layout == NULL) {
296 static PangoFontMap *font_map = NULL;
297 AdgDress dress;
298 AdgPangoStyle *pango_style;
299 PangoFontDescription *font_description;
300 cairo_font_options_t *options;
301 PangoContext *context;
303 /* Keep around the font_map object. The rationale is:
304 * https://bugzilla.gnome.org/show_bug.cgi?id=143542
306 * Basically, PangoFontMap is a heavy object and
307 * creating/destroying it is not the right thing to do.
309 * In reality, the blocking issue for me was the following
310 * line makes the adg-demo program crash on MinGW32:
311 * g_object_unref(font_map);
313 if (font_map == NULL)
314 font_map = pango_cairo_font_map_new();
316 dress = data->font_dress;
317 pango_style = (AdgPangoStyle *) adg_entity_style(entity, dress);
318 font_description = adg_pango_style_get_description(pango_style);
320 context = pango_context_new();
321 pango_context_set_font_map(context, font_map);
322 pango_cairo_context_set_resolution(context, 72);
324 options = adg_font_style_new_options((AdgFontStyle *) pango_style);
325 pango_cairo_context_set_font_options(context, options);
326 cairo_font_options_destroy(options);
328 data->layout = pango_layout_new(context);
329 g_object_unref(context);
331 pango_layout_set_spacing(data->layout, adg_pango_style_get_spacing(pango_style));
332 pango_layout_set_text(data->layout, data->text, -1);
333 pango_layout_set_font_description(data->layout, font_description);
336 pango_layout_get_extents(data->layout, NULL, &size);
338 data->raw_extents.org.x = pango_units_to_double(size.x);
339 data->raw_extents.org.y = pango_units_to_double(size.y);
340 data->raw_extents.size.x = pango_units_to_double(size.width);
341 data->raw_extents.size.y = pango_units_to_double(size.height);
342 data->raw_extents.is_defined = TRUE;
344 _adg_refresh_extents((AdgText *) entity);
347 static void
348 _adg_render(AdgEntity *entity, cairo_t *cr)
350 AdgText *text;
351 AdgTextPrivate *data;
353 text = (AdgText *) entity;
354 data = text->data;
356 if (data->layout != NULL) {
357 adg_entity_apply_dress(entity, data->font_dress, cr);
358 cairo_transform(cr, adg_entity_get_global_matrix(entity));
359 cairo_transform(cr, adg_entity_get_local_matrix(entity));
361 /* Realign the text to follow the cairo toy text convention:
362 * use bottom/left corner as reference (pango uses top/left). */
363 cairo_translate(cr, 0, -data->raw_extents.size.y);
365 pango_cairo_update_layout(cr, data->layout);
366 pango_cairo_show_layout(cr, data->layout);
370 static void
371 _adg_set_font_dress(AdgTextual *textual, AdgDress dress)
373 g_object_set(textual, "font-dress", dress, NULL);
376 static AdgDress
377 _adg_get_font_dress(AdgTextual *textual)
379 AdgTextPrivate *data = ((AdgText *) textual)->data;
380 return data->font_dress;
383 static void
384 _adg_set_text(AdgTextual *textual, const gchar *text)
386 g_object_set(textual, "text", text, NULL);
389 static gchar *
390 _adg_dup_text(AdgTextual *textual)
392 AdgTextPrivate *data = ((AdgText *) textual)->data;
393 return g_strdup(data->text);
396 static void
397 _adg_refresh_extents(AdgText *text)
399 AdgTextPrivate *data;
400 AdgEntity *entity;
401 cairo_matrix_t ctm;
402 CpmlExtents new_extents;
404 data = text->data;
406 if (! data->raw_extents.is_defined)
407 return;
409 entity = (AdgEntity *) text;
411 adg_matrix_copy(&ctm, adg_entity_get_global_matrix(entity));
412 adg_matrix_transform(&ctm, adg_entity_get_local_matrix(entity),
413 ADG_TRANSFORM_AFTER);
414 cpml_extents_copy(&new_extents, &data->raw_extents);
416 /* Realign the text to follow the cairo toy text convention:
417 * use bottom/left corner as reference (pango uses top/left). */
418 new_extents.org.y -= new_extents.size.y;
420 cpml_extents_transform(&new_extents, &ctm);
421 adg_entity_set_extents(entity, &new_extents);
424 static void
425 _adg_clear_layout(AdgText *text)
427 AdgTextPrivate *data = text->data;
429 if (data->layout != NULL) {
430 g_object_unref(data->layout);
431 data->layout = NULL;