@@ -113,6 +113,11 @@ L.control.sliderControl = function(options)
113113} ;
114114
115115L . TileLayer . include ( {
116+ /**
117+ * Render the tile layer to a canvas with the same dimensions as the map's viewport.
118+ * @param {Function } callback A callback called when rendering to the canvas is complete. Standard callback of `Function(err?, canvas?)` signature
119+ * @returns {void }
120+ */
116121 renderToCanvas : function ( callback ) {
117122 // Defer until we're done loading tiles
118123 if ( this . _loading ) {
@@ -130,6 +135,16 @@ L.TileLayer.include({
130135 return callback ( new Error ( 'Unable to render to canvas: Zoom out of range!' ) ) ; ;
131136 }
132137
138+ // There's a lot of information we need from the map to understand where a tile should be rendered
139+ // This approach cheats a little bit: instead of trying to figure out which tiles need to be rendered,
140+ // we assume they all already are loaded and rendered correctly. This lets us sidestep a handful of steps
141+ // So, starting from the list of loaded and active tiles, we need to create a canvas with the same dimensions
142+ // as the map viewport, and then render into each each tile.
143+ // This is complicated by the way `leaflet` works: not only do we have to understand where a tile exists
144+ // with respect to the layer, we also have to understand where that layer exists with respect to the map
145+ // and whether or not that layer has had any zoom transformations applied.
146+ // Once we have all that information, we can apply the necessary transformations to the tile to place
147+ // it in the correct location.
133148 const zoom = this . _clampZoom ( map . getZoom ( ) ) ;
134149 const layerScale = map . getZoomScale ( zoom , this . _tileZoom ) ;
135150 const level = this . _levels [ this . _tileZoom ] ;
@@ -140,7 +155,7 @@ L.TileLayer.include({
140155 const scaledSize = tileSize . multiplyBy ( layerScale ) ;
141156 const canvasDimensions = map . getSize ( ) ;
142157
143- // This canvas is the size of all the tiles that are currently visible, partial or otherwise :
158+ // This canvas should be the same size as the map's viewport :
144159 const canvas = document . createElement ( 'canvas' ) ;
145160 canvas . width = canvasDimensions . x ;
146161 canvas . height = canvasDimensions . y ;
@@ -153,6 +168,11 @@ L.TileLayer.include({
153168 console . warn ( `Missing tile ${ tile . coords } ` , tile ) ;
154169 continue ;
155170 } else {
171+ // We can get the tile's position with respect to the layer with `_getTilePos`, but
172+ // we also need to scale/translate it according to the tile container's transformations,
173+ // and then the map pane's translation
174+ // We take advantage of the 2d context's ability to do the scaling for us.
175+ // Note: this may not be precise: see the hack at the end of `GameMap.js`
156176 const offset = this . _getTilePos ( tile . coords ) . multiplyBy ( layerScale ) . add ( layerTranslate ) . add ( mapTranslate ) ;
157177 context . drawImage ( tile . el , 0 , 0 , tileSize . x , tileSize . y , offset . x , offset . y , scaledSize . x , scaledSize . y ) ;
158178 }
@@ -162,6 +182,14 @@ L.TileLayer.include({
162182 }
163183} ) ;
164184
185+ /**
186+ * Iterate over an array in an asynchronous way. The item callback will be provided with a `next` callback, to be called when
187+ * time to move on to the next item. Call `next` with an error to abort iteration early. `done` will receive this error.
188+ * @param {Array<any> } arr The array to iterate over
189+ * @param {Function } callback The callback to execute on each item. Called with two arguments: the current item, and `next`, to be called when done
190+ * @param {Function } done A callback to be called at the end of iteration. Receives two arg
191+ * @returns {void }
192+ */
165193const eachAsync = ( arr , callback , done ) => {
166194 let i = 0 ;
167195 const len = arr . length ;
@@ -173,6 +201,7 @@ const eachAsync = (arr, callback, done) => {
173201 if ( i < len ) {
174202 callback ( arr [ i ] , ( ) => {
175203 i ++ ;
204+ // We use requestAnimationFrame to break out of the call stack. Probably overkill here.
176205 requestAnimationFrame ( ( ) => next ( ) ) ;
177206 } ) ;
178207 } else {
@@ -184,6 +213,10 @@ const eachAsync = (arr, callback, done) => {
184213}
185214
186215L . Map . include ( {
216+ /**
217+ * If we have an overlay pane, render it to a canvas
218+ * @returns {null|HTMLCanvasElement }
219+ */
187220 _renderOverlay : function ( ) {
188221 if ( ! this . _panes ) {
189222 return null ;
@@ -194,6 +227,7 @@ L.Map.include({
194227 return null ;
195228 }
196229
230+ // This is taken from the `leaflet-image` plugin, with some small modifications
197231 const dimensions = this . getSize ( ) ;
198232 const bounds = this . getPixelBounds ( ) ,
199233 origin = this . getPixelOrigin ( ) ,
@@ -211,10 +245,17 @@ L.Map.include({
211245 return null ;
212246 } ,
213247
248+ /**
249+ * Render the entire map to a canvas. This canvas can then be saved as an image with something like
250+ * FileSaver.js
251+ * @param {Function } callback Callback that receives (err?, canvas?) when done generating the image
252+ */
214253 renderToCanvas : function ( callback ) {
215254 const layers = [ ] ;
255+ // Right now, we only support rendering `TileLayer` layers and the overlay pane.
216256 const tileLayers = Object . values ( this . _layers ) . filter ( l => l instanceof L . TileLayer ) ;
217257 eachAsync ( tileLayers , ( layer , next ) => {
258+ // We'll render each layer to a separate canvas, then composite them at the end.
218259 layer . renderToCanvas ( ( err , canvas ) => {
219260 if ( err ) {
220261 return next ( err ) ;
@@ -227,6 +268,7 @@ L.Map.include({
227268 return callback ( err ) ;
228269 }
229270
271+ // If we have an overlay, let's render it out to a canvas, as well.
230272 const overlay = this . _renderOverlay ( ) ;
231273 // if we have an overlay layer, draw it last
232274 if ( overlay ) {
@@ -254,13 +296,11 @@ L.Control.ExportControl = L.Control.extend({
254296 position : 'topleft' ,
255297 } ,
256298
257- initialize : function ( options )
258- {
299+ initialize : function ( options ) {
259300 L . Util . setOptions ( this , options ) ;
260301 } ,
261302
262- onAdd : function ( map )
263- {
303+ onAdd : function ( map ) {
264304 this . options . map = map ;
265305 const className = 'leaflet-control-zoom leaflet-bar' ;
266306 const container = L . DomUtil . create ( 'div' , className ) ;
@@ -284,6 +324,7 @@ L.Control.ExportControl = L.Control.extend({
284324 onRemove : function ( ) { } ,
285325
286326 _exportImage : function ( ) {
327+ // This seems janky, since this control is added via `GameMap`...
287328 window . SCIM . exportImage ( ) ;
288329 }
289330} ) ;
0 commit comments