harfrust 0.14.0

A complete HarfBuzz shaping algorithm port to Rust.
Documentation
use crate::ot::apply::ApplyContext;
use crate::ot::apply::{
    Apply, PairPosFormat1Cache, PairPosFormat1SmallCache, PairPosFormat2Cache,
    PairPosFormat2SmallCache, SkippingIterator, SubtableExternalCache, SubtableExternalCacheMode,
};
use crate::ot::{coverage_index, coverage_index_cached, ClassDefInfo, CoverageInfo};
use crate::ot::{glyph_class, glyph_class_cached};
use crate::set_digest::SetDigest;
use alloc::boxed::Box;
use alloc::vec::Vec;
use read_fonts::tables::gpos::{PairPosFormat1, PairPosFormat2};

fn collect_pair_set_digests(table: &PairPosFormat1) -> Box<[SetDigest]> {
    table
        .pair_sets()
        .iter()
        .map(|pair_set| {
            let Ok(pair_set) = pair_set else {
                return SetDigest::full();
            };
            let mut digest = SetDigest::new();
            for pair_value in pair_set.pair_value_records().iter() {
                let Ok(pair_value) = pair_value else {
                    return SetDigest::full();
                };
                digest.add(pair_value.second_glyph().to_u32());
            }
            digest
        })
        .collect::<Vec<_>>()
        .into_boxed_slice()
}

impl Apply for PairPosFormat1<'_> {
    fn apply_with_external_cache(
        &self,
        ctx: &mut ApplyContext,
        external_cache: &SubtableExternalCache,
    ) -> Option<()> {
        let first_glyph = ctx.buffer.cur(0).as_glyph();

        let (first_glyph_coverage_index, pair_set_digests) = match external_cache {
            SubtableExternalCache::PairPosFormat1Cache(cache) => (
                coverage_index_cached(
                    |gid| self.coverage().ok()?.get(gid),
                    first_glyph,
                    &cache.coverage,
                )?,
                Some(&cache.pair_sets),
            ),
            SubtableExternalCache::PairPosFormat1SmallCache(cache) => (
                cache.coverage.index(&self.offset_data(), first_glyph)?,
                Some(&cache.pair_sets),
            ),
            _ => (coverage_index(self.coverage(), first_glyph)?, None),
        };

        let mut iter = SkippingIterator::new(ctx, false);
        iter.reset(iter.buffer.idx);

        let mut unsafe_to = 0;
        if !iter.next(Some(&mut unsafe_to)) {
            ctx.buffer
                .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
            return None;
        }

        let second_glyph_index = iter.index();
        let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();

        if pair_set_digests
            .and_then(|digests| digests.get(first_glyph_coverage_index as usize))
            .is_some_and(|digest| !digest.may_have(second_glyph.to_u32()))
        {
            ctx.buffer
                .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
            return None;
        }

        let finish = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
            if has_record2 {
                *iter_index += 1;
                // https://github.com/harfbuzz/harfbuzz/issues/3824
                // https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
                ctx.buffer
                    .unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
            }

            ctx.buffer.idx = *iter_index;

            Some(())
        };

        let success =
            |ctx: &mut ApplyContext, iter_index: &mut usize, flag1, flag2, has_record2| {
                if flag1 || flag2 {
                    ctx.buffer
                        .unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
                } else {
                    // Even a zero-valued pair record is a concat hazard: changing
                    // the second glyph can select a non-zero record for the first.
                    ctx.buffer
                        .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
                }
                finish(ctx, iter_index, has_record2)
            };

        let mut buf_idx = iter.buf_idx;
        let set_offset = self
            .pair_set_offsets()
            .get(first_glyph_coverage_index as usize)?
            .get()
            .to_u32() as usize;
        let format1 = self.value_format1();
        let format1_len = format1.record_byte_len();
        let format2 = self.value_format2();
        let record_size = format1_len + format2.record_byte_len() + 2;
        let data = self.offset_data();
        let set_data = data.split_off(set_offset)?;
        let pair_count = set_data.read_at::<u16>(0).ok()? as usize;
        let mut hi = pair_count;
        let mut lo = 0;
        while lo < hi {
            // This recommends using usize::midpoint which expands to u128.
            // We definitely do not want to do that here since the input values
            // are 16-bit.
            #[allow(clippy::manual_midpoint)]
            let mid = (lo + hi) / 2;
            let record_offset = 2 + mid * record_size;
            let glyph_id = set_data
                .read_at::<read_fonts::types::GlyphId16>(record_offset)
                .ok()?;
            if glyph_id < second_glyph {
                lo = mid + 1;
            } else if glyph_id > second_glyph {
                hi = mid;
            } else {
                let has_record2 = !format2.is_empty();
                let worked1 = !format1.is_empty()
                    && super::value::apply(
                        ctx,
                        ctx.buffer.idx,
                        &set_data,
                        record_offset + 2,
                        format1,
                    ) == Some(true);
                let worked2 = has_record2
                    && super::value::apply(
                        ctx,
                        second_glyph_index,
                        &set_data,
                        record_offset + format1_len + 2,
                        format2,
                    ) == Some(true);
                return success(ctx, &mut buf_idx, worked1, worked2, has_record2);
            }
        }
        // The same hazard the digest above reports, reached the long way.
        ctx.buffer
            .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
        None
    }

    fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
        match mode {
            SubtableExternalCacheMode::Full => SubtableExternalCache::PairPosFormat1Cache(
                Box::new(PairPosFormat1Cache::new(collect_pair_set_digests(self))),
            ),
            SubtableExternalCacheMode::Small => {
                if let Some(coverage) =
                    CoverageInfo::new(&self.offset_data(), self.coverage_offset().to_u32() as u16)
                {
                    SubtableExternalCache::PairPosFormat1SmallCache(PairPosFormat1SmallCache {
                        coverage,
                        pair_sets: collect_pair_set_digests(self),
                    })
                } else {
                    SubtableExternalCache::None
                }
            }
            SubtableExternalCacheMode::None => SubtableExternalCache::None,
        }
    }
}

impl Apply for PairPosFormat2<'_> {
    fn apply_with_external_cache(
        &self,
        ctx: &mut ApplyContext,
        external_cache: &SubtableExternalCache,
    ) -> Option<()> {
        let first_glyph = ctx.buffer.cur(0).as_glyph();
        match external_cache {
            SubtableExternalCache::PairPosFormat2Cache(cache) => coverage_index_cached(
                |gid| self.coverage().ok()?.get(gid),
                first_glyph,
                &cache.coverage,
            )?,
            SubtableExternalCache::PairPosFormat2SmallCache(cache) => {
                cache.coverage.index(&self.offset_data(), first_glyph)?
            }
            _ => coverage_index(self.coverage(), first_glyph)?,
        };
        let mut iter = SkippingIterator::new(ctx, false);
        iter.reset(iter.buffer.idx);

        let mut unsafe_to = 0;
        if !iter.next(Some(&mut unsafe_to)) {
            ctx.buffer
                .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
            return None;
        }

        let second_glyph_index = iter.index();
        let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();

        let finish = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
            if has_record2 {
                *iter_index += 1;
                // https://github.com/harfbuzz/harfbuzz/issues/3824
                // https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
                ctx.buffer
                    .unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
            }

            ctx.buffer.idx = *iter_index;

            Some(())
        };

        let boring = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
            ctx.buffer
                .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
            finish(ctx, iter_index, has_record2)
        };

        let success =
            |ctx: &mut ApplyContext, iter_index: &mut usize, flag1, flag2, has_record2| {
                if flag1 || flag2 {
                    ctx.buffer
                        .unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
                    finish(ctx, iter_index, has_record2)
                } else {
                    boring(ctx, iter_index, has_record2)
                }
            };
        let data = self.offset_data();
        let (class1, class2) = match external_cache {
            SubtableExternalCache::PairPosFormat2Cache(cache) => (
                glyph_class_cached(
                    |gid| glyph_class(self.class_def1(), gid),
                    first_glyph,
                    &cache.first,
                ),
                glyph_class_cached(
                    |gid| glyph_class(self.class_def2(), gid),
                    second_glyph,
                    &cache.second,
                ),
            ),
            SubtableExternalCache::PairPosFormat2SmallCache(cache) => (
                cache.first.class(&data, first_glyph),
                cache.second.class(&data, second_glyph),
            ),
            _ => (
                glyph_class(self.class_def1(), first_glyph),
                glyph_class(self.class_def2(), second_glyph),
            ),
        };
        let mut buf_idx = iter.buf_idx;
        let format1 = self.value_format1();
        let format1_len = format1.record_byte_len();
        let format2 = self.value_format2();
        let record_size = format1_len + format2.record_byte_len();
        // Compute an offset into the 2D array of positioning records
        let record_offset = (class1 as usize * record_size * self.class2_count() as usize)
            + (class2 as usize * record_size)
            + self.class1_records_byte_range().start;
        let has_record2 = !format2.is_empty();
        let worked1 = !format1.is_empty()
            && super::value::apply(ctx, ctx.buffer.idx, &data, record_offset, format1)
                == Some(true);
        let worked2 = has_record2
            && super::value::apply(
                ctx,
                second_glyph_index,
                &data,
                record_offset + format1_len,
                format2,
            ) == Some(true);
        success(ctx, &mut buf_idx, worked1, worked2, has_record2)
    }

    fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
        match mode {
            SubtableExternalCacheMode::Full => {
                SubtableExternalCache::PairPosFormat2Cache(Box::new(PairPosFormat2Cache::new()))
            }
            SubtableExternalCacheMode::Small => {
                let data = self.offset_data();
                let coverage = CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16);
                let class1 = ClassDefInfo::new(&data, self.class_def1_offset().to_u32() as u16);
                let class2 = ClassDefInfo::new(&data, self.class_def2_offset().to_u32() as u16);
                if let Some((coverage, (first, second))) = coverage.zip(class1.zip(class2)) {
                    SubtableExternalCache::PairPosFormat2SmallCache(PairPosFormat2SmallCache {
                        coverage,
                        first,
                        second,
                    })
                } else {
                    SubtableExternalCache::None
                }
            }
            SubtableExternalCacheMode::None => SubtableExternalCache::None,
        }
    }
}