Skip to content

Commit

Permalink
Replace DPI with universal scale and use separate DPI only for raster…
Browse files Browse the repository at this point in the history
… images
  • Loading branch information
staticintlucas committed Aug 13, 2023
1 parent 613e919 commit c715f0d
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 48 deletions.
20 changes: 10 additions & 10 deletions src/drawing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ impl Drawing {
Self {
bounds,
keys,
scale: options.dpi * 0.75, // 1u = 0.75in => dpu = 0.75*dpi
scale: options.scale,
}
}

pub fn to_svg(&self) -> String {
svg::draw(self)
}

pub fn to_png(&self) -> Result<Vec<u8>> {
png::draw(self)
pub fn to_png(&self, dpi: f64) -> Result<Vec<u8>> {
png::draw(self, dpi)
}

pub fn to_pdf(&self) -> Vec<u8> {
Expand All @@ -66,7 +66,7 @@ impl Drawing {
pub struct DrawingOptions {
profile: Profile,
font: Font,
dpi: f64,
scale: f64,
outline_width: f64,
show_keys: bool,
show_margin: bool,
Expand All @@ -77,7 +77,7 @@ impl Default for DrawingOptions {
Self {
profile: Profile::default(),
font: Font::default(),
dpi: 96.,
scale: 1.,
outline_width: 10.,
show_keys: true,
show_margin: false,
Expand All @@ -101,8 +101,8 @@ impl DrawingOptions {
self
}

pub fn dpi(&mut self, dpi: f64) -> &mut Self {
self.dpi = dpi;
pub fn scale(&mut self, scale: f64) -> &mut Self {
self.scale = scale;
self
}

Expand Down Expand Up @@ -140,7 +140,7 @@ mod tests {
fn test_drawing_options() {
let options = DrawingOptions::default();

assert_approx_eq!(options.dpi, 96.);
assert_approx_eq!(options.scale, 1.);
assert_eq!(options.font.glyphs.len(), 0);

let profile = Profile::default();
Expand All @@ -149,7 +149,7 @@ mod tests {
options
.profile(profile)
.font(font)
.dpi(192.)
.scale(2.)
.show_keys(false)
.show_margin(true);

Expand All @@ -158,6 +158,6 @@ mod tests {
ProfileType::Cylindrical { .. }
);
assert_eq!(options.font.glyphs.len(), 2);
assert_eq!(options.dpi, 192.);
assert_eq!(options.scale, 2.);
}
}
56 changes: 37 additions & 19 deletions src/drawing/pdf.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
use kurbo::{PathEl, Point};
use kurbo::{Affine, PathEl, Point};
use miniz_oxide::deflate::{compress_to_vec_zlib, CompressionLevel};
use pdf_writer::{Content, Filter, Finish, PdfWriter, Rect, Ref, TextStr};

use crate::drawing::Drawing;

use super::{KeyDrawing, Path};

const PDF_DPI: f64 = 72.0; // PDF uses 72 dpi
const COMPRESSION_LEVEL: u8 = CompressionLevel::DefaultLevel as u8;

macro_rules! transform {
(($($x:expr, $y:expr),+), $origin:expr, $scale:expr) => {
// Negate Y since PDF has rising Y axis
($((($origin.x + $x / 1e3) * $scale) as f32, (($origin.y - $y / 1e3) * $scale) as f32),+)
};
($p:expr, $affine:expr) => {{
let p = $affine * $p;
(p.x as f32, p.y as f32)
}};
($p1:expr, $p:expr, $affine:expr) => {{
let (p1, p) = ($affine * $p1, $affine * $p);
(p1.x as f32, p1.y as f32, p.x as f32, p.y as f32)
}};
($p1:expr, $p2:expr, $p:expr, $affine:expr) => {{
let (p1, p2, p) = ($affine * $p1, $affine * $p2, $affine * $p);
(
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
p.x as f32,
p.y as f32,
)
}};
}

struct RefGen(i32);
Expand All @@ -29,7 +45,7 @@ impl RefGen {
}

pub(crate) fn draw(drawing: &Drawing) -> Vec<u8> {
let scale = drawing.scale * (72.0 / 96.0);
let scale = drawing.scale * PDF_DPI * 0.75; // 0.75 in/key
let size = drawing.bounds.size() * scale;

let mut ref_gen = RefGen::new();
Expand All @@ -56,8 +72,10 @@ pub(crate) fn draw(drawing: &Drawing) -> Vec<u8> {

let mut content = Content::new();

// Flip origin since PDF has rising Y axis
let affine = Affine::scale_non_uniform(scale, -scale).then_translate((0., size.height).into());
for key in &drawing.keys {
draw_key(&mut content, key, drawing.bounds.height(), scale);
draw_key(&mut content, key, &affine);
}

let data = compress_to_vec_zlib(&content.finish(), COMPRESSION_LEVEL);
Expand All @@ -76,45 +94,45 @@ pub(crate) fn draw(drawing: &Drawing) -> Vec<u8> {
writer.finish()
}

fn draw_key(content: &mut Content, key: &KeyDrawing, height: f64, scale: f64) {
fn draw_key(content: &mut Content, key: &KeyDrawing, affine: &Affine) {
let affine = *affine * Affine::scale(1e-3).then_translate(key.origin.to_vec2());
for path in &key.paths {
// Flip origin since PDF has rising Y axis
let origin = Point::new(key.origin.x, height - key.origin.y);
draw_path(content, path, origin, scale);
draw_path(content, path, &affine);
}
}

fn draw_path(content: &mut Content, path: &Path, origin: Point, scale: f64) {
fn draw_path(content: &mut Content, path: &Path, affine: &Affine) {
// previous point, needed for quad => cubic Bézier conversion
let mut origin = Point::ORIGIN;
let mut p0 = Point::ORIGIN;

for el in &path.path {
match el {
PathEl::MoveTo(p) => {
origin = p;
p0 = p;
let (x, y) = transform!((p.x, p.y), origin, scale);
let (x, y) = transform!(p, *affine);
content.move_to(x, y);
}
PathEl::LineTo(p) => {
p0 = p;
let (x, y) = transform!((p.x, p.y), origin, scale);
let (x, y) = transform!(p, *affine);
content.line_to(x, y);
}
PathEl::CurveTo(p1, p2, p) => {
p0 = p;
let (x1, y1, x2, y2, x, y) =
transform!((p1.x, p1.y, p2.x, p2.y, p.x, p.y), origin, scale);
let (x1, y1, x2, y2, x, y) = transform!(p1, p2, p, *affine);
content.cubic_to(x1, y1, x2, y2, x, y);
}
PathEl::QuadTo(p1, p) => {
// Convert quad to cubic since PostScript doesn't have quadratic Béziers
let (p1, p2) = (p0 + (2.0 / 3.0) * (p1 - p0), p + (2.0 / 3.0) * (p1 - p));
p0 = p;
let (x1, y1, x2, y2, x, y) =
transform!((p1.x, p1.y, p2.x, p2.y, p.x, p.y), origin, scale);
let (x1, y1, x2, y2, x, y) = transform!(p1, p2, p, *affine);
content.cubic_to(x1, y1, x2, y2, x, y);
}
PathEl::ClosePath => {
p0 = origin;
content.close_path();
}
}
Expand All @@ -129,7 +147,7 @@ fn draw_path(content: &mut Content, path: &Path, origin: Point, scale: f64) {
let (r, g, b) = outline.color.into();
content.set_stroke_rgb(r, g, b);
#[allow(clippy::cast_possible_truncation)]
content.set_line_width((outline.width * scale / 1e3) as f32);
content.set_line_width((outline.width * affine.as_coeffs()[0]) as f32);
}

match (path.fill, path.outline) {
Expand Down
50 changes: 34 additions & 16 deletions src/drawing/png.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt;

use kurbo::{PathEl, Point};
use kurbo::{Affine, PathEl};
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Shader, Stroke, Transform};

use crate::drawing::Drawing;
Expand All @@ -20,15 +20,32 @@ impl fmt::Display for PngEncodingError {
}

macro_rules! transform {
(($($x:expr, $y:expr),+), $origin:expr, $scale:expr) => {
($((($origin.x + $x / 1e3) * $scale) as f32, (($origin.y + $y / 1e3) * $scale) as f32),+)
};
($p:expr, $affine:expr) => {{
let p = $affine * $p;
(p.x as f32, p.y as f32)
}};
($p1:expr, $p:expr, $affine:expr) => {{
let (p1, p) = ($affine * $p1, $affine * $p);
(p1.x as f32, p1.y as f32, p.x as f32, p.y as f32)
}};
($p1:expr, $p2:expr, $p:expr, $affine:expr) => {{
let (p1, p2, p) = ($affine * $p1, $affine * $p2, $affine * $p);
(
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
p.x as f32,
p.y as f32,
)
}};
}

impl std::error::Error for PngEncodingError {}

pub(crate) fn draw(drawing: &Drawing) -> Result<Vec<u8>> {
let size = drawing.bounds.size() * drawing.scale;
pub(crate) fn draw(drawing: &Drawing, dpi: f64) -> Result<Vec<u8>> {
let scale = drawing.scale * dpi * 0.75; // 0.75 in/key
let size = drawing.bounds.size() * scale;

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let (width, height) = (size.width.ceil() as u32, size.height.ceil() as u32);
Expand All @@ -39,40 +56,41 @@ pub(crate) fn draw(drawing: &Drawing) -> Result<Vec<u8>> {

pixmap.fill(tiny_skia::Color::TRANSPARENT);

let affine = Affine::scale(scale);
for key in &drawing.keys {
draw_key(&mut pixmap, key, drawing.scale);
draw_key(&mut pixmap, key, &affine);
}

Ok(pixmap.encode_png().map_err(|e| PngEncodingError {
message: e.to_string(),
})?)
}

fn draw_key(pixmap: &mut Pixmap, key: &KeyDrawing, scale: f64) {
fn draw_key(pixmap: &mut Pixmap, key: &KeyDrawing, affine: &Affine) {
let affine = *affine * Affine::scale(1e-3).then_translate(key.origin.to_vec2());
for path in &key.paths {
draw_path(pixmap, path, key.origin, scale);
draw_path(pixmap, path, &affine);
}
}

fn draw_path(pixmap: &mut Pixmap, path: &Path, origin: Point, scale: f64) {
fn draw_path(pixmap: &mut Pixmap, path: &Path, affine: &Affine) {
let mut path_builder = PathBuilder::new();
for el in &path.path {
match el {
PathEl::MoveTo(p) => {
let (x, y) = transform!((p.x, p.y), origin, scale);
let (x, y) = transform!(p, *affine);
path_builder.move_to(x, y);
}
PathEl::LineTo(p) => {
let (x, y) = transform!((p.x, p.y), origin, scale);
let (x, y) = transform!(p, *affine);
path_builder.line_to(x, y);
}
PathEl::CurveTo(p1, p2, p) => {
let (x1, y1, x2, y2, x, y) =
transform!((p1.x, p1.y, p2.x, p2.y, p.x, p.y), origin, scale);
let (x1, y1, x2, y2, x, y) = transform!(p1, p2, p, *affine);
path_builder.cubic_to(x1, y1, x2, y2, x, y);
}
PathEl::QuadTo(p1, p) => {
let (x1, y1, x, y) = transform!((p1.x, p1.y, p.x, p.y), origin, scale);
let (x1, y1, x, y) = transform!(p1, p, *affine);
path_builder.quad_to(x1, y1, x, y);
}
PathEl::ClosePath => path_builder.close(),
Expand Down Expand Up @@ -103,7 +121,7 @@ fn draw_path(pixmap: &mut Pixmap, path: &Path, origin: Point, scale: f64) {
};
#[allow(clippy::cast_possible_truncation)]
let stroke = Stroke {
width: (outline.width * scale / 1e3) as f32,
width: (outline.width * affine.as_coeffs()[0]) as f32,
..Default::default()
};
pixmap.stroke_path(&skia_path, &paint, &stroke, Transform::default(), None);
Expand Down
6 changes: 3 additions & 3 deletions src/drawing/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ macro_rules! fmt_num {

pub(crate) fn draw(drawing: &Drawing) -> String {
let size = drawing.bounds.size() * drawing.scale;
let view_box = drawing.bounds.scale_from_origin(1e3);
let view_box = drawing.bounds.scale_from_origin(1e3); // Use 1000 user units per key

let document = Document::new()
.set("width", fmt_num!("{}", size.width))
.set("height", fmt_num!("{}", size.height))
.set("width", fmt_num!("{}mm", size.width * 19.05))
.set("height", fmt_num!("{}mm", size.height * 19.05))
.set(
"viewBox",
fmt_num!(
Expand Down

0 comments on commit c715f0d

Please sign in to comment.