@@ -24,6 +24,34 @@ globalThis.qwebrPrefixComment = function(x, comment) {
24
24
return `${ comment } ${ x } ` ;
25
25
} ;
26
26
27
+ // Function to store the code in the history
28
+ globalThis . qwebrLogCodeToHistory = function ( codeToRun , options ) {
29
+ qwebrRCommandHistory . push (
30
+ `# Ran code in ${ options . label } at ${ new Date ( ) . toLocaleString ( ) } ----\n${ codeToRun } `
31
+ ) ;
32
+ }
33
+
34
+ // Function to attach a download button onto the canvas
35
+ // allowing the user to download the image.
36
+ function qwebrImageCanvasDownloadButton ( canvas , canvasContainer ) {
37
+
38
+ // Create the download button
39
+ const downloadButton = document . createElement ( 'button' ) ;
40
+ downloadButton . className = 'qwebr-canvas-image-download-btn' ;
41
+ downloadButton . textContent = 'Download Image' ;
42
+ canvasContainer . appendChild ( downloadButton ) ;
43
+
44
+ // Trigger a download of the image when the button is clicked
45
+ downloadButton . addEventListener ( 'click' , function ( ) {
46
+ const image = canvas . toDataURL ( 'image/png' ) ;
47
+ const link = document . createElement ( 'a' ) ;
48
+ link . href = image ;
49
+ link . download = 'qwebr-canvas-image.png' ;
50
+ link . click ( ) ;
51
+ } ) ;
52
+ }
53
+
54
+
27
55
// Function to parse the pager results
28
56
globalThis . qwebrParseTypePager = async function ( msg ) {
29
57
@@ -64,10 +92,8 @@ globalThis.qwebrComputeEngine = async function(
64
92
// 1. We setup a canvas device to write to by making a namespace call into the {webr} package
65
93
// 2. We use values inside of the options array to set the figure size.
66
94
// 3. We capture the output stream information (STDOUT and STERR)
67
- // 4. While parsing the results, we disable image creation.
68
-
69
- // Create a canvas variable for graphics
70
- let canvas = undefined ;
95
+ // 4. We disable the current device's image creation.
96
+ // 5. Piece-wise parse the results into the different output areas
71
97
72
98
// Create a pager variable for help/file contents
73
99
let pager = [ ] ;
@@ -91,23 +117,45 @@ globalThis.qwebrComputeEngine = async function(
91
117
// Initialize webR
92
118
await mainWebR . init ( ) ;
93
119
94
- // Setup a webR canvas by making a namespace call into the {webr} package
95
- await mainWebR . evalRVoid ( `webr::canvas(width=${ fig_width } , height=${ fig_height } )` ) ;
96
-
97
- const result = await mainWebRCodeShelter . captureR ( codeToRun , {
120
+ // Configure capture output
121
+ let captureOutputOptions = {
98
122
withAutoprint : true ,
99
123
captureStreams : true ,
100
- captureConditions : false // ,
124
+ captureConditions : false ,
101
125
// env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
102
- } ) ;
126
+ } ;
127
+
128
+ // Determine if the browser supports OffScreen
129
+ if ( qwebrOffScreenCanvasSupport ( ) ) {
130
+ // Mirror default options of webr::canvas()
131
+ // with changes to figure height and width.
132
+ captureOutputOptions . captureGraphics = {
133
+ width : fig_width ,
134
+ height : fig_height ,
135
+ bg : "white" , // default: transparent
136
+ pointsize : 12 ,
137
+ capture : true
138
+ } ;
139
+ } else {
140
+ // Disable generating graphics
141
+ captureOutputOptions . captureGraphics = false ;
142
+ }
143
+
144
+ // Store the code to run in history
145
+ qwebrLogCodeToHistory ( codeToRun , options ) ;
146
+
147
+ // Setup a webR canvas by making a namespace call into the {webr} package
148
+ // Evaluate the R code
149
+ // Remove the active canvas silently
150
+ const result = await mainWebRCodeShelter . captureR (
151
+ `${ codeToRun } ` ,
152
+ captureOutputOptions
153
+ ) ;
103
154
104
155
// -----
105
156
106
157
// Start attempting to parse the result data
107
158
processResultOutput:try {
108
-
109
- // Stop creating images
110
- await mainWebR . evalRVoid ( "dev.off()" ) ;
111
159
112
160
// Avoid running through output processing
113
161
if ( options . results === "hide" || options . output === "false" ) {
@@ -130,34 +178,11 @@ globalThis.qwebrComputeEngine = async function(
130
178
131
179
132
180
// Clean the state
133
- // We're now able to process both graphics and pager events.
181
+ // We're now able to process pager events.
134
182
// As a result, we cannot maintain a true 1-to-1 output order
135
183
// without individually feeding each line
136
184
const msgs = await mainWebR . flush ( ) ;
137
185
138
- // Output each image event stored
139
- msgs . forEach ( ( msg ) => {
140
- // Determine if old canvas can be used or a new canvas is required.
141
- if ( msg . type === 'canvas' ) {
142
- // Add image to the current canvas
143
- if ( msg . data . event === 'canvasImage' ) {
144
- canvas . getContext ( '2d' ) . drawImage ( msg . data . image , 0 , 0 ) ;
145
- } else if ( msg . data . event === 'canvasNewPage' ) {
146
-
147
- // Generate a new canvas element
148
- canvas = document . createElement ( "canvas" ) ;
149
- canvas . setAttribute ( "width" , 2 * fig_width ) ;
150
- canvas . setAttribute ( "height" , 2 * fig_height ) ;
151
- canvas . style . width = options [ "out-width" ] ? options [ "out-width" ] : `${ fig_width } px` ;
152
- if ( options [ "out-height" ] ) {
153
- canvas . style . height = options [ "out-height" ] ;
154
- }
155
- canvas . style . display = "block" ;
156
- canvas . style . margin = "auto" ;
157
- }
158
- }
159
- } ) ;
160
-
161
186
// Use `map` to process the filtered "pager" events asynchronously
162
187
const pager = await Promise . all (
163
188
msgs . filter ( msg => msg . type === 'pager' ) . map (
@@ -177,6 +202,13 @@ globalThis.qwebrComputeEngine = async function(
177
202
// Display results as HTML elements to retain output styling
178
203
const div = document . createElement ( "div" ) ;
179
204
div . innerHTML = out ;
205
+
206
+ // Calculate a scaled font-size value
207
+ const scaledFontSize = qwebrScaledFontSize (
208
+ elements . outputCodeDiv , options ) ;
209
+
210
+ // Override output code cell size
211
+ pre . style . fontSize = `${ scaledFontSize } px` ;
180
212
pre . appendChild ( div ) ;
181
213
} else {
182
214
// If nothing is present, hide the element.
@@ -185,23 +217,55 @@ globalThis.qwebrComputeEngine = async function(
185
217
186
218
elements . outputCodeDiv . appendChild ( pre ) ;
187
219
188
- // Place the graphics on the canvas
189
- if ( canvas ) {
220
+ // Determine if we have graphs to display
221
+ if ( result . images . length > 0 ) {
222
+
190
223
// Create figure element
191
- const figureElement = document . createElement ( 'figure' ) ;
224
+ const figureElement = document . createElement ( "figure" ) ;
225
+ figureElement . className = "qwebr-canvas-image" ;
226
+
227
+ // Place each rendered graphic onto a canvas element
228
+ result . images . forEach ( ( img ) => {
229
+
230
+ // Construct canvas for object
231
+ const canvas = document . createElement ( "canvas" ) ;
232
+
233
+ // Add an image download button
234
+ qwebrImageCanvasDownloadButton ( canvas , figureElement ) ;
235
+
236
+ // Set canvas size to image
237
+ canvas . width = img . width ;
238
+ canvas . height = img . height ;
239
+
240
+ // Apply output truncations
241
+ canvas . style . width = options [ "out-width" ] ? options [ "out-width" ] : `${ fig_width } px` ;
242
+ if ( options [ "out-height" ] ) {
243
+ canvas . style . height = options [ "out-height" ] ;
244
+ }
245
+
246
+ // Apply styling
247
+ canvas . style . display = "block" ;
248
+ canvas . style . margin = "auto" ;
192
249
193
- // Append canvas to figure
194
- figureElement . appendChild ( canvas ) ;
250
+ // Draw image onto Canvas
251
+ const ctx = canvas . getContext ( "2d" ) ;
252
+ ctx . drawImage ( img , 0 , 0 , img . width , img . height ) ;
253
+
254
+ // Append canvas to figure output area
255
+ figureElement . appendChild ( canvas ) ;
195
256
257
+ } ) ;
258
+
196
259
if ( options [ 'fig-cap' ] ) {
197
260
// Create figcaption element
198
261
const figcaptionElement = document . createElement ( 'figcaption' ) ;
199
262
figcaptionElement . innerText = options [ 'fig-cap' ] ;
200
263
// Append figcaption to figure
201
264
figureElement . appendChild ( figcaptionElement ) ;
202
265
}
203
-
266
+
204
267
elements . outputGraphDiv . appendChild ( figureElement ) ;
268
+
205
269
}
206
270
207
271
// Display the pager data
0 commit comments