@@ -24,6 +24,34 @@ globalThis.qwebrPrefixComment = function(x, comment) {
2424 return `${ comment } ${ x } ` ;
2525} ;
2626
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+
2755// Function to parse the pager results
2856globalThis . qwebrParseTypePager = async function ( msg ) {
2957
@@ -64,10 +92,8 @@ globalThis.qwebrComputeEngine = async function(
6492 // 1. We setup a canvas device to write to by making a namespace call into the {webr} package
6593 // 2. We use values inside of the options array to set the figure size.
6694 // 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
7197
7298 // Create a pager variable for help/file contents
7399 let pager = [ ] ;
@@ -91,23 +117,45 @@ globalThis.qwebrComputeEngine = async function(
91117 // Initialize webR
92118 await mainWebR . init ( ) ;
93119
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 = {
98122 withAutoprint : true ,
99123 captureStreams : true ,
100- captureConditions : false // ,
124+ captureConditions : false ,
101125 // 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+ ) ;
103154
104155 // -----
105156
106157 // Start attempting to parse the result data
107158 processResultOutput:try {
108-
109- // Stop creating images
110- await mainWebR . evalRVoid ( "dev.off()" ) ;
111159
112160 // Avoid running through output processing
113161 if ( options . results === "hide" || options . output === "false" ) {
@@ -130,34 +178,11 @@ globalThis.qwebrComputeEngine = async function(
130178
131179
132180 // Clean the state
133- // We're now able to process both graphics and pager events.
181+ // We're now able to process pager events.
134182 // As a result, we cannot maintain a true 1-to-1 output order
135183 // without individually feeding each line
136184 const msgs = await mainWebR . flush ( ) ;
137185
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-
161186 // Use `map` to process the filtered "pager" events asynchronously
162187 const pager = await Promise . all (
163188 msgs . filter ( msg => msg . type === 'pager' ) . map (
@@ -177,6 +202,13 @@ globalThis.qwebrComputeEngine = async function(
177202 // Display results as HTML elements to retain output styling
178203 const div = document . createElement ( "div" ) ;
179204 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` ;
180212 pre . appendChild ( div ) ;
181213 } else {
182214 // If nothing is present, hide the element.
@@ -185,23 +217,55 @@ globalThis.qwebrComputeEngine = async function(
185217
186218 elements . outputCodeDiv . appendChild ( pre ) ;
187219
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+
190223 // 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" ;
192249
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 ) ;
195256
257+ } ) ;
258+
196259 if ( options [ 'fig-cap' ] ) {
197260 // Create figcaption element
198261 const figcaptionElement = document . createElement ( 'figcaption' ) ;
199262 figcaptionElement . innerText = options [ 'fig-cap' ] ;
200263 // Append figcaption to figure
201264 figureElement . appendChild ( figcaptionElement ) ;
202265 }
203-
266+
204267 elements . outputGraphDiv . appendChild ( figureElement ) ;
268+
205269 }
206270
207271 // Display the pager data
0 commit comments