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}