Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / toolkit / modules / Geometry.jsm
blob42fff57a36843244ad5877fc0a8db3506f060043
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 var EXPORTED_SYMBOLS = ["Point", "Rect"];
7 /**
8  * Simple Point class.
9  *
10  * Any method that takes an x and y may also take a point.
11  */
12 function Point(x, y) {
13   this.set(x, y);
16 Point.prototype = {
17   clone: function clone() {
18     return new Point(this.x, this.y);
19   },
21   set: function set(x, y) {
22     this.x = x;
23     this.y = y;
24     return this;
25   },
27   equals: function equals(x, y) {
28     return this.x == x && this.y == y;
29   },
31   toString: function toString() {
32     return "(" + this.x + "," + this.y + ")";
33   },
35   map: function map(f) {
36     this.x = f.call(this, this.x);
37     this.y = f.call(this, this.y);
38     return this;
39   },
41   add: function add(x, y) {
42     this.x += x;
43     this.y += y;
44     return this;
45   },
47   subtract: function subtract(x, y) {
48     this.x -= x;
49     this.y -= y;
50     return this;
51   },
53   scale: function scale(s) {
54     this.x *= s;
55     this.y *= s;
56     return this;
57   },
59   isZero() {
60     return this.x == 0 && this.y == 0;
61   },
64 (function() {
65   function takePointOrArgs(f) {
66     return function(arg1, arg2) {
67       if (arg2 === undefined) {
68         return f.call(this, arg1.x, arg1.y);
69       }
70       return f.call(this, arg1, arg2);
71     };
72   }
74   for (let f of ["add", "subtract", "equals", "set"]) {
75     Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
76   }
77 })();
79 /**
80  * Rect is a simple data structure for representation of a rectangle supporting
81  * many basic geometric operations.
82  *
83  * NOTE: Since its operations are closed, rectangles may be empty and will report
84  * non-positive widths and heights in that case.
85  */
87 function Rect(x, y, w, h) {
88   this.left = x;
89   this.top = y;
90   this.right = x + w;
91   this.bottom = y + h;
94 Rect.fromRect = function fromRect(r) {
95   return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
98 Rect.prototype = {
99   get x() {
100     return this.left;
101   },
102   get y() {
103     return this.top;
104   },
105   get width() {
106     return this.right - this.left;
107   },
108   get height() {
109     return this.bottom - this.top;
110   },
111   set x(v) {
112     let diff = this.left - v;
113     this.left = v;
114     this.right -= diff;
115   },
116   set y(v) {
117     let diff = this.top - v;
118     this.top = v;
119     this.bottom -= diff;
120   },
121   set width(v) {
122     this.right = this.left + v;
123   },
124   set height(v) {
125     this.bottom = this.top + v;
126   },
128   isEmpty: function isEmpty() {
129     return this.left >= this.right || this.top >= this.bottom;
130   },
132   setRect(x, y, w, h) {
133     this.left = x;
134     this.top = y;
135     this.right = x + w;
136     this.bottom = y + h;
138     return this;
139   },
141   setBounds(l, t, r, b) {
142     this.top = t;
143     this.left = l;
144     this.bottom = b;
145     this.right = r;
147     return this;
148   },
150   equals: function equals(other) {
151     return (
152       other != null &&
153       ((this.isEmpty() && other.isEmpty()) ||
154         (this.top == other.top &&
155           this.left == other.left &&
156           this.bottom == other.bottom &&
157           this.right == other.right))
158     );
159   },
161   clone: function clone() {
162     return new Rect(
163       this.left,
164       this.top,
165       this.right - this.left,
166       this.bottom - this.top
167     );
168   },
170   center: function center() {
171     if (this.isEmpty()) {
172       throw new Error("Empty rectangles do not have centers");
173     }
174     return new Point(
175       this.left + (this.right - this.left) / 2,
176       this.top + (this.bottom - this.top) / 2
177     );
178   },
180   copyFrom(other) {
181     this.top = other.top;
182     this.left = other.left;
183     this.bottom = other.bottom;
184     this.right = other.right;
186     return this;
187   },
189   translate(x, y) {
190     this.left += x;
191     this.right += x;
192     this.top += y;
193     this.bottom += y;
195     return this;
196   },
198   toString() {
199     return (
200       "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"
201     );
202   },
204   /** return a new rect that is the union of that one and this one */
205   union(other) {
206     return this.clone().expandToContain(other);
207   },
209   contains(other) {
210     if (other.isEmpty()) {
211       return true;
212     }
213     if (this.isEmpty()) {
214       return false;
215     }
217     return (
218       other.left >= this.left &&
219       other.right <= this.right &&
220       other.top >= this.top &&
221       other.bottom <= this.bottom
222     );
223   },
225   intersect(other) {
226     return this.clone().restrictTo(other);
227   },
229   intersects(other) {
230     if (this.isEmpty() || other.isEmpty()) {
231       return false;
232     }
234     let x1 = Math.max(this.left, other.left);
235     let x2 = Math.min(this.right, other.right);
236     let y1 = Math.max(this.top, other.top);
237     let y2 = Math.min(this.bottom, other.bottom);
238     return x1 < x2 && y1 < y2;
239   },
241   /** Restrict area of this rectangle to the intersection of both rectangles. */
242   restrictTo: function restrictTo(other) {
243     if (this.isEmpty() || other.isEmpty()) {
244       return this.setRect(0, 0, 0, 0);
245     }
247     let x1 = Math.max(this.left, other.left);
248     let x2 = Math.min(this.right, other.right);
249     let y1 = Math.max(this.top, other.top);
250     let y2 = Math.min(this.bottom, other.bottom);
251     // If width or height is 0, the intersection was empty.
252     return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
253   },
255   /** Expand this rectangle to the union of both rectangles. */
256   expandToContain: function expandToContain(other) {
257     if (this.isEmpty()) {
258       return this.copyFrom(other);
259     }
260     if (other.isEmpty()) {
261       return this;
262     }
264     let l = Math.min(this.left, other.left);
265     let r = Math.max(this.right, other.right);
266     let t = Math.min(this.top, other.top);
267     let b = Math.max(this.bottom, other.bottom);
268     return this.setRect(l, t, r - l, b - t);
269   },
271   /**
272    * Expands to the smallest rectangle that contains original rectangle and is bounded
273    * by lines with integer coefficients.
274    */
275   expandToIntegers: function round() {
276     this.left = Math.floor(this.left);
277     this.top = Math.floor(this.top);
278     this.right = Math.ceil(this.right);
279     this.bottom = Math.ceil(this.bottom);
280     return this;
281   },
283   scale: function scale(xscl, yscl) {
284     this.left *= xscl;
285     this.right *= xscl;
286     this.top *= yscl;
287     this.bottom *= yscl;
288     return this;
289   },
291   map: function map(f) {
292     this.left = f.call(this, this.left);
293     this.top = f.call(this, this.top);
294     this.right = f.call(this, this.right);
295     this.bottom = f.call(this, this.bottom);
296     return this;
297   },
299   /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
300   translateInside: function translateInside(other) {
301     let offsetX = 0;
302     if (this.left <= other.left) {
303       offsetX = other.left - this.left;
304     } else if (this.right > other.right) {
305       offsetX = other.right - this.right;
306     }
308     let offsetY = 0;
309     if (this.top <= other.top) {
310       offsetY = other.top - this.top;
311     } else if (this.bottom > other.bottom) {
312       offsetY = other.bottom - this.bottom;
313     }
315     return this.translate(offsetX, offsetY);
316   },
318   /** Subtract other area from this. Returns array of rects whose union is this-other. */
319   subtract: function subtract(other) {
320     let r = new Rect(0, 0, 0, 0);
321     let result = [];
322     other = other.intersect(this);
323     if (other.isEmpty()) {
324       return [this.clone()];
325     }
327     // left strip
328     r.setBounds(this.left, this.top, other.left, this.bottom);
329     if (!r.isEmpty()) {
330       result.push(r.clone());
331     }
332     // inside strip
333     r.setBounds(other.left, this.top, other.right, other.top);
334     if (!r.isEmpty()) {
335       result.push(r.clone());
336     }
337     r.setBounds(other.left, other.bottom, other.right, this.bottom);
338     if (!r.isEmpty()) {
339       result.push(r.clone());
340     }
341     // right strip
342     r.setBounds(other.right, this.top, this.right, this.bottom);
343     if (!r.isEmpty()) {
344       result.push(r.clone());
345     }
347     return result;
348   },
350   /**
351    * Blends two rectangles together.
352    * @param rect Rectangle to blend this one with
353    * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
354    * @return New blended rectangle.
355    */
356   blend: function blend(rect, scalar) {
357     return new Rect(
358       this.left + (rect.left - this.left) * scalar,
359       this.top + (rect.top - this.top) * scalar,
360       this.width + (rect.width - this.width) * scalar,
361       this.height + (rect.height - this.height) * scalar
362     );
363   },
365   /**
366    * Grows or shrinks the rectangle while keeping the center point.
367    * Accepts single multipler, or separate for both axes.
368    */
369   inflate: function inflate(xscl, yscl) {
370     let xAdj = (this.width * xscl - this.width) / 2;
371     let s = arguments.length > 1 ? yscl : xscl;
372     let yAdj = (this.height * s - this.height) / 2;
373     this.left -= xAdj;
374     this.right += xAdj;
375     this.top -= yAdj;
376     this.bottom += yAdj;
377     return this;
378   },
380   /**
381    * Grows or shrinks the rectangle by fixed amount while keeping the center point.
382    * Accepts single fixed amount
383    */
384   inflateFixed: function inflateFixed(fixed) {
385     this.left -= fixed;
386     this.right += fixed;
387     this.top -= fixed;
388     this.bottom += fixed;
389     return this;
390   },