1 /*
2 * RGraphicsUtils.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include "RGraphicsUtils.hpp"
17
18 #include <boost/format.hpp>
19
20 #include <shared_core/Error.hpp>
21
22 #include <core/Algorithm.hpp>
23 #include <core/Log.hpp>
24
25 #include <r/RExec.hpp>
26 #include <r/ROptions.hpp>
27 #include <r/RUtil.hpp>
28
29 #include <Rinternals.h>
30 #define R_USE_PROTOTYPES 1
31 #include <R_ext/GraphicsEngine.h>
32 #include <R_ext/GraphicsDevice.h>
33
34 #include <r/session/RGraphicsConstants.h>
35
36 #include <r/RErrorCategory.hpp>
37
38 using namespace rstudio::core;
39
40 namespace rstudio {
41 namespace r {
42 namespace session {
43 namespace graphics {
44
45 namespace {
46
47 int s_compatibleEngineVersion = 13;
48
49 #ifdef __APPLE__
50 class QuartzStatus : boost::noncopyable
51 {
52 public:
QuartzStatus()53 QuartzStatus() : checked_(false), installed_(false) {}
54
isInstalled()55 bool isInstalled()
56 {
57 if (!checked_)
58 {
59 checked_ = true;
60
61 Error error = r::exec::RFunction("capabilities",
62 "aqua").call(&installed_);
63 if (error)
64 LOG_ERROR(error);
65 }
66
67 return installed_;
68 }
69
70 private:
71 bool checked_;
72 bool installed_;
73 };
74
hasRequiredGraphicsDevices(std::string * pMessage)75 bool hasRequiredGraphicsDevices(std::string* pMessage)
76 {
77 static QuartzStatus s_quartzStatus;
78 if (!s_quartzStatus.isInstalled())
79 {
80 if (pMessage != nullptr)
81 {
82 *pMessage = "\nWARNING: The version of R you are running against "
83 "does not support the quartz graphics device (which is "
84 "required by RStudio for graphics). The Plots tab will "
85 "be disabled until a version of R that supports quartz "
86 "is installed.";
87 }
88 return false;
89 }
90 else
91 {
92 return true;
93 }
94 }
95
96 #else
97
hasRequiredGraphicsDevices(std::string * pMessage)98 bool hasRequiredGraphicsDevices(std::string* pMessage)
99 {
100 return true;
101 }
102
103 #endif
104
105 } // anonymous namespace
106
getDefaultBackend()107 std::string getDefaultBackend()
108 {
109 return r::options::getOption<std::string>(
110 kGraphicsOptionBackend,
111 "default",
112 false);
113 }
114
getDefaultAntialiasing()115 std::string getDefaultAntialiasing()
116 {
117 return r::options::getOption<std::string>(
118 kGraphicsOptionAntialias,
119 "default",
120 false);
121 }
122
setCompatibleEngineVersion(int version)123 void setCompatibleEngineVersion(int version)
124 {
125 s_compatibleEngineVersion = version;
126 }
127
validateRequirements(std::string * pMessage)128 bool validateRequirements(std::string* pMessage)
129 {
130 // get engineVersion
131 int engineVersion = R_GE_getVersion();
132
133 // version too old
134 if (engineVersion < 5)
135 {
136 if (pMessage != nullptr)
137 {
138 boost::format fmt(
139 "R graphics engine version %1% is not supported by RStudio. "
140 "The Plots tab will be disabled until a newer version of "
141 "R is installed.");
142 *pMessage = boost::str(fmt % engineVersion);
143 }
144
145 return false;
146 }
147
148 // version too new
149 else if (engineVersion > s_compatibleEngineVersion)
150 {
151 if (pMessage != nullptr)
152 {
153 boost::format fmt(
154 "R graphics engine version %1% is not supported by this "
155 "version of RStudio. The Plots tab will be disabled until "
156 "a newer version of RStudio is installed.");
157 *pMessage = boost::str(fmt % engineVersion);
158 }
159
160 return false;
161 }
162
163
164 // check for required devices
165 else
166 {
167 return hasRequiredGraphicsDevices(pMessage);
168 }
169 }
170
extraBitmapParams()171 std::string extraBitmapParams()
172 {
173 std::vector<std::string> params;
174
175 std::vector<std::string> supportedBackends;
176 Error error = r::exec::RFunction(".rs.graphics.supportedBackends").call(&supportedBackends);
177 if (error)
178 {
179 LOG_ERROR(error);
180 return "";
181 }
182
183 std::string backend = getDefaultBackend();
184
185 // if the requested backend is not supported, silently use the default
186 // (this could happen if a package's configuration was migrated from
187 // one machine to another on a different OS)
188 if (backend != "default")
189 {
190 auto it = std::find(supportedBackends.begin(), supportedBackends.end(), backend);
191 if (it == supportedBackends.end())
192 backend = "default";
193 }
194
195 // don't use the 'ragg' backend here (these parameters are normally passed
196 // to devices defined by the 'grDevices' package, and it doesn't handle
197 // 'ragg')
198 if (backend != "default" && backend != "ragg")
199 {
200 params.push_back("type = \"" + backend + "\"");
201 }
202
203 std::string antialias = getDefaultAntialiasing();
204
205 #ifdef _WIN32
206 // fix up antialias for windows backend
207 if ((backend == "windows" || backend == "default") && antialias == "subpixel")
208 antialias = "cleartype";
209 #endif
210
211 if (antialias != "default")
212 params.push_back("antialias = \"" + antialias + "\"");
213
214 if (params.empty())
215 return "";
216
217 return ", " + core::algorithm::join(params, ", ");
218 }
219
220 struct RestorePreviousGraphicsDeviceScope::Impl
221 {
Implrstudio::r::session::graphics::RestorePreviousGraphicsDeviceScope::Impl222 Impl() : pPreviousDevice(nullptr) {}
223 pGEDevDesc pPreviousDevice;
224 };
225
226
RestorePreviousGraphicsDeviceScope()227 RestorePreviousGraphicsDeviceScope::RestorePreviousGraphicsDeviceScope()
228 : pImpl_(new Impl())
229 {
230 // save ptr to previously selected device (if there is one)
231 pImpl_->pPreviousDevice = Rf_NoDevices() ? nullptr : GEcurrentDevice();
232 }
233
~RestorePreviousGraphicsDeviceScope()234 RestorePreviousGraphicsDeviceScope::~RestorePreviousGraphicsDeviceScope()
235 {
236 try
237 {
238 // reslect the previously selected device if we had one
239 if (pImpl_->pPreviousDevice != nullptr)
240 {
241 int deviceNumber = Rf_ndevNumber(pImpl_->pPreviousDevice->dev);
242 Rf_selectDevice(deviceNumber);
243 }
244 }
245 catch (...)
246 {
247 }
248 }
249
reportError(const core::Error & error)250 void reportError(const core::Error& error)
251 {
252 std::string endUserMessage = r::endUserErrorMessage(error);
253 std::string errmsg = "Graphics error: " + endUserMessage;
254 REprintf("%s\n", errmsg.c_str());
255 }
256
logAndReportError(const Error & error,const ErrorLocation & location)257 void logAndReportError(const Error& error, const ErrorLocation& location)
258 {
259 // log
260 core::log::logError(error, location);
261
262 // report to user
263 reportError(error);
264 }
265
266
267 } // namespace graphics
268 } // namespace session
269 } // namespace r
270 } // namespace rstudio
271
272