jardiflore/main.rs
Alexis Métaireau b64dd9a32c
Use the proper spacing, adding a margin when that's needed.
Dimensions given by Flo:

> Largeur: 1.6cm
> Boucle: 7.2cm
> Partie imprimable: 14.6cm
> Longueur hors boucle + partie imprimée = 22cm
2025-04-03 21:26:34 +02:00

199 lines
5.6 KiB
Rust

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<u8> = Rgba([0, 0, 0, 255]);
const COLOR_BACKGROUND: Rgba<u8> = 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<Vec<Record>, Box<dyn std::error::Error>> {
let mut workbook: Xlsx<_> = open_workbook("data.xlsx")?;
let range = workbook.worksheet_range("Feuille1")?;
let records: Vec<Record> = 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<dyn std::error::Error>> {
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(())
}