aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kotur <qtr@google.com>2021-03-16 20:53:14 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-03-16 20:53:14 +0000
commite4e2781062ba96accf836d05a907f87566a6f583 (patch)
treeba83cdbdb209dec2b72e56e01982599900f5bb89
parentdbc5f3e479629cfd71c8d8e8f0043a8a6752b809 (diff)
parent68f08f00ec023303e7ada7c19b6695b3c1f3f052 (diff)
downloadplotters-backend-e4e2781062ba96accf836d05a907f87566a6f583.tar.gz
Initial import of plotters-backend-0.2.1. am: 251cc39ae9 am: e01bccf43b am: d07d046b12 am: 68f08f00ec
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/plotters-backend/+/1621409 Change-Id: I2308cb18fcf3fa45ee1a727b44569a28a28ae969
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--.gitignore11
-rw-r--r--Cargo.toml24
-rw-r--r--Cargo.toml.orig14
-rw-r--r--README.md8
-rw-r--r--src/lib.rs326
-rw-r--r--src/rasterizer/circle.rs342
-rw-r--r--src/rasterizer/line.rs123
-rw-r--r--src/rasterizer/mod.rs32
-rw-r--r--src/rasterizer/path.rs115
-rw-r--r--src/rasterizer/polygon.rs242
-rw-r--r--src/rasterizer/rect.rs57
-rw-r--r--src/style.rs33
-rw-r--r--src/text.rs244
14 files changed, 1576 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..ac3edff
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "f18b6658db651d8b4be26f12edffb80186c4da83"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0f28dff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+/target
+**/*.rs.bk
+Cargo.lock
+.*.sw*
+backup/*
+**/Cargo.lock
+**/target
+examples/wasm-demo/www/pkg
+examples/.ipynb_checkpoints/
+tarpaulin-report.html
+.vscode/* \ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0cdc7e3
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,24 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "plotters-backend"
+version = "0.2.1"
+authors = ["Hao Hou <haohou302@gmail.com>"]
+description = "Plotters Backend API"
+homepage = "https://plotters-rs.github.io"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/plotters-rs/plotters-backend"
+
+[dependencies]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..58c12be
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,14 @@
+[package]
+name = "plotters-backend"
+version = "0.2.1"
+authors = ["Hao Hou <haohou302@gmail.com>"]
+edition = "2018"
+license = "MIT"
+description = "Plotters Backend API"
+homepage = "https://plotters-rs.github.io"
+repository = "https://github.com/plotters-rs/plotters-backend"
+readme = "README.md"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dc7720b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# plotters-backend - The base crate for implementing a backend for Plotters
+
+This is a part of plotters project. For more details, please check the following links:
+
+- For high-level intro of Plotters, see: [Plotters on crates.io](https://crates.io/crates/plotters)
+- Check the main repo at [Plotters repo](https://github.com/38/plotters.git)
+- For detailed documentation about this crate, check [plotters-backend on docs.rs](https://docs.rs/plotters-backend/)
+- You can also visit Plotters [Homepage](https://plotters-rs.github.io)
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..f4a92b0
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,326 @@
+/*!
+ The Plotters backend API crate. This is a part of Plotters, the Rust drawing and plotting library, for more details regarding the entire
+ Plotters project, please check the [main crate](https://crates.io/crates/plotters).
+
+ This is the crate that used as the connector between Plotters and different backend crates. Since Plotters 0.3, all the backends has been
+ hosted as seperate crates for the usability and maintainability reasons.
+
+ At the same time, Plotters is now supporting third-party backends and all the backends are now supports "plug-and-play":
+ To use a external backend, just depends on both the Plotters main crate and the third-party backend crate.
+
+ # Notes for implementing Backend for Plotters
+
+ To create a new Plotters backend, this crate should be imported to the crate and the trait [DrawingBackend](trait.DrawingBackend.html) should
+ be implemented. It's highly recommended that the third-party backend uses `plotters-backend` by version specification `^x.y.*`.
+ For more details, see the [compatibility note](#compatibility-note).
+
+ If the backend only implements [DrawingBackend::draw_pixel](trait.DrawingBackend.html#tymethod.draw_pixel), the default CPU rasterizer will be
+ used to give your backend ability of drawing different shapes. For those backend that supports advanced drawing instructions, such as, GPU
+ acelerated shape drawing, all the provided trait method can be overriden from the specific backend code.
+
+ If your backend have text rendering ability, you may want to override the [DrawingBackend::estimate_text_size](trait.DrawingBackend.html#tymethod.estimate_text_size)
+ to avoid wrong spacing, since the Plotters default text handling code may behaves differently from the backend in terms of text rendering.
+
+ ## Animated or Realtime Rendering
+ Backend might render the image realtimely/animated, for example, a GTK backend for realtime display or a GIF image rendering. To support these
+ features, you need to play with `ensure_prepared` and `present` method. The following figure illustrates how Plotters operates a drawing backend.
+
+ - `ensure_prepared` - Called before each time when plotters want to draw. This function should initialize the backend for current frame, if the backend is already prepared
+ for a frame, this function should simply do nothing.
+ - `present` - Called when plotters want to finish current frame drawing
+
+
+ ```text
+ .ensure_prepared() &&
+ +-------------+ +-------------+ .draw_pixels() +--------------+ drop
+ |Start drwaing|--->|Ready to draw| ------------------------+---->|Finish 1 frame| --------->
+ +-------------+ +-------------+ | +--------------+
+ ^ ^ | |
+ | +------------------------------- + |
+ | continue drawing |
+ +----------------------------------------------------------------+
+ start render the next frame
+ .present()
+ ```
+ - For both animated and static drawing, `DrawingBackend::present` indicates current frame should be flushed.
+ - For both animated and static drawing, `DrawingBackend::ensure_prepared` is called every time when plotters need to draw.
+ - For static drawing, the `DrawingBackend::present` is only called once manually, or from the Drop impl for the backend.
+ - For dynamic drawing, frames are defined by invocation of `DrawingBackend::present`, everything prior the invocation should belongs to previous frame
+
+ # Compatibility Note
+ Since Plotters v0.3, plotters use the "plug-and-play" schema to import backends, this requires both Plotters and the backend crates depdens on a
+ same version of `plotters-backend` crate. This crate (`plotters-backend`) will enforce that any revision (means the last number in a version number)
+ won't contains breaking change - both on the Plotters side and backend side.
+
+ Plotters main crate is always importing the backend crate with version specification `plotters-backend = "^<major>.<minor>*"`.
+ It's highly recommended that all the external crates follows the same rule to import `plotters-backend` depdendency, to avoid protential breaking
+ caused by `plotters-backend` crates gets a revision update.
+
+ We also impose a versioning rule with `plotters` and some backends:
+ The compatible main crate (`plotters`) and this crate (`plotters-backend`) are always use the same major and minor version number.
+ All the plotters main crate and second-party backends with version "x.y.*" should be compatible, and they should depens on the latest version of `plotters-backend x.y.*`
+
+*/
+use std::error::Error;
+
+pub mod rasterizer;
+mod style;
+mod text;
+
+pub use style::{BackendColor, BackendStyle};
+pub use text::{text_anchor, BackendTextStyle, FontFamily, FontStyle, FontTransform};
+
+use text_anchor::{HPos, VPos};
+
+/// A coordinate in the pixel-based backend. The coordinate follows the framebuffer's convention,
+/// which defines the top-left point as (0, 0).
+pub type BackendCoord = (i32, i32);
+
+/// The error produced by a drawing backend.
+#[derive(Debug)]
+pub enum DrawingErrorKind<E: Error + Send + Sync> {
+ /// A drawing backend error
+ DrawingError(E),
+ /// A font rendering error
+ FontError(Box<dyn Error + Send + Sync + 'static>),
+}
+
+impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ match self {
+ DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e),
+ DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e),
+ }
+ }
+}
+
+impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {}
+
+/// The drawing backend trait, which implements the low-level drawing APIs.
+/// This trait has a set of default implementation. And the minimal requirement of
+/// implementing a drawing backend is implementing the `draw_pixel` function.
+///
+/// If the drawing backend supports vector graphics, the other drawing APIs should be
+/// override by the backend specific implementation. Otherwise, the default implementation
+/// will use the pixel-based approach to draw other types of low-level shapes.
+pub trait DrawingBackend: Sized {
+ /// The error type reported by the backend
+ type ErrorType: Error + Send + Sync;
+
+ /// Get the dimension of the drawing backend in pixels
+ fn get_size(&self) -> (u32, u32);
+
+ /// Ensure the backend is ready to draw
+ fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
+
+ /// Finalize the drawing step and present all the changes.
+ /// This is used as the real-time rendering support.
+ /// The backend may implement in the following way, when `ensure_prepared` is called
+ /// it checks if it needs a fresh buffer and `present` is called rendering all the
+ /// pending changes on the screen.
+ fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
+
+ /// Draw a pixel on the drawing backend
+ /// - `point`: The backend pixel-based coordinate to draw
+ /// - `color`: The color of the pixel
+ fn draw_pixel(
+ &mut self,
+ point: BackendCoord,
+ color: BackendColor,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
+
+ /// Draw a line on the drawing backend
+ /// - `from`: The start point of the line
+ /// - `to`: The end point of the line
+ /// - `style`: The style of the line
+ fn draw_line<S: BackendStyle>(
+ &mut self,
+ from: BackendCoord,
+ to: BackendCoord,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ rasterizer::draw_line(self, from, to, style)
+ }
+
+ /// Draw a rectangle on the drawing backend
+ /// - `upper_left`: The coordinate of the upper-left corner of the rect
+ /// - `bottom_right`: The coordinate of the bottom-right corner of the rect
+ /// - `style`: The style
+ /// - `fill`: If the rectangle should be filled
+ fn draw_rect<S: BackendStyle>(
+ &mut self,
+ upper_left: BackendCoord,
+ bottom_right: BackendCoord,
+ style: &S,
+ fill: bool,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ rasterizer::draw_rect(self, upper_left, bottom_right, style, fill)
+ }
+
+ /// Draw a path on the drawing backend
+ /// - `path`: The iterator of key points of the path
+ /// - `style`: The style of the path
+ fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
+ &mut self,
+ path: I,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+
+ if style.stroke_width() == 1 {
+ let mut begin: Option<BackendCoord> = None;
+ for end in path.into_iter() {
+ if let Some(begin) = begin {
+ let result = self.draw_line(begin, end, style);
+ if result.is_err() {
+ return result;
+ }
+ }
+ begin = Some(end);
+ }
+ } else {
+ let p: Vec<_> = path.into_iter().collect();
+ let v = rasterizer::polygonize(&p[..], style.stroke_width());
+ return self.fill_polygon(v, &style.color());
+ }
+ Ok(())
+ }
+
+ /// Draw a circle on the drawing backend
+ /// - `center`: The center coordinate of the circle
+ /// - `radius`: The radius of the circle
+ /// - `style`: The style of the shape
+ /// - `fill`: If the circle should be filled
+ fn draw_circle<S: BackendStyle>(
+ &mut self,
+ center: BackendCoord,
+ radius: u32,
+ style: &S,
+ fill: bool,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ rasterizer::draw_circle(self, center, radius, style, fill)
+ }
+
+ fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
+ &mut self,
+ vert: I,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ let vert_buf: Vec<_> = vert.into_iter().collect();
+
+ rasterizer::fill_polygon(self, &vert_buf[..], style)
+ }
+
+ /// Draw a text on the drawing backend
+ /// - `text`: The text to draw
+ /// - `style`: The text style
+ /// - `pos` : The text anchor point
+ fn draw_text<TStyle: BackendTextStyle>(
+ &mut self,
+ text: &str,
+ style: &TStyle,
+ pos: BackendCoord,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ let color = style.color();
+ if color.alpha == 0.0 {
+ return Ok(());
+ }
+
+ let layout = style
+ .layout_box(text)
+ .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?;
+ let ((min_x, min_y), (max_x, max_y)) = layout;
+ let width = (max_x - min_x) as i32;
+ let height = (max_y - min_y) as i32;
+ let dx = match style.anchor().h_pos {
+ HPos::Left => 0,
+ HPos::Right => -width,
+ HPos::Center => -width / 2,
+ };
+ let dy = match style.anchor().v_pos {
+ VPos::Top => 0,
+ VPos::Center => -height / 2,
+ VPos::Bottom => -height,
+ };
+ let trans = style.transform();
+ let (w, h) = self.get_size();
+ match style.draw(text, (0, 0), |x, y, color| {
+ let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y);
+ let (x, y) = (pos.0 + x, pos.1 + y);
+ if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 {
+ self.draw_pixel((x, y), color)
+ } else {
+ Ok(())
+ }
+ }) {
+ Ok(drawing_result) => drawing_result,
+ Err(font_error) => Err(DrawingErrorKind::FontError(Box::new(font_error))),
+ }
+ }
+
+ /// Estimate the size of the horizontal text if rendered on this backend.
+ /// This is important because some of the backend may not have font ability.
+ /// Thus this allows those backend reports proper value rather than ask the
+ /// font rasterizer for that.
+ ///
+ /// - `text`: The text to estimate
+ /// - `font`: The font to estimate
+ /// - *Returns* The estimated text size
+ fn estimate_text_size<TStyle: BackendTextStyle>(
+ &self,
+ text: &str,
+ style: &TStyle,
+ ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
+ let layout = style
+ .layout_box(text)
+ .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?;
+ Ok((
+ ((layout.1).0 - (layout.0).0) as u32,
+ ((layout.1).1 - (layout.0).1) as u32,
+ ))
+ }
+
+ /// Blit a bitmap on to the backend.
+ ///
+ /// - `text`: pos the left upper conner of the bitmap to blit
+ /// - `src`: The source of the image
+ ///
+ /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but
+ /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap
+ /// element that matches the pixel format, but we need to fix this.
+ fn blit_bitmap<'a>(
+ &mut self,
+ pos: BackendCoord,
+ (iw, ih): (u32, u32),
+ src: &'a [u8],
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ let (w, h) = self.get_size();
+
+ for dx in 0..iw {
+ if pos.0 + dx as i32 >= w as i32 {
+ break;
+ }
+ for dy in 0..ih {
+ if pos.1 + dy as i32 >= h as i32 {
+ break;
+ }
+ // FIXME: This assume we have RGB image buffer
+ let r = src[(dx + dy * w) as usize * 3];
+ let g = src[(dx + dy * w) as usize * 3 + 1];
+ let b = src[(dx + dy * w) as usize * 3 + 2];
+ let color = BackendColor {
+ alpha: 1.0,
+ rgb: (r, g, b),
+ };
+ let result = self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), color);
+ if result.is_err() {
+ return result;
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/rasterizer/circle.rs b/src/rasterizer/circle.rs
new file mode 100644
index 0000000..779f095
--- /dev/null
+++ b/src/rasterizer/circle.rs
@@ -0,0 +1,342 @@
+use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
+
+fn draw_part_a<
+ B: DrawingBackend,
+ Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
+>(
+ height: f64,
+ radius: u32,
+ mut draw: Draw,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ let half_width = (radius as f64 * radius as f64
+ - (radius as f64 - height) * (radius as f64 - height))
+ .sqrt();
+
+ let x0 = (-half_width).ceil() as i32;
+ let x1 = half_width.floor() as i32;
+
+ let y0 = (radius as f64 - height).ceil();
+
+ for x in x0..=x1 {
+ let y1 = (radius as f64 * radius as f64 - x as f64 * x as f64).sqrt();
+ check_result!(draw(x, (y0, y1)));
+ }
+
+ Ok(())
+}
+
+fn draw_part_b<
+ B: DrawingBackend,
+ Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
+>(
+ from: f64,
+ size: f64,
+ mut draw: Draw,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ let from = from.floor();
+ for x in (from - size).floor() as i32..=from as i32 {
+ check_result!(draw(x, (-x as f64, x as f64)));
+ }
+ Ok(())
+}
+
+fn draw_part_c<
+ B: DrawingBackend,
+ Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>,
+>(
+ r: i32,
+ r_limit: i32,
+ mut draw: Draw,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ let half_size = r as f64 / (2f64).sqrt();
+
+ let (x0, x1) = ((-half_size).ceil() as i32, half_size.floor() as i32);
+
+ for x in x0..x1 {
+ let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt();
+ let inner_y0 = r as f64 - 1.0;
+ let mut y1 = outter_y0.min(inner_y0);
+ let y0 = ((r as f64) * (r as f64) - x as f64 * x as f64).sqrt();
+
+ if y0 > y1 {
+ y1 = y0.ceil();
+ if y1 >= r as f64 {
+ continue;
+ }
+ }
+
+ check_result!(draw(x, (y0, y1)));
+ }
+
+ for x in x1 + 1..r {
+ let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt();
+ let inner_y0 = r as f64 - 1.0;
+ let y0 = outter_y0.min(inner_y0);
+ let y1 = x as f64;
+
+ if y1 < y0 {
+ check_result!(draw(x, (y0, y1 + 1.0)));
+ check_result!(draw(-x, (y0, y1 + 1.0)));
+ }
+ }
+
+ Ok(())
+}
+
+fn draw_sweep_line<B: DrawingBackend, S: BackendStyle>(
+ b: &mut B,
+ style: &S,
+ (x0, y0): BackendCoord,
+ (dx, dy): (i32, i32),
+ p0: i32,
+ (s, e): (f64, f64),
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ let mut s = if dx < 0 || dy < 0 { -s } else { s };
+ let mut e = if dx < 0 || dy < 0 { -e } else { e };
+ if s > e {
+ std::mem::swap(&mut s, &mut e);
+ }
+
+ let vs = s.ceil() - s;
+ let ve = e - e.floor();
+
+ if dx == 0 {
+ check_result!(b.draw_line(
+ (p0 + x0, s.ceil() as i32 + y0),
+ (p0 + x0, e.floor() as i32 + y0),
+ &style.color()
+ ));
+ check_result!(b.draw_pixel((p0 + x0, s.ceil() as i32 + y0 - 1), style.color().mix(vs)));
+ check_result!(b.draw_pixel((p0 + x0, e.floor() as i32 + y0 + 1), style.color().mix(ve)));
+ } else {
+ check_result!(b.draw_line(
+ (s.ceil() as i32 + x0, p0 + y0),
+ (e.floor() as i32 + x0, p0 + y0),
+ &style.color()
+ ));
+ check_result!(b.draw_pixel((s.ceil() as i32 + x0 - 1, p0 + y0), style.color().mix(vs)));
+ check_result!(b.draw_pixel((e.floor() as i32 + x0 + 1, p0 + y0), style.color().mix(ve)));
+ }
+
+ Ok(())
+}
+
+fn draw_annulus<B: DrawingBackend, S: BackendStyle>(
+ b: &mut B,
+ center: BackendCoord,
+ radius: (u32, u32),
+ style: &S,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ let a0 = ((radius.0 - radius.1) as f64).min(radius.0 as f64 * (1.0 - 1.0 / (2f64).sqrt()));
+ let a1 = (radius.0 as f64 - a0 - radius.1 as f64).max(0.0);
+
+ check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
+ b,
+ style,
+ center,
+ (0, 1),
+ p,
+ r
+ )));
+ check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
+ b,
+ style,
+ center,
+ (0, -1),
+ p,
+ r
+ )));
+ check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
+ b,
+ style,
+ center,
+ (1, 0),
+ p,
+ r
+ )));
+ check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line(
+ b,
+ style,
+ center,
+ (-1, 0),
+ p,
+ r
+ )));
+
+ if a1 > 0.0 {
+ check_result!(draw_part_b::<B, _>(
+ radius.0 as f64 - a0,
+ a1.floor(),
+ |h, (f, t)| {
+ let h = h as i32;
+ let f = f as i32;
+ let t = t as i32;
+ check_result!(b.draw_line(
+ (center.0 + h, center.1 + f),
+ (center.0 + h, center.1 + t),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 - h, center.1 + f),
+ (center.0 - h, center.1 + t),
+ &style.color()
+ ));
+
+ check_result!(b.draw_line(
+ (center.0 + f + 1, center.1 + h),
+ (center.0 + t - 1, center.1 + h),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + f + 1, center.1 - h),
+ (center.0 + t - 1, center.1 - h),
+ &style.color()
+ ));
+
+ Ok(())
+ }
+ ));
+ }
+
+ check_result!(draw_part_c::<B, _>(
+ radius.1 as i32,
+ radius.0 as i32,
+ |p, r| draw_sweep_line(b, style, center, (0, 1), p, r)
+ ));
+ check_result!(draw_part_c::<B, _>(
+ radius.1 as i32,
+ radius.0 as i32,
+ |p, r| draw_sweep_line(b, style, center, (0, -1), p, r)
+ ));
+ check_result!(draw_part_c::<B, _>(
+ radius.1 as i32,
+ radius.0 as i32,
+ |p, r| draw_sweep_line(b, style, center, (1, 0), p, r)
+ ));
+ check_result!(draw_part_c::<B, _>(
+ radius.1 as i32,
+ radius.0 as i32,
+ |p, r| draw_sweep_line(b, style, center, (-1, 0), p, r)
+ ));
+
+ let d_inner = ((radius.1 as f64) / (2f64).sqrt()) as i32;
+ let d_outter = (((radius.0 as f64) / (2f64).sqrt()) as i32).min(radius.1 as i32 - 1);
+ let d_outter_actually = (radius.1 as i32).min(
+ (radius.0 as f64 * radius.0 as f64 - radius.1 as f64 * radius.1 as f64 / 2.0)
+ .sqrt()
+ .ceil() as i32,
+ );
+
+ check_result!(b.draw_line(
+ (center.0 - d_inner, center.1 - d_inner),
+ (center.0 - d_outter, center.1 - d_outter),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + d_inner, center.1 - d_inner),
+ (center.0 + d_outter, center.1 - d_outter),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 - d_inner, center.1 + d_inner),
+ (center.0 - d_outter, center.1 + d_outter),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + d_inner, center.1 + d_inner),
+ (center.0 + d_outter, center.1 + d_outter),
+ &style.color()
+ ));
+
+ check_result!(b.draw_line(
+ (center.0 - d_inner, center.1 + d_inner),
+ (center.0 - d_outter_actually, center.1 + d_inner),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + d_inner, center.1 - d_inner),
+ (center.0 + d_inner, center.1 - d_outter_actually),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + d_inner, center.1 + d_inner),
+ (center.0 + d_inner, center.1 + d_outter_actually),
+ &style.color()
+ ));
+ check_result!(b.draw_line(
+ (center.0 + d_inner, center.1 + d_inner),
+ (center.0 + d_outter_actually, center.1 + d_inner),
+ &style.color()
+ ));
+
+ Ok(())
+}
+
+pub fn draw_circle<B: DrawingBackend, S: BackendStyle>(
+ b: &mut B,
+ center: BackendCoord,
+ mut radius: u32,
+ style: &S,
+ mut fill: bool,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+
+ if !fill && style.stroke_width() != 1 {
+ let inner_radius = radius - (style.stroke_width() / 2).min(radius);
+ radius += style.stroke_width() / 2;
+ if inner_radius > 0 {
+ return draw_annulus(b, center, (radius, inner_radius), style);
+ } else {
+ fill = true;
+ }
+ }
+
+ let min = (f64::from(radius) * (1.0 - (2f64).sqrt() / 2.0)).ceil() as i32;
+ let max = (f64::from(radius) * (1.0 + (2f64).sqrt() / 2.0)).floor() as i32;
+
+ let range = min..=max;
+
+ let (up, down) = (
+ range.start() + center.1 - radius as i32,
+ range.end() + center.1 - radius as i32,
+ );
+
+ for dy in range {
+ let dy = dy - radius as i32;
+ let y = center.1 + dy;
+
+ let lx = (f64::from(radius) * f64::from(radius)
+ - (f64::from(dy) * f64::from(dy)).max(1e-5))
+ .sqrt();
+
+ let left = center.0 - lx.floor() as i32;
+ let right = center.0 + lx.floor() as i32;
+
+ let v = lx - lx.floor();
+
+ let x = center.0 + dy;
+ let top = center.1 - lx.floor() as i32;
+ let bottom = center.1 + lx.floor() as i32;
+
+ if fill {
+ check_result!(b.draw_line((left, y), (right, y), &style.color()));
+ check_result!(b.draw_line((x, top), (x, up), &style.color()));
+ check_result!(b.draw_line((x, down), (x, bottom), &style.color()));
+ } else {
+ check_result!(b.draw_pixel((left, y), style.color().mix(1.0 - v)));
+ check_result!(b.draw_pixel((right, y), style.color().mix(1.0 - v)));
+
+ check_result!(b.draw_pixel((x, top), style.color().mix(1.0 - v)));
+ check_result!(b.draw_pixel((x, bottom), style.color().mix(1.0 - v)));
+ }
+
+ check_result!(b.draw_pixel((left - 1, y), style.color().mix(v)));
+ check_result!(b.draw_pixel((right + 1, y), style.color().mix(v)));
+ check_result!(b.draw_pixel((x, top - 1), style.color().mix(v)));
+ check_result!(b.draw_pixel((x, bottom + 1), style.color().mix(v)));
+ }
+
+ Ok(())
+}
diff --git a/src/rasterizer/line.rs b/src/rasterizer/line.rs
new file mode 100644
index 0000000..0f24b0a
--- /dev/null
+++ b/src/rasterizer/line.rs
@@ -0,0 +1,123 @@
+use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
+
+pub fn draw_line<DB: DrawingBackend, S: BackendStyle>(
+ back: &mut DB,
+ mut from: BackendCoord,
+ mut to: BackendCoord,
+ style: &S,
+) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+
+ if style.stroke_width() != 1 {
+ // If the line is wider than 1px, then we need to make it a polygon
+ let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1));
+ let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt();
+
+ if l < 1e-5 {
+ return Ok(());
+ }
+
+ let v = (v.0 as f64 / l, v.1 as f64 / l);
+
+ let r = f64::from(style.stroke_width()) / 2.0;
+ let mut trans = [(v.1 * r, -v.0 * r), (-v.1 * r, v.0 * r)];
+ let mut vertices = vec![];
+
+ for point in [from, to].iter() {
+ for t in trans.iter() {
+ vertices.push((
+ (f64::from(point.0) + t.0) as i32,
+ (f64::from(point.1) + t.1) as i32,
+ ))
+ }
+
+ trans.swap(0, 1);
+ }
+
+ return back.fill_polygon(vertices, style);
+ }
+
+ if from.0 == to.0 {
+ if from.1 > to.1 {
+ std::mem::swap(&mut from, &mut to);
+ }
+ for y in from.1..=to.1 {
+ check_result!(back.draw_pixel((from.0, y), style.color()));
+ }
+ return Ok(());
+ }
+
+ if from.1 == to.1 {
+ if from.0 > to.0 {
+ std::mem::swap(&mut from, &mut to);
+ }
+ for x in from.0..=to.0 {
+ check_result!(back.draw_pixel((x, from.1), style.color()));
+ }
+ return Ok(());
+ }
+
+ let steep = (from.0 - to.0).abs() < (from.1 - to.1).abs();
+
+ if steep {
+ from = (from.1, from.0);
+ to = (to.1, to.0);
+ }
+
+ let (from, to) = if from.0 > to.0 {
+ (to, from)
+ } else {
+ (from, to)
+ };
+
+ let mut size_limit = back.get_size();
+
+ if steep {
+ size_limit = (size_limit.1, size_limit.0);
+ }
+
+ let grad = f64::from(to.1 - from.1) / f64::from(to.0 - from.0);
+
+ let mut put_pixel = |(x, y): BackendCoord, b: f64| {
+ if steep {
+ back.draw_pixel((y, x), style.color().mix(b))
+ } else {
+ back.draw_pixel((x, y), style.color().mix(b))
+ }
+ };
+
+ let y_step_limit =
+ (f64::from(to.1.min(size_limit.1 as i32 - 1).max(0) - from.1) / grad).floor() as i32;
+
+ let batch_start = (f64::from(from.1.min(size_limit.1 as i32 - 2).max(0) - from.1) / grad)
+ .abs()
+ .ceil() as i32
+ + from.0;
+
+ let batch_limit =
+ to.0.min(size_limit.0 as i32 - 2)
+ .min(from.0 + y_step_limit - 1);
+
+ let mut y = f64::from(from.1) + f64::from(batch_start - from.0) * grad;
+
+ for x in batch_start..=batch_limit {
+ check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y));
+ check_result!(put_pixel((x, y as i32 + 1), y - y.floor()));
+
+ y += grad;
+ }
+
+ if to.0 >= batch_limit + 1 && y < f64::from(to.1) {
+ let x = batch_limit as i32 + 1;
+ if 1.0 + y.floor() - y > 1e-5 {
+ check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y));
+ }
+ if y - y.floor() > 1e-5 && y + 1.0 < f64::from(to.1) {
+ check_result!(put_pixel((x, y as i32 + 1), y - y.floor()));
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs
new file mode 100644
index 0000000..11835f4
--- /dev/null
+++ b/src/rasterizer/mod.rs
@@ -0,0 +1,32 @@
+/*! # The built-in rasterizers.
+
+ Plotters make a minimal backend ability assumption - which is drawing a pixel on
+ backend. And this is the rasterizer that utilize this minimal ability to build a
+ fully functioning backend.
+
+*/
+
+// TODO: ? operator is very slow. See issue #58 for details
+macro_rules! check_result {
+ ($e:expr) => {
+ let result = $e;
+ if result.is_err() {
+ return result;
+ }
+ };
+}
+
+mod line;
+pub use line::draw_line;
+
+mod rect;
+pub use rect::draw_rect;
+
+mod circle;
+pub use circle::draw_circle;
+
+mod polygon;
+pub use polygon::fill_polygon;
+
+mod path;
+pub use path::polygonize;
diff --git a/src/rasterizer/path.rs b/src/rasterizer/path.rs
new file mode 100644
index 0000000..b2db356
--- /dev/null
+++ b/src/rasterizer/path.rs
@@ -0,0 +1,115 @@
+use crate::BackendCoord;
+
+fn get_dir_vector(from: BackendCoord, to: BackendCoord, flag: bool) -> ((f64, f64), (f64, f64)) {
+ let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1));
+ let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt();
+
+ let v = (v.0 as f64 / l, v.1 as f64 / l);
+
+ if flag {
+ (v, (v.1, -v.0))
+ } else {
+ (v, (-v.1, v.0))
+ }
+}
+
+fn compute_polygon_vertex(triple: &[BackendCoord; 3], d: f64) -> BackendCoord {
+ let (a_t, a_n) = get_dir_vector(triple[0], triple[1], false);
+ let (b_t, b_n) = get_dir_vector(triple[2], triple[1], true);
+
+ let a_p = (
+ f64::from(triple[1].0) + d * a_n.0,
+ f64::from(triple[1].1) + d * a_n.1,
+ );
+ let b_p = (
+ f64::from(triple[1].0) + d * b_n.0,
+ f64::from(triple[1].1) + d * b_n.1,
+ );
+
+ // u * a_t + a_p = v * b_t + b_p
+ // u * a_t.0 - v * b_t.0 = b_p.0 - a_p.0
+ // u * a_t.1 - v * b_t.1 = b_p.1 - a_p.1
+ if a_p.0 as i32 == b_p.0 as i32 && a_p.1 as i32 == b_p.1 as i32 {
+ return (a_p.0 as i32, a_p.1 as i32);
+ }
+
+ let a0 = a_t.0;
+ let b0 = -b_t.0;
+ let c0 = b_p.0 - a_p.0;
+ let a1 = a_t.1;
+ let b1 = -b_t.1;
+ let c1 = b_p.1 - a_p.1;
+
+ // This is the coner case that
+ if (a0 * b1 - a1 * b0).abs() < 1e-10 {
+ return (a_p.0 as i32, a_p.1 as i32);
+ }
+
+ let u = (c0 * b1 - c1 * b0) / (a0 * b1 - a1 * b0);
+
+ let x = a_p.0 + u * a_t.0;
+ let y = a_p.1 + u * a_t.1;
+
+ (x.round() as i32, y.round() as i32)
+}
+
+fn traverse_vertices<'a>(
+ mut vertices: impl Iterator<Item = &'a BackendCoord>,
+ width: u32,
+ mut op: impl FnMut(BackendCoord),
+) {
+ let mut a = vertices.next().unwrap();
+ let mut b = vertices.next().unwrap();
+
+ while a == b {
+ a = b;
+ if let Some(new_b) = vertices.next() {
+ b = new_b;
+ } else {
+ return;
+ }
+ }
+
+ let (_, n) = get_dir_vector(*a, *b, false);
+
+ op((
+ (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32,
+ (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32,
+ ));
+
+ let mut recent = [(0, 0), *a, *b];
+
+ for p in vertices {
+ if *p == recent[2] {
+ continue;
+ }
+ recent.swap(0, 1);
+ recent.swap(1, 2);
+ recent[2] = *p;
+ op(compute_polygon_vertex(&recent, f64::from(width) / 2.0));
+ }
+
+ let b = recent[1];
+ let a = recent[2];
+
+ let (_, n) = get_dir_vector(a, b, true);
+
+ op((
+ (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32,
+ (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32,
+ ));
+}
+
+/// Covert a path with >1px stroke width into polygon.
+pub fn polygonize(vertices: &[BackendCoord], stroke_width: u32) -> Vec<BackendCoord> {
+ if vertices.len() < 2 {
+ return vec![];
+ }
+
+ let mut ret = vec![];
+
+ traverse_vertices(vertices.iter(), stroke_width, |v| ret.push(v));
+ traverse_vertices(vertices.iter().rev(), stroke_width, |v| ret.push(v));
+
+ ret
+}
diff --git a/src/rasterizer/polygon.rs b/src/rasterizer/polygon.rs
new file mode 100644
index 0000000..ce33c5c
--- /dev/null
+++ b/src/rasterizer/polygon.rs
@@ -0,0 +1,242 @@
+use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
+
+use std::cmp::{Ord, Ordering, PartialOrd};
+
+#[derive(Clone, Debug)]
+struct Edge {
+ epoch: u32,
+ total_epoch: u32,
+ slave_begin: i32,
+ slave_end: i32,
+}
+
+impl Edge {
+ fn horizontal_sweep(mut from: BackendCoord, mut to: BackendCoord) -> Option<Edge> {
+ if from.0 == to.0 {
+ return None;
+ }
+
+ if from.0 > to.0 {
+ std::mem::swap(&mut from, &mut to);
+ }
+
+ Some(Edge {
+ epoch: 0,
+ total_epoch: (to.0 - from.0) as u32,
+ slave_begin: from.1,
+ slave_end: to.1,
+ })
+ }
+
+ fn vertical_sweep(from: BackendCoord, to: BackendCoord) -> Option<Edge> {
+ Edge::horizontal_sweep((from.1, from.0), (to.1, to.0))
+ }
+
+ fn get_master_pos(&self) -> i32 {
+ (self.total_epoch - self.epoch) as i32
+ }
+
+ fn inc_epoch(&mut self) {
+ self.epoch += 1;
+ }
+
+ fn get_slave_pos(&self) -> f64 {
+ f64::from(self.slave_begin)
+ + (i64::from(self.slave_end - self.slave_begin) * i64::from(self.epoch)) as f64
+ / f64::from(self.total_epoch)
+ }
+}
+
+impl PartialOrd for Edge {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.get_slave_pos().partial_cmp(&other.get_slave_pos())
+ }
+}
+
+impl PartialEq for Edge {
+ fn eq(&self, other: &Self) -> bool {
+ self.get_slave_pos() == other.get_slave_pos()
+ }
+}
+
+impl Eq for Edge {}
+
+impl Ord for Edge {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.get_slave_pos()
+ .partial_cmp(&other.get_slave_pos())
+ .unwrap()
+ }
+}
+
+pub fn fill_polygon<DB: DrawingBackend, S: BackendStyle>(
+ back: &mut DB,
+ vertices: &[BackendCoord],
+ style: &S,
+) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
+ if let Some((x_span, y_span)) =
+ vertices
+ .iter()
+ .fold(None, |res: Option<((i32, i32), (i32, i32))>, (x, y)| {
+ Some(
+ res.map(|((min_x, max_x), (min_y, max_y))| {
+ (
+ (min_x.min(*x), max_x.max(*x)),
+ (min_y.min(*y), max_y.max(*y)),
+ )
+ })
+ .unwrap_or(((*x, *x), (*y, *y))),
+ )
+ })
+ {
+ // First of all, let's handle the case that all the points is in a same vertical or
+ // horizontal line
+ if x_span.0 == x_span.1 || y_span.0 == y_span.1 {
+ return back.draw_line((x_span.0, y_span.0), (x_span.1, y_span.1), style);
+ }
+
+ let horizontal_sweep = x_span.1 - x_span.0 > y_span.1 - y_span.0;
+
+ let mut edges: Vec<_> = vertices
+ .iter()
+ .zip(vertices.iter().skip(1))
+ .map(|(a, b)| (*a, *b))
+ .collect();
+ edges.push((vertices[vertices.len() - 1], vertices[0]));
+ edges.sort_by_key(|((x1, y1), (x2, y2))| {
+ if horizontal_sweep {
+ *x1.min(x2)
+ } else {
+ *y1.min(y2)
+ }
+ });
+
+ for edge in &mut edges.iter_mut() {
+ if horizontal_sweep {
+ if (edge.0).0 > (edge.1).0 {
+ std::mem::swap(&mut edge.0, &mut edge.1);
+ }
+ } else if (edge.0).1 > (edge.1).1 {
+ std::mem::swap(&mut edge.0, &mut edge.1);
+ }
+ }
+
+ let (low, high) = if horizontal_sweep { x_span } else { y_span };
+
+ let mut idx = 0;
+
+ let mut active_edge: Vec<Edge> = vec![];
+
+ for sweep_line in low..=high {
+ let mut new_vec = vec![];
+
+ for mut e in active_edge {
+ if e.get_master_pos() > 0 {
+ e.inc_epoch();
+ new_vec.push(e);
+ }
+ }
+
+ active_edge = new_vec;
+
+ loop {
+ if idx >= edges.len() {
+ break;
+ }
+ let line = if horizontal_sweep {
+ (edges[idx].0).0
+ } else {
+ (edges[idx].0).1
+ };
+ if line > sweep_line {
+ break;
+ }
+
+ let edge_obj = if horizontal_sweep {
+ Edge::horizontal_sweep(edges[idx].0, edges[idx].1)
+ } else {
+ Edge::vertical_sweep(edges[idx].0, edges[idx].1)
+ };
+
+ if let Some(edge_obj) = edge_obj {
+ active_edge.push(edge_obj);
+ }
+
+ idx += 1;
+ }
+
+ active_edge.sort();
+
+ let mut first = None;
+ let mut second = None;
+
+ for edge in active_edge.iter() {
+ if first.is_none() {
+ first = Some(edge.clone())
+ } else if second.is_none() {
+ second = Some(edge.clone())
+ }
+
+ if let Some(a) = first.clone() {
+ if let Some(b) = second.clone() {
+ if a.get_master_pos() == 0 && b.get_master_pos() != 0 {
+ first = Some(b);
+ second = None;
+ continue;
+ }
+
+ if a.get_master_pos() != 0 && b.get_master_pos() == 0 {
+ first = Some(a);
+ second = None;
+ continue;
+ }
+
+ let from = a.get_slave_pos();
+ let to = b.get_slave_pos();
+
+ if a.get_master_pos() == 0 && b.get_master_pos() == 0 && to - from > 1.0 {
+ first = None;
+ second = None;
+ continue;
+ }
+
+ if horizontal_sweep {
+ check_result!(back.draw_line(
+ (sweep_line, from.ceil() as i32),
+ (sweep_line, to.floor() as i32),
+ &style.color(),
+ ));
+ check_result!(back.draw_pixel(
+ (sweep_line, from.floor() as i32),
+ style.color().mix(from.ceil() - from),
+ ));
+ check_result!(back.draw_pixel(
+ (sweep_line, to.ceil() as i32),
+ style.color().mix(to - to.floor()),
+ ));
+ } else {
+ check_result!(back.draw_line(
+ (from.ceil() as i32, sweep_line),
+ (to.floor() as i32, sweep_line),
+ &style.color(),
+ ));
+ check_result!(back.draw_pixel(
+ (from.floor() as i32, sweep_line),
+ style.color().mix(from.ceil() - from),
+ ));
+ check_result!(back.draw_pixel(
+ (to.ceil() as i32, sweep_line),
+ style.color().mix(to.floor() - to),
+ ));
+ }
+
+ first = None;
+ second = None;
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/rasterizer/rect.rs b/src/rasterizer/rect.rs
new file mode 100644
index 0000000..cd6c774
--- /dev/null
+++ b/src/rasterizer/rect.rs
@@ -0,0 +1,57 @@
+use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
+
+pub fn draw_rect<B: DrawingBackend, S: BackendStyle>(
+ b: &mut B,
+ upper_left: BackendCoord,
+ bottom_right: BackendCoord,
+ style: &S,
+ fill: bool,
+) -> Result<(), DrawingErrorKind<B::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+ let (upper_left, bottom_right) = (
+ (
+ upper_left.0.min(bottom_right.0),
+ upper_left.1.min(bottom_right.1),
+ ),
+ (
+ upper_left.0.max(bottom_right.0),
+ upper_left.1.max(bottom_right.1),
+ ),
+ );
+
+ if fill {
+ if bottom_right.0 - upper_left.0 < bottom_right.1 - upper_left.1 {
+ for x in upper_left.0..=bottom_right.0 {
+ check_result!(b.draw_line((x, upper_left.1), (x, bottom_right.1), style));
+ }
+ } else {
+ for y in upper_left.1..=bottom_right.1 {
+ check_result!(b.draw_line((upper_left.0, y), (bottom_right.0, y), style));
+ }
+ }
+ } else {
+ b.draw_line(
+ (upper_left.0, upper_left.1),
+ (upper_left.0, bottom_right.1),
+ style,
+ )?;
+ b.draw_line(
+ (upper_left.0, upper_left.1),
+ (bottom_right.0, upper_left.1),
+ style,
+ )?;
+ b.draw_line(
+ (bottom_right.0, bottom_right.1),
+ (upper_left.0, bottom_right.1),
+ style,
+ )?;
+ b.draw_line(
+ (bottom_right.0, bottom_right.1),
+ (bottom_right.0, upper_left.1),
+ style,
+ )?;
+ }
+ Ok(())
+}
diff --git a/src/style.rs b/src/style.rs
new file mode 100644
index 0000000..028a06b
--- /dev/null
+++ b/src/style.rs
@@ -0,0 +1,33 @@
+/// The color type that is used by all the backend
+#[derive(Clone, Copy)]
+pub struct BackendColor {
+ pub alpha: f64,
+ pub rgb: (u8, u8, u8),
+}
+
+impl BackendColor {
+ #[inline(always)]
+ pub fn mix(&self, alpha: f64) -> Self {
+ Self {
+ alpha: self.alpha * alpha,
+ rgb: self.rgb,
+ }
+ }
+}
+
+/// The style data for the backend drawing API
+pub trait BackendStyle {
+ /// Get the color of current style
+ fn color(&self) -> BackendColor;
+
+ /// Get the stroke width of current style
+ fn stroke_width(&self) -> u32 {
+ 1
+ }
+}
+
+impl BackendStyle for BackendColor {
+ fn color(&self) -> BackendColor {
+ *self
+ }
+}
diff --git a/src/text.rs b/src/text.rs
new file mode 100644
index 0000000..58cc9f2
--- /dev/null
+++ b/src/text.rs
@@ -0,0 +1,244 @@
+use super::{BackendColor, BackendCoord};
+use std::error::Error;
+
+/// Describes font family.
+/// This can be either a specific font family name, such as "arial",
+/// or a general font family class, such as "serif" and "sans-serif"
+#[derive(Clone, Copy)]
+pub enum FontFamily<'a> {
+ /// The system default serif font family
+ Serif,
+ /// The system default sans-serif font family
+ SansSerif,
+ /// The system default monospace font
+ Monospace,
+ /// A specific font family name
+ Name(&'a str),
+}
+
+impl<'a> FontFamily<'a> {
+ /// Make a CSS compatible string for the font family name.
+ /// This can be used as the value of `font-family` attribute in SVG.
+ pub fn as_str(&self) -> &str {
+ match self {
+ FontFamily::Serif => "serif",
+ FontFamily::SansSerif => "sans-serif",
+ FontFamily::Monospace => "monospace",
+ FontFamily::Name(face) => face,
+ }
+ }
+}
+
+impl<'a> From<&'a str> for FontFamily<'a> {
+ fn from(from: &'a str) -> FontFamily<'a> {
+ match from.to_lowercase().as_str() {
+ "serif" => FontFamily::Serif,
+ "sans-serif" => FontFamily::SansSerif,
+ "monospace" => FontFamily::Monospace,
+ _ => FontFamily::Name(from),
+ }
+ }
+}
+
+/// Text anchor attributes are used to properly position the text.
+///
+/// # Examples
+///
+/// In the example below, the text anchor (X) position is `Pos::new(HPos::Right, VPos::Center)`.
+/// ```text
+/// ***** X
+/// ```
+/// The position is always relative to the text regardless of its rotation.
+/// In the example below, the text has style
+/// `style.transform(FontTransform::Rotate90).pos(Pos::new(HPos::Center, VPos::Top))`.
+/// ```text
+/// *
+/// *
+/// * X
+/// *
+/// *
+/// ```
+pub mod text_anchor {
+ /// The horizontal position of the anchor point relative to the text.
+ #[derive(Clone, Copy)]
+ pub enum HPos {
+ /// Anchor point is on the left side of the text
+ Left,
+ /// Anchor point is on the right side of the text
+ Right,
+ /// Anchor point is in the horizontal center of the text
+ Center,
+ }
+
+ /// The vertical position of the anchor point relative to the text.
+ #[derive(Clone, Copy)]
+ pub enum VPos {
+ /// Anchor point is on the top of the text
+ Top,
+ /// Anchor point is in the vertical center of the text
+ Center,
+ /// Anchor point is on the bottom of the text
+ Bottom,
+ }
+
+ /// The text anchor position.
+ #[derive(Clone, Copy)]
+ pub struct Pos {
+ /// The horizontal position of the anchor point
+ pub h_pos: HPos,
+ /// The vertical position of the anchor point
+ pub v_pos: VPos,
+ }
+
+ impl Pos {
+ /// Create a new text anchor position.
+ ///
+ /// - `h_pos`: The horizontal position of the anchor point
+ /// - `v_pos`: The vertical position of the anchor point
+ /// - **returns** The newly created text anchor position
+ ///
+ /// ```rust
+ /// use plotters_backend::text_anchor::{Pos, HPos, VPos};
+ ///
+ /// let pos = Pos::new(HPos::Left, VPos::Top);
+ /// ```
+ pub fn new(h_pos: HPos, v_pos: VPos) -> Self {
+ Pos { h_pos, v_pos }
+ }
+
+ /// Create a default text anchor position (top left).
+ ///
+ /// - **returns** The default text anchor position
+ ///
+ /// ```rust
+ /// use plotters_backend::text_anchor::{Pos, HPos, VPos};
+ ///
+ /// let pos = Pos::default();
+ /// ```
+ pub fn default() -> Self {
+ Pos {
+ h_pos: HPos::Left,
+ v_pos: VPos::Top,
+ }
+ }
+ }
+}
+
+/// Specifying text transformations
+#[derive(Clone)]
+pub enum FontTransform {
+ /// Nothing to transform
+ None,
+ /// Rotating the text 90 degree clockwise
+ Rotate90,
+ /// Rotating the text 180 degree clockwise
+ Rotate180,
+ /// Rotating the text 270 degree clockwise
+ Rotate270,
+}
+
+impl FontTransform {
+ /// Transform the coordinate to perform the rotation
+ ///
+ /// - `x`: The x coordinate in pixels before transform
+ /// - `y`: The y coordinate in pixels before transform
+ /// - **returns**: The coordinate after transform
+ pub fn transform(&self, x: i32, y: i32) -> (i32, i32) {
+ match self {
+ FontTransform::None => (x, y),
+ FontTransform::Rotate90 => (-y, x),
+ FontTransform::Rotate180 => (-x, -y),
+ FontTransform::Rotate270 => (y, -x),
+ }
+ }
+}
+
+/// Describes the font style. Such as Italic, Oblique, etc.
+#[derive(Clone, Copy)]
+pub enum FontStyle {
+ /// The normal style
+ Normal,
+ /// The oblique style
+ Oblique,
+ /// The italic style
+ Italic,
+ /// The bold style
+ Bold,
+}
+
+impl FontStyle {
+ /// Convert the font style into a CSS compatible string which can be used in `font-style` attribute.
+ pub fn as_str(&self) -> &str {
+ match self {
+ FontStyle::Normal => "normal",
+ FontStyle::Italic => "italic",
+ FontStyle::Oblique => "oblique",
+ FontStyle::Bold => "bold",
+ }
+ }
+}
+
+impl<'a> From<&'a str> for FontStyle {
+ fn from(from: &'a str) -> FontStyle {
+ match from.to_lowercase().as_str() {
+ "normal" => FontStyle::Normal,
+ "italic" => FontStyle::Italic,
+ "oblique" => FontStyle::Oblique,
+ "bold" => FontStyle::Bold,
+ _ => FontStyle::Normal,
+ }
+ }
+}
+
+/// The trait that abstracts a style of a text.
+///
+/// This is used because the the backend crate have no knowledge about how
+/// the text handling is implemented in plotters.
+///
+/// But the backend still wants to know some information about the font, for
+/// the backend doesn't handles text drawing, may want to call the `draw` method which
+/// is implemented by the plotters main crate. While for the backend that handles the
+/// text drawing, those font information provides instructions about how the text should be
+/// rendered: color, size, slant, anchor, font, etc.
+///
+/// This trait decouples the detailed implementaiton about the font and the backend code which
+/// wants to perfome some operation on the font.
+///
+pub trait BackendTextStyle {
+ /// The error type of this text style implementation
+ type FontError: Error + Sync + Send + 'static;
+
+ fn color(&self) -> BackendColor {
+ BackendColor {
+ alpha: 1.0,
+ rgb: (0, 0, 0),
+ }
+ }
+
+ fn size(&self) -> f64 {
+ 1.0
+ }
+
+ fn transform(&self) -> FontTransform {
+ FontTransform::None
+ }
+
+ fn style(&self) -> FontStyle {
+ FontStyle::Normal
+ }
+
+ fn anchor(&self) -> text_anchor::Pos {
+ text_anchor::Pos::default()
+ }
+
+ fn family(&self) -> FontFamily;
+
+ fn layout_box(&self, text: &str) -> Result<((i32, i32), (i32, i32)), Self::FontError>;
+
+ fn draw<E, DrawFunc: FnMut(i32, i32, BackendColor) -> Result<(), E>>(
+ &self,
+ text: &str,
+ pos: BackendCoord,
+ draw: DrawFunc,
+ ) -> Result<Result<(), E>, Self::FontError>;
+}