extern crate nanovg_sys as ffi;
use std::ops::Drop;
use std::path::Path as IoPath;
use std::ffi::{NulError, CString};
use std::os::raw::{c_int, c_float, c_uchar, c_char};
use std::{mem, ptr};
#[cfg(target_os = "windows")]
fn init_gl() -> Result<(), ()> {
if unsafe { ffi::gladLoadGL() } == 1 {
Ok(())
} else {
Err(())
}
}
#[cfg(not(target_os = "windows"))]
fn init_gl() -> Result<(), ()> {
Ok(())
}
#[cfg(feature = "gl3")]
fn create_gl(flags: ffi::NVGcreateFlags) -> *mut ffi::NVGcontext {
unsafe { ffi::nvgCreateGL3(flags.bits()) }
}
#[cfg(feature = "gl2")]
fn create_gl(flags: ffi::NVGcreateFlags) -> *mut ffi::NVGcontext {
unsafe { ffi::nvgCreateGL2(flags.bits()) }
}
#[cfg(feature = "gles3")]
fn create_gl(flags: ffi::NVGcreateFlags) -> *mut ffi::NVGcontext {
unsafe { ffi::nvgCreateGLES3(flags.bits()) }
}
#[cfg(feature = "gles2")]
fn create_gl(flags: ffi::NVGcreateFlags) -> *mut ffi::NVGcontext {
unsafe { ffi::nvgCreateGLES2(flags.bits()) }
}
#[cfg(not(any(feature = "gl3", feature = "gl2", feature = "gles3", feature = "gles2")))]
fn create_gl(flags: ffi::NVGcreateFlags) -> *mut ffi::NVGcontext {
panic!("Unable to determine the backend / implementation. Have you enabled one of the features?")
}
#[derive(Debug)]
pub struct ContextBuilder {
flags: ffi::NVGcreateFlags,
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
flags: ffi::NVGcreateFlags::empty(),
}
}
pub fn antialias(mut self) -> Self {
self.flags.insert(ffi::NVGcreateFlags::NVG_ANTIALIAS);
self
}
pub fn stencil_strokes(mut self) -> Self {
self.flags.insert(ffi::NVGcreateFlags::NVG_STENCIL_STROKES);
self
}
pub fn debug(mut self) -> Self {
self.flags.insert(ffi::NVGcreateFlags::NVG_DEBUG);
self
}
pub fn build(self) -> Result<Context, ()> {
init_gl()?;
let raw = create_gl(self.flags);
if !raw.is_null() {
Ok(Context(raw))
} else {
Err(())
}
}
}
#[derive(Debug)]
pub struct Context(*mut ffi::NVGcontext);
impl Context {
pub fn raw(&self) -> *mut ffi::NVGcontext {
self.0
}
pub fn frame<'a, F: FnOnce(Frame<'a>)>(
&'a self,
(width, height): (f32, f32),
device_pixel_ratio: f32,
handler: F,
) {
unsafe {
ffi::nvgBeginFrame(
self.raw(),
width as c_float,
height as c_float,
device_pixel_ratio as c_float,
);
}
{
let frame = Frame::new(self, Transform::new());
handler(frame);
}
unsafe {
ffi::nvgEndFrame(self.raw());
}
}
fn global_composite_operation(&self, operation: CompositeOperation) {
let ctx = self.raw();
match operation {
CompositeOperation::Basic(basic) => unsafe {
ffi::nvgGlobalCompositeOperation(ctx, basic.into_raw() as c_int);
},
CompositeOperation::BlendFunc {
source: src,
destination: dst,
} => unsafe {
ffi::nvgGlobalCompositeBlendFunc(ctx, src.into_raw().bits(), dst.into_raw().bits());
},
CompositeOperation::BlendFuncSeparate {
rgb_source: rs,
rgb_destination: rd,
alpha_source: als,
alpha_destination: ald,
} => unsafe {
let (rs, rd, als, ald) = (
rs.into_raw().bits(),
rd.into_raw().bits(),
als.into_raw().bits(),
ald.into_raw().bits(),
);
ffi::nvgGlobalCompositeBlendFuncSeparate(ctx, rs, rd, als, ald);
},
}
}
fn global_alpha(&self, alpha: f32) {
unsafe {
ffi::nvgGlobalAlpha(self.raw(), alpha as c_float);
}
}
fn scissor(&self, scissor: Option<Scissor>) {
match scissor {
Some(ref scissor) => {
self.with_applied_transform(scissor.transform,
|| unsafe {
ffi::nvgScissor(self.raw(), scissor.x, scissor.y, scissor.width, scissor.height);
}
);
},
None => unsafe {
ffi::nvgResetScissor(self.raw());
}
}
}
fn intersect(&self, intersect: &Intersect) {
self.scissor(Some(intersect.with));
self.with_applied_transform(intersect.transform,
|| unsafe {
ffi::nvgIntersectScissor(self.raw(), intersect.x, intersect.y, intersect.width, intersect.height);
}
);
}
fn clip(&self, clip: Clip) {
match clip {
Clip::Scissor(scissor) => self.scissor(Some(scissor)),
Clip::Intersect(ref intersect) => self.intersect(intersect),
Clip::None => (),
}
}
fn transform(&self, transform: Option<Transform>) {
match transform {
Some(ref transform) => {
let t = transform.matrix;
unsafe { ffi::nvgTransform(self.raw(), t[0], t[1], t[2], t[3], t[4], t[5]); }
},
None => unsafe { ffi::nvgResetTransform(self.raw()); }
}
}
fn current_transform(&self) -> Transform {
let mut current = Transform::new();
unsafe {
ffi::nvgCurrentTransform(self.raw(), current.matrix.as_mut_ptr());
}
current
}
fn with_applied_transform<F: FnOnce()>(&self, transform: Option<Transform>, handler: F) {
let current = self.current_transform();
if let Some(transform) = transform {
if transform.absolute {
self.transform(None);
}
self.transform(Some(transform));
}
handler();
self.transform(None);
self.transform(Some(current));
}
}
impl Drop for Context {
#[cfg(feature = "gl3")]
fn drop(&mut self) {
unsafe {
ffi::nvgDeleteGL3(self.0);
}
}
#[cfg(feature = "gl2")]
fn drop(&mut self) {
unsafe {
ffi::nvgDeleteGL2(self.0);
}
}
#[cfg(feature = "gles3")]
fn drop(&mut self) {
unsafe {
ffi::nvgDeleteGLES3(self.0);
}
}
#[cfg(feature = "gles2")]
fn drop(&mut self) {
unsafe {
ffi::nvgDeleteGLES2(self.0);
}
}
#[cfg(not(any(feature = "gl3", feature = "gl2", feature = "gles3", feature = "gles2")))]
fn drop(&mut self) {}
}
#[derive(Clone, Copy, Debug)]
pub struct Scissor {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub transform: Option<Transform>
}
#[derive(Clone, Copy, Debug)]
pub struct Intersect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub with: Scissor,
pub transform: Option<Transform>
}
#[derive(Clone, Copy, Debug)]
pub enum Clip {
Scissor(Scissor),
Intersect(Intersect),
None,
}
#[derive(Clone, Copy, Debug)]
pub struct PathOptions {
pub clip: Clip,
pub composite_operation: CompositeOperation,
pub alpha: f32,
pub transform: Option<Transform>,
}
impl Default for PathOptions {
fn default() -> Self {
Self {
clip: Clip::None,
composite_operation: CompositeOperation::Basic(BasicCompositeOperation::Atop),
alpha: 1.0,
transform: None,
}
}
}
#[derive(Debug)]
pub struct Frame<'a> {
context: &'a Context,
transform: Transform,
}
impl<'a> Frame<'a> {
fn new(context: &'a Context, transform: Transform) -> Self {
Self { context, transform }
}
pub fn context(&self) -> &'a Context {
self.context
}
pub fn transform(&self) -> &Transform {
&self.transform
}
pub fn transformed<'b, F: FnOnce(Frame<'b>)>(&'b mut self, transform: Transform, handler: F) {
let frame = Frame::new(self.context, transform * self.transform);
handler(frame);
}
pub fn path<F: FnOnce(Path)>(&self, handler: F, options: PathOptions) {
self.context.global_composite_operation(options.composite_operation);
self.context.global_alpha(options.alpha);
self.draw_prepare(options.clip, options.transform);
unsafe { ffi::nvgBeginPath(self.context.raw()); }
handler(Path::new(self));
}
fn draw_prepare(&self, clip: Clip, transform: Option<Transform>) {
self.context.transform(None);
self.context.scissor(None);
self.context.transform(Some(self.transform));
self.context.clip(clip);
if let Some(transform) = transform {
if transform.absolute {
self.context.transform(None);
}
self.context.transform(Some(transform));
}
}
fn text_prepare(&self, font: Font, options: TextOptions) {
unsafe {
ffi::nvgFontFaceId(self.context.raw(), font.id());
ffi::nvgFillColor(self.context.raw(), options.color.into_raw());
ffi::nvgFontSize(self.context.raw(), options.size);
ffi::nvgFontBlur(self.context.raw(), options.blur);
ffi::nvgTextLetterSpacing(self.context.raw(), options.letter_spacing);
ffi::nvgTextLineHeight(self.context.raw(), options.line_height);
ffi::nvgTextAlign(self.context.raw(), options.align.into_raw().bits());
}
}
pub fn text<S: AsRef<str>>(
&self,
font: Font,
(x, y): (f32, f32),
text: S,
options: TextOptions,
) {
let text = CString::new(text.as_ref()).unwrap();
self.text_prepare(font, options);
self.draw_prepare(options.clip, options.transform);
unsafe {
ffi::nvgText(self.context.raw(), x, y, text.as_ptr(), ptr::null());
}
}
pub fn text_box<S: AsRef<str>>(
&self,
font: Font,
(x, y): (f32, f32),
text: S,
options: TextOptions,
) {
let text = CString::new(text.as_ref()).unwrap();
self.text_prepare(font, options);
self.draw_prepare(options.clip, options.transform);
unsafe {
ffi::nvgTextBox(
self.context.raw(),
x,
y,
options.line_max_width,
text.as_ptr(),
ptr::null(),
);
}
}
pub fn text_bounds<S: AsRef<str>>(
&self,
font: Font,
(x, y): (f32, f32),
text: S,
options: TextOptions,
) -> (f32, TextBounds) {
let text = CString::new(text.as_ref()).unwrap();
self.text_prepare(font, options);
let mut bounds = [0.0f32; 4];
let measure = unsafe {
ffi::nvgTextBounds(
self.context.raw(),
x,
y,
text.as_ptr(),
ptr::null(),
bounds.as_mut_ptr(),
)
};
(measure, TextBounds::new(&bounds))
}
pub fn text_box_bounds<S: AsRef<str>>(
&self,
font: Font,
(x, y): (f32, f32),
text: S,
options: TextOptions,
) -> TextBounds {
let text = CString::new(text.as_ref()).unwrap();
self.text_prepare(font, options);
let mut bounds = [0.0f32; 4];
unsafe {
ffi::nvgTextBoxBounds(
self.context.raw(),
x,
y,
options.line_max_width,
text.as_ptr(),
ptr::null(),
bounds.as_mut_ptr(),
);
}
TextBounds::new(&bounds)
}
pub fn text_glyph_positions<S: AsRef<str>>(
&self,
(x, y): (f32, f32),
text: S,
) -> TextGlyphPositions {
TextGlyphPositions::new(
self.context,
x,
y,
CString::new(text.as_ref()).unwrap()
)
}
pub fn text_metrics(&self, font: Font, options: TextOptions) -> TextMetrics {
self.text_prepare(font, options);
let mut metrics = TextMetrics::new();
unsafe {
ffi::nvgTextMetrics(
self.context.raw(),
&mut metrics.ascender,
&mut metrics.descender,
&mut metrics.line_height
);
}
metrics
}
pub fn text_break_lines<S: AsRef<str>>(
&self,
text: S,
break_row_width: f32,
) -> TextBreakLines {
TextBreakLines::new(
self.context,
CString::new(text.as_ref()).unwrap(),
break_row_width
)
}
}
#[derive(Debug)]
pub struct Path<'a, 'b>
where
'b: 'a,
{
frame: &'a Frame<'b>,
}
impl<'a, 'b> Path<'a, 'b> {
fn new(frame: &'a Frame<'b>) -> Self {
Self { frame }
}
fn ctx(&self) -> *mut ffi::NVGcontext {
self.frame.context.raw()
}
pub fn context(&self) -> &'a Context {
self.frame.context()
}
pub fn fill<T: Paint>(&self, paint: T, options: FillOptions) {
let ctx = self.ctx();
unsafe {
ffi::nvgShapeAntiAlias(ctx, options.antialias as c_int);
paint.fill(self.context());
ffi::nvgFill(ctx);
}
}
pub fn stroke<T: Paint>(&self, paint: T, options: StrokeOptions) {
let ctx = self.ctx();
unsafe {
ffi::nvgShapeAntiAlias(ctx, options.antialias as c_int);
ffi::nvgStrokeWidth(ctx, options.width as c_float);
ffi::nvgLineCap(ctx, options.line_cap.into_raw() as c_int);
ffi::nvgLineJoin(ctx, options.line_join.into_raw() as c_int);
ffi::nvgMiterLimit(ctx, options.miter_limit as c_float);
paint.stroke(self.context());
ffi::nvgStroke(ctx);
}
}
pub fn arc(
&self,
(cx, cy): (f32, f32),
radius: f32,
start_angle: f32,
end_angle: f32,
winding: Winding,
) {
unsafe {
ffi::nvgArc(
self.ctx(),
cx,
cy,
radius,
start_angle,
end_angle,
winding.into_raw(),
);
}
}
pub fn rect(&self, (x, y): (f32, f32), (w, h): (f32, f32)) {
unsafe {
ffi::nvgRect(
self.ctx(),
x as c_float,
y as c_float,
w as c_float,
h as c_float,
);
}
}
pub fn rounded_rect(&self, (x, y): (f32, f32), (w, h): (f32, f32), radius: f32) {
unsafe {
ffi::nvgRoundedRect(self.ctx(), x, y, w, h, radius);
}
}
pub fn rounded_rect_varying(
&self,
(x, y): (f32, f32),
(w, h): (f32, f32),
top_radii: (f32, f32),
bottom_radii: (f32, f32),
) {
unsafe {
ffi::nvgRoundedRectVarying(
self.ctx(),
x,
y,
w,
h,
top_radii.0,
top_radii.1,
bottom_radii.1,
bottom_radii.0,
);
}
}
pub fn ellipse(&self, (cx, cy): (f32, f32), radius_x: f32, radius_y: f32) {
unsafe {
ffi::nvgEllipse(self.ctx(), cx, cy, radius_x, radius_y);
}
}
pub fn circle(&self, (cx, cy): (f32, f32), radius: f32) {
unsafe {
ffi::nvgCircle(self.ctx(), cx, cy, radius);
}
}
pub fn line_to(&self, (x, y): (f32, f32)) {
unsafe {
ffi::nvgLineTo(self.ctx(), x, y);
}
}
pub fn cubic_bezier_to(&self, (x, y): (f32, f32), control1: (f32, f32), control2: (f32, f32)) {
unsafe {
ffi::nvgBezierTo(
self.ctx(),
control1.0,
control1.1,
control2.0,
control2.1,
x,
y,
);
}
}
pub fn quad_bezier_to(&self, (x, y): (f32, f32), control: (f32, f32)) {
unsafe {
ffi::nvgQuadTo(self.ctx(), control.0, control.1, x, y);
}
}
pub fn arc_to(&self, p1: (f32, f32), p2: (f32, f32), radius: f32) {
unsafe {
ffi::nvgArcTo(self.ctx(), p1.0, p1.1, p2.0, p2.1, radius);
}
}
pub fn winding(&self, winding: Winding) {
unsafe {
ffi::nvgPathWinding(self.ctx(), winding.into_raw());
}
}
pub fn move_to(&self, (x, y): (f32, f32)) {
unsafe {
ffi::nvgMoveTo(self.ctx(), x, y);
}
}
pub fn close(&self) {
unsafe {
ffi::nvgClosePath(self.ctx());
}
}
}
#[derive(Debug)]
pub struct FillOptions {
pub antialias: bool,
}
impl Default for FillOptions {
fn default() -> Self {
Self {
antialias: true,
}
}
}
#[derive(Debug)]
pub struct StrokeOptions {
pub width: f32,
pub line_cap: LineCap,
pub line_join: LineJoin,
pub miter_limit: f32,
pub antialias: bool,
}
impl Default for StrokeOptions {
fn default() -> Self {
Self {
width: 1.0,
line_cap: LineCap::Butt,
line_join: LineJoin::Miter,
miter_limit: 10.0,
antialias: true,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum LineCap {
Butt,
Round,
Square,
}
impl LineCap {
fn into_raw(self) -> ffi::NVGlineCap {
match self {
LineCap::Butt => ffi::NVGlineCap::NVG_BUTT,
LineCap::Round => ffi::NVGlineCap::NVG_ROUND,
LineCap::Square => ffi::NVGlineCap::NVG_SQUARE,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum LineJoin {
Miter,
Round,
Bevel
}
impl LineJoin {
fn into_raw(self) -> ffi::NVGlineCap {
match self {
LineJoin::Miter => ffi::NVGlineCap::NVG_MITER,
LineJoin::Round => ffi::NVGlineCap::NVG_ROUND,
LineJoin::Bevel => ffi::NVGlineCap::NVG_BEVEL,
}
}
}
pub trait Paint {
fn fill(&self, context: &Context);
fn stroke(&self, context: &Context);
}
#[derive(Clone, Copy, Debug)]
pub struct Color(ffi::NVGcolor);
impl Color {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Color(ffi::NVGcolor { rgba: [r, g, b, a] })
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Color(unsafe {
ffi::nvgRGB(r as c_uchar, g as c_uchar, b as c_uchar)
})
}
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Color(unsafe {
ffi::nvgRGBA(r as c_uchar, g as c_uchar, b as c_uchar, a as c_uchar)
})
}
pub fn from_hsl(h: f32, s: f32, l: f32) -> Self {
Color(unsafe {
ffi::nvgHSL(h as c_float, s as c_float, l as c_float)
})
}
pub fn from_hsla(h: f32, s: f32, l: f32, a: u8) -> Self {
Color(unsafe {
ffi::nvgHSLA(h as c_float, s as c_float, l as c_float, a as c_uchar)
})
}
fn into_raw(self) -> ffi::NVGcolor {
self.0
}
pub fn red(&self) -> f32 {
self.0.rgba[0]
}
pub fn green(&self) -> f32 {
self.0.rgba[1]
}
pub fn blue(&self) -> f32 {
self.0.rgba[2]
}
pub fn alpha(&self) -> f32 {
self.0.rgba[3]
}
pub fn set_red(&mut self, red: f32) {
self.0.rgba[0] = red;
}
pub fn set_green(&mut self, green: f32) {
self.0.rgba[1] = green;
}
pub fn set_blue(&mut self, blue: f32) {
self.0.rgba[2] = blue;
}
pub fn set_alpha(&mut self, alpha: f32) {
self.0.rgba[3] = alpha;
}
pub fn lerp(a: Color, b: Color, t: f32) -> Color {
Color(unsafe {
ffi::nvgLerpRGBA(a.into_raw(), b.into_raw(), t as c_float)
})
}
}
impl Paint for Color {
fn fill(&self, context: &Context) {
unsafe {
ffi::nvgFillColor(context.raw(), self.into_raw());
}
}
fn stroke(&self, context: &Context) {
unsafe {
ffi::nvgStrokeColor(context.raw(), self.into_raw());
}
}
}
impl Paint for Gradient {
fn fill(&self, context: &Context) {
let raw = self.create_raw();
unsafe {
ffi::nvgFillPaint(context.raw(), raw);
}
}
fn stroke(&self, context: &Context) {
let raw = self.create_raw();
unsafe {
ffi::nvgStrokePaint(context.raw(), raw);
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Gradient {
Linear {
start: (f32, f32),
end: (f32, f32),
start_color: Color,
end_color: Color,
},
Box {
position: (f32, f32),
size: (f32, f32),
radius: f32,
feather: f32,
start_color: Color,
end_color: Color,
},
Radial {
center: (f32, f32),
inner_radius: f32,
outer_radius: f32,
start_color: Color,
end_color: Color,
}
}
impl Gradient {
fn create_raw(&self) -> ffi::NVGpaint {
match self {
&Gradient::Linear {
start,
end,
start_color,
end_color,
} => {
let (sx, sy) = start;
let (ex, ey) = end;
unsafe {
ffi::nvgLinearGradient(
ptr::null_mut(),
sx,
sy,
ex,
ey,
start_color.into_raw(),
end_color.into_raw(),
)
}
},
&Gradient::Box {
position,
size,
radius,
feather,
start_color,
end_color,
} => {
unsafe {
let (x, y) = position;
let (w, h) = size;
ffi::nvgBoxGradient(
ptr::null_mut(),
x,
y,
w,
h,
radius,
feather,
start_color.into_raw(),
end_color.into_raw(),
)
}
},
&Gradient::Radial {
center,
inner_radius,
outer_radius,
start_color,
end_color,
} => {
unsafe {
let (cx, cy) = center;
ffi::nvgRadialGradient(
ptr::null_mut(),
cx,
cy,
inner_radius,
outer_radius,
start_color.into_raw(),
end_color.into_raw(),
)
}
},
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ImagePattern<'a> {
pub image: &'a Image<'a>,
pub origin: (f32, f32),
pub size: (f32, f32),
pub angle: f32,
pub alpha: f32,
}
impl<'a> ImagePattern<'a> {
fn create_raw(&self) -> ffi::NVGpaint {
let (ox, oy) = self.origin;
let (ex, ey) = self.size;
unsafe {
ffi::nvgImagePattern(ptr::null_mut(), ox, oy, ex, ey, self.angle, self.image.raw(), self.alpha)
}
}
}
impl<'a> Paint for ImagePattern<'a> {
fn fill(&self, context: &Context) {
let raw = self.create_raw();
unsafe {
ffi::nvgFillPaint(context.raw(), raw);
}
}
fn stroke(&self, context: &Context) {
let raw = self.create_raw();
unsafe {
ffi::nvgStrokePaint(context.raw(), raw);
}
}
}
#[derive(Debug)]
pub struct ImageBuilder<'a> {
context: &'a Context,
flags: ffi::NVGimageFlags,
}
impl<'a> ImageBuilder<'a> {
fn new(context: &'a Context) -> Self {
Self {
context,
flags: ffi::NVGimageFlags::empty(),
}
}
pub fn context(&self) -> &'a Context {
self.context
}
pub fn mipmaps(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_GENERATE_MIPMAPS;
self
}
pub fn repeat_x(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_REPEATX;
self
}
pub fn repeat_y(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_REPEATY;
self
}
pub fn flipy(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_FLIPY;
self
}
pub fn premultiplied(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_PREMULTIPLIED;
self
}
pub fn nearest(mut self) -> Self {
self.flags |= ffi::NVGimageFlags::NVG_IMAGE_NEAREST;
self
}
pub fn build_from_file<P: AsRef<IoPath>>(self, file: P) -> ImageBuilderResult<'a> {
let path = match file.as_ref().to_str() {
Some(p) => CString::new(p.to_owned())?,
None => return Err(ImageBuilderError::CStringError),
};
let handle =
unsafe { ffi::nvgCreateImage(self.context.raw(), (*path).as_ptr(), self.flags.bits()) };
if handle > 0 {
Ok(Image(self.context, handle))
} else {
Err(ImageBuilderError::CreateImageFailed)
}
}
pub fn build_from_memory(self, data: &[u8]) -> ImageBuilderResult<'a> {
let handle = unsafe {
ffi::nvgCreateImageMem(
self.context.raw(),
self.flags.bits(),
data.as_ptr() as *mut _,
data.len() as c_int,
)
};
if handle > 0 {
Ok(Image(self.context, handle))
} else {
Err(ImageBuilderError::CreateImageFailed)
}
}
pub fn build_from_rgba(
self,
width: usize,
height: usize,
data: &[u32],
) -> ImageBuilderResult<'a> {
if data.len() < width * height {
return Err(ImageBuilderError::NotEnoughData);
}
let handle = unsafe {
ffi::nvgCreateImageRGBA(
self.context.raw(),
width as c_int,
height as c_int,
self.flags.bits(),
data.as_ptr() as *const _,
)
};
if handle > 0 {
Ok(Image(self.context, handle))
} else {
Err(ImageBuilderError::CreateImageFailed)
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum ImageBuilderError {
CStringError,
CreateImageFailed,
NotEnoughData,
}
impl From<NulError> for ImageBuilderError {
fn from(_: NulError) -> Self {
ImageBuilderError::CStringError
}
}
pub type ImageBuilderResult<'a> = Result<Image<'a>, ImageBuilderError>;
#[derive(Debug)]
pub struct Image<'a>(&'a Context, c_int);
impl<'a> Image<'a> {
pub fn new(context: &'a Context) -> ImageBuilder {
ImageBuilder::new(context)
}
pub fn context(&self) -> &'a Context {
self.0
}
pub fn size(&self) -> (usize, usize) {
let (mut w, mut h): (c_int, c_int) = (0, 0);
unsafe {
ffi::nvgImageSize(
self.ctx().raw(),
self.raw(),
&mut w as *mut _,
&mut h as *mut _,
);
}
(w as usize, h as usize)
}
pub fn update(&mut self, data: &[u32]) {
unsafe {
ffi::nvgUpdateImage(self.ctx().raw(), self.raw(), data.as_ptr() as *const _);
}
}
fn ctx(&self) -> &Context {
self.0
}
fn raw(&self) -> c_int {
self.1
}
}
impl<'a> Drop for Image<'a> {
fn drop(&mut self) {
unsafe {
ffi::nvgDeleteImage(self.ctx().raw(), self.raw());
}
self.1 = 0;
}
}
#[derive(Copy, Clone, Debug)]
pub enum Direction {
Clockwise,
CounterClockwise,
}
impl Direction {
fn into_raw(self) -> ffi::NVGwinding {
match self {
Direction::Clockwise => ffi::NVGwinding::NVG_CW,
Direction::CounterClockwise => ffi::NVGwinding::NVG_CCW,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Solidity {
Hole,
Solid,
}
impl Solidity {
fn into_raw(self) -> ffi::NVGsolidity {
match self {
Solidity::Hole => ffi::NVGsolidity::NVG_HOLE,
Solidity::Solid => ffi::NVGsolidity::NVG_SOLID,
}
}
}
#[derive(Debug)]
pub enum Winding {
Direction(Direction),
Solidity(Solidity),
}
impl Winding {
fn into_raw(self) -> c_int {
match self {
Winding::Direction(direction) => direction.into_raw().bits(),
Winding::Solidity(solidity) => solidity.into_raw().bits(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum CompositeOperation {
Basic(BasicCompositeOperation),
BlendFunc {
source: BlendFactor,
destination: BlendFactor,
},
BlendFuncSeparate {
rgb_source: BlendFactor,
rgb_destination: BlendFactor,
alpha_source: BlendFactor,
alpha_destination: BlendFactor,
},
}
#[derive(Copy, Clone, Debug)]
pub enum BasicCompositeOperation {
SourceOver,
SourceIn,
SourceOut,
Atop,
DestinationOver,
DestinationIn,
DestinationOut,
DestinationAtop,
Lighter,
Copy,
Xor,
}
impl BasicCompositeOperation {
fn into_raw(self) -> ffi::NVGcompositeOperation {
use BasicCompositeOperation::*;
use ffi::NVGcompositeOperation::*;
match self {
SourceOver => NVG_SOURCE_OVER,
SourceIn => NVG_SOURCE_IN,
SourceOut => NVG_SOURCE_OUT,
Atop => NVG_ATOP,
DestinationOver => NVG_DESTINATION_OVER,
DestinationIn => NVG_DESTINATION_IN,
DestinationOut => NVG_DESTINATION_OUT,
DestinationAtop => NVG_DESTINATION_ATOP,
Lighter => NVG_LIGHTER,
Copy => NVG_COPY,
Xor => NVG_XOR,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum BlendFactor {
Zero,
One,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
SourceAlphaSaturate,
}
impl BlendFactor {
fn into_raw(self) -> ffi::NVGblendFactor {
use BlendFactor::*;
match self {
Zero => ffi::NVGblendFactor::NVG_ZERO,
One => ffi::NVGblendFactor::NVG_ONE,
SourceColor => ffi::NVGblendFactor::NVG_SRC_COLOR,
OneMinusSourceColor => ffi::NVGblendFactor::NVG_ONE_MINUS_SRC_COLOR,
DestinationColor => ffi::NVGblendFactor::NVG_DST_COLOR,
OneMinusDestinationColor => ffi::NVGblendFactor::NVG_ONE_MINUS_DST_COLOR,
SourceAlpha => ffi::NVGblendFactor::NVG_SRC_ALPHA,
OneMinusSourceAlpha => ffi::NVGblendFactor::NVG_ONE_MINUS_SRC_ALPHA,
DestinationAlpha => ffi::NVGblendFactor::NVG_DST_ALPHA,
OneMinusDestinationAlpha => ffi::NVGblendFactor::NVG_ONE_MINUS_DST_ALPHA,
SourceAlphaSaturate => ffi::NVGblendFactor::NVG_SRC_ALPHA_SATURATE,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Font<'a>(&'a Context, c_int);
#[derive(Debug)]
pub enum CreateFontError {
CStringError,
InvalidPath,
InvalidHandle,
}
impl From<NulError> for CreateFontError {
fn from(_: NulError) -> Self {
CreateFontError::CStringError
}
}
pub type CreateFontResult<'a> = Result<Font<'a>, CreateFontError>;
impl<'a> Font<'a> {
fn ctx(&self) -> *mut ffi::NVGcontext {
self.0.raw()
}
fn id(&self) -> c_int {
self.1
}
pub fn context(&self) -> &'a Context {
self.0
}
pub fn from_file<S: AsRef<str>, P: AsRef<IoPath>>(
context: &'a Context,
name: S,
path: P,
) -> CreateFontResult {
let name = CString::new(name.as_ref())?;
let path = CString::new(path.as_ref().to_str().ok_or(CreateFontError::InvalidPath)?)?;
let handle = unsafe {
ffi::nvgCreateFont(context.raw(), name.as_ptr(), path.as_ptr())
};
if handle > ffi::FONS_INVALID {
Ok(Font(context, handle))
} else {
Err(CreateFontError::InvalidHandle)
}
}
pub fn from_memory<'b, S: AsRef<str>>(
context: &'a Context,
name: S,
memory: &'b [u8],
) -> CreateFontResult<'a> {
let name = CString::new(name.as_ref())?;
let handle = unsafe {
ffi::nvgCreateFontMem(
context.raw(),
name.as_ptr(),
memory.as_ptr() as *mut _,
memory.len() as c_int,
0,
)
};
if handle > ffi::FONS_INVALID {
Ok(Font(context, handle))
} else {
Err(CreateFontError::InvalidHandle)
}
}
pub fn find<S: AsRef<str>>(context: &'a Context, name: S) -> CreateFontResult {
let name = CString::new(name.as_ref())?;
let handle = unsafe {
ffi::nvgFindFont(context.raw(), name.as_ptr())
};
if handle > ffi::FONS_INVALID {
Ok(Font(context, handle))
} else {
Err(CreateFontError::InvalidHandle)
}
}
pub fn add_fallback(&self, fallback: Font) -> bool {
let res = unsafe { ffi::nvgAddFallbackFontId(self.ctx(), self.id(), fallback.id()) };
res != 0
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextOptions {
pub size: f32,
pub blur: f32,
pub letter_spacing: f32,
pub line_height: f32,
pub line_max_width: f32,
pub align: Alignment,
pub color: Color,
pub clip: Clip,
pub transform: Option<Transform>,
}
impl Default for TextOptions {
fn default() -> Self {
Self {
size: 12.0,
blur: 0.0,
letter_spacing: 0.0,
line_height: 1.0,
line_max_width: std::f32::MAX,
align: Alignment::new(),
color: Color::new(0.0, 0.0, 0.0, 0.0),
clip: Clip::None,
transform: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextBounds {
pub min_x: f32,
pub min_y: f32,
pub max_x: f32,
pub max_y: f32,
}
impl TextBounds {
fn new(bounds: &[f32; 4]) -> TextBounds {
TextBounds {
min_x: bounds[0],
min_y: bounds[1],
max_x: bounds[2],
max_y: bounds[3],
}
}
}
pub struct TextGlyphPositions<'a> {
context: &'a Context,
x: f32,
y: f32,
start: *const c_char,
glyphs: [ffi::NVGglyphPosition; 2],
}
impl<'a> TextGlyphPositions<'a> {
fn new(context: &'a Context, x: f32, y: f32, text: CString) -> TextGlyphPositions<'a> {
TextGlyphPositions {
context: context,
x: x,
y: y,
start: text.into_raw(),
glyphs: [unsafe { mem::zeroed() }; 2]
}
}
}
impl<'a> Iterator for TextGlyphPositions<'a> {
type Item = GlyphPosition;
fn next(&mut self) -> Option<Self::Item> {
let num_glyphs = unsafe {
ffi::nvgTextGlyphPositions(
self.context.raw(),
self.x,
self.y,
self.start,
ptr::null(),
self.glyphs.as_mut_ptr(),
2
)
};
match num_glyphs {
1 => {
self.start = &('\0' as c_char);
Some(GlyphPosition::new(&self.glyphs[0]))
},
2 => {
self.x = self.glyphs[1].x;
self.start = self.glyphs[1].s;
Some(GlyphPosition::new(&self.glyphs[0]))
},
_ => None
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextRow<'a> {
pub width: f32,
pub min_x: f32,
pub max_x: f32,
pub text: &'a str,
}
impl<'a> TextRow<'a> {
fn new(row: &ffi::NVGtextRow, text: &'a str) -> TextRow<'a> {
TextRow {
width: row.width,
min_x: row.minx,
max_x: row.maxx,
text: text,
}
}
}
#[derive(Debug)]
pub struct TextBreakLines<'a> {
context: &'a Context,
start: *const c_char,
break_row_width: f32,
row: ffi::NVGtextRow,
}
impl<'a> TextBreakLines<'a> {
fn new(context: &'a Context, text: CString, break_row_width: f32) -> TextBreakLines<'a> {
TextBreakLines {
context: context,
start: text.into_raw(),
break_row_width: break_row_width,
row: unsafe { mem::zeroed() },
}
}
}
impl<'a> Iterator for TextBreakLines<'a> {
type Item = TextRow<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let nrows = ffi::nvgTextBreakLines(self.context.raw(), self.start, ptr::null(), self.break_row_width, &mut self.row, 1);
self.start = self.row.next;
if nrows > 0 {
let string_length = self.row.end as usize - self.row.start as usize;
let string_slice = std::slice::from_raw_parts(self.row.start as *const u8, string_length);
let text_str = std::str::from_utf8(string_slice).unwrap();
Some(TextRow::new(&self.row, text_str))
} else {
None
}
}
}
}
#[derive(Clone, Debug)]
pub struct GlyphPosition {
pub x: f32,
pub min_x: f32,
pub max_x: f32,
}
impl GlyphPosition {
fn new(glyph: &ffi::NVGglyphPosition) -> GlyphPosition {
GlyphPosition {
x: glyph.x,
min_x: glyph.minx,
max_x: glyph.maxx,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextMetrics {
pub ascender: f32,
pub descender: f32,
pub line_height: f32,
}
impl TextMetrics {
fn new() -> TextMetrics {
TextMetrics {
ascender: 0.0,
descender: 0.0,
line_height: 0.0,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Alignment(ffi::NVGalign);
impl Alignment {
fn into_raw(self) -> ffi::NVGalign {
self.0
}
pub fn new() -> Self {
Alignment(ffi::NVGalign::empty()).top().left()
}
pub fn left(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_RIGHT);
self.0.remove(ffi::NVGalign::NVG_ALIGN_CENTER);
self.0.insert(ffi::NVGalign::NVG_ALIGN_LEFT);
self
}
pub fn center(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_LEFT);
self.0.remove(ffi::NVGalign::NVG_ALIGN_RIGHT);
self.0.insert(ffi::NVGalign::NVG_ALIGN_CENTER);
self
}
pub fn right(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_LEFT);
self.0.remove(ffi::NVGalign::NVG_ALIGN_CENTER);
self.0.insert(ffi::NVGalign::NVG_ALIGN_RIGHT);
self
}
pub fn top(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_BOTTOM);
self.0.remove(ffi::NVGalign::NVG_ALIGN_MIDDLE);
self.0.remove(ffi::NVGalign::NVG_ALIGN_BASELINE);
self.0.insert(ffi::NVGalign::NVG_ALIGN_TOP);
self
}
pub fn middle(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_TOP);
self.0.remove(ffi::NVGalign::NVG_ALIGN_BOTTOM);
self.0.remove(ffi::NVGalign::NVG_ALIGN_BASELINE);
self.0.insert(ffi::NVGalign::NVG_ALIGN_MIDDLE);
self
}
pub fn bottom(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_TOP);
self.0.remove(ffi::NVGalign::NVG_ALIGN_MIDDLE);
self.0.remove(ffi::NVGalign::NVG_ALIGN_BASELINE);
self.0.insert(ffi::NVGalign::NVG_ALIGN_BOTTOM);
self
}
pub fn baseline(mut self) -> Self {
self.0.remove(ffi::NVGalign::NVG_ALIGN_TOP);
self.0.remove(ffi::NVGalign::NVG_ALIGN_MIDDLE);
self.0.remove(ffi::NVGalign::NVG_ALIGN_BOTTOM);
self.0.insert(ffi::NVGalign::NVG_ALIGN_BASELINE);
self
}
}
#[derive(Clone, Copy, Debug)]
pub struct Transform {
pub matrix: [f32; 6],
absolute: bool,
}
impl Transform {
pub fn new() -> Self {
Self {
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
absolute: false,
}
}
pub fn absolute(mut self) -> Self {
self.absolute = true;
self
}
pub fn relative(mut self) -> Self {
self.absolute = false;
self
}
pub fn with_translation(self, x: f32, y: f32) -> Self {
let mut new = self.clone();
new.matrix[4] = x;
new.matrix[5] = y;
new
}
pub fn with_scale(self, x: f32, y: f32) -> Self {
let mut new = self.clone();
new.matrix[0] = x;
new.matrix[3] = y;
new
}
pub fn with_skew(self, x: f32, y: f32) -> Self {
let mut new = self.clone();
new.matrix[2] = x;
new.matrix[1] = y;
new
}
pub fn with_rotation(self, theta: f32) -> Self {
let mut new = self.clone();
new.matrix[0] = theta.cos();
new.matrix[2] = -theta.sin();
new.matrix[1] = theta.sin();
new.matrix[3] = theta.cos();
new
}
pub fn translate(self, x: f32, y: f32) -> Self {
let mut new = self.clone();
let mut t = [0.0f32; 6];
unsafe {
ffi::nvgTransformTranslate(t.as_mut_ptr(), x, y);
ffi::nvgTransformPremultiply(new.matrix.as_mut_ptr(), t.as_mut_ptr());
}
new
}
pub fn rotate(self, angle: f32) -> Self {
let mut new = self.clone();
let mut t = [0.0f32; 6];
unsafe {
ffi::nvgTransformRotate(t.as_mut_ptr(), angle);
ffi::nvgTransformPremultiply(new.matrix.as_mut_ptr(), t.as_mut_ptr());
}
new
}
pub fn skew_x(self, angle: f32) -> Self {
let mut new = self.clone();
let mut t = [0.0f32; 6];
unsafe {
ffi::nvgTransformSkewX(t.as_mut_ptr(), angle);
ffi::nvgTransformPremultiply(new.matrix.as_mut_ptr(), t.as_mut_ptr());
}
new
}
pub fn skew_y(self, angle: f32) -> Self {
let mut new = self.clone();
let mut t = [0.0f32; 6];
unsafe {
ffi::nvgTransformSkewY(t.as_mut_ptr(), angle);
ffi::nvgTransformPremultiply(new.matrix.as_mut_ptr(), t.as_mut_ptr());
}
new
}
pub fn scale(self, x: f32, y: f32) -> Self {
let mut new = self.clone();
let mut t = [0.0f32; 6];
unsafe {
ffi::nvgTransformScale(t.as_mut_ptr(), x, y);
ffi::nvgTransformPremultiply(new.matrix.as_mut_ptr(), t.as_mut_ptr());
}
new
}
pub fn transform_point(&self, (x, y): (f32, f32)) -> (f32, f32) {
let mut transformed = (0.0f32, 0.0f32);
unsafe {
ffi::nvgTransformPoint(&mut transformed.0, &mut transformed.1, self.matrix.as_ptr(), x, y);
}
transformed
}
pub fn try_inverse(&self) -> Option<Transform> {
let mut inv = Transform::new();
let result = unsafe {
ffi::nvgTransformInverse(inv.matrix.as_mut_ptr(), self.matrix.as_ptr())
};
if result == 1 {
Some(inv)
}
else {
None
}
}
}
impl std::ops::Mul for Transform {
type Output = Transform;
fn mul(self, rhs: Transform) -> Self::Output {
let mut result = self.clone();
unsafe {
ffi::nvgTransformMultiply(result.matrix.as_mut_ptr(), rhs.matrix.as_ptr());
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! trans_eq_bool {
($t1:expr, $t2:expr) => {
f32_eq!($t1.matrix[0], $t2.matrix[0]) &&
f32_eq!($t1.matrix[1], $t2.matrix[1]) &&
f32_eq!($t1.matrix[2], $t2.matrix[2]) &&
f32_eq!($t1.matrix[3], $t2.matrix[3]) &&
f32_eq!($t1.matrix[4], $t2.matrix[4]) &&
f32_eq!($t1.matrix[5], $t2.matrix[5])
};
}
macro_rules! trans_eq {
($t1:expr, $t2:expr) => {
assert!(trans_eq_bool!($t1, $t2))
};
}
macro_rules! trans_not_eq {
($t1:expr, $t2:expr) => {
assert!(!trans_eq_bool!($t1, $t2))
};
}
#[test]
fn test_transform() {
trans_eq!(Transform::new(), Transform {
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
});
trans_eq!(Transform::new().with_translation(11.1, 22.2), Transform {
matrix: [1.0, 0.0, 0.0, 1.0, 11.1, 22.2],
});
trans_eq!(Transform::new().with_scale(11.1, 22.2), Transform {
matrix: [11.1, 0.0, 0.0, 22.2, 0.0, 0.0],
});
trans_eq!(Transform::new().with_skew(11.1, 22.2), Transform {
matrix: [1.0, 22.2, 11.1, 1.0, 0.0, 0.0],
});
let angle = 90f32.to_radians();
trans_eq!(Transform::new().with_rotation(angle), Transform {
matrix: [angle.cos(), angle.sin(), -angle.sin(), angle.cos(), 0.0, 0.0],
});
let identity = Transform::new();
let trans = Transform::new().with_translation(10.0, 20.0);
trans_eq!(identity * trans, trans);
trans_eq!(trans * identity, trans);
trans_eq!(identity * identity, identity);
let a = Transform::new().with_rotation(123.0);
let b = Transform::new().with_skew(66.6, 1337.2);
trans_not_eq!(a * b, b * a);
}
}