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}