1/* Pango
2 * shape.c: Convert characters into glyphs.
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
24#include <string.h>
25#include <math.h>
26#include <glib.h>
27
28#include "pango-impl-utils.h"
29#include "pango-glyph.h"
30
31#include "pango-item-private.h"
32#include "pango-font-private.h"
33
34#include <hb-ot.h>
35
36/* {{{ Harfbuzz shaping */
37/* {{{ Buffer handling */
38
39static hb_buffer_t *cached_buffer = NULL; /* MT-safe */
40G_LOCK_DEFINE_STATIC (cached_buffer);
41
42static hb_buffer_t *
43acquire_buffer (gboolean *free_buffer)
44{
45 hb_buffer_t *buffer;
46
47 if (G_LIKELY (G_TRYLOCK (cached_buffer)))
48 {
49 if (G_UNLIKELY (!cached_buffer))
50 cached_buffer = hb_buffer_create ();
51
52 buffer = cached_buffer;
53 *free_buffer = FALSE;
54 }
55 else
56 {
57 buffer = hb_buffer_create ();
58 *free_buffer = TRUE;
59 }
60
61 return buffer;
62}
63
64static void
65release_buffer (hb_buffer_t *buffer,
66 gboolean free_buffer)
67{
68 if (G_LIKELY (!free_buffer))
69 {
70 hb_buffer_reset (buffer);
71 G_UNLOCK (cached_buffer);
72 }
73 else
74 hb_buffer_destroy (buffer);
75}
76
77/* }}} */
78/* {{{ Use PangoFont with Harfbuzz */
79
80typedef struct
81{
82 PangoFont *font;
83 hb_font_t *parent;
84 PangoShowFlags show_flags;
85} PangoHbShapeContext;
86
87static hb_bool_t
88pango_hb_font_get_nominal_glyph (hb_font_t *font,
89 void *font_data,
90 hb_codepoint_t unicode,
91 hb_codepoint_t *glyph,
92 void *user_data G_GNUC_UNUSED)
93{
94 PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
95
96 if (context->show_flags != 0)
97 {
98 if ((context->show_flags & PANGO_SHOW_SPACES) != 0 &&
99 g_unichar_type (c: unicode) == G_UNICODE_SPACE_SEPARATOR)
100 {
101 /* Replace 0x20 by visible space, since we
102 * don't draw a hex box for 0x20
103 */
104 if (unicode == 0x20)
105 unicode = 0x2423;
106 else
107 {
108 *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
109 return TRUE;
110 }
111 }
112
113 if ((context->show_flags & PANGO_SHOW_IGNORABLES) != 0 &&
114 pango_is_default_ignorable (ch: unicode))
115 {
116 if (pango_get_ignorable (ch: unicode))
117 *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
118 else
119 *glyph = PANGO_GLYPH_EMPTY;
120 return TRUE;
121 }
122
123 if ((context->show_flags & PANGO_SHOW_LINE_BREAKS) != 0 &&
124 unicode == 0x2028)
125 {
126 /* Always mark LS as unknown. If it ends up
127 * at the line end, PangoLayout takes care of
128 * hiding them, and if they end up in the middle
129 * of a line, we are in single paragraph mode
130 * and want to show the LS
131 */
132 *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
133 return TRUE;
134 }
135 }
136
137 if (hb_font_get_nominal_glyph (font: context->parent, unicode, glyph))
138 return TRUE;
139
140 /* HarfBuzz knows how to synthesize other spaces from 0x20, so never
141 * replace them with unknown glyphs, just tell HarfBuzz that we don't
142 * have a glyph.
143 *
144 * For 0x20, on the other hand, we need to pretend that we have a glyph
145 * and rely on our glyph extents code to provide a reasonable width for
146 * PANGO_GET_UNKNOWN_WIDTH (0x20).
147 */
148 if (g_unichar_type (c: unicode) == G_UNICODE_SPACE_SEPARATOR)
149 {
150 if (unicode == 0x20)
151 {
152 *glyph = PANGO_GET_UNKNOWN_GLYPH (0x20);
153 return TRUE;
154 }
155
156 return FALSE;
157 }
158
159 *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
160
161 /* We draw our own invalid-Unicode shape, so prevent HarfBuzz
162 * from using REPLACEMENT CHARACTER.
163 */
164 if (unicode > 0x10FFFF)
165 return TRUE;
166
167 return FALSE;
168}
169
170static hb_position_t
171pango_hb_font_get_glyph_h_advance (hb_font_t *font,
172 void *font_data,
173 hb_codepoint_t glyph,
174 void *user_data G_GNUC_UNUSED)
175{
176 PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
177
178 if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
179 {
180 PangoRectangle logical;
181
182 pango_font_get_glyph_extents (font: context->font, glyph, NULL, logical_rect: &logical);
183 return logical.width;
184 }
185
186 return hb_font_get_glyph_h_advance (font: context->parent, glyph);
187}
188
189static hb_position_t
190pango_hb_font_get_glyph_v_advance (hb_font_t *font,
191 void *font_data,
192 hb_codepoint_t glyph,
193 void *user_data G_GNUC_UNUSED)
194{
195 PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
196
197 if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
198 {
199 PangoRectangle logical;
200
201 pango_font_get_glyph_extents (font: context->font, glyph, NULL, logical_rect: &logical);
202 return logical.height;
203 }
204
205 return hb_font_get_glyph_v_advance (font: context->parent, glyph);
206}
207
208static hb_bool_t
209pango_hb_font_get_glyph_extents (hb_font_t *font,
210 void *font_data,
211 hb_codepoint_t glyph,
212 hb_glyph_extents_t *extents,
213 void *user_data G_GNUC_UNUSED)
214{
215 PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
216
217 if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
218 {
219 PangoRectangle ink;
220
221 pango_font_get_glyph_extents (font: context->font, glyph, ink_rect: &ink, NULL);
222
223 extents->x_bearing = ink.x;
224 extents->y_bearing = ink.y;
225 extents->width = ink.width;
226 extents->height = ink.height;
227
228 return TRUE;
229 }
230
231 return hb_font_get_glyph_extents (font: context->parent, glyph, extents);
232}
233
234static hb_font_t *
235pango_font_get_hb_font_for_context (PangoFont *font,
236 PangoHbShapeContext *context)
237{
238 hb_font_t *hb_font;
239 static hb_font_funcs_t *funcs;
240
241 hb_font = pango_font_get_hb_font (font);
242
243 if (G_UNLIKELY (g_once_init_enter (&funcs)))
244 {
245 hb_font_funcs_t *f = hb_font_funcs_create ();
246
247 hb_font_funcs_set_nominal_glyph_func (ffuncs: f, func: pango_hb_font_get_nominal_glyph, NULL, NULL);
248 hb_font_funcs_set_glyph_h_advance_func (ffuncs: f, func: pango_hb_font_get_glyph_h_advance, NULL, NULL);
249 hb_font_funcs_set_glyph_v_advance_func (ffuncs: f, func: pango_hb_font_get_glyph_v_advance, NULL, NULL);
250 hb_font_funcs_set_glyph_extents_func (ffuncs: f, func: pango_hb_font_get_glyph_extents, NULL, NULL);
251
252 hb_font_funcs_make_immutable (ffuncs: f);
253 g_once_init_leave (&funcs, f);
254 }
255
256 context->font = font;
257 context->parent = hb_font;
258
259 hb_font = hb_font_create_sub_font (parent: hb_font);
260 hb_font_set_funcs (font: hb_font, klass: funcs, font_data: context, NULL);
261
262 return hb_font;
263}
264
265/* }}} */
266/* {{{ Utilities */
267
268static PangoShowFlags
269find_show_flags (const PangoAnalysis *analysis)
270{
271 GSList *l;
272 PangoShowFlags flags = 0;
273
274 for (l = analysis->extra_attrs; l; l = l->next)
275 {
276 PangoAttribute *attr = l->data;
277
278 if (attr->klass->type == PANGO_ATTR_SHOW)
279 flags |= ((PangoAttrInt*)attr)->value;
280 }
281
282 return flags;
283}
284
285static PangoTextTransform
286find_text_transform (const PangoAnalysis *analysis)
287{
288 GSList *l;
289 PangoTextTransform transform = PANGO_TEXT_TRANSFORM_NONE;
290
291 for (l = analysis->extra_attrs; l; l = l->next)
292 {
293 PangoAttribute *attr = l->data;
294
295 if (attr->klass->type == PANGO_ATTR_TEXT_TRANSFORM)
296 transform = (PangoTextTransform) ((PangoAttrInt*)attr)->value;
297 }
298
299 return transform;
300}
301
302static gboolean
303glyph_has_color (hb_font_t *font,
304 hb_codepoint_t glyph)
305{
306 hb_face_t *face;
307 hb_blob_t *blob;
308
309 face = hb_font_get_face (font);
310
311 if (hb_ot_color_glyph_get_layers (face, glyph, start_offset: 0, NULL, NULL) > 0)
312 return TRUE;
313
314 if (hb_ot_color_has_png (face))
315 {
316 blob = hb_ot_color_glyph_reference_png (font, glyph);
317 if (blob)
318 {
319 guint length = hb_blob_get_length (blob);
320 hb_blob_destroy (blob);
321 if (length > 0)
322 return TRUE;
323 }
324 }
325
326 if (hb_ot_color_has_svg (face))
327 {
328 blob = hb_ot_color_glyph_reference_svg (face, glyph);
329 if (blob)
330 {
331 guint length = hb_blob_get_length (blob);
332 hb_blob_destroy (blob);
333 if (length > 0)
334 return TRUE;
335 }
336 }
337
338 return FALSE;
339}
340
341/* }}} */
342
343static void
344pango_hb_shape (const char *item_text,
345 int item_length,
346 const char *paragraph_text,
347 int paragraph_length,
348 const PangoAnalysis *analysis,
349 PangoLogAttr *log_attrs,
350 int num_chars,
351 PangoGlyphString *glyphs,
352 PangoShapeFlags flags)
353{
354 PangoHbShapeContext context = { 0, };
355 hb_buffer_flags_t hb_buffer_flags;
356 hb_font_t *hb_font;
357 hb_buffer_t *hb_buffer;
358 hb_direction_t hb_direction;
359 gboolean free_buffer;
360 hb_glyph_info_t *hb_glyph;
361 hb_glyph_position_t *hb_position;
362 int last_cluster;
363 guint i, num_glyphs;
364 unsigned int item_offset = item_text - paragraph_text;
365 hb_feature_t features[32];
366 unsigned int num_features = 0;
367 PangoGlyphInfo *infos;
368 PangoTextTransform transform;
369 int hyphen_index;
370
371 g_return_if_fail (analysis != NULL);
372 g_return_if_fail (analysis->font != NULL);
373
374 context.show_flags = find_show_flags (analysis);
375 hb_font = pango_font_get_hb_font_for_context (font: analysis->font, context: &context);
376 hb_buffer = acquire_buffer (free_buffer: &free_buffer);
377
378 transform = find_text_transform (analysis);
379
380 hb_direction = PANGO_GRAVITY_IS_VERTICAL (analysis->gravity) ? HB_DIRECTION_TTB : HB_DIRECTION_LTR;
381 if (analysis->level % 2)
382 hb_direction = HB_DIRECTION_REVERSE (hb_direction);
383 if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
384 hb_direction = HB_DIRECTION_REVERSE (hb_direction);
385
386 hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT;
387
388 if (context.show_flags & PANGO_SHOW_IGNORABLES)
389 hb_buffer_flags |= HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES;
390
391 /* setup buffer */
392
393 hb_buffer_set_direction (buffer: hb_buffer, direction: hb_direction);
394 hb_buffer_set_script (buffer: hb_buffer, script: (hb_script_t) g_unicode_script_to_iso15924 (script: analysis->script));
395 hb_buffer_set_language (buffer: hb_buffer, language: hb_language_from_string (pango_language_to_string (analysis->language), len: -1));
396 hb_buffer_set_cluster_level (buffer: hb_buffer, cluster_level: HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
397 hb_buffer_set_flags (buffer: hb_buffer, flags: hb_buffer_flags);
398 hb_buffer_set_invisible_glyph (buffer: hb_buffer, PANGO_GLYPH_EMPTY);
399
400 if (analysis->flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
401 {
402 const char *p = paragraph_text + item_offset + item_length;
403 int last_char_len = p - g_utf8_prev_char (p);
404
405 hyphen_index = item_offset + item_length - last_char_len;
406
407 if (log_attrs[num_chars].break_removes_preceding)
408 item_length -= last_char_len;
409 }
410
411 /* Add pre-context */
412 hb_buffer_add_utf8 (buffer: hb_buffer, text: paragraph_text, text_length: item_offset, item_offset, item_length: 0);
413
414 if (transform == PANGO_TEXT_TRANSFORM_NONE)
415 {
416 hb_buffer_add_utf8 (buffer: hb_buffer, text: paragraph_text, text_length: item_offset + item_length, item_offset, item_length);
417 }
418 else
419 {
420 const char *p;
421 int i;
422
423 /* Transform the item text according to text transform.
424 * Note: we assume text transforms won't cross font boundaries
425 */
426 for (p = paragraph_text + item_offset, i = 0;
427 p < paragraph_text + item_offset + item_length;
428 p = g_utf8_next_char (p), i++)
429 {
430 int index = p - paragraph_text;
431 gunichar ch = g_utf8_get_char (p);
432 char *str = NULL;
433
434 switch (transform)
435 {
436 case PANGO_TEXT_TRANSFORM_LOWERCASE:
437 if (g_unichar_isalnum (c: ch))
438 str = g_utf8_strdown (str: p, g_utf8_next_char (p) - p);
439 break;
440
441 case PANGO_TEXT_TRANSFORM_UPPERCASE:
442 if (g_unichar_isalnum (c: ch))
443 str = g_utf8_strup (str: p, g_utf8_next_char (p) - p);
444 break;
445
446 case PANGO_TEXT_TRANSFORM_CAPITALIZE:
447 if (log_attrs[i].is_word_start)
448 ch = g_unichar_totitle (c: ch);
449 break;
450
451 case PANGO_TEXT_TRANSFORM_NONE:
452 default:
453 g_assert_not_reached ();
454 }
455
456 if (str)
457 {
458 for (const char *q = str; *q; q = g_utf8_next_char (q))
459 {
460 ch = g_utf8_get_char (p: q);
461 hb_buffer_add (buffer: hb_buffer, codepoint: ch, cluster: index);
462 }
463 g_free (mem: str);
464 }
465 else
466 hb_buffer_add (buffer: hb_buffer, codepoint: ch, cluster: index);
467 }
468 }
469
470 /* Add post-context */
471 hb_buffer_add_utf8 (buffer: hb_buffer, text: paragraph_text, text_length: paragraph_length, item_offset: item_offset + item_length, item_length: 0);
472
473 if (analysis->flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
474 {
475 /* Insert either a Unicode or ASCII hyphen. We may
476 * want to look for script-specific hyphens here.
477 */
478 hb_codepoint_t glyph;
479
480 /* Note: We rely on hb_buffer_add clearing existing post-context */
481 if (hb_font_get_nominal_glyph (font: hb_font, unicode: 0x2010, glyph: &glyph))
482 hb_buffer_add (buffer: hb_buffer, codepoint: 0x2010, cluster: hyphen_index);
483 else if (hb_font_get_nominal_glyph (font: hb_font, unicode: '-', glyph: &glyph))
484 hb_buffer_add (buffer: hb_buffer, codepoint: '-', cluster: hyphen_index);
485 }
486
487 pango_analysis_collect_features (analysis, features, G_N_ELEMENTS (features), num_features: &num_features);
488
489 hb_shape (font: hb_font, buffer: hb_buffer, features, num_features);
490
491 if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
492 hb_buffer_reverse (buffer: hb_buffer);
493
494 /* buffer output */
495 num_glyphs = hb_buffer_get_length (buffer: hb_buffer);
496 hb_glyph = hb_buffer_get_glyph_infos (buffer: hb_buffer, NULL);
497 pango_glyph_string_set_size (string: glyphs, new_len: num_glyphs);
498 infos = glyphs->glyphs;
499 last_cluster = -1;
500
501 for (i = 0; i < num_glyphs; i++)
502 {
503 infos[i].glyph = hb_glyph->codepoint;
504 glyphs->log_clusters[i] = hb_glyph->cluster - item_offset;
505 infos[i].attr.is_cluster_start = glyphs->log_clusters[i] != last_cluster;
506 infos[i].attr.is_color = glyph_has_color (font: hb_font, glyph: hb_glyph->codepoint);
507 hb_glyph++;
508 last_cluster = glyphs->log_clusters[i];
509 }
510
511 hb_position = hb_buffer_get_glyph_positions (buffer: hb_buffer, NULL);
512 if (PANGO_GRAVITY_IS_VERTICAL (analysis->gravity))
513 for (i = 0; i < num_glyphs; i++)
514 {
515 /* 90 degrees rotation counter-clockwise. */
516 infos[i].geometry.width = - hb_position->y_advance;
517 infos[i].geometry.x_offset = - hb_position->y_offset;
518 infos[i].geometry.y_offset = - hb_position->x_offset;
519 hb_position++;
520 }
521 else /* horizontal */
522 for (i = 0; i < num_glyphs; i++)
523 {
524 infos[i].geometry.width = hb_position->x_advance;
525 infos[i].geometry.x_offset = hb_position->x_offset;
526 infos[i].geometry.y_offset = - hb_position->y_offset;
527 hb_position++;
528 }
529
530 release_buffer (buffer: hb_buffer, free_buffer);
531 hb_font_destroy (font: hb_font);
532}
533
534/* }}} */
535/* {{{ Fallback shaping */
536
537/* This is not meant to produce reasonable results */
538
539static void
540fallback_shape (const char *text,
541 unsigned int length,
542 const PangoAnalysis *analysis,
543 PangoGlyphString *glyphs)
544{
545 int n_chars;
546 const char *p;
547 int cluster = 0;
548 int i;
549
550 n_chars = text ? pango_utf8_strlen (p: text, max: length) : 0;
551
552 pango_glyph_string_set_size (string: glyphs, new_len: n_chars);
553
554 p = text;
555 for (i = 0; i < n_chars; i++)
556 {
557 gunichar wc;
558 PangoGlyph glyph;
559 PangoRectangle logical_rect;
560
561 wc = g_utf8_get_char (p);
562
563 if (g_unichar_type (c: wc) != G_UNICODE_NON_SPACING_MARK)
564 cluster = p - text;
565
566 if (pango_is_zero_width (ch: wc))
567 glyph = PANGO_GLYPH_EMPTY;
568 else
569 glyph = PANGO_GET_UNKNOWN_GLYPH (wc);
570
571 pango_font_get_glyph_extents (font: analysis->font, glyph, NULL, logical_rect: &logical_rect);
572
573 glyphs->glyphs[i].glyph = glyph;
574
575 glyphs->glyphs[i].geometry.x_offset = 0;
576 glyphs->glyphs[i].geometry.y_offset = 0;
577 glyphs->glyphs[i].geometry.width = logical_rect.width;
578
579 glyphs->log_clusters[i] = cluster;
580
581 p = g_utf8_next_char (p);
582 }
583
584 if (analysis->level & 1)
585 pango_glyph_string_reverse_range (glyphs, start: 0, end: glyphs->num_glyphs);
586}
587
588/* }}} */
589/* {{{ Shaping implementation */
590
591static void
592pango_shape_internal (const char *item_text,
593 int item_length,
594 const char *paragraph_text,
595 int paragraph_length,
596 const PangoAnalysis *analysis,
597 PangoLogAttr *log_attrs,
598 int num_chars,
599 PangoGlyphString *glyphs,
600 PangoShapeFlags flags)
601{
602 int i;
603 int last_cluster;
604
605 glyphs->num_glyphs = 0;
606
607 if (item_length == -1)
608 item_length = strlen (s: item_text);
609
610 if (!paragraph_text)
611 {
612 paragraph_text = item_text;
613 paragraph_length = item_length;
614 }
615 if (paragraph_length == -1)
616 paragraph_length = strlen (s: paragraph_text);
617
618 g_return_if_fail (paragraph_text <= item_text);
619 g_return_if_fail (paragraph_text + paragraph_length >= item_text + item_length);
620
621 if (analysis->font)
622 {
623 pango_hb_shape (item_text, item_length,
624 paragraph_text, paragraph_length,
625 analysis,
626 log_attrs, num_chars,
627 glyphs, flags);
628
629 if (G_UNLIKELY (glyphs->num_glyphs == 0))
630 {
631 /* If a font has been correctly chosen, but no glyphs are output,
632 * there's probably something wrong with the font.
633 *
634 * Trying to be informative, we print out the font description,
635 * and the text, but to not flood the terminal with
636 * zillions of the message, we set a flag to only err once per
637 * font.
638 */
639 GQuark warned_quark = g_quark_from_static_string (string: "pango-shape-fail-warned");
640
641 if (!g_object_get_qdata (G_OBJECT (analysis->font), quark: warned_quark))
642 {
643 PangoFontDescription *desc;
644 char *font_name;
645
646 desc = pango_font_describe (font: analysis->font);
647 font_name = pango_font_description_to_string (desc);
648 pango_font_description_free (desc);
649
650 g_warning ("shaping failure, expect ugly output. font='%s', text='%.*s'",
651 font_name, item_length, item_text);
652
653 g_free (mem: font_name);
654
655 g_object_set_qdata (G_OBJECT (analysis->font), quark: warned_quark,
656 GINT_TO_POINTER (1));
657 }
658 }
659 }
660 else
661 glyphs->num_glyphs = 0;
662
663 if (G_UNLIKELY (!glyphs->num_glyphs))
664 {
665 fallback_shape (text: item_text, length: item_length, analysis, glyphs);
666 if (G_UNLIKELY (!glyphs->num_glyphs))
667 return;
668 }
669
670 /* make sure last_cluster is invalid */
671 last_cluster = glyphs->log_clusters[0] - 1;
672 for (i = 0; i < glyphs->num_glyphs; i++)
673 {
674 /* Set glyphs[i].attr.is_cluster_start based on log_clusters[] */
675 if (glyphs->log_clusters[i] != last_cluster)
676 {
677 glyphs->glyphs[i].attr.is_cluster_start = TRUE;
678 last_cluster = glyphs->log_clusters[i];
679 }
680 else
681 glyphs->glyphs[i].attr.is_cluster_start = FALSE;
682
683
684 /* Shift glyph if width is negative, and negate width.
685 * This is useful for rotated font matrices and shouldn't
686 * harm in normal cases.
687 */
688 if (glyphs->glyphs[i].geometry.width < 0)
689 {
690 glyphs->glyphs[i].geometry.width = -glyphs->glyphs[i].geometry.width;
691 glyphs->glyphs[i].geometry.x_offset += glyphs->glyphs[i].geometry.width;
692 }
693 }
694
695 /* Make sure glyphstring direction conforms to analysis->level */
696 if (G_UNLIKELY ((analysis->level & 1) &&
697 glyphs->log_clusters[0] < glyphs->log_clusters[glyphs->num_glyphs - 1]))
698 {
699 g_warning ("Expected RTL run but got LTR. Fixing.");
700
701 /* *Fix* it so we don't crash later */
702 pango_glyph_string_reverse_range (glyphs, start: 0, end: glyphs->num_glyphs);
703 }
704
705 if (flags & PANGO_SHAPE_ROUND_POSITIONS)
706 {
707 if (analysis->font && pango_font_is_hinted (font: analysis->font))
708 {
709 double x_scale_inv, y_scale_inv;
710 double x_scale, y_scale;
711
712 pango_font_get_scale_factors (font: analysis->font, x_scale: &x_scale_inv, y_scale: &y_scale_inv);
713
714 if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
715 {
716 x_scale_inv = -x_scale_inv;
717 y_scale_inv = -y_scale_inv;
718 }
719
720 x_scale = 1.0 / x_scale_inv;
721 y_scale = 1.0 / y_scale_inv;
722
723 if (x_scale == 1.0 && y_scale == 1.0)
724 {
725 for (i = 0; i < glyphs->num_glyphs; i++)
726 glyphs->glyphs[i].geometry.width = PANGO_UNITS_ROUND (glyphs->glyphs[i].geometry.width);
727 }
728 else
729 {
730 #if 0
731 if (PANGO_GRAVITY_IS_VERTICAL (analysis->gravity))
732 {
733 /* XXX */
734 double tmp = x_scale;
735 x_scale = y_scale;
736 y_scale = -tmp;
737 }
738 #endif
739 #define HINT(value, scale_inv, scale) (PANGO_UNITS_ROUND ((int) ((value) * scale)) * scale_inv)
740 #define HINT_X(value) HINT ((value), x_scale, x_scale_inv)
741 #define HINT_Y(value) HINT ((value), y_scale, y_scale_inv)
742 for (i = 0; i < glyphs->num_glyphs; i++)
743 {
744 glyphs->glyphs[i].geometry.width = HINT_X (glyphs->glyphs[i].geometry.width);
745 glyphs->glyphs[i].geometry.x_offset = HINT_X (glyphs->glyphs[i].geometry.x_offset);
746 glyphs->glyphs[i].geometry.y_offset = HINT_Y (glyphs->glyphs[i].geometry.y_offset);
747 }
748 #undef HINT_Y
749 #undef HINT_X
750 #undef HINT
751 }
752 }
753 else
754 {
755 for (i = 0; i < glyphs->num_glyphs; i++)
756 {
757 glyphs->glyphs[i].geometry.width =
758 PANGO_UNITS_ROUND (glyphs->glyphs[i].geometry.width);
759 glyphs->glyphs[i].geometry.x_offset =
760 PANGO_UNITS_ROUND (glyphs->glyphs[i].geometry.x_offset);
761 glyphs->glyphs[i].geometry.y_offset =
762 PANGO_UNITS_ROUND (glyphs->glyphs[i].geometry.y_offset);
763 }
764 }
765 }
766}
767
768/* }}} */
769/* {{{ Public API */
770
771/**
772 * pango_shape:
773 * @text: the text to process
774 * @length: the length (in bytes) of @text
775 * @analysis: `PangoAnalysis` structure from [func@Pango.itemize]
776 * @glyphs: glyph string in which to store results
777 *
778 * Convert the characters in @text into glyphs.
779 *
780 * Given a segment of text and the corresponding `PangoAnalysis` structure
781 * returned from [func@Pango.itemize], convert the characters into glyphs. You
782 * may also pass in only a substring of the item from [func@Pango.itemize].
783 *
784 * It is recommended that you use [func@Pango.shape_full] instead, since
785 * that API allows for shaping interaction happening across text item
786 * boundaries.
787 *
788 * Note that the extra attributes in the @analyis that is returned from
789 * [func@Pango.itemize] have indices that are relative to the entire paragraph,
790 * so you need to subtract the item offset from their indices before
791 * calling [func@Pango.shape].
792 */
793void
794pango_shape (const char *text,
795 int length,
796 const PangoAnalysis *analysis,
797 PangoGlyphString *glyphs)
798{
799 pango_shape_full (item_text: text, item_length: length, paragraph_text: text, paragraph_length: length, analysis, glyphs);
800}
801
802/**
803 * pango_shape_full:
804 * @item_text: valid UTF-8 text to shape.
805 * @item_length: the length (in bytes) of @item_text. -1 means nul-terminated text.
806 * @paragraph_text: (nullable): text of the paragraph (see details).
807 * @paragraph_length: the length (in bytes) of @paragraph_text. -1 means nul-terminated text.
808 * @analysis: `PangoAnalysis` structure from [func@Pango.itemize].
809 * @glyphs: glyph string in which to store results.
810 *
811 * Convert the characters in @text into glyphs.
812 *
813 * Given a segment of text and the corresponding `PangoAnalysis` structure
814 * returned from [func@Pango.itemize], convert the characters into glyphs.
815 * You may also pass in only a substring of the item from [func@Pango.itemize].
816 *
817 * This is similar to [func@Pango.shape], except it also can optionally take
818 * the full paragraph text as input, which will then be used to perform
819 * certain cross-item shaping interactions. If you have access to the broader
820 * text of which @item_text is part of, provide the broader text as
821 * @paragraph_text. If @paragraph_text is %NULL, item text is used instead.
822 *
823 * Note that the extra attributes in the @analyis that is returned from
824 * [func@Pango.itemize] have indices that are relative to the entire paragraph,
825 * so you do not pass the full paragraph text as @paragraph_text, you need
826 * to subtract the item offset from their indices before calling
827 * [func@Pango.shape_full].
828 *
829 * Since: 1.32
830 */
831void
832pango_shape_full (const char *item_text,
833 int item_length,
834 const char *paragraph_text,
835 int paragraph_length,
836 const PangoAnalysis *analysis,
837 PangoGlyphString *glyphs)
838{
839 pango_shape_with_flags (item_text, item_length,
840 paragraph_text, paragraph_length,
841 analysis,
842 glyphs,
843 flags: PANGO_SHAPE_NONE);
844}
845
846/**
847 * pango_shape_with_flags:
848 * @item_text: valid UTF-8 text to shape
849 * @item_length: the length (in bytes) of @item_text.
850 * -1 means nul-terminated text.
851 * @paragraph_text: (nullable): text of the paragraph (see details).
852 * @paragraph_length: the length (in bytes) of @paragraph_text.
853 * -1 means nul-terminated text.
854 * @analysis: `PangoAnalysis` structure from [func@Pango.itemize]
855 * @glyphs: glyph string in which to store results
856 * @flags: flags influencing the shaping process
857 *
858 * Convert the characters in @text into glyphs.
859 *
860 * Given a segment of text and the corresponding `PangoAnalysis` structure
861 * returned from [func@Pango.itemize], convert the characters into glyphs.
862 * You may also pass in only a substring of the item from [func@Pango.itemize].
863 *
864 * This is similar to [func@Pango.shape_full], except it also takes flags
865 * that can influence the shaping process.
866 *
867 * Note that the extra attributes in the @analyis that is returned from
868 * [func@Pango.itemize] have indices that are relative to the entire paragraph,
869 * so you do not pass the full paragraph text as @paragraph_text, you need
870 * to subtract the item offset from their indices before calling
871 * [func@Pango.shape_with_flags].
872 *
873 * Since: 1.44
874 */
875void
876pango_shape_with_flags (const char *item_text,
877 int item_length,
878 const char *paragraph_text,
879 int paragraph_length,
880 const PangoAnalysis *analysis,
881 PangoGlyphString *glyphs,
882 PangoShapeFlags flags)
883{
884 pango_shape_internal (item_text, item_length,
885 paragraph_text, paragraph_length,
886 analysis, NULL, num_chars: 0,
887 glyphs, flags);
888}
889
890/**
891 * pango_shape_item:
892 * @item: `PangoItem` to shape
893 * @paragraph_text: (nullable): text of the paragraph (see details).
894 * @paragraph_length: the length (in bytes) of @paragraph_text.
895 * -1 means nul-terminated text.
896 * @log_attrs: (nullable): array of `PangoLogAttr` for @item
897 * @glyphs: glyph string in which to store results
898 * @flags: flags influencing the shaping process
899 *
900 * Convert the characters in @item into glyphs.
901 *
902 * This is similar to [func@Pango.shape_with_flags], except it takes a
903 * `PangoItem` instead of separate @item_text and @analysis arguments.
904 * It also takes @log_attrs, which may be used in implementing text
905 * transforms.
906 *
907 * Note that the extra attributes in the @analyis that is returned from
908 * [func@Pango.itemize] have indices that are relative to the entire paragraph,
909 * so you do not pass the full paragraph text as @paragraph_text, you need
910 * to subtract the item offset from their indices before calling
911 * [func@Pango.shape_with_flags].
912 *
913 * Since: 1.50
914 */
915void
916pango_shape_item (PangoItem *item,
917 const char *paragraph_text,
918 int paragraph_length,
919 PangoLogAttr *log_attrs,
920 PangoGlyphString *glyphs,
921 PangoShapeFlags flags)
922{
923 pango_shape_internal (item_text: paragraph_text + item->offset, item_length: item->length,
924 paragraph_text, paragraph_length,
925 analysis: &item->analysis,
926 log_attrs, num_chars: item->num_chars,
927 glyphs, flags);
928}
929
930/* }}} */
931
932/* vim:set foldmethod=marker expandtab: */
933

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