# Canvas+ext

[browser](/web/browser.md) ⟩ [canvas](/web/browser/canvas.md) ⟩ +ext

{% hint style="success" %}
extend [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) and [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement).

<img src="/files/PBY9gpcuS4DntZni4Q2T" alt="" data-size="original">

:point\_right: [custom](/web/appendix/custom.md)
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}

* replit ⟩ [Canvas+ext (v1.1)](https://replit.com/@pegasusroe/Canvasext-v11#js/ext/Canvas+ext.js)  -> require [Rect](/web/appendix/custom/class/rect.md), [Vector](/web/appendix/custom/class/vector.md), [Number+ext](/web/js/val/prim/num/number+ext.md), [Iterable+ext](/web/js/iteration/iterable+ext.md)

```javascript
// (Canvas+ext v1.1)
// 2023.01.22 - 21:29 (/) .drawPath() -> 1. opts destructuring, 2. line dash
//                    (/) .moveToPoint() -> remove `newPath` param
//                    (/) fix .drawArcTo(), .drawPolylineTo()
//                    (+) .drawPolyline(), .drawQuadraticCurve(), .drawCubicBezierCurve()

// 2023.01.21 - 09:02 (/) .drawPolygon() - fix start angle
// 2023.01.21 - 08:14 (+) .drawPolygon()
// 2023.01.20 - 23:36 (•) first version
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
import { vec, polar } from './Vector.js';     // 👔 Vector
import { range } from './Iterable+ext.js';    // 👔 Iterable+ext
// -----------------------------------------------------------------


// ⭐ HTMLCanvasElement + extensions
// -----------------------------------------------------------------
// 🔹 .draw2D()
//
Object.defineProperties(HTMLCanvasElement.prototype, {
    // 🔹 .draw2D()
    draw2D: {
        value: function(draw){
            draw(this.getContext("2d"));
        },
    },
});

// ⭐ CanvasRenderingContext2D + extensions
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// 🔹 .drawArc()
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolyline()
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurve()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurve()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            // newPath = false,        // begin new subpath ? (default = false)
        }={}) {
            // if (newPath) this.beginPath();
            this.moveTo(...p.coords);        // 👔 Vector
        }
    },

    // ⭐ .drawPath()
    drawPath: {
        value: function(draw, {
            // path
            newPath = true,        // begin new subpath ?
            closePath = true,      // close subpath ?
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
            // line dash
            dash,
        }={}){
            
            // new subpath ?
            if (newPath) this.beginPath();    // start new path if necessary
            
            // draw path
            draw();

            // close path ?
            if (closePath) this.closePath();
            
            // fill ?
            if (fill) {
                if (fillStyle) this.fillStyle = fillStyle;
                this.fill();
            };
            
            // stroke ?
            if (stroke) {
                if (strokeStyle) this.strokeStyle = strokeStyle;
                if (dash) this.setLineDash(dash);
                this.stroke();
            };
            
        },
    },

    // --------------------------
    //     arc/circle related
    // --------------------------
    
    // 🔹 .drawArc()
    drawArc: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 2 * PI,         // end angle
            clockwise = true,
            // path: general
            ...opts
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { ...opts });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc specific
            start,                 // start point
            // path: general
            ...opts
        }={}){
            // arc specific
            //      ╭────────── default ──────────╮
            opts = {newPath: false, closePath:false, ...opts};
            // if (start) { opts.newPath = true }    // new subpath if has start point
            // draw arc to ...
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start);
                // connect arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { ...opts });
        },
    },

    // 🔹 .drawCircle()
    drawCircle: {
        value: function(center, radius, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, 0, 2*PI);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawEllipse()
    drawEllipse: {
        value: function(center, radiusX, radiusY, {
            // path: ellipse related
            start = 0,
            end = 2 * PI,
            rotation = 0,
            clockwise = true,
            // path: general
            newPath = true,
            closePath = true,
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.ellipse(
                    ...center.coords, radiusX, radiusY, 
                    rotation, start, end, !clockwise
                );
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawWedge()
    drawWedge: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 90 * deg,       // end angle    // 👔 Number+ext
            clockwise = true,
            // path: general
            ...opts
        }={}){
            // wedge specific parameters
            opts = {
                ...opts, 
                newPath: true, 
                closePath: true,    // add line back to center
            };
            // draw wedge
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { ...opts });
        },
    },

    // --------------------------
    //     bezier curve
    // --------------------------

    // 🔹 .drawQuadraticCurveTo()
    drawQuadraticCurveTo: {
        value: function(control, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.quadraticCurveTo(...control.coords, ...target.coords);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawQuadraticCurve()
    drawQuadraticCurve: {
        value: function(p1, ctrl, p2, {
            ...opts
        }={}){
            // curve specific
            //      ╭───────────────── default ────────────────╮
            opts = {newPath: true, fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                // move to start point
                this.moveToPoint(p1);
                // connect bezier curve
                this.quadraticCurveTo(...ctrl.coords, ...p2.coords);
            }, { ...opts });
        },
    },

    // 🔹 .drawCubicBezierCurveTo()
    drawCubicBezierCurveTo: {
        value: function(control1, control2, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path ?    (default: false)
            closePath = false,     // close subpath ?     (default: false)
            // fill/stroke
            fill = false,          // fill ?              (default: false)
            stroke = false,        // stroke ?            (default: false)
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.bezierCurveTo(
                    ...control1.coords, ...control2.coords, 
                    ...target.coords,
                );
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCubicBezierCurve()
    drawCubicBezierCurve: {
        value: function(p1, c1, c2, p2, {
            ...opts
        }={}){
            // curve specific
            //      ╭───────────────── default ────────────────╮
            opts = {newPath: true, fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                // move to start point 
                this.moveToPoint(p1);
                // connect bezier curve
                this.bezierCurveTo(
                    ...c1.coords, ...c2.coords, 
                    ...p2.coords,
                );
            }, { ...opts });
        },
    },

    // --------------------------
    //     polygon
    // --------------------------

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {...opts}={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { ...opts });
        },
    },

    // 🔹 .drawPolygon()
    drawPolygon: {
        value: function(n, {
            // polygon specific
            center = vec(100, 100),   // 👔 Vector
            radius = 50,
            rotate = 0,               // rotation
            clockwise = true,         // path direction
            // path: general
            ...opts
        }={}){
            this.drawPath(() => {
                
                // dθ = ± 2π / n
                const dt = 2 * Math.PI / n * (clockwise ? 1 : -1);
                // start angle
                const a0 = -90 * deg + rotate + (n.isEven ? dt/2 : 0);

                // vertices
                const points = range(0, n-1)
                    .map(i => center.plus(polar(radius, a0 + dt * i)))
                    .array;
                
                // draw polyline
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
                
            }, { ...opts });
        },
    },

    // 🔹 .drawPolyline()
    drawPolyline: {
        value: function(points, {
            ...opts
        }={}){
            // polyline specific
            //      ╭───────── default ─────────╮
            opts = {fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
            }, { ...opts });
            
        },
    },
    
    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline specific
            start,                 // start point
            newPath = false,       // start new path?
            closePath = false,
            // path: general
            ...opts
        }={}){
            // polyline specific
            if (start) newPath = true;    // new subpath if has start point
            //      ╭───────── default ─────────╮
            opts = {fill: false, closePath: false, ...opts, newPath};
            // draw polyline to ...
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start);
                // connect arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { ...opts });
        },
    },
    
});

// ⭐ export
// -----------------------------------------------------------------
export {};        // ES module export
```

{% endtab %}

{% tab title="💈範例" %}

* [regular polygons](/web/browser/canvas/examples/polygon.md)

* (this example)

```javascript
const { log } = console
const { sqrt } = Math;

// ⭐️ import
import { $ } from './js/ext/Node_ext.js';        // 👔 Node + ext
import { deg } from './js/ext/Number+ext.js';    // 👔 Number+ext
import { size } from './js/ext/Size.js';         // 👔 Size
import { rect } from './js/ext/Rect.js';         // 👔 Rect
import { vec, polar, linearCombination as lc } from './js/ext/Vector.js'; // 👔 Vector
import {  } from './js/ext/Element+boxes.js';    // 👔 Element + boxes
import {  } from './js/ext/Canvas+ext.js';       // 👔 Canvas + ext
import {  } from './js/ext/MouseEvent+ext.js';   // 👔 MouseEvent + ext
import { range } from './js/ext/Iterable+ext.js';   // 👔 Iterable + ext
// --------------------------------------------------------------------

// elements
const canvas = $('#canvas');

// canvas
const {width: W, height: H} = canvas.paddingBox;
const ROWS = 3, COLS = 3;

// layout
const dx = 20, dy = 20;             // padding
const w = 120, h = 120;    // box size

const STEPX = w + dx;                // translation
const STEPY = h + dy;                // translation

// expected canvas size
const SIZE = size(COLS*STEPX + dx, ROWS*STEPY + dy);  
log(`${ROWS} rows x ${COLS} cols: needs canvas size ${SIZE}`);

const u = vec(STEPX, 0);    // vectors for translation
const v = vec(0, STEPY);    // vectors for translation

const INSET = 10;

// styles
const HUE = 30;

// first rect
const p0 = vec(dx, dy);    // origin of first rect
const diag = vec(w, h);    // diagonal vector
const rect0 = rect(p0, diag);

const p1 = p0.plus(STEPX, 0);

// rects
const rects = range(0,2).map(i => 
    range(0,2)
        .map(j => rect0.translate(lc([j,i], [u,v])))  // 👔 Rect (v1.1)
        .array
).array;

// draw rects
canvas.draw2D(ctx => {

    // ---------
    //   inset
    // ---------
    
    // positive inset
    range(0, 4).forEach(i => {                // 👔 Iterable + ext
        ctx.drawRect(rects[0][0].inset(INSET * i), {
            fillStyle: `hsl(${HUE * i ** 1.3} 90% 50%)`
        });
    })

    ctx.drawRect(rects[0][1], {dash: [5, 5]});

    // ⭐️ negative inset
    ctx.drawRect(rects[0][1].inset(-10), {fillStyle: `#eee6`});

    // -----------
    //   polygon
    // -----------

    ctx.drawPolygon(4, {
        center: rects[0][2].center, 
        radius: w / sqrt(2),
    });
});

// ⭐ draw paths
canvas.draw2D(ctx => {            // 👔 Node+ext, 👔 Canvas+ext

    const c21 = rect0.translate(v).center;           // center (👔 Vector)
    const R = h/2 - 10;
    const r = 35;
    const d = 2*R + dx;                // traslation offset
    // const u = vec(d, 0);               // translation vector

    ctx.lineWidth = 4;                 // stroke width

    // ⭐ 1. polygon
    // --------------------------------------
    ctx.drawPolygon(6, {
        center: rects[1][0].center,
        radius: R+10, 
        fillStyle: 'yellow',
        dash: [],
    });

    // ⭐ circle
    // --------------------------------------
    ctx.drawCircle(rects[1][0].center, R-10, { fillStyle: 'red' });
    
    // ⭐ 2. ellipse 
    // --------------------------------------
    ctx.drawEllipse(rects[1][1].center, R, r, {
        rotation: 30*deg, start: 90*deg,           // 👔 Number+ext
        fillStyle: "orange",
    });

    // ⭐ 3. wedge 1 (bigger)
    // --------------------------------------
    ctx.drawWedge(rects[1][2].center, R, {
        end: 300*deg, fillStyle: 'yellow'
    });

    // ⭐ wedge 2 (smaller)
    const c23_1 = rects[1][2].center.plus(polar(dx, -29 * deg));
    ctx.drawWedge(c23_1, R, {start: -60*deg, end: 0, fillStyle: 'orange'});

    // ⭐ 4. partial rounded sqaure
    // --------------------------------------
    const rect31 = rects[2][0].inset(10);
    const p0 = rect31.top;                  // top middle
    const [p3, p2, p1, p4] = rect31.points; // BL, BR, TR, TL
    
    // begin in top middle.
    ctx.drawArcTo(p1, p2, 20, {start: p0, newPath: true});
    ctx.drawArcTo(p2, p3, 20);
    
    ctx.drawArcTo(p3, p4, 20, {
        closePath: true, 
        stroke: true, fill: true, 
        fillStyle: 'hsl(120 90% 50% / 70%)',    // green
    });

    // ⭐ 5. quadratic Bezier curve (1 control point)
    // --------------------------------------
    const p21 = p3.plus(u);
    const p22 = p21.plus(lc([0.3, -0.7],[u,v])); // control point
    const p23 = p21.plus(w, 0);
    const offset = vec(5,5);        // control point size, 👔 Vector

    ctx.drawQuadraticCurve(p21, p22, p23, {
        fill: true, 
        fillStyle: 'hsl(225 90% 50% / 70%)',    // blue
    }); 
    
    // ⭐ 6. cubic Bezier curve (2 control points)
    // --------------------------------------
    const p31 = p21.plus(d, -R);
    const p34 = p31.plus(2*R, 0);
    const p32 = p31.plus(30, -R);    // control point
    const p33 = p34.plus(-30, R);    // control point

    ctx.drawCubicBezierCurve(p31, p32, p33, p34, {
        fill: true, 
        fillStyle: 'hsl(270 90% 50% / 70%)',    // tranparent green
    });
    
    // ------------
    //   polyline
    // ------------

    const q1 = rects[0][2].bottomLeft.plus(10, -10), 
        q2 = rects[0][2].top.plus(0, 10), 
        q3 = rects[0][2].bottomRight.plus(-10, -10);
    
    ctx.lineWidth = 1;
    ctx.setLineDash([3, 6]);         // ⭐ dash line
    
    ctx.drawPolyline([p21, p22, p23]);   
    ctx.drawPolyline([q1, q2, q3]);
    ctx.drawPolyline([p31, p32, p33, p34]);

    // ⭐ return to solid lines
    ctx.setLineDash([]);        
    
    // draw control points
    [p21, p22, p23, p31, p32, p33, p34].forEach(p => {
        ctx.drawRect(rect(p, offset, { center: true }), { 
            fillStyle: 'tomato',
        });   
    });
    
});
```

{% endtab %}

{% tab title="🗺️ 圖表" %}
{% embed url="<https://replit.com/@pegasusroe/Canvasext-v11#index.js>" %}
{% endtab %}

{% tab title="🔴 主題" %}

* [drawOnCanvas2D()](/web/browser/canvas/+ext/drawoncanvas2d.md)
* [ctx.polyline()](/web/browser/canvas/+ext/ctx.polyline.md)
* [ctx.point()](/web/browser/canvas/+ext/ctx.point.md)
* [ctx.gradient()](/web/browser/canvas/state/styles/gradient/ctx.gradient.md)
* [regular polygons](/web/browser/canvas/examples/polygon.md) - add subpath
  {% endtab %}
  {% endtabs %}

## History

{% tabs %}
{% tab title="idx" %}

```
0: (•) first version
1: (+) .drawPolygon()
```

{% endtab %}

{% tab title="0" %}

```javascript
// 2023.01.20 - 23:36 
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
// -----------------------------------------------------------------


// ⭐ HTMLCanvasElement + extensions
// -----------------------------------------------------------------
// 🔹 .draw2D()
//
Object.defineProperties(HTMLCanvasElement.prototype, {
    // 🔹 .draw2D()
    draw2D: {
        value: function(draw){
            draw(this.getContext("2d"));
        },
    },
});

// ⭐ CanvasRenderingContext2D + extensions
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawArc()
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            if (newPath) this.beginPath();
            this.moveTo(...p.coords);        // 👔 Vector
        }
    },

    // ⭐ .drawPath()
    drawPath: {
        value: function(draw, {
            // path
            newPath = true,        // begin new subpath ?
            closePath = true,      // close subpath ?
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            
            // new subpath ?
            if (newPath) this.beginPath();    // start new path if necessary
            
            // draw path
            draw();

            // close path ?
            if (closePath) this.closePath();
            
            // fill ?
            if (fill) {
                if (fillStyle) this.fillStyle = fillStyle;
                this.fill();
            };
            
            // stroke ?
            if (stroke) {
                if (strokeStyle) this.strokeStyle = strokeStyle;
                this.stroke()
            };
            
        },
    },

    // --------------------------
    //     arc/circle related
    // --------------------------
    
    // 🔹 .drawArc()
    drawArc: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 2 * PI,         // end angle
            clockwise = true,
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCircle()
    drawCircle: {
        value: function(center, radius, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, 0, 2*PI);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawEllipse()
    drawEllipse: {
        value: function(center, radiusX, radiusY, {
            // path: ellipse related
            start = 0,
            end = 2 * PI,
            rotation = 0,
            clockwise = true,
            // path: general
            newPath = true,
            closePath = true,
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.ellipse(
                    ...center.coords, radiusX, radiusY, 
                    rotation, start, end, !clockwise
                );
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawWedge()
    drawWedge: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 90 * deg,       // end angle    // 👔 Number+ext
            clockwise = true,
            // path: general
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     bezier curve
    // --------------------------

    // 🔹 .drawQuadraticCurveTo()
    drawQuadraticCurveTo: {
        value: function(control, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.quadraticCurveTo(...control.coords, ...target.coords);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCubicBezierCurveTo()
    drawCubicBezierCurveTo: {
        value: function(control1, control2, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path ?    (default: false)
            closePath = false,     // close subpath ?     (default: false)
            // fill/stroke
            fill = false,          // fill ?              (default: false)
            stroke = false,        // stroke ?            (default: false)
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.bezierCurveTo(
                    ...control1.coords, ...control2.coords, 
                    ...target.coords,
                );
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     polygon
    // --------------------------

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

// ⭐ export
// -----------------------------------------------------------------
export {};        // ES module export
```

{% endtab %}

{% tab title="1" %}

```javascript
// 2023.01.21 - 08:14 (+) .drawPolygon()
// 2023.01.20 - 23:36 (•) first version
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
import { vec, polar } from './Vector.js';     // 👔 Vector
import { range } from './Iterable+ext.js';    // 👔 Iterable+ext
// -----------------------------------------------------------------


// ⭐ HTMLCanvasElement + extensions
// -----------------------------------------------------------------
// 🔹 .draw2D()
//
Object.defineProperties(HTMLCanvasElement.prototype, {
    // 🔹 .draw2D()
    draw2D: {
        value: function(draw){
            draw(this.getContext("2d"));
        },
    },
});

// ⭐ CanvasRenderingContext2D + extensions
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawArc()
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            if (newPath) this.beginPath();
            this.moveTo(...p.coords);        // 👔 Vector
        }
    },

    // ⭐ .drawPath()
    drawPath: {
        value: function(draw, {
            // path
            newPath = true,        // begin new subpath ?
            closePath = true,      // close subpath ?
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            
            // new subpath ?
            if (newPath) this.beginPath();    // start new path if necessary
            
            // draw path
            draw();

            // close path ?
            if (closePath) this.closePath();
            
            // fill ?
            if (fill) {
                if (fillStyle) this.fillStyle = fillStyle;
                this.fill();
            };
            
            // stroke ?
            if (stroke) {
                if (strokeStyle) this.strokeStyle = strokeStyle;
                this.stroke()
            };
            
        },
    },

    // --------------------------
    //     arc/circle related
    // --------------------------
    
    // 🔹 .drawArc()
    drawArc: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 2 * PI,         // end angle
            clockwise = true,
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCircle()
    drawCircle: {
        value: function(center, radius, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, 0, 2*PI);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawEllipse()
    drawEllipse: {
        value: function(center, radiusX, radiusY, {
            // path: ellipse related
            start = 0,
            end = 2 * PI,
            rotation = 0,
            clockwise = true,
            // path: general
            newPath = true,
            closePath = true,
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.ellipse(
                    ...center.coords, radiusX, radiusY, 
                    rotation, start, end, !clockwise
                );
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawWedge()
    drawWedge: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 90 * deg,       // end angle    // 👔 Number+ext
            clockwise = true,
            // path: general
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     bezier curve
    // --------------------------

    // 🔹 .drawQuadraticCurveTo()
    drawQuadraticCurveTo: {
        value: function(control, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.quadraticCurveTo(...control.coords, ...target.coords);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCubicBezierCurveTo()
    drawCubicBezierCurveTo: {
        value: function(control1, control2, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path ?    (default: false)
            closePath = false,     // close subpath ?     (default: false)
            // fill/stroke
            fill = false,          // fill ?              (default: false)
            stroke = false,        // stroke ?            (default: false)
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.bezierCurveTo(
                    ...control1.coords, ...control2.coords, 
                    ...target.coords,
                );
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     polygon
    // --------------------------

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolygon()
    drawPolygon: {
        value: function(n, {
            // polygon specific
            center = vec(100, 100),   // 👔 Vector
            radius = 50,
            rotate = 0,               // rotation
            clockwise = true,         // path direction
            // path: general
            newPath = true,           // start new path?
            closePath = true,
            // fill/stroke
            fill = true,              // fill?
            stroke = true,            // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                
                // dθ = ± 2π / n
                const dt = 2 * Math.PI / n * (clockwise ? 1 : -1);   
                const a0 = -90 * deg + rotate;        // start angle

                // vertices
                const points = range(0, n-1)
                    .map(i => center.plus(polar(radius, a0 + dt * i)))
                    .array;
                
                // draw polyline
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
                
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

// ⭐ export
// -----------------------------------------------------------------
export {};        // ES module export
```

{% endtab %}

{% tab title="2" %}

* replit ⟩ [Canvas+ext](https://replit.com/@pegasusroe/vector#js/Canvas+ext.js) ,  -> require [Rect](/web/appendix/custom/class/rect.md), [Vector](/web/appendix/custom/class/vector.md), [Number+ext](/web/js/val/prim/num/number+ext.md), [Iterable+ext](/web/js/iteration/iterable+ext.md)

```javascript
// 2023.01.21 - 09:02 (/) .drawPolygon() - fix start angle
// 2023.01.21 - 08:14 (+) .drawPolygon()
// 2023.01.20 - 23:36 (•) first version
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
import { vec, polar } from './Vector.js';     // 👔 Vector
import { range } from './Iterable+ext.js';    // 👔 Iterable+ext
// -----------------------------------------------------------------


// ⭐ HTMLCanvasElement + extensions
// -----------------------------------------------------------------
// 🔹 .draw2D()
//
Object.defineProperties(HTMLCanvasElement.prototype, {
    // 🔹 .draw2D()
    draw2D: {
        value: function(draw){
            draw(this.getContext("2d"));
        },
    },
});

// ⭐ CanvasRenderingContext2D + extensions
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawArc()
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            if (newPath) this.beginPath();
            this.moveTo(...p.coords);        // 👔 Vector
        }
    },

    // ⭐ .drawPath()
    drawPath: {
        value: function(draw, {
            // path
            newPath = true,        // begin new subpath ?
            closePath = true,      // close subpath ?
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            
            // new subpath ?
            if (newPath) this.beginPath();    // start new path if necessary
            
            // draw path
            draw();

            // close path ?
            if (closePath) this.closePath();
            
            // fill ?
            if (fill) {
                if (fillStyle) this.fillStyle = fillStyle;
                this.fill();
            };
            
            // stroke ?
            if (stroke) {
                if (strokeStyle) this.strokeStyle = strokeStyle;
                this.stroke()
            };
            
        },
    },

    // --------------------------
    //     arc/circle related
    // --------------------------
    
    // 🔹 .drawArc()
    drawArc: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 2 * PI,         // end angle
            clockwise = true,
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCircle()
    drawCircle: {
        value: function(center, radius, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, 0, 2*PI);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawEllipse()
    drawEllipse: {
        value: function(center, radiusX, radiusY, {
            // path: ellipse related
            start = 0,
            end = 2 * PI,
            rotation = 0,
            clockwise = true,
            // path: general
            newPath = true,
            closePath = true,
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.ellipse(
                    ...center.coords, radiusX, radiusY, 
                    rotation, start, end, !clockwise
                );
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawWedge()
    drawWedge: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 90 * deg,       // end angle    // 👔 Number+ext
            clockwise = true,
            // path: general
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     bezier curve
    // --------------------------

    // 🔹 .drawQuadraticCurveTo()
    drawQuadraticCurveTo: {
        value: function(control, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.quadraticCurveTo(...control.coords, ...target.coords);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCubicBezierCurveTo()
    drawCubicBezierCurveTo: {
        value: function(control1, control2, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path ?    (default: false)
            closePath = false,     // close subpath ?     (default: false)
            // fill/stroke
            fill = false,          // fill ?              (default: false)
            stroke = false,        // stroke ?            (default: false)
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.bezierCurveTo(
                    ...control1.coords, ...control2.coords, 
                    ...target.coords,
                );
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // --------------------------
    //     polygon
    // --------------------------

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolygon()
    drawPolygon: {
        value: function(n, {
            // polygon specific
            center = vec(100, 100),   // 👔 Vector
            radius = 50,
            rotate = 0,               // rotation
            clockwise = true,         // path direction
            // path: general
            newPath = true,           // start new path?
            closePath = true,
            // fill/stroke
            fill = true,              // fill?
            stroke = true,            // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                
                // dθ = ± 2π / n
                const dt = 2 * Math.PI / n * (clockwise ? 1 : -1);
                // start angle
                const a0 = -90 * deg + rotate + (n.isEven ? dt/2 : 0);

                // vertices
                const points = range(0, n-1)
                    .map(i => center.plus(polar(radius, a0 + dt * i)))
                    .array;
                
                // draw polyline
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
                
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

// ⭐ export
// -----------------------------------------------------------------
export {};        // ES module export
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/web/browser/canvas/+ext.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
