1 2tools = [{ 3 name: "Free-Form Select", 4 description: "Selects a free-form part of the picture to move, copy, or edit.", 5 cursor: ["precise", [16, 16], "crosshair"], 6 // passive: @TODO, 7 8 // The vertices of the polygon 9 points: [], 10 11 // The boundaries of the polygon 12 x_min: +Infinity, 13 x_max: -Infinity, 14 y_min: +Infinity, 15 y_max: -Infinity, 16 17 pointerdown: function(){ 18 var tool = this; 19 tool.x_min = pointer.x; 20 tool.x_max = pointer.x+1; 21 tool.y_min = pointer.y; 22 tool.y_max = pointer.y+1; 23 tool.points = []; 24 25 // End prior selection, drawing it to the canvas 26 deselect(); 27 // Checkpoint so we can roll back inverty brush 28 // XXX: Shouldn't use the undo stack for this at all! 29 // TODO: Create an OnCanvasObject for the inverty brush, and make selection a passive action 30 undoable(); 31 32 // The inverty brush is continuous in space which means 33 // paint(ctx, x, y) will be called for each pixel the pointer moves 34 // and we only need to record individual pointer events to make the polygon 35 var onpointermove = function(e){ 36 var pointer = e2c(e); 37 // Constrain the pointer to the canvas 38 pointer.x = Math.min(canvas.width, pointer.x); 39 pointer.x = Math.max(0, pointer.x); 40 pointer.y = Math.min(canvas.height, pointer.y); 41 pointer.y = Math.max(0, pointer.y); 42 // Add the point 43 tool.points.push(pointer); 44 // Update the boundaries of the polygon 45 tool.x_min = Math.min(pointer.x, tool.x_min); 46 tool.x_max = Math.max(pointer.x, tool.x_max); 47 tool.y_min = Math.min(pointer.y, tool.y_min); 48 tool.y_max = Math.max(pointer.y, tool.y_max); 49 }; 50 $G.on("pointermove", onpointermove); 51 $G.one("pointerup", function(){ 52 $G.off("pointermove", onpointermove); 53 }); 54 }, 55 continuous: "space", 56 paint: function(ctx, x, y){ 57 58 // Constrain the inverty paint brush position to the canvas 59 x = Math.min(canvas.width, x); 60 x = Math.max(0, x); 61 y = Math.min(canvas.height, y); 62 y = Math.max(0, y); 63 64 // Find the dimensions on the canvas of the tiny square to invert 65 var inverty_size = 2; 66 var rect_x = ~~(x - inverty_size/2); 67 var rect_y = ~~(y - inverty_size/2); 68 var rect_w = inverty_size; 69 var rect_h = inverty_size; 70 71 var ctx_src = undos[undos.length-1].getContext("2d");//psh 72 73 // Make two tiny ImageData objects, 74 var id_dest = ctx.getImageData(rect_x, rect_y, rect_w, rect_h); 75 var id_src = ctx_src.getImageData(rect_x, rect_y, rect_w, rect_h); 76 77 for(var i=0, l=id_dest.data.length; i<l; i+=4){ 78 id_dest.data[i+0] = 255 - id_src.data[i+0]; 79 id_dest.data[i+1] = 255 - id_src.data[i+1]; 80 id_dest.data[i+2] = 255 - id_src.data[i+2]; 81 id_dest.data[i+3] = 255; 82 // @TODO maybe: invert based on id_src.data[i+3] and the background 83 } 84 85 ctx.putImageData(id_dest, rect_x, rect_y); 86 87 }, 88 pointerup: function(){ 89 // Revert the inverty brush paint 90 ctx.copy(undos[undos.length-1]); 91 92 var contents_within_polygon = copy_contents_within_polygon( 93 canvas, 94 this.points, 95 this.x_min, 96 this.y_min, 97 this.x_max, 98 this.y_max 99 ); 100 101 selection = new Selection( 102 this.x_min, 103 this.y_min, 104 this.x_max - this.x_min, 105 this.y_max - this.y_min 106 ); 107 selection.instantiate(contents_within_polygon); 108 selection.cut_out_background(); 109 }, 110 $options: $choose_transparency 111}, { 112 name: "Select", 113 description: "Selects a rectangular part of the picture to move, copy, or edit.", 114 cursor: ["precise", [16, 16], "crosshair"], 115 passive: true, 116 drag_start_x: 0, 117 drag_start_y: 0, 118 pointerdown: function(){ 119 this.drag_start_x = pointer.x; 120 this.drag_start_y = pointer.y; 121 if(selection){ 122 selection.draw(); 123 selection.destroy(); 124 selection = null; 125 } 126 var pointer_has_moved = false; 127 $G.one("pointermove", function(){ 128 pointer_has_moved = true; 129 }); 130 $G.one("pointerup", function(){ 131 if(!pointer_has_moved && selection){ 132 selection.draw();//? 133 selection.destroy(); 134 selection = null; 135 } 136 }); 137 selection = new Selection(pointer.x, pointer.y, 1, 1); 138 }, 139 paint: function(){ 140 if(!selection){ return; } 141 var x1 = Math.max(0, Math.min(this.drag_start_x, pointer.x)); 142 var y1 = Math.max(0, Math.min(this.drag_start_y, pointer.y)); 143 var x2 = Math.min(canvas.width, Math.max(this.drag_start_x, pointer.x)); 144 var y2 = Math.min(canvas.height, Math.max(this.drag_start_y, pointer.y)); 145 selection.x = x1; 146 selection.y = y1; 147 selection.width = Math.max(1, x2 - x1); 148 selection.height = Math.max(1, y2 - y1); 149 selection.position(); 150 }, 151 pointerup: function(){ 152 if(!selection){ return; } 153 154 if(ctrl){ 155 selection.crop(); 156 selection.destroy(); 157 selection = null; 158 }else{ 159 selection.instantiate(); 160 } 161 }, 162 cancel: function(){ 163 if(!selection){return;} 164 selection.destroy(); 165 selection = null; 166 }, 167 $options: $choose_transparency 168}, { 169 name: "Eraser/Color Eraser", 170 description: "Erases a portion of the picture, using the selected eraser shape.",
171 cursor: ["precise", [16, 16], "crosshair"], 172 // @TODO: draw square on canvas as cursor 173 continuous: "space", 174 paint: function(ctx, x, y){ 175 176 var rect_x = ~~(x - eraser_size/2); 177 var rect_y = ~~(y - eraser_size/2); 178 var rect_w = eraser_size; 179 var rect_h = eraser_size; 180 181 if(button === 0){ 182 // Eraser 183 if(transparency){ 184 ctx.clearRect(rect_x, rect_y, rect_w, rect_h); 185 }else{ 186 ctx.fillStyle = colors.background; 187 ctx.fillRect(rect_x, rect_y, rect_w, rect_h); 188 } 189 }else{ 190 // Color Eraser 191 // Right click with the eraser to selectively replace 192 // the selected foreground color with the selected background color 193 194 var fg_rgba = get_rgba_from_color(colors.foreground); 195 var bg_rgba = get_rgba_from_color(colors.background); 196 197 var id = ctx.getImageData(rect_x, rect_y, rect_w, rect_h); 198 199 for(var i=0, l=id.data.length; i<l; i+=4){ 200 if( 201 id.data[i+0] === fg_rgba[0] && 202 id.data[i+1] === fg_rgba[1] && 203 id.data[i+2] === fg_rgba[2] && 204 id.data[i+3] === fg_rgba[3] 205 ){ 206 id.data[i+0] = bg_rgba[0]; 207 id.data[i+1] = bg_rgba[1]; 208 id.data[i+2] = bg_rgba[2]; 209 id.data[i+3] = bg_rgba[3]; 210 } 211 } 212 213 ctx.putImageData(id, rect_x, rect_y); 214 } 215 }, 216 $options: $choose_eraser_size 217}, { 218 name: "Fill With Color", 219 description: "Fills an area with the selected drawing color.", 220 cursor: ["fill-bucket", [8, 22], "crosshair"], 221 pointerdown: function(ctx, x, y){ 222 223 // Get the rgba values of the selected fill color 224 var rgba = get_rgba_from_color(fill_color); 225 226 if(shift){ 227 // Perform a global (non-contiguous) fill operation, AKA color replacement 228 draw_noncontiguous_fill(ctx, x, y, rgba[0], rgba[1], rgba[2], rgba[3]); 229 } else { 230 // Perform a normal fill operation 231 draw_fill(ctx, x, y, rgba[0], rgba[1], rgba[2], rgba[3]); 232 } 233 } 234}, { 235 name: "Pick Color", 236 description: "Picks up a color from the picture for drawing.", 237 cursor: ["eye-dropper", [9, 22], "crosshair"], 238 deselect: true, 239 passive: true, 240 241 current_color: "", 242 display_current_color: function(){ 243 this.$options.css({ 244 background: this.current_color 245 }); 246 }, 247 pointerdown: function(){ 248 var _this = this; 249 $G.one("pointerup", function(){ 250 _this.$options.css({ 251 background: "" 252 }); 253 }); 254 }, 255 paint: function(ctx, x, y){ 256 if(x >= 0 && y >= 0 && x < canvas.width && y < canvas.height){ 257 var id = ctx.getImageData(~~x, ~~y, 1, 1); 258 var r = id.data[0]; 259 var g = id.data[1]; 260 var b = id.data[2]; 261 var a = id.data[3]; 262 this.current_color = "rgba("+r+","+g+","+b+","+a/255+")"; 263 }else{ 264 this.current_color = "white"; 265 } 266 this.display_current_color(); 267 }, 268 pointerup: function(){ 269 colors[fill_color_k] = this.current_color; 270 $G.trigger("option-changed"); 271 }, 272 $options: $(E("div")) 273}, { 274 name: "Magnifier", 275 description: "Changes the magnification.", 276 cursor: ["magnifier", [16, 16], "zoom-in"], 277 // @TODO: use zoom-in/zoom-out as default, 278 // even though the custom cursor image is less descriptive 279 deselect: true, 280 passive: true, 281 // @TODO: choose and preview viewport with rectangular cursor 282 pointerdown: function(){ 283 if(magnification > 1){ 284 reset_magnification(); 285 }else{ 286 set_magnification(this.$options.enlarged_magnification); 287 } 288 }, 289 $options: $choose_magnification 290}, { 291 name: "Pencil", 292 description: "Draws a free-form line one pixel wide.", 293 cursor: ["pencil", [13, 23], "crosshair"], 294 continuous: "space", 295 stroke_only: true, 296 pencil_canvas: Canvas(), 297 paint: function(ctx, x, y){ 298 // XXX: WET (Write Everything Twice) / DAMP (Duplicate Anything Moderately Pastable) (I'm coining that) 299 // TODO: DRY (Don't Repeat Yourself) / DEHYDRATE (Delete Everything Hindering Yourself Drastically Reducing Aqueous Text Evil) (I'm coining that too) 300 var csz = get_brush_canvas_size(pencil_size, "circle"); 301 if( 302 this.rendered_shape !== "circle" || 303 this.rendered_color !== stroke_color || 304 this.rendered_size !== pencil_size 305 ){ 306 this.pencil_canvas.width = csz; 307 this.pencil_canvas.height = csz; 308 // don't need to do this.pencil_canvas.ctx.disable_image_smoothing() currently because images aren't drawn to the brush 309 310 this.pencil_canvas.ctx.fillStyle = this.pencil_canvas.ctx.strokeStyle = stroke_color; 311 render_brush(this.pencil_canvas.ctx, "circle", pencil_size); 312 313 this.rendered_color = stroke_color; 314 this.rendered_size = pencil_size; 315 this.rendered_shape = "circle"; 316 } 317 ctx.drawImage(this.pencil_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2)); 318 } 319}, { 320 name: "Brush", 321 description: "Draws using a brush with the selected shape and size.",
322 cursor: ["precise-dotted", [16, 16], "crosshair"], 323 continuous: "space", 324 rendered_color: "", 325 rendered_size: 0, 326 rendered_shape: "", 327 paint: function(ctx, x, y){ 328 var csz = get_brush_canvas_size(brush_size, brush_shape); 329 if( 330 this.rendered_shape !== brush_shape || 331 this.rendered_color !== stroke_color || 332 this.rendered_size !== brush_size 333 ){ 334 brush_canvas.width = csz; 335 brush_canvas.height = csz; 336 // don't need to do brush_ctx.disable_image_smoothing() currently because images aren't drawn to the brush 337 338 brush_ctx.fillStyle = brush_ctx.strokeStyle = stroke_color; 339 render_brush(brush_ctx, brush_shape, brush_size); 340 341 this.rendered_color = stroke_color; 342 this.rendered_size = brush_size; 343 this.rendered_shape = brush_shape; 344 } 345 ctx.drawImage(brush_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2)); 346 }, 347 $options: $choose_brush 348}, { 349 name: "Airbrush", 350 description: "Draws using an airbrush of the selected size.", 351 cursor: ["airbrush", [7, 22], "crosshair"], 352 continuous: "time", 353 paint: function(ctx, x, y){ 354 var r = airbrush_size / 2; 355 for(var i = 0; i < 6 + r/5; i++){ 356 var rx = (Math.random()*2-1) * r; 357 var ry = (Math.random()*2-1) * r; 358 var d = rx*rx + ry*ry; 359 if(d <= r * r){ 360 ctx.fillRect(x + ~~rx, y + ~~ry, 1, 1); 361 } 362 } 363 }, 364 $options: $choose_airbrush_size 365}, { 366 name: "Text", 367 description: "Inserts text into the picture.", 368 cursor: ["precise", [16, 16], "crosshair"], 369 passive: true, 370 activate: function(){ 371 setTimeout(FontDetective.preload, 10); 372 }, 373 drag_start_x: 0, 374 drag_start_y: 0, 375 pointerdown: function(){ 376 this.drag_start_x = pointer.x; 377 this.drag_start_y = pointer.y; 378 if(textbox){ 379 textbox.draw(); 380 textbox.destroy(); 381 } 382 var pointer_has_moved = false; 383 $G.one("pointermove", function(){ 384 pointer_has_moved = true; 385 }); 386 $G.one("pointerup", function(){ 387 if(!pointer_has_moved && textbox){ 388 textbox.draw(); 389 textbox.destroy(); 390 textbox = null; 391 } 392 }); 393 textbox = new TextBox(pointer.x, pointer.y, 1, 1); 394 }, 395 paint: function(){ 396 if(!textbox){ return; } 397 var x1 = Math.max(0, Math.min(this.drag_start_x, pointer.x)); 398 var y1 = Math.max(0, Math.min(this.drag_start_y, pointer.y)); 399 var x2 = Math.min(canvas.width, Math.max(this.drag_start_x, pointer.x)); 400 var y2 = Math.min(canvas.height, Math.max(this.drag_start_y, pointer.y)); 401 textbox.x = x1; 402 textbox.y = y1; 403 textbox.width = Math.max(1, x2 - x1); 404 textbox.height = Math.max(1, y2 - y1); 405 textbox.position(); 406 }, 407 pointerup: function(){ 408 if(!textbox){ return; } 409 textbox.instantiate(); 410 }, 411 cancel: function(){ 412 if(!textbox){ return; } 413 textbox.destroy(); 414 textbox = null; 415 }, 416 $options: $choose_transparency 417}, { 418 name: "Line", 419 description: "Draws a straight line with the selected line width.", 420 cursor: ["precise", [16, 16], "crosshair"], 421 stroke_only: true, 422 shape: function(ctx, x, y, w, h){ 423 update_brush_for_drawing_lines(stroke_size); 424 draw_line(ctx, x, y, x+w, y+h, stroke_size); 425 }, 426 $options: $choose_stroke_size 427}, { 428 name: "Curve", 429 description: "Draws a curved line with the selected line width.", 430 cursor: ["precise", [16, 16], "crosshair"], 431 stroke_only: true, 432 points: [], 433 passive: function(){ 434 // Actions are passive if you've already started using the tool, 435 // but the first action should be undoable / cancelable 436 return this.points.length > 0; 437 }, 438 pointerup: function(ctx, x, y){ 439 if(this.points.length >= 4){ 440 this.points = []; 441 } 442 }, 443 pointerdown: function(ctx, x, y){ 444 if(this.points.length < 1){ 445 // This would be so much better in CoffeeScript 446 var thine = this; 447 undoable(function(){ 448 thine.points.push({x: x, y: y}); 449 // second point so first action draws a line 450 thine.points.push({x: x, y: y}); 451 }); 452 }else{ 453 this.points.push({x: x, y: y}); 454 } 455 }, 456 paint: function(ctx, x, y){ 457 if(this.points.length < 1){ return; } 458 459 var i = this.points.length - 1; 460 this.points[i].x = x; 461 this.points[i].y = y; 462 463 update_brush_for_drawing_lines(stroke_size); 464 465 if(this.points.length === 4){ 466 draw_bezier_curve( 467 ctx, 468 this.points[0].x, this.points[0].y, 469 this.points[2].x, this.points[2].y, 470 this.points[3].x, this.points[3].y, 471 this.points[1].x, this.points[1].y, 472 stroke_size 473 ); 474 }else if(this.points.length === 3){ 475 draw_quadratic_curve( 476 ctx, 477 this.points[0].x, this.points[0].y, 478 this.points[2].x, this.points[2].y, 479 this.points[1].x, this.points[1].y, 480 stroke_size 481 ); 482 }else{ 483 draw_line( 484 ctx, 485 this.points[0].x, this.points[0].y, 486 this.points[1].x, this.points[1].y, 487 stroke_size 488 ); 489 } 490 }, 491 cancel: function(){ 492 this.points = []; 493 }, 494 end: function(){ 495 this.points = []; 496 }, 497 shape: function(){true}, 498 $options: $choose_stroke_size 499}, { 500 name: "Rectangle", 501 description: "Draws a rectangle with the selected fill style.",
502 cursor: ["precise", [16, 16], "crosshair"], 503 shape: function(ctx, x, y, w, h){ 504 if(w < 0){ x += w; w = -w; } 505 if(h < 0){ y += h; h = -h; } 506 507 if(this.$options.fill){ 508 ctx.fillRect(x, y, w, h); 509 } 510 if(this.$options.stroke){ 511 if(w < stroke_size * 2 || h < stroke_size * 2){ 512 ctx.save(); 513 ctx.fillStyle = ctx.strokeStyle; 514 ctx.fillRect(x, y, w, h); 515 ctx.restore(); 516 }else{ 517 // TODO: shouldn't that be ~~(stroke_size / 2)? 518 ctx.strokeRect(x + stroke_size / 2, y + stroke_size / 2, w - stroke_size, h - stroke_size); 519 } 520 } 521 }, 522 $options: $ChooseShapeStyle() 523}, { 524 name: "Polygon", 525 description: "Draws a polygon with the selected fill style.", 526 cursor: ["precise", [16, 16], "crosshair"], 527 528 // Record the last click for double-clicking 529 // A double click happens on pointerdown of a second click 530 // (within a cylindrical volume in 2d space + 1d time) 531 last_click_pointerdown: {x: -Infinity, y: -Infinity, time: -Infinity}, 532 last_click_pointerup: {x: -Infinity, y: -Infinity, time: -Infinity}, 533 534 // The vertices of the polygon 535 points: [], 536 537 passive: function(){ 538 // actions are passive if you've already started using the tool 539 // but the first action should be undoable 540 return this.points.length > 0; 541 // In other words, it's supposed to be one undoable action 542 }, 543 pointerup: function(ctx, x, y){ 544 if(this.points.length < 1){ return; } 545 546 var i = this.points.length - 1; 547 this.points[i].x = x; 548 this.points[i].y = y; 549 var dx = this.points[i].x - this.points[0].x; 550 var dy = this.points[i].y - this.points[0].y; 551 var d = Math.sqrt(dx*dx + dy*dy); 552 if(d < stroke_size * 5.1010101){ // arbitrary 101 (TODO: find correct value (or formula)) 553 this.complete(ctx, x, y); 554 } 555 556 this.last_click_pointerup = {x: x, y: y, time: +(new Date)}; 557 }, 558 pointerdown: function(ctx, x, y){ 559 var tool = this; 560 561 if(tool.points.length < 1){ 562 // @TODO: stop needing this: 563 tool.canvas_base = canvas; 564 565 undoable(function(){ 566 // @TODO: stop needing this: 567 tool.canvas_base = undos[undos.length-1]; 568 569 // Add the first point of the polygon 570 tool.points.push({x: x, y: y}); 571 // Add a second point so first action draws a line 572 tool.points.push({x: x, y: y}); 573 }); 574 }else{ 575 var lx = tool.last_click_pointerdown.x; 576 var ly = tool.last_click_pointerdown.y; 577 var lt = tool.last_click_pointerdown.time; 578 var dx = x - lx; 579 var dy = y - ly; 580 var dt = +(new Date) - lt; 581 var d = Math.sqrt(dx*dx + dy*dy); 582 if(d < 4.1010101 && dt < 250){ // arbitrary 101 (TODO: find correct value (or formula)) 583 tool.complete(ctx, x, y); 584 // Release the pointer to prevent tool.paint() 585 // being called and clearing the canvas 586 $canvas.trigger("pointerup"); 587 }else{ 588 // Add the point 589 tool.points.push({x: x, y: y}); 590 } 591 } 592 tool.last_click_pointerdown = {x: x, y: y, time: +new Date}; 593 }, 594 paint: function(ctx, x, y){ 595 if(this.points.length < 1){ return; } 596 597 // Clear the canvas to the previous image to get 598 // rid of lines drawn while constructing the shape 599 // @TODO: stop needing this 600 ctx.copy(this.canvas_base); 601
602 var i = this.points.length - 1; 603 this.points[i].x = x; 604 this.points[i].y = y; 605 606 ctx.strokeStyle = stroke_color; 607 draw_line_strip( 608 ctx, 609 this.points 610 ); 611 }, 612 complete: function(ctx, x, y){ 613 if(this.points.length < 1){ return; } 614 615 // Clear the canvas to the previous image to get 616 // rid of lines drawn while constructing the shape 617 // @TODO: stop needing this 618 ctx.copy(this.canvas_base); 619 620 ctx.fillStyle = fill_color; 621 ctx.strokeStyle = stroke_color; 622 623 draw_polygon( 624 ctx, 625 this.points, 626 this.$options.stroke, 627 this.$options.fill 628 ); 629 630 this.reset(); 631 }, 632 cancel: function(){ 633 this.reset(); 634 }, 635 end: function(){ 636 this.reset(); 637 }, 638 reset: function(){ 639 this.points = []; 640 this.last_click_pointerdown = {x: -Infinity, y: -Infinity, time: -Infinity}; 641 this.last_click_pointerup = {x: -Infinity, y: -Infinity, time: -Infinity}; 642 }, 643 shape_colors: true, 644 $options: $ChooseShapeStyle() 645}, { 646 name: "Ellipse", 647 description: "Draws an ellipse with the selected fill style.", 648 cursor: ["precise", [16, 16], "crosshair"], 649 shape: function(ctx, x, y, w, h){ 650 if(w < 0){ x += w; w = -w; } 651 if(h < 0){ y += h; h = -h; } 652 653 if(w < stroke_size || h < stroke_size){ 654 ctx.fillStyle = ctx.strokeStyle; 655 draw_ellipse(ctx, x, y, w, h, false, true); 656 }else{ 657 draw_ellipse( 658 ctx, 659 x + ~~(stroke_size / 2), 660 y + ~~(stroke_size / 2), 661 w - stroke_size, 662 h - stroke_size, 663 this.$options.stroke, 664 this.$options.fill 665 ); 666 } 667 }, 668 $options: $ChooseShapeStyle() 669}, { 670 name: "Rounded Rectangle", 671 description: "Draws a rounded rectangle with the selected fill style.", 672 cursor: ["precise", [16, 16], "crosshair"], 673 shape: function(ctx, x, y, w, h){ 674 if(w < 0){ x += w; w = -w; } 675 if(h < 0){ y += h; h = -h; } 676 677 if(w < stroke_size || h < stroke_size){ 678 ctx.fillStyle = ctx.strokeStyle; 679 var radius = Math.min(8, w/2, h/2); 680 // var radius_x = Math.min(8, w/2); 681 // var radius_y = Math.min(8, h/2); 682 draw_rounded_rectangle( 683 ctx, 684 x, y, w, h, 685 radius, radius, 686 // radius_x, radius_y, 687 false, 688 true 689 ); 690 }else{ 691 var radius = Math.min(8, (w - stroke_size)/2, (h - stroke_size)/2); 692 // var radius_x = Math.min(8, (w - stroke_size)/2); 693 // var radius_y = Math.min(8, (h - stroke_size)/2); 694 draw_rounded_rectangle( 695 ctx, 696 x + ~~(stroke_size / 2), 697 y + ~~(stroke_size / 2), 698 w - stroke_size, 699 h - stroke_size, 700 radius, radius, 701 // radius_x, radius_y, 702 this.$options.stroke, 703 this.$options.fill 704 ); 705 } 706 }, 707 $options: $ChooseShapeStyle() 708}];
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.