use clap::ArgMatches;
use headless_chrome::{protocol::page::ScreenshotFormat, Browser};
use regex::Regex;
use std::{fs, process, usize};
pub struct Arguments {
pub header: String,
pub body: String,
pub footer: String,
pub width: usize,
pub height: usize,
pub base64: bool,
pub debug: bool,
}
struct LabelConfig {
test_css: String,
real_css: String,
header: String,
paragraphs: Vec<String>,
footer: String,
max_width: usize,
max_height: usize,
html_path: String,
}
impl From<ArgMatches> for Arguments {
fn from(a: ArgMatches) -> Self {
let header = a.value_of_t_or_exit("header");
let body = a.value_of_t_or_exit("body");
let footer = a.value_of_t_or_exit("footer");
let width = a.value_of_t_or_exit("width");
let height = a.value_of_t_or_exit("height");
let base64 = a.is_present("base64");
let debug = a.is_present("debug");
Self {
header,
body,
footer,
width,
height,
base64,
debug,
}
}
}
pub fn create(args: Arguments) {
let html_path: String = format!(
"file://{}/label.html",
std::env::current_dir()
.expect("Couldn't get current working directory")
.to_str()
.expect("Couldn't turn path to str")
);
let test_css = format!(
r#"<style>body {{ width: fit-content; height: fit-content; position: relative; }} #div {{ position: relative; }} .border {{ border-left: {width}px solid grey; height: 2px; }} #header {{ padding-top: 1px; }} #footer * {{ margin: 0; }}</style>"#,
width = args.width,
);
let real_css = format!(
r#"<style>body {{ width: {width}px; height: {height}px; position: relative; }} #div {{ position: relative; }} .border {{ border-left: {width}px solid grey; height: 2px; }} #header {{ padding-top: 1px; }} #footer {{ position: absolute; bottom: 0; }}</style>"#,
width = args.width,
height = args.height
);
let header: String = format!(
r#"<div id="div"><div id="header">{}<div class="border"></div></div>"#,
args.header
);
let footer = format!(
r#"<div id="footer"><div class="border"></div>{}</div>"#,
args.footer
);
let paragraphs: Vec<String> = {
let mut body = args.body.clone();
let mut i: Vec<String> = Vec::new();
let p = Regex::new(r"</p>|</h2>|</h1>").unwrap();
for _x in p.captures_iter(&args.body) {
i.push(body.drain(..p.find(&body).unwrap().end()).collect());
}
i
};
let config = LabelConfig {
test_css,
real_css,
header,
paragraphs,
footer,
max_width: args.width,
max_height: args.height,
html_path,
};
let labels = make_labels(config);
if args.debug {
for (nro, label) in labels.iter().enumerate() {
fs::write(format!("label{}.png", nro), label)
.expect("Can't create image in current folder");
}
}
let mut json: String = match serde_json::to_string(&labels) {
Ok(i) => i,
_ => {
eprintln!("Error creating json list");
process::exit(1);
}
};
if args.base64 {
json = base64::encode(json);
}
println!("{}", json);
}
fn make_labels(mut config: LabelConfig) -> Vec<Vec<u8>> {
let mut labels: Vec<Vec<u8>> = Vec::new();
let mut skip_count: usize = 0;
loop {
let (custom_html, lines_in_use, skip_count_return) = generate_html(
&config.paragraphs,
&config.test_css,
&config.header,
&config.footer,
&skip_count,
);
skip_count += skip_count_return;
save_html(custom_html);
let png_data = generate_image(&config.html_path);
let png_size = imagesize::blob_size(&png_data).expect("Can't get test image size");
if png_size.width > config.max_width || png_size.height > config.max_height {
skip_count += 1;
if config.paragraphs.len() - skip_count == 0 {
let (custom_html, _, _) = generate_html(
&config.paragraphs,
&config.real_css,
&config.header,
&config.footer,
&skip_count,
);
save_html(custom_html);
labels.push(generate_image(&config.html_path));
return labels;
}
} else {
let (custom_html, _, _) = generate_html(
&config.paragraphs,
&config.real_css,
&config.header,
&config.footer,
&skip_count,
);
save_html(custom_html);
labels.push(generate_image(&config.html_path));
if skip_count == 0 {
break;
} else {
skip_count = 0;
for _line in 0..=lines_in_use {
config.paragraphs.remove(0);
}
}
}
}
labels
}
fn generate_html(
paragraphs: &[String],
css: &str,
header: &str,
footer: &str,
skip_count: &usize,
) -> (String, usize, usize) {
let mut lines_in_use: usize = 0;
let mut i: String = format!(
r#"<html><head><meta charset="UTF-8">{css}</head><body>{header}"#,
css = css,
header = header
);
let skip_line: String = {
if skip_count == &0 {
String::new()
} else {
paragraphs[paragraphs.len() - skip_count - 1].to_string()
}
};
for line in paragraphs {
if line == &skip_line {
break;
}
i = format!("{}{}", i, line);
lines_in_use += 1;
}
let i = format!("{}</div>{}</body></html>", i, footer);
(i, lines_in_use, 0)
}
fn generate_image(html_path: &str) -> Vec<u8> {
let bro = Browser::default().expect("Can't create new headless browser instance");
let tab = bro
.wait_for_initial_tab()
.expect("Can't open new empty tab");
tab.navigate_to(html_path).unwrap();
tab.wait_for_element("body")
.expect("<body> not found in the lable.html file")
.capture_screenshot(ScreenshotFormat::PNG)
.expect("Can't take screenshot of the <body>")
}
fn save_html(html: String) {
fs::write("label.html", html).expect("Can't create label.html file in current folder");
}