ilmenite 0.7.0

A rust library for shaping, placing, and rasterizing text primarily for Basalt.
Documentation
use crate::raster::{CpuRasterContext, GpuRasterContext};
use crate::shaders::glyph_cs;
use crate::{
    ImtError, ImtGeometry, ImtImageView, ImtParsedGlyph, ImtParser, ImtPoint, ImtRasterOpts,
};
use std::iter;
use std::sync::Arc;
use vulkano::buffer::cpu_access::CpuAccessibleBuffer;
use vulkano::buffer::BufferUsage;
use vulkano::command_buffer::{
    AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBuffer,
};
use vulkano::image::{ImageCreateFlags, ImageDimensions, ImageUsage, StorageImage};
use vulkano::pipeline::PipelineBindPoint;
use vulkano::sync::GpuFuture;

#[derive(Clone)]
pub enum ImtBitmapData {
    Empty,
    LRGBA(Arc<Vec<f32>>),
    Image(Arc<ImtImageView>),
}

#[derive(Debug, Clone)]
pub struct ImtBitmapMetrics {
    pub width: u32,
    pub height: u32,
    pub bearing_x: f32,
    pub bearing_y: f32,
}

/// Data is Linear RGBA
#[derive(Clone)]
pub struct ImtGlyphBitmap {
    parsed: Arc<ImtParsedGlyph>,
    metrics: ImtBitmapMetrics,
    lines: Vec<(ImtPoint, ImtPoint)>,
    scaler: f32,
    offset_x: f32,
    offset_y: f32,
    data: Option<ImtBitmapData>,
}

fn expand_round(val: f32, direction: bool) -> f32 {
    if direction {
        if val.is_sign_positive() {
            val.ceil() + 1.0
        } else {
            val.trunc() + 1.0
        }
    } else {
        if val.is_sign_positive() {
            val.trunc() - 1.0
        } else {
            val.ceil() - 1.0
        }
    }
}

impl ImtGlyphBitmap {
    pub fn new(
        parser: &ImtParser,
        parsed: Arc<ImtParsedGlyph>,
        text_height: f32,
        raster_opts: &ImtRasterOpts,
    ) -> ImtGlyphBitmap {
        let font_props = parser.font_props();
        let scaler = font_props.scaler * text_height;

        let mut bearing_x = parsed.min_x * scaler;
        let mut bearing_y = (font_props.ascender - parsed.max_y) * scaler;

        let (offset_x, offset_y) = if raster_opts.align_whole_pixels {
            let offset_x = (bearing_x - bearing_x.ceil()) + 1.0;
            bearing_x = bearing_x.ceil();
            let offset_y = -(bearing_y - bearing_y.ceil()) - 1.0;
            bearing_y = bearing_y.ceil();
            (offset_x, offset_y)
        } else {
            (0.0, 0.0)
        };

        let height = (expand_round(parsed.max_y * scaler, true)
            - expand_round(parsed.min_y * scaler, false)) as u32
            + 1;
        let width = (expand_round(parsed.max_x * scaler, true)
            - expand_round(parsed.min_x * scaler, false)) as u32
            + 1;

        ImtGlyphBitmap {
            parsed,
            metrics: ImtBitmapMetrics {
                width,
                height,
                bearing_x,
                bearing_y,
            },
            offset_x,
            offset_y,
            data: None,
            lines: Vec::new(),
            scaler,
        }
    }

    pub fn data(&self) -> Option<ImtBitmapData> {
        self.data.clone()
    }

    pub fn metrics(&self) -> ImtBitmapMetrics {
        self.metrics.clone()
    }

    pub(crate) fn raster_cpu(&mut self, context: &CpuRasterContext) -> Result<(), ImtError> {
        let ray_count = context.rays.len();
        let sample_count = context.samples.len();

        let ray_intersects = |l1p1: [f32; 2],

                              l1p2: [f32; 2],
                              l2p1: [f32; 2],
                              l2p2: [f32; 2]|

         -> Option<[f32; 2]> {
            let r = [l1p2[0] - l1p1[0], l1p2[1] - l1p1[1]];
            let s = [l2p2[0] - l2p1[0], l2p2[1] - l2p1[1]];
            let det = (r[0] * s[1]) - (r[1] * s[0]);
            let u = (((l2p1[0] - l1p1[0]) * r[1]) - ((l2p1[1] - l1p1[1]) * r[0])) / det;
            let t = (((l2p1[0] - l1p1[0]) * s[1]) - ((l2p1[1] - l1p1[1]) * s[0])) / det;

            if t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0 {
                Some([(l1p1[0] + r[0]) * t, (l1p1[1] + r[1]) * t])
            } else {
                None
            }
        };

        let cell_height = self.scaler / (sample_count as f32).sqrt();
        let cell_width = cell_height / 3.0;

        let sample_filled = |ray_src: [f32; 2], ray_len: f32| -> Option<f32> {
            let mut rays_filled = 0;
            let mut ray_fill_amt = 0.0;

            for ray in context.rays.iter() {
                let mut hits = 0_isize;

                let ray_dest =
                    [ray_src[0] + (ray[0] * ray_len), ray_src[1] + (ray[1] * ray_len)];

                let ray_angle = (ray[1] / ray[0]).atan();
                let mut ray_max_dist = (cell_width / 2.0) / ray_angle.cos();

                if ray_max_dist > (cell_height / 2.0) {
                    ray_max_dist = (cell_height / 2.0) / (1.570796327 - ray_angle).cos();
                }

                let mut ray_min_dist = ray_max_dist;

                for line in self.lines.iter() {
                    match ray_intersects(ray_src, ray_dest, [line.0.x, line.0.y], [
                        line.1.x, line.1.y,
                    ]) {
                        Some(intersect_point) => {
                            let dist = ((ray_src[0] - intersect_point[0]).powi(2)
                                + (ray_src[1] - intersect_point[1]).powi(2))
                            .sqrt();

                            if dist < ray_min_dist {
                                ray_min_dist = dist;
                            }

                            hits += 1;
                        },
                        None => (),
                    }
                }

                if hits % 2 != 0 {
                    rays_filled += 1;
                    ray_fill_amt += ray_min_dist / ray_max_dist;
                }
            }

            if rays_filled >= ray_count / 2 {
                Some(ray_fill_amt / rays_filled as f32)
            } else {
                None
            }
        };

        let transform_coords =
            |coords: [usize; 2], offset_i: usize, offset: [f32; 2]| -> [f32; 2] {
                let mut coords = [coords[0] as f32, coords[1] as f32 * -1.0];
                coords[0] -= self.offset_x;
                coords[1] -= self.offset_y;
                coords[0] += context.samples[offset_i][0];
                coords[1] += context.samples[offset_i][1];
                coords[0] += offset[0];
                coords[1] += offset[1];
                coords[0] /= self.scaler;
                coords[1] /= self.scaler;
                coords[0] += self.parsed.min_x;
                coords[1] += self.parsed.max_y;
                coords
            };

        let get_value = |coords: [usize; 2], offset: [f32; 2], ray_len: f32| -> f32 {
            let mut fill_amt_sum = 0.0;

            for i in 0..sample_count {
                if let Some(fill_amt) =
                    sample_filled(transform_coords(coords, i, offset), ray_len)
                {
                    fill_amt_sum += fill_amt;
                }
            }

            fill_amt_sum / sample_count as f32
        };

        let mut bitmap: Vec<f32> =
            Vec::with_capacity((self.metrics.width * self.metrics.height * 4) as usize);
        bitmap.resize((self.metrics.width * self.metrics.height * 4) as usize, 0.0);
        let ray_len = ((self.metrics.width as f32 / self.scaler).powi(2)
            + (self.metrics.height as f32 / self.scaler).powi(2))
        .sqrt();

        for x in 0..self.metrics.width {
            for y in 0..self.metrics.height {
                let rindex = (((y * self.metrics.width) + x) * 4) as usize;
                let r = get_value([x as usize, y as usize], [1.0 / 6.0, 0.0], ray_len);
                let g = get_value([x as usize, y as usize], [3.0 / 6.0, 0.0], ray_len);
                let b = get_value([x as usize, y as usize], [5.0 / 6.0, 0.0], ray_len);
                let a = (r + g + b) / 3.0;
                bitmap[rindex] = r / a;
                bitmap[rindex + 1] = g / a;
                bitmap[rindex + 2] = b / a;
                bitmap[rindex + 3] = a;
            }
        }

        self.data = Some(ImtBitmapData::LRGBA(Arc::new(bitmap)));
        Ok(())
    }

    pub(crate) fn raster_gpu(&mut self, context: &GpuRasterContext) -> Result<(), ImtError> {
        if self.metrics.width == 0 || self.metrics.height == 0 {
            self.data = Some(ImtBitmapData::Empty);
            return Ok(());
        }

        let glyph_buf: Arc<CpuAccessibleBuffer<glyph_cs::ty::Glyph>> =
            CpuAccessibleBuffer::from_data(
                context.device.clone(),
                BufferUsage {
                    uniform_buffer: true,
                    ..BufferUsage::none()
                },
                false,
                glyph_cs::ty::Glyph {
                    scaler: self.scaler,
                    width: self.metrics.width,
                    height: self.metrics.height,
                    line_count: self.lines.len() as u32,
                    bounds: [
                        self.parsed.min_x,
                        self.parsed.max_x,
                        self.parsed.min_y,
                        self.parsed.max_y,
                    ],
                    offset: [self.offset_x, self.offset_y],
                },
            )
            .unwrap();

        let bitmap_img = ImtImageView::from_storage(
            StorageImage::with_usage(
                context.device.clone(),
                ImageDimensions::Dim2d {
                    width: self.metrics.width,
                    height: self.metrics.height,
                    array_layers: 1,
                },
                context.raster_image_format,
                ImageUsage {
                    transfer_source: true,
                    storage: true,
                    ..ImageUsage::none()
                },
                ImageCreateFlags::none(),
                iter::once(context.queue.family()),
            )
            .unwrap(),
        )
        .unwrap();

        let line_buf: Arc<CpuAccessibleBuffer<[[f32; 4]]>> = CpuAccessibleBuffer::from_iter(
            context.device.clone(),
            BufferUsage {
                storage_buffer: true,
                ..BufferUsage::none()
            },
            false,
            self.lines.iter().map(|line| [line.0.x, line.0.y, line.1.x, line.1.y]),
        )
        .unwrap();

        let mut desc_set_pool = context.set_pool.lock();
        let mut desc_set_builder = desc_set_pool.next();

        desc_set_builder
            .add_buffer(context.common_buf.clone())
            .unwrap()
            .add_buffer(glyph_buf)
            .unwrap()
            .add_image(bitmap_img.clone())
            .unwrap()
            .add_buffer(line_buf)
            .unwrap();

        let descriptor_set = desc_set_builder.build().unwrap();

        drop(desc_set_pool);

        let mut cmd_buf = AutoCommandBufferBuilder::primary(
            context.device.clone(),
            context.queue.family(),
            CommandBufferUsage::OneTimeSubmit,
        )
        .unwrap();

        cmd_buf
            .bind_pipeline_compute(context.pipeline.clone())
            .bind_descriptor_sets(
                PipelineBindPoint::Compute,
                context.pipeline.layout().clone(),
                0,
                descriptor_set,
            )
            .dispatch([self.metrics.width, self.metrics.height, 1])
            .unwrap();

        cmd_buf
            .build()
            .unwrap()
            .execute(context.queue.clone())
            .unwrap()
            .then_signal_fence_and_flush()
            .unwrap()
            .wait(None)
            .unwrap();

        if !context.raster_to_image {
            let len = (self.metrics.width * self.metrics.height * 4) as u64;
            let bitmap_buf: Arc<CpuAccessibleBuffer<[u8]>> = unsafe {
                CpuAccessibleBuffer::uninitialized_array(
                    context.device.clone(),
                    len,
                    BufferUsage {
                        transfer_destination: true,
                        ..BufferUsage::none()
                    },
                    true,
                )
                .unwrap()
            };

            let mut cmd_buf = AutoCommandBufferBuilder::primary(
                context.device.clone(),
                context.queue.family(),
                CommandBufferUsage::OneTimeSubmit,
            )
            .unwrap();

            cmd_buf.copy_image_to_buffer(bitmap_img, bitmap_buf.clone()).unwrap();

            cmd_buf
                .build()
                .unwrap()
                .execute(context.queue.clone())
                .unwrap()
                .then_signal_fence_and_flush()
                .unwrap()
                .wait(None)
                .unwrap();

            self.data = Some(ImtBitmapData::LRGBA(Arc::new(
                bitmap_buf
                    .read()
                    .unwrap()
                    .iter()
                    .map(|v| *v as f32 / u8::max_value() as f32)
                    .collect(),
            )));
        } else {
            self.data = Some(ImtBitmapData::Image(bitmap_img));
        }

        Ok(())
    }

    pub(crate) fn create_outline(&mut self) {
        for geometry in self.parsed.geometry.clone() {
            self.draw_geometry(&geometry);
        }
    }

    fn draw_geometry(&mut self, geo: &ImtGeometry) {
        match geo {
            &ImtGeometry::Line(ref points) => self.draw_line(&points[0], &points[1]),
            &ImtGeometry::Curve(ref points) =>
                self.draw_curve(&points[0], &points[1], &points[2]),
        }
    }

    fn draw_line(&mut self, point_a: &ImtPoint, point_b: &ImtPoint) {
        self.lines.push((
            ImtPoint {
                x: point_a.x,
                y: point_a.y,
            },
            ImtPoint {
                x: point_b.x,
                y: point_b.y,
            },
        ));
    }

    fn draw_curve(&mut self, point_a: &ImtPoint, point_b: &ImtPoint, point_c: &ImtPoint) {
        let mut length = 0.0;
        let mut last_point = point_a.clone();
        let mut steps = 10_usize;

        for s in 1..=steps {
            let t = s as f32 / steps as f32;
            let next_point = ImtPoint {
                x: ((1.0 - t).powi(2) * point_a.x)
                    + (2.0 * (1.0 - t) * t * point_b.x)
                    + (t.powi(2) * point_c.x),
                y: ((1.0 - t).powi(2) * point_a.y)
                    + (2.0 * (1.0 - t) * t * point_b.y)
                    + (t.powi(2) * point_c.y),
            };

            length += last_point.dist(&next_point);
            last_point = next_point;
        }

        steps = (length * self.scaler * 2.0).ceil() as usize;

        if steps < 3 {
            steps = 3;
        }

        last_point = point_a.clone();

        for s in 1..=steps {
            let t = s as f32 / steps as f32;
            let next_point = ImtPoint {
                x: ((1.0 - t).powi(2) * point_a.x)
                    + (2.0 * (1.0 - t) * t * point_b.x)
                    + (t.powi(2) * point_c.x),
                y: ((1.0 - t).powi(2) * point_a.y)
                    + (2.0 * (1.0 - t) * t * point_b.y)
                    + (t.powi(2) * point_c.y),
            };

            self.draw_line(&last_point, &next_point);
            last_point = next_point;
        }
    }
}