use calamine::{open_workbook, Data, Reader, Xlsx}; use structopt::StructOpt; use ab_glyph::{FontRef, PxScale}; use image::{ImageBuffer, Rgba}; use imageproc::drawing::draw_text_mut; use small_uid::SmallUid; use std::path::{Path, PathBuf}; // Configuration for the label const DPI: f32 = 300.0; const MM_TO_PX: f32 = DPI / 25.4; const LABEL_WIDTH: f32 = 220.0 * MM_TO_PX; const LABEL_HEIGHT: f32 = 16.0 * MM_TO_PX; // Elements positionning const MARGIN_LEFT: f32 = 72.0 * MM_TO_PX; const LOGO_WIDTH: u32 = 151; const LOGO_HEIGHT: u32 = 64; const LOGO_POS_X: u32 = LABEL_WIDTH as u32 - LOGO_WIDTH - 10; const LOGO_POS_Y: u32 = 10; const CLIENT_CODE_POS_X: i32 = MARGIN_LEFT as i32 + 10; const CLIENT_CODE_POS_Y: i32 = 10; const CLIENT_CODE_SCALE: f32 = 24.0; const SPECIES_VARIETY_POS_X: i32 = MARGIN_LEFT as i32 + 10; const SPECIES_VARIETY_POS_Y: i32 = 80; const SPECIES_VARIETY_SCALE: f32 = 24.0; const DATE_POS_X: i32 = MARGIN_LEFT as i32 + 10; const DATE_POS_Y: i32 = 40; const DATE_SCALE: f32 = 32.0; const COLOR_TEXT: Rgba = Rgba([0, 0, 0, 255]); const COLOR_BACKGROUND: Rgba = Rgba([255, 255, 255, 255]); #[derive(Debug, Clone)] struct Record { client_code: String, species: String, variety: String, delivery_week: String, } const COLUMN_MAP: [(&str, char); 5] = [ ("client_code", 'A'), ("species", 'B'), ("variety", 'C'), ("sowing_week", 'I'), ("delivery_week", 'J'), ]; fn letter_to_index(col: char) -> usize { (col as u8 - b'A') as usize } fn column_name_to_index(column: &str) -> usize { COLUMN_MAP .map(|(name, col)| (name, letter_to_index(col))) .iter() .find(|(name, _)| *name == column) .unwrap() .1 } fn get_cell_value(row: &[Data], column: &str) -> String { let col_index = column_name_to_index(column); match row.get(col_index) { Some(Data::String(s)) => s.to_string(), Some(Data::Float(f)) => f.to_string(), _ => "".to_string(), } } fn parse_xlsx(delivery_week_filter: &str) -> Result, Box> { let mut workbook: Xlsx<_> = open_workbook("data.xlsx")?; let range = workbook.worksheet_range("Feuille1")?; let records: Vec = range .rows() .filter_map(|row| { let delivery_week = get_cell_value(row, "delivery_week"); if delivery_week.to_lowercase() != delivery_week_filter.to_lowercase() { return None; } Some(Record { client_code: get_cell_value(row, "client_code"), species: get_cell_value(row, "species"), variety: get_cell_value(row, "variety"), delivery_week, }) }) .collect(); Ok(records) } // The label looks like this: // // Tomate (Solanum lycopersicum) (species) /^\ // Variété: San Marzano (variety) / j \ // / ard \ // / iflore\ // --------- // SEMAINE DE LIVRAISON: 22 // // 123 Allée des Récoltes // Tél: Téléphone. // fn generate_label(output_dir: &String, record: Record) -> PathBuf { let font_data = include_bytes!("./assets/DejaVuSans.ttf"); let font = FontRef::try_from_slice(font_data).unwrap(); let logo = image::open("./assets/logo.png").unwrap(); let mut label = ImageBuffer::new(LABEL_WIDTH as u32, LABEL_HEIGHT as u32); // Fill in the background for pixel in label.pixels_mut() { *pixel = COLOR_BACKGROUND; } // Logo let resized_logo = image::imageops::resize( &logo, LOGO_WIDTH, LOGO_HEIGHT, image::imageops::FilterType::Lanczos3, ); image::imageops::overlay( &mut label, &resized_logo, LOGO_POS_X as i64, LOGO_POS_Y as i64, ); draw_text_mut( &mut label, COLOR_TEXT, CLIENT_CODE_POS_X, CLIENT_CODE_POS_Y, PxScale::from(CLIENT_CODE_SCALE), &font, &record.client_code, ); draw_text_mut( &mut label, COLOR_TEXT, DATE_POS_X, DATE_POS_Y, PxScale::from(DATE_SCALE), &font, &record.delivery_week, ); let species_varieties: String = "".to_owned() + &record.species + " " + &record.variety; draw_text_mut( &mut label, COLOR_TEXT, SPECIES_VARIETY_POS_X, SPECIES_VARIETY_POS_Y, PxScale::from(SPECIES_VARIETY_SCALE), &font, &species_varieties, ); // Save the label let output_path = Path::new(output_dir).join(format!("label_{}.png", SmallUid::new())); label.save(output_path.clone()).unwrap(); return output_path; } #[derive(StructOpt)] struct Opts { #[structopt(short = "o", long, default_value = "output")] output_dir: String, #[structopt(short = "w", long, default_value = "06")] week: String, } fn main() -> Result<(), Box> { let opts = Opts::from_args(); std::fs::create_dir_all(&opts.output_dir).unwrap(); let week_number = "s".to_owned() + &opts.week; let records = parse_xlsx(&week_number)?; for record in records { let path = generate_label(&opts.output_dir, record); println!("{}", path.into_os_string().into_string().unwrap()) } Ok(()) }