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