1/* Pango
2 * glyphstring.c:
3 *
4 * Copyright (C) 1999 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 <glib.h>
24#include "pango-glyph.h"
25#include "pango-font.h"
26#include "pango-impl-utils.h"
27
28#include <hb-ot.h>
29
30/**
31 * pango_glyph_string_new:
32 *
33 * Create a new `PangoGlyphString`.
34 *
35 * Return value: the newly allocated `PangoGlyphString`, which
36 * should be freed with [method@Pango.GlyphString.free].
37 */
38PangoGlyphString *
39pango_glyph_string_new (void)
40{
41 PangoGlyphString *string = g_slice_new (PangoGlyphString);
42
43 string->num_glyphs = 0;
44 string->space = 0;
45 string->glyphs = NULL;
46 string->log_clusters = NULL;
47
48 return string;
49}
50
51/**
52 * pango_glyph_string_set_size:
53 * @string: a `PangoGlyphString`.
54 * @new_len: the new length of the string
55 *
56 * Resize a glyph string to the given length.
57 */
58void
59pango_glyph_string_set_size (PangoGlyphString *string, gint new_len)
60{
61 g_return_if_fail (new_len >= 0);
62
63 while (new_len > string->space)
64 {
65 if (string->space == 0)
66 {
67 string->space = 4;
68 }
69 else
70 {
71 const guint max_space =
72 MIN (G_MAXINT, G_MAXSIZE / MAX (sizeof(PangoGlyphInfo), sizeof(gint)));
73
74 guint more_space = (guint)string->space * 2;
75
76 if (more_space > max_space)
77 {
78 more_space = max_space;
79
80 if ((guint)new_len > max_space)
81 {
82 g_error ("%s: failed to allocate glyph string of length %i\n",
83 G_STRLOC, new_len);
84 }
85 }
86
87 string->space = more_space;
88 }
89 }
90
91 string->glyphs = g_realloc (mem: string->glyphs, n_bytes: string->space * sizeof (PangoGlyphInfo));
92 string->log_clusters = g_realloc (mem: string->log_clusters, n_bytes: string->space * sizeof (gint));
93 string->num_glyphs = new_len;
94}
95
96G_DEFINE_BOXED_TYPE (PangoGlyphString, pango_glyph_string,
97 pango_glyph_string_copy,
98 pango_glyph_string_free);
99
100/**
101 * pango_glyph_string_copy:
102 * @string: (nullable): a `PangoGlyphString`
103 *
104 * Copy a glyph string and associated storage.
105 *
106 * Return value: (nullable): the newly allocated `PangoGlyphString`
107 */
108PangoGlyphString *
109pango_glyph_string_copy (PangoGlyphString *string)
110{
111 PangoGlyphString *new_string;
112
113 if (string == NULL)
114 return NULL;
115
116 new_string = g_slice_new (PangoGlyphString);
117
118 *new_string = *string;
119
120 new_string->glyphs = g_memdup2 (mem: string->glyphs,
121 byte_size: string->space * sizeof (PangoGlyphInfo));
122 new_string->log_clusters = g_memdup2 (mem: string->log_clusters,
123 byte_size: string->space * sizeof (gint));
124
125 return new_string;
126}
127
128/**
129 * pango_glyph_string_free:
130 * @string: (nullable): a `PangoGlyphString`, may be %NULL
131 *
132 * Free a glyph string and associated storage.
133 */
134void
135pango_glyph_string_free (PangoGlyphString *string)
136{
137 if (string == NULL)
138 return;
139
140 g_free (mem: string->glyphs);
141 g_free (mem: string->log_clusters);
142 g_slice_free (PangoGlyphString, string);
143}
144
145/**
146 * pango_glyph_string_extents_range:
147 * @glyphs: a `PangoGlyphString`
148 * @start: start index
149 * @end: end index (the range is the set of bytes with
150 * indices such that start <= index < end)
151 * @font: a `PangoFont`
152 * @ink_rect: (out caller-allocates) (optional): rectangle used to
153 * store the extents of the glyph string range as drawn
154 * @logical_rect: (out caller-allocates) (optional): rectangle used to
155 * store the logical extents of the glyph string range
156 *
157 * Computes the extents of a sub-portion of a glyph string.
158 *
159 * The extents are relative to the start of the glyph string range
160 * (the origin of their coordinate system is at the start of the range,
161 * not at the start of the entire glyph string).
162 */
163void
164pango_glyph_string_extents_range (PangoGlyphString *glyphs,
165 int start,
166 int end,
167 PangoFont *font,
168 PangoRectangle *ink_rect,
169 PangoRectangle *logical_rect)
170{
171 int x_pos = 0;
172 int i;
173
174 /* Note that the handling of empty rectangles for ink
175 * and logical rectangles is different. A zero-height ink
176 * rectangle makes no contribution to the overall ink rect,
177 * while a zero-height logical rect still reserves horizontal
178 * width. Also, we may return zero-width, positive height
179 * logical rectangles, while we'll never do that for the
180 * ink rect.
181 */
182 g_return_if_fail (start <= end);
183
184 if (G_UNLIKELY (!ink_rect && !logical_rect))
185 return;
186
187 if (ink_rect)
188 {
189 ink_rect->x = 0;
190 ink_rect->y = 0;
191 ink_rect->width = 0;
192 ink_rect->height = 0;
193 }
194
195 if (logical_rect)
196 {
197 logical_rect->x = 0;
198 logical_rect->y = 0;
199 logical_rect->width = 0;
200 logical_rect->height = 0;
201 }
202
203 for (i = start; i < end; i++)
204 {
205 PangoRectangle glyph_ink;
206 PangoRectangle glyph_logical;
207
208 PangoGlyphGeometry *geometry = &glyphs->glyphs[i].geometry;
209
210 pango_font_get_glyph_extents (font, glyph: glyphs->glyphs[i].glyph,
211 ink_rect: ink_rect ? &glyph_ink : NULL,
212 logical_rect: logical_rect ? &glyph_logical : NULL);
213
214 if (ink_rect && glyph_ink.width != 0 && glyph_ink.height != 0)
215 {
216 if (ink_rect->width == 0 || ink_rect->height == 0)
217 {
218 ink_rect->x = x_pos + glyph_ink.x + geometry->x_offset;
219 ink_rect->width = glyph_ink.width;
220 ink_rect->y = glyph_ink.y + geometry->y_offset;
221 ink_rect->height = glyph_ink.height;
222 }
223 else
224 {
225 int new_x, new_y;
226
227 new_x = MIN (ink_rect->x, x_pos + glyph_ink.x + geometry->x_offset);
228 ink_rect->width = MAX (ink_rect->x + ink_rect->width,
229 x_pos + glyph_ink.x + glyph_ink.width + geometry->x_offset) - new_x;
230 ink_rect->x = new_x;
231
232 new_y = MIN (ink_rect->y, glyph_ink.y + geometry->y_offset);
233 ink_rect->height = MAX (ink_rect->y + ink_rect->height,
234 glyph_ink.y + glyph_ink.height + geometry->y_offset) - new_y;
235 ink_rect->y = new_y;
236 }
237 }
238
239 if (logical_rect)
240 {
241 logical_rect->width += geometry->width;
242
243 if (i == start)
244 {
245 logical_rect->y = glyph_logical.y;
246 logical_rect->height = glyph_logical.height;
247 }
248 else
249 {
250 int new_y = MIN (logical_rect->y, glyph_logical.y);
251 logical_rect->height = MAX (logical_rect->y + logical_rect->height,
252 glyph_logical.y + glyph_logical.height) - new_y;
253 logical_rect->y = new_y;
254 }
255 }
256
257 x_pos += geometry->width;
258 }
259}
260
261/**
262 * pango_glyph_string_extents:
263 * @glyphs: a `PangoGlyphString`
264 * @font: a `PangoFont`
265 * @ink_rect: (out) (optional): rectangle used to store the extents of the glyph string as drawn
266 * @logical_rect: (out) (optional): rectangle used to store the logical extents of the glyph string
267 *
268 * Compute the logical and ink extents of a glyph string.
269 *
270 * See the documentation for [method@Pango.Font.get_glyph_extents] for details
271 * about the interpretation of the rectangles.
272 *
273 * Examples of logical (red) and ink (green) rects:
274 *
275 * ![](rects1.png) ![](rects2.png)
276 */
277void
278pango_glyph_string_extents (PangoGlyphString *glyphs,
279 PangoFont *font,
280 PangoRectangle *ink_rect,
281 PangoRectangle *logical_rect)
282{
283 pango_glyph_string_extents_range (glyphs, start: 0, end: glyphs->num_glyphs,
284 font, ink_rect, logical_rect);
285}
286
287/**
288 * pango_glyph_string_get_width:
289 * @glyphs: a `PangoGlyphString`
290 *
291 * Computes the logical width of the glyph string.
292 *
293 * This can also be computed using [method@Pango.GlyphString.extents].
294 * However, since this only computes the width, it's much faster. This
295 * is in fact only a convenience function that computes the sum of
296 * @geometry.width for each glyph in the @glyphs.
297 *
298 * Return value: the logical width of the glyph string.
299 *
300 * Since: 1.14
301 */
302int
303pango_glyph_string_get_width (PangoGlyphString *glyphs)
304{
305 int i;
306 int width = 0;
307
308 for (i = 0; i < glyphs->num_glyphs; i++)
309 width += glyphs->glyphs[i].geometry.width;
310
311 return width;
312}
313
314/**
315 * pango_glyph_string_get_logical_widths:
316 * @glyphs: a `PangoGlyphString`
317 * @text: the text corresponding to the glyphs
318 * @length: the length of @text, in bytes
319 * @embedding_level: the embedding level of the string
320 * @logical_widths: (array): an array whose length is the number of
321 * characters in text (equal to `g_utf8_strlen (text, length)` unless
322 * text has `NUL` bytes) to be filled in with the resulting character widths.
323 *
324 * Given a `PangoGlyphString` and corresponding text, determine the width
325 * corresponding to each character.
326 *
327 * When multiple characters compose a single cluster, the width of the
328 * entire cluster is divided equally among the characters.
329 *
330 * See also [method@Pango.GlyphItem.get_logical_widths].
331 */
332void
333pango_glyph_string_get_logical_widths (PangoGlyphString *glyphs,
334 const char *text,
335 int length,
336 int embedding_level,
337 int *logical_widths)
338{
339 /* Build a PangoGlyphItem and call the other API */
340 PangoItem item = {0, length, pango_utf8_strlen (p: text, max: length),
341 {NULL, NULL, NULL,
342 embedding_level, PANGO_GRAVITY_AUTO, 0,
343 PANGO_SCRIPT_UNKNOWN, NULL,
344 NULL}};
345 PangoGlyphItem glyph_item = {&item, glyphs};
346
347 pango_glyph_item_get_logical_widths (glyph_item: &glyph_item, text, logical_widths);
348}
349
350/* The initial implementation here is script independent,
351 * but it might actually need to be virtualized into the
352 * rendering modules. Otherwise, we probably will end up
353 * enforcing unnatural cursor behavior for some languages.
354 *
355 * The only distinction that Uniscript makes is whether
356 * cursor positioning is allowed within clusters or not.
357 */
358
359/**
360 * pango_glyph_string_index_to_x:
361 * @glyphs: the glyphs return from [func@shape]
362 * @text: the text for the run
363 * @length: the number of bytes (not characters) in @text.
364 * @analysis: the analysis information return from [func@itemize]
365 * @index_: the byte index within @text
366 * @trailing: whether we should compute the result for the beginning (%FALSE)
367 * or end (%TRUE) of the character.
368 * @x_pos: (out): location to store result
369 *
370 * Converts from character position to x position.
371 *
372 * The X position is measured from the left edge of the run.
373 * Character positions are obtained using font metrics for ligatures
374 * where available, and computed by dividing up each cluster
375 * into equal portions, otherwise.
376 *
377 * <picture>
378 * <source srcset="glyphstring-positions-dark.png" media="(prefers-color-scheme: dark)">
379 * <img alt="Glyph positions" src="glyphstring-positions-light.png">
380 * </picture>
381 */
382void
383pango_glyph_string_index_to_x (PangoGlyphString *glyphs,
384 const char *text,
385 int length,
386 PangoAnalysis *analysis,
387 int index,
388 gboolean trailing,
389 int *x_pos)
390{
391 pango_glyph_string_index_to_x_full (glyphs,
392 text, length,
393 analysis,
394 NULL,
395 index_: index, trailing,
396 x_pos);
397}
398
399/**
400 * pango_glyph_string_index_to_x_full:
401 * @glyphs: the glyphs return from [func@shape]
402 * @text: the text for the run
403 * @length: the number of bytes (not characters) in @text.
404 * @analysis: the analysis information return from [func@itemize]
405 * @attrs: (nullable): `PangoLogAttr` array for @text
406 * @index_: the byte index within @text
407 * @trailing: whether we should compute the result for the beginning (%FALSE)
408 * or end (%TRUE) of the character.
409 * @x_pos: (out): location to store result
410 *
411 * Converts from character position to x position.
412 *
413 * This variant of [method@Pango.GlyphString.index_to_x] additionally
414 * accepts a `PangoLogAttr` array. The grapheme boundary information
415 * in it can be used to disambiguate positioning inside some complex
416 * clusters.
417 *
418 * Since: 1.50
419 */
420void
421pango_glyph_string_index_to_x_full (PangoGlyphString *glyphs,
422 const char *text,
423 int length,
424 PangoAnalysis *analysis,
425 PangoLogAttr *attrs,
426 int index,
427 gboolean trailing,
428 int *x_pos)
429{
430 int i;
431 int start_xpos = 0;
432 int end_xpos = 0;
433 int width = 0;
434
435 int start_index = -1;
436 int end_index = -1;
437
438 int cluster_chars = 0;
439 int cluster_offset = 0;
440 int start_glyph_pos = -1;
441 int end_glyph_pos = -1;
442
443 const char *p;
444
445 g_return_if_fail (glyphs != NULL);
446 g_return_if_fail (length >= 0);
447 g_return_if_fail (length == 0 || text != NULL);
448
449 if (!x_pos) /* Allow the user to do the useless */
450 return;
451
452 if (glyphs->num_glyphs == 0)
453 {
454 *x_pos = 0;
455 return;
456 }
457
458 start_glyph_pos = -1;
459 end_glyph_pos = -1;
460
461 /* Calculate the starting and ending character positions
462 * and x positions for the cluster
463 */
464 if (analysis->level % 2) /* Right to left */
465 {
466 for (i = glyphs->num_glyphs - 1; i >= 0; i--)
467 width += glyphs->glyphs[i].geometry.width;
468
469 for (i = glyphs->num_glyphs - 1; i >= 0; i--)
470 {
471 if (glyphs->log_clusters[i] > index)
472 {
473 end_index = glyphs->log_clusters[i];
474 end_xpos = width;
475 break;
476 }
477
478 if (glyphs->log_clusters[i] != start_index)
479 {
480 start_index = glyphs->log_clusters[i];
481 start_xpos = width;
482 }
483
484 width -= glyphs->glyphs[i].geometry.width;
485 }
486
487 for (i = glyphs->num_glyphs - 1; i >= 0; i--)
488 {
489 if (glyphs->log_clusters[i] == start_index)
490 {
491 if (end_glyph_pos < 0)
492 end_glyph_pos = i;
493 start_glyph_pos = i;
494 }
495 }
496 }
497 else /* Left to right */
498 {
499 for (i = 0; i < glyphs->num_glyphs; i++)
500 {
501 if (glyphs->log_clusters[i] > index)
502 {
503 end_index = glyphs->log_clusters[i];
504 end_xpos = width;
505 break;
506 }
507
508 if (glyphs->log_clusters[i] != start_index)
509 {
510 start_index = glyphs->log_clusters[i];
511 start_xpos = width;
512 }
513
514 width += glyphs->glyphs[i].geometry.width;
515 }
516
517 for (i = 0; i < glyphs->num_glyphs; i++)
518 {
519 if (glyphs->log_clusters[i] == start_index)
520 {
521 if (start_glyph_pos < 0)
522 start_glyph_pos = i;
523 end_glyph_pos = i;
524 }
525 }
526 }
527
528 if (end_index == -1)
529 {
530 end_index = length;
531 end_xpos = (analysis->level % 2) ? 0 : width;
532 }
533
534 /* Calculate offset of character within cluster.
535 * To come up with accurate answers here, we need to know grapheme
536 * boundaries.
537 */
538 for (p = text + start_index, i = attrs ? g_utf8_pointer_to_offset (str: text, pos: text + start_index) : 0;
539 p < text + end_index;
540 p = g_utf8_next_char (p), i++)
541 {
542 if (attrs && !attrs[i].is_cursor_position)
543 continue;
544
545 if (p < text + index)
546 cluster_offset++;
547 cluster_chars++;
548 }
549
550 if (trailing)
551 cluster_offset = MIN (cluster_offset + 1, cluster_chars);
552
553 if (G_UNLIKELY (!cluster_chars)) /* pedantic */
554 {
555 *x_pos = start_xpos;
556 return;
557 }
558
559 /* Try to get a ligature caret position for the glyph from the font.
560 * This only makes sense if the cluster contains a single spacing
561 * glyph, so we need to check that all but one of them are marks.
562 */
563 if (cluster_offset > 0 && cluster_offset < cluster_chars)
564 {
565 hb_font_t *hb_font;
566 hb_position_t caret;
567 unsigned int caret_count = 1;
568 int glyph_pos;
569 int num_carets;
570
571 hb_font = pango_font_get_hb_font (font: analysis->font);
572
573 if (start_glyph_pos == end_glyph_pos)
574 glyph_pos = start_glyph_pos;
575 else
576 {
577 hb_face_t *hb_face;
578
579 hb_face = hb_font_get_face (font: hb_font);
580
581 glyph_pos = -1;
582 for (i = start_glyph_pos; i <= end_glyph_pos; i++)
583 {
584 if (hb_ot_layout_get_glyph_class (face: hb_face, glyph: glyphs->glyphs[i].glyph) != HB_OT_LAYOUT_GLYPH_CLASS_MARK)
585 {
586 if (glyph_pos != -1)
587 {
588 /* multiple non-mark glyphs in cluster, giving up */
589 goto fallback;
590 }
591 glyph_pos = i;
592 }
593 }
594 if (glyph_pos == -1)
595 {
596 /* no non-mark glyph in a multi-glyph cluster, giving up */
597 goto fallback;
598 }
599 }
600
601 num_carets = hb_ot_layout_get_ligature_carets (font: hb_font,
602 direction: (analysis->level % 2) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
603 glyph: glyphs->glyphs[glyph_pos].glyph,
604 start_offset: cluster_offset - 1, caret_count: &caret_count, caret_array: &caret);
605 if (caret_count == 0 || num_carets == 0)
606 {
607 /* no ligature caret information found for this glyph */
608 goto fallback;
609 }
610
611 if (analysis->level % 2) /* Right to left */
612 *x_pos = end_xpos + caret;
613 else
614 *x_pos = start_xpos + caret;
615 *x_pos += glyphs->glyphs[glyph_pos].geometry.x_offset;
616 return;
617 }
618
619fallback:
620
621 *x_pos = ((cluster_chars - cluster_offset) * start_xpos +
622 cluster_offset * end_xpos) / cluster_chars;
623}
624
625/**
626 * pango_glyph_string_x_to_index:
627 * @glyphs: the glyphs returned from [func@shape]
628 * @text: the text for the run
629 * @length: the number of bytes (not characters) in text.
630 * @analysis: the analysis information return from [func@itemize]
631 * @x_pos: the x offset (in Pango units)
632 * @index_: (out): location to store calculated byte index within @text
633 * @trailing: (out): location to store a boolean indicating whether the
634 * user clicked on the leading or trailing edge of the character
635 *
636 * Convert from x offset to character position.
637 *
638 * Character positions are computed by dividing up each cluster into
639 * equal portions. In scripts where positioning within a cluster is
640 * not allowed (such as Thai), the returned value may not be a valid
641 * cursor position; the caller must combine the result with the logical
642 * attributes for the text to compute the valid cursor position.
643 */
644void
645pango_glyph_string_x_to_index (PangoGlyphString *glyphs,
646 const char *text,
647 int length,
648 PangoAnalysis *analysis,
649 int x_pos,
650 int *index,
651 gboolean *trailing)
652{
653 int i;
654 int start_xpos = 0;
655 int end_xpos = 0;
656 int width = 0;
657
658 int start_index = -1;
659 int end_index = -1;
660
661 int cluster_chars = 0;
662 const char *p;
663
664 gboolean found = FALSE;
665
666 /* Find the cluster containing the position */
667
668 width = 0;
669
670 if (analysis->level % 2) /* Right to left */
671 {
672 for (i = glyphs->num_glyphs - 1; i >= 0; i--)
673 width += glyphs->glyphs[i].geometry.width;
674
675 for (i = glyphs->num_glyphs - 1; i >= 0; i--)
676 {
677 if (glyphs->log_clusters[i] != start_index)
678 {
679 if (found)
680 {
681 end_index = glyphs->log_clusters[i];
682 end_xpos = width;
683 break;
684 }
685 else
686 {
687 start_index = glyphs->log_clusters[i];
688 start_xpos = width;
689 }
690 }
691
692 width -= glyphs->glyphs[i].geometry.width;
693
694 if (width <= x_pos && x_pos < width + glyphs->glyphs[i].geometry.width)
695 found = TRUE;
696 }
697 }
698 else /* Left to right */
699 {
700 for (i = 0; i < glyphs->num_glyphs; i++)
701 {
702 if (glyphs->log_clusters[i] != start_index)
703 {
704 if (found)
705 {
706 end_index = glyphs->log_clusters[i];
707 end_xpos = width;
708 break;
709 }
710 else
711 {
712 start_index = glyphs->log_clusters[i];
713 start_xpos = width;
714 }
715 }
716
717 if (width <= x_pos && x_pos < width + glyphs->glyphs[i].geometry.width)
718 found = TRUE;
719
720 width += glyphs->glyphs[i].geometry.width;
721 }
722 }
723
724 if (end_index == -1)
725 {
726 end_index = length;
727 end_xpos = (analysis->level % 2) ? 0 : width;
728 }
729
730 /* Calculate number of chars within cluster */
731 p = text + start_index;
732 while (p < text + end_index)
733 {
734 p = g_utf8_next_char (p);
735 cluster_chars++;
736 }
737
738 if (start_xpos == end_xpos)
739 {
740 if (index)
741 *index = start_index;
742 if (trailing)
743 *trailing = FALSE;
744 }
745 else
746 {
747 double cp = ((double)(x_pos - start_xpos) * cluster_chars) / (end_xpos - start_xpos);
748
749 /* LTR and right-to-left have to be handled separately
750 * here because of the edge condition when we are exactly
751 * at a pixel boundary; end_xpos goes with the next
752 * character for LTR, with the previous character for RTL.
753 */
754 if (start_xpos < end_xpos) /* Left-to-right */
755 {
756 if (index)
757 {
758 const char *p = text + start_index;
759 int i = 0;
760
761 while (i + 1 <= cp)
762 {
763 p = g_utf8_next_char (p);
764 i++;
765 }
766
767 *index = (p - text);
768 }
769
770 if (trailing)
771 *trailing = (cp - (int)cp >= 0.5) ? TRUE : FALSE;
772 }
773 else /* Right-to-left */
774 {
775 if (index)
776 {
777 const char *p = text + start_index;
778 int i = 0;
779
780 while (i + 1 < cp)
781 {
782 p = g_utf8_next_char (p);
783 i++;
784 }
785
786 *index = (p - text);
787 }
788
789 if (trailing)
790 {
791 double cp_flip = cluster_chars - cp;
792 *trailing = (cp_flip - (int)cp_flip >= 0.5) ? FALSE : TRUE;
793 }
794 }
795 }
796}
797

source code of gtk/subprojects/pango/pango/glyphstring.c