Repository: typst/typst
File: crates/typst/src/layout/inline/shaping.rs (Rust)

Code before:
                // In the unlikely chance that we hyphenate after an empty line,
                // ensure that the glyph range still falls after self.base so
                // that subtracting either of the endpoints by self.base doesn't
                // underflow. See <https://github.com/typst/typst/issues/2283>.
                .unwrap_or_else(|| self.base..self.base);
            self.width += x_advance.at(self.size);
            self.glyphs.to_mut().push(ShapedGlyph {
                font,
                glyph_id: glyph_id.0,
                x_advance,
                x_offset: Em::zero(),
                y_offset: Em::zero(),
                adjustability: Adjustability::default(),
                range,
                safe_to_break: true,
                c: '-',
                span: (Span::detached(), 0),
                is_justifiable: false,
                script: Script::Common,
            });
            Some(())
        });
    }

    /// Prepend a hyphen to begin of the text.
    pub fn prepend_hyphen(&mut self, engine: &Engine, fallback: bool) {
        let world = engine.world;
        let book = world.book();
        let fallback_func = if fallback {
            Some(|| book.select_fallback(None, self.variant, "-"))
        } else {
            None
        };
        let mut chain = families(self.styles)
            .map(|family| book.select(family, self.variant))
            .chain(fallback_func.iter().map(|f| f()))
            .flatten();

        chain.find_map(|id| {
            let font = world.font(id)?;
            let ttf = font.ttf();
            let glyph_id = ttf.glyph_index('-')?;
            let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?);
            let range = self
                .glyphs
                .first()
                .map(|g| g.range.start..g.range.start)
                // In the unlikely chance that we hyphenate after an empty line,
                // ensure that the glyph range still falls after self.base so
                // that subtracting either of the endpoints by self.base doesn't
                // underflow. See <https://github.com/typst/typst/issues/2283>.

Review comment (refactor):
There is a lot of code duplication with `push_hyphen`. Could you extract some shared function here?

Code after:
        chain.find_map(|id| {
            let font = world.font(id)?;
            let ttf = font.ttf();
            let glyph_id = ttf.glyph_index('-')?;
            let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?);
            let range = match side {
                Side::Left => self.glyphs.first().map(|g| g.range.start..g.range.start),
                Side::Right => self.glyphs.last().map(|g| g.range.end..g.range.end),
            }
            // In the unlikely chance that we hyphenate after an empty line,
            // ensure that the glyph range still falls after self.base so
            // that subtracting either of the endpoints by self.base doesn't
            // underflow. See <https://github.com/typst/typst/issues/2283>.
            .unwrap_or_else(|| self.base..self.base);
            self.width += x_advance.at(self.size);
            let glyph = ShapedGlyph {
                font,
                glyph_id: glyph_id.0,
                x_advance,
                x_offset: Em::zero(),
                y_offset: Em::zero(),
                adjustability: Adjustability::default(),
                range,
                safe_to_break: true,
                c: '-',
                span: (Span::detached(), 0),
                is_justifiable: false,
                script: Script::Common,
            };
            match side {
                Side::Left => self.glyphs.to_mut().insert(0, glyph),
                Side::Right => self.glyphs.to_mut().push(glyph),
            }
            Some(())
        });
    }

    /// Find the subslice of glyphs that represent the given text range if both
    /// sides are safe to break.
    fn slice_safe_to_break(&self, text_range: Range<usize>) -> Option<&[ShapedGlyph]> {
        let Range { mut start, mut end } = text_range;
        if !self.dir.is_positive() {
            std::mem::swap(&mut start, &mut end);
        }

        let left = self.find_safe_to_break(start, Side::Left)?;
        let right = self.find_safe_to_break(end, Side::Right)?;
        Some(&self.glyphs[left..right])
    }

    /// Find the glyph offset matching the text index that is most towards the