katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use crate::export_surface_span::SurfaceTextSpan;
use image::{Rgba, RgbaImage};

use super::super::SpanVisualRange;

const SPAN_BACKGROUND_COLOR: Rgba<u8> = Rgba([255, 235, 59, 255]);
const INLINE_CODE_BACKGROUND: Rgba<u8> = Rgba([239, 242, 246, 255]);
const HIGHLIGHT_Y_OFFSET_SCALE: f32 = 0.12;
const HIGHLIGHT_HEIGHT_SCALE: f32 = 1.18;
const INLINE_CODE_Y_OFFSET_SCALE: f32 = 0.08;
const INLINE_CODE_LEFT_PADDING: u32 = 4;
const INLINE_CODE_EXTRA_WIDTH: u32 = 8;
const INLINE_CODE_HEIGHT_SCALE: f32 = 1.24;

pub(super) fn draw_span_backgrounds(
    image: &mut RgbaImage,
    spans: &[SurfaceTextSpan],
    ranges: &[Option<SpanVisualRange>],
    x: u32,
    y: u32,
    size: f32,
) {
    for (index, span) in spans.iter().enumerate() {
        SpanBackgroundPainter::paint(image, span, ranges.get(index), x, y, size);
    }
}

struct SpanBackgroundPainter;

impl SpanBackgroundPainter {
    fn paint(
        image: &mut RgbaImage,
        span: &SurfaceTextSpan,
        range: Option<&Option<SpanVisualRange>>,
        x: u32,
        y: u32,
        size: f32,
    ) {
        let Some(range) = range.and_then(|range| *range) else {
            return;
        };
        if span.style.highlight {
            Self::paint_highlight(image, range, x, y, size);
            return;
        }
        if span.style.inline_code {
            Self::paint_inline_code(image, range, x, y, size);
        }
    }

    fn paint_highlight(image: &mut RgbaImage, range: SpanVisualRange, x: u32, y: u32, size: f32) {
        draw_fill_rect(
            image,
            x.saturating_add(range.start_x),
            y + (size * HIGHLIGHT_Y_OFFSET_SCALE) as u32,
            range.width(),
            (size * HIGHLIGHT_HEIGHT_SCALE) as u32,
            SPAN_BACKGROUND_COLOR,
        );
    }

    fn paint_inline_code(image: &mut RgbaImage, range: SpanVisualRange, x: u32, y: u32, size: f32) {
        draw_fill_rect(
            image,
            x.saturating_add(range.start_x)
                .saturating_sub(INLINE_CODE_LEFT_PADDING),
            y + (size * INLINE_CODE_Y_OFFSET_SCALE) as u32,
            range.width() + INLINE_CODE_EXTRA_WIDTH,
            (size * INLINE_CODE_HEIGHT_SCALE) as u32,
            INLINE_CODE_BACKGROUND,
        );
    }
}

fn draw_fill_rect(image: &mut RgbaImage, x: u32, y: u32, width: u32, height: u32, color: Rgba<u8>) {
    for dy in 0..height {
        for dx in 0..width {
            PixelWriter::put(image, x + dx, y + dy, color);
        }
    }
}

struct PixelWriter;

impl PixelWriter {
    fn put(image: &mut RgbaImage, target_x: u32, target_y: u32, color: Rgba<u8>) {
        if target_x < image.width() && target_y < image.height() {
            image.put_pixel(target_x, target_y, color);
        }
    }
}

#[cfg(test)]
#[path = "export_surface_font_rendering_backgrounds_tests.rs"]
mod tests;