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
|
||||
/.idea
|
||||
*.iml
|
||||
Cargo.lock
|
||||
Cargo.lock
|
||||
*.pdf
|
||||
|
|
|
@ -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"
|
|
@ -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.
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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
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