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}