extendr_api/graphics/
device_descriptor.rs

1use super::{color::Color, FontFace, LineType};
2
3// From R internals[^1]:
4//
5// > There should be a ‘pointsize’ argument which defaults to 12, and it should
6// > give the pointsize in big points (1/72 inch). How exactly this is
7// > interpreted is font-specific, but it should use a font which works with
8// > lines packed 1/6 inch apart, and looks good with lines 1/5 inch apart (that
9// > is with 2pt leading).
10//
11// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
12const POINTSIZE: f64 = 12.0;
13
14const PT: f64 = 1.0 / 72.0;
15const PT_PER_INCH: f64 = 72.0;
16
17// From R internals[^1]:
18//
19// > where ‘fnsize’ is the ‘size’ of the standard font (cex=1) on the device, in
20// > device units.
21//
22// and it seems the Postscript device chooses `pointsize` as this.
23//
24// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Handling-text
25const FONTSIZE: f64 = POINTSIZE;
26
27// From R internals[^1]:
28//
29// > The default size of a device should be 7 inches square.
30//
31// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
32const WIDTH_INCH: f64 = 7.0;
33const HEIGH_INCH: f64 = 7.0;
34
35#[allow(dead_code)]
36pub(crate) enum CanHAdjOption {
37    NotSupported = 0,
38    FixedAdjustment = 1,
39    VariableAdjustment = 2,
40}
41
42pub enum DevCapTransparency {
43    Unset = 0,
44    No = 1,
45    Yes = 2,
46}
47
48pub enum DevCapTransparentBg {
49    Unset = 0,
50    No = 1,
51    Fully = 2,
52    Semi = 3,
53}
54
55#[allow(dead_code)]
56pub(crate) enum DevCapRaster {
57    Unset = 0,
58    No = 1,
59    Yes = 2,
60    ExceptForMissingValues = 3,
61}
62
63#[allow(dead_code)]
64pub(crate) enum DevCapCapture {
65    Unset = 0,
66    No = 1,
67    Yes = 2,
68}
69
70#[allow(dead_code)]
71pub(crate) enum DevCapLocator {
72    Unset = 0,
73    No = 1,
74    Yes = 2,
75}
76
77/// A builder of [extendr_ffi::DevDesc].
78///
79// # Design notes (which feels a bit too internal to be exposed as an official document)
80//
81// Compared to the original [DevDesc], `DeviceDescriptor` omits several fields
82// that seem not very useful. For example,
83//
84// - `clipLeft`, `clipRight`, `clipBottom`, and `clipTop`: In most of the cases,
85//   this should match the device size at first.
86// - `xCharOffset`, `yCharOffset`, and `yLineBias`: Because I get [the
87//   hatred](https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/include/R_ext/GraphicsDevice.h#L101-L103).
88//   They are rarely used.
89// - `gamma`, and `canChangeGamma`: These fields are now ignored because gamma
90//   support has been removed.
91// - `deviceSpecific`: This can be provided later when we actually create a
92//   [Device].
93// - `canGenMouseDown`, `canGenMouseMove`, `canGenMouseUp`, `canGenKeybd`, and
94//   `canGenIdle`: These fields are currently not used by R and preserved only
95//   for backward-compatibility.
96// - `gettingEvent`, `getEvent`: This is set true when getGraphicsEvent is
97//   actively looking for events. Reading the description on ["6.1.6 Graphics
98//   events" of R
99//   Internals](https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Graphics-events),
100//   it seems this flag is not what is controlled by a graphic device.
101// - `canHAdj`: it seems this parameter is used only for tweaking the `hadj`
102//   before passing it to the `text()` function. This tweak probably can be done
103//   inside `text()` easily, so let's pretend to be able to handle any
104//   adjustments... c.f.
105//   <https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/main/engine.c#L1995-L2000>
106#[allow(non_snake_case)]
107pub struct DeviceDescriptor {
108    pub(crate) left: f64,
109    pub(crate) right: f64,
110    pub(crate) bottom: f64,
111    pub(crate) top: f64,
112
113    // Note: the header file questions about `ipr` and `cra` [1]. Actually,
114    // svglite and ragg have `pointsize` and `scaling` parameters instead. But,
115    // I couldn't be sure if it's enough as an framework (I mean, as a package,
116    // abstracting these parameters to `pointsize` and `scaling` is definitely a
117    // good idea), so I chose to expose these parameters as they are.
118    //
119    // [1]:
120    //     https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/include/R_ext/GraphicsDevice.h#L75-L81
121    pub(crate) ipr: [f64; 2],
122    pub(crate) cra: [f64; 2],
123
124    pub(crate) startps: f64,
125    pub(crate) startcol: Color,
126    pub(crate) startfill: Color,
127    pub(crate) startlty: LineType,
128    pub(crate) startfont: FontFace,
129}
130
131#[allow(non_snake_case)]
132impl DeviceDescriptor {
133    pub fn new() -> Self {
134        Self {
135            // The From R internals [1] " The default size of a device should be 7
136            // inches square."
137            left: 0.0,
138            right: WIDTH_INCH * PT_PER_INCH,
139            bottom: 0.0,
140            top: HEIGH_INCH * PT_PER_INCH,
141
142            ipr: [PT, PT],
143
144            // Font size. Not sure why these 0.9 and 1.2 are chosen, but R
145            // internals says this is "a good choice."
146            cra: [0.9 * FONTSIZE, 1.2 * FONTSIZE],
147
148            startps: POINTSIZE,
149            startcol: Color::hex(0x000000),
150            startfill: Color::hex(0xffffff),
151            startlty: LineType::Solid,
152            startfont: FontFace::Plain,
153        }
154    }
155
156    /// Sets the device sizes (unit: point).
157    ///
158    /// If not specified, the following numbers (7 inches square, following [the
159    /// R Internals' convetion]) will be used.
160    ///
161    /// * `left`: 0
162    /// * `right`: 7 inches * points per inch = `7 * 72`
163    /// * `bottom`: 0
164    /// * `top`: 7 inches * points per inch = `7 * 72`
165    ///
166    ///  Please note that, depending on the the coordinate system of the device,
167    ///  `left` might be larger than `right`, or `bottom` larger than `top` (for
168    ///  example, in SVG, the origin is at the top left corner).
169    ///
170    /// [the R Internals' convetion]:
171    ///     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
172    pub fn device_size(mut self, left: f64, right: f64, bottom: f64, top: f64) -> Self {
173        self.left = left;
174        self.right = right;
175        self.bottom = bottom;
176        self.top = top;
177        self
178    }
179
180    /// Sets inches per raster unit (i.e. point). **Note that most of the cases,
181    /// there's no need to change this value.**
182    ///
183    /// A point is usually 1/72 (the default value), but another value can be
184    /// specified here to scale the device. The first element is width, the
185    /// second is height.
186    pub fn ipr(mut self, ipr: [f64; 2]) -> Self {
187        self.ipr = ipr;
188        self
189    }
190
191    /// Sets the font size (unit: point). **Note that most of the cases, there's
192    /// no need to change this value.**
193    ///
194    /// The first element is width, the second is height. If not specified,
195    /// `[0.9 * 12.0, 1.2 * 12.0]`, which is [suggested by the R Internals as "a
196    /// good choice"] will be used (12 point is the usual default for graphics
197    /// devices).
198    ///
199    /// [suggested by the R Internals as "a good choice"]:
200    ///     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Handling-text
201    pub fn cra(mut self, cra: [f64; 2]) -> Self {
202        self.cra = cra;
203        self
204    }
205
206    /// Sets the initial value of pointsize.
207    ///
208    /// If not specified, 12, which is the usual default for graphics devices,
209    /// will be used.
210    pub fn startps(mut self, startps: f64) -> Self {
211        self.startps = startps;
212        self
213    }
214
215    /// Sets the initial value of colour.
216    ///
217    /// If not specified, black (`0x000000`) will be used.
218    pub fn startcol(mut self, startcol: Color) -> Self {
219        self.startcol = startcol;
220        self
221    }
222    /// Sets the initial value of fill.
223    ///
224    /// If not specified, white (`0xffffff`) will be used.
225    pub fn startfill(mut self, startfill: Color) -> Self {
226        self.startfill = startfill;
227        self
228    }
229
230    /// Sets the initial value of line type.
231    ///
232    /// If not specified, [LineType::Solid] will be used.
233    pub fn startlty(mut self, startlty: LineType) -> Self {
234        self.startlty = startlty;
235        self
236    }
237
238    /// Sets the initial value of font face.
239    ///
240    /// If not specified, [FontFace::Plain] will be used.
241    pub fn startfont(mut self, startfont: FontFace) -> Self {
242        self.startfont = startfont;
243        self
244    }
245}
246
247impl Default for DeviceDescriptor {
248    fn default() -> Self {
249        Self::new()
250    }
251}