katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
#[derive(Clone, Copy, Debug)]
pub(super) struct SpanVisualRange {
    start_x: u32,
    end_x: u32,
}

impl SpanVisualRange {
    pub(super) fn new(start_x: f32, end_x: f32) -> Self {
        Self {
            start_x: start_x.floor().max(0.0) as u32,
            end_x: end_x.ceil().max(0.0) as u32,
        }
    }

    fn merge(self, start_x: f32, end_x: f32) -> Self {
        Self::new(
            (self.start_x as f32).min(start_x),
            (self.end_x as f32).max(end_x),
        )
    }

    pub(super) fn width(self) -> u32 {
        self.end_x.saturating_sub(self.start_x).max(1)
    }

    pub(super) fn start_x(&self) -> u32 {
        self.start_x
    }
}

pub(super) fn span_visual_ranges(
    buffer: &cosmic_text::Buffer,
    span_count: usize,
) -> Vec<Option<SpanVisualRange>> {
    let mut ranges: Vec<Option<SpanVisualRange>> = vec![None; span_count];
    for run in buffer.layout_runs() {
        for glyph in run.glyphs {
            SpanVisualRangeCollector::collect_glyph(&mut ranges, span_count, glyph);
        }
    }
    ranges
}

struct SpanVisualRangeCollector;

impl SpanVisualRangeCollector {
    fn collect_glyph(
        ranges: &mut [Option<SpanVisualRange>],
        span_count: usize,
        glyph: &cosmic_text::LayoutGlyph,
    ) {
        if glyph.metadata == 0 {
            return;
        }
        let span_index = glyph.metadata.saturating_sub(1);
        if span_index >= span_count {
            return;
        }
        let start_x = glyph.x;
        let end_x = glyph.w + start_x;
        ranges[span_index] = Some(match ranges[span_index] {
            Some(range) => range.merge(start_x, end_x),
            None => SpanVisualRange::new(start_x, end_x),
        });
    }
}