Export current document as SVG when pressing Ctrl+Shift+S (#160)

* Export current document when pressing Ctrl+Shift+S

* Use a blob for download

* Add Ctrl + E shortcut, match on lower case

* Don't mount element in DOM

* Polish some keybindings

* Add initialization for MappingEntries

* Implement svg coloring

* Add newline after svg tag

* Add spaces to svg style format

* Fix more svg formatting

* Add space before />

* Remove duplicate whitespace
This commit is contained in:
TrueDoctor
2021-05-28 20:43:51 +02:00
committed by GitHub
parent 9f63c6e8ea
commit eb48fbd180
16 changed files with 137 additions and 43 deletions

View File

@@ -55,7 +55,7 @@ impl Color {
pub fn components(&self) -> (f32, f32, f32, f32) {
(self.red, self.green, self.blue, self.alpha)
}
pub fn as_hex(&self) -> String {
pub fn rgba_hex(&self) -> String {
format!(
"{:02X?}{:02X?}{:02X?}{:02X?}",
(self.r() * 255.) as u8,
@@ -64,4 +64,7 @@ impl Color {
(self.a() * 255.) as u8,
)
}
pub fn rgb_hex(&self) -> String {
format!("{:02X?}{:02X?}{:02X?}", (self.r() * 255.) as u8, (self.g() * 255.) as u8, (self.b() * 255.) as u8,)
}
}

View File

@@ -22,7 +22,7 @@ impl LayerData for Circle {
fn render(&mut self, svg: &mut String) {
let _ = write!(
svg,
r#"<circle cx="{}" cy="{}" r="{}" {} />"#,
r#"<circle cx="{}" cy="{}" r="{}"{} />"#,
self.shape.center.x,
self.shape.center.y,
self.shape.radius,

View File

@@ -25,7 +25,7 @@ impl LayerData for Ellipse {
let _ = write!(
svg,
r#"<ellipse cx="0" cy="0" rx="{}" ry="{}" transform="translate({} {}) rotate({})" {} />"#,
r#"<ellipse cx="0" cy="0" rx="{}" ry="{}" transform="translate({} {}) rotate({})"{} />"#,
rx,
ry,
cx,

View File

@@ -23,6 +23,6 @@ impl LayerData for Line {
let kurbo::Point { x: x1, y: y1 } = self.shape.p0;
let kurbo::Point { x: x2, y: y2 } = self.shape.p1;
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}" {} />"#, x1, y1, x2, y2, self.style.render(),);
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}"{} />"#, x1, y1, x2, y2, self.style.render(),);
}
}

View File

@@ -24,10 +24,13 @@ impl LayerData for PolyLine {
return;
}
let _ = write!(svg, r#"<polyline points=""#);
for p in &self.points {
let _ = write!(svg, " {:.3} {:.3}", p.x, p.y);
let mut points = self.points.iter();
let first = points.next().unwrap();
let _ = write!(svg, "{:.3} {:.3}", first.x, first.y);
for point in points {
let _ = write!(svg, " {:.3} {:.3}", point.x, point.y);
}
let _ = write!(svg, r#"" {}/>"#, self.style.render());
let _ = write!(svg, r#""{} />"#, self.style.render());
}
}
@@ -41,5 +44,5 @@ fn polyline_should_render() {
let mut svg = String::new();
polyline.render(&mut svg);
assert_eq!(r#"<polyline points=" 3.000 4.124 1.000 5.540" style="stroke: #00FF00FF;stroke-width:0.4;"/>"#, svg);
assert_eq!(r##"<polyline points="3.000 4.124 1.000 5.540" stroke="#00FF00" stroke-width="0.4" />"##, svg);
}

View File

@@ -22,7 +22,7 @@ impl LayerData for Rect {
fn render(&mut self, svg: &mut String) {
let _ = write!(
svg,
r#"<rect x="{}" y="{}" width="{}" height="{}" {} />"#,
r#"<rect x="{}" y="{}" width="{}" height="{}"{} />"#,
self.shape.min_x(),
self.shape.min_y(),
self.shape.width(),

View File

@@ -26,7 +26,7 @@ impl LayerData for Shape {
fn render(&mut self, svg: &mut String) {
let _ = write!(
svg,
r#"<polygon points="{}" transform="translate({} {}) scale({} {})" {} />"#,
r#"<polygon points="{}" transform="translate({} {}) scale({} {})"{} />"#,
self.shape,
self.bounding_rect.origin().x,
self.bounding_rect.origin().y,

View File

@@ -1,5 +1,14 @@
use crate::color::Color;
use serde::{Deserialize, Serialize};
const OPACITY_PERCISION: usize = 3;
fn format_opacity(name: &str, opacity: f32) -> String {
if (opacity - 1.).abs() > 10f32.powi(-(OPACITY_PERCISION as i32)) {
format!(r#" {}-opacity="{:.percision$}""#, name, opacity, percision = OPACITY_PERCISION)
} else {
String::new()
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
@@ -15,8 +24,8 @@ impl Fill {
}
pub fn render(&self) -> String {
match self.color {
Some(c) => format!("fill: #{};", c.as_hex()),
None => "fill: none;".to_string(),
Some(c) => format!(r##" fill="#{}"{}"##, c.rgb_hex(), format_opacity("fill", c.a())),
None => r#" fill="none""#.to_string(),
}
}
}
@@ -33,7 +42,7 @@ impl Stroke {
Self { color, width }
}
pub fn render(&self) -> String {
format!("stroke: #{};stroke-width:{};", self.color.as_hex(), self.width)
format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width)
}
}
@@ -49,7 +58,7 @@ impl PathStyle {
}
pub fn render(&self) -> String {
format!(
"style=\"{}{}\"",
"{}{}",
match self.fill {
Some(fill) => fill.render(),
None => String::new(),

View File

@@ -39,6 +39,9 @@ impl ShapePoints {
}
}
// TODO: The display impl and iter impl share large amounts of code and should be refactored. (Display should use the Iterator)
// TODO: Once that is done, the trailing space from the display impl should be removed
// Also consider implementing index
impl std::fmt::Display for ShapePoints {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn rotate(v: &Vec2, theta: f64) -> Vec2 {