use clap::ArgMatches;
use regex::Regex;
use std::{collections::HashMap, fs};
pub struct Arguments {
pub header: String,
pub body: String,
pub footer: String,
pub width: usize,
pub height: usize,
pub debug: bool,
pub black: bool,
}
struct LabelConfig {
test_css: String,
real_css: String,
header: String,
paragraphs: Vec<String>,
footer: String,
max_height: usize,
black: bool,
}
impl From<ArgMatches> for Arguments {
fn from(a: ArgMatches) -> Self {
let header: String = a
.get_one::<String>("header")
.unwrap_or(&"".to_string())
.to_owned();
let body = a.get_one::<String>("body").unwrap().to_owned();
let footer: String = a
.get_one::<String>("footer")
.unwrap_or(&"".to_string())
.to_owned();
let width = a.get_one::<String>("width").unwrap().parse().unwrap();
let height = a.get_one::<String>("height").unwrap().parse().unwrap();
let debug = a.get_flag("debug");
let black = a.get_flag("black");
Self {
header,
body,
footer,
width,
height,
debug,
black,
}
}
}
pub fn create(args: Arguments) -> Vec<Vec<u8>> {
let test_css = format!(
r#"<style>body {{ width: {width}px; height: fit-content; position: relative; }} #div {{ position: relative; }} .border {{ border-left: {width}px solid grey; height: 2px; }} #header {{ padding-top: 1px; }}</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; }} .black {{ filter: grayscale(100%); }} .black * {{ color: unset !important; background-color: unset !important; }}</style>"#,
width = args.width,
height = args.height
);
let header = if !args.header.is_empty() {
format!(
r#"<div id="div"><div id="header">{}<div class="border"></div></div>"#,
args.header
)
} else {
r#"<div id="div"><div id="header"></div>"#.to_string()
};
let footer = if !args.footer.is_empty() {
format!(
r#"<div id="footer"><div class="border"></div>{}</div>"#,
args.footer
)
} else {
r#"<div id="footer"></div>"#.to_string()
};
let paragraphs: Vec<String> = {
let mut body = args.body.clone();
let mut i: Vec<String> = Vec::new();
let p = Regex::new(r"</p>|</h\d>|<br>").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_height: args.height,
black: args.black,
};
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");
}
}
labels
}
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,
config.black,
);
skip_count += skip_count_return;
let png_data = generate_image(&custom_html);
let png_size = imagesize::blob_size(&png_data).expect("Can't get test image size");
if 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,
config.black,
);
labels.push(generate_image(&custom_html));
return labels;
}
} else {
let (custom_html, _, _) = generate_html(
&config.paragraphs,
&config.real_css,
&config.header,
&config.footer,
&skip_count,
config.black,
);
labels.push(generate_image(&custom_html));
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,
black: bool,
) -> (String, usize, usize) {
let mut lines_in_use: usize = 0;
let black_css = if black {
r#" class="black""#.to_string()
} else {
String::new()
};
let mut i: String = format!(
r#"<html><head><meta charset="UTF-8">{css}</head><body{black_css}>{header}"#,
css = css,
black_css = black_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: &str) -> Vec<u8> {
let mut image_app = wkhtmlapp::ImgApp::new().expect("Failed to init image Application");
let args = HashMap::from([("quiet", "true")]);
let res = image_app
.set_format(wkhtmlapp::ImgFormat::Png)
.unwrap()
.set_args(args)
.unwrap()
.run(wkhtmlapp::WkhtmlInput::Html(html), "label converter")
.unwrap();
std::fs::read(res).unwrap()
}