Skip to content

Commit

Permalink
wip: Add different kind of Eye
Browse files Browse the repository at this point in the history
  • Loading branch information
erwanvivien committed Oct 15, 2023
1 parent aede53b commit 3b168b4
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 3 deletions.
3 changes: 2 additions & 1 deletion examples/svg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
fn main() {
use fast_qr::{
convert::{svg::SvgBuilder, Builder, ModuleShape},
convert::{svg::SvgBuilder, Builder, EyeFrameShape, ModuleShape},
QRBuilder, Version, ECL,
};

Expand All @@ -12,5 +12,6 @@ fn main() {

let _svg = SvgBuilder::default()
.module_shape(ModuleShape::RoundedSquare)
.eye_frame_shape(EyeFrameShape::Square)
.to_file(&qrcode, "svg.svg");
}
189 changes: 189 additions & 0 deletions src/convert/eye_shape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/// Corresponds to the position of the Eye
/// - TopLeft
/// - TopRight
/// - BottomRight
#[derive(Debug, Clone, Copy)]
pub enum EyePosition {
/// Top left eye
TopLeft,
/// Top right eye
TopRight,
/// Bottom left eye
BottomLeft,
}

impl EyePosition {
/// Iterates over all possible eye positions
///
/// TopLeft, TopRight, BottomLeft
pub const ALL: [Self; 3] = [Self::TopLeft, Self::TopRight, Self::BottomLeft];
}

/// Converts an eye position to a custom svg
///
/// # Example
///
/// For the fully squared shape, the svg is `M{x},{y}h7v7h-7`
///
/// The eye function for the eye frame should be max of 7x7. \
/// The eye function for the eye ball should be max of 3x3.
///
/// ```rust
/// fn square(y: usize, x: usize, _: EyePosition) -> String {
/// format!("h7v7h-7")
/// }
/// ```
pub type EyeFunction = fn(usize, usize, EyePosition) -> String;

// TODO: Find a way to use the same enum for wasm and not wasm
// Current bug being that wasm_bindgen & #[cfg(not(target_arch = "wasm32"))] are not compatible(?)
/// Different possible Shapes to represent modules in a [`crate::QRCode`]
#[repr(C)]
#[wasm_bindgen]
#[cfg(feature = "wasm-bindgen")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub enum EyeFrameShape {
/// Square shape
Square,
/// Rounded square shape
Rounded,
/// Circle shape
Circle,
/// Rounded square shape with the outer corner rounded
RoundedSquaredOuterCorner,
/// Leaf shape
Leaf,
/// Rounded square shape with all but the inner corner rounded
RoundedSquaredInnerCorner,
/// Square shape with a dot in the middle
DottedSquare,
/// Eye lash shape
EyeLash,
}

/// Different possible Shapes to represent modules in a [`crate::QRCode`]
#[cfg(not(feature = "wasm-bindgen"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub enum EyeFrameShape {
/// Empty shape, most often used with [`ModuleShape::Custom`]
Empty,
/// Square shape
Square,
/// Rounded square shape
Rounded,
/// Circle shape
Circle,
/// Rounded square shape with the outer corner rounded
RoundedSquaredOuterCorner,
/// Leaf shape
Leaf,
/// Rounded square shape with all but the inner corner rounded
RoundedSquaredInnerCorner,
/// Square shape with a dot in the middle
DottedSquare,
/// Eye lash shape
EyeLash,
/// Custom Shape with a function / closure
/// # Example
/// ```rust
/// use fast_qr::convert::EyeFrameShape;
/// let command_function = |eye_position| {
/// match eye_position {
/// EyePosition::TopLeft => String::from("..."),
/// _ => String::from("..."),
/// }
/// };
/// let command = EyeFrameShape::Command(command_function);
/// ```
Command(EyeFunction),
}

impl From<EyeFrameShape> for usize {
fn from(shape: EyeFrameShape) -> Self {
match shape {
EyeFrameShape::Empty => 0,
EyeFrameShape::Square => 1,
EyeFrameShape::Rounded => 2,
EyeFrameShape::Circle => 3,
EyeFrameShape::RoundedSquaredOuterCorner => 4,
EyeFrameShape::Leaf => 5,
EyeFrameShape::RoundedSquaredInnerCorner => 6,
EyeFrameShape::DottedSquare => 7,
EyeFrameShape::EyeLash => 8,
#[cfg(not(feature = "wasm-bindgen"))]
EyeFrameShape::Command(_) => 9,
}
}
}

impl From<String> for EyeFrameShape {
#[allow(clippy::match_same_arms)]
fn from(shape: String) -> Self {
match shape.as_ref() {
"empty" => Self::Empty,
"square" => Self::Square,
"rounded" => Self::Rounded,
"circle" => Self::Circle,
"rounded_squared_side_1" => Self::RoundedSquaredOuterCorner,
"rounded_squared_side_2" => Self::Leaf,
"rounded_squared_side_3" => Self::RoundedSquaredInnerCorner,
"dotted_square" => Self::DottedSquare,
"eye_lash" => Self::EyeLash,

_ => Self::Square,
}
}
}

impl From<EyeFrameShape> for &str {
fn from(shape: EyeFrameShape) -> Self {
match shape {
EyeFrameShape::Empty => "empty",
EyeFrameShape::Square => "square",
EyeFrameShape::Rounded => "rounded",
EyeFrameShape::Circle => "circle",
EyeFrameShape::RoundedSquaredOuterCorner => "rounded_squared_side_1",
EyeFrameShape::Leaf => "rounded_squared_side_2",
EyeFrameShape::RoundedSquaredInnerCorner => "rounded_squared_side_3",
EyeFrameShape::DottedSquare => "dotted_square",
EyeFrameShape::EyeLash => "eye_lash",

#[cfg(not(feature = "wasm-bindgen"))]
EyeFrameShape::Command(_) => "command",
}
}
}

impl EyeFrameShape {
pub(crate) const fn empty(_: usize, _: usize, _: EyePosition) -> String {
String::new()
}

pub(crate) fn square(y: usize, x: usize, _: EyePosition) -> String {
let offset_x = x + 6;
let offset_y = y + 6;

let x_1 = x + 1;
let y_1 = y + 1;

let x_2 = x + 2;
let y_2 = y + 2;

format!( "M{x_1},{y}h6v1h-6zM{x},{y}v7h1v-7zM{offset_x},{y_1}v6h1v-6zM{x_1},{offset_y}h5v1h-5zM{x_2},{y_2}h3v3h-3z")
}

const FUNCTIONS: [EyeFunction; 2] = [EyeFrameShape::empty, EyeFrameShape::square];
}

impl core::ops::Deref for EyeFrameShape {
type Target = EyeFunction;

fn deref(&self) -> &Self::Target {
let index: usize = (*self).into();
match self {
#[cfg(not(target_arch = "wasm32"))]
Self::Command(func) => func,
_ => &Self::FUNCTIONS[index],
}
}
}
14 changes: 14 additions & 0 deletions src/convert/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ impl Builder for ImageBuilder {
self.svg_builder.module_shape_color(shape, color);
self
}

fn eye_frame_shape(&mut self, shape: super::EyeFrameShape) -> &mut Self {
self.svg_builder.eye_frame_shape(shape);
self
}

fn eye_frame_shape_color<C: Into<Color>>(
&mut self,
shape: super::EyeFrameShape,
color: C,
) -> &mut Self {
self.svg_builder.eye_frame_shape_color(shape, color);
self
}
}

impl ImageBuilder {
Expand Down
13 changes: 13 additions & 0 deletions src/convert/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub use color::{rgba2hex, Color};
mod module_shape;
pub use module_shape::{ModuleFunction, ModuleShape};

mod eye_shape;
pub use eye_shape::{EyeFrameShape, EyeFunction, EyePosition};

#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
use wasm_bindgen::prelude::*;

Expand Down Expand Up @@ -86,6 +89,16 @@ pub trait Builder {
/// Add a shape to the shapes list with a specific color
fn module_shape_color<C: Into<Color>>(&mut self, shape: ModuleShape, color: C) -> &mut Self;

// Manages the eye part
/// Adds a shape to the eye shapes list
fn eye_frame_shape(&mut self, shape: EyeFrameShape) -> &mut Self;
/// Add a shape to the eye shapes list with a specific color
fn eye_frame_shape_color<C: Into<Color>>(
&mut self,
shape: EyeFrameShape,
color: C,
) -> &mut Self;

// Manages the image part

/// Provides the image path or an base64 encoded image
Expand Down
71 changes: 69 additions & 2 deletions src/convert/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
//! # }
//! ```
use crate::{QRCode, Version};
use crate::{convert::EyePosition, ModuleType, QRCode, Version};

use super::{module_shape::ModuleFunction, Builder, Color, ImageBackgroundShape, ModuleShape};
use super::{
module_shape::ModuleFunction, Builder, Color, EyeFrameShape, ImageBackgroundShape, ModuleShape,
};

/// Builder for svg, can set shape, margin, background_color, dot_color
pub struct SvgBuilder {
Expand All @@ -41,6 +43,12 @@ pub struct SvgBuilder {
/// The color for each module, default is #000000
dot_color: Color,

// Eye Frame
/// Eye Frame Shape
eye_frame_shape: EyeFrameShape,
/// Eye Frame Color
eye_frame_color: Color,

// Image Embedding
/// Image to embed in the svg, can be a path or a base64 string
image: Option<String>,
Expand Down Expand Up @@ -74,6 +82,9 @@ impl Default for SvgBuilder {
commands: Vec::new(),
command_colors: Vec::new(),

eye_frame_shape: EyeFrameShape::Empty,
eye_frame_color: [0, 0, 0, 255].into(),

// Image Embedding
image: None,
image_background_color: [255; 4].into(),
Expand Down Expand Up @@ -139,9 +150,40 @@ impl Builder for SvgBuilder {
self.image_position = Some((x, y));
self
}

fn eye_frame_shape(&mut self, shape: EyeFrameShape) -> &mut Self {
self.eye_frame_shape = shape;
self
}

fn eye_frame_shape_color<C: Into<Color>>(
&mut self,
shape: EyeFrameShape,
color: C,
) -> &mut Self {
self.eye_frame_shape = shape;
self.eye_frame_color = color.into();
self
}
}

impl SvgBuilder {
/// Return the coordinates of the eye according to the eye position
///
/// Return (x, y)
fn eye_placement(&self, qr: &QRCode, eye_position: EyePosition) -> (usize, usize) {
let margin = self.margin;
let offset = qr.size + margin - 7;

dbg!((margin, offset));

match eye_position {
EyePosition::TopLeft => (margin, margin),
EyePosition::TopRight => (offset, margin),
EyePosition::BottomLeft => (margin, offset),
}
}

fn image_placement(
image_background_shape: ImageBackgroundShape,
margin: usize,
Expand Down Expand Up @@ -275,6 +317,12 @@ impl SvgBuilder {
continue;
}

if self.eye_frame_shape != EyeFrameShape::Empty
&& cell.module_type() == ModuleType::FinderPattern
{
continue;
}

for (i, command) in commands.iter().enumerate() {
paths[i].push_str(&command(x + self.margin, y + self.margin, cell));
}
Expand All @@ -295,6 +343,25 @@ impl SvgBuilder {
paths[i].push_str(&format!(r#"" fill="{}"/>"#, command_color.to_str()));
}

let mut finder_pattern_path = String::with_capacity(100);
if self.eye_frame_shape != EyeFrameShape::Empty {
finder_pattern_path.push_str(r#"<path d=""#);

for eye_position in EyePosition::ALL {
let (x, y) = self.eye_placement(qr, eye_position);

let eye_frame_shape_function = self.eye_frame_shape;
let eye_shape_str = eye_frame_shape_function(y, x, eye_position);

finder_pattern_path.push_str(&eye_shape_str);
}

finder_pattern_path
.push_str(&format!(r#"" fill="{}"/>"#, self.eye_frame_color.to_str()));

paths.push(finder_pattern_path);
}

paths.join("")
}

Expand Down

0 comments on commit 3b168b4

Please sign in to comment.