| 1 | use super::buffer::*; |
| 2 | use super::ot_layout::*; |
| 3 | use super::ot_layout_gpos_table::GPOS; |
| 4 | use super::ot_map::*; |
| 5 | use super::ot_shape_plan::hb_ot_shape_plan_t; |
| 6 | use super::ot_shaper::*; |
| 7 | use super::unicode::{hb_unicode_general_category_t, CharExt, GeneralCategoryExt}; |
| 8 | use super::*; |
| 9 | use super::{hb_font_t, hb_tag_t}; |
| 10 | use crate::hb::aat_layout::hb_aat_layout_remove_deleted_glyphs; |
| 11 | use crate::hb::algs::{rb_flag, rb_flag_unsafe}; |
| 12 | use crate::hb::buffer::glyph_flag::{SAFE_TO_INSERT_TATWEEL, UNSAFE_TO_BREAK, UNSAFE_TO_CONCAT}; |
| 13 | use crate::hb::unicode::hb_gc::{ |
| 14 | RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER, RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER, |
| 15 | RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR, RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER, |
| 16 | RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER, |
| 17 | }; |
| 18 | use crate::BufferFlags; |
| 19 | use crate::{Direction, Feature, Language, Script}; |
| 20 | |
| 21 | pub struct hb_ot_shape_planner_t<'a> { |
| 22 | pub face: &'a hb_font_t<'a>, |
| 23 | pub direction: Direction, |
| 24 | pub script: Option<Script>, |
| 25 | pub ot_map: hb_ot_map_builder_t<'a>, |
| 26 | pub apply_morx: bool, |
| 27 | pub script_zero_marks: bool, |
| 28 | pub script_fallback_mark_positioning: bool, |
| 29 | pub shaper: &'static hb_ot_shaper_t, |
| 30 | } |
| 31 | |
| 32 | impl<'a> hb_ot_shape_planner_t<'a> { |
| 33 | pub fn new( |
| 34 | face: &'a hb_font_t<'a>, |
| 35 | direction: Direction, |
| 36 | script: Option<Script>, |
| 37 | language: Option<&Language>, |
| 38 | ) -> Self { |
| 39 | let ot_map = hb_ot_map_builder_t::new(face, script, language); |
| 40 | |
| 41 | let mut shaper = match script { |
| 42 | Some(script) => hb_ot_shape_complex_categorize( |
| 43 | script, |
| 44 | direction, |
| 45 | ot_map.chosen_script(TableIndex::GSUB), |
| 46 | ), |
| 47 | None => &DEFAULT_SHAPER, |
| 48 | }; |
| 49 | |
| 50 | let script_zero_marks = shaper.zero_width_marks != HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE; |
| 51 | let script_fallback_mark_positioning = shaper.fallback_position; |
| 52 | |
| 53 | // https://github.com/harfbuzz/harfbuzz/issues/2124 |
| 54 | let apply_morx = |
| 55 | face.tables().morx.is_some() && (direction.is_horizontal() || face.gsub.is_none()); |
| 56 | |
| 57 | // https://github.com/harfbuzz/harfbuzz/issues/1528 |
| 58 | if apply_morx && shaper as *const _ != &DEFAULT_SHAPER as *const _ { |
| 59 | shaper = &DUMBER_SHAPER; |
| 60 | } |
| 61 | |
| 62 | hb_ot_shape_planner_t { |
| 63 | face, |
| 64 | direction, |
| 65 | script, |
| 66 | ot_map, |
| 67 | apply_morx, |
| 68 | script_zero_marks, |
| 69 | script_fallback_mark_positioning, |
| 70 | shaper, |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | pub fn collect_features(&mut self, user_features: &[Feature]) { |
| 75 | const COMMON_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[ |
| 76 | (hb_tag_t::from_bytes(b"abvm" ), F_GLOBAL), |
| 77 | (hb_tag_t::from_bytes(b"blwm" ), F_GLOBAL), |
| 78 | (hb_tag_t::from_bytes(b"ccmp" ), F_GLOBAL), |
| 79 | (hb_tag_t::from_bytes(b"locl" ), F_GLOBAL), |
| 80 | (hb_tag_t::from_bytes(b"mark" ), F_GLOBAL_MANUAL_JOINERS), |
| 81 | (hb_tag_t::from_bytes(b"mkmk" ), F_GLOBAL_MANUAL_JOINERS), |
| 82 | (hb_tag_t::from_bytes(b"rlig" ), F_GLOBAL), |
| 83 | ]; |
| 84 | |
| 85 | const HORIZONTAL_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[ |
| 86 | (hb_tag_t::from_bytes(b"calt" ), F_GLOBAL), |
| 87 | (hb_tag_t::from_bytes(b"clig" ), F_GLOBAL), |
| 88 | (hb_tag_t::from_bytes(b"curs" ), F_GLOBAL), |
| 89 | (hb_tag_t::from_bytes(b"dist" ), F_GLOBAL), |
| 90 | (hb_tag_t::from_bytes(b"kern" ), F_GLOBAL_HAS_FALLBACK), |
| 91 | (hb_tag_t::from_bytes(b"liga" ), F_GLOBAL), |
| 92 | (hb_tag_t::from_bytes(b"rclt" ), F_GLOBAL), |
| 93 | ]; |
| 94 | |
| 95 | let empty = F_NONE; |
| 96 | |
| 97 | self.ot_map.is_simple = true; |
| 98 | |
| 99 | self.ot_map |
| 100 | .enable_feature(hb_tag_t::from_bytes(b"rvrn" ), empty, 1); |
| 101 | self.ot_map.add_gsub_pause(None); |
| 102 | |
| 103 | match self.direction { |
| 104 | Direction::LeftToRight => { |
| 105 | self.ot_map |
| 106 | .enable_feature(hb_tag_t::from_bytes(b"ltra" ), empty, 1); |
| 107 | self.ot_map |
| 108 | .enable_feature(hb_tag_t::from_bytes(b"ltrm" ), empty, 1); |
| 109 | } |
| 110 | Direction::RightToLeft => { |
| 111 | self.ot_map |
| 112 | .enable_feature(hb_tag_t::from_bytes(b"rtla" ), empty, 1); |
| 113 | self.ot_map |
| 114 | .add_feature(hb_tag_t::from_bytes(b"rtlm" ), empty, 1); |
| 115 | } |
| 116 | _ => {} |
| 117 | } |
| 118 | |
| 119 | // Automatic fractions. |
| 120 | self.ot_map |
| 121 | .add_feature(hb_tag_t::from_bytes(b"frac" ), empty, 1); |
| 122 | self.ot_map |
| 123 | .add_feature(hb_tag_t::from_bytes(b"numr" ), empty, 1); |
| 124 | self.ot_map |
| 125 | .add_feature(hb_tag_t::from_bytes(b"dnom" ), empty, 1); |
| 126 | |
| 127 | // Random! |
| 128 | self.ot_map.enable_feature( |
| 129 | hb_tag_t::from_bytes(b"rand" ), |
| 130 | F_RANDOM, |
| 131 | hb_ot_map_t::MAX_VALUE, |
| 132 | ); |
| 133 | |
| 134 | // Tracking. We enable dummy feature here just to allow disabling |
| 135 | // AAT 'trak' table using features. |
| 136 | // https://github.com/harfbuzz/harfbuzz/issues/1303 |
| 137 | self.ot_map |
| 138 | .enable_feature(hb_tag_t::from_bytes(b"trak" ), F_HAS_FALLBACK, 1); |
| 139 | |
| 140 | self.ot_map |
| 141 | .enable_feature(hb_tag_t::from_bytes(b"Harf" ), empty, 1); // Considered required. |
| 142 | self.ot_map |
| 143 | .enable_feature(hb_tag_t::from_bytes(b"HARF" ), empty, 1); // Considered discretionary. |
| 144 | |
| 145 | if let Some(func) = self.shaper.collect_features { |
| 146 | self.ot_map.is_simple = false; |
| 147 | func(self); |
| 148 | } |
| 149 | |
| 150 | self.ot_map |
| 151 | .enable_feature(hb_tag_t::from_bytes(b"Buzz" ), empty, 1); // Considered required. |
| 152 | self.ot_map |
| 153 | .enable_feature(hb_tag_t::from_bytes(b"BUZZ" ), empty, 1); // Considered discretionary. |
| 154 | |
| 155 | for &(tag, flags) in COMMON_FEATURES { |
| 156 | self.ot_map.add_feature(tag, flags, 1); |
| 157 | } |
| 158 | |
| 159 | if self.direction.is_horizontal() { |
| 160 | for &(tag, flags) in HORIZONTAL_FEATURES { |
| 161 | self.ot_map.add_feature(tag, flags, 1); |
| 162 | } |
| 163 | } else { |
| 164 | // We only apply `vert` feature. See: |
| 165 | // https://github.com/harfbuzz/harfbuzz/commit/d71c0df2d17f4590d5611239577a6cb532c26528 |
| 166 | // https://lists.freedesktop.org/archives/harfbuzz/2013-August/003490.html |
| 167 | |
| 168 | // We really want to find a 'vert' feature if there's any in the font, no |
| 169 | // matter which script/langsys it is listed (or not) under. |
| 170 | // See various bugs referenced from: |
| 171 | // https://github.com/harfbuzz/harfbuzz/issues/63 |
| 172 | self.ot_map |
| 173 | .enable_feature(hb_tag_t::from_bytes(b"vert" ), F_GLOBAL_SEARCH, 1); |
| 174 | } |
| 175 | |
| 176 | if !user_features.is_empty() { |
| 177 | self.ot_map.is_simple = false; |
| 178 | } |
| 179 | |
| 180 | for feature in user_features { |
| 181 | let flags = if feature.is_global() { F_GLOBAL } else { empty }; |
| 182 | self.ot_map.add_feature(feature.tag, flags, feature.value); |
| 183 | } |
| 184 | |
| 185 | if let Some(func) = self.shaper.override_features { |
| 186 | func(self); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | pub fn compile(mut self, user_features: &[Feature]) -> hb_ot_shape_plan_t { |
| 191 | let ot_map = self.ot_map.compile(); |
| 192 | |
| 193 | let frac_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"frac" )); |
| 194 | let numr_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"numr" )); |
| 195 | let dnom_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"dnom" )); |
| 196 | let has_frac = frac_mask != 0 || (numr_mask != 0 && dnom_mask != 0); |
| 197 | |
| 198 | let rtlm_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"rtlm" )); |
| 199 | let has_vert = ot_map.get_1_mask(hb_tag_t::from_bytes(b"vert" )) != 0; |
| 200 | |
| 201 | let horizontal = self.direction.is_horizontal(); |
| 202 | let kern_tag = if horizontal { |
| 203 | hb_tag_t::from_bytes(b"kern" ) |
| 204 | } else { |
| 205 | hb_tag_t::from_bytes(b"vkrn" ) |
| 206 | }; |
| 207 | let kern_mask = ot_map.get_mask(kern_tag).0; |
| 208 | let requested_kerning = kern_mask != 0; |
| 209 | let trak_mask = ot_map.get_mask(hb_tag_t::from_bytes(b"trak" )).0; |
| 210 | let requested_tracking = trak_mask != 0; |
| 211 | |
| 212 | let has_gpos_kern = ot_map |
| 213 | .get_feature_index(TableIndex::GPOS, kern_tag) |
| 214 | .is_some(); |
| 215 | let disable_gpos = self.shaper.gpos_tag.is_some() |
| 216 | && self.shaper.gpos_tag != ot_map.chosen_script(TableIndex::GPOS); |
| 217 | |
| 218 | // Decide who provides glyph classes. GDEF or Unicode. |
| 219 | let fallback_glyph_classes = !hb_ot_layout_has_glyph_classes(self.face); |
| 220 | |
| 221 | // Decide who does substitutions. GSUB, morx, or fallback. |
| 222 | let apply_morx = self.apply_morx; |
| 223 | |
| 224 | let mut apply_gpos = false; |
| 225 | let mut apply_kerx = false; |
| 226 | let mut apply_kern = false; |
| 227 | |
| 228 | // Decide who does positioning. GPOS, kerx, kern, or fallback. |
| 229 | let has_kerx = self.face.tables().kerx.is_some(); |
| 230 | let has_gsub = !apply_morx && self.face.tables().gsub.is_some(); |
| 231 | let has_gpos = !disable_gpos && self.face.tables().gpos.is_some(); |
| 232 | |
| 233 | // Prefer GPOS over kerx if GSUB is present; |
| 234 | // https://github.com/harfbuzz/harfbuzz/issues/3008 |
| 235 | if has_kerx && !(has_gsub && has_gpos) { |
| 236 | apply_kerx = true; |
| 237 | } else if has_gpos { |
| 238 | apply_gpos = true; |
| 239 | } |
| 240 | |
| 241 | if !apply_kerx && (!has_gpos_kern || !apply_gpos) { |
| 242 | if has_kerx { |
| 243 | apply_kerx = true; |
| 244 | } else if hb_ot_layout_has_kerning(self.face) { |
| 245 | apply_kern = true; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | let apply_fallback_kern = !(apply_gpos || apply_kerx || apply_kern); |
| 250 | let zero_marks = self.script_zero_marks |
| 251 | && !apply_kerx |
| 252 | && (!apply_kern || !hb_ot_layout_has_machine_kerning(self.face)); |
| 253 | |
| 254 | let has_gpos_mark = ot_map.get_1_mask(hb_tag_t::from_bytes(b"mark" )) != 0; |
| 255 | |
| 256 | let mut adjust_mark_positioning_when_zeroing = !apply_gpos |
| 257 | && !apply_kerx |
| 258 | && (!apply_kern || !hb_ot_layout_has_cross_kerning(self.face)); |
| 259 | |
| 260 | let fallback_mark_positioning = |
| 261 | adjust_mark_positioning_when_zeroing && self.script_fallback_mark_positioning; |
| 262 | |
| 263 | // If we're using morx shaping, we cancel mark position adjustment because |
| 264 | // Apple Color Emoji assumes this will NOT be done when forming emoji sequences; |
| 265 | // https://github.com/harfbuzz/harfbuzz/issues/2967. |
| 266 | if apply_morx { |
| 267 | adjust_mark_positioning_when_zeroing = false; |
| 268 | } |
| 269 | |
| 270 | // Currently we always apply trak. |
| 271 | let apply_trak = requested_tracking && self.face.tables().trak.is_some(); |
| 272 | |
| 273 | let mut plan = hb_ot_shape_plan_t { |
| 274 | direction: self.direction, |
| 275 | script: self.script, |
| 276 | shaper: self.shaper, |
| 277 | ot_map, |
| 278 | data: None, |
| 279 | frac_mask, |
| 280 | numr_mask, |
| 281 | dnom_mask, |
| 282 | rtlm_mask, |
| 283 | kern_mask, |
| 284 | trak_mask, |
| 285 | requested_kerning, |
| 286 | has_frac, |
| 287 | has_vert, |
| 288 | has_gpos_mark, |
| 289 | zero_marks, |
| 290 | fallback_glyph_classes, |
| 291 | fallback_mark_positioning, |
| 292 | adjust_mark_positioning_when_zeroing, |
| 293 | apply_gpos, |
| 294 | apply_kern, |
| 295 | apply_fallback_kern, |
| 296 | apply_kerx, |
| 297 | apply_morx, |
| 298 | apply_trak, |
| 299 | user_features: user_features.to_vec(), |
| 300 | }; |
| 301 | |
| 302 | if let Some(func) = self.shaper.create_data { |
| 303 | plan.data = Some(func(&plan)); |
| 304 | } |
| 305 | |
| 306 | plan |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | pub struct hb_ot_shape_context_t<'a> { |
| 311 | pub plan: &'a hb_ot_shape_plan_t, |
| 312 | pub face: &'a hb_font_t<'a>, |
| 313 | pub buffer: &'a mut hb_buffer_t, |
| 314 | // Transient stuff |
| 315 | pub target_direction: Direction, |
| 316 | } |
| 317 | |
| 318 | // Pull it all together! |
| 319 | pub fn shape_internal(ctx: &mut hb_ot_shape_context_t) { |
| 320 | ctx.buffer.enter(); |
| 321 | |
| 322 | initialize_masks(ctx); |
| 323 | set_unicode_props(ctx.buffer); |
| 324 | insert_dotted_circle(ctx.buffer, ctx.face); |
| 325 | |
| 326 | form_clusters(ctx.buffer); |
| 327 | |
| 328 | ensure_native_direction(ctx.buffer); |
| 329 | |
| 330 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.preprocess_text { |
| 331 | func(ctx.plan, ctx.face, ctx.buffer); |
| 332 | } |
| 333 | |
| 334 | substitute_pre(ctx); |
| 335 | position(ctx); |
| 336 | substitute_post(ctx); |
| 337 | |
| 338 | propagate_flags(ctx.buffer); |
| 339 | |
| 340 | ctx.buffer.direction = ctx.target_direction; |
| 341 | ctx.buffer.leave(); |
| 342 | } |
| 343 | |
| 344 | fn substitute_pre(ctx: &mut hb_ot_shape_context_t) { |
| 345 | hb_ot_substitute_default(ctx); |
| 346 | hb_ot_substitute_plan(ctx); |
| 347 | |
| 348 | if ctx.plan.apply_morx && ctx.plan.apply_gpos { |
| 349 | hb_aat_layout_remove_deleted_glyphs(ctx.buffer); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | fn substitute_post(ctx: &mut hb_ot_shape_context_t) { |
| 354 | if ctx.plan.apply_morx && !ctx.plan.apply_gpos { |
| 355 | aat_layout::hb_aat_layout_remove_deleted_glyphs(ctx.buffer); |
| 356 | } |
| 357 | |
| 358 | deal_with_variation_selectors(ctx.buffer); |
| 359 | hide_default_ignorables(ctx.buffer, ctx.face); |
| 360 | |
| 361 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.postprocess_glyphs { |
| 362 | func(ctx.plan, ctx.face, ctx.buffer); |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | fn hb_ot_substitute_default(ctx: &mut hb_ot_shape_context_t) { |
| 367 | rotate_chars(ctx); |
| 368 | |
| 369 | ot_shape_normalize::_hb_ot_shape_normalize(ctx.plan, ctx.buffer, ctx.face); |
| 370 | |
| 371 | setup_masks(ctx); |
| 372 | |
| 373 | // This is unfortunate to go here, but necessary... |
| 374 | if ctx.plan.fallback_mark_positioning { |
| 375 | ot_shape_fallback::_hb_ot_shape_fallback_mark_position_recategorize_marks( |
| 376 | ctx.plan, ctx.face, ctx.buffer, |
| 377 | ); |
| 378 | } |
| 379 | |
| 380 | map_glyphs_fast(ctx.buffer); |
| 381 | } |
| 382 | |
| 383 | fn hb_ot_substitute_plan(ctx: &mut hb_ot_shape_context_t) { |
| 384 | hb_ot_layout_substitute_start(ctx.face, ctx.buffer); |
| 385 | |
| 386 | if ctx.plan.fallback_glyph_classes { |
| 387 | hb_synthesize_glyph_classes(ctx.buffer); |
| 388 | } |
| 389 | |
| 390 | if ctx.plan.apply_morx { |
| 391 | aat_layout::hb_aat_layout_substitute(ctx.plan, ctx.face, ctx.buffer); |
| 392 | } else { |
| 393 | super::ot_layout_gsub_table::substitute(ctx.plan, ctx.face, ctx.buffer); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | fn position(ctx: &mut hb_ot_shape_context_t) { |
| 398 | ctx.buffer.clear_positions(); |
| 399 | |
| 400 | position_default(ctx); |
| 401 | |
| 402 | position_complex(ctx); |
| 403 | |
| 404 | if ctx.buffer.direction.is_backward() { |
| 405 | ctx.buffer.reverse(); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | fn position_default(ctx: &mut hb_ot_shape_context_t) { |
| 410 | let len = ctx.buffer.len; |
| 411 | |
| 412 | if ctx.buffer.direction.is_horizontal() { |
| 413 | for (info, pos) in ctx.buffer.info[..len] |
| 414 | .iter() |
| 415 | .zip(&mut ctx.buffer.pos[..len]) |
| 416 | { |
| 417 | pos.x_advance = ctx.face.glyph_h_advance(info.as_glyph()); |
| 418 | } |
| 419 | } else { |
| 420 | for (info, pos) in ctx.buffer.info[..len] |
| 421 | .iter() |
| 422 | .zip(&mut ctx.buffer.pos[..len]) |
| 423 | { |
| 424 | let glyph = info.as_glyph(); |
| 425 | pos.y_advance = ctx.face.glyph_v_advance(glyph); |
| 426 | pos.x_offset -= ctx.face.glyph_h_origin(glyph); |
| 427 | pos.y_offset -= ctx.face.glyph_v_origin(glyph); |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | if ctx.buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK != 0 { |
| 432 | ot_shape_fallback::_hb_ot_shape_fallback_spaces(ctx.plan, ctx.face, ctx.buffer); |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | fn position_complex(ctx: &mut hb_ot_shape_context_t) { |
| 437 | // If the font has no GPOS and direction is forward, then when |
| 438 | // zeroing mark widths, we shift the mark with it, such that the |
| 439 | // mark is positioned hanging over the previous glyph. When |
| 440 | // direction is backward we don't shift and it will end up |
| 441 | // hanging over the next glyph after the final reordering. |
| 442 | // |
| 443 | // Note: If fallback positioning happens, we don't care about |
| 444 | // this as it will be overridden. |
| 445 | let adjust_offsets_when_zeroing = |
| 446 | ctx.plan.adjust_mark_positioning_when_zeroing && ctx.buffer.direction.is_forward(); |
| 447 | |
| 448 | // We change glyph origin to what GPOS expects (horizontal), apply GPOS, change it back. |
| 449 | |
| 450 | GPOS::position_start(ctx.face, ctx.buffer); |
| 451 | |
| 452 | if ctx.plan.zero_marks |
| 453 | && ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY |
| 454 | { |
| 455 | zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing); |
| 456 | } |
| 457 | |
| 458 | position_by_plan(ctx.plan, ctx.face, ctx.buffer); |
| 459 | |
| 460 | if ctx.plan.zero_marks |
| 461 | && ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE |
| 462 | { |
| 463 | zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing); |
| 464 | } |
| 465 | |
| 466 | // Finish off. Has to follow a certain order. |
| 467 | GPOS::position_finish_advances(ctx.face, ctx.buffer); |
| 468 | zero_width_default_ignorables(ctx.buffer); |
| 469 | |
| 470 | if ctx.plan.apply_morx { |
| 471 | aat_layout::hb_aat_layout_zero_width_deleted_glyphs(ctx.buffer); |
| 472 | } |
| 473 | |
| 474 | GPOS::position_finish_offsets(ctx.face, ctx.buffer); |
| 475 | |
| 476 | if ctx.plan.fallback_mark_positioning { |
| 477 | ot_shape_fallback::position_marks( |
| 478 | ctx.plan, |
| 479 | ctx.face, |
| 480 | ctx.buffer, |
| 481 | adjust_offsets_when_zeroing, |
| 482 | ); |
| 483 | } |
| 484 | } |
| 485 | |
| 486 | fn position_by_plan(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) { |
| 487 | if plan.apply_gpos { |
| 488 | super::ot_layout_gpos_table::position(plan, face, buffer); |
| 489 | } else if plan.apply_kerx { |
| 490 | aat_layout::hb_aat_layout_position(plan, face, buffer); |
| 491 | } |
| 492 | if plan.apply_kern { |
| 493 | super::kerning::hb_ot_layout_kern(plan, face, buffer); |
| 494 | } else if plan.apply_fallback_kern { |
| 495 | ot_shape_fallback::_hb_ot_shape_fallback_kern(plan, face, buffer); |
| 496 | } |
| 497 | |
| 498 | if plan.apply_trak { |
| 499 | aat_layout::hb_aat_layout_track(plan, face, buffer); |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | fn initialize_masks(ctx: &mut hb_ot_shape_context_t) { |
| 504 | let global_mask: u32 = ctx.plan.ot_map.get_global_mask(); |
| 505 | ctx.buffer.reset_masks(global_mask); |
| 506 | } |
| 507 | |
| 508 | fn setup_masks(ctx: &mut hb_ot_shape_context_t) { |
| 509 | setup_masks_fraction(ctx); |
| 510 | |
| 511 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.setup_masks { |
| 512 | func(ctx.plan, ctx.face, ctx.buffer); |
| 513 | } |
| 514 | |
| 515 | for feature: &Feature in &ctx.plan.user_features { |
| 516 | if !feature.is_global() { |
| 517 | let (mask: u32, shift: u32) = ctx.plan.ot_map.get_mask(feature.tag); |
| 518 | ctx.buffer |
| 519 | .set_masks(value:feature.value << shift, mask, cluster_start:feature.start, cluster_end:feature.end); |
| 520 | } |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | fn setup_masks_fraction(ctx: &mut hb_ot_shape_context_t) { |
| 525 | let buffer = &mut ctx.buffer; |
| 526 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII == 0 || !ctx.plan.has_frac { |
| 527 | return; |
| 528 | } |
| 529 | |
| 530 | let (pre_mask, post_mask) = if buffer.direction.is_forward() { |
| 531 | ( |
| 532 | ctx.plan.numr_mask | ctx.plan.frac_mask, |
| 533 | ctx.plan.frac_mask | ctx.plan.dnom_mask, |
| 534 | ) |
| 535 | } else { |
| 536 | ( |
| 537 | ctx.plan.frac_mask | ctx.plan.dnom_mask, |
| 538 | ctx.plan.numr_mask | ctx.plan.frac_mask, |
| 539 | ) |
| 540 | }; |
| 541 | |
| 542 | let len = buffer.len; |
| 543 | let mut i = 0; |
| 544 | while i < len { |
| 545 | // FRACTION SLASH |
| 546 | if buffer.info[i].glyph_id == 0x2044 { |
| 547 | let mut start = i; |
| 548 | while start > 0 |
| 549 | && _hb_glyph_info_get_general_category(&buffer.info[start - 1]) |
| 550 | == hb_unicode_general_category_t::DecimalNumber |
| 551 | { |
| 552 | start -= 1; |
| 553 | } |
| 554 | |
| 555 | let mut end = i + 1; |
| 556 | while end < len |
| 557 | && _hb_glyph_info_get_general_category(&buffer.info[end]) |
| 558 | == hb_unicode_general_category_t::DecimalNumber |
| 559 | { |
| 560 | end += 1; |
| 561 | } |
| 562 | |
| 563 | if start == i || end == i + 1 { |
| 564 | if start == i { |
| 565 | buffer.unsafe_to_concat(Some(start), Some(start + 1)); |
| 566 | } |
| 567 | |
| 568 | if end == i + 1 { |
| 569 | buffer.unsafe_to_concat(Some(end - 1), Some(end)); |
| 570 | } |
| 571 | |
| 572 | i += 1; |
| 573 | continue; |
| 574 | } |
| 575 | |
| 576 | buffer.unsafe_to_break(Some(start), Some(end)); |
| 577 | |
| 578 | for info in &mut buffer.info[start..i] { |
| 579 | info.mask |= pre_mask; |
| 580 | } |
| 581 | |
| 582 | buffer.info[i].mask |= ctx.plan.frac_mask; |
| 583 | |
| 584 | for info in &mut buffer.info[i + 1..end] { |
| 585 | info.mask |= post_mask; |
| 586 | } |
| 587 | |
| 588 | i = end; |
| 589 | } else { |
| 590 | i += 1; |
| 591 | } |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | fn set_unicode_props(buffer: &mut hb_buffer_t) { |
| 596 | // Implement enough of Unicode Graphemes here that shaping |
| 597 | // in reverse-direction wouldn't break graphemes. Namely, |
| 598 | // we mark all marks and ZWJ and ZWJ,Extended_Pictographic |
| 599 | // sequences as continuations. The foreach_grapheme() |
| 600 | // macro uses this bit. |
| 601 | // |
| 602 | // https://www.unicode.org/reports/tr29/#Regex_Definitions |
| 603 | |
| 604 | let len = buffer.len; |
| 605 | |
| 606 | let mut i = 0; |
| 607 | while i < len { |
| 608 | // Mutably borrow buffer.info[i] and immutably borrow |
| 609 | // buffer.info[i - 1] (if present) in a way that the borrow |
| 610 | // checker can understand. |
| 611 | let (prior, later) = buffer.info.split_at_mut(i); |
| 612 | let info = &mut later[0]; |
| 613 | info.init_unicode_props(&mut buffer.scratch_flags); |
| 614 | |
| 615 | let gen_cat = _hb_glyph_info_get_general_category(info); |
| 616 | |
| 617 | if (rb_flag_unsafe(gen_cat.to_rb()) |
| 618 | & (rb_flag(RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER) |
| 619 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER) |
| 620 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER) |
| 621 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER) |
| 622 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR))) |
| 623 | != 0 |
| 624 | { |
| 625 | i += 1; |
| 626 | continue; |
| 627 | } |
| 628 | |
| 629 | // Marks are already set as continuation by the above line. |
| 630 | // Handle Emoji_Modifier and ZWJ-continuation. |
| 631 | if gen_cat == hb_unicode_general_category_t::ModifierSymbol |
| 632 | && matches!(info.glyph_id, 0x1F3FB..=0x1F3FF) |
| 633 | { |
| 634 | _hb_glyph_info_set_continuation(info); |
| 635 | } else if i != 0 && matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) { |
| 636 | // Should never fail because we checked for i > 0. |
| 637 | // TODO: use let chains when they become stable |
| 638 | let prev = prior.last().unwrap(); |
| 639 | if matches!(prev.glyph_id, 0x1F1E6..=0x1F1FF) && !_hb_glyph_info_is_continuation(prev) { |
| 640 | _hb_glyph_info_set_continuation(info); |
| 641 | } |
| 642 | } else if _hb_glyph_info_is_zwj(info) { |
| 643 | _hb_glyph_info_set_continuation(info); |
| 644 | if let Some(next) = buffer.info[..len].get_mut(i + 1) { |
| 645 | if next.as_char().is_emoji_extended_pictographic() { |
| 646 | next.init_unicode_props(&mut buffer.scratch_flags); |
| 647 | _hb_glyph_info_set_continuation(next); |
| 648 | i += 1; |
| 649 | } |
| 650 | } |
| 651 | } else if matches!(info.glyph_id, 0xFF9E..=0xFF9F | 0xE0020..=0xE007F) { |
| 652 | // Or part of the Other_Grapheme_Extend that is not marks. |
| 653 | // As of Unicode 15 that is just: |
| 654 | // |
| 655 | // 200C ; Other_Grapheme_Extend # Cf ZERO WIDTH NON-JOINER |
| 656 | // FF9E..FF9F ; Other_Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA |
| 657 | // SEMI-VOICED SOUND MARK E0020..E007F ; Other_Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG |
| 658 | // |
| 659 | // ZWNJ is special, we don't want to merge it as there's no need, and keeping |
| 660 | // it separate results in more granular clusters. |
| 661 | // Tags are used for Emoji sub-region flag sequences: |
| 662 | // https://github.com/harfbuzz/harfbuzz/issues/1556 |
| 663 | // Katakana ones were requested: |
| 664 | // https://github.com/harfbuzz/harfbuzz/issues/3844 |
| 665 | _hb_glyph_info_set_continuation(info); |
| 666 | } |
| 667 | |
| 668 | i += 1; |
| 669 | } |
| 670 | } |
| 671 | |
| 672 | pub(crate) fn syllabic_clear_var( |
| 673 | _: &hb_ot_shape_plan_t, |
| 674 | _: &hb_font_t, |
| 675 | buffer: &mut hb_buffer_t, |
| 676 | ) -> bool { |
| 677 | for info: &mut hb_glyph_info_t in &mut buffer.info { |
| 678 | info.set_syllable(0); |
| 679 | } |
| 680 | |
| 681 | false |
| 682 | } |
| 683 | |
| 684 | fn insert_dotted_circle(buffer: &mut hb_buffer_t, face: &hb_font_t) { |
| 685 | if !bufferBufferFlags |
| 686 | .flags |
| 687 | .contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE) |
| 688 | && buffer.flags.contains(BufferFlags::BEGINNING_OF_TEXT) |
| 689 | && buffer.context_len[0] == 0 |
| 690 | && _hb_glyph_info_is_unicode_mark(&buffer.info[0]) |
| 691 | && face.has_glyph(0x25CC) |
| 692 | { |
| 693 | let mut info: hb_glyph_info_t = hb_glyph_info_t { |
| 694 | glyph_id: 0x25CC, |
| 695 | mask: buffer.cur(0).mask, |
| 696 | cluster: buffer.cur(0).cluster, |
| 697 | ..hb_glyph_info_t::default() |
| 698 | }; |
| 699 | |
| 700 | info.init_unicode_props(&mut buffer.scratch_flags); |
| 701 | buffer.clear_output(); |
| 702 | buffer.output_info(info); |
| 703 | buffer.sync(); |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | fn form_clusters(buffer: &mut hb_buffer_t) { |
| 708 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII != 0 { |
| 709 | if buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES { |
| 710 | foreach_grapheme!(buffer, start, end, { buffer.merge_clusters(start, end) }); |
| 711 | } else { |
| 712 | foreach_grapheme!(buffer, start, end, { |
| 713 | buffer.unsafe_to_break(Some(start), Some(end)); |
| 714 | }); |
| 715 | } |
| 716 | } |
| 717 | } |
| 718 | |
| 719 | fn ensure_native_direction(buffer: &mut hb_buffer_t) { |
| 720 | let dir = buffer.direction; |
| 721 | let mut hor = buffer |
| 722 | .script |
| 723 | .and_then(Direction::from_script) |
| 724 | .unwrap_or_default(); |
| 725 | |
| 726 | // Numeric runs in natively-RTL scripts are actually native-LTR, so we reset |
| 727 | // the horiz_dir if the run contains at least one decimal-number char, and no |
| 728 | // letter chars (ideally we should be checking for chars with strong |
| 729 | // directionality but hb-unicode currently lacks bidi categories). |
| 730 | // |
| 731 | // This allows digit sequences in Arabic etc to be shaped in "native" |
| 732 | // direction, so that features like ligatures will work as intended. |
| 733 | // |
| 734 | // https://github.com/harfbuzz/harfbuzz/issues/501 |
| 735 | // |
| 736 | // Similar thing about Regional_Indicators; They are bidi=L, but Script=Common. |
| 737 | // If they are present in a run of natively-RTL text, they get assigned a script |
| 738 | // with natively RTL direction, which would result in wrong shaping if we |
| 739 | // assign such native RTL direction to them then. Detect that as well. |
| 740 | // |
| 741 | // https://github.com/harfbuzz/harfbuzz/issues/3314 |
| 742 | |
| 743 | if hor == Direction::RightToLeft && dir == Direction::LeftToRight { |
| 744 | let mut found_number = false; |
| 745 | let mut found_letter = false; |
| 746 | let mut found_ri = false; |
| 747 | for info in &buffer.info { |
| 748 | let gc = _hb_glyph_info_get_general_category(info); |
| 749 | if gc == hb_unicode_general_category_t::DecimalNumber { |
| 750 | found_number = true; |
| 751 | } else if gc.is_letter() { |
| 752 | found_letter = true; |
| 753 | break; |
| 754 | } else if matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) { |
| 755 | found_ri = true; |
| 756 | } |
| 757 | } |
| 758 | if (found_number || found_ri) && !found_letter { |
| 759 | hor = Direction::LeftToRight; |
| 760 | } |
| 761 | } |
| 762 | |
| 763 | // TODO vertical: |
| 764 | // The only BTT vertical script is Ogham, but it's not clear to me whether OpenType |
| 765 | // Ogham fonts are supposed to be implemented BTT or not. Need to research that |
| 766 | // first. |
| 767 | if (dir.is_horizontal() && dir != hor && hor != Direction::Invalid) |
| 768 | || (dir.is_vertical() && dir != Direction::TopToBottom) |
| 769 | { |
| 770 | _hb_ot_layout_reverse_graphemes(buffer); |
| 771 | buffer.direction = buffer.direction.reverse(); |
| 772 | } |
| 773 | } |
| 774 | |
| 775 | fn rotate_chars(ctx: &mut hb_ot_shape_context_t) { |
| 776 | let len = ctx.buffer.len; |
| 777 | |
| 778 | if ctx.target_direction.is_backward() { |
| 779 | let rtlm_mask = ctx.plan.rtlm_mask; |
| 780 | |
| 781 | for info in &mut ctx.buffer.info[..len] { |
| 782 | if let Some(c) = info.as_char().mirrored().map(u32::from) { |
| 783 | if ctx.face.has_glyph(c) { |
| 784 | info.glyph_id = c; |
| 785 | continue; |
| 786 | } |
| 787 | } |
| 788 | info.mask |= rtlm_mask; |
| 789 | } |
| 790 | } |
| 791 | |
| 792 | if ctx.target_direction.is_vertical() && !ctx.plan.has_vert { |
| 793 | for info in &mut ctx.buffer.info[..len] { |
| 794 | if let Some(c) = info.as_char().vertical().map(u32::from) { |
| 795 | if ctx.face.has_glyph(c) { |
| 796 | info.glyph_id = c; |
| 797 | } |
| 798 | } |
| 799 | } |
| 800 | } |
| 801 | } |
| 802 | |
| 803 | fn map_glyphs_fast(buffer: &mut hb_buffer_t) { |
| 804 | // Normalization process sets up glyph_index(), we just copy it. |
| 805 | let len: usize = buffer.len; |
| 806 | for info: &mut hb_glyph_info_t in &mut buffer.info[..len] { |
| 807 | info.glyph_id = info.glyph_index(); |
| 808 | } |
| 809 | |
| 810 | for info: &mut hb_glyph_info_t in &mut buffer.out_info_mut()[..len] { |
| 811 | info.glyph_id = info.glyph_index(); |
| 812 | } |
| 813 | } |
| 814 | |
| 815 | fn hb_synthesize_glyph_classes(buffer: &mut hb_buffer_t) { |
| 816 | let len: usize = buffer.len; |
| 817 | for info: &mut hb_glyph_info_t in &mut buffer.info[..len] { |
| 818 | // Never mark default-ignorables as marks. |
| 819 | // They won't get in the way of lookups anyway, |
| 820 | // but having them as mark will cause them to be skipped |
| 821 | // over if the lookup-flag says so, but at least for the |
| 822 | // Mongolian variation selectors, looks like Uniscribe |
| 823 | // marks them as non-mark. Some Mongolian fonts without |
| 824 | // GDEF rely on this. Another notable character that |
| 825 | // this applies to is COMBINING GRAPHEME JOINER. |
| 826 | let class: GlyphPropsFlags = if _hb_glyph_info_get_general_category(info) |
| 827 | != hb_unicode_general_category_t::NonspacingMark |
| 828 | || _hb_glyph_info_is_default_ignorable(info) |
| 829 | { |
| 830 | GlyphPropsFlags::BASE_GLYPH |
| 831 | } else { |
| 832 | GlyphPropsFlags::MARK |
| 833 | }; |
| 834 | |
| 835 | info.set_glyph_props(class.bits()); |
| 836 | } |
| 837 | } |
| 838 | |
| 839 | fn zero_width_default_ignorables(buffer: &mut hb_buffer_t) { |
| 840 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0 |
| 841 | && !bufferBufferFlags |
| 842 | .flags |
| 843 | .contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES) |
| 844 | && !bufferBufferFlags |
| 845 | .flags |
| 846 | .contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES) |
| 847 | { |
| 848 | let len: usize = buffer.len; |
| 849 | for (info: &hb_glyph_info_t, pos: &mut GlyphPosition) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) { |
| 850 | if _hb_glyph_info_is_default_ignorable(info) { |
| 851 | pos.x_advance = 0; |
| 852 | pos.y_advance = 0; |
| 853 | pos.x_offset = 0; |
| 854 | pos.y_offset = 0; |
| 855 | } |
| 856 | } |
| 857 | } |
| 858 | } |
| 859 | |
| 860 | fn deal_with_variation_selectors(buffer: &mut hb_buffer_t) { |
| 861 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK == 0 { |
| 862 | return; |
| 863 | } |
| 864 | |
| 865 | // Note: In harfbuzz, this is part of the condition above (with OR), so it needs to stay |
| 866 | // in sync. |
| 867 | let Some(nf) = buffer.not_found_variation_selector else { |
| 868 | return; |
| 869 | }; |
| 870 | |
| 871 | let count = buffer.len; |
| 872 | let info = &mut buffer.info; |
| 873 | let pos = &mut buffer.pos; |
| 874 | |
| 875 | for i in 0..count { |
| 876 | if _hb_glyph_info_is_variation_selector(&info[i]) { |
| 877 | info[i].glyph_id = nf; |
| 878 | pos[i].x_advance = 0; |
| 879 | pos[i].y_advance = 0; |
| 880 | pos[i].x_offset = 0; |
| 881 | pos[i].y_offset = 0; |
| 882 | _hb_glyph_info_set_variation_selector(&mut info[i], false); |
| 883 | } |
| 884 | } |
| 885 | } |
| 886 | |
| 887 | fn zero_mark_widths_by_gdef(buffer: &mut hb_buffer_t, adjust_offsets: bool) { |
| 888 | let len: usize = buffer.len; |
| 889 | for (info: &hb_glyph_info_t, pos: &mut GlyphPosition) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) { |
| 890 | if _hb_glyph_info_is_mark(info) { |
| 891 | if adjust_offsets { |
| 892 | pos.x_offset -= pos.x_advance; |
| 893 | pos.y_offset -= pos.y_advance; |
| 894 | } |
| 895 | |
| 896 | pos.x_advance = 0; |
| 897 | pos.y_advance = 0; |
| 898 | } |
| 899 | } |
| 900 | } |
| 901 | |
| 902 | fn hide_default_ignorables(buffer: &mut hb_buffer_t, face: &hb_font_t) { |
| 903 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0 |
| 904 | && !buffer |
| 905 | .flags |
| 906 | .contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES) |
| 907 | { |
| 908 | if !buffer |
| 909 | .flags |
| 910 | .contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES) |
| 911 | { |
| 912 | if let Some(invisible) = buffer |
| 913 | .invisible |
| 914 | .or_else(|| face.get_nominal_glyph(u32::from(' ' ))) |
| 915 | { |
| 916 | let len = buffer.len; |
| 917 | for info in &mut buffer.info[..len] { |
| 918 | if _hb_glyph_info_is_default_ignorable(info) { |
| 919 | info.glyph_id = u32::from(invisible.0); |
| 920 | } |
| 921 | } |
| 922 | return; |
| 923 | } |
| 924 | } |
| 925 | |
| 926 | buffer.delete_glyphs_inplace(_hb_glyph_info_is_default_ignorable); |
| 927 | } |
| 928 | } |
| 929 | |
| 930 | fn propagate_flags(buffer: &mut hb_buffer_t) { |
| 931 | // Propagate cluster-level glyph flags to be the same on all cluster glyphs. |
| 932 | // Simplifies using them. |
| 933 | |
| 934 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GLYPH_FLAGS == 0 { |
| 935 | return; |
| 936 | } |
| 937 | |
| 938 | /* If we are producing SAFE_TO_INSERT_TATWEEL, then do two things: |
| 939 | * |
| 940 | * - If the places that the Arabic shaper marked as SAFE_TO_INSERT_TATWEEL, |
| 941 | * are UNSAFE_TO_BREAK, then clear the SAFE_TO_INSERT_TATWEEL, |
| 942 | * - Any place that is SAFE_TO_INSERT_TATWEEL, is also now UNSAFE_TO_BREAK. |
| 943 | * |
| 944 | * We couldn't make this interaction earlier. It has to be done here. |
| 945 | */ |
| 946 | let flip_tatweel = buffer |
| 947 | .flags |
| 948 | .contains(BufferFlags::PRODUCE_SAFE_TO_INSERT_TATWEEL); |
| 949 | |
| 950 | let clear_concat = !buffer.flags.contains(BufferFlags::PRODUCE_UNSAFE_TO_CONCAT); |
| 951 | |
| 952 | foreach_cluster!(buffer, start, end, { |
| 953 | let mut mask = 0; |
| 954 | for info in &buffer.info[start..end] { |
| 955 | mask |= info.mask & glyph_flag::DEFINED; |
| 956 | } |
| 957 | |
| 958 | if flip_tatweel { |
| 959 | if mask & UNSAFE_TO_BREAK != 0 { |
| 960 | mask &= !SAFE_TO_INSERT_TATWEEL; |
| 961 | } |
| 962 | |
| 963 | if mask & SAFE_TO_INSERT_TATWEEL != 0 { |
| 964 | mask |= UNSAFE_TO_BREAK | UNSAFE_TO_CONCAT; |
| 965 | } |
| 966 | } |
| 967 | |
| 968 | if clear_concat { |
| 969 | mask &= !UNSAFE_TO_CONCAT; |
| 970 | |
| 971 | for info in &mut buffer.info[start..end] { |
| 972 | info.mask = mask; |
| 973 | } |
| 974 | } |
| 975 | }); |
| 976 | } |
| 977 | |