Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / toolkit / modules / Geometry.jsm
blob47072b7445eaa4286316e5df30fab0a46480d25b
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 this.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 this.Point = 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: function() {
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       else
70         return f.call(this, arg1, arg2);
71     };
72   }
74   for each (let f in ['add', 'subtract', 'equals', 'set'])
75     Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
76 })();
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 this.Rect = 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() { return this.left; },
100   get y() { return this.top; },
101   get width() { return this.right - this.left; },
102   get height() { return this.bottom - this.top; },
103   set x(v) {
104     let diff = this.left - v;
105     this.left = v;
106     this.right -= diff;
107   },
108   set y(v) {
109     let diff = this.top - v;
110     this.top = v;
111     this.bottom -= diff;
112   },
113   set width(v) { this.right = this.left + v; },
114   set height(v) { this.bottom = this.top + v; },
116   isEmpty: function isEmpty() {
117     return this.left >= this.right || this.top >= this.bottom;
118   },
120   setRect: function(x, y, w, h) {
121     this.left = x;
122     this.top = y;
123     this.right = x+w;
124     this.bottom = y+h;
126     return this;
127   },
129   setBounds: function(l, t, r, b) {
130     this.top = t;
131     this.left = l;
132     this.bottom = b;
133     this.right = r;
135     return this;
136   },
138   equals: function equals(other) {
139     return other != null &&
140             (this.isEmpty() && other.isEmpty() ||
141             this.top == other.top &&
142             this.left == other.left &&
143             this.bottom == other.bottom &&
144             this.right == other.right);
145   },
147   clone: function clone() {
148     return new Rect(this.left, this.top, this.right - this.left, this.bottom - this.top);
149   },
151   center: function center() {
152     if (this.isEmpty())
153       throw "Empty rectangles do not have centers";
154     return new Point(this.left + (this.right - this.left) / 2,
155                           this.top + (this.bottom - this.top) / 2);
156   },
158   copyFrom: function(other) {
159     this.top = other.top;
160     this.left = other.left;
161     this.bottom = other.bottom;
162     this.right = other.right;
164     return this;
165   },
167   translate: function(x, y) {
168     this.left += x;
169     this.right += x;
170     this.top += y;
171     this.bottom += y;
173     return this;
174   },
176   toString: function() {
177     return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]";
178   },
180   /** return a new rect that is the union of that one and this one */
181   union: function(other) {
182     return this.clone().expandToContain(other);
183   },
185   contains: function(other) {
186     if (other.isEmpty()) return true;
187     if (this.isEmpty()) return false;
189     return (other.left >= this.left &&
190             other.right <= this.right &&
191             other.top >= this.top &&
192             other.bottom <= this.bottom);
193   },
195   intersect: function(other) {
196     return this.clone().restrictTo(other);
197   },
199   intersects: function(other) {
200     if (this.isEmpty() || other.isEmpty())
201       return false;
203     let x1 = Math.max(this.left, other.left);
204     let x2 = Math.min(this.right, other.right);
205     let y1 = Math.max(this.top, other.top);
206     let y2 = Math.min(this.bottom, other.bottom);
207     return x1 < x2 && y1 < y2;
208   },
210   /** Restrict area of this rectangle to the intersection of both rectangles. */
211   restrictTo: function restrictTo(other) {
212     if (this.isEmpty() || other.isEmpty())
213       return this.setRect(0, 0, 0, 0);
215     let x1 = Math.max(this.left, other.left);
216     let x2 = Math.min(this.right, other.right);
217     let y1 = Math.max(this.top, other.top);
218     let y2 = Math.min(this.bottom, other.bottom);
219     // If width or height is 0, the intersection was empty.
220     return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
221   },
223   /** Expand this rectangle to the union of both rectangles. */
224   expandToContain: function expandToContain(other) {
225     if (this.isEmpty()) return this.copyFrom(other);
226     if (other.isEmpty()) return this;
228     let l = Math.min(this.left, other.left);
229     let r = Math.max(this.right, other.right);
230     let t = Math.min(this.top, other.top);
231     let b = Math.max(this.bottom, other.bottom);
232     return this.setRect(l, t, r-l, b-t);
233   },
235   /**
236    * Expands to the smallest rectangle that contains original rectangle and is bounded
237    * by lines with integer coefficients.
238    */
239   expandToIntegers: function round() {
240     this.left = Math.floor(this.left);
241     this.top = Math.floor(this.top);
242     this.right = Math.ceil(this.right);
243     this.bottom = Math.ceil(this.bottom);
244     return this;
245   },
247   scale: function scale(xscl, yscl) {
248     this.left *= xscl;
249     this.right *= xscl;
250     this.top *= yscl;
251     this.bottom *= yscl;
252     return this;
253   },
255   map: function map(f) {
256     this.left = f.call(this, this.left);
257     this.top = f.call(this, this.top);
258     this.right = f.call(this, this.right);
259     this.bottom = f.call(this, this.bottom);
260     return this;
261   },
263   /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
264   translateInside: function translateInside(other) {
265     let offsetX = (this.left <= other.left ? other.left - this.left :
266                   (this.right > other.right ? other.right - this.right : 0));
267     let offsetY = (this.top <= other.top ? other.top - this.top :
268                   (this.bottom > other.bottom ? other.bottom - this.bottom : 0));
269     return this.translate(offsetX, offsetY);
270   },
272   /** Subtract other area from this. Returns array of rects whose union is this-other. */
273   subtract: function subtract(other) {
274     let r = new Rect(0, 0, 0, 0);
275     let result = [];
276     other = other.intersect(this);
277     if (other.isEmpty())
278       return [this.clone()];
280     // left strip
281     r.setBounds(this.left, this.top, other.left, this.bottom);
282     if (!r.isEmpty())
283       result.push(r.clone());
284     // inside strip
285     r.setBounds(other.left, this.top, other.right, other.top);
286     if (!r.isEmpty())
287       result.push(r.clone());
288     r.setBounds(other.left, other.bottom, other.right, this.bottom);
289     if (!r.isEmpty())
290       result.push(r.clone());
291     // right strip
292     r.setBounds(other.right, this.top, this.right, this.bottom);
293     if (!r.isEmpty())
294       result.push(r.clone());
296     return result;
297   },
299   /**
300    * Blends two rectangles together.
301    * @param rect Rectangle to blend this one with
302    * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
303    * @return New blended rectangle.
304    */
305   blend: function blend(rect, scalar) {
306     return new Rect(
307       this.left   + (rect.left   - this.left  ) * scalar,
308       this.top    + (rect.top    - this.top   ) * scalar,
309       this.width  + (rect.width  - this.width ) * scalar,
310       this.height + (rect.height - this.height) * scalar);
311   },
313   /**
314    * Grows or shrinks the rectangle while keeping the center point.
315    * Accepts single multipler, or separate for both axes.
316    */
317   inflate: function inflate(xscl, yscl) {
318     let xAdj = (this.width * xscl - this.width) / 2;
319     let s = (arguments.length > 1) ? yscl : xscl;
320     let yAdj = (this.height * s - this.height) / 2;
321     this.left -= xAdj;
322     this.right += xAdj;
323     this.top -= yAdj;
324     this.bottom += yAdj;
325     return this;
326   }