Add support for saving generated puzzles as PDFs

This commit is contained in:
Joel Therrien 2020-09-23 14:07:15 -07:00
parent a777b6639b
commit 6df119d4c9
6 changed files with 147 additions and 7 deletions

3
.gitignore vendored
View file

@ -1,4 +1,5 @@
/target
/.idea
*.iml
Cargo.lock
Cargo.lock
*.pdf

View file

@ -10,4 +10,5 @@ edition = "2018"
csv = "1.1.3"
argparse = "0.2.2"
rand = "0.7"
rand_chacha = "0.2.2"
rand_chacha = "0.2.2"
printpdf = "0.3.2"

View file

@ -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.

View file

@ -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<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
let mut file = std::fs::File::create(filename)?;

View file

@ -1,3 +1,4 @@
pub mod grid;
pub mod solver;
pub mod generator;
pub mod generator;
pub mod pdf;

131
src/pdf.rs Normal file
View 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);
}