extendr_api/graphics/
device_driver.rs

1use super::{device_descriptor::*, Device, Raster, TextMetric};
2use crate::*;
3use core::slice;
4use extendr_ffi::{
5    pDevDesc, pGEcontext, DevDesc, GEaddDevice2, GEcreateDevDesc, GEinitDisplayList,
6    R_CheckDeviceAvailable, R_GE_checkVersionOrDie, R_GE_definitions, R_GE_gcontext, R_GE_version,
7    R_NilValue, Rboolean,
8};
9/// The underlying C structure `DevDesc` has two fields related to clipping:
10///
11/// - `canClip`
12/// - `deviceClip` (available on R >= 4.1)
13///
14/// `canClip` indicates whether the device has clipping functionality at all. If
15/// not, the graphic engine kindly clips before sending the drawing operations
16/// to the device. But, this isn't very ideal in some points. Especially, it's
17/// bad that the engine will omit "any text that does not appear to be wholly
18/// inside the clipping region," according to [the R Internals]. So, the device
19/// should implement `clip()` and set `canClip` to `true` if possible.
20///
21/// Even when `canClip` is `true`, the engine does clip to protect the device
22/// from large values by default. But, for efficiency, the device can take all
23/// the responsibility of clipping. That is `deviceClip`, which was introduced
24/// in R 4.1. If this is set to `true`, the engine will perform no clipping at
25/// all. For more details, please refer to [the offical announcement blog post].
26///
27/// So, in short, a graphic device can choose either of the following:
28///
29/// - clipping without the help of the graphic engine (`Device`)
30/// - clipping with the help of the graphic engine (`DeviceAndEngine`)
31/// - no clipping at all (`Engine`)
32///
33/// [the R Internals]:
34///     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Handling-text
35/// [the announcement blog post]:
36///     https://developer.r-project.org/Blog/public/2020/06/08/improvements-to-clipping-in-the-r-graphics-engine/
37pub enum ClippingStrategy {
38    Device,
39    DeviceAndEngine,
40    Engine,
41}
42
43/// A graphic device implementation.
44///
45/// # Safety
46///
47/// To implement these callback functions, extreme care is needed to avoid any
48/// `panic!()` because it immediately crashes the R session. Usually, extendr
49/// handles a panic gracefully, but there's no such protect on the callback
50/// functions.
51#[allow(non_snake_case, unused_variables, clippy::too_many_arguments)]
52pub trait DeviceDriver: std::marker::Sized {
53    /// Whether the device accepts the drawing operation of a raster. By
54    /// default, the default implementation, which just ignores the raster,
55    /// is used so this can be left `true`. If there's a necessity to
56    /// explicitly refuse the operation, this can be set `false`.
57    const USE_RASTER: bool = true;
58
59    /// Whether the device accepts a capturing operation. By default, the
60    /// default implementation, which just returns an empty capture, is used so
61    /// this can be left `true`. If there's a necessity to explicitly refuse the
62    /// operation, this can be set `false`.
63    const USE_CAPTURE: bool = true;
64
65    /// Whether the device has a locator capability, i.e.,
66    /// reading the position of the graphics cursor when the mouse button is pressed.
67    /// It works with X11, windows and quartz devices.
68    const USE_LOCATOR: bool = true;
69
70    /// Whether the device maintains a plot history. This corresponds to
71    /// `displayListOn` in the underlying [DevDesc].
72    const USE_PLOT_HISTORY: bool = false;
73
74    /// To what extent the device takes the responsibility of clipping. See
75    /// [ClippingStrategy] for the details.
76    const CLIPPING_STRATEGY: ClippingStrategy = ClippingStrategy::DeviceAndEngine;
77
78    /// Set this to `false` if the implemented `strWidth()` and `text()` only
79    /// accept ASCII text.
80    const ACCEPT_UTF8_TEXT: bool = true;
81
82    /// A callback function to setup the device when the device is activated.
83    fn activate(&mut self, dd: DevDesc) {}
84
85    /// A callback function to draw a circle.
86    ///
87    /// The header file[^1] states:
88    ///
89    /// * The border of the circle should be drawn in the given `col` (i.e. `gc.col`).
90    /// * The circle should be filled with the given `fill` (i.e. `gc.fill`) colour.
91    /// * If `col` is `NA_INTEGER` then no border should be drawn.
92    /// * If `fill` is `NA_INTEGER` then the circle should not be filled.
93    ///
94    /// [^1]: <https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/include/R_ext/GraphicsDevice.h#L205-L210>
95    fn circle(&mut self, center: (f64, f64), r: f64, gc: R_GE_gcontext, dd: DevDesc) {}
96
97    /// A callback function to clip.
98    fn clip(&mut self, from: (f64, f64), to: (f64, f64), dd: DevDesc) {}
99
100    /// A callback function to free device-specific resources when the device is
101    /// killed. Note that, `self` MUST NOT be dropped within this function
102    /// because the wrapper that extendr internally generates will do it.
103    fn close(&mut self, dd: DevDesc) {}
104
105    /// A callback function to clean up when the device is deactivated.
106    fn deactivate(&mut self, dd: DevDesc) {}
107
108    /// A callback function to draw a line.
109    fn line(&mut self, from: (f64, f64), to: (f64, f64), gc: R_GE_gcontext, dd: DevDesc) {}
110
111    /// A callback function that returns the [TextMetric] (ascent, descent, and width) of the
112    /// given character in device unit.
113    ///
114    /// The default implementation returns `(0, 0, 0)`, following the convention
115    /// described in [the header file]:
116    ///
117    /// > If the device cannot provide metric information then it MUST return
118    /// > 0.0 for ascent, descent, and width.
119    ///
120    /// [The header file]:
121    ///     https://github.com/wch/r-source/blob/9bb47ca929c41a133786fa8fff7c70162bb75e50/src/include/R_ext/GraphicsDevice.h#L321-L322
122    fn char_metric(&mut self, c: char, gc: R_GE_gcontext, dd: DevDesc) -> TextMetric {
123        TextMetric {
124            ascent: 0.0,
125            descent: 0.0,
126            width: 0.0,
127        }
128    }
129
130    /// A callback function called whenever the graphics engine starts
131    /// drawing (mode=1) or stops drawing (mode=0).
132    fn mode(&mut self, mode: i32, dd: DevDesc) {}
133
134    /// A callback function called whenever a new plot requires a new page.
135    fn new_page(&mut self, gc: R_GE_gcontext, dd: DevDesc) {}
136
137    /// A callback function to draw a polygon.
138    fn polygon<T: IntoIterator<Item = (f64, f64)>>(
139        &mut self,
140        coords: T,
141        gc: R_GE_gcontext,
142        dd: DevDesc,
143    ) {
144    }
145
146    /// A callback function to draw a polyline.
147    fn polyline<T: IntoIterator<Item = (f64, f64)>>(
148        &mut self,
149        coords: T,
150        gc: R_GE_gcontext,
151        dd: DevDesc,
152    ) {
153    }
154
155    /// A callback function to draw a rect.
156    fn rect(&mut self, from: (f64, f64), to: (f64, f64), gc: R_GE_gcontext, dd: DevDesc) {}
157
158    /// A callback function to draw paths.
159    ///
160    /// `nper` contains number of points in each polygon. `winding` represents
161    /// the filling rule; `true` means "nonzero", `false` means "evenodd" (c.f.
162    /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule>).
163    fn path<T: IntoIterator<Item = impl IntoIterator<Item = (f64, f64)>>>(
164        &mut self,
165        coords: T,
166        winding: bool,
167        gc: R_GE_gcontext,
168        dd: DevDesc,
169    ) {
170    }
171
172    /// A callback function to draw a [Raster].
173    ///
174    /// `pos` gives the bottom-left corner. `angle` is the rotation in degrees,
175    /// with positive rotation anticlockwise from the positive x-axis.
176    /// `interpolate` is whether to apply the linear interpolation on the raster
177    /// image.
178    fn raster<T: AsRef<[u32]>>(
179        &mut self,
180        raster: Raster<T>,
181        pos: (f64, f64),
182        size: (f64, f64),
183        angle: f64,
184        interpolate: bool,
185        gc: R_GE_gcontext,
186        dd: DevDesc,
187    ) {
188    }
189
190    /// A callback function that captures and returns the current canvas.
191    ///
192    /// This is only meaningful for raster devices.
193    fn capture(&mut self, dd: DevDesc) -> Robj {
194        ().into()
195    }
196
197    /// A callback function that returns the current device size in the format
198    /// of `(left, right, bottom, top)` in points.
199    ///
200    /// - If the size of the graphic device won't change after creation, the
201    ///   function can simply return the `left`, `right`, `bottom`, and `top` of
202    ///   the `DevDesc` (the default implementation).
203    /// - If the size can change, probably the actual size should be tracked in
204    ///   the device-specific struct, i.e. `self`, and the function should refer
205    ///   to the field (e.g., [`cbm_Size()` in the cairo device]).
206    ///
207    /// Note that, while this function is what is supposed to be called
208    /// "whenever the device is resized," it's not automatically done by the
209    /// graphic engine. [The header file] states:
210    ///
211    /// > This is not usually called directly by the graphics engine because the
212    /// > detection of device resizes (e.g., a window resize) are usually
213    /// > detected by device-specific code.
214    ///
215    /// [The header file]:
216    ///     <https://github.com/wch/r-source/blob/8ebcb33a9f70e729109b1adf60edd5a3b22d3c6f/src/include/R_ext/GraphicsDevice.h#L508-L527>
217    /// [`cbm_Size()` in the cairo device]:
218    ///     <https://github.com/wch/r-source/blob/8ebcb33a9f70e729109b1adf60edd5a3b22d3c6f/src/library/grDevices/src/cairo/cairoBM.c#L73-L83>
219    fn size(&mut self, dd: DevDesc) -> (f64, f64, f64, f64) {
220        (dd.left, dd.right, dd.bottom, dd.top)
221    }
222
223    /// A callback function that returns the width of the given string in the
224    /// device units.
225    ///
226    /// The default implementation use `char_metric()` on each character in the
227    /// text and sums the widths. This should be sufficient for most of the
228    /// cases, but the developer can choose to implement this. The header
229    /// file[^1] suggests the possible reasons:
230    ///
231    /// - for performance
232    /// - to decide what to do when font metric information is not available
233    ///
234    /// [^1]: <https://github.com/wch/r-source/blob/9bb47ca929c41a133786fa8fff7c70162bb75e50/src/include/R_ext/GraphicsDevice.h#L67-L74>
235    fn text_width(&mut self, text: &str, gc: R_GE_gcontext, dd: DevDesc) -> f64 {
236        text.chars()
237            .map(|c| self.char_metric(c, gc, dd).width)
238            .sum()
239    }
240
241    /// A callback function to draw a text.
242    ///
243    /// `angle` is the rotation in degrees, with positive rotation anticlockwise
244    /// from the positive x-axis.
245    fn text(
246        &mut self,
247        pos: (f64, f64),
248        text: &str,
249        angle: f64,
250        hadj: f64,
251        gc: R_GE_gcontext,
252        dd: DevDesc,
253    ) {
254    }
255
256    /// A callback function called when the user aborts some operation. It seems
257    /// this is rarely implemented.
258    fn on_exit(&mut self, dd: DevDesc) {}
259
260    /// A callback function to confirm a new frame. It seems this is rarely
261    /// implementad.
262    fn new_frame_confirm(&mut self, dd: DevDesc) -> bool {
263        true
264    }
265
266    /// A callback function to manage the "suspension level" of the device. R
267    /// function `dev.hold()` is used to increase the level,  and `dev.flush()`
268    /// to decrease it. When the level reaches zero, output is supposed to be
269    /// flushed to the device. This is only meaningful for screen devices.
270    fn holdflush(&mut self, dd: DevDesc, level: i32) -> i32 {
271        0
272    }
273
274    /// A callback function that returns the coords of the event
275    fn locator(&mut self, x: *mut f64, y: *mut f64, dd: DevDesc) -> bool {
276        true
277    }
278
279    /// A callback function for X11_eventHelper.
280    // TODO:
281    // Argument `code` should, ideally, be of type c_int,
282    // but compiler throws erors. It should be ok to use
283    // i32 here.
284    fn eventHelper(&mut self, dd: DevDesc, code: i32) {}
285
286    /// Create a [Device].
287    fn create_device<T: DeviceDriver>(
288        self,
289        device_descriptor: DeviceDescriptor,
290        device_name: &'static str,
291    ) -> Device {
292        #![allow(non_snake_case)]
293        #![allow(unused_variables)]
294        use std::os::raw::{c_char, c_int, c_uint};
295
296        // The code here is a Rust interpretation of the C-version of example
297        // code on the R Internals:
298        //
299        // https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Device-structures
300
301        unsafe {
302            single_threaded(|| {
303                // Check the API version
304                R_GE_checkVersionOrDie(R_GE_version as _);
305
306                // Check if there are too many devices
307                R_CheckDeviceAvailable();
308            });
309        }
310
311        // Define wrapper functions. This is a bit boring, and frustrationg to
312        // see `create_device()` bloats to such a massive function because of
313        // this, but probably there's no other way to do this nicely...
314
315        unsafe extern "C" fn device_driver_activate<T: DeviceDriver>(arg1: pDevDesc) {
316            // Derefernce to the original struct without moving it. While this
317            // is a dangerous operation, it should be safe as long as the data
318            // lives only within this function.
319            //
320            // Note that, we bravely unwrap() here because deviceSpecific should
321            // never be a null pointer, as we set it. If the pDevDesc got
322            // currupted, it might happen, but we can do nothing in that weird
323            // case anyway.
324            let data = ((*arg1).deviceSpecific as *mut T).as_mut().unwrap();
325
326            data.activate(*arg1);
327        }
328
329        unsafe extern "C" fn device_driver_circle<T: DeviceDriver>(
330            x: f64,
331            y: f64,
332            r: f64,
333            gc: pGEcontext,
334            dd: pDevDesc,
335        ) {
336            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
337            data.circle((x, y), r, *gc, *dd);
338        }
339
340        unsafe extern "C" fn device_driver_clip<T: DeviceDriver>(
341            x0: f64,
342            x1: f64,
343            y0: f64,
344            y1: f64,
345            dd: pDevDesc,
346        ) {
347            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
348            data.clip((x0, y0), (x1, y1), *dd);
349        }
350
351        // Note: the close() wrapper is special. This function is responsible
352        // for tearing down the DeviceDriver itself, which is always needed even
353        // when no close callback is implemented.
354        unsafe extern "C" fn device_driver_close<T: DeviceDriver>(dd: pDevDesc) {
355            let dev_desc = *dd;
356            let data_ptr = dev_desc.deviceSpecific as *mut T;
357            // Convert back to a Rust struct to drop the resources on Rust's side.
358            let mut data = Box::from_raw(data_ptr);
359
360            data.close(dev_desc);
361        }
362
363        unsafe extern "C" fn device_driver_deactivate<T: DeviceDriver>(arg1: pDevDesc) {
364            let mut data = ((*arg1).deviceSpecific as *mut T).read();
365            data.deactivate(*arg1);
366        }
367
368        unsafe extern "C" fn device_driver_line<T: DeviceDriver>(
369            x1: f64,
370            y1: f64,
371            x2: f64,
372            y2: f64,
373            gc: pGEcontext,
374            dd: pDevDesc,
375        ) {
376            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
377            data.line((x1, y1), (x2, y2), *gc, *dd);
378        }
379
380        unsafe extern "C" fn device_driver_char_metric<T: DeviceDriver>(
381            c: c_int,
382            gc: pGEcontext,
383            ascent: *mut f64,
384            descent: *mut f64,
385            width: *mut f64,
386            dd: pDevDesc,
387        ) {
388            // Be aware that `c` can be a negative value if `hasTextUTF8` is
389            // true, and we do set it true. The header file[^1] states:
390            //
391            // > the metricInfo entry point should accept negative values for
392            // > 'c' and treat them as indicating Unicode points (as well as
393            // > positive values in a MBCS locale).
394            //
395            // The negativity might be useful if the implementation treats ASCII
396            // and non-ASCII characters differently, but I think it's rare. So,
397            // we just use `c.abs()`.
398            //
399            // [^1]: https://github.com/wch/r-source/blob/9bb47ca929c41a133786fa8fff7c70162bb75e50/src/include/R_ext/GraphicsDevice.h#L615-L617
400            if let Some(c) = std::char::from_u32(c.abs() as _) {
401                let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
402                let metric_info = data.char_metric(c, *gc, *dd);
403                *ascent = metric_info.ascent;
404                *descent = metric_info.descent;
405                *width = metric_info.width;
406            }
407        }
408
409        unsafe extern "C" fn device_driver_mode<T: DeviceDriver>(mode: c_int, dd: pDevDesc) {
410            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
411            data.mode(mode as _, *dd);
412        }
413
414        unsafe extern "C" fn device_driver_new_page<T: DeviceDriver>(gc: pGEcontext, dd: pDevDesc) {
415            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
416            data.new_page(*gc, *dd);
417        }
418
419        unsafe extern "C" fn device_driver_polygon<T: DeviceDriver>(
420            n: c_int,
421            x: *mut f64,
422            y: *mut f64,
423            gc: pGEcontext,
424            dd: pDevDesc,
425        ) {
426            let x = slice::from_raw_parts(x, n as _).iter();
427            let y = slice::from_raw_parts(y, n as _).iter();
428            // TODO: does this map has some overhead? If so, maybe we should change the interface?
429            let coords = x.zip(y).map(|(&x, &y)| (x, y));
430
431            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
432            data.polygon(coords, *gc, *dd);
433        }
434
435        unsafe extern "C" fn device_driver_polyline<T: DeviceDriver>(
436            n: c_int,
437            x: *mut f64,
438            y: *mut f64,
439            gc: pGEcontext,
440            dd: pDevDesc,
441        ) {
442            let x = slice::from_raw_parts(x, n as _).iter();
443            let y = slice::from_raw_parts(y, n as _).iter();
444            // TODO: does this map has some overhead? If so, maybe we should change the interface?
445            let coords = x.zip(y).map(|(&x, &y)| (x, y));
446
447            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
448            data.polyline(coords, *gc, *dd);
449        }
450
451        unsafe extern "C" fn device_driver_rect<T: DeviceDriver>(
452            x0: f64,
453            y0: f64,
454            x1: f64,
455            y1: f64,
456            gc: pGEcontext,
457            dd: pDevDesc,
458        ) {
459            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
460            data.rect((x0, y0), (x1, y1), *gc, *dd);
461        }
462
463        unsafe extern "C" fn device_driver_path<T: DeviceDriver>(
464            x: *mut f64,
465            y: *mut f64,
466            npoly: c_int,
467            nper: *mut c_int,
468            winding: Rboolean,
469            gc: pGEcontext,
470            dd: pDevDesc,
471        ) {
472            let nper = slice::from_raw_parts(nper, npoly as _);
473            // TODO: This isn't very efficient as we need to iterate over nper at least twice.
474            let n = nper.iter().sum::<i32>() as usize;
475            let x = slice::from_raw_parts(x, n as _).iter();
476            let y = slice::from_raw_parts(y, n as _).iter();
477            // TODO: does this map has some overhead? If so, maybe we should change the interface?
478            let mut coords_flat = x.zip(y).map(|(&x, &y)| (x, y));
479
480            let coords = nper.iter().map(|&np| {
481                coords_flat
482                    .by_ref()
483                    .take(np as _)
484                    // TODO: Probably this don't need to be collected.
485                    .collect::<Vec<(f64, f64)>>()
486            });
487
488            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
489
490            // It seems `NA` is just treated as `true`. Probably it doesn't matter much here.
491            // c.f. https://github.com/wch/r-source/blob/6b22b60126646714e0f25143ac679240be251dbe/src/library/grDevices/src/devPS.c#L4235
492            let winding = winding != Rboolean::FALSE;
493
494            data.path(coords, winding, *gc, *dd);
495        }
496
497        unsafe extern "C" fn device_driver_raster<T: DeviceDriver>(
498            raster: *mut c_uint,
499            w: c_int,
500            h: c_int,
501            x: f64,
502            y: f64,
503            width: f64,
504            height: f64,
505            rot: f64,
506            interpolate: Rboolean,
507            gc: pGEcontext,
508            dd: pDevDesc,
509        ) {
510            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
511            let raster = slice::from_raw_parts(raster, (w * h) as _);
512
513            data.raster::<&[u32]>(
514                Raster {
515                    pixels: raster,
516                    width: w as _,
517                },
518                (x, y),
519                (width, height),
520                rot,
521                // It seems `NA` is just treated as `true`. Probably it doesn't matter much here.
522                // c.f. https://github.com/wch/r-source/blob/6b22b60126646714e0f25143ac679240be251dbe/src/library/grDevices/src/devPS.c#L4062
523                interpolate != Rboolean::FALSE,
524                *gc,
525                *dd,
526            );
527        }
528
529        unsafe extern "C" fn device_driver_capture<T: DeviceDriver>(dd: pDevDesc) -> SEXP {
530            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
531            // TODO: convert the output more nicely
532            data.capture(*dd).get()
533        }
534
535        unsafe extern "C" fn device_driver_size<T: DeviceDriver>(
536            left: *mut f64,
537            right: *mut f64,
538            bottom: *mut f64,
539            top: *mut f64,
540            dd: pDevDesc,
541        ) {
542            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
543            let sizes = data.size(*dd);
544            *left = sizes.0;
545            *right = sizes.1;
546            *bottom = sizes.2;
547            *top = sizes.3;
548        }
549
550        unsafe extern "C" fn device_driver_text_width<T: DeviceDriver>(
551            str: *const c_char,
552            gc: pGEcontext,
553            dd: pDevDesc,
554        ) -> f64 {
555            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
556            let cstr = std::ffi::CStr::from_ptr(str);
557
558            // TODO: Should we do something when the str is not available?
559            if let Ok(cstr) = cstr.to_str() {
560                data.text_width(cstr, *gc, *dd)
561            } else {
562                0.0
563            }
564        }
565
566        unsafe extern "C" fn device_driver_text<T: DeviceDriver>(
567            x: f64,
568            y: f64,
569            str: *const c_char,
570            rot: f64,
571            hadj: f64,
572            gc: pGEcontext,
573            dd: pDevDesc,
574        ) {
575            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
576            let cstr = std::ffi::CStr::from_ptr(str);
577
578            // TODO: Should we do something when the str is not available?
579            if let Ok(cstr) = cstr.to_str() {
580                data.text((x, y), cstr, rot, hadj, *gc, *dd);
581            }
582        }
583
584        unsafe extern "C" fn device_driver_on_exit<T: DeviceDriver>(dd: pDevDesc) {
585            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
586            data.on_exit(*dd);
587        }
588
589        unsafe extern "C" fn device_driver_new_frame_confirm<T: DeviceDriver>(
590            dd: pDevDesc,
591        ) -> Rboolean {
592            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
593            data.new_frame_confirm(*dd).into()
594        }
595
596        unsafe extern "C" fn device_driver_holdflush<T: DeviceDriver>(
597            dd: pDevDesc,
598            level: c_int,
599        ) -> c_int {
600            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
601            data.holdflush(*dd, level as _)
602        }
603
604        unsafe extern "C" fn device_driver_locator<T: DeviceDriver>(
605            x: *mut f64,
606            y: *mut f64,
607            dd: pDevDesc,
608        ) -> Rboolean {
609            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
610            data.locator(x, y, *dd).into()
611        }
612
613        unsafe extern "C" fn device_driver_eventHelper<T: DeviceDriver>(dd: pDevDesc, code: c_int) {
614            let mut data = ((*dd).deviceSpecific as *mut T).read();
615            data.eventHelper(*dd, code);
616        }
617
618        #[cfg(use_r_ge_version_14)]
619        unsafe extern "C" fn device_driver_setPattern<T: DeviceDriver>(
620            pattern: SEXP,
621            dd: pDevDesc,
622        ) -> SEXP {
623            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
624            // TODO
625            // data.setPattern(pattern, *dd)
626            R_NilValue
627        }
628
629        #[cfg(use_r_ge_version_14)]
630        unsafe extern "C" fn device_driver_releasePattern<T: DeviceDriver>(
631            ref_: SEXP,
632            dd: pDevDesc,
633        ) {
634            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
635            // TODO
636            // data.reelasePattern(ref_, *dd);
637        }
638
639        #[cfg(use_r_ge_version_14)]
640        unsafe extern "C" fn device_driver_setClipPath<T: DeviceDriver>(
641            path: SEXP,
642            ref_: SEXP,
643            dd: pDevDesc,
644        ) -> SEXP {
645            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
646            // TODO
647            // data.setClipPath(path, ref_, *dd)
648            R_NilValue
649        }
650
651        #[cfg(use_r_ge_version_14)]
652        unsafe extern "C" fn device_driver_releaseClipPath<T: DeviceDriver>(
653            ref_: SEXP,
654            dd: pDevDesc,
655        ) {
656            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
657            // TODO
658            // data.releaseClipPath(ref_, *dd);
659        }
660
661        #[cfg(use_r_ge_version_14)]
662        unsafe extern "C" fn device_driver_setMask<T: DeviceDriver>(
663            path: SEXP,
664            ref_: SEXP,
665            dd: pDevDesc,
666        ) -> SEXP {
667            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
668            // TODO
669            // data.setMask(path, ref_, *dd)
670            R_NilValue
671        }
672
673        #[cfg(use_r_ge_version_14)]
674        unsafe extern "C" fn device_driver_releaseMask<T: DeviceDriver>(ref_: SEXP, dd: pDevDesc) {
675            let data = ((*dd).deviceSpecific as *mut T).as_mut().unwrap();
676            // TODO
677            // data.releaseMask(ref_, *dd);
678        }
679
680        //
681        // ************* defining the wrapper functions ends here ****************
682        //
683
684        // `Box::new()` allocates memory on the heap and places `self` into it.
685        // Then, an unsafe function `Box::into_raw()` converts it to a raw
686        // pointer. By doing so, Rust won't drop the object so that it will
687        // survive after after being passed to the R's side. Accordingly, it's
688        // extendr's responsibility to drop it. This deallocation will be done
689        // in the `close()` wrapper; the struct will be gotten back to the
690        // Rust's side by `Box::from_raw()` so that Rust will drop it when
691        // returning from the function.
692        let deviceSpecific = Box::into_raw(Box::new(self)) as *mut std::os::raw::c_void;
693
694        // When we go across the boundary of FFI, the general rule is that the
695        // allocated memory needs to be deallocated by the same allocator; if we
696        // allocate memory on Rust's side, it needs to be dropped on Rust's
697        // side. If we allocate memory on R's side, it needs to be freed on R's
698        // side. Here, `DevDesc` is the latter case.
699        //
700        // The problem is that, while `DevDesc` is supposed to be `free()`ed on
701        // R's side when device is closed by `dev.off()` (more specifically, in
702        // `GEdestroyDevDesc()`), there's no API that creates a `DevDesc`
703        // instance; typically, it's created by `calloc()` and a manual cast to
704        // `DevDesc*`. Please see [the example code on R Internals].
705        //
706        // Because of the absence of such an API, the only choice here is to use
707        // `libc::calloc()` and treat it as `*DevDesc`, taking the risk of
708        // uninitialized fields. This solves the problem if the same "libc" (or
709        // C runtime) as R is used. In other words, there's still a risk of
710        // allocator mismatch. We need to be careful to configure PATHs
711        // correctly to make sure the same toolchain used for compiling R itself
712        // is chosen when the program is compiled.
713        //
714        // [Example code on R Internals]:
715        //     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Device-structures
716        let p_dev_desc = unsafe { libc::calloc(1, std::mem::size_of::<DevDesc>()) as *mut DevDesc };
717
718        unsafe {
719            (*p_dev_desc).left = device_descriptor.left;
720            (*p_dev_desc).right = device_descriptor.right;
721            (*p_dev_desc).bottom = device_descriptor.bottom;
722            (*p_dev_desc).top = device_descriptor.top;
723
724            // This should be the same as the size of the device
725            (*p_dev_desc).clipLeft = device_descriptor.left;
726            (*p_dev_desc).clipRight = device_descriptor.right;
727            (*p_dev_desc).clipBottom = device_descriptor.bottom;
728            (*p_dev_desc).clipTop = device_descriptor.top;
729
730            // Not sure where these numbers came from, but it seems this is a
731            // common practice, considering the postscript device and svglite
732            // device do so.
733            (*p_dev_desc).xCharOffset = 0.4900;
734            (*p_dev_desc).yCharOffset = 0.3333;
735            (*p_dev_desc).yLineBias = 0.2;
736
737            (*p_dev_desc).ipr = device_descriptor.ipr;
738            (*p_dev_desc).cra = device_descriptor.cra;
739
740            // Gamma-related parameters are all ignored. R-internals indicates so:
741            //
742            // canChangeGamma – Rboolean: can the display gamma be adjusted? This is now
743            // ignored, as gamma support has been removed.
744            //
745            // and actually it seems this parameter is never used.
746            (*p_dev_desc).gamma = 1.0;
747
748            (*p_dev_desc).canClip = match <T>::CLIPPING_STRATEGY {
749                ClippingStrategy::Engine => Rboolean::FALSE,
750                _ => Rboolean::TRUE,
751            };
752
753            // As described above, gamma is not supported.
754            (*p_dev_desc).canChangeGamma = Rboolean::FALSE;
755
756            (*p_dev_desc).canHAdj = CanHAdjOption::VariableAdjustment as _;
757
758            (*p_dev_desc).startps = device_descriptor.startps;
759            (*p_dev_desc).startcol = device_descriptor.startcol.to_i32();
760            (*p_dev_desc).startfill = device_descriptor.startfill.to_i32();
761            (*p_dev_desc).startlty = device_descriptor.startlty.to_i32();
762            (*p_dev_desc).startfont = device_descriptor.startfont.to_i32();
763
764            (*p_dev_desc).startgamma = 1.0;
765
766            // A raw pointer to the data specific to the device.
767            (*p_dev_desc).deviceSpecific = deviceSpecific;
768
769            (*p_dev_desc).displayListOn = <T>::USE_PLOT_HISTORY.into();
770
771            // These are currently not used, so just set FALSE.
772            (*p_dev_desc).canGenMouseDown = Rboolean::FALSE;
773            (*p_dev_desc).canGenMouseMove = Rboolean::FALSE;
774            (*p_dev_desc).canGenMouseUp = Rboolean::FALSE;
775            (*p_dev_desc).canGenKeybd = Rboolean::FALSE;
776            (*p_dev_desc).canGenIdle = Rboolean::FALSE;
777
778            // The header file says:
779            //
780            // This is set while getGraphicsEvent is actively looking for events.
781            //
782            // It seems no implementation sets this, so this is probably what is
783            // modified on the engine's side.
784            (*p_dev_desc).gettingEvent = Rboolean::FALSE;
785
786            (*p_dev_desc).activate = Some(device_driver_activate::<T>);
787            (*p_dev_desc).circle = Some(device_driver_circle::<T>);
788            (*p_dev_desc).clip = match <T>::CLIPPING_STRATEGY {
789                ClippingStrategy::Engine => None,
790                _ => Some(device_driver_clip::<T>),
791            };
792            (*p_dev_desc).close = Some(device_driver_close::<T>);
793            (*p_dev_desc).deactivate = Some(device_driver_deactivate::<T>);
794            (*p_dev_desc).locator = Some(device_driver_locator::<T>); // TOD;
795            (*p_dev_desc).line = Some(device_driver_line::<T>);
796            (*p_dev_desc).metricInfo = Some(device_driver_char_metric::<T>);
797            (*p_dev_desc).mode = Some(device_driver_mode::<T>);
798            (*p_dev_desc).newPage = Some(device_driver_new_page::<T>);
799            (*p_dev_desc).polygon = Some(device_driver_polygon::<T>);
800            (*p_dev_desc).polyline = Some(device_driver_polyline::<T>);
801            (*p_dev_desc).rect = Some(device_driver_rect::<T>);
802            (*p_dev_desc).path = Some(device_driver_path::<T>);
803            (*p_dev_desc).raster = if <T>::USE_RASTER {
804                Some(device_driver_raster::<T>)
805            } else {
806                None
807            };
808            (*p_dev_desc).cap = if <T>::USE_CAPTURE {
809                Some(device_driver_capture::<T>)
810            } else {
811                None
812            };
813            (*p_dev_desc).size = Some(device_driver_size::<T>);
814            (*p_dev_desc).strWidth = Some(device_driver_text_width::<T>);
815            (*p_dev_desc).text = Some(device_driver_text::<T>);
816            (*p_dev_desc).onExit = Some(device_driver_on_exit::<T>);
817
818            // This is no longer used and exists only for backward-compatibility
819            // of the structure.
820            (*p_dev_desc).getEvent = None;
821
822            (*p_dev_desc).newFrameConfirm = Some(device_driver_new_frame_confirm::<T>);
823
824            // UTF-8 support
825            (*p_dev_desc).hasTextUTF8 = <T>::ACCEPT_UTF8_TEXT.into();
826            (*p_dev_desc).textUTF8 = if <T>::ACCEPT_UTF8_TEXT {
827                Some(device_driver_text::<T>)
828            } else {
829                None
830            };
831            (*p_dev_desc).strWidthUTF8 = if <T>::ACCEPT_UTF8_TEXT {
832                Some(device_driver_text_width::<T>)
833            } else {
834                None
835            };
836            (*p_dev_desc).wantSymbolUTF8 = <T>::ACCEPT_UTF8_TEXT.into();
837
838            // R internals says:
839            //
840            //     Some devices can produce high-quality rotated text, but those based on
841            //     bitmaps often cannot. Those which can should set useRotatedTextInContour
842            //     to be true from graphics API version 4.
843            //
844            // It seems this is used only by plot3d, so FALSE should be appropriate in
845            // most of the cases.
846            (*p_dev_desc).useRotatedTextInContour = Rboolean::FALSE;
847
848            (*p_dev_desc).eventEnv = empty_env().get();
849            (*p_dev_desc).eventHelper = Some(device_driver_eventHelper::<T>);
850
851            (*p_dev_desc).holdflush = Some(device_driver_holdflush::<T>);
852
853            // TODO: implement capability properly.
854            (*p_dev_desc).haveTransparency = DevCapTransparency::Yes as _;
855            (*p_dev_desc).haveTransparentBg = DevCapTransparentBg::Fully as _;
856
857            // There might be some cases where we want to use `Unset` or
858            // `ExceptForMissingValues`, but, for the sake of simplicity, we
859            // only use yes or no. Let's revisit here when necessary.
860            (*p_dev_desc).haveRaster = if <T>::USE_RASTER {
861                DevCapRaster::Yes as _
862            } else {
863                DevCapRaster::No as _
864            };
865
866            (*p_dev_desc).haveCapture = if <T>::USE_CAPTURE {
867                DevCapCapture::Yes as _
868            } else {
869                DevCapCapture::No as _
870            };
871
872            (*p_dev_desc).haveLocator = if <T>::USE_LOCATOR {
873                DevCapLocator::Yes as _
874            } else {
875                DevCapLocator::No as _
876            };
877
878            // NOTE: Unlike the features that will be added in  Graphics API
879            // version 15 (i.e. R 4.2), the features in API v13 & v14 (i.e. R
880            // 4.1) are not optional. We need to provide the placeholder
881            // functions for it.
882            #[cfg(use_r_ge_version_14)]
883            {
884                (*p_dev_desc).setPattern = Some(device_driver_setPattern::<T>);
885                (*p_dev_desc).releasePattern = Some(device_driver_releasePattern::<T>);
886
887                (*p_dev_desc).setClipPath = Some(device_driver_setClipPath::<T>);
888                (*p_dev_desc).releaseClipPath = Some(device_driver_releaseClipPath::<T>);
889
890                (*p_dev_desc).setMask = Some(device_driver_setMask::<T>);
891                (*p_dev_desc).releaseMask = Some(device_driver_releaseMask::<T>);
892
893                (*p_dev_desc).deviceVersion = R_GE_definitions as _;
894
895                (*p_dev_desc).deviceClip = match <T>::CLIPPING_STRATEGY {
896                    ClippingStrategy::Device => Rboolean::TRUE,
897                    _ => Rboolean::FALSE,
898                };
899            }
900
901            #[cfg(use_r_ge_version_15)]
902            {
903                (*p_dev_desc).defineGroup = None;
904                (*p_dev_desc).useGroup = None;
905                (*p_dev_desc).releaseGroup = None;
906
907                (*p_dev_desc).stroke = None;
908                (*p_dev_desc).fill = None;
909                (*p_dev_desc).fillStroke = None;
910
911                (*p_dev_desc).capabilities = None;
912            } // unsafe ends here
913        }
914        let device_name = CString::new(device_name).unwrap();
915
916        single_threaded(|| unsafe {
917            let device = GEcreateDevDesc(p_dev_desc);
918
919            // NOTE: Some graphic device use `GEaddDevice2f()`, a version of
920            // `GEaddDevice2()` with a filename, instead, but `GEaddDevice2()`
921            // should be appropriate for general purposes.
922            GEaddDevice2(device, device_name.as_ptr() as *mut i8);
923            GEinitDisplayList(device);
924
925            Device { inner: device }
926        })
927    }
928}