Add support for saving generated puzzles as PDFs
This commit is contained in:
parent
a777b6639b
commit
6df119d4c9
6 changed files with 147 additions and 7 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
/target
|
/target
|
||||||
/.idea
|
/.idea
|
||||||
*.iml
|
*.iml
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
*.pdf
|
||||||
|
|
|
@ -10,4 +10,5 @@ edition = "2018"
|
||||||
csv = "1.1.3"
|
csv = "1.1.3"
|
||||||
argparse = "0.2.2"
|
argparse = "0.2.2"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
rand_chacha = "0.2.2"
|
rand_chacha = "0.2.2"
|
||||||
|
printpdf = "0.3.2"
|
|
@ -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.
|
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.
|
* `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.
|
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.
|
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.
|
||||||
|
|
|
@ -71,15 +71,21 @@ fn main() {
|
||||||
|
|
||||||
match filename {
|
match filename {
|
||||||
Some(filename) => {
|
Some(filename) => {
|
||||||
save_grid(&grid, &filename).unwrap();
|
// check if we save to a csv or a pdf
|
||||||
println!("Grid saved to {}", filename);
|
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 => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
||||||
// Not using the csv crate for writing because it's being difficult and won't accept raw integers
|
// 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)?;
|
let mut file = std::fs::File::create(filename)?;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
pub mod solver;
|
pub mod solver;
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
|
pub mod pdf;
|
131
src/pdf.rs
Normal file
131
src/pdf.rs
Normal file
|
@ -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<dyn std::error::Error>>{
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in a new issue