rectangle.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Rectangle = void 0;
  4. const util_1 = require("./util");
  5. const angle_1 = require("./angle");
  6. const line_1 = require("./line");
  7. const point_1 = require("./point");
  8. const geometry_1 = require("./geometry");
  9. class Rectangle extends geometry_1.Geometry {
  10. get left() {
  11. return this.x;
  12. }
  13. get top() {
  14. return this.y;
  15. }
  16. get right() {
  17. return this.x + this.width;
  18. }
  19. get bottom() {
  20. return this.y + this.height;
  21. }
  22. get origin() {
  23. return new point_1.Point(this.x, this.y);
  24. }
  25. get topLeft() {
  26. return new point_1.Point(this.x, this.y);
  27. }
  28. get topCenter() {
  29. return new point_1.Point(this.x + this.width / 2, this.y);
  30. }
  31. get topRight() {
  32. return new point_1.Point(this.x + this.width, this.y);
  33. }
  34. get center() {
  35. return new point_1.Point(this.x + this.width / 2, this.y + this.height / 2);
  36. }
  37. get bottomLeft() {
  38. return new point_1.Point(this.x, this.y + this.height);
  39. }
  40. get bottomCenter() {
  41. return new point_1.Point(this.x + this.width / 2, this.y + this.height);
  42. }
  43. get bottomRight() {
  44. return new point_1.Point(this.x + this.width, this.y + this.height);
  45. }
  46. get corner() {
  47. return new point_1.Point(this.x + this.width, this.y + this.height);
  48. }
  49. get rightMiddle() {
  50. return new point_1.Point(this.x + this.width, this.y + this.height / 2);
  51. }
  52. get leftMiddle() {
  53. return new point_1.Point(this.x, this.y + this.height / 2);
  54. }
  55. get topLine() {
  56. return new line_1.Line(this.topLeft, this.topRight);
  57. }
  58. get rightLine() {
  59. return new line_1.Line(this.topRight, this.bottomRight);
  60. }
  61. get bottomLine() {
  62. return new line_1.Line(this.bottomLeft, this.bottomRight);
  63. }
  64. get leftLine() {
  65. return new line_1.Line(this.topLeft, this.bottomLeft);
  66. }
  67. constructor(x, y, width, height) {
  68. super();
  69. this.x = x == null ? 0 : x;
  70. this.y = y == null ? 0 : y;
  71. this.width = width == null ? 0 : width;
  72. this.height = height == null ? 0 : height;
  73. }
  74. getOrigin() {
  75. return this.origin;
  76. }
  77. getTopLeft() {
  78. return this.topLeft;
  79. }
  80. getTopCenter() {
  81. return this.topCenter;
  82. }
  83. getTopRight() {
  84. return this.topRight;
  85. }
  86. getCenter() {
  87. return this.center;
  88. }
  89. getCenterX() {
  90. return this.x + this.width / 2;
  91. }
  92. getCenterY() {
  93. return this.y + this.height / 2;
  94. }
  95. getBottomLeft() {
  96. return this.bottomLeft;
  97. }
  98. getBottomCenter() {
  99. return this.bottomCenter;
  100. }
  101. getBottomRight() {
  102. return this.bottomRight;
  103. }
  104. getCorner() {
  105. return this.corner;
  106. }
  107. getRightMiddle() {
  108. return this.rightMiddle;
  109. }
  110. getLeftMiddle() {
  111. return this.leftMiddle;
  112. }
  113. getTopLine() {
  114. return this.topLine;
  115. }
  116. getRightLine() {
  117. return this.rightLine;
  118. }
  119. getBottomLine() {
  120. return this.bottomLine;
  121. }
  122. getLeftLine() {
  123. return this.leftLine;
  124. }
  125. /**
  126. * Returns a rectangle that is the bounding box of the rectangle.
  127. *
  128. * If `angle` is specified, the bounding box calculation will take into
  129. * account the rotation of the rectangle by angle degrees around its center.
  130. */
  131. bbox(angle) {
  132. if (!angle) {
  133. return this.clone();
  134. }
  135. const rad = angle_1.Angle.toRad(angle);
  136. const st = Math.abs(Math.sin(rad));
  137. const ct = Math.abs(Math.cos(rad));
  138. const w = this.width * ct + this.height * st;
  139. const h = this.width * st + this.height * ct;
  140. return new Rectangle(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h);
  141. }
  142. round(precision = 0) {
  143. this.x = util_1.GeometryUtil.round(this.x, precision);
  144. this.y = util_1.GeometryUtil.round(this.y, precision);
  145. this.width = util_1.GeometryUtil.round(this.width, precision);
  146. this.height = util_1.GeometryUtil.round(this.height, precision);
  147. return this;
  148. }
  149. add(x, y, width, height) {
  150. const rect = Rectangle.create(x, y, width, height);
  151. const minX = Math.min(this.x, rect.x);
  152. const minY = Math.min(this.y, rect.y);
  153. const maxX = Math.max(this.x + this.width, rect.x + rect.width);
  154. const maxY = Math.max(this.y + this.height, rect.y + rect.height);
  155. this.x = minX;
  156. this.y = minY;
  157. this.width = maxX - minX;
  158. this.height = maxY - minY;
  159. return this;
  160. }
  161. update(x, y, width, height) {
  162. const rect = Rectangle.create(x, y, width, height);
  163. this.x = rect.x;
  164. this.y = rect.y;
  165. this.width = rect.width;
  166. this.height = rect.height;
  167. return this;
  168. }
  169. inflate(dx, dy) {
  170. const w = dx;
  171. const h = dy != null ? dy : dx;
  172. this.x -= w;
  173. this.y -= h;
  174. this.width += 2 * w;
  175. this.height += 2 * h;
  176. return this;
  177. }
  178. snapToGrid(gx, gy) {
  179. const origin = this.origin.snapToGrid(gx, gy);
  180. const corner = this.corner.snapToGrid(gx, gy);
  181. this.x = origin.x;
  182. this.y = origin.y;
  183. this.width = corner.x - origin.x;
  184. this.height = corner.y - origin.y;
  185. return this;
  186. }
  187. translate(tx, ty) {
  188. const p = point_1.Point.create(tx, ty);
  189. this.x += p.x;
  190. this.y += p.y;
  191. return this;
  192. }
  193. scale(sx, sy, origin = new point_1.Point()) {
  194. const pos = this.origin.scale(sx, sy, origin);
  195. this.x = pos.x;
  196. this.y = pos.y;
  197. this.width *= sx;
  198. this.height *= sy;
  199. return this;
  200. }
  201. rotate(degree, center = this.getCenter()) {
  202. if (degree !== 0) {
  203. const rad = angle_1.Angle.toRad(degree);
  204. const cos = Math.cos(rad);
  205. const sin = Math.sin(rad);
  206. let p1 = this.getOrigin();
  207. let p2 = this.getTopRight();
  208. let p3 = this.getBottomRight();
  209. let p4 = this.getBottomLeft();
  210. p1 = point_1.Point.rotateEx(p1, cos, sin, center);
  211. p2 = point_1.Point.rotateEx(p2, cos, sin, center);
  212. p3 = point_1.Point.rotateEx(p3, cos, sin, center);
  213. p4 = point_1.Point.rotateEx(p4, cos, sin, center);
  214. const rect = new Rectangle(p1.x, p1.y, 0, 0);
  215. rect.add(p2.x, p2.y, 0, 0);
  216. rect.add(p3.x, p3.y, 0, 0);
  217. rect.add(p4.x, p4.y, 0, 0);
  218. this.update(rect);
  219. }
  220. return this;
  221. }
  222. rotate90() {
  223. const t = (this.width - this.height) / 2;
  224. this.x += t;
  225. this.y -= t;
  226. const tmp = this.width;
  227. this.width = this.height;
  228. this.height = tmp;
  229. return this;
  230. }
  231. /**
  232. * Translates the rectangle by `rect.x` and `rect.y` and expand it by
  233. * `rect.width` and `rect.height`.
  234. */
  235. moveAndExpand(rect) {
  236. const ref = Rectangle.clone(rect);
  237. this.x += ref.x || 0;
  238. this.y += ref.y || 0;
  239. this.width += ref.width || 0;
  240. this.height += ref.height || 0;
  241. return this;
  242. }
  243. /**
  244. * Returns an object where `sx` and `sy` give the maximum scaling that can be
  245. * applied to the rectangle so that it would still fit into `limit`. If
  246. * `origin` is specified, the rectangle is scaled around it; otherwise, it is
  247. * scaled around its center.
  248. */
  249. getMaxScaleToFit(limit, origin = this.center) {
  250. const rect = Rectangle.clone(limit);
  251. const ox = origin.x;
  252. const oy = origin.y;
  253. // Find the maximal possible scale for all corners, so when the scale
  254. // is applied the point is still inside the rectangle.
  255. let sx1 = Infinity;
  256. let sx2 = Infinity;
  257. let sx3 = Infinity;
  258. let sx4 = Infinity;
  259. let sy1 = Infinity;
  260. let sy2 = Infinity;
  261. let sy3 = Infinity;
  262. let sy4 = Infinity;
  263. // Top Left
  264. const p1 = rect.topLeft;
  265. if (p1.x < ox) {
  266. sx1 = (this.x - ox) / (p1.x - ox);
  267. }
  268. if (p1.y < oy) {
  269. sy1 = (this.y - oy) / (p1.y - oy);
  270. }
  271. // Bottom Right
  272. const p2 = rect.bottomRight;
  273. if (p2.x > ox) {
  274. sx2 = (this.x + this.width - ox) / (p2.x - ox);
  275. }
  276. if (p2.y > oy) {
  277. sy2 = (this.y + this.height - oy) / (p2.y - oy);
  278. }
  279. // Top Right
  280. const p3 = rect.topRight;
  281. if (p3.x > ox) {
  282. sx3 = (this.x + this.width - ox) / (p3.x - ox);
  283. }
  284. if (p3.y < oy) {
  285. sy3 = (this.y - oy) / (p3.y - oy);
  286. }
  287. // Bottom Left
  288. const p4 = rect.bottomLeft;
  289. if (p4.x < ox) {
  290. sx4 = (this.x - ox) / (p4.x - ox);
  291. }
  292. if (p4.y > oy) {
  293. sy4 = (this.y + this.height - oy) / (p4.y - oy);
  294. }
  295. return {
  296. sx: Math.min(sx1, sx2, sx3, sx4),
  297. sy: Math.min(sy1, sy2, sy3, sy4),
  298. };
  299. }
  300. /**
  301. * Returns a number that specifies the maximum scaling that can be applied to
  302. * the rectangle along both axes so that it would still fit into `limit`. If
  303. * `origin` is specified, the rectangle is scaled around it; otherwise, it is
  304. * scaled around its center.
  305. */
  306. getMaxUniformScaleToFit(limit, origin = this.center) {
  307. const scale = this.getMaxScaleToFit(limit, origin);
  308. return Math.min(scale.sx, scale.sy);
  309. }
  310. containsPoint(x, y) {
  311. return util_1.GeometryUtil.containsPoint(this, point_1.Point.create(x, y));
  312. }
  313. containsRect(x, y, width, height) {
  314. const b = Rectangle.create(x, y, width, height);
  315. const x1 = this.x;
  316. const y1 = this.y;
  317. const w1 = this.width;
  318. const h1 = this.height;
  319. const x2 = b.x;
  320. const y2 = b.y;
  321. const w2 = b.width;
  322. const h2 = b.height;
  323. // one of the dimensions is 0
  324. if (w1 === 0 || h1 === 0 || w2 === 0 || h2 === 0) {
  325. return false;
  326. }
  327. return x2 >= x1 && y2 >= y1 && x2 + w2 <= x1 + w1 && y2 + h2 <= y1 + h1;
  328. }
  329. /**
  330. * Returns an array of the intersection points of the rectangle and the line.
  331. * Return `null` if no intersection exists.
  332. */
  333. intersectsWithLine(line) {
  334. const rectLines = [
  335. this.topLine,
  336. this.rightLine,
  337. this.bottomLine,
  338. this.leftLine,
  339. ];
  340. const points = [];
  341. const dedupeArr = [];
  342. rectLines.forEach((l) => {
  343. const p = line.intersectsWithLine(l);
  344. if (p !== null && dedupeArr.indexOf(p.toString()) < 0) {
  345. points.push(p);
  346. dedupeArr.push(p.toString());
  347. }
  348. });
  349. return points.length > 0 ? points : null;
  350. }
  351. /**
  352. * Returns the point on the boundary of the rectangle that is the intersection
  353. * of the rectangle with a line starting in the center the rectangle ending in
  354. * the point `p`.
  355. *
  356. * If `angle` is specified, the intersection will take into account the
  357. * rotation of the rectangle by `angle` degrees around its center.
  358. */
  359. intersectsWithLineFromCenterToPoint(p, angle) {
  360. const ref = point_1.Point.clone(p);
  361. const center = this.center;
  362. let result = null;
  363. if (angle != null && angle !== 0) {
  364. ref.rotate(angle, center);
  365. }
  366. const sides = [this.topLine, this.rightLine, this.bottomLine, this.leftLine];
  367. const connector = new line_1.Line(center, ref);
  368. for (let i = sides.length - 1; i >= 0; i -= 1) {
  369. const intersection = sides[i].intersectsWithLine(connector);
  370. if (intersection !== null) {
  371. result = intersection;
  372. break;
  373. }
  374. }
  375. if (result && angle != null && angle !== 0) {
  376. result.rotate(-angle, center);
  377. }
  378. return result;
  379. }
  380. intersectsWithRect(x, y, width, height) {
  381. const ref = Rectangle.create(x, y, width, height);
  382. // no intersection
  383. if (!this.isIntersectWithRect(ref)) {
  384. return null;
  385. }
  386. const myOrigin = this.origin;
  387. const myCorner = this.corner;
  388. const rOrigin = ref.origin;
  389. const rCorner = ref.corner;
  390. const xx = Math.max(myOrigin.x, rOrigin.x);
  391. const yy = Math.max(myOrigin.y, rOrigin.y);
  392. return new Rectangle(xx, yy, Math.min(myCorner.x, rCorner.x) - xx, Math.min(myCorner.y, rCorner.y) - yy);
  393. }
  394. isIntersectWithRect(x, y, width, height) {
  395. const ref = Rectangle.create(x, y, width, height);
  396. const myOrigin = this.origin;
  397. const myCorner = this.corner;
  398. const rOrigin = ref.origin;
  399. const rCorner = ref.corner;
  400. if (rCorner.x <= myOrigin.x ||
  401. rCorner.y <= myOrigin.y ||
  402. rOrigin.x >= myCorner.x ||
  403. rOrigin.y >= myCorner.y) {
  404. return false;
  405. }
  406. return true;
  407. }
  408. /**
  409. * Normalize the rectangle, i.e. make it so that it has non-negative
  410. * width and height. If width is less than `0`, the function swaps left and
  411. * right corners and if height is less than `0`, the top and bottom corners
  412. * are swapped.
  413. */
  414. normalize() {
  415. let newx = this.x;
  416. let newy = this.y;
  417. let newwidth = this.width;
  418. let newheight = this.height;
  419. if (this.width < 0) {
  420. newx = this.x + this.width;
  421. newwidth = -this.width;
  422. }
  423. if (this.height < 0) {
  424. newy = this.y + this.height;
  425. newheight = -this.height;
  426. }
  427. this.x = newx;
  428. this.y = newy;
  429. this.width = newwidth;
  430. this.height = newheight;
  431. return this;
  432. }
  433. /**
  434. * Returns a rectangle that is a union of this rectangle and rectangle `rect`.
  435. */
  436. union(rect) {
  437. const ref = Rectangle.clone(rect);
  438. const myOrigin = this.origin;
  439. const myCorner = this.corner;
  440. const rOrigin = ref.origin;
  441. const rCorner = ref.corner;
  442. const originX = Math.min(myOrigin.x, rOrigin.x);
  443. const originY = Math.min(myOrigin.y, rOrigin.y);
  444. const cornerX = Math.max(myCorner.x, rCorner.x);
  445. const cornerY = Math.max(myCorner.y, rCorner.y);
  446. return new Rectangle(originX, originY, cornerX - originX, cornerY - originY);
  447. }
  448. /**
  449. * Returns a string ("top", "left", "right" or "bottom") denoting the side of
  450. * the rectangle which is nearest to the point `p`.
  451. */
  452. getNearestSideToPoint(p) {
  453. const ref = point_1.Point.clone(p);
  454. const distLeft = ref.x - this.x;
  455. const distRight = this.x + this.width - ref.x;
  456. const distTop = ref.y - this.y;
  457. const distBottom = this.y + this.height - ref.y;
  458. let closest = distLeft;
  459. let side = 'left';
  460. if (distRight < closest) {
  461. closest = distRight;
  462. side = 'right';
  463. }
  464. if (distTop < closest) {
  465. closest = distTop;
  466. side = 'top';
  467. }
  468. if (distBottom < closest) {
  469. side = 'bottom';
  470. }
  471. return side;
  472. }
  473. /**
  474. * Returns a point on the boundary of the rectangle nearest to the point `p`.
  475. */
  476. getNearestPointToPoint(p) {
  477. const ref = point_1.Point.clone(p);
  478. if (this.containsPoint(ref)) {
  479. const side = this.getNearestSideToPoint(ref);
  480. if (side === 'left') {
  481. return new point_1.Point(this.x, ref.y);
  482. }
  483. if (side === 'top') {
  484. return new point_1.Point(ref.x, this.y);
  485. }
  486. if (side === 'right') {
  487. return new point_1.Point(this.x + this.width, ref.y);
  488. }
  489. if (side === 'bottom') {
  490. return new point_1.Point(ref.x, this.y + this.height);
  491. }
  492. }
  493. return ref.adhereToRect(this);
  494. }
  495. equals(rect) {
  496. return (rect != null &&
  497. rect.x === this.x &&
  498. rect.y === this.y &&
  499. rect.width === this.width &&
  500. rect.height === this.height);
  501. }
  502. clone() {
  503. return new Rectangle(this.x, this.y, this.width, this.height);
  504. }
  505. toJSON() {
  506. return { x: this.x, y: this.y, width: this.width, height: this.height };
  507. }
  508. serialize() {
  509. return `${this.x} ${this.y} ${this.width} ${this.height}`;
  510. }
  511. }
  512. exports.Rectangle = Rectangle;
  513. (function (Rectangle) {
  514. function isRectangle(instance) {
  515. return instance != null && instance instanceof Rectangle;
  516. }
  517. Rectangle.isRectangle = isRectangle;
  518. })(Rectangle = exports.Rectangle || (exports.Rectangle = {}));
  519. (function (Rectangle) {
  520. function isRectangleLike(o) {
  521. return (o != null &&
  522. typeof o === 'object' &&
  523. typeof o.x === 'number' &&
  524. typeof o.y === 'number' &&
  525. typeof o.width === 'number' &&
  526. typeof o.height === 'number');
  527. }
  528. Rectangle.isRectangleLike = isRectangleLike;
  529. })(Rectangle = exports.Rectangle || (exports.Rectangle = {}));
  530. (function (Rectangle) {
  531. function create(x, y, width, height) {
  532. if (x == null || typeof x === 'number') {
  533. return new Rectangle(x, y, width, height);
  534. }
  535. return clone(x);
  536. }
  537. Rectangle.create = create;
  538. function clone(rect) {
  539. if (Rectangle.isRectangle(rect)) {
  540. return rect.clone();
  541. }
  542. if (Array.isArray(rect)) {
  543. return new Rectangle(rect[0], rect[1], rect[2], rect[3]);
  544. }
  545. return new Rectangle(rect.x, rect.y, rect.width, rect.height);
  546. }
  547. Rectangle.clone = clone;
  548. /**
  549. * Returns a new rectangle from the given ellipse.
  550. */
  551. function fromEllipse(ellipse) {
  552. return new Rectangle(ellipse.x - ellipse.a, ellipse.y - ellipse.b, 2 * ellipse.a, 2 * ellipse.b);
  553. }
  554. Rectangle.fromEllipse = fromEllipse;
  555. function fromSize(size) {
  556. return new Rectangle(0, 0, size.width, size.height);
  557. }
  558. Rectangle.fromSize = fromSize;
  559. function fromPositionAndSize(pos, size) {
  560. return new Rectangle(pos.x, pos.y, size.width, size.height);
  561. }
  562. Rectangle.fromPositionAndSize = fromPositionAndSize;
  563. })(Rectangle = exports.Rectangle || (exports.Rectangle = {}));
  564. //# sourceMappingURL=rectangle.js.map