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                patternFill: R_NilValue,
266            };
267
268            context
269                .fontfamily
270                .iter_mut()
271                .zip(b"Helvetica".iter())
272                .for_each(|(d, s)| *d = *s as i8);
273
274            Self {
275                context,
276                xscale,
277                yscale,
278                offset,
279                scalar,
280            }
281        }
282    }
283
284    /// Set the line or text color of a primitive.
285    pub fn color(&mut self, col: Color) -> &mut Self {
286        self.context.col = col.to_i32();
287        self
288    }
289
290    /// Set the fill color of a primitive.
291    pub fn fill(&mut self, fill: Color) -> &mut Self {
292        self.context.fill = fill.to_i32();
293        self
294    }
295
296    /// Set the gamma of the device. `out_color = in_color ** gamma`
297    pub fn gamma(&mut self, gamma: f64) -> &mut Self {
298        self.context.gamma = gamma;
299        self
300    }
301
302    /// Set the width of the line in chosen units.
303    pub fn line_width(&mut self, lwd: f64) -> &mut Self {
304        self.context.lwd = (lwd * self.scalar).max(1.0);
305        self
306    }
307
308    /// Set the type of the line.
309    /// ```ignore
310    /// Blank    => <invisible>
311    /// Solid    => ------
312    /// Dashed   => - - - -
313    /// Dotted   => . . . .
314    /// DotDash  => . - . -
315    /// LongDash => --  --
316    /// TwoDash  => . . - -
317    /// ```
318    pub fn line_type(&mut self, lty: LineType) -> &mut Self {
319        self.context.lty = lty.to_i32();
320        self
321    }
322
323    /// Set the line end type.
324    /// ```ignore
325    ///   LineEnd::RoundCap
326    ///   LineEnd::ButtCap
327    ///   LineEnd::SquareCap
328    /// ```
329    pub fn line_end(&mut self, lend: LineEnd) -> &mut Self {
330        self.context.lend = lend.into();
331        self
332    }
333
334    /// Set the line join type.
335    /// ```ignore
336    ///   LineJoin::RoundJoin
337    ///   LineJoin::MitreJoin
338    ///   LineJoin::BevelJoin
339    /// ```
340    pub fn line_join(&mut self, ljoin: LineJoin) -> &mut Self {
341        self.context.ljoin = ljoin.into();
342        self
343    }
344
345    pub fn point_size(&mut self, ps: f64) -> &mut Self {
346        self.context.ps = ps;
347        self
348    }
349
350    /// Set the line miter limit - the point where the line becomes a bevel join.
351    pub fn line_mitre(&mut self, lmitre: f64) -> &mut Self {
352        self.context.lmitre = lmitre * self.scalar;
353        self
354    }
355
356    /// Set the line height for text.
357    pub fn line_height(&mut self, lineheight: f64) -> &mut Self {
358        self.context.lineheight = lineheight;
359        self
360    }
361
362    // pub fn char_extra_size(&mut self, cex: f64) -> &mut Self {
363    //     self.context.cex = cex;
364    //     self
365    // }
366
367    /// Set the font face.
368    /// ```ignore
369    ///   FontFace::PlainFont
370    ///   FontFace::BoldFont
371    ///   FontFace::ItalicFont
372    ///   FontFace::BoldItalicFont
373    ///   FontFace::SymbolFont
374    /// ```
375    pub fn font_face(&mut self, fontface: FontFace) -> &mut Self {
376        self.context.fontface = fontface.to_i32();
377        self
378    }
379
380    //
381    pub fn font_family(&mut self, fontfamily: &str) -> &mut Self {
382        let maxlen = self.context.fontfamily.len() - 1;
383
384        for c in self.context.fontfamily.iter_mut() {
385            *c = 0;
386        }
387
388        for (i, b) in fontfamily.bytes().enumerate().take(maxlen) {
389            self.context.fontfamily[i] = b as std::os::raw::c_char;
390        }
391        self
392    }
393
394    /// Set the transform as a 3x2 matrix.
395    pub fn transform(
396        &mut self,
397        xscale: (f64, f64),
398        yscale: (f64, f64),
399        offset: (f64, f64),
400    ) -> &mut Self {
401        self.xscale = xscale;
402        self.yscale = yscale;
403        self.offset = offset;
404        self
405    }
406
407    pub(crate) fn context(&self) -> pGEcontext {
408        &self.context as *const extendr_ffi::R_GE_gcontext as *mut extendr_ffi::R_GE_gcontext
409    }
410
411    // Affine transform.
412    pub(crate) fn t(&self, xy: (f64, f64)) -> (f64, f64) {
413        (
414            self.offset.0 + xy.0 * self.xscale.0 + xy.1 * self.yscale.0,
415            self.offset.1 + xy.0 * self.xscale.1 + xy.1 * self.yscale.1,
416        )
417    }
418
419    // Affine relative transform (width, height).
420    pub(crate) fn trel(&self, wh: (f64, f64)) -> (f64, f64) {
421        (
422            wh.0 * self.xscale.0 + wh.1 * self.yscale.0,
423            wh.0 * self.xscale.1 + wh.1 * self.yscale.1,
424        )
425    }
426
427    // Scalar transform (eg. radius etc).
428    pub(crate) fn ts(&self, value: f64) -> f64 {
429        value * self.scalar
430    }
431
432    // Inverse scalar transform (eg. text width etc).
433    pub(crate) fn its(&self, value: f64) -> f64 {
434        value / self.scalar
435    }
436
437    pub(crate) fn tmetric(&self, tm: TextMetric) -> TextMetric {
438        TextMetric {
439            ascent: tm.ascent / self.scalar,
440            descent: tm.descent / self.scalar,
441            width: tm.width / self.scalar,
442        }
443    }
444}
445
446#[allow(non_snake_case)]
447impl Device {
448    /// Get the current device.
449    pub fn current() -> Result<Device> {
450        // At present we can't trap an R error from a function
451        // that does not return a SEXP.
452        unsafe {
453            Ok(Device {
454                inner: GEcurrentDevice(),
455            })
456        }
457    }
458
459    /// Enable device rendering.
460    pub fn mode_on(&self) -> Result<()> {
461        unsafe {
462            if Rf_NoDevices() != 0 {
463                Err(Error::NoGraphicsDevices(Robj::from(())))
464            } else {
465                GEMode(1, self.inner());
466                Ok(())
467            }
468        }
469    }
470
471    /// Disable device rendering and flush.
472    pub fn mode_off(&self) -> Result<()> {
473        unsafe {
474            if Rf_NoDevices() != 0 {
475                Err(Error::NoGraphicsDevices(Robj::from(())))
476            } else {
477                GEMode(0, self.inner());
478                Ok(())
479            }
480        }
481    }
482
483    /// Get the device number for this device.
484    pub fn device_number(&self) -> i32 {
485        unsafe { GEdeviceNumber(self.inner()) }
486    }
487
488    /// Get a device by number.
489    pub fn get_device(number: i32) -> Result<Device> {
490        unsafe {
491            if number < 0 || number >= Rf_NumDevices() {
492                Err(Error::NoGraphicsDevices(Robj::from(())))
493            } else {
494                Ok(Device {
495                    inner: GEgetDevice(number),
496                })
497            }
498        }
499    }
500
501    /// Convert device coordinates into a specified unit.
502    /// This is usually done by the API.
503    pub fn from_device_coords(&self, value: (f64, f64), from: Unit) -> (f64, f64) {
504        let from = unit_to_ge(from);
505        unsafe {
506            (
507                GEfromDeviceX(value.0, from, self.inner()),
508                GEfromDeviceY(value.1, from, self.inner()),
509            )
510        }
511    }
512
513    /// Convert a specified unit coordinates into device coordinates.
514    /// This is usually done by the API.
515    pub fn to_device_coords(&self, value: (f64, f64), to: Unit) -> (f64, f64) {
516        if to == Unit::Device {
517            value
518        } else {
519            let to = unit_to_ge(to);
520            unsafe {
521                (
522                    GEtoDeviceX(value.0, to, self.inner()),
523                    GEtoDeviceY(value.1, to, self.inner()),
524                )
525            }
526        }
527    }
528
529    /// Convert device width/height coordinates into a specified unit.
530    /// This is usually done by the API.
531    pub fn from_device_wh(&self, value: (f64, f64), from: Unit) -> (f64, f64) {
532        let from = unit_to_ge(from);
533        unsafe {
534            (
535                GEfromDeviceWidth(value.0, from, self.inner()),
536                GEfromDeviceHeight(value.1, from, self.inner()),
537            )
538        }
539    }
540
541    /// Convert a specified unit width/height coordinates into device coordinates.
542    /// This is usually done by the API.
543    pub fn to_device_wh(&self, value: (f64, f64), to: Unit) -> (f64, f64) {
544        let to = unit_to_ge(to);
545        unsafe {
546            (
547                GEtoDeviceWidth(value.0, to, self.inner()),
548                GEtoDeviceHeight(value.1, to, self.inner()),
549            )
550        }
551    }
552
553    /// Start a new page. The page color can be set in advance.
554    pub fn new_page(&self, gc: &Context) {
555        unsafe { GENewPage(gc.context(), self.inner()) }
556    }
557
558    /// Change the clip rectangle.
559    pub fn clip(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
560        let from = gc.t(from);
561        let to = gc.t(to);
562        unsafe { GESetClip(from.0, from.1, to.0, to.1, self.inner()) }
563    }
564
565    /// Draw a stroked line. gc.color() is the stroke color.
566    pub fn line(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
567        let from = gc.t(from);
568        let to = gc.t(to);
569        unsafe { GELine(from.0, from.1, to.0, to.1, gc.context(), self.inner()) }
570    }
571
572    /// Draw a stroked/filled polyline. gc.color() is the stroke color.
573    /// The input is anything yielding (x,y) coordinate pairs.
574    /// Polylines are not closed.
575    pub fn polyline<T: IntoIterator<Item = (f64, f64)>>(&self, coords: T, gc: &Context) {
576        let (mut x, mut y): (Vec<_>, Vec<_>) = coords.into_iter().map(|xy| gc.t(xy)).unzip();
577        let xptr = x.as_mut_slice().as_mut_ptr();
578        let yptr = y.as_mut_slice().as_mut_ptr();
579        unsafe {
580            GEPolyline(
581                x.len() as std::os::raw::c_int,
582                xptr,
583                yptr,
584                gc.context(),
585                self.inner(),
586            )
587        }
588    }
589
590    /// Draw a stroked/filled polygon. gc.color() is the stroke color.
591    /// The input is anything yielding (x,y) coordinate pairs.
592    /// Polygons are closed.
593    pub fn polygon<T: IntoIterator<Item = (f64, f64)>>(&self, coords: T, gc: &Context) {
594        let (mut x, mut y): (Vec<_>, Vec<_>) = coords.into_iter().map(|xy| gc.t(xy)).unzip();
595        let xptr = x.as_mut_slice().as_mut_ptr();
596        let yptr = y.as_mut_slice().as_mut_ptr();
597        unsafe {
598            GEPolygon(
599                x.len() as std::os::raw::c_int,
600                xptr,
601                yptr,
602                gc.context(),
603                self.inner(),
604            )
605        }
606    }
607
608    // /// Return a list of (x, y) points generated from a spline.
609    // /// The iterator returns ((x, y), s) where s is -1 to 1.
610    // pub fn xspline<T: Iterator<Item = ((f64, f64), f64)> + Clone>(
611    //     &self,
612    //     coords: T,
613    //     open: bool,
614    //     rep_ends: bool,
615    //     draw: bool,
616    //     gc: &Context,
617    // ) -> Robj {
618    //     let (mut x, mut y): (Vec<_>, Vec<_>) = coords
619    //         .clone()
620    //         .map(|(xy, _s)| gc.t(xy))
621    //         .unzip();
622    //     let mut s: Vec<_> = coords.map(|(_xy, s)| s).collect();
623    //     let xptr = x.as_mut_slice().as_mut_ptr();
624    //     let yptr = y.as_mut_slice().as_mut_ptr();
625    //     let sptr = s.as_mut_slice().as_mut_ptr();
626    //     unsafe {
627    //         new_owned(GEXspline(
628    //             x.len() as std::os::raw::c_int,
629    //             xptr,
630    //             yptr,
631    //             sptr,
632    //             if open { 1 } else { 0 },
633    //             if rep_ends { 1 } else { 0 },
634    //             if draw { 1 } else { 0 },
635    //             gc.context(),
636    //             self.inner(),
637    //         ))
638    //     }
639    // }
640
641    /// Draw a stroked/filled circle.
642    /// gc.color() is the stroke color.
643    /// gc.fill() is the fill color.
644    pub fn circle(&self, center: (f64, f64), radius: f64, gc: &Context) {
645        let center = gc.t(center);
646        let radius = gc.ts(radius);
647        unsafe { GECircle(center.0, center.1, radius, gc.context(), self.inner()) }
648    }
649
650    /// Draw a stroked/filled axis-aligned rectangle.
651    /// gc.color() is the stroke color.
652    /// gc.fill() is the fill color.
653    pub fn rect(&self, from: (f64, f64), to: (f64, f64), gc: &Context) {
654        let from = gc.t(from);
655        let to = gc.t(to);
656        unsafe { GERect(from.0, from.1, to.0, to.1, gc.context(), self.inner()) }
657    }
658
659    /// Draw a path with multiple segments.
660    /// gc.color() is the stroke color.
661    /// gc.fill() is the fill color.
662    /// The input is an interator of iterators yielding (x,y) pairs.
663    pub fn path<T: IntoIterator<Item = impl IntoIterator<Item = (f64, f64)>>>(
664        &self,
665        coords: T,
666        winding: bool,
667        gc: &Context,
668    ) {
669        let mut x = Vec::new();
670        let mut y = Vec::new();
671        let mut nper: Vec<std::os::raw::c_int> = Vec::new();
672        let coords = coords.into_iter();
673        for segment in coords {
674            let mut n = 0;
675            for xy in segment {
676                let xy = gc.t(xy);
677                x.push(xy.0);
678                y.push(xy.1);
679                n += 1;
680            }
681            nper.push(n);
682        }
683
684        let xptr = x.as_mut_slice().as_mut_ptr();
685        let yptr = y.as_mut_slice().as_mut_ptr();
686        let nperptr = nper.as_mut_slice().as_mut_ptr();
687        unsafe {
688            GEPath(
689                xptr,
690                yptr,
691                nper.len() as std::os::raw::c_int,
692                nperptr,
693                winding.into(),
694                gc.context(),
695                self.inner(),
696            )
697        }
698    }
699
700    /// Screen capture. Returns an integer matrix representing pixels if it is able.
701    pub fn capture(&self) -> Robj {
702        unsafe { Robj::from_sexp(GECap(self.inner())) }
703    }
704
705    /// Draw a bitmap.
706    pub fn raster<T: AsRef<[u32]>>(
707        &self,
708        raster: Raster<T>,
709        pos: (f64, f64),
710        size: (f64, f64),
711        angle: f64,
712        interpolate: bool,
713        gc: &Context,
714    ) {
715        let (x, y) = gc.t(pos);
716        let (width, height) = gc.trel(size);
717        let w = raster.width;
718        let pixels = raster.pixels.as_ref();
719        let h = pixels.len() / w;
720        unsafe {
721            let raster = pixels.as_ptr() as *mut u32;
722            let w = w as i32;
723            let h = h as i32;
724            let interpolate = interpolate.into();
725            GERaster(
726                raster,
727                w,
728                h,
729                x,
730                y,
731                width,
732                height,
733                angle,
734                interpolate,
735                gc.context(),
736                self.inner(),
737            )
738        };
739    }
740
741    /// Draw a text string starting at pos.
742    /// TODO: do we need to convert units?
743    pub fn text<T: AsRef<str>>(
744        &self,
745        pos: (f64, f64),
746        text: T,
747        center: (f64, f64),
748        rot: f64,
749        gc: &Context,
750    ) {
751        unsafe {
752            let (x, y) = gc.t(pos);
753            let (xc, yc) = gc.trel(center);
754            let text = std::ffi::CString::new(text.as_ref()).unwrap();
755            let enc = cetype_t::CE_UTF8;
756            GEText(
757                x,
758                y,
759                text.as_ptr(),
760                enc,
761                xc,
762                yc,
763                rot,
764                gc.context(),
765                self.inner(),
766            );
767        }
768    }
769
770    /// Draw a special symbol centered on pos.
771    /// See <https://stat.ethz.ch/R-manual/R-devel/library/graphics/html/points.html>
772    pub fn symbol(&self, pos: (f64, f64), symbol: i32, size: f64, gc: &Context) {
773        unsafe {
774            let (x, y) = gc.t(pos);
775            GESymbol(x, y, symbol, gc.ts(size), gc.context(), self.inner());
776        }
777    }
778
779    /// Get the metrics for a single unicode codepoint.
780    pub fn char_metric(&self, c: char, gc: &Context) -> TextMetric {
781        unsafe {
782            let mut res = TextMetric {
783                ascent: 0.0,
784                descent: 0.0,
785                width: 0.0,
786            };
787            GEMetricInfo(
788                c as i32,
789                gc.context(),
790                &mut res.ascent as *mut f64,
791                &mut res.descent as *mut f64,
792                &mut res.width as *mut f64,
793                self.inner(),
794            );
795            gc.tmetric(res)
796        }
797    }
798
799    /// Get the width of a unicode string.
800    pub fn text_width<T: AsRef<str>>(&self, text: T, gc: &Context) -> f64 {
801        let text = std::ffi::CString::new(text.as_ref()).unwrap();
802        let enc = cetype_t::CE_UTF8;
803        unsafe { gc.its(GEStrWidth(text.as_ptr(), enc, gc.context(), self.inner())) }
804    }
805
806    /// Get the height of a unicode string.
807    pub fn text_height<T: AsRef<str>>(&self, text: T, gc: &Context) -> f64 {
808        let text = std::ffi::CString::new(text.as_ref()).unwrap();
809        let enc = cetype_t::CE_UTF8;
810        unsafe { gc.its(GEStrHeight(text.as_ptr(), enc, gc.context(), self.inner())) }
811    }
812
813    /// Get the metrics for a unicode string.
814    pub fn text_metric<T: AsRef<str>>(&self, text: T, gc: &Context) -> TextMetric {
815        let text = std::ffi::CString::new(text.as_ref()).unwrap();
816        let enc = cetype_t::CE_UTF8;
817        unsafe {
818            let mut res = TextMetric {
819                ascent: 0.0,
820                descent: 0.0,
821                width: 0.0,
822            };
823            GEStrMetric(
824                text.as_ptr(),
825                enc,
826                gc.context(),
827                &mut res.ascent as *mut f64,
828                &mut res.descent as *mut f64,
829                &mut res.width as *mut f64,
830                self.inner(),
831            );
832            gc.tmetric(res)
833        }
834    }
835
836    /// Get the width of a mathematical expression.
837    pub fn math_text_width(&self, expr: &Robj, gc: &Context) -> f64 {
838        unsafe { gc.its(GEExpressionWidth(expr.get(), gc.context(), self.inner())) }
839    }
840
841    /// Get the height of a mathematical expression.
842    pub fn math_text_height(&self, expr: &Robj, gc: &Context) -> f64 {
843        unsafe { gc.its(GEExpressionHeight(expr.get(), gc.context(), self.inner())) }
844    }
845
846    /// Get the metrics for a mathematical expression.
847    pub fn math_text_metric(&self, expr: &Robj, gc: &Context) -> TextMetric {
848        unsafe {
849            let mut res = TextMetric {
850                ascent: 0.0,
851                descent: 0.0,
852                width: 0.0,
853            };
854            GEExpressionMetric(
855                expr.get(),
856                gc.context(),
857                &mut res.ascent as *mut f64,
858                &mut res.descent as *mut f64,
859                &mut res.width as *mut f64,
860                self.inner(),
861            );
862            gc.tmetric(res)
863        }
864    }
865
866    /// Draw a mathematical expression.
867    pub fn math_text(
868        &self,
869        expr: &Robj,
870        pos: (f64, f64),
871        center: (f64, f64),
872        rot: f64,
873        gc: &Context,
874    ) {
875        unsafe {
876            let (x, y) = gc.t(pos);
877            let (xc, yc) = gc.trel(center);
878            GEMathText(x, y, expr.get(), xc, yc, rot, gc.context(), self.inner());
879        }
880    }
881}