1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
use super::{color::Color, FontFace, LineType};

// From R internals[^1]:
//
// > There should be a ‘pointsize’ argument which defaults to 12, and it should
// > give the pointsize in big points (1/72 inch). How exactly this is
// > interpreted is font-specific, but it should use a font which works with
// > lines packed 1/6 inch apart, and looks good with lines 1/5 inch apart (that
// > is with 2pt leading).
//
// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
const POINTSIZE: f64 = 12.0;

const PT: f64 = 1.0 / 72.0;
const PT_PER_INCH: f64 = 72.0;

// From R internals[^1]:
//
// > where ‘fnsize’ is the ‘size’ of the standard font (cex=1) on the device, in
// > device units.
//
// and it seems the Postscript device chooses `pointsize` as this.
//
// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Handling-text
const FONTSIZE: f64 = POINTSIZE;

// From R internals[^1]:
//
// > The default size of a device should be 7 inches square.
//
// [^1]: https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
const WIDTH_INCH: f64 = 7.0;
const HEIGH_INCH: f64 = 7.0;

#[allow(dead_code)]
pub(crate) enum CanHAdjOption {
    NotSupported = 0,
    FixedAdjustment = 1,
    VariableAdjustment = 2,
}

pub enum DevCapTransparency {
    Unset = 0,
    No = 1,
    Yes = 2,
}

pub enum DevCapTransparentBg {
    Unset = 0,
    No = 1,
    Fully = 2,
    Semi = 3,
}

#[allow(dead_code)]
pub(crate) enum DevCapRaster {
    Unset = 0,
    No = 1,
    Yes = 2,
    ExceptForMissingValues = 3,
}

#[allow(dead_code)]
pub(crate) enum DevCapCapture {
    Unset = 0,
    No = 1,
    Yes = 2,
}

#[allow(dead_code)]
pub(crate) enum DevCapLocator {
    Unset = 0,
    No = 1,
    Yes = 2,
}

/// A builder of [libR_sys::DevDesc].
///
// # Design notes (which feels a bit too internal to be exposed as an official document)
//
// Compared to the original [DevDesc], `DeviceDescriptor` omits several fields
// that seem not very useful. For example,
//
// - `clipLeft`, `clipRight`, `clipBottom`, and `clipTop`: In most of the cases,
//   this should match the device size at first.
// - `xCharOffset`, `yCharOffset`, and `yLineBias`: Because I get [the
//   hatred](https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/include/R_ext/GraphicsDevice.h#L101-L103).
//   They are rarely used.
// - `gamma`, and `canChangeGamma`: These fields are now ignored because gamma
//   support has been removed.
// - `deviceSpecific`: This can be provided later when we actually create a
//   [Device].
// - `canGenMouseDown`, `canGenMouseMove`, `canGenMouseUp`, `canGenKeybd`, and
//   `canGenIdle`: These fields are currently not used by R and preserved only
//   for backward-compatibility.
// - `gettingEvent`, `getEvent`: This is set true when getGraphicsEvent is
//   actively looking for events. Reading the description on ["6.1.6 Graphics
//   events" of R
//   Internals](https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Graphics-events),
//   it seems this flag is not what is controlled by a graphic device.
// - `canHAdj`: it seems this parameter is used only for tweaking the `hadj`
//   before passing it to the `text()` function. This tweak probably can be done
//   inside `text()` easily, so let's pretend to be able to handle any
//   adjustments... c.f.
//   <https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/main/engine.c#L1995-L2000>
#[allow(non_snake_case)]
pub struct DeviceDescriptor {
    pub(crate) left: f64,
    pub(crate) right: f64,
    pub(crate) bottom: f64,
    pub(crate) top: f64,

    // Note: the header file questions about `ipr` and `cra` [1]. Actually,
    // svglite and ragg have `pointsize` and `scaling` parameters instead. But,
    // I couldn't be sure if it's enough as an framework (I mean, as a package,
    // abstracting these parameters to `pointsize` and `scaling` is definitely a
    // good idea), so I chose to expose these parameters as they are.
    //
    // [1]:
    //     https://github.com/wch/r-source/blob/9f284035b7e503aebe4a804579e9e80a541311bb/src/include/R_ext/GraphicsDevice.h#L75-L81
    pub(crate) ipr: [f64; 2],
    pub(crate) cra: [f64; 2],

    pub(crate) startps: f64,
    pub(crate) startcol: Color,
    pub(crate) startfill: Color,
    pub(crate) startlty: LineType,
    pub(crate) startfont: FontFace,
}

#[allow(non_snake_case)]
impl DeviceDescriptor {
    pub fn new() -> Self {
        Self {
            // The From R internals [1] " The default size of a device should be 7
            // inches square."
            left: 0.0,
            right: WIDTH_INCH * PT_PER_INCH,
            bottom: 0.0,
            top: HEIGH_INCH * PT_PER_INCH,

            ipr: [PT, PT],

            // Font size. Not sure why these 0.9 and 1.2 are chosen, but R
            // internals says this is "a good choice."
            cra: [0.9 * FONTSIZE, 1.2 * FONTSIZE],

            startps: POINTSIZE,
            startcol: Color::hex(0x000000),
            startfill: Color::hex(0xffffff),
            startlty: LineType::Solid,
            startfont: FontFace::Plain,
        }
    }

    /// Sets the device sizes (unit: point).
    ///
    /// If not specified, the following numbers (7 inches square, following [the
    /// R Internals' convetion]) will be used.
    ///
    /// * `left`: 0
    /// * `right`: 7 inches * points per inch = `7 * 72`
    /// * `bottom`: 0
    /// * `top`: 7 inches * points per inch = `7 * 72`
    ///
    ///  Please note that, depending on the the coordinate system of the device,
    ///  `left` might be larger than `right`, or `bottom` larger than `top` (for
    ///  example, in SVG, the origin is at the top left corner).
    ///
    /// [the R Internals' convetion]:
    ///     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Conventions
    pub fn device_size(mut self, left: f64, right: f64, bottom: f64, top: f64) -> Self {
        self.left = left;
        self.right = right;
        self.bottom = bottom;
        self.top = top;
        self
    }

    /// Sets inches per raster unit (i.e. point). **Note that most of the cases,
    /// there's no need to change this value.**
    ///
    /// A point is usually 1/72 (the default value), but another value can be
    /// specified here to scale the device. The first element is width, the
    /// second is height.
    pub fn ipr(mut self, ipr: [f64; 2]) -> Self {
        self.ipr = ipr;
        self
    }

    /// Sets the font size (unit: point). **Note that most of the cases, there's
    /// no need to change this value.**
    ///
    /// The first element is width, the second is height. If not specified,
    /// `[0.9 * 12.0, 1.2 * 12.0]`, which is [suggested by the R Internals as "a
    /// good choice"] will be used (12 point is the usual default for graphics
    /// devices).
    ///
    /// [suggested by the R Internals as "a good choice"]:
    ///     https://cran.r-project.org/doc/manuals/r-release/R-ints.html#Handling-text
    pub fn cra(mut self, cra: [f64; 2]) -> Self {
        self.cra = cra;
        self
    }

    /// Sets the initial value of pointsize.
    ///
    /// If not specified, 12, which is the usual default for graphics devices,
    /// will be used.
    pub fn startps(mut self, startps: f64) -> Self {
        self.startps = startps;
        self
    }

    /// Sets the initial value of colour.
    ///
    /// If not specified, black (`0x000000`) will be used.
    pub fn startcol(mut self, startcol: Color) -> Self {
        self.startcol = startcol;
        self
    }
    /// Sets the initial value of fill.
    ///
    /// If not specified, white (`0xffffff`) will be used.
    pub fn startfill(mut self, startfill: Color) -> Self {
        self.startfill = startfill;
        self
    }

    /// Sets the initial value of line type.
    ///
    /// If not specified, [LineType::Solid] will be used.
    pub fn startlty(mut self, startlty: LineType) -> Self {
        self.startlty = startlty;
        self
    }

    /// Sets the initial value of font face.
    ///
    /// If not specified, [FontFace::Plain] will be used.
    pub fn startfont(mut self, startfont: FontFace) -> Self {
        self.startfont = startfont;
        self
    }
}

impl Default for DeviceDescriptor {
    fn default() -> Self {
        Self::new()
    }
}