Bug 1838629 - Part 7: Inline nursery cell allocation method in Allocator.h r=sfink
[gecko.git] / toolkit / modules / Geometry.sys.mjs
blob1eae68ce2a22508f142a7f1ce273148a9eee7bb4
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6  * Simple Point class.
7  *
8  * Any method that takes an x and y may also take a point.
9  */
10 export function Point(x, y) {
11   this.set(x, y);
14 Point.prototype = {
15   clone: function clone() {
16     return new Point(this.x, this.y);
17   },
19   set: function set(x, y) {
20     this.x = x;
21     this.y = y;
22     return this;
23   },
25   equals: function equals(x, y) {
26     return this.x == x && this.y == y;
27   },
29   toString: function toString() {
30     return "(" + this.x + "," + this.y + ")";
31   },
33   map: function map(f) {
34     this.x = f.call(this, this.x);
35     this.y = f.call(this, this.y);
36     return this;
37   },
39   add: function add(x, y) {
40     this.x += x;
41     this.y += y;
42     return this;
43   },
45   subtract: function subtract(x, y) {
46     this.x -= x;
47     this.y -= y;
48     return this;
49   },
51   scale: function scale(s) {
52     this.x *= s;
53     this.y *= s;
54     return this;
55   },
57   isZero() {
58     return this.x == 0 && this.y == 0;
59   },
62 (function () {
63   function takePointOrArgs(f) {
64     return function (arg1, arg2) {
65       if (arg2 === undefined) {
66         return f.call(this, arg1.x, arg1.y);
67       }
68       return f.call(this, arg1, arg2);
69     };
70   }
72   for (let f of ["add", "subtract", "equals", "set"]) {
73     Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
74   }
75 })();
77 /**
78  * Rect is a simple data structure for representation of a rectangle supporting
79  * many basic geometric operations.
80  *
81  * NOTE: Since its operations are closed, rectangles may be empty and will report
82  * non-positive widths and heights in that case.
83  */
85 export function Rect(x, y, w, h) {
86   this.left = x;
87   this.top = y;
88   this.right = x + w;
89   this.bottom = y + h;
92 Rect.fromRect = function fromRect(r) {
93   return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
96 Rect.prototype = {
97   get x() {
98     return this.left;
99   },
100   get y() {
101     return this.top;
102   },
103   get width() {
104     return this.right - this.left;
105   },
106   get height() {
107     return this.bottom - this.top;
108   },
109   set x(v) {
110     let diff = this.left - v;
111     this.left = v;
112     this.right -= diff;
113   },
114   set y(v) {
115     let diff = this.top - v;
116     this.top = v;
117     this.bottom -= diff;
118   },
119   set width(v) {
120     this.right = this.left + v;
121   },
122   set height(v) {
123     this.bottom = this.top + v;
124   },
126   isEmpty: function isEmpty() {
127     return this.left >= this.right || this.top >= this.bottom;
128   },
130   setRect(x, y, w, h) {
131     this.left = x;
132     this.top = y;
133     this.right = x + w;
134     this.bottom = y + h;
136     return this;
137   },
139   setBounds(l, t, r, b) {
140     this.top = t;
141     this.left = l;
142     this.bottom = b;
143     this.right = r;
145     return this;
146   },
148   equals: function equals(other) {
149     return (
150       other != null &&
151       ((this.isEmpty() && other.isEmpty()) ||
152         (this.top == other.top &&
153           this.left == other.left &&
154           this.bottom == other.bottom &&
155           this.right == other.right))
156     );
157   },
159   clone: function clone() {
160     return new Rect(
161       this.left,
162       this.top,
163       this.right - this.left,
164       this.bottom - this.top
165     );
166   },
168   center: function center() {
169     if (this.isEmpty()) {
170       throw new Error("Empty rectangles do not have centers");
171     }
172     return new Point(
173       this.left + (this.right - this.left) / 2,
174       this.top + (this.bottom - this.top) / 2
175     );
176   },
178   copyFrom(other) {
179     this.top = other.top;
180     this.left = other.left;
181     this.bottom = other.bottom;
182     this.right = other.right;
184     return this;
185   },
187   translate(x, y) {
188     this.left += x;
189     this.right += x;
190     this.top += y;
191     this.bottom += y;
193     return this;
194   },
196   toString() {
197     return (
198       "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"
199     );
200   },
202   /** return a new rect that is the union of that one and this one */
203   union(other) {
204     return this.clone().expandToContain(other);
205   },
207   contains(other) {
208     if (other.isEmpty()) {
209       return true;
210     }
211     if (this.isEmpty()) {
212       return false;
213     }
215     return (
216       other.left >= this.left &&
217       other.right <= this.right &&
218       other.top >= this.top &&
219       other.bottom <= this.bottom
220     );
221   },
223   intersect(other) {
224     return this.clone().restrictTo(other);
225   },
227   intersects(other) {
228     if (this.isEmpty() || other.isEmpty()) {
229       return false;
230     }
232     let x1 = Math.max(this.left, other.left);
233     let x2 = Math.min(this.right, other.right);
234     let y1 = Math.max(this.top, other.top);
235     let y2 = Math.min(this.bottom, other.bottom);
236     return x1 < x2 && y1 < y2;
237   },
239   /** Restrict area of this rectangle to the intersection of both rectangles. */
240   restrictTo: function restrictTo(other) {
241     if (this.isEmpty() || other.isEmpty()) {
242       return this.setRect(0, 0, 0, 0);
243     }
245     let x1 = Math.max(this.left, other.left);
246     let x2 = Math.min(this.right, other.right);
247     let y1 = Math.max(this.top, other.top);
248     let y2 = Math.min(this.bottom, other.bottom);
249     // If width or height is 0, the intersection was empty.
250     return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
251   },
253   /** Expand this rectangle to the union of both rectangles. */
254   expandToContain: function expandToContain(other) {
255     if (this.isEmpty()) {
256       return this.copyFrom(other);
257     }
258     if (other.isEmpty()) {
259       return this;
260     }
262     let l = Math.min(this.left, other.left);
263     let r = Math.max(this.right, other.right);
264     let t = Math.min(this.top, other.top);
265     let b = Math.max(this.bottom, other.bottom);
266     return this.setRect(l, t, r - l, b - t);
267   },
269   /**
270    * Expands to the smallest rectangle that contains original rectangle and is bounded
271    * by lines with integer coefficients.
272    */
273   expandToIntegers: function round() {
274     this.left = Math.floor(this.left);
275     this.top = Math.floor(this.top);
276     this.right = Math.ceil(this.right);
277     this.bottom = Math.ceil(this.bottom);
278     return this;
279   },
281   scale: function scale(xscl, yscl) {
282     this.left *= xscl;
283     this.right *= xscl;
284     this.top *= yscl;
285     this.bottom *= yscl;
286     return this;
287   },
289   map: function map(f) {
290     this.left = f.call(this, this.left);
291     this.top = f.call(this, this.top);
292     this.right = f.call(this, this.right);
293     this.bottom = f.call(this, this.bottom);
294     return this;
295   },
297   /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
298   translateInside: function translateInside(other) {
299     let offsetX = 0;
300     if (this.left <= other.left) {
301       offsetX = other.left - this.left;
302     } else if (this.right > other.right) {
303       offsetX = other.right - this.right;
304     }
306     let offsetY = 0;
307     if (this.top <= other.top) {
308       offsetY = other.top - this.top;
309     } else if (this.bottom > other.bottom) {
310       offsetY = other.bottom - this.bottom;
311     }
313     return this.translate(offsetX, offsetY);
314   },
316   /** Subtract other area from this. Returns array of rects whose union is this-other. */
317   subtract: function subtract(other) {
318     let r = new Rect(0, 0, 0, 0);
319     let result = [];
320     other = other.intersect(this);
321     if (other.isEmpty()) {
322       return [this.clone()];
323     }
325     // left strip
326     r.setBounds(this.left, this.top, other.left, this.bottom);
327     if (!r.isEmpty()) {
328       result.push(r.clone());
329     }
330     // inside strip
331     r.setBounds(other.left, this.top, other.right, other.top);
332     if (!r.isEmpty()) {
333       result.push(r.clone());
334     }
335     r.setBounds(other.left, other.bottom, other.right, this.bottom);
336     if (!r.isEmpty()) {
337       result.push(r.clone());
338     }
339     // right strip
340     r.setBounds(other.right, this.top, this.right, this.bottom);
341     if (!r.isEmpty()) {
342       result.push(r.clone());
343     }
345     return result;
346   },
348   /**
349    * Blends two rectangles together.
350    * @param rect Rectangle to blend this one with
351    * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
352    * @return New blended rectangle.
353    */
354   blend: function blend(rect, scalar) {
355     return new Rect(
356       this.left + (rect.left - this.left) * scalar,
357       this.top + (rect.top - this.top) * scalar,
358       this.width + (rect.width - this.width) * scalar,
359       this.height + (rect.height - this.height) * scalar
360     );
361   },
363   /**
364    * Grows or shrinks the rectangle while keeping the center point.
365    * Accepts single multipler, or separate for both axes.
366    */
367   inflate: function inflate(xscl, yscl) {
368     let xAdj = (this.width * xscl - this.width) / 2;
369     let s = arguments.length > 1 ? yscl : xscl;
370     let yAdj = (this.height * s - this.height) / 2;
371     this.left -= xAdj;
372     this.right += xAdj;
373     this.top -= yAdj;
374     this.bottom += yAdj;
375     return this;
376   },
378   /**
379    * Grows or shrinks the rectangle by fixed amount while keeping the center point.
380    * Accepts single fixed amount
381    */
382   inflateFixed: function inflateFixed(fixed) {
383     this.left -= fixed;
384     this.right += fixed;
385     this.top -= fixed;
386     this.bottom += fixed;
387     return this;
388   },