doc: update copyright line for 2021
[adg.git] / src / adg / adg-text.c
blobd7f0d00dfcd998abbd485e8c983ad73d4c087c95
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 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)
71 G_ADD_PRIVATE(AdgText))
73 enum {
74 PROP_0,
75 PROP_FONT_DRESS,
76 PROP_TEXT
80 static void _adg_dispose (GObject *object);
81 static void _adg_finalize (GObject *object);
82 static void _adg_get_property (GObject *object,
83 guint param_id,
84 GValue *value,
85 GParamSpec *pspec);
86 static void _adg_set_property (GObject *object,
87 guint param_id,
88 const GValue *value,
89 GParamSpec *pspec);
90 static void _adg_global_changed (AdgEntity *entity);
91 static void _adg_local_changed (AdgEntity *entity);
92 static void _adg_invalidate (AdgEntity *entity);
93 static void _adg_arrange (AdgEntity *entity);
94 static void _adg_render (AdgEntity *entity,
95 cairo_t *cr);
96 static void _adg_set_font_dress (AdgTextual *textual,
97 AdgDress dress);
98 static AdgDress _adg_get_font_dress (AdgTextual *textual);
99 static void _adg_set_text (AdgTextual *textual,
100 const gchar *text);
101 static gchar * _adg_dup_text (AdgTextual *textual);
102 static void _adg_refresh_extents (AdgText *text);
103 static void _adg_clear_layout (AdgText *text);
106 static void
107 adg_text_class_init(AdgTextClass *klass)
109 GObjectClass *gobject_class;
110 AdgEntityClass *entity_class;
112 gobject_class = (GObjectClass *) klass;
113 entity_class = (AdgEntityClass *) klass;
115 gobject_class->dispose = _adg_dispose;
116 gobject_class->finalize = _adg_finalize;
117 gobject_class->get_property = _adg_get_property;
118 gobject_class->set_property = _adg_set_property;
120 entity_class->global_changed = _adg_global_changed;
121 entity_class->local_changed = _adg_local_changed;
122 entity_class->invalidate = _adg_invalidate;
123 entity_class->arrange = _adg_arrange;
124 entity_class->render = _adg_render;
126 g_object_class_override_property(gobject_class, PROP_FONT_DRESS, "font-dress");
127 g_object_class_override_property(gobject_class, PROP_TEXT, "text");
130 static void
131 _adg_iface_init(AdgTextualIface *iface)
133 iface->set_font_dress = _adg_set_font_dress;
134 iface->get_font_dress = _adg_get_font_dress;
135 iface->set_text = _adg_set_text;
136 iface->dup_text = _adg_dup_text;
137 iface->text_changed = NULL;
140 static void
141 adg_text_init(AdgText *text)
143 AdgTextPrivate *data = adg_text_get_instance_private(text);
144 data->font_dress = ADG_DRESS_FONT_TEXT;
145 data->text = NULL;
146 data->layout = NULL;
147 adg_entity_set_local_mix((AdgEntity *) text, ADG_MIX_ANCESTORS_NORMALIZED);
150 static void
151 _adg_dispose(GObject *object)
153 AdgText *text = (AdgText *) object;
155 _adg_clear_layout(text);
157 if (_ADG_OLD_OBJECT_CLASS->dispose)
158 _ADG_OLD_OBJECT_CLASS->dispose(object);
161 static void
162 _adg_finalize(GObject *object)
164 AdgTextPrivate *data = adg_text_get_instance_private((AdgText *) object);
166 g_free(data->text);
168 if (_ADG_OLD_OBJECT_CLASS->finalize)
169 _ADG_OLD_OBJECT_CLASS->finalize(object);
172 static void
173 _adg_get_property(GObject *object, guint prop_id,
174 GValue *value, GParamSpec *pspec)
176 AdgTextPrivate *data = adg_text_get_instance_private((AdgText *) object);
178 switch (prop_id) {
179 case PROP_FONT_DRESS:
180 g_value_set_enum(value, data->font_dress);
181 break;
182 case PROP_TEXT:
183 g_value_set_string(value, data->text);
184 break;
185 default:
186 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
187 break;
191 static void
192 _adg_set_property(GObject *object, guint prop_id,
193 const GValue *value, GParamSpec *pspec)
195 AdgText *text = (AdgText *) object;
196 AdgTextPrivate *data = adg_text_get_instance_private(text);
198 switch (prop_id) {
199 case PROP_FONT_DRESS:
200 data->font_dress = g_value_get_enum(value);
201 _adg_clear_layout(text);
202 break;
203 case PROP_TEXT:
204 g_free(data->text);
205 data->text = g_value_dup_string(value);
206 _adg_clear_layout(text);
207 break;
208 default:
209 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
210 break;
216 * adg_text_new:
217 * @text: the text
219 * Creates a new text entity using @text as its content.
221 * Returns: the newly created text entity
223 * Since: 1.0
225 AdgText *
226 adg_text_new(const gchar *text)
228 return g_object_new(ADG_TYPE_TEXT,
229 "text", text, NULL);
233 static void
234 _adg_global_changed(AdgEntity *entity)
236 if (_ADG_OLD_ENTITY_CLASS->global_changed != NULL)
237 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
239 _adg_refresh_extents((AdgText *) entity);
242 static void
243 _adg_local_changed(AdgEntity *entity)
245 if (_ADG_OLD_ENTITY_CLASS->local_changed != NULL)
246 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
248 _adg_refresh_extents((AdgText *) entity);
251 static void
252 _adg_invalidate(AdgEntity *entity)
254 _adg_clear_layout((AdgText *) entity);
256 if (_ADG_OLD_ENTITY_CLASS->invalidate)
257 _ADG_OLD_ENTITY_CLASS->invalidate(entity);
260 static void
261 _adg_arrange(AdgEntity *entity)
263 AdgText *text = (AdgText *) entity;
264 AdgTextPrivate *data = adg_text_get_instance_private(text);
265 PangoRectangle size;
267 if (adg_is_string_empty(data->text)) {
268 /* Undefined text */
269 CpmlExtents new_extents;
270 new_extents.is_defined = FALSE;
271 adg_entity_set_extents(entity, &new_extents);
272 _adg_clear_layout(text);
273 return;
274 } else if (data->layout != NULL) {
275 /* Cached result */
276 return;
279 if (data->layout == NULL) {
280 static PangoFontMap *font_map = NULL;
281 AdgDress dress;
282 AdgPangoStyle *pango_style;
283 PangoFontDescription *font_description;
284 cairo_font_options_t *options;
285 PangoContext *context;
287 /* Keep around the font_map object. The rationale is:
288 * https://bugzilla.gnome.org/show_bug.cgi?id=143542
290 * Basically, PangoFontMap is a heavy object and
291 * creating/destroying it is not the right thing to do.
293 * In reality, the blocking issue for me was the following
294 * line makes the adg-demo program crash on MinGW32:
295 * g_object_unref(font_map);
297 if (font_map == NULL)
298 font_map = pango_cairo_font_map_new();
300 dress = data->font_dress;
301 pango_style = (AdgPangoStyle *) adg_entity_style(entity, dress);
302 font_description = adg_pango_style_get_description(pango_style);
304 context = pango_context_new();
305 pango_context_set_font_map(context, font_map);
306 pango_cairo_context_set_resolution(context, 72);
308 options = adg_font_style_new_options((AdgFontStyle *) pango_style);
309 pango_cairo_context_set_font_options(context, options);
310 cairo_font_options_destroy(options);
312 data->layout = pango_layout_new(context);
313 g_object_unref(context);
315 pango_layout_set_spacing(data->layout, adg_pango_style_get_spacing(pango_style));
316 pango_layout_set_text(data->layout, data->text, -1);
317 pango_layout_set_font_description(data->layout, font_description);
320 pango_layout_get_extents(data->layout, NULL, &size);
322 data->raw_extents.org.x = pango_units_to_double(size.x);
323 data->raw_extents.org.y = pango_units_to_double(size.y);
324 data->raw_extents.size.x = pango_units_to_double(size.width);
325 data->raw_extents.size.y = pango_units_to_double(size.height);
326 data->raw_extents.is_defined = TRUE;
328 _adg_refresh_extents((AdgText *) entity);
331 static void
332 _adg_render(AdgEntity *entity, cairo_t *cr)
334 AdgText *text = (AdgText *) entity;
335 AdgTextPrivate *data = adg_text_get_instance_private(text);
337 if (data->layout != NULL) {
338 adg_entity_apply_dress(entity, data->font_dress, cr);
339 cairo_transform(cr, adg_entity_get_global_matrix(entity));
340 cairo_transform(cr, adg_entity_get_local_matrix(entity));
342 /* Realign the text to follow the cairo toy text convention:
343 * use bottom/left corner as reference (pango uses top/left). */
344 cairo_translate(cr, 0, -data->raw_extents.size.y);
346 pango_cairo_update_layout(cr, data->layout);
347 pango_cairo_show_layout(cr, data->layout);
351 static void
352 _adg_set_font_dress(AdgTextual *textual, AdgDress dress)
354 g_object_set(textual, "font-dress", dress, NULL);
357 static AdgDress
358 _adg_get_font_dress(AdgTextual *textual)
360 AdgTextPrivate *data = adg_text_get_instance_private((AdgText *) textual);
361 return data->font_dress;
364 static void
365 _adg_set_text(AdgTextual *textual, const gchar *text)
367 g_object_set(textual, "text", text, NULL);
370 static gchar *
371 _adg_dup_text(AdgTextual *textual)
373 AdgTextPrivate *data = adg_text_get_instance_private((AdgText *) textual);
374 return g_strdup(data->text);
377 static void
378 _adg_refresh_extents(AdgText *text)
380 AdgTextPrivate *data = adg_text_get_instance_private(text);
381 AdgEntity *entity;
382 cairo_matrix_t ctm;
383 CpmlExtents new_extents;
385 if (! data->raw_extents.is_defined)
386 return;
388 entity = (AdgEntity *) text;
390 adg_matrix_copy(&ctm, adg_entity_get_global_matrix(entity));
391 adg_matrix_transform(&ctm, adg_entity_get_local_matrix(entity),
392 ADG_TRANSFORM_AFTER);
393 cpml_extents_copy(&new_extents, &data->raw_extents);
395 /* Realign the text to follow the cairo toy text convention:
396 * use bottom/left corner as reference (pango uses top/left). */
397 new_extents.org.y -= new_extents.size.y;
399 cpml_extents_transform(&new_extents, &ctm);
400 adg_entity_set_extents(entity, &new_extents);
403 static void
404 _adg_clear_layout(AdgText *text)
406 AdgTextPrivate *data = adg_text_get_instance_private(text);
408 if (data->layout != NULL) {
409 g_object_unref(data->layout);
410 data->layout = NULL;