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 | */ |
38 | PangoGlyphString * |
39 | pango_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 | */ |
58 | void |
59 | pango_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 | |
96 | G_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 | */ |
108 | PangoGlyphString * |
109 | pango_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 | */ |
134 | void |
135 | pango_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 | */ |
163 | void |
164 | pango_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 | */ |
277 | void |
278 | pango_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 | */ |
302 | int |
303 | pango_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 | */ |
332 | void |
333 | pango_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 | */ |
382 | void |
383 | pango_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 | */ |
420 | void |
421 | pango_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 | |
619 | fallback: |
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 | */ |
644 | void |
645 | pango_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 | |