extendr_api/graphics/
mod.rs

1// ## Resources for Developers
2//
3// Graphic device is documented in the R-internals. The header file also
4// contains the useful information. The code of the graphics package is also useful to
5// see what values are used by default (i.e. `GInit`).
6//
7// - https://cran.r-project.org/doc/manuals/r-devel/R-ints.html
8// - https://github.com/wch/r-source/blob/trunk/src/include/R_ext/GraphicsDevice.h
9// - https://github.com/wch/r-source/blob/trunk/src/library/graphics/src/graphics.c
10//
11// While the documents are good, we need to refer to the real implementaions to
12// find hints.
13//
14// - postscript device: https://github.com/wch/r-source/blob/trunk/src/library/grDevices/src/devPS.c
15// - svglite package: https://github.com/r-lib/svglite/blob/main/src/devSVG.cpp
16// - devout package: https://github.com/coolbutuseless/devout/blob/master/src/rdevice.cpp
17//
18// For newer features, the blog posts by Paul Murrell might be helpful:
19//
20// - https://developer.r-project.org/Blog/public/2020/07/15/new-features-in-the-r-graphics-engine/index.html
21// - https://developer.r-project.org/Blog/public/2021/12/06/groups-and-paths-and-masks-in-r-graphics/index.html
22// - https://developer.r-project.org/Blog/public/2021/12/14/updating-graphics-devices-for-r-4.2.0/index.html
23
24//! Graphic Device Operations
25//!
26//! ## Control an existing graphic device
27//!
28//! TODO
29//!
30//! ## Implement a new graphic device
31//!
32//! The following two things are needed to implement a graphic device.
33//!
34//! - [DeviceDriver] trait: the actual implementation of graphic device methods.
35//! - [DeviceDescriptor] struct: the parameters that might differ per device
36//!   instance (e.g. sizes, and colors).
37//!
38//! For example, the following code implements a simple graphic device that shows a message when it's
39//! activated (and ignores everything else).
40//!
41//! ```
42//! use extendr_api::{
43//!     graphics::{DeviceDescriptor, DeviceDriver, DevDesc},
44//!     prelude::*,
45//! };
46//!
47//! struct MyDevice<'a> {
48//!     welcome_message: &'a str,
49//! }
50//!
51//! impl<'a> DeviceDriver for MyDevice<'a> {
52//!     fn activate(&mut self, _dd: DevDesc) {
53//!         let welcome_message = self.welcome_message;
54//!         rprintln!("message from device: {welcome_message}");
55//!     }
56//! }
57//!
58//! /// Create a new device.
59//! ///
60//! /// @export
61//! #[extendr]
62//! fn my_device(welcome_message: String) {
63//!     let device_driver = MyDevice {
64//!         welcome_message: welcome_message.as_str(),
65//!     };
66//!
67//!     let device_descriptor = DeviceDescriptor::new();
68//!     let device = device_driver.create_device::<MyDevice>(device_descriptor, "my device");
69//! }
70//! ```
71//!
72//! This can be called from R.
73//!
74//! ```r
75//! my_device("I'm so active!!!")
76//! #> message from device: I'm so active!!!
77//! ```
78
79use crate::*;
80
81// These are used in the callback functions.
82pub use extendr_ffi::{cetype_t, graphics::*, R_NilValue, Rf_NoDevices, Rf_NumDevices};
83
84pub mod color;
85pub mod device_descriptor;
86pub mod device_driver;
87
88use color::Color;
89pub use device_descriptor::*;
90pub use device_driver::*;
91
92pub struct Context {
93    context: R_GE_gcontext,
94    xscale: (f64, f64),
95    yscale: (f64, f64),
96    offset: (f64, f64),
97    scalar: f64,
98}
99
100#[derive(Clone, Debug, PartialEq)]
101pub struct Device {
102    inner: pGEDevDesc,
103}
104
105#[derive(Clone, Debug, PartialEq)]
106pub struct Pattern {
107    inner: Robj,
108}
109
110#[derive(Clone, Debug, PartialEq)]
111pub struct TextMetric {
112    pub ascent: f64,
113    pub descent: f64,
114    pub width: f64,
115}
116
117/// A row-major array of pixels. One pixel is 32-bit, whose each byte represents
118/// alpha, blue, green, and red in the order.
119#[derive(Clone, Debug, PartialEq)]
120pub struct Raster<P: AsRef<[u32]>> {
121    pub pixels: P,
122    pub width: usize,
123}
124
125impl Device {
126    pub(crate) fn inner(&self) -> pGEDevDesc {
127        self.inner
128    }
129
130    // pub(crate) fn asref(&self) -> &GEDevDesc {
131    //     unsafe { &*self.inner }
132    // }
133
134    // pub(crate) fn dev(&self) -> &DevDesc {
135    //     unsafe { &*self.asref().dev }
136    // }
137}
138
139#[derive(PartialEq, Debug, Clone)]
140pub enum LineEnd {
141    Round,
142    Butt,
143    Square,
144}
145
146#[derive(PartialEq, Debug, Clone)]
147pub enum LineJoin {
148    Round,
149    Mitre,
150    Bevel,
151}
152
153#[derive(PartialEq, Debug, Clone)]
154pub enum LineType {
155    Blank,
156    Solid,
157    Dashed,
158    Dotted,
159    DotDash,
160    LongDash,
161    TwoDash,
162}
163
164#[derive(PartialEq, Debug, Clone)]
165pub enum Unit {
166    Device,
167    Normalized,
168    Inches,
169    CM,
170}
171
172#[derive(PartialEq, Debug, Clone)]
173pub enum FontFace {
174    Plain,
175    Bold,
176    Italic,
177    BoldItalic,
178    Symbol,
179}
180
181impl From<LineEnd> for R_GE_lineend {
182    fn from(value: LineEnd) -> Self {
183        match value {
184            LineEnd::Round => Self::GE_ROUND_CAP,
185            LineEnd::Butt => Self::GE_BUTT_CAP,
186            LineEnd::Square => Self::GE_SQUARE_CAP,
187        }
188    }
189}
190
191impl From<LineJoin> for R_GE_linejoin {
192    fn from(value: LineJoin) -> Self {
193        match value {
194            LineJoin::Round => Self::GE_ROUND_JOIN,
195            LineJoin::Mitre => Self::GE_MITRE_JOIN,
196            LineJoin::Bevel => Self::GE_BEVEL_JOIN,
197        }
198    }
199}
200
201impl LineType {
202    fn to_i32(&self) -> i32 {
203        match self {
204            Self::Blank => LTY_BLANK as _,
205            Self::Solid => LTY_SOLID as _,
206            Self::Dashed => LTY_DASHED as _,
207            Self::Dotted => LTY_DOTTED as _,
208            Self::DotDash => LTY_DOTDASH as _,
209            Self::LongDash => LTY_LONGDASH as _,
210            Self::TwoDash => LTY_TWODASH as _,
211        }
212    }
213}
214
215impl FontFace {
216    fn to_i32(&self) -> i32 {
217        match self {
218            Self::Plain => 1,
219            Self::Bold => 2,
220            Self::Italic => 3,
221            Self::BoldItalic => 4,
222            Self::Symbol => 5,
223        }
224    }
225}
226
227fn unit_to_ge(unit: Unit) -> GEUnit {
228    match unit {
229        Unit::Device => GEUnit::GE_DEVICE,
230        Unit::Normalized => GEUnit::GE_NDC,
231        Unit::Inches => GEUnit::GE_INCHES,
232        Unit::CM => GEUnit::GE_CM,
233    }
234}
235
236impl Context {
237    pub fn from_device(dev: &Device, unit: Unit) -> Self {
238        #[allow(unused_unsafe)]
239        unsafe {
240            let offset = dev.to_device_coords((0., 0.), unit.clone());
241            let mut xscale = dev.to_device_coords((1., 0.), unit.clone());
242            let mut yscale = dev.to_device_coords((0., 1.), unit);
243            xscale.0 -= offset.0;
244            xscale.1 -= offset.1;
245            yscale.0 -= offset.0;
246            yscale.1 -= offset.1;
247
248            // sqrt(abs(det(m)))
249            let scalar = (xscale.0 * yscale.1 - xscale.1 * yscale.0).abs().sqrt();
250
251            let mut context = R_GE_gcontext {
252                col: Color::rgb(0xff, 0xff, 0xff).to_i32(),
253                fill: Color::rgb(0xc0, 0xc0, 0xc0).to_i32(),
254                gamma: 1.0,
255                lwd: 1.0,
256                lty: 0,
257                lend: R_GE_lineend::GE_ROUND_CAP,
258                ljoin: R_GE_linejoin::GE_ROUND_JOIN,
259                lmitre: 10.0,
260                cex: 1.0,
261                ps: 14.0,
262                lineheight: 1.0,
263                fontface: 1,
264                fontfamily: [0; 201],
265
266                #[cfg(use_r_ge_version_14)]
267                patternFill: R_NilValue,
268            };
269
270            context
271                .fontfamily
272                .iter_mut()
273                .zip(b"Helvetica".iter())
274                .for_each(|(d, s)| *d = *s as i8);
275
276            Self {
277                context,
278                xscale,
279                yscale,
280                offset,
281                scalar,
282            }
283        }
284    }
285
286    /// Set the line or text color of a primitive.
287    pub fn color(&mut self, col: Color) -> &mut Self {
288        self.context.col = col.to_i32();
289        self
290    }
291
292    /// Set the fill color of a primitive.
293    pub fn fill(&mut self, fill: Color) -> &mut Self {
294        self.context.fill = fill.to_i32();
295        self
296    }
297
298    /// Set the gamma of the device. `out_color = in_color ** gamma`
299    pub fn gamma(&mut self, gamma: f64) -> &mut Self {
300        self.context.gamma = gamma;
301        self
302    }
303
304    /// Set the width of the line in chosen units.
305    pub fn line_width(&mut self, lwd: f64) -> &mut Self {
306        self.context.lwd = (lwd * self.scalar).max(1.0);
307        self
308    }
309
310    /// Set the type of the line.
311    /// ```ignore
312    /// Blank    => <invisible>
313    /// Solid    => ------
314    /// Dashed   => - - - -
315    /// Dotted   => . . . .
316    /// DotDash  => . - . -
317    /// LongDash => --  --
318    /// TwoDash  => . . - -
319    /// ```
320    pub fn line_type(&mut self, lty: LineType) -> &mut Self {
321        self.context.lty = lty.to_i32();
322        self
323    }
324
325    /// Set the line end type.
326    /// ```ignore
327    ///   LineEnd::RoundCap
328    ///   LineEnd::ButtCap
329    ///   LineEnd::SquareCap
330    /// ```
331    pub fn line_end(&mut self, lend: LineEnd) -> &mut Self {
332        self.context.lend = lend.into();
333        self
334    }
335
336    /// Set the line join type.
337    /// ```ignore
338    ///   LineJoin::RoundJoin
339    ///   LineJoin::MitreJoin
340    ///   LineJoin::BevelJoin
341    /// ```
342    pub fn line_join(&mut self, ljoin: LineJoin) -> &mut Self {
343        self.context.ljoin = ljoin.into();
344        self
345    }
346
347    pub fn point_size(&mut self, ps: f64) -> &mut Self {
348        self.context.ps = ps;
349        self
350    }
351
352    /// Set the line miter limit - the point where the line becomes a bevel join.
353    pub fn line_mitre(&mut self, lmitre: f64) -> &mut Self {
354        self.context.lmitre = lmitre * self.scalar;
355        self
356    }
357
358    /// Set the line height for text.
359    pub fn line_height(&mut self, lineheight: f64) -> &mut Self {
360        self.context.lineheight = lineheight;
361        self
362    }
363
364    // pub fn char_extra_size(&mut self, cex: f64) -> &mut Self {
365    //     self.context.cex = cex;
366    //     self
367    // }
368
369    /// Set the font face.
370    /// ```ignore
371    ///   FontFace::PlainFont
372    ///   FontFace::BoldFont
373    ///   FontFace::ItalicFont
374    ///   FontFace::BoldItalicFont
375    ///   FontFace::SymbolFont
376    /// ```
377    pub fn font_face(&mut self, fontface: FontFace) -> &mut Self {
378        self.context.fontface = fontface.to_i32();
379        self
380    }
381
382    //
383    pub fn font_family(&mut self, fontfamily: &str) -> &mut Self {
384        let maxlen = self.context.fontfamily.len() - 1;
385
386        for c in self.context.fontfamily.iter_mut() {
387            *c = 0;
388        }
389
390        for (i, b) in fontfamily.bytes().enumerate().take(maxlen) {
391            self.context.fontfamily[i] = b as std::os::raw::c_char;
392        }
393        self
394    }
395
396    /// Set the transform as a 3x2 matrix.
397    pub fn transform(
398        &mut self,
399        xscale: (f64, f64),
400        yscale: (f64, f64),
401        offset: (f64, f64),
402    ) -> &mut Self {
403        self.xscale = xscale;
404        self.yscale = yscale;
405        self.offset = offset;
406        self
407    }
408
409    pub(crate) fn context(&self) -> pGEcontext {
410        unsafe { std::mem::transmute(&self.context) }
411    }
412
413    // Affine transform.
414    pub(crate) fn t(&self, xy: (f64, f64)) -> (f64, f64) {
415        (
416            self.offset.0 + xy.0 * self.xscale.0 + xy.1 * self.yscale.0,
417            self.offset.1 + xy.0 * self.xscale.1 + xy.1 * self.yscale.1,
418        )
419    }
420
421    // Affine relative transform (width, height).
422    pub(crate) fn trel(&self, wh: (f64, f64)) -> (f64, f64) {
423        (
424            wh.0 * self.xscale.0 + wh.1 * self.yscale.0,
425            wh.0 * self.xscale.1 + wh.1 * self.yscale.1,
426        )
427    }
428
429    // Scalar transform (eg. radius etc).
430    pub(crate) fn ts(&self, value: f64) -> f64 {
431        value * self.scalar
432    }
433
434    // Inverse scalar transform (eg. text width etc).
435    pub(crate) fn its(&self, value: f64) -> f64 {
436        value / self.scalar
437    }
438
439    pub(crate) fn tmetric(&self, tm: TextMetric) -> TextMetric {
440        TextMetric {
441            ascent: tm.ascent / self.scalar,
442            descent: tm.descent / self.scalar,
443            width: tm.width / self.scalar,
444        }
445    }
446}
447
448#[allow(non_snake_case)]
449impl Device {
450    /// Get the current device.
451    pub fn current() -> Result<Device> {
452        // At present we can't trap an R error from a function
453        // that does not return a SEXP.
454        unsafe {
455            Ok(Device {
456                inner: GEcurrentDevice(),
457            })
458        }
459    }
460
461    /// Enable device rendering.
462    pub fn mode_on(&self) -> Result<()> {
463        unsafe {
464            if Rf_NoDevices() != 0 {
465                Err(Error::NoGraphicsDevices(Robj::from(())))
466            } else {
467                GEMode(1, self.inner());
468                Ok(())
469            }
470        }
471    }
472
473    /// Disable device rendering and flush.
474    pub fn mode_off(&self) -> Result<()> {
475        unsafe {
476            if Rf_NoDevices() != 0 {
477                Err(Error::NoGraphicsDevices(Robj::from(())))
478            } else {
479                GEMode(0, self.inner());
480                Ok(())
481            }
482        }
483    }
484
485    /// Get the device number for this device.
486    pub fn device_number(&self) -> i32 {
487        unsafe { GEdeviceNumber(self.inner()) }
488    }
489
490    /// Get a device by number.
491    pub fn get_device(number: i32) -> Result<Device> {
492        unsafe {
493            if number < 0 || number >= Rf_NumDevices() {
494                Err(Error::NoGraphicsDevices(Robj::from(())))
495            } else {
496                Ok(Device {
497                    inner: GEgetDevice(number),
498                })
499            }
500        }
501    }
502
503    /// Convert device coordinates into a specified unit.
504    /// This is usually done by the API.
505    pub fn from_device_coords(&self, value: (f64, f64), from: Unit) -> (f64, f64) {
506        let from = unit_to_ge(from);
507        unsafe {
508            (
509                GEfromDeviceX(value.0, from, self.inner()),
510                GEfromDeviceY(value.1, from, self.inner()),
511            )
512        }
513    }
514
515    /// Convert a specified unit coordinates into device coordinates.
516    /// This is usually done by the API.
517    pub fn to_device_coords(&self, value: (f64, f64), to: Unit) -> (f64, f64) {
518        if to == Unit::Device {
519            value
520        } else {
521            let to = unit_to_ge(to);
522            unsafe {
523                (
524                    GEtoDeviceX(value.0, to, self.inner()),
525                    GEtoDeviceY(value.1, to, self.inner()),
526                )
527            }
528        }
529    }
530
531    /// Convert device width/height coordinates into a specified unit.
532    /// This is usually done by the API.
533    pub fn from_device_wh(&self, value: (f64, f64), from: Unit) -> (f64, f64) {
534        let from = unit_to_ge(from);
535        unsafe {
536            (
537                GEfromDeviceWidth(value.0, from, self.inner()),
538                GEfromDeviceHeight(value.1, from, self.inner()),
539            )
540        }
541    }
542
543    /// Convert a specified unit width/height coordinates into device coordinates.
544    /// This is usually done by the API.
545    pub fn to_device_wh(&self, value: (f64, f64), to: Unit) -> (f64, f64) {
546        let to = unit_to_ge(to);
547        unsafe {
548            (
549                GEtoDeviceWidth(value.0, to, self.inner()),
550                GEtoDeviceHeight(value.1, to, self.inner()),
551            )
552        }
553    }
554
555    /// Start a new page. The page color can be set in advance.
556    pub fn new_page(&self, gc: &Context) {
557        unsafe { GENewPage(gc.context(), self.inner()) }
558    }
559
560    /// Change the clip rectangle.
561    pub fn clip(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
562        let from = gc.t(from);
563        let to = gc.t(to);
564        unsafe { GESetClip(from.0, from.1, to.0, to.1, self.inner()) }
565    }
566
567    /// Draw a stroked line. gc.color() is the stroke color.
568    pub fn line(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
569        let from = gc.t(from);
570        let to = gc.t(to);
571        unsafe { GELine(from.0, from.1, to.0, to.1, gc.context(), self.inner()) }
572    }
573
574    /// Draw a stroked/filled polyline. gc.color() is the stroke color.
575    /// The input is anything yielding (x,y) coordinate pairs.
576    /// Polylines are not closed.
577    pub fn polyline<T: IntoIterator<Item = (f64, f64)>>(&self, coords: T, gc: &Context) {
578        let (mut x, mut y): (Vec<_>, Vec<_>) = coords.into_iter().map(|xy| gc.t(xy)).unzip();
579        let xptr = x.as_mut_slice().as_mut_ptr();
580        let yptr = y.as_mut_slice().as_mut_ptr();
581        unsafe {
582            GEPolyline(
583                x.len() as std::os::raw::c_int,
584                xptr,
585                yptr,
586                gc.context(),
587                self.inner(),
588            )
589        }
590    }
591
592    /// Draw a stroked/filled polygon. gc.color() is the stroke color.
593    /// The input is anything yielding (x,y) coordinate pairs.
594    /// Polygons are closed.
595    pub fn polygon<T: IntoIterator<Item = (f64, f64)>>(&self, coords: T, gc: &Context) {
596        let (mut x, mut y): (Vec<_>, Vec<_>) = coords.into_iter().map(|xy| gc.t(xy)).unzip();
597        let xptr = x.as_mut_slice().as_mut_ptr();
598        let yptr = y.as_mut_slice().as_mut_ptr();
599        unsafe {
600            GEPolygon(
601                x.len() as std::os::raw::c_int,
602                xptr,
603                yptr,
604                gc.context(),
605                self.inner(),
606            )
607        }
608    }
609
610    // /// Return a list of (x, y) points generated from a spline.
611    // /// The iterator returns ((x, y), s) where s is -1 to 1.
612    // pub fn xspline<T: Iterator<Item = ((f64, f64), f64)> + Clone>(
613    //     &self,
614    //     coords: T,
615    //     open: bool,
616    //     rep_ends: bool,
617    //     draw: bool,
618    //     gc: &Context,
619    // ) -> Robj {
620    //     let (mut x, mut y): (Vec<_>, Vec<_>) = coords
621    //         .clone()
622    //         .map(|(xy, _s)| gc.t(xy))
623    //         .unzip();
624    //     let mut s: Vec<_> = coords.map(|(_xy, s)| s).collect();
625    //     let xptr = x.as_mut_slice().as_mut_ptr();
626    //     let yptr = y.as_mut_slice().as_mut_ptr();
627    //     let sptr = s.as_mut_slice().as_mut_ptr();
628    //     unsafe {
629    //         new_owned(GEXspline(
630    //             x.len() as std::os::raw::c_int,
631    //             xptr,
632    //             yptr,
633    //             sptr,
634    //             if open { 1 } else { 0 },
635    //             if rep_ends { 1 } else { 0 },
636    //             if draw { 1 } else { 0 },
637    //             gc.context(),
638    //             self.inner(),
639    //         ))
640    //     }
641    // }
642
643    /// Draw a stroked/filled circle.
644    /// gc.color() is the stroke color.
645    /// gc.fill() is the fill color.
646    pub fn circle(&self, center: (f64, f64), radius: f64, gc: &Context) {
647        let center = gc.t(center);
648        let radius = gc.ts(radius);
649        unsafe { GECircle(center.0, center.1, radius, gc.context(), self.inner()) }
650    }
651
652    /// Draw a stroked/filled axis-aligned rectangle.
653    /// gc.color() is the stroke color.
654    /// gc.fill() is the fill color.
655    pub fn rect(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
656        let from = gc.t(from);
657        let to = gc.t(to);
658        unsafe { GERect(from.0, from.1, to.0, to.1, gc.context(), self.inner()) }
659    }
660
661    /// Draw a path with multiple segments.
662    /// gc.color() is the stroke color.
663    /// gc.fill() is the fill color.
664    /// The input is an interator of iterators yielding (x,y) pairs.
665    pub fn path<T: IntoIterator<Item = impl IntoIterator<Item = (f64, f64)>>>(
666        &self,
667        coords: T,
668        winding: bool,
669        gc: &Context,
670    ) {
671        let mut x = Vec::new();
672        let mut y = Vec::new();
673        let mut nper: Vec<std::os::raw::c_int> = Vec::new();
674        let coords = coords.into_iter();
675        for segment in coords {
676            let mut n = 0;
677            for xy in segment {
678                let xy = gc.t(xy);
679                x.push(xy.0);
680                y.push(xy.1);
681                n += 1;
682            }
683            nper.push(n);
684        }
685
686        let xptr = x.as_mut_slice().as_mut_ptr();
687        let yptr = y.as_mut_slice().as_mut_ptr();
688        let nperptr = nper.as_mut_slice().as_mut_ptr();
689        unsafe {
690            GEPath(
691                xptr,
692                yptr,
693                nper.len() as std::os::raw::c_int,
694                nperptr,
695                winding.into(),
696                gc.context(),
697                self.inner(),
698            )
699        }
700    }
701
702    /// Screen capture. Returns an integer matrix representing pixels if it is able.
703    pub fn capture(&self) -> Robj {
704        unsafe { Robj::from_sexp(GECap(self.inner())) }
705    }
706
707    /// Draw a bitmap.
708    pub fn raster<T: AsRef<[u32]>>(
709        &self,
710        raster: Raster<T>,
711        pos: (f64, f64),
712        size: (f64, f64),
713        angle: f64,
714        interpolate: bool,
715        gc: &Context,
716    ) {
717        let (x, y) = gc.t(pos);
718        let (width, height) = gc.trel(size);
719        let w = raster.width;
720        let pixels = raster.pixels.as_ref();
721        let h = pixels.len() / w;
722        unsafe {
723            let raster = pixels.as_ptr() as *mut u32;
724            let w = w as i32;
725            let h = h as i32;
726            let interpolate = interpolate.into();
727            GERaster(
728                raster,
729                w,
730                h,
731                x,
732                y,
733                width,
734                height,
735                angle,
736                interpolate,
737                gc.context(),
738                self.inner(),
739            )
740        };
741    }
742
743    /// Draw a text string starting at pos.
744    /// TODO: do we need to convert units?
745    pub fn text<T: AsRef<str>>(
746        &self,
747        pos: (f64, f64),
748        text: T,
749        center: (f64, f64),
750        rot: f64,
751        gc: &Context,
752    ) {
753        unsafe {
754            let (x, y) = gc.t(pos);
755            let (xc, yc) = gc.trel(center);
756            let text = std::ffi::CString::new(text.as_ref()).unwrap();
757            let enc = cetype_t::CE_UTF8;
758            GEText(
759                x,
760                y,
761                text.as_ptr(),
762                enc,
763                xc,
764                yc,
765                rot,
766                gc.context(),
767                self.inner(),
768            );
769        }
770    }
771
772    /// Draw a special symbol centered on pos.
773    /// See <https://stat.ethz.ch/R-manual/R-devel/library/graphics/html/points.html>
774    pub fn symbol(&self, pos: (f64, f64), symbol: i32, size: f64, gc: &Context) {
775        unsafe {
776            let (x, y) = gc.t(pos);
777            GESymbol(x, y, symbol, gc.ts(size), gc.context(), self.inner());
778        }
779    }
780
781    /// Get the metrics for a single unicode codepoint.
782    pub fn char_metric(&self, c: char, gc: &Context) -> TextMetric {
783        unsafe {
784            let mut res = TextMetric {
785                ascent: 0.0,
786                descent: 0.0,
787                width: 0.0,
788            };
789            GEMetricInfo(
790                c as i32,
791                gc.context(),
792                &mut res.ascent as *mut f64,
793                &mut res.descent as *mut f64,
794                &mut res.width as *mut f64,
795                self.inner(),
796            );
797            gc.tmetric(res)
798        }
799    }
800
801    /// Get the width of a unicode string.
802    pub fn text_width<T: AsRef<str>>(&self, text: T, gc: &Context) -> f64 {
803        let text = std::ffi::CString::new(text.as_ref()).unwrap();
804        let enc = cetype_t::CE_UTF8;
805        unsafe { gc.its(GEStrWidth(text.as_ptr(), enc, gc.context(), self.inner())) }
806    }
807
808    /// Get the height of a unicode string.
809    pub fn text_height<T: AsRef<str>>(&self, text: T, gc: &Context) -> f64 {
810        let text = std::ffi::CString::new(text.as_ref()).unwrap();
811        let enc = cetype_t::CE_UTF8;
812        unsafe { gc.its(GEStrHeight(text.as_ptr(), enc, gc.context(), self.inner())) }
813    }
814
815    /// Get the metrics for a unicode string.
816    pub fn text_metric<T: AsRef<str>>(&self, text: T, gc: &Context) -> TextMetric {
817        let text = std::ffi::CString::new(text.as_ref()).unwrap();
818        let enc = cetype_t::CE_UTF8;
819        unsafe {
820            let mut res = TextMetric {
821                ascent: 0.0,
822                descent: 0.0,
823                width: 0.0,
824            };
825            GEStrMetric(
826                text.as_ptr(),
827                enc,
828                gc.context(),
829                &mut res.ascent as *mut f64,
830                &mut res.descent as *mut f64,
831                &mut res.width as *mut f64,
832                self.inner(),
833            );
834            gc.tmetric(res)
835        }
836    }
837
838    /// Get the width of a mathematical expression.
839    pub fn math_text_width(&self, expr: &Robj, gc: &Context) -> f64 {
840        unsafe { gc.its(GEExpressionWidth(expr.get(), gc.context(), self.inner())) }
841    }
842
843    /// Get the height of a mathematical expression.
844    pub fn math_text_height(&self, expr: &Robj, gc: &Context) -> f64 {
845        unsafe { gc.its(GEExpressionHeight(expr.get(), gc.context(), self.inner())) }
846    }
847
848    /// Get the metrics for a mathematical expression.
849    pub fn math_text_metric(&self, expr: &Robj, gc: &Context) -> TextMetric {
850        unsafe {
851            let mut res = TextMetric {
852                ascent: 0.0,
853                descent: 0.0,
854                width: 0.0,
855            };
856            GEExpressionMetric(
857                expr.get(),
858                gc.context(),
859                &mut res.ascent as *mut f64,
860                &mut res.descent as *mut f64,
861                &mut res.width as *mut f64,
862                self.inner(),
863            );
864            gc.tmetric(res)
865        }
866    }
867
868    /// Draw a mathematical expression.
869    pub fn math_text(
870        &self,
871        expr: &Robj,
872        pos: (f64, f64),
873        center: (f64, f64),
874        rot: f64,
875        gc: &Context,
876    ) {
877        unsafe {
878            let (x, y) = gc.t(pos);
879            let (xc, yc) = gc.trel(center);
880            GEMathText(x, y, expr.get(), xc, yc, rot, gc.context(), self.inner());
881        }
882    }
883}