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 | |
28 | typedef struct _PangoTab PangoTab; |
29 | |
30 | struct _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 | */ |
46 | struct _PangoTabArray |
47 | { |
48 | gint size; |
49 | gint allocated; |
50 | gboolean positions_in_pixels; |
51 | PangoTab *tabs; |
52 | }; |
53 | |
54 | static void |
55 | init_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 | */ |
79 | PangoTabArray* |
80 | pango_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 | */ |
124 | PangoTabArray * |
125 | pango_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 | |
169 | G_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 | */ |
182 | PangoTabArray* |
183 | pango_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 | */ |
203 | void |
204 | pango_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 | */ |
221 | gint |
222 | pango_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 | */ |
239 | void |
240 | pango_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 | */ |
272 | void |
273 | pango_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 | */ |
298 | void |
299 | pango_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 | */ |
329 | void |
330 | pango_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 | */ |
365 | gboolean |
366 | pango_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 | */ |
383 | void |
384 | pango_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 | */ |
408 | char * |
409 | pango_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 | |
438 | static const char * |
439 | skip_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 | */ |
458 | PangoTabArray * |
459 | pango_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 | |
538 | fail: |
539 | pango_tab_array_free (tab_array: array); |
540 | array = NULL; |
541 | |
542 | success: |
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 | */ |
563 | void |
564 | pango_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 | */ |
593 | gunichar |
594 | pango_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 | |
604 | static int |
605 | compare_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 | */ |
621 | void |
622 | pango_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 | |