# Rect

[custom](/web/appendix/custom.md) ⟩ [class](/web/appendix/custom/class.md) ⟩ 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: [custom](/web/appendix/custom.md)
{% endhint %}

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

* replit ⟩ [Rect (v1.1)](https://replit.com/@pegasusroe/Rect-v11#js/ext/Rect.js) ,  require -> [Vector](/web/appendix/custom/class/vector.md), [Size](/web/appendix/custom/class/size.md)

```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](/files/Gr2XqqGZMhlxjlTwhn9s)

: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>](/web/browser/canvas.md) ⟩ [clipping](/web/browser/canvas/state/clipping.md)
* [Element+boxes](/web/browser/dom/type/element/+boxes.md)
* [Canvas+ext](/web/browser/canvas/+ext.md)
  {% endtab %}

{% tab title="👥 相關" %}

* [\<canvas>](/web/browser/canvas.md) ⟩ [rectangle](/web/browser/canvas/rectangle.md)
* [box models](/web/browser/dom/type/element/boxes.md)
  {% 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](/web/appendix/custom/class/vector.md), [Size](/web/appendix/custom/class/size.md)

```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 %}


---

# 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/appendix/custom/class/rect.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.
