2 * Copyright © 1998-2004 David Turner and Werner Lemberg
3 * Copyright © 2004,2007,2009,2010 Red Hat, Inc.
4 * Copyright © 2011,2012 Google, Inc.
6 * This is part of HarfBuzz, a text shaping library.
8 * Permission is hereby granted, without written agreement and without
9 * license or royalty fees, to use, copy, modify, and distribute this
10 * software and its documentation for any purpose, provided that the
11 * above copyright notice and the following two paragraphs appear in
12 * all copies of this software.
14 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
15 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
16 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
17 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
20 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
21 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
22 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
23 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
24 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
26 * Red Hat Author(s): Owen Taylor, Behdad Esfahbod
27 * Google Author(s): Behdad Esfahbod
30 #include "hb-buffer-private.hh"
31 #include "hb-utf-private.hh"
34 #ifndef HB_DEBUG_BUFFER
35 #define HB_DEBUG_BUFFER (HB_DEBUG+0)
38 /* Here is how the buffer works internally:
40 * There are two info pointers: info and out_info. They always have
41 * the same allocated size, but different lengths.
43 * As an optimization, both info and out_info may point to the
44 * same piece of memory, which is owned by info. This remains the
45 * case as long as out_len doesn't exceed i at any time.
46 * In that case, swap_buffers() is no-op and the glyph operations operate
49 * As soon as out_info gets longer than info, out_info is moved over
50 * to an alternate buffer (which we reuse the pos buffer for!), and its
51 * current contents (out_len entries) are copied to the new place.
52 * This should all remain transparent to the user. swap_buffers() then
53 * switches info and out_info.
61 hb_buffer_t::enlarge (unsigned int size
)
63 if (unlikely (in_error
))
66 unsigned int new_allocated
= allocated
;
67 hb_glyph_position_t
*new_pos
= NULL
;
68 hb_glyph_info_t
*new_info
= NULL
;
69 bool separate_out
= out_info
!= info
;
71 if (unlikely (_hb_unsigned_int_mul_overflows (size
, sizeof (info
[0]))))
74 while (size
>= new_allocated
)
75 new_allocated
+= (new_allocated
>> 1) + 32;
77 ASSERT_STATIC (sizeof (info
[0]) == sizeof (pos
[0]));
78 if (unlikely (_hb_unsigned_int_mul_overflows (new_allocated
, sizeof (info
[0]))))
81 new_pos
= (hb_glyph_position_t
*) realloc (pos
, new_allocated
* sizeof (pos
[0]));
82 new_info
= (hb_glyph_info_t
*) realloc (info
, new_allocated
* sizeof (info
[0]));
85 if (unlikely (!new_pos
|| !new_info
))
91 if (likely (new_info
))
94 out_info
= separate_out
? (hb_glyph_info_t
*) pos
: info
;
95 if (likely (!in_error
))
96 allocated
= new_allocated
;
98 return likely (!in_error
);
102 hb_buffer_t::make_room_for (unsigned int num_in
,
103 unsigned int num_out
)
105 if (unlikely (!ensure (out_len
+ num_out
))) return false;
107 if (out_info
== info
&&
108 out_len
+ num_out
> idx
+ num_in
)
110 assert (have_output
);
112 out_info
= (hb_glyph_info_t
*) pos
;
113 memcpy (out_info
, info
, out_len
* sizeof (out_info
[0]));
120 hb_buffer_t::get_scratch_buffer (unsigned int *size
)
123 have_positions
= false;
128 *size
= allocated
* sizeof (pos
[0]);
134 /* HarfBuzz-Internal API */
137 hb_buffer_t::reset (void)
139 if (unlikely (hb_object_is_inert (this)))
142 hb_unicode_funcs_destroy (unicode
);
143 unicode
= hb_unicode_funcs_get_default ();
145 hb_segment_properties_t default_props
= _HB_BUFFER_PROPS_DEFAULT
;
146 props
= default_props
;
148 content_type
= HB_BUFFER_CONTENT_TYPE_INVALID
;
151 have_positions
= false;
159 memset (allocated_var_bytes
, 0, sizeof allocated_var_bytes
);
160 memset (allocated_var_owner
, 0, sizeof allocated_var_owner
);
162 memset (context
, 0, sizeof context
);
163 memset (context_len
, 0, sizeof context_len
);
167 hb_buffer_t::add (hb_codepoint_t codepoint
,
169 unsigned int cluster
)
171 hb_glyph_info_t
*glyph
;
173 if (unlikely (!ensure (len
+ 1))) return;
177 memset (glyph
, 0, sizeof (*glyph
));
178 glyph
->codepoint
= codepoint
;
180 glyph
->cluster
= cluster
;
186 hb_buffer_t::remove_output (void)
188 if (unlikely (hb_object_is_inert (this)))
192 have_positions
= false;
199 hb_buffer_t::clear_output (void)
201 if (unlikely (hb_object_is_inert (this)))
205 have_positions
= false;
212 hb_buffer_t::clear_positions (void)
214 if (unlikely (hb_object_is_inert (this)))
218 have_positions
= true;
223 memset (pos
, 0, sizeof (pos
[0]) * len
);
227 hb_buffer_t::swap_buffers (void)
229 if (unlikely (in_error
)) return;
231 assert (have_output
);
234 if (out_info
!= info
)
236 hb_glyph_info_t
*tmp_string
;
239 out_info
= tmp_string
;
240 pos
= (hb_glyph_position_t
*) out_info
;
253 hb_buffer_t::replace_glyphs (unsigned int num_in
,
254 unsigned int num_out
,
255 const uint32_t *glyph_data
)
257 if (unlikely (!make_room_for (num_in
, num_out
))) return;
259 merge_clusters (idx
, idx
+ num_in
);
261 hb_glyph_info_t orig_info
= info
[idx
];
262 hb_glyph_info_t
*pinfo
= &out_info
[out_len
];
263 for (unsigned int i
= 0; i
< num_out
; i
++)
266 pinfo
->codepoint
= glyph_data
[i
];
275 hb_buffer_t::output_glyph (hb_codepoint_t glyph_index
)
277 if (unlikely (!make_room_for (0, 1))) return;
279 out_info
[out_len
] = info
[idx
];
280 out_info
[out_len
].codepoint
= glyph_index
;
286 hb_buffer_t::output_info (hb_glyph_info_t
&glyph_info
)
288 if (unlikely (!make_room_for (0, 1))) return;
290 out_info
[out_len
] = glyph_info
;
296 hb_buffer_t::copy_glyph (void)
298 if (unlikely (!make_room_for (0, 1))) return;
300 out_info
[out_len
] = info
[idx
];
306 hb_buffer_t::replace_glyph (hb_codepoint_t glyph_index
)
308 if (unlikely (out_info
!= info
|| out_len
!= idx
)) {
309 if (unlikely (!make_room_for (1, 1))) return;
310 out_info
[out_len
] = info
[idx
];
312 out_info
[out_len
].codepoint
= glyph_index
;
320 hb_buffer_t::set_masks (hb_mask_t value
,
322 unsigned int cluster_start
,
323 unsigned int cluster_end
)
325 hb_mask_t not_mask
= ~mask
;
331 if (cluster_start
== 0 && cluster_end
== (unsigned int)-1) {
332 unsigned int count
= len
;
333 for (unsigned int i
= 0; i
< count
; i
++)
334 info
[i
].mask
= (info
[i
].mask
& not_mask
) | value
;
338 unsigned int count
= len
;
339 for (unsigned int i
= 0; i
< count
; i
++)
340 if (cluster_start
<= info
[i
].cluster
&& info
[i
].cluster
< cluster_end
)
341 info
[i
].mask
= (info
[i
].mask
& not_mask
) | value
;
345 hb_buffer_t::reverse_range (unsigned int start
,
350 if (start
== end
- 1)
353 for (i
= start
, j
= end
- 1; i
< j
; i
++, j
--) {
362 for (i
= start
, j
= end
- 1; i
< j
; i
++, j
--) {
363 hb_glyph_position_t t
;
373 hb_buffer_t::reverse (void)
378 reverse_range (0, len
);
382 hb_buffer_t::reverse_clusters (void)
384 unsigned int i
, start
, count
, last_cluster
;
393 last_cluster
= info
[0].cluster
;
394 for (i
= 1; i
< count
; i
++) {
395 if (last_cluster
!= info
[i
].cluster
) {
396 reverse_range (start
, i
);
398 last_cluster
= info
[i
].cluster
;
401 reverse_range (start
, i
);
405 hb_buffer_t::merge_clusters (unsigned int start
,
408 if (unlikely (end
- start
< 2))
411 unsigned int cluster
= info
[start
].cluster
;
413 for (unsigned int i
= start
+ 1; i
< end
; i
++)
414 cluster
= MIN (cluster
, info
[i
].cluster
);
417 while (end
< len
&& info
[end
- 1].cluster
== info
[end
].cluster
)
421 while (idx
< start
&& info
[start
- 1].cluster
== info
[start
].cluster
)
424 /* If we hit the start of buffer, continue in out-buffer. */
426 for (unsigned i
= out_len
; i
&& out_info
[i
- 1].cluster
== info
[start
].cluster
; i
--)
427 out_info
[i
- 1].cluster
= cluster
;
429 for (unsigned int i
= start
; i
< end
; i
++)
430 info
[i
].cluster
= cluster
;
433 hb_buffer_t::merge_out_clusters (unsigned int start
,
436 if (unlikely (end
- start
< 2))
439 unsigned int cluster
= out_info
[start
].cluster
;
441 for (unsigned int i
= start
+ 1; i
< end
; i
++)
442 cluster
= MIN (cluster
, out_info
[i
].cluster
);
445 while (start
&& out_info
[start
- 1].cluster
== out_info
[start
].cluster
)
449 while (end
< out_len
&& out_info
[end
- 1].cluster
== out_info
[end
].cluster
)
452 /* If we hit the end of out-buffer, continue in buffer. */
454 for (unsigned i
= idx
; i
< len
&& info
[i
].cluster
== out_info
[end
- 1].cluster
; i
++)
455 info
[i
].cluster
= cluster
;
457 for (unsigned int i
= start
; i
< end
; i
++)
458 out_info
[i
].cluster
= cluster
;
462 hb_buffer_t::guess_properties (void)
464 if (unlikely (!len
)) return;
465 assert (content_type
== HB_BUFFER_CONTENT_TYPE_UNICODE
);
467 /* If script is set to INVALID, guess from buffer contents */
468 if (props
.script
== HB_SCRIPT_INVALID
) {
469 for (unsigned int i
= 0; i
< len
; i
++) {
470 hb_script_t script
= unicode
->script (info
[i
].codepoint
);
471 if (likely (script
!= HB_SCRIPT_COMMON
&&
472 script
!= HB_SCRIPT_INHERITED
&&
473 script
!= HB_SCRIPT_UNKNOWN
)) {
474 props
.script
= script
;
480 /* If direction is set to INVALID, guess from script */
481 if (props
.direction
== HB_DIRECTION_INVALID
) {
482 props
.direction
= hb_script_get_horizontal_direction (props
.script
);
485 /* If language is not set, use default language from locale */
486 if (props
.language
== HB_LANGUAGE_INVALID
) {
487 /* TODO get_default_for_script? using $LANGUAGE */
488 props
.language
= hb_language_get_default ();
494 dump_var_allocation (const hb_buffer_t
*buffer
)
497 for (unsigned int i
= 0; i
< 8; i
++)
498 buf
[i
] = '0' + buffer
->allocated_var_bytes
[7 - i
];
500 DEBUG_MSG (BUFFER
, buffer
,
501 "Current var allocation: %s",
505 void hb_buffer_t::allocate_var (unsigned int byte_i
, unsigned int count
, const char *owner
)
507 assert (byte_i
< 8 && byte_i
+ count
<= 8);
510 dump_var_allocation (this);
511 DEBUG_MSG (BUFFER
, this,
512 "Allocating var bytes %d..%d for %s",
513 byte_i
, byte_i
+ count
- 1, owner
);
515 for (unsigned int i
= byte_i
; i
< byte_i
+ count
; i
++) {
516 assert (!allocated_var_bytes
[i
]);
517 allocated_var_bytes
[i
]++;
518 allocated_var_owner
[i
] = owner
;
522 void hb_buffer_t::deallocate_var (unsigned int byte_i
, unsigned int count
, const char *owner
)
525 dump_var_allocation (this);
527 DEBUG_MSG (BUFFER
, this,
528 "Deallocating var bytes %d..%d for %s",
529 byte_i
, byte_i
+ count
- 1, owner
);
531 assert (byte_i
< 8 && byte_i
+ count
<= 8);
532 for (unsigned int i
= byte_i
; i
< byte_i
+ count
; i
++) {
533 assert (allocated_var_bytes
[i
]);
534 assert (0 == strcmp (allocated_var_owner
[i
], owner
));
535 allocated_var_bytes
[i
]--;
539 void hb_buffer_t::assert_var (unsigned int byte_i
, unsigned int count
, const char *owner
)
542 dump_var_allocation (this);
544 DEBUG_MSG (BUFFER
, this,
545 "Asserting var bytes %d..%d for %s",
546 byte_i
, byte_i
+ count
- 1, owner
);
548 assert (byte_i
< 8 && byte_i
+ count
<= 8);
549 for (unsigned int i
= byte_i
; i
< byte_i
+ count
; i
++) {
550 assert (allocated_var_bytes
[i
]);
551 assert (0 == strcmp (allocated_var_owner
[i
], owner
));
555 void hb_buffer_t::deallocate_var_all (void)
557 memset (allocated_var_bytes
, 0, sizeof (allocated_var_bytes
));
558 memset (allocated_var_owner
, 0, sizeof (allocated_var_owner
));
568 if (!(buffer
= hb_object_create
<hb_buffer_t
> ()))
569 return hb_buffer_get_empty ();
577 hb_buffer_get_empty (void)
579 static const hb_buffer_t _hb_buffer_nil
= {
580 HB_OBJECT_HEADER_STATIC
,
582 const_cast<hb_unicode_funcs_t
*> (&_hb_unicode_funcs_nil
),
583 _HB_BUFFER_PROPS_DEFAULT
,
585 HB_BUFFER_CONTENT_TYPE_INVALID
,
587 true, /* have_output */
588 true /* have_positions */
590 /* Zero is good enough for everything else. */
593 return const_cast<hb_buffer_t
*> (&_hb_buffer_nil
);
597 hb_buffer_reference (hb_buffer_t
*buffer
)
599 return hb_object_reference (buffer
);
603 hb_buffer_destroy (hb_buffer_t
*buffer
)
605 if (!hb_object_destroy (buffer
)) return;
607 hb_unicode_funcs_destroy (buffer
->unicode
);
616 hb_buffer_set_user_data (hb_buffer_t
*buffer
,
617 hb_user_data_key_t
*key
,
619 hb_destroy_func_t destroy
,
622 return hb_object_set_user_data (buffer
, key
, data
, destroy
, replace
);
626 hb_buffer_get_user_data (hb_buffer_t
*buffer
,
627 hb_user_data_key_t
*key
)
629 return hb_object_get_user_data (buffer
, key
);
634 hb_buffer_set_content_type (hb_buffer_t
*buffer
,
635 hb_buffer_content_type_t content_type
)
637 buffer
->content_type
= content_type
;
640 hb_buffer_content_type_t
641 hb_buffer_get_content_type (hb_buffer_t
*buffer
)
643 return buffer
->content_type
;
648 hb_buffer_set_unicode_funcs (hb_buffer_t
*buffer
,
649 hb_unicode_funcs_t
*unicode
)
651 if (unlikely (hb_object_is_inert (buffer
)))
655 unicode
= hb_unicode_funcs_get_default ();
658 hb_unicode_funcs_reference (unicode
);
659 hb_unicode_funcs_destroy (buffer
->unicode
);
660 buffer
->unicode
= unicode
;
664 hb_buffer_get_unicode_funcs (hb_buffer_t
*buffer
)
666 return buffer
->unicode
;
670 hb_buffer_set_direction (hb_buffer_t
*buffer
,
671 hb_direction_t direction
)
674 if (unlikely (hb_object_is_inert (buffer
)))
677 buffer
->props
.direction
= direction
;
681 hb_buffer_get_direction (hb_buffer_t
*buffer
)
683 return buffer
->props
.direction
;
687 hb_buffer_set_script (hb_buffer_t
*buffer
,
690 if (unlikely (hb_object_is_inert (buffer
)))
693 buffer
->props
.script
= script
;
697 hb_buffer_get_script (hb_buffer_t
*buffer
)
699 return buffer
->props
.script
;
703 hb_buffer_set_language (hb_buffer_t
*buffer
,
704 hb_language_t language
)
706 if (unlikely (hb_object_is_inert (buffer
)))
709 buffer
->props
.language
= language
;
713 hb_buffer_get_language (hb_buffer_t
*buffer
)
715 return buffer
->props
.language
;
720 hb_buffer_reset (hb_buffer_t
*buffer
)
726 hb_buffer_pre_allocate (hb_buffer_t
*buffer
, unsigned int size
)
728 return buffer
->ensure (size
);
732 hb_buffer_allocation_successful (hb_buffer_t
*buffer
)
734 return !buffer
->in_error
;
738 hb_buffer_add (hb_buffer_t
*buffer
,
739 hb_codepoint_t codepoint
,
741 unsigned int cluster
)
743 buffer
->add (codepoint
, mask
, cluster
);
744 buffer
->clear_context (1);
748 hb_buffer_set_length (hb_buffer_t
*buffer
,
751 if (unlikely (hb_object_is_inert (buffer
)))
754 if (!buffer
->ensure (length
))
757 /* Wipe the new space */
758 if (length
> buffer
->len
) {
759 memset (buffer
->info
+ buffer
->len
, 0, sizeof (buffer
->info
[0]) * (length
- buffer
->len
));
760 if (buffer
->have_positions
)
761 memset (buffer
->pos
+ buffer
->len
, 0, sizeof (buffer
->pos
[0]) * (length
- buffer
->len
));
764 buffer
->len
= length
;
767 buffer
->clear_context (0);
768 buffer
->clear_context (1);
774 hb_buffer_get_length (hb_buffer_t
*buffer
)
779 /* Return value valid as long as buffer not modified */
781 hb_buffer_get_glyph_infos (hb_buffer_t
*buffer
,
782 unsigned int *length
)
785 *length
= buffer
->len
;
787 return (hb_glyph_info_t
*) buffer
->info
;
790 /* Return value valid as long as buffer not modified */
791 hb_glyph_position_t
*
792 hb_buffer_get_glyph_positions (hb_buffer_t
*buffer
,
793 unsigned int *length
)
795 if (!buffer
->have_positions
)
796 buffer
->clear_positions ();
799 *length
= buffer
->len
;
801 return (hb_glyph_position_t
*) buffer
->pos
;
805 hb_buffer_reverse (hb_buffer_t
*buffer
)
811 hb_buffer_reverse_clusters (hb_buffer_t
*buffer
)
813 buffer
->reverse_clusters ();
817 hb_buffer_guess_properties (hb_buffer_t
*buffer
)
819 buffer
->guess_properties ();
822 template <typename T
>
824 hb_buffer_add_utf (hb_buffer_t
*buffer
,
827 unsigned int item_offset
,
830 assert (buffer
->content_type
== HB_BUFFER_CONTENT_TYPE_UNICODE
||
831 (!buffer
->len
&& buffer
->content_type
== HB_BUFFER_CONTENT_TYPE_INVALID
));
833 if (unlikely (hb_object_is_inert (buffer
)))
836 if (text_length
== -1)
837 text_length
= hb_utf_strlen (text
);
839 if (item_length
== -1)
840 item_length
= text_length
- item_offset
;
842 buffer
->ensure (buffer
->len
+ item_length
* sizeof (T
) / 4);
844 /* If buffer is empty and pre-context provided, install it.
845 * This check is written this way, to make sure people can
846 * provide pre-context in one add_utf() call, then provide
847 * text in a follow-up call. See:
849 * https://bugzilla.mozilla.org/show_bug.cgi?id=801410#c13
851 if (!buffer
->len
&& item_offset
> 0)
853 /* Add pre-context */
854 buffer
->clear_context (0);
855 const T
*prev
= text
+ item_offset
;
856 const T
*start
= text
;
857 while (start
< prev
&& buffer
->context_len
[0] < buffer
->CONTEXT_LENGTH
)
860 prev
= hb_utf_prev (prev
, start
, &u
);
861 buffer
->context
[0][buffer
->context_len
[0]++] = u
;
865 const T
*next
= text
+ item_offset
;
866 const T
*end
= next
+ item_length
;
870 const T
*old_next
= next
;
871 next
= hb_utf_next (next
, end
, &u
);
872 buffer
->add (u
, 1, old_next
- (const T
*) text
);
875 /* Add post-context */
876 buffer
->clear_context (1);
877 end
= text
+ text_length
;
878 while (next
< end
&& buffer
->context_len
[1] < buffer
->CONTEXT_LENGTH
)
881 next
= hb_utf_next (next
, end
, &u
);
882 buffer
->context
[1][buffer
->context_len
[1]++] = u
;
885 buffer
->content_type
= HB_BUFFER_CONTENT_TYPE_UNICODE
;
889 hb_buffer_add_utf8 (hb_buffer_t
*buffer
,
892 unsigned int item_offset
,
895 hb_buffer_add_utf (buffer
, (const uint8_t *) text
, text_length
, item_offset
, item_length
);
899 hb_buffer_add_utf16 (hb_buffer_t
*buffer
,
900 const uint16_t *text
,
902 unsigned int item_offset
,
905 hb_buffer_add_utf (buffer
, text
, text_length
, item_offset
, item_length
);
909 hb_buffer_add_utf32 (hb_buffer_t
*buffer
,
910 const uint32_t *text
,
912 unsigned int item_offset
,
915 hb_buffer_add_utf (buffer
, text
, text_length
, item_offset
, item_length
);
920 compare_info_codepoint (const hb_glyph_info_t
*pa
,
921 const hb_glyph_info_t
*pb
)
923 return (int) pb
->codepoint
- (int) pa
->codepoint
;
927 normalize_glyphs_cluster (hb_buffer_t
*buffer
,
932 hb_glyph_position_t
*pos
= buffer
->pos
;
934 /* Total cluster advance */
935 hb_position_t total_x_advance
= 0, total_y_advance
= 0;
936 for (unsigned int i
= start
; i
< end
; i
++)
938 total_x_advance
+= pos
[i
].x_advance
;
939 total_y_advance
+= pos
[i
].y_advance
;
942 hb_position_t x_advance
= 0, y_advance
= 0;
943 for (unsigned int i
= start
; i
< end
; i
++)
945 pos
[i
].x_offset
+= x_advance
;
946 pos
[i
].y_offset
+= y_advance
;
948 x_advance
+= pos
[i
].x_advance
;
949 y_advance
+= pos
[i
].y_advance
;
951 pos
[i
].x_advance
= 0;
952 pos
[i
].y_advance
= 0;
957 /* Transfer all cluster advance to the last glyph. */
958 pos
[end
- 1].x_advance
= total_x_advance
;
959 pos
[end
- 1].y_advance
= total_y_advance
;
961 hb_bubble_sort (buffer
->info
+ start
, end
- start
- 1, compare_info_codepoint
, buffer
->pos
+ start
);
963 /* Transfer all cluster advance to the first glyph. */
964 pos
[start
].x_advance
+= total_x_advance
;
965 pos
[start
].y_advance
+= total_y_advance
;
966 for (unsigned int i
= start
+ 1; i
< end
; i
++) {
967 pos
[i
].x_offset
-= total_x_advance
;
968 pos
[i
].y_offset
-= total_y_advance
;
970 hb_bubble_sort (buffer
->info
+ start
+ 1, end
- start
- 1, compare_info_codepoint
, buffer
->pos
+ start
+ 1);
975 hb_buffer_normalize_glyphs (hb_buffer_t
*buffer
)
977 assert (buffer
->have_positions
);
978 assert (buffer
->content_type
== HB_BUFFER_CONTENT_TYPE_GLYPHS
);
980 bool backward
= HB_DIRECTION_IS_BACKWARD (buffer
->props
.direction
);
982 unsigned int count
= buffer
->len
;
983 if (unlikely (!count
)) return;
984 hb_glyph_info_t
*info
= buffer
->info
;
986 unsigned int start
= 0;
988 for (end
= start
+ 1; end
< count
; end
++)
989 if (info
[start
].cluster
!= info
[end
].cluster
) {
990 normalize_glyphs_cluster (buffer
, start
, end
, backward
);
993 normalize_glyphs_cluster (buffer
, start
, end
, backward
);