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