1/* Pango
2 * pango-tabs.c: Tab-related stuff
3 *
4 * Copyright (C) 2000 Red Hat Software
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
21
22#include "config.h"
23#include "pango-tabs.h"
24#include "pango-impl-utils.h"
25#include <stdlib.h>
26#include <string.h>
27
28typedef struct _PangoTab PangoTab;
29
30struct _PangoTab
31{
32 int location;
33 PangoTabAlign alignment;
34 gunichar decimal_point;
35};
36
37/**
38 * PangoTabArray:
39 *
40 * A `PangoTabArray` contains an array of tab stops.
41 *
42 * `PangoTabArray` can be used to set tab stops in a `PangoLayout`.
43 * Each tab stop has an alignment, a position, and optionally
44 * a character to use as decimal point.
45 */
46struct _PangoTabArray
47{
48 gint size;
49 gint allocated;
50 gboolean positions_in_pixels;
51 PangoTab *tabs;
52};
53
54static void
55init_tabs (PangoTabArray *array, gint start, gint end)
56{
57 while (start < end)
58 {
59 array->tabs[start].location = 0;
60 array->tabs[start].alignment = PANGO_TAB_LEFT;
61 array->tabs[start].decimal_point = 0;
62 ++start;
63 }
64}
65
66/**
67 * pango_tab_array_new:
68 * @initial_size: Initial number of tab stops to allocate, can be 0
69 * @positions_in_pixels: whether positions are in pixel units
70 *
71 * Creates an array of @initial_size tab stops.
72 *
73 * Tab stops are specified in pixel units if @positions_in_pixels is %TRUE,
74 * otherwise in Pango units. All stops are initially at position 0.
75 *
76 * Return value: the newly allocated `PangoTabArray`, which should
77 * be freed with [method@Pango.TabArray.free].
78 */
79PangoTabArray*
80pango_tab_array_new (gint initial_size,
81 gboolean positions_in_pixels)
82{
83 PangoTabArray *array;
84
85 g_return_val_if_fail (initial_size >= 0, NULL);
86
87 /* alloc enough to treat array->tabs as an array of length
88 * size, though it's declared as an array of length 1.
89 * If we allowed tab array resizing we'd need to drop this
90 * optimization.
91 */
92 array = g_slice_new (PangoTabArray);
93 array->size = initial_size; array->allocated = initial_size;
94
95 if (array->allocated > 0)
96 {
97 array->tabs = g_new (PangoTab, array->allocated);
98 init_tabs (array, start: 0, end: array->allocated);
99 }
100 else
101 array->tabs = NULL;
102
103 array->positions_in_pixels = positions_in_pixels;
104
105 return array;
106}
107
108/**
109 * pango_tab_array_new_with_positions:
110 * @size: number of tab stops in the array
111 * @positions_in_pixels: whether positions are in pixel units
112 * @first_alignment: alignment of first tab stop
113 * @first_position: position of first tab stop
114 * @...: additional alignment/position pairs
115 *
116 * Creates a `PangoTabArray` and allows you to specify the alignment
117 * and position of each tab stop.
118 *
119 * You **must** provide an alignment and position for @size tab stops.
120 *
121 * Return value: the newly allocated `PangoTabArray`, which should
122 * be freed with [method@Pango.TabArray.free].
123 */
124PangoTabArray *
125pango_tab_array_new_with_positions (gint size,
126 gboolean positions_in_pixels,
127 PangoTabAlign first_alignment,
128 gint first_position,
129 ...)
130{
131 PangoTabArray *array;
132 va_list args;
133 int i;
134
135 g_return_val_if_fail (size >= 0, NULL);
136
137 array = pango_tab_array_new (initial_size: size, positions_in_pixels);
138
139 if (size == 0)
140 return array;
141
142 array->tabs[0].alignment = first_alignment;
143 array->tabs[0].location = first_position;
144 array->tabs[0].decimal_point = 0;
145
146 if (size == 1)
147 return array;
148
149 va_start (args, first_position);
150
151 i = 1;
152 while (i < size)
153 {
154 PangoTabAlign align = va_arg (args, PangoTabAlign);
155 int pos = va_arg (args, int);
156
157 array->tabs[i].alignment = align;
158 array->tabs[i].location = pos;
159 array->tabs[i].decimal_point = 0;
160
161 ++i;
162 }
163
164 va_end (args);
165
166 return array;
167}
168
169G_DEFINE_BOXED_TYPE (PangoTabArray, pango_tab_array,
170 pango_tab_array_copy,
171 pango_tab_array_free);
172
173/**
174 * pango_tab_array_copy:
175 * @src: `PangoTabArray` to copy
176 *
177 * Copies a `PangoTabArray`.
178 *
179 * Return value: the newly allocated `PangoTabArray`, which should
180 * be freed with [method@Pango.TabArray.free].
181 */
182PangoTabArray*
183pango_tab_array_copy (PangoTabArray *src)
184{
185 PangoTabArray *copy;
186
187 g_return_val_if_fail (src != NULL, NULL);
188
189 copy = pango_tab_array_new (initial_size: src->size, positions_in_pixels: src->positions_in_pixels);
190
191 if (copy->tabs)
192 memcpy (dest: copy->tabs, src: src->tabs, n: sizeof(PangoTab) * src->size);
193
194 return copy;
195}
196
197/**
198 * pango_tab_array_free:
199 * @tab_array: a `PangoTabArray`
200 *
201 * Frees a tab array and associated resources.
202 */
203void
204pango_tab_array_free (PangoTabArray *tab_array)
205{
206 g_return_if_fail (tab_array != NULL);
207
208 g_free (mem: tab_array->tabs);
209
210 g_slice_free (PangoTabArray, tab_array);
211}
212
213/**
214 * pango_tab_array_get_size:
215 * @tab_array: a `PangoTabArray`
216 *
217 * Gets the number of tab stops in @tab_array.
218 *
219 * Return value: the number of tab stops in the array.
220 */
221gint
222pango_tab_array_get_size (PangoTabArray *tab_array)
223{
224 g_return_val_if_fail (tab_array != NULL, 0);
225
226 return tab_array->size;
227}
228
229/**
230 * pango_tab_array_resize:
231 * @tab_array: a `PangoTabArray`
232 * @new_size: new size of the array
233 *
234 * Resizes a tab array.
235 *
236 * You must subsequently initialize any tabs
237 * that were added as a result of growing the array.
238 */
239void
240pango_tab_array_resize (PangoTabArray *tab_array,
241 gint new_size)
242{
243 if (new_size > tab_array->allocated)
244 {
245 gint current_end = tab_array->allocated;
246
247 /* Ratchet allocated size up above the index. */
248 if (tab_array->allocated == 0)
249 tab_array->allocated = 2;
250
251 while (new_size > tab_array->allocated)
252 tab_array->allocated = tab_array->allocated * 2;
253
254 tab_array->tabs = g_renew (PangoTab, tab_array->tabs,
255 tab_array->allocated);
256
257 init_tabs (array: tab_array, start: current_end, end: tab_array->allocated);
258 }
259
260 tab_array->size = new_size;
261}
262
263/**
264 * pango_tab_array_set_tab:
265 * @tab_array: a `PangoTabArray`
266 * @tab_index: the index of a tab stop
267 * @alignment: tab alignment
268 * @location: tab location in Pango units
269 *
270 * Sets the alignment and location of a tab stop.
271 */
272void
273pango_tab_array_set_tab (PangoTabArray *tab_array,
274 gint tab_index,
275 PangoTabAlign alignment,
276 gint location)
277{
278 g_return_if_fail (tab_array != NULL);
279 g_return_if_fail (tab_index >= 0);
280 g_return_if_fail (location >= 0);
281
282 if (tab_index >= tab_array->size)
283 pango_tab_array_resize (tab_array, new_size: tab_index + 1);
284
285 tab_array->tabs[tab_index].alignment = alignment;
286 tab_array->tabs[tab_index].location = location;
287}
288
289/**
290 * pango_tab_array_get_tab:
291 * @tab_array: a `PangoTabArray`
292 * @tab_index: tab stop index
293 * @alignment: (out) (optional): location to store alignment
294 * @location: (out) (optional): location to store tab position
295 *
296 * Gets the alignment and position of a tab stop.
297 */
298void
299pango_tab_array_get_tab (PangoTabArray *tab_array,
300 gint tab_index,
301 PangoTabAlign *alignment,
302 gint *location)
303{
304 g_return_if_fail (tab_array != NULL);
305 g_return_if_fail (tab_index < tab_array->size);
306 g_return_if_fail (tab_index >= 0);
307
308 if (alignment)
309 *alignment = tab_array->tabs[tab_index].alignment;
310
311 if (location)
312 *location = tab_array->tabs[tab_index].location;
313}
314
315/**
316 * pango_tab_array_get_tabs:
317 * @tab_array: a `PangoTabArray`
318 * @alignments: (out) (optional): location to store an array of tab
319 * stop alignments
320 * @locations: (out) (optional) (array): location to store an array
321 * of tab positions
322 *
323 * If non-%NULL, @alignments and @locations are filled with allocated
324 * arrays.
325 *
326 * The arrays are of length [method@Pango.TabArray.get_size].
327 * You must free the returned array.
328 */
329void
330pango_tab_array_get_tabs (PangoTabArray *tab_array,
331 PangoTabAlign **alignments,
332 gint **locations)
333{
334 gint i;
335
336 g_return_if_fail (tab_array != NULL);
337
338 if (alignments)
339 *alignments = g_new (PangoTabAlign, tab_array->size);
340
341 if (locations)
342 *locations = g_new (gint, tab_array->size);
343
344 i = 0;
345 while (i < tab_array->size)
346 {
347 if (alignments)
348 (*alignments)[i] = tab_array->tabs[i].alignment;
349 if (locations)
350 (*locations)[i] = tab_array->tabs[i].location;
351
352 ++i;
353 }
354}
355
356/**
357 * pango_tab_array_get_positions_in_pixels:
358 * @tab_array: a `PangoTabArray`
359 *
360 * Returns %TRUE if the tab positions are in pixels,
361 * %FALSE if they are in Pango units.
362 *
363 * Return value: whether positions are in pixels.
364 */
365gboolean
366pango_tab_array_get_positions_in_pixels (PangoTabArray *tab_array)
367{
368 g_return_val_if_fail (tab_array != NULL, FALSE);
369
370 return tab_array->positions_in_pixels;
371}
372
373/**
374 * pango_tab_array_set_positions_in_pixels:
375 * @tab_array: a `PangoTabArray`
376 * @positions_in_pixels: whether positions are in pixels
377 *
378 * Sets whether positions in this array are specified in
379 * pixels.
380 *
381 * Since: 1.50
382 */
383void
384pango_tab_array_set_positions_in_pixels (PangoTabArray *tab_array,
385 gboolean positions_in_pixels)
386{
387 g_return_if_fail (tab_array != NULL);
388
389 tab_array->positions_in_pixels = positions_in_pixels;
390}
391
392/**
393 * pango_tab_array_to_string:
394 * @tab_array: a `PangoTabArray`
395 *
396 * Serializes a `PangoTabArray` to a string.
397 *
398 * No guarantees are made about the format of the string,
399 * it may change between Pango versions.
400 *
401 * The intended use of this function is testing and
402 * debugging. The format is not meant as a permanent
403 * storage format.
404 *
405 * Returns: (transfer full): a newly allocated string
406 * Since: 1.50
407 */
408char *
409pango_tab_array_to_string (PangoTabArray *tab_array)
410{
411 GString *s;
412
413 s = g_string_new (init: "");
414
415 for (int i = 0; i < tab_array->size; i++)
416 {
417 if (i > 0)
418 g_string_append_c (s, '\n');
419
420 if (tab_array->tabs[i].alignment == PANGO_TAB_RIGHT)
421 g_string_append (string: s, val: "right:");
422 else if (tab_array->tabs[i].alignment == PANGO_TAB_CENTER)
423 g_string_append (string: s, val: "center:");
424 else if (tab_array->tabs[i].alignment == PANGO_TAB_DECIMAL)
425 g_string_append (string: s, val: "decimal:");
426
427 g_string_append_printf (string: s, format: "%d", tab_array->tabs[i].location);
428 if (tab_array->positions_in_pixels)
429 g_string_append (string: s, val: "px");
430
431 if (tab_array->tabs[i].decimal_point != 0)
432 g_string_append_printf (string: s, format: ":%d", tab_array->tabs[i].decimal_point);
433 }
434
435 return g_string_free (string: s, FALSE);
436}
437
438static const char *
439skip_whitespace (const char *p)
440{
441 while (g_ascii_isspace (*p))
442 p++;
443 return p;
444}
445
446/**
447 * pango_tab_array_from_string:
448 * @text: a string
449 *
450 * Deserializes a `PangoTabArray` from a string.
451 *
452 * This is the counterpart to [method@Pango.TabArray.to_string].
453 * See that functions for details about the format.
454 *
455 * Returns: (transfer full) (nullable): a new `PangoTabArray`
456 * Since: 1.50
457 */
458PangoTabArray *
459pango_tab_array_from_string (const char *text)
460{
461 PangoTabArray *array;
462 gboolean pixels;
463 const char *p;
464 int i;
465
466 pixels = strstr (haystack: text, needle: "px") != NULL;
467
468 array = pango_tab_array_new (initial_size: 0, positions_in_pixels: pixels);
469
470 p = skip_whitespace (p: text);
471
472 i = 0;
473 while (*p)
474 {
475 char *endp;
476 gint64 pos;
477 PangoTabAlign align;
478
479 if (g_str_has_prefix (str: p, prefix: "left:"))
480 {
481 align = PANGO_TAB_LEFT;
482 p += strlen (s: "left:");
483 }
484 else if (g_str_has_prefix (str: p, prefix: "right:"))
485 {
486 align = PANGO_TAB_RIGHT;
487 p += strlen (s: "right:");
488 }
489 else if (g_str_has_prefix (str: p, prefix: "center:"))
490 {
491 align = PANGO_TAB_CENTER;
492 p += strlen (s: "center:");
493 }
494 else if (g_str_has_prefix (str: p, prefix: "decimal:"))
495 {
496 align = PANGO_TAB_DECIMAL;
497 p += strlen (s: "decimal:");
498 }
499 else
500 {
501 align = PANGO_TAB_LEFT;
502 }
503
504 pos = g_ascii_strtoll (nptr: p, endptr: &endp, base: 10);
505 if (pos < 0 ||
506 (pixels && *endp != 'p') ||
507 (!pixels && !g_ascii_isspace (*endp) && *endp != ':' && *endp != '\0')) goto fail;
508
509 pango_tab_array_set_tab (tab_array: array, tab_index: i, alignment: align, location: pos);
510
511 p = (const char *)endp;
512 if (pixels)
513 {
514 if (p[0] != 'p' || p[1] != 'x') goto fail;
515 p += 2;
516 }
517
518 if (p[0] == ':')
519 {
520 gunichar ch;
521
522 p++;
523 ch = g_ascii_strtoll (nptr: p, endptr: &endp, base: 10);
524 if (!g_ascii_isspace (*endp) && *endp != '\0') goto fail;
525
526 pango_tab_array_set_decimal_point (tab_array: array, tab_index: i, decimal_point: ch);
527
528 p = (const char *)endp;
529 }
530
531 p = skip_whitespace (p);
532
533 i++;
534 }
535
536 goto success;
537
538fail:
539 pango_tab_array_free (tab_array: array);
540 array = NULL;
541
542success:
543 return array;
544}
545
546/**
547 * pango_tab_array_set_decimal_point:
548 * @tab_array: a `PangoTabArray`
549 * @tab_index: the index of a tab stop
550 * @decimal_point: the decimal point to use
551 *
552 * Sets the Unicode character to use as decimal point.
553 *
554 * This is only relevant for tabs with %PANGO_TAB_DECIMAL alignment,
555 * which align content at the first occurrence of the decimal point
556 * character.
557 *
558 * By default, Pango uses the decimal point according
559 * to the current locale.
560 *
561 * Since: 1.50
562 */
563void
564pango_tab_array_set_decimal_point (PangoTabArray *tab_array,
565 int tab_index,
566 gunichar decimal_point)
567{
568 g_return_if_fail (tab_array != NULL);
569 g_return_if_fail (tab_index >= 0);
570
571 if (tab_index >= tab_array->size)
572 pango_tab_array_resize (tab_array, new_size: tab_index + 1);
573
574 tab_array->tabs[tab_index].decimal_point = decimal_point;
575}
576
577/**
578 * pango_tab_array_get_decimal_point:
579 * @tab_array: a `PangoTabArray`
580 * @tab_index: the index of a tab stop
581 *
582 * Gets the Unicode character to use as decimal point.
583 *
584 * This is only relevant for tabs with %PANGO_TAB_DECIMAL alignment,
585 * which align content at the first occurrence of the decimal point
586 * character.
587 *
588 * The default value of 0 means that Pango will use the
589 * decimal point according to the current locale.
590 *
591 * Since: 1.50
592 */
593gunichar
594pango_tab_array_get_decimal_point (PangoTabArray *tab_array,
595 int tab_index)
596{
597 g_return_val_if_fail (tab_array != NULL, 0);
598 g_return_val_if_fail (tab_index < tab_array->size, 0);
599 g_return_val_if_fail (tab_index >= 0, 0);
600
601 return tab_array->tabs[tab_index].decimal_point;
602}
603
604static int
605compare_tabs (const void *p1, const void *p2)
606{
607 const PangoTab *t1 = p1;
608 const PangoTab *t2 = p2;
609
610 return t1->location - t2->location;
611}
612
613/**
614 * pango_tab_array_sort:
615 * @tab_array: a `PangoTabArray`
616 *
617 * Utility function to ensure that the tab stops are in increasing order.
618 *
619 * Since: 1.50
620 */
621void
622pango_tab_array_sort (PangoTabArray *tab_array)
623{
624 g_return_if_fail (tab_array != NULL);
625
626 qsort (base: tab_array->tabs, nmemb: tab_array->size, size: sizeof (PangoTab), compar: compare_tabs);
627}
628

source code of gtk/subprojects/pango/pango/pango-tabs.c