mirror of
https://framagit.org/almet/jardiflore.git
synced 2025-04-28 12:02:37 +02:00
249 lines
6.9 KiB
Rust
249 lines
6.9 KiB
Rust
use calamine::{open_workbook, Data, Reader, Xlsx};
|
|
|
|
use printpdf::image::RawImage;
|
|
use printpdf::{
|
|
Mm, Op, PdfDocument, PdfPage, PdfSaveOptions, PdfWarnMsg, RawImageData, RawImageFormat,
|
|
XObjectTransform,
|
|
};
|
|
use structopt::StructOpt;
|
|
|
|
use ab_glyph::{FontRef, PxScale};
|
|
use image::{ImageBuffer, Rgba};
|
|
use imageproc::drawing::draw_text_mut;
|
|
|
|
// A4 dimensions
|
|
const A4_WIDTH: f32 = 210.0;
|
|
const A4_HEIGHT: f32 = 297.0;
|
|
|
|
// Configuration for the label
|
|
const DPI: f32 = 300.0;
|
|
const MM_TO_PX: f32 = DPI / 25.4;
|
|
const PX_TO_PT: f32 = 0.75;
|
|
const LABEL_WIDTH: f32 = 210.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)
|
|
}
|
|
|
|
fn generate_label(record: Record) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
|
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<Rgba<u8>, Vec<u8>> =
|
|
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,
|
|
);
|
|
return label;
|
|
}
|
|
|
|
fn combine_labels(
|
|
labels: &[ImageBuffer<Rgba<u8>, Vec<u8>>],
|
|
output_dir: &str,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let labels_per_page = (A4_HEIGHT / (LABEL_HEIGHT / MM_TO_PX)) as usize;
|
|
println!("Labels per page: {}", labels_per_page);
|
|
|
|
// Create a new PDF document
|
|
let mut doc = PdfDocument::new("Labels Document");
|
|
let mut pages = Vec::new();
|
|
let mut warnings: Vec<PdfWarnMsg> = Vec::new();
|
|
|
|
// Group labels by page
|
|
for chunk in labels.chunks(labels_per_page) {
|
|
let mut page_contents = Vec::new();
|
|
let mut y_position = 0.0;
|
|
|
|
for label in chunk {
|
|
let pixels = label.clone().into_raw();
|
|
let image = RawImage {
|
|
pixels: RawImageData::U8(pixels),
|
|
width: LABEL_WIDTH as usize,
|
|
height: LABEL_HEIGHT as usize,
|
|
data_format: RawImageFormat::RGBA8,
|
|
tag: Vec::new(),
|
|
};
|
|
let image_id = doc.add_image(&image);
|
|
|
|
page_contents.push(Op::UseXobject {
|
|
id: image_id,
|
|
transform: XObjectTransform {
|
|
translate_x: None,
|
|
translate_y: Some(printpdf::Px(y_position as usize).into_pt(DPI)),
|
|
rotate: None,
|
|
scale_x: None,
|
|
scale_y: None,
|
|
dpi: None,
|
|
},
|
|
});
|
|
|
|
// Move y position up for next label
|
|
y_position += LABEL_HEIGHT;
|
|
}
|
|
|
|
// Create page and add to pages list
|
|
let page = PdfPage::new(Mm(A4_WIDTH), Mm(A4_HEIGHT), page_contents);
|
|
pages.push(page);
|
|
}
|
|
|
|
// Add all pages to document and save
|
|
let pdf_bytes: Vec<u8> = doc
|
|
.with_pages(pages)
|
|
.save(&PdfSaveOptions::default(), &mut warnings);
|
|
|
|
// Write PDF bytes to file
|
|
std::fs::write("test.pdf", pdf_bytes)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[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)?;
|
|
|
|
let mut labels = Vec::new();
|
|
for record in records {
|
|
labels.push(generate_label(record));
|
|
}
|
|
println!("labels: {}", labels.len());
|
|
combine_labels(&labels, &opts.output_dir)
|
|
}
|