use ab_glyph::{FontArc, PxScale}; use fontdb::{Database, Family, Query, Source}; use image::{imageops, ColorType, DynamicImage, ImageBuffer}; use imageproc::drawing::{draw_filled_rect_mut, draw_polygon_mut, draw_text_mut, text_size, Canvas}; use imageproc::point::Point; use imageproc::rect::Rect; use std::io; use std::path::Path; use crate::ast::DepthExpr; type Offset = (u32, u32, u32); // x, y or width, height center type NCOffset = (u32, u32); // TODO: based on how much text const COMPONENT_TEXT_PADDING: u32 = 1 * RESOLUTION_MULTIPLIER; const TEXT_SCALE: f32 = 12.0 * RESOLUTION_MULTIPLIER as f32; const TEXT_LEN_WRAP: usize = 10; // TODO: LENGTH based on where to where const DIRECTION_LINE_THICKNESS: u32 = 2 * RESOLUTION_MULTIPLIER; const DIRECTION_LINE_LENGTH: u32 = 13 * RESOLUTION_MULTIPLIER; const DIRECTION_LINE_ARROW_OFFSET: u32 = 5 * RESOLUTION_MULTIPLIER / 2; const RESOLUTION_MULTIPLIER: u32 = 5; /* offset based mutation model, to avoid overflowing on previous image */ mod colors { use image::Rgba; pub const STARSTOP: Rgba = Rgba([ 63, 122, 86, 255, ]); pub const PROCESS: Rgba = Rgba([ 0, 104, 124, 255, ]); pub const IO: Rgba = Rgba([ 149, 30, 88, 255, ]); pub const DIRECT: Rgba = Rgba([ 31, 133, 53, 255, ]); pub const BG: Rgba = Rgba([ 0, 0, 0, 0, ]); pub const FG: Rgba = Rgba([ 255, 255, 255, 255, ]); } fn wrap_str(s: &str) -> Vec { s.chars() .collect::>() .chunks(TEXT_LEN_WRAP) .map(|c| { c.iter() .collect() }) .collect() } // TODO: store as singleton fn get_font() -> Result { let mut db = Database::new(); db.load_system_fonts(); match &db .face( db.query(&Query { families: &[Family::SansSerif], weight: fontdb::Weight::NORMAL, stretch: fontdb::Stretch::Normal, style: fontdb::Style::Normal, }) .or_else(|| { db.faces() .next() .map(|f| f.id) }) .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no system fonts"))?, ) .ok_or_else(|| io::Error::other("face missing"))? .source { Source::File(path) => std::fs::read(path).and_then(|bytes| FontArc::try_from_vec(bytes).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "parse font failed"))), Source::Binary(data) => FontArc::try_from_vec( data.as_ref() .as_ref() .to_vec(), ) .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "parse font failed")), _ => Err(io::Error::new(io::ErrorKind::Unsupported, "unsupported font source")), } } fn ext(img: &mut DynamicImage, (curw, curh): &mut NCOffset, (extw, exth): &mut NCOffset) { let (w, h) = img.dimensions(); let extedw = *extw + *curw; let extedh = *exth + *curh; if extedw <= w && extedh <= h { return; } let mut resized = ImageBuffer::from_pixel(w.max(extedw), h.max(extedh), colors::BG); imageops::overlay(&mut resized, img, 0, 0); *img = image::DynamicImage::ImageRgba8(resized); } // TODO: make draw_rect func fn draw_process(img: &mut DynamicImage, txt: &str, (curw, curh, c): &mut Offset) { let wrapped = wrap_str(txt); let pxscale = PxScale::from(TEXT_SCALE / 2_f32); let font = &get_font().unwrap(); let (text_w, _) = text_size(pxscale, font, wrapped[0].as_str()); let w = text_w + (2 * COMPONENT_TEXT_PADDING); let h = TEXT_SCALE as u32 / 2 * wrapped.len() as u32 + (2 * COMPONENT_TEXT_PADDING); // text_size's height is weird and incorrect ext(img, &mut (*c, *curh), &mut (w, h)); let cw = c .checked_sub(w / 2) .unwrap_or(0); draw_filled_rect_mut(img, Rect::at(cw as i32, *curh as i32).of_size(w, h), colors::PROCESS); // TODO: move to draw_text() for s in wrapped { draw_text_mut(img, colors::FG, cw as i32 + COMPONENT_TEXT_PADDING as i32, *curh as i32 + COMPONENT_TEXT_PADDING as i32, pxscale, font, s.as_str()); *curh += TEXT_SCALE as u32 / 2; } if *c == 0 { *c = *curw + (w / 2); } *curh += 2 * COMPONENT_TEXT_PADDING; } fn draw_direction(img: &mut DynamicImage, (_, orih, c): &mut Offset, dst: Option<&NCOffset>) { let (srcx, srcy) = (*c - (DIRECTION_LINE_THICKNESS / 2), *orih); let default = (srcx + DIRECTION_LINE_THICKNESS, srcy + DIRECTION_LINE_LENGTH - DIRECTION_LINE_ARROW_OFFSET); let (dstx, dsty) = dst.unwrap_or(&default); ext(img, &mut (*c, *orih), &mut (dstx - *c, dsty - *c)); let (srcx, srcy) = (srcx as i32, srcy as i32); let (dsty, dstx) = (*dsty as i32, *dstx as i32); draw_polygon_mut( img, &[ Point::new(srcx, srcy), Point::new(srcx, dsty), Point::new(dstx, dsty), Point::new(dstx, srcy), ], colors::DIRECT, ); *orih = dsty as u32; // arrow let x = *c as i32; let y = *orih as i32; let offset = DIRECTION_LINE_ARROW_OFFSET as i32; // left draw_polygon_mut( img, &[ Point::new(x, y), Point::new(x - offset, y - offset), Point::new(x, y - offset), ], colors::DIRECT, ); // right draw_polygon_mut( img, &[ Point::new(x, y), Point::new(x + offset, y - offset), Point::new(x, y - offset), ], colors::DIRECT, ); } fn build(ast: &[DepthExpr]) -> DynamicImage { let mut img = DynamicImage::new(0, 0, ColorType::Rgba8); // TODO: move to mutable singleton let mut offset: Offset = (0, 0, 0); draw_process(&mut img, "abcdefghijklmnop", &mut offset); draw_direction(&mut img, &mut offset, None); draw_process(&mut img, "a", &mut offset); draw_direction(&mut img, &mut offset, None); draw_process(&mut img, "a", &mut offset); draw_direction(&mut img, &mut offset, None); draw_process(&mut img, "a", &mut offset); draw_direction(&mut img, &mut offset, None); draw_process(&mut img, "a", &mut offset); let (a, b) = (offset.2, offset.1); draw_direction(&mut img, &mut offset, Some(&(a + 100, b + 1000))); draw_process(&mut img, "a", &mut offset); img } pub fn create(ast: &[DepthExpr], path: &Path) { build(ast) .save(path) .unwrap(); }