| 219 | // Fetches a monochrome sprite bitmap for the specified text. |
| 220 | // Load in batches for speed. |
| 221 | function cloudSprite(d, data, di) { |
| 222 | if (d.sprite) return; |
| 223 | c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio); |
| 224 | var x = 0, |
| 225 | y = 0, |
| 226 | maxh = 0, |
| 227 | n = data.length; |
| 228 | di--; |
| 229 | while (++di < n) { |
| 230 | d = data[di]; |
| 231 | c.save(); |
| 232 | c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font; |
| 233 | var w = c.measureText(d.text + "m").width * ratio, |
| 234 | h = d.size << 1; |
| 235 | if (d.rotate) { |
| 236 | var sr = Math.sin(d.rotate * cloudRadians), |
| 237 | cr = Math.cos(d.rotate * cloudRadians), |
| 238 | wcr = w * cr, |
| 239 | wsr = w * sr, |
| 240 | hcr = h * cr, |
| 241 | hsr = h * sr; |
| 242 | w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5; |
| 243 | h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr)); |
| 244 | } else { |
| 245 | w = (w + 0x1f) >> 5 << 5; |
| 246 | } |
| 247 | if (h > maxh) maxh = h; |
| 248 | if (x + w >= (cw << 5)) { |
| 249 | x = 0; |
| 250 | y += maxh; |
| 251 | maxh = 0; |
| 252 | } |
| 253 | if (y + h >= ch) break; |
| 254 | c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio); |
| 255 | if (d.rotate) c.rotate(d.rotate * cloudRadians); |
| 256 | c.fillText(d.text, 0, 0); |
| 257 | c.restore(); |
| 258 | d.width = w; |
| 259 | d.height = h; |
| 260 | d.xoff = x; |
| 261 | d.yoff = y; |
| 262 | d.x1 = w >> 1; |
| 263 | d.y1 = h >> 1; |
| 264 | d.x0 = -d.x1; |
| 265 | d.y0 = -d.y1; |
| 266 | x += w; |
| 267 | } |
| 268 | var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data, |
| 269 | sprite = []; |
| 270 | while (--di >= 0) { |
| 271 | d = data[di]; |
| 272 | var w = d.width, |
| 273 | w32 = w >> 5, |
| 274 | h = d.y1 - d.y0, |
| 275 | p = d.padding; |
| 276 | // Zero the buffer |
| 277 | for (var i = 0; i < h * w32; i++) sprite[i] = 0; |
| 278 | x = d.xoff; |