|  | @@ -1,89 +1,19 @@
 | 
	
		
			
				|  |  | -use std::fs::File;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -use clap::Parser;
 | 
	
		
			
				|  |  | -use serde::Deserialize;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +mod data;
 | 
	
		
			
				|  |  | +mod draw;
 | 
	
		
			
				|  |  |  mod image;
 | 
	
		
			
				|  |  | +mod opts;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -use crate::image::{Image, Pixel};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#[derive(Deserialize, Debug)]
 | 
	
		
			
				|  |  | -struct ColorEntry {
 | 
	
		
			
				|  |  | -    color: Pixel,
 | 
	
		
			
				|  |  | -    symbol: String,
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#[derive(Deserialize, Debug)]
 | 
	
		
			
				|  |  | -struct Mapping(Vec<ColorEntry>);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl Mapping {
 | 
	
		
			
				|  |  | -    fn load(path: impl AsRef<std::path::Path>) -> Result<Mapping, Box<dyn std::error::Error>> {
 | 
	
		
			
				|  |  | -        Ok(serde_yaml::from_reader(File::open(path)?)?)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    fn lookup(&self, color: Pixel) -> &str {
 | 
	
		
			
				|  |  | -        let found = self.0.iter().find(|entry| entry.color == color);
 | 
	
		
			
				|  |  | -        match found {
 | 
	
		
			
				|  |  | -            Some(entry) => &entry.symbol,
 | 
	
		
			
				|  |  | -            None => panic!("Unable to find entry for {:?}", color),
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#[derive(Deserialize)]
 | 
	
		
			
				|  |  | -struct Config {
 | 
	
		
			
				|  |  | -    grid_every: u32,
 | 
	
		
			
				|  |  | -    line_weight: f64,
 | 
	
		
			
				|  |  | -    major_line_weight: f64,
 | 
	
		
			
				|  |  | -    grid_size: f64,
 | 
	
		
			
				|  |  | -    font: String,
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl Config {
 | 
	
		
			
				|  |  | -    fn scale(&self, n: u32) -> f64 {
 | 
	
		
			
				|  |  | -        n as f64 * self.grid_size
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl Default for Config {
 | 
	
		
			
				|  |  | -    fn default() -> Config {
 | 
	
		
			
				|  |  | -        Config {
 | 
	
		
			
				|  |  | -            grid_every: 10,
 | 
	
		
			
				|  |  | -            line_weight: 1.0,
 | 
	
		
			
				|  |  | -            major_line_weight: 3.0,
 | 
	
		
			
				|  |  | -            grid_size: 24.0,
 | 
	
		
			
				|  |  | -            font: "Fira Sans 12".to_string(),
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#[derive(Parser, Debug)]
 | 
	
		
			
				|  |  | -#[clap(author, version, about, long_about = None)]
 | 
	
		
			
				|  |  | -struct Options {
 | 
	
		
			
				|  |  | -    image: String,
 | 
	
		
			
				|  |  | -    mapping: String,
 | 
	
		
			
				|  |  | -    output: Option<String>,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    #[clap(long, default_value_t = 10)]
 | 
	
		
			
				|  |  | -    grid: u32,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    #[clap(long, default_value_t = 1.0)]
 | 
	
		
			
				|  |  | -    line_weight: f64,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    #[clap(long, default_value_t = 3.0)]
 | 
	
		
			
				|  |  | -    major_line_weight: f64,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    #[clap(long, default_value_t = 24.0)]
 | 
	
		
			
				|  |  | -    size: f64,
 | 
	
		
			
				|  |  | +use clap::Parser;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    #[clap(long, default_value_t = String::from("Fira Sans 12"))]
 | 
	
		
			
				|  |  | -    font: String,
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +use crate::data::{Config, Mapping};
 | 
	
		
			
				|  |  | +use crate::draw::Pattern;
 | 
	
		
			
				|  |  | +use crate::image::Image;
 | 
	
		
			
				|  |  | +use crate::opts::Options;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
	
		
			
				|  |  |      let opts = Options::parse();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    let img = Image::load(opts.image)?;
 | 
	
		
			
				|  |  | +    let image = Image::load(opts.image)?;
 | 
	
		
			
				|  |  |      let mapping = Mapping::load(opts.mapping)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      let config = Config {
 | 
	
	
		
			
				|  | @@ -95,53 +25,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      let surf = cairo::SvgSurface::new(
 | 
	
		
			
				|  |  | -        config.scale(img.width),
 | 
	
		
			
				|  |  | -        config.scale(img.height),
 | 
	
		
			
				|  |  | +        config.scale(image.width),
 | 
	
		
			
				|  |  | +        config.scale(image.height),
 | 
	
		
			
				|  |  |          Some(opts.output.unwrap_or_else(|| "output.svg".to_string())),
 | 
	
		
			
				|  |  |      )?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    let ctx = cairo::Context::new(&surf)?;
 | 
	
		
			
				|  |  | -    ctx.set_source_rgb(1.0, 1.0, 1.0);
 | 
	
		
			
				|  |  | -    ctx.paint()?;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    let layout = pangocairo::functions::create_layout(&ctx).unwrap();
 | 
	
		
			
				|  |  | -    let font = pango::FontDescription::from_string(&config.font);
 | 
	
		
			
				|  |  | -    layout.set_width(2880);
 | 
	
		
			
				|  |  | -    layout.set_font_description(Some(&font));
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    ctx.set_source_rgb(0.0, 0.0, 0.0);
 | 
	
		
			
				|  |  | -    for i in 0..=img.width {
 | 
	
		
			
				|  |  | -        ctx.move_to(config.scale(i), 0.0);
 | 
	
		
			
				|  |  | -        ctx.line_to(config.scale(i), config.scale(img.height));
 | 
	
		
			
				|  |  | -        ctx.set_line_width(if i % config.grid_every == 0 {
 | 
	
		
			
				|  |  | -            config.major_line_weight
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            config.line_weight
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        ctx.stroke()?;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    for j in 0..=img.height {
 | 
	
		
			
				|  |  | -        ctx.move_to(0.0, config.scale(j));
 | 
	
		
			
				|  |  | -        ctx.line_to(config.scale(img.width), config.scale(j));
 | 
	
		
			
				|  |  | -        ctx.set_line_width(if j % config.grid_every == 0 {
 | 
	
		
			
				|  |  | -            config.major_line_weight
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            config.line_weight
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        ctx.stroke()?;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    ctx.set_source_rgb(0.0, 0.0, 0.0);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    for ((x, y), pixel) in img.iter() {
 | 
	
		
			
				|  |  | -        let l = mapping.lookup(pixel);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if !l.is_empty() {
 | 
	
		
			
				|  |  | -            ctx.move_to(config.scale(x) + 5.0, config.scale(y) + 3.0);
 | 
	
		
			
				|  |  | -            layout.set_text(l);
 | 
	
		
			
				|  |  | -            pangocairo::functions::show_layout(&ctx, &layout);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    Pattern {
 | 
	
		
			
				|  |  | +        image,
 | 
	
		
			
				|  |  | +        config,
 | 
	
		
			
				|  |  | +        mapping,
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    .draw(cairo::Context::new(&surf)?)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      surf.finish();
 | 
	
		
			
				|  |  |  
 |