# Rect

[custom](..) ⟩ [class]() ⟩ Rect &#x20;

{% hint style="success" %}
an object that has <mark style="color:yellow;">**origin**</mark> (x, y) and <mark style="color:yellow;">**size**</mark> (width, height).

:point\_right: [..](.. "mention")
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}
* replit ⟩ [Rect (v1.1)](https://replit.com/@pegasusroe/Rect-v11#js/ext/Rect.js) ,  require -> [vector](vector "mention"), [size](size "mention")

```javascript
// (Rect 1.1)
// 2023.01.22 - 22:57 (+) .points    
//                    (+) .topRight, .topLeft, .top, .bottom, .left, .right
// 2023.01.22 - 20:18 (+) .translate()
// 2023.01.21 - 13:38 (+) .inset()
// 2023.01.18 - 14:33 (+) .toString()
// 2023.01.18 - 09:01 (/) refactor rect()
// 2023.01.17 - 22:19 (•) first draft
// -------------------------------------------------

const {min, abs} = Math;

// ⭐️ import
// -------------------------------------------------
import {vec} from './Vector.js';        // 👔 Vector
import {Size} from './Size.js';         // 👔 Size

// ⭐️ Rect
// -------------------------------------------------
// - new Rect(x, y, w, h)
// - rect(x, y, w, h)
// - rect(corner, offset)
// - rect(center, offset, true)
// -------------------------------------------------
// 🔸 .x, .y, .width, .height
// 🔸 .coords                    - [x, y, w, h]
// 🔸 .size
// -------------------------------------------------
// 🔸 .origin, .offset           - vectors
// 🔸 .center, .bottomLeft, .bottomRight
// 🔸 .bottomLeft, .bottomRight, .topLeft, .topRight
// 🔸 .top
// 🔸 .points
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .inset()
// 🔹 .translate()
// -------------------------------------------------
// 🔹 .toString()
// 

class Rect {

    // init
    constructor(
        x, y,            // a corner point, not necessarily the top-left corner
        width, height    // a "vector" pointing to the opposite corner
    ) {
        // top-left corner
        this.x = min(x, x + width);
        this.y = min(y, y + height);
        // always positive width/height
        this.width  = abs(width);
        this.height = abs(height);
    }

    // ------------------------
    //     size related
    // ------------------------

    // 🔸 .size
    get size() { 
        return new Size(this.width, this.height)         // 👔 Size
    }

    // 🔸 .halfWidth, .halfHeight
    get halfWidth()  { return this.width / 2 }
    get halfHeight() { return this.height / 2 }

    // ------------------------
    //     vectors / points
    // ------------------------

    // 🔸 .origin (topLeft)
    get origin() { return vec(this.x, this.y) }            // 👔 Vector
    get topLeft() { return this.origin } 
    
    // 🔸 .origin, .offset, .halfOffset
    get offset() { return vec(this.width, this.height) }   // 👔 Vector
    get halfOffset() { return this.offset.times(1/2) }     // 👔 Vector
    
    // 🔸 .center
    get center() { return this.origin.plus(this.halfOffset) }
    
    // 🔸 .bottomLeft
    get bottomLeft() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(-w, h);
    }

    // 🔸 .bottomRight
    get bottomRight() { 
        return this.origin.plus(this.offset);              // 👔 Vector
    }

    // 🔸 .topRight
    get topRight() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(w, -h);
    }

    // 🔸 .top
    get top() { 
        return this.center.plus(0, -this.halfHeight);
    }

    // 🔸 .bottom
    get bottom() { 
        return this.center.plus(0, this.halfHeight);
    }

    // 🔸 .left
    get left() { 
        return this.center.plus(-this.halfWidth, 0);
    }

    // 🔸 .right
    get right() { 
        return this.center.plus(this.halfWidth, 0);
    }

    // 🔸 .points
    get points() { 
        
        const {
            bottomLeft: p0, bottomRight: p1,
            topRight: p2, topLeft: p3,
        } = this;
        
        return [p0, p1, p2, p3];
    }

    // ------------------------
    //     rect operations
    // ------------------------

    // 🔹 .inset()
    inset(dx, dy = dx) {
        const v = vec(dx, dy);
        // new origin: o + v
        const origin = this.origin.plus(v);
        // new corner: o + diag - v     (diag = this.offset)
        // new offset = (o + diag - v) - (o + v) = diag - 2v
        const offset = this.offset.plus(v.times(-2));
        return rect(origin, offset);
    }

    // 🔹 .translate()
    translate(...args) {
        const v = vec(...args);    // vec(v) or vec(x, y)
        return rect(this.origin.plus(v), this.offset);
    }

    // ------------------------
    //     helpers / debug
    // ------------------------

    // rect.coords
    get coords() {
        const {x, y, width, height} = this;
        return [x, y, width, height];
    }

    // 🔹 .toString()
    toString() {
        return `(${this.coords})`;
    }
    
}

// convenience factory functions

// 🔸 rect()
function rect(...args) { 
    const len = args.length;
    switch (len) {
            
        // treat as (x, y, width, height)
        case 4: return new Rect(...args);
            
        // treat as (point, offset, center?)
        case 2:
        case 3:
            const [point, offset, center] = args;
            // `point` is corner
            if (!center) return new Rect(...point.coords, ...offset.coords);
            // `point` is center
            const corner = point.plus(offset);
            const offsetToOppositeCorner = offset.times(-2);
            return rect(corner, offsetToOppositeCorner);
            
        default:
            const msg = [
                `❌ rect(${args})`,
                `• expecting 2/3/4 arguments, but got ${len}.`,
                ``,
                `💡 rect() syntax:`,
                `------------------`,
                `• rect(x, y, width, height)`,
                `• rect(corner, offset): "corner" and "offset" are vectors.`,
                `• rect(center, offset, true): "true" means "center" is a center point.`,
            ];
            throw new Error(msg.join('\n'));
    }
    
}

// export
export {Rect, rect};    // ES module export
```
{% endtab %}

{% tab title="💈範例" %}
![fill/stroke rectangles by Rect](https://2527454625-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MfvEFZnSBhKT6fJmus0%2Fuploads%2FPdZWnw8qKSQAHLl8svES%2Fclipping.png?alt=media\&token=3f3306bc-02aa-4b3f-b06f-73cbe028f524)

:floppy\_disk: replit：[Rect](https://replit.com/@pegasusroe/JS-Rect#script.js)

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

function deg(x) { return PI * x / 180 }   // degrees

// ⭐️ import
import {vec, polar} from './js/Vector.js';    // Vector
import {rect} from './js/Rect.js';            // Rect

// 🔸 ctx.polygon(): regular polygon (subpath)
//
// parameters: 
// - n   : number of sides
// - x, y: center
// - r   : radius
//
// require: 
// - Vector, vec(), polar()
CanvasRenderingContext2D.prototype.polygon = function(n, x, y, r, {
    startAngle = -Math.PI / 2,     // starting angle
    clockwise = true,            // clockwise by default
} = {}) {

    const center = vec(x, y);     // center of polygon
    const delta = 2 * Math.PI / n * (clockwise ? 1 : -1);    // dθ = ± 2π / n

    // begin new subpath (at first vertex)
    this.moveTo(...center.plus(polar(r, startAngle)).coords);

    // connect first (n-1) sides
    for (let i = 1; i < n; i++) {
        this.lineTo(...center.plus(polar(r, startAngle + delta * i)).coords);
    }

    // connect back to first vertex
    this.closePath();
};

// ⭐ draw on <canvas> 2D context
function drawOnCanvas2D(selector, draw) {
    let canvas = document.querySelector(selector);
    if (!canvas) { console.log(`⛔ no such element: "${selector}"`); return; }
    let context = canvas.getContext("2d");
    if (draw) draw(context);
}

// ------------------
//        main
// ------------------

// draw on canvas
drawOnCanvas2D('#playground', (c) => {

    // drawing settings
    const center = vec(200, 225);    // triangle center
    const R = 200, r = 80;          // radii (large/small)

    // text
    const text = "<canvas>";
    const textHeight = (R - r)/2;
    
    c.font = `bold ${textHeight}pt sans-serif`;    // Big font
    
    const textWidth = c.measureText(text).width;
    const textCenter = center.plus(0, R / 2 - R / 16);
    const textRect = rect(textCenter, vec(textWidth/2, textHeight/2), {center: true});
    log(`text size: ${textRect.size}`);
    
    c.strokeRect(...textRect.coords);
    c.strokeText(text, ...textRect.bottomLeft.coords);
    
    // vertical stripe down the middle 
    const stripCenter = center.plus(0, -R/4);    // center of triangle height
    const stripHalfDimension = vec(40, .85*R);
    const strip = rect(stripCenter, stripHalfDimension, {center: true});
    
    c.strokeRect(...strip.coords);

    // create path for clipping region
    c.polygon(3, ...center.coords, R);
    c.polygon(3, ...center.coords, r, { clockwise: false });

    // ⭐ make that path the clipping region
    c.clip();

    c.fillStyle = 'hsl(0 80% 10% / 0.05)';
    c.fill();
    c.stroke();        // ⭐ half of line width will be clipped away
    
    // fill strip (inside the clipping region)
    c.fillStyle = "hsl(270 80% 50% / 0.8)";
    c.fillRect(...strip.coords);
    
    // fill text (inside the clipping region)
    c.fillStyle = "hsl(330 80% 50% / 0.8)";
    c.fillText(text, ...textRect.bottomLeft.coords);
});

// ⭐️ log
// -------------------------------------------------------
;[
    '😃 Have a Good Day!',

].forEach(x => log(x));
```
{% endtab %}

{% tab title="⬇️ 應用" %}
* [canvas](../../../browser/canvas "mention") ⟩ [clipping](../../../browser/canvas/state/clipping "mention")
* [+boxes](../../../browser/dom/type/element/+boxes "mention")
* [+ext](../../../browser/canvas/+ext "mention")
{% endtab %}

{% tab title="👥 相關" %}
* [canvas](../../../browser/canvas "mention") ⟩ [rectangle](../../../browser/canvas/rectangle "mention")
* [boxes](../../../browser/dom/type/element/boxes "mention")
{% endtab %}
{% endtabs %}

## History

{% tabs %}
{% tab title="idx" %}
```
0: (?) first recorded
1: (+) refactor, .toString()
2: (+) .inset()
```
{% endtab %}

{% tab title="0" %}
```javascript
// ------------------
//     ⭐️ Rect
// ------------------

class Rect {

    // init
    constructor(
        x, y,            // a corner point, not necessarily the top-left corner
        width, height    // a "vector" pointing to the opposite corner
    ) {
        // top-left corner
        this.x = Math.min(x, x + width);
        this.y = Math.min(y, y + height);
        // always positive width/height
        this.width  = Math.abs(width);
        this.height = Math.abs(height);
    }

    // rect.coords
    get coords() {
        const {x, y, width, height} = this;
        return [x, y, width, height];
    }

    // convenience properties
    get halfWidth()  { return this.width / 2 }
    get halfHeight() { return this.height / 2 }

    // key points/vectors
    get origin() { return vec(this.x, this.y) }
    get offset() { return vec(this.width, this.height) }
    get halfOffset() { return this.offset.times(1/2) }
    
    get center() { return this.origin.add(this.halfOffset) }
    get bottomLeft() { return this.center.plus(-this.halfWidth, this.halfHeight) }
    
}

// convenience factory functions
function rect(corner, offset) { return new Rect(...corner.coords, ...offset.coords) }

// 🔸 Rect.withCenter()
Rect.withCenter = function(
    center,    // center of rect
    offset     // offset "vector" to a corner
) {
    const corner = center.add(offset);
    const offsetToOppositeCorner = offset.times(-2);
    return rect(corner, offsetToOppositeCorner);
};
```
{% endtab %}

{% tab title="1" %}
```javascript
// 2023.01.18 - 14:33 (+) .toString()
// 2023.01.18 - 09:01 (/) refactor rect()
// 2023.01.17 - 22:19 (•) first draft
// -------------------------------------------------

const {min, abs} = Math;

// ⭐️ import
// -------------------------------------------------
import {vec} from './Vector.js';        // 👔 Vector
import {Size} from './Size.js';         // 👔 Size

// ⭐️ Rect
// -------------------------------------------------
// - new Rect(x, y, w, h)
// - rect(x, y, w, h)
// - rect(corner, offset)
// - rect(center, offset, true)
// -------------------------------------------------
// .x, .y, .width, .height
// .coords                    - [x, y, w, h]
// .size
// .origin, .offset
// .center, .bottomLeft
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .toString()
// 

class Rect {

    // init
    constructor(
        x, y,            // a corner point, not necessarily the top-left corner
        width, height    // a "vector" pointing to the opposite corner
    ) {
        // top-left corner
        this.x = min(x, x + width);
        this.y = min(y, y + height);
        // always positive width/height
        this.width  = abs(width);
        this.height = abs(height);
    }

    // rect.coords
    get coords() {
        const {x, y, width, height} = this;
        return [x, y, width, height];
    }

    // .size
    get size() { 
        return new Size(this.width, this.height)         // 👔 Size
    }

    // .halfWidth, .halfHeight
    get halfWidth()  { return this.width / 2 }
    get halfHeight() { return this.height / 2 }

    // .origin, .offset, .halfOffset
    get origin() { return vec(this.x, this.y) }            // 👔 Vector
    get offset() { return vec(this.width, this.height) }   // 👔 Vector
    get halfOffset() { return this.offset.times(1/2) }     // 👔 Vector
    
    // .center, .bottomLeft
    get center() { return this.origin.plus(this.halfOffset) }    // 👔 Vector
    
    get bottomLeft() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(-w, h);                    // 👔 Vector
    }

    // 🔹 .toString()
    toString() {
        return `(${this.coords})`;
    }
    
}

// convenience factory functions

// 🔸 rect()
function rect(...args) { 
    const len = args.length;
    switch (len) {
            
        // treat as (x, y, width, height)
        case 4: return new Rect(...args);
            
        // treat as (point, offset, center?)
        case 2:
        case 3:
            const [point, offset, center] = args;
            // `point` is corner
            if (!center) return new Rect(...point.coords, ...offset.coords);
            // `point` is center
            const corner = point.plus(offset);
            const offsetToOppositeCorner = offset.times(-2);
            return rect(corner, offsetToOppositeCorner);
            
        default:
            const msg = [
                `❌ rect(${args})`,
                `• expecting 2/3/4 arguments, but got ${len}.`,
                ``,
                `💡 rect() syntax:`,
                `------------------`,
                `• rect(x, y, width, height)`,
                `• rect(corner, offset): "corner" and "offset" are vectors.`,
                `• rect(center, offset, true): "true" means "center" is a center point.`,
            ];
            throw new Error(msg.join('\n'));
    }
    
}

// export
export {Rect, rect};    // ES module export
```
{% endtab %}

{% tab title="2" %}
* replit ⟩ [various boxes](https://replit.com/@pegasusroe/various-boxes#js/Rect.js) ,  require -> [vector](vector "mention"), [size](size "mention")

```javascript
// 2023.01.21 - 13:38 (+) .inset()
// 2023.01.18 - 14:33 (+) .toString()
// 2023.01.18 - 09:01 (/) refactor rect()
// 2023.01.17 - 22:19 (•) first draft
// -------------------------------------------------

const {min, abs} = Math;

// ⭐️ import
// -------------------------------------------------
import {vec} from './Vector.js';        // 👔 Vector
import {Size} from './Size.js';         // 👔 Size

// ⭐️ Rect
// -------------------------------------------------
// - new Rect(x, y, w, h)
// - rect(x, y, w, h)
// - rect(corner, offset)
// - rect(center, offset, true)
// -------------------------------------------------
// 🔸 .x, .y, .width, .height
// 🔸 .coords                    - [x, y, w, h]
// 🔸 .size
// 🔸 .origin, .offset           - vectors
// 🔸 .center, .bottomLeft, .bottomRight
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .inset()
// -------------------------------------------------
// 🔹 .toString()
// 

class Rect {

    // init
    constructor(
        x, y,            // a corner point, not necessarily the top-left corner
        width, height    // a "vector" pointing to the opposite corner
    ) {
        // top-left corner
        this.x = min(x, x + width);
        this.y = min(y, y + height);
        // always positive width/height
        this.width  = abs(width);
        this.height = abs(height);
    }

    

    // ------------------------
    //     size related
    // ------------------------

    // 🔸 .size
    get size() { 
        return new Size(this.width, this.height)         // 👔 Size
    }

    // 🔸 .halfWidth, .halfHeight
    get halfWidth()  { return this.width / 2 }
    get halfHeight() { return this.height / 2 }

    // ------------------------
    //     vectors / points
    // ------------------------

    // 🔸 .origin, .offset, .halfOffset
    get origin() { return vec(this.x, this.y) }            // 👔 Vector
    get offset() { return vec(this.width, this.height) }   // 👔 Vector
    get halfOffset() { return this.offset.times(1/2) }     // 👔 Vector
    
    // 🔸 .center
    get center() { return this.origin.plus(this.halfOffset) }    // 👔 Vector
    
    // 🔸 .bottomLeft
    get bottomLeft() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(-w, h);                    // 👔 Vector
    }

    // 🔸 .bottomRight
    get bottomRight() { 
        return this.origin.plus(this.offset);              // 👔 Vector
    }

    // ------------------------
    //     rect
    // ------------------------

    // 🔹 .inset()
    inset(dx, dy = dx) {
        const v = vec(dx, dy);
        // new origin: o + v
        const origin = this.origin.plus(v);
        // new corner: o + diag - v     (diag = this.offset)
        // new offset = (o + diag - v) - (o + v) = diag - 2v
        const offset = this.offset.plus(v.times(-2));
        return rect(origin, offset);
    }

    // ------------------------
    //     helpers / debug
    // ------------------------

    // rect.coords
    get coords() {
        const {x, y, width, height} = this;
        return [x, y, width, height];
    }

    // 🔹 .toString()
    toString() {
        return `(${this.coords})`;
    }
    
}

// convenience factory functions

// 🔸 rect()
function rect(...args) { 
    const len = args.length;
    switch (len) {
            
        // treat as (x, y, width, height)
        case 4: return new Rect(...args);
            
        // treat as (point, offset, center?)
        case 2:
        case 3:
            const [point, offset, center] = args;
            // `point` is corner
            if (!center) return new Rect(...point.coords, ...offset.coords);
            // `point` is center
            const corner = point.plus(offset);
            const offsetToOppositeCorner = offset.times(-2);
            return rect(corner, offsetToOppositeCorner);
            
        default:
            const msg = [
                `❌ rect(${args})`,
                `• expecting 2/3/4 arguments, but got ${len}.`,
                ``,
                `💡 rect() syntax:`,
                `------------------`,
                `• rect(x, y, width, height)`,
                `• rect(corner, offset): "corner" and "offset" are vectors.`,
                `• rect(center, offset, true): "true" means "center" is a center point.`,
            ];
            throw new Error(msg.join('\n'));
    }
    
}

// export
export {Rect, rect};    // ES module export
```
{% endtab %}
{% endtabs %}
