Skip to content

Commit

Permalink
Add GTK/Cairo support
Browse files Browse the repository at this point in the history
  • Loading branch information
38 committed Oct 5, 2019
1 parent 27f8877 commit 0eb7ff4
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ backup/*
**/target
examples/wasm-demo/www/pkg
examples/.ipynb_checkpoints/
tarpaulin-report.html
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Plotters latest (?)

### Added

- Cairo backend, which supports using Plotters draw a GTK surface.

### Bug Fix

- `FontError` from rusttype isn't `Sync` and `Send`. We don't have trait bound to ensure this. (Thanks to @dalance)
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ svg = { version = "0.6.0", optional = true }
num-traits = { version = "^0.2", optional = true }
palette = { version = "^0.4", default-features = false, optional = true }
gif = { version = "^0.10.3", optional = true }
cairo-rs = { version = "0.7.1", optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rusttype = "0.8.1"
Expand All @@ -31,10 +32,13 @@ default_features = false
features = ["jpeg", "png_codec", "bmp"]

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3.4", features = ['Document', 'Element', 'HtmlElement', 'Node', 'Window', 'HtmlCanvasElement', 'CanvasRenderingContext2d'] }
js-sys= "0.3.4"
wasm-bindgen = "0.2.43"

[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3.4"
features = ['Document', 'Element', 'HtmlElement', 'Node', 'Window', 'HtmlCanvasElement', 'CanvasRenderingContext2d']

[features]
default = ["bitmap", "svg", "chrono", "palette_ext", "make_partial_axis", "gif_backend"]
palette_ext = ["palette", "num-traits"]
Expand All @@ -44,6 +48,7 @@ datetime = ["chrono"]
evcxr = ["svg"]
piston = ["piston_window"]
make_partial_axis = ["num-traits"]
cairo = ["cairo-rs"]


[dev-dependencies]
Expand Down
13 changes: 13 additions & 0 deletions examples/gtk-demo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "gtk-demo"
version = "0.1.0"
authors = ["Hao Hou <haohou302@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
plotters = {path = "../..", features = ["cairo"]}
cairo-rs = "0.7.1"
gtk = "0.7.0"
gio = "0.7.0"
58 changes: 58 additions & 0 deletions examples/gtk-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::env::args;

use gio::prelude::*;
use gtk::prelude::*;
use gtk::DrawingArea;

use cairo::Context;
use plotters::prelude::*;

fn build_ui(app: &gtk::Application) {
drawable(app, 500, 500, |_, cr| {
let root = CairoBackend::new(cr, (500, 500)).unwrap().into_drawing_area();

root.fill(&WHITE).unwrap();

let mut chart = ChartBuilder::on(&root)
.caption("This is a test", ("Arial", 20))
.x_label_area_size(40)
.y_label_area_size(40)
.build_ranged(0..100, 0..100)
.unwrap();

chart.configure_mesh()
.draw()
.unwrap();

Inhibit(false)
})
}

fn main() {
let application = gtk::Application::new(
Some("io.github.plotters-rs.plotters-gtk-test"),
Default::default(),
)
.expect("Initialization failed...");

application.connect_activate(|app| {
build_ui(app);
});

application.run(&args().collect::<Vec<_>>());
}

pub fn drawable<F>(application: &gtk::Application, width: i32, height: i32, draw_fn: F)
where
F: Fn(&DrawingArea, &Context) -> Inhibit + 'static,
{
let window = gtk::ApplicationWindow::new(application);
let drawing_area = Box::new(DrawingArea::new)();

drawing_area.connect_draw(draw_fn);

window.set_default_size(width, height);

window.add(&drawing_area);
window.show_all();
}
270 changes: 270 additions & 0 deletions src/drawing/backend_impl/cairo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
use cairo::{Context as CairoContext, FontSlant, FontWeight, Status as CairoStatus};

#[allow(unused_imports)]
use crate::drawing::backend::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
#[allow(unused_imports)]
use crate::style::{Color, FontDesc, FontTransform, RGBAColor};

/// The drawing backend that is backed with a Cairo context
pub struct CairoBackend<'a> {
context: &'a CairoContext,
width: u32,
height: u32,
init_flag: bool,
}

#[derive(Debug)]
pub struct CairoError(CairoStatus);

impl std::fmt::Display for CairoError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{:?}", self)
}
}

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

impl<'a> CairoBackend<'a> {
fn call_cairo<F: Fn(&CairoContext)>(&self, f: F) -> Result<(), DrawingErrorKind<CairoError>> {
f(self.context);
if self.context.status() == CairoStatus::Success {
return Ok(());
}
Err(DrawingErrorKind::DrawingError(CairoError(
self.context.status(),
)))
}

fn set_color(&self, color: &RGBAColor) -> Result<(), DrawingErrorKind<CairoError>> {
self.call_cairo(|c| {
c.set_source_rgba(
f64::from(color.rgb().0) / 255.0,
f64::from(color.rgb().1) / 255.0,
f64::from(color.rgb().2) / 255.0,
f64::from(color.alpha()),
)
})?;
Ok(())
}

fn set_stroke_width(&self, width: u32) -> Result<(), DrawingErrorKind<CairoError>> {
self.call_cairo(|c| c.set_line_width(f64::from(width)))?;
Ok(())
}

pub fn new(context: &'a CairoContext, (w, h): (u32, u32)) -> Result<Self, CairoError> {
let ret = Self {
context,
width: w,
height: h,
init_flag: false,
};
Ok(ret)
}
}

impl<'a> DrawingBackend for CairoBackend<'a> {
type ErrorType = CairoError;

fn get_size(&self) -> (u32, u32) {
(self.width, self.height)
}

fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if !self.init_flag {
let (x0, y0, x1, y1) = self.context.clip_extents();
self.call_cairo(|c| {
c.scale(
(x1 - x0) / f64::from(self.width),
(y1 - y0) / f64::from(self.height),
)
})?;
self.init_flag = true;
}
Ok(())
}

fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
Ok(())
}

fn draw_pixel(
&mut self,
point: BackendCoord,
color: &RGBAColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.call_cairo(|c| c.rectangle(f64::from(point.0), f64::from(point.1), 1.0, 1.0))?;
self.call_cairo(|c| {
c.set_source_rgba(
f64::from(color.rgb().0) / 255.0,
f64::from(color.rgb().1) / 255.0,
f64::from(color.rgb().2) / 255.0,
f64::from(color.alpha()),
)
})?;
self.call_cairo(|c| c.fill())?;
Ok(())
}

fn draw_line<S: BackendStyle>(
&mut self,
from: BackendCoord,
to: BackendCoord,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.call_cairo(|c| c.move_to(f64::from(from.0), f64::from(from.1)))?;

self.set_color(&style.as_color())?;
self.set_stroke_width(style.stroke_width())?;

self.call_cairo(|c| c.line_to(f64::from(to.0), f64::from(to.1)))?;
self.call_cairo(|c| c.stroke())?;
Ok(())
}

fn draw_rect<S: BackendStyle>(
&mut self,
upper_left: BackendCoord,
bottom_right: BackendCoord,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.set_color(&style.as_color())?;
self.set_stroke_width(style.stroke_width())?;

self.call_cairo(|c| {
c.rectangle(
f64::from(upper_left.0),
f64::from(upper_left.0),
f64::from(bottom_right.0 - upper_left.0),
f64::from(bottom_right.1 - upper_left.1),
)
})?;

if fill {
self.call_cairo(|c| c.fill())?;
} else {
self.call_cairo(|c| c.stroke())?;
}

Ok(())
}

fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.set_color(&style.as_color())?;
self.set_stroke_width(style.stroke_width())?;

let mut path = path.into_iter();

if let Some((x, y)) = path.next() {
self.call_cairo(|c| c.move_to(f64::from(x), f64::from(y)))?;
}

for (x, y) in path {
self.call_cairo(|c| c.line_to(f64::from(x), f64::from(y)))?;
}

self.call_cairo(|c| c.stroke())?;

Ok(())
}

fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.set_color(&style.as_color())?;
self.set_stroke_width(style.stroke_width())?;

let mut path = path.into_iter();

if let Some((x, y)) = path.next() {
self.call_cairo(|c| c.move_to(f64::from(x), f64::from(y)))?;
} else {
return Ok(());
}

for (x, y) in path {
self.call_cairo(|c| c.line_to(f64::from(x), f64::from(y)))?;
}

self.call_cairo(|c| c.close_path())?;
self.call_cairo(|c| c.fill())?;

Ok(())
}

fn draw_circle<S: BackendStyle>(
&mut self,
center: BackendCoord,
radius: u32,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
self.set_color(&style.as_color())?;
self.set_stroke_width(style.stroke_width())?;

self.call_cairo(|c| {
c.arc(
f64::from(center.0),
f64::from(center.1),
f64::from(radius),
0.0,
std::f64::consts::PI * 2.0,
)
})?;

if fill {
self.call_cairo(|c| c.fill())?;
} else {
self.call_cairo(|c| c.stroke())?;
}
Ok(())
}

fn draw_text<'b>(
&mut self,
text: &str,
font: &FontDesc<'b>,
pos: BackendCoord,
color: &RGBAColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
let (mut x, mut y) = (pos.0, pos.1);

let degree = match font.get_transform() {
FontTransform::None => 0.0,
FontTransform::Rotate90 => 90.0,
FontTransform::Rotate180 => 180.0,
FontTransform::Rotate270 => 270.0,
} / 180.0
* std::f64::consts::PI;

if degree != 0.0 {
let layout = font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
self.call_cairo(|c| c.save())?;
let offset = font.get_transform().offset(layout);
self.call_cairo(|c| c.translate(f64::from(x + offset.0), f64::from(y + offset.1)))?;
self.call_cairo(|c| c.rotate(degree))?;
x = 0;
y = 0;
}

self.call_cairo(|c| {
c.select_font_face(font.get_name(), FontSlant::Normal, FontWeight::Normal)
})?;
self.call_cairo(|c| c.set_font_size(font.get_size()))?;
self.set_color(&color)?;
self.call_cairo(|c| c.move_to(f64::from(x), f64::from(y) + font.get_size()))?;
self.call_cairo(|c| c.show_text(text))?;

if degree != 0.0 {
self.call_cairo(|c| c.restore())?;
}
Ok(())
}
}
Loading

0 comments on commit 0eb7ff4

Please sign in to comment.