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