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