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 | |
39 | static hb_buffer_t *cached_buffer = NULL; /* MT-safe */ |
40 | G_LOCK_DEFINE_STATIC (cached_buffer); |
41 | |
42 | static hb_buffer_t * |
43 | acquire_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 | |
64 | static void |
65 | release_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 | |
80 | typedef struct |
81 | { |
82 | PangoFont *font; |
83 | hb_font_t *parent; |
84 | PangoShowFlags show_flags; |
85 | } PangoHbShapeContext; |
86 | |
87 | static hb_bool_t |
88 | pango_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 | |
170 | static hb_position_t |
171 | pango_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 | |
189 | static hb_position_t |
190 | pango_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 | |
208 | static hb_bool_t |
209 | pango_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 | |
234 | static hb_font_t * |
235 | pango_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 | |
268 | static PangoShowFlags |
269 | find_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 | |
285 | static PangoTextTransform |
286 | find_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 | |
302 | static gboolean |
303 | glyph_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 | |
343 | static void |
344 | pango_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 | |
539 | static void |
540 | fallback_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 | |
591 | static void |
592 | pango_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 | */ |
793 | void |
794 | pango_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 | */ |
831 | void |
832 | pango_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 | */ |
875 | void |
876 | pango_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 | */ |
915 | void |
916 | pango_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 | |