Skip to content

Commit 4a11bcc

Browse files
committed
feat: initial yew svg component support
1 parent e45a748 commit 4a11bcc

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ maintenance = { status = "passively-maintained" }
2020

2121
[dependencies]
2222
image = { version = "0.25", default-features = false, optional = true }
23+
yew = { version = "0.21", optional = true }
2324

2425
[dev-dependencies]
2526
image = "0.25"
@@ -31,6 +32,7 @@ std = []
3132
bench = []
3233
svg = []
3334
pic = []
35+
yew = ["dep:yew"]
3436

3537
[[bin]]
3638
name = "qrencode"

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub mod ec;
4848
pub mod optimize;
4949
pub mod render;
5050
pub mod types;
51+
pub mod yew;
5152

5253
pub use crate::types::{Color, EcLevel, QrResult, Version};
5354

src/yew.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#![cfg(feature = "yew")]
2+
3+
use yew::{function_component, html, AttrValue, Callback, Html, MouseEvent, Properties};
4+
5+
use alloc::format;
6+
use alloc::string::String;
7+
use core::fmt::Write;
8+
use core::ops::Not as _;
9+
10+
use crate::render::{Canvas as RenderCanvas, Pixel};
11+
use crate::types::Color as ModuleColor;
12+
use crate::{EcLevel, Version};
13+
14+
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15+
pub struct Color<'a>(pub &'a str);
16+
17+
pub struct SvgAttrs {
18+
width: u32,
19+
height: u32,
20+
fg: String,
21+
bg: String,
22+
d: String,
23+
}
24+
25+
impl<'a> Pixel for Color<'a> {
26+
type Canvas = Canvas<'a>;
27+
type Image = SvgAttrs;
28+
29+
fn default_color(color: ModuleColor) -> Self {
30+
Color(color.select("#000", "#fff"))
31+
}
32+
}
33+
34+
#[doc(hidden)]
35+
pub struct Canvas<'a> {
36+
d: String,
37+
w: u32,
38+
h: u32,
39+
fg: &'a str,
40+
bg: &'a str,
41+
}
42+
43+
impl<'a> RenderCanvas for Canvas<'a> {
44+
type Pixel = Color<'a>;
45+
type Image = SvgAttrs;
46+
47+
fn new(width: u32, height: u32, dark_pixel: Color<'a>, light_pixel: Color<'a>) -> Self {
48+
Self { d: String::new(), w: width, h: height, fg: dark_pixel.0, bg: light_pixel.0 }
49+
}
50+
51+
fn draw_dark_pixel(&mut self, x: u32, y: u32) {
52+
self.draw_dark_rect(x, y, 1, 1);
53+
}
54+
55+
fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
56+
write!(self.d, "M{left} {top}h{width}v{height}h-{width}z").unwrap();
57+
}
58+
59+
fn into_image(self) -> SvgAttrs {
60+
SvgAttrs { width: self.w, height: self.h, fg: self.fg.to_owned(), bg: self.bg.to_owned(), d: self.d }
61+
}
62+
}
63+
64+
#[derive(Clone, PartialEq, Properties)]
65+
pub struct Props {
66+
pub version: Version,
67+
pub ec_level: EcLevel,
68+
69+
pub url: AttrValue,
70+
pub size: AttrValue,
71+
#[prop_or_default]
72+
pub on_click: Callback<MouseEvent>,
73+
74+
#[prop_or("#000000".into())]
75+
pub dark_color: AttrValue,
76+
#[prop_or("#ffffff".into())]
77+
pub light_color: AttrValue,
78+
79+
/// set this to `false` if you disabled event bubbling as an optimization
80+
/// see https://yew.rs/docs/concepts/html/events#event-bubbling
81+
#[prop_or(true)]
82+
pub event_bubbling: bool,
83+
}
84+
85+
#[function_component]
86+
pub fn QrCode(props: &Props) -> Html {
87+
let code = crate::QrCode::with_version(&*props.url, props.version, props.ec_level).unwrap();
88+
let SvgAttrs { width, height, fg, bg, d } =
89+
code.render().dark_color(Color(&props.dark_color)).light_color(Color(&props.light_color)).build();
90+
91+
html! {
92+
<svg
93+
style={format!("height: {}; aspect-ratio: 1 / 1;", props.size)}
94+
onclick={props.on_click.clone()}
95+
xmlns="http://www.w3.org/2000/svg"
96+
version="1.1"
97+
viewBox={format!("0 0 {width} {height}")}
98+
shape-rendering="crispEdges"
99+
>
100+
<path
101+
d={format!("M0 0h{width}v{height}H0z")}
102+
fill={bg}
103+
onclick={props.event_bubbling.not().then(|| props.on_click.clone())}
104+
/>
105+
<path fill={fg} {d} onclick={props.event_bubbling.not().then(|| props.on_click.clone())} />
106+
</svg>
107+
}
108+
}

0 commit comments

Comments
 (0)