From 6df119d4c92efe8d7a7c5760b50e3038a84f103d Mon Sep 17 00:00:00 2001 From: Joel Therrien Date: Wed, 23 Sep 2020 14:07:15 -0700 Subject: [PATCH] Add support for saving generated puzzles as PDFs --- .gitignore | 3 +- Cargo.toml | 3 +- README.md | 2 +- src/bin/generator.rs | 12 +++- src/lib.rs | 3 +- src/pdf.rs | 131 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/pdf.rs diff --git a/.gitignore b/.gitignore index 5d84ee3..d32d859 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /.idea *.iml -Cargo.lock \ No newline at end of file +Cargo.lock +*.pdf diff --git a/Cargo.toml b/Cargo.toml index ce31564..cba546b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ edition = "2018" csv = "1.1.3" argparse = "0.2.2" rand = "0.7" -rand_chacha = "0.2.2" \ No newline at end of file +rand_chacha = "0.2.2" +printpdf = "0.3.2" \ No newline at end of file diff --git a/README.md b/README.md index e93c2ef..9eb9ba5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Two binaries, `solver` and `generator` will be generated in `target/release/`. Try running both of them, first with the `-h` flag to see what other arguments they take. * `solver` reads a CSV file for a puzzle, prints it, solves it, and then prints the solved version. Some example CSV files are in the `puzzle` folder. -* `generator` tries to generate a new puzzle from scratch. You can set a maximum number of hints that it will allow and it will try to generate a puzzle that meets that requirement. You can also optionally write it to a CSV file. +* `generator` tries to generate a new puzzle from scratch. You can set a maximum number of hints that it will allow and it will try to generate a puzzle that meets that requirement. You can also optionally write it to a CSV file or a PDF file (determined by file extension). Regarding code quality, I could probably have commented more and I certainly should have written more unit tests. I also wish that I didn't rely so heavily on `Rc` & `RefCell`, which provide ways to get around (sometimes necessarily) the compiler's strict rules on references and ownership. diff --git a/src/bin/generator.rs b/src/bin/generator.rs index fb36309..5647d7e 100644 --- a/src/bin/generator.rs +++ b/src/bin/generator.rs @@ -71,15 +71,21 @@ fn main() { match filename { Some(filename) => { - save_grid(&grid, &filename).unwrap(); - println!("Grid saved to {}", filename); + // check if we save to a csv or a pdf + if filename.ends_with(".pdf") { + sudoku_solver::pdf::draw_grid(&grid, &filename).unwrap(); + println!("Grid saved as pdf to {}", filename); + } else{ + save_grid_csv(&grid, &filename).unwrap(); + println!("Grid saved as CSV to {}", filename); + } }, None => {} } } -fn save_grid(grid: &Grid, filename: &str) -> Result<(), Box>{ +fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box>{ // Not using the csv crate for writing because it's being difficult and won't accept raw integers let mut file = std::fs::File::create(filename)?; diff --git a/src/lib.rs b/src/lib.rs index f813ae4..c76ea25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod grid; pub mod solver; -pub mod generator; \ No newline at end of file +pub mod generator; +pub mod pdf; \ No newline at end of file diff --git a/src/pdf.rs b/src/pdf.rs new file mode 100644 index 0000000..1d742c7 --- /dev/null +++ b/src/pdf.rs @@ -0,0 +1,131 @@ +use printpdf::*; +use std::fs::File; +use std::io::BufWriter; +use crate::grid::{Grid, CellValue}; + +const BOTTOM_LEFT_X : f64 = 10.0; +const BOTTOM_LEFT_Y : f64 = 279.0 - 200.0 - 10.0; +const GRID_DIMENSION : f64 = 190.0; + +const A4 : (Mm, Mm) = (Mm(215.0), Mm(279.0)); + +pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box>{ + let (doc, page1, layer1) = PdfDocument::new("Sudoku Puzzle", A4.0, A4.1, "Layer 1"); + let layer = doc.get_page(page1).get_layer(layer1); + + let font = doc.add_builtin_font(BuiltinFont::HelveticaBold)?; + let font_size = 45; + + draw_empty_grid(&layer); + + // x represents position on left-right scale + // y represents position on up-down scale + + // Now need to add any values + // One thing to note - higher y values are associated with the top of the page, while for my grid + // higher row values are associated with the bottom of the page. + + let x_offset = 6.1; + let y_offset = -16.5; + + + for r in 0..9 { + let y = Mm(BOTTOM_LEFT_Y + (GRID_DIMENSION / 9.0) * (9.0 - r as f64) + y_offset); + + for c in 0..9 { + let x = Mm(BOTTOM_LEFT_X + (GRID_DIMENSION / 9.0) * (c as f64) + x_offset); + + let cell = grid.get(r, c).unwrap(); + let value = &*cell.value.borrow(); + match value { + CellValue::Fixed(digit) => { + let text = digit.to_string(); + layer.use_text(text, font_size, x, y, &font); + }, + _ => {} + } + } + } + + doc.save(&mut BufWriter::new(File::create(filename)?))?; + + return Ok(()); + +} + +fn draw_empty_grid(layer: &PdfLayerReference){ + + // x represents position on left-right scale + // y represents position on up-down scale + + // Thick lines first + + layer.set_outline_thickness(2.0); + // Horizontal first + { + let starting_x = Mm(BOTTOM_LEFT_X); + let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION); + let y_increment = GRID_DIMENSION/3.0; + for i in 0..4 { + let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment); + draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y)); + } + } + + // Vertical lines next + { + let starting_y = Mm(BOTTOM_LEFT_Y); + let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION); + let x_increment = GRID_DIMENSION/3.0; + for i in 0..4 { + let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment); + draw_line(layer, Point::new(x, starting_y), Point::new(x, ending_y)); + } + } + + // Thin lines next + + layer.set_outline_thickness(0.0); // Special value to make line be 1px on all devices and zoom levels + // Horizontal first + { + let starting_x = Mm(BOTTOM_LEFT_X); + let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION); + let y_increment = GRID_DIMENSION/9.0; + for i in 1..9 { + if i % 3 != 0{ + let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment); + draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y)); + } + + } + } + + // Vertical lines next + { + let starting_y = Mm(BOTTOM_LEFT_Y); + let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION); + let x_increment = GRID_DIMENSION/9.0; + for i in 1..9 { + if i % 3 != 0 { + let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment); + draw_line(layer, Point::new(x, starting_y), Point::new(x, ending_y)); + } + } + } +} + + + +fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point){ + let points = vec![(point1, false), (point2, false)]; + + let line = Line { + points, + is_closed: false, + has_fill: false, + has_stroke: true, + is_clipping_path: false + }; + + layer.add_shape(line); +} \ No newline at end of file