DOMMatrix.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. 'use strict'
  2. const util = require('util')
  3. // DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix
  4. class DOMPoint {
  5. constructor (x, y, z, w) {
  6. if (typeof x === 'object' && x !== null) {
  7. w = x.w
  8. z = x.z
  9. y = x.y
  10. x = x.x
  11. }
  12. this.x = typeof x === 'number' ? x : 0
  13. this.y = typeof y === 'number' ? y : 0
  14. this.z = typeof z === 'number' ? z : 0
  15. this.w = typeof w === 'number' ? w : 1
  16. }
  17. }
  18. // Constants to index into _values (col-major)
  19. const M11 = 0; const M12 = 1; const M13 = 2; const M14 = 3
  20. const M21 = 4; const M22 = 5; const M23 = 6; const M24 = 7
  21. const M31 = 8; const M32 = 9; const M33 = 10; const M34 = 11
  22. const M41 = 12; const M42 = 13; const M43 = 14; const M44 = 15
  23. const DEGREE_PER_RAD = 180 / Math.PI
  24. const RAD_PER_DEGREE = Math.PI / 180
  25. function parseMatrix (init) {
  26. let parsed = init.replace('matrix(', '')
  27. parsed = parsed.split(',', 7) // 6 + 1 to handle too many params
  28. if (parsed.length !== 6) throw new Error(`Failed to parse ${init}`)
  29. parsed = parsed.map(parseFloat)
  30. return [
  31. parsed[0], parsed[1], 0, 0,
  32. parsed[2], parsed[3], 0, 0,
  33. 0, 0, 1, 0,
  34. parsed[4], parsed[5], 0, 1
  35. ]
  36. }
  37. function parseMatrix3d (init) {
  38. let parsed = init.replace('matrix3d(', '')
  39. parsed = parsed.split(',', 17) // 16 + 1 to handle too many params
  40. if (parsed.length !== 16) throw new Error(`Failed to parse ${init}`)
  41. return parsed.map(parseFloat)
  42. }
  43. function parseTransform (tform) {
  44. const type = tform.split('(', 1)[0]
  45. switch (type) {
  46. case 'matrix':
  47. return parseMatrix(tform)
  48. case 'matrix3d':
  49. return parseMatrix3d(tform)
  50. // TODO This is supposed to support any CSS transform value.
  51. default:
  52. throw new Error(`${type} parsing not implemented`)
  53. }
  54. }
  55. class DOMMatrix {
  56. constructor (init) {
  57. this._is2D = true
  58. this._values = new Float64Array([
  59. 1, 0, 0, 0,
  60. 0, 1, 0, 0,
  61. 0, 0, 1, 0,
  62. 0, 0, 0, 1
  63. ])
  64. let i
  65. if (typeof init === 'string') { // parse CSS transformList
  66. if (init === '') return // default identity matrix
  67. const tforms = init.split(/\)\s+/, 20).map(parseTransform)
  68. if (tforms.length === 0) return
  69. init = tforms[0]
  70. for (i = 1; i < tforms.length; i++) init = multiply(tforms[i], init)
  71. }
  72. i = 0
  73. if (init && init.length === 6) {
  74. setNumber2D(this, M11, init[i++])
  75. setNumber2D(this, M12, init[i++])
  76. setNumber2D(this, M21, init[i++])
  77. setNumber2D(this, M22, init[i++])
  78. setNumber2D(this, M41, init[i++])
  79. setNumber2D(this, M42, init[i++])
  80. } else if (init && init.length === 16) {
  81. setNumber2D(this, M11, init[i++])
  82. setNumber2D(this, M12, init[i++])
  83. setNumber3D(this, M13, init[i++])
  84. setNumber3D(this, M14, init[i++])
  85. setNumber2D(this, M21, init[i++])
  86. setNumber2D(this, M22, init[i++])
  87. setNumber3D(this, M23, init[i++])
  88. setNumber3D(this, M24, init[i++])
  89. setNumber3D(this, M31, init[i++])
  90. setNumber3D(this, M32, init[i++])
  91. setNumber3D(this, M33, init[i++])
  92. setNumber3D(this, M34, init[i++])
  93. setNumber2D(this, M41, init[i++])
  94. setNumber2D(this, M42, init[i++])
  95. setNumber3D(this, M43, init[i++])
  96. setNumber3D(this, M44, init[i])
  97. } else if (init !== undefined) {
  98. throw new TypeError('Expected string or array.')
  99. }
  100. }
  101. toString () {
  102. return this.is2D
  103. ? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`
  104. : `matrix3d(${this._values.join(', ')})`
  105. }
  106. multiply (other) {
  107. return newInstance(this._values).multiplySelf(other)
  108. }
  109. multiplySelf (other) {
  110. this._values = multiply(other._values, this._values)
  111. if (!other.is2D) this._is2D = false
  112. return this
  113. }
  114. preMultiplySelf (other) {
  115. this._values = multiply(this._values, other._values)
  116. if (!other.is2D) this._is2D = false
  117. return this
  118. }
  119. translate (tx, ty, tz) {
  120. return newInstance(this._values).translateSelf(tx, ty, tz)
  121. }
  122. translateSelf (tx, ty, tz) {
  123. if (typeof tx !== 'number') tx = 0
  124. if (typeof ty !== 'number') ty = 0
  125. if (typeof tz !== 'number') tz = 0
  126. this._values = multiply([
  127. 1, 0, 0, 0,
  128. 0, 1, 0, 0,
  129. 0, 0, 1, 0,
  130. tx, ty, tz, 1
  131. ], this._values)
  132. if (tz !== 0) this._is2D = false
  133. return this
  134. }
  135. scale (scaleX, scaleY, scaleZ, originX, originY, originZ) {
  136. return newInstance(this._values).scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
  137. }
  138. scale3d (scale, originX, originY, originZ) {
  139. return newInstance(this._values).scale3dSelf(scale, originX, originY, originZ)
  140. }
  141. scale3dSelf (scale, originX, originY, originZ) {
  142. return this.scaleSelf(scale, scale, scale, originX, originY, originZ)
  143. }
  144. scaleSelf (scaleX, scaleY, scaleZ, originX, originY, originZ) {
  145. // Not redundant with translate's checks because we need to negate the values later.
  146. if (typeof originX !== 'number') originX = 0
  147. if (typeof originY !== 'number') originY = 0
  148. if (typeof originZ !== 'number') originZ = 0
  149. this.translateSelf(originX, originY, originZ)
  150. if (typeof scaleX !== 'number') scaleX = 1
  151. if (typeof scaleY !== 'number') scaleY = scaleX
  152. if (typeof scaleZ !== 'number') scaleZ = 1
  153. this._values = multiply([
  154. scaleX, 0, 0, 0,
  155. 0, scaleY, 0, 0,
  156. 0, 0, scaleZ, 0,
  157. 0, 0, 0, 1
  158. ], this._values)
  159. this.translateSelf(-originX, -originY, -originZ)
  160. if (scaleZ !== 1 || originZ !== 0) this._is2D = false
  161. return this
  162. }
  163. rotateFromVector (x, y) {
  164. return newInstance(this._values).rotateFromVectorSelf(x, y)
  165. }
  166. rotateFromVectorSelf (x, y) {
  167. if (typeof x !== 'number') x = 0
  168. if (typeof y !== 'number') y = 0
  169. const theta = (x === 0 && y === 0) ? 0 : Math.atan2(y, x) * DEGREE_PER_RAD
  170. return this.rotateSelf(theta)
  171. }
  172. rotate (rotX, rotY, rotZ) {
  173. return newInstance(this._values).rotateSelf(rotX, rotY, rotZ)
  174. }
  175. rotateSelf (rotX, rotY, rotZ) {
  176. if (rotY === undefined && rotZ === undefined) {
  177. rotZ = rotX
  178. rotX = rotY = 0
  179. }
  180. if (typeof rotY !== 'number') rotY = 0
  181. if (typeof rotZ !== 'number') rotZ = 0
  182. if (rotX !== 0 || rotY !== 0) this._is2D = false
  183. rotX *= RAD_PER_DEGREE
  184. rotY *= RAD_PER_DEGREE
  185. rotZ *= RAD_PER_DEGREE
  186. let c, s
  187. c = Math.cos(rotZ)
  188. s = Math.sin(rotZ)
  189. this._values = multiply([
  190. c, s, 0, 0,
  191. -s, c, 0, 0,
  192. 0, 0, 1, 0,
  193. 0, 0, 0, 1
  194. ], this._values)
  195. c = Math.cos(rotY)
  196. s = Math.sin(rotY)
  197. this._values = multiply([
  198. c, 0, -s, 0,
  199. 0, 1, 0, 0,
  200. s, 0, c, 0,
  201. 0, 0, 0, 1
  202. ], this._values)
  203. c = Math.cos(rotX)
  204. s = Math.sin(rotX)
  205. this._values = multiply([
  206. 1, 0, 0, 0,
  207. 0, c, s, 0,
  208. 0, -s, c, 0,
  209. 0, 0, 0, 1
  210. ], this._values)
  211. return this
  212. }
  213. rotateAxisAngle (x, y, z, angle) {
  214. return newInstance(this._values).rotateAxisAngleSelf(x, y, z, angle)
  215. }
  216. rotateAxisAngleSelf (x, y, z, angle) {
  217. if (typeof x !== 'number') x = 0
  218. if (typeof y !== 'number') y = 0
  219. if (typeof z !== 'number') z = 0
  220. // Normalize axis
  221. const length = Math.sqrt(x * x + y * y + z * z)
  222. if (length === 0) return this
  223. if (length !== 1) {
  224. x /= length
  225. y /= length
  226. z /= length
  227. }
  228. angle *= RAD_PER_DEGREE
  229. const c = Math.cos(angle)
  230. const s = Math.sin(angle)
  231. const t = 1 - c
  232. const tx = t * x
  233. const ty = t * y
  234. // NB: This is the generic transform. If the axis is a major axis, there are
  235. // faster transforms.
  236. this._values = multiply([
  237. tx * x + c, tx * y + s * z, tx * z - s * y, 0,
  238. tx * y - s * z, ty * y + c, ty * z + s * x, 0,
  239. tx * z + s * y, ty * z - s * x, t * z * z + c, 0,
  240. 0, 0, 0, 1
  241. ], this._values)
  242. if (x !== 0 || y !== 0) this._is2D = false
  243. return this
  244. }
  245. skewX (sx) {
  246. return newInstance(this._values).skewXSelf(sx)
  247. }
  248. skewXSelf (sx) {
  249. if (typeof sx !== 'number') return this
  250. const t = Math.tan(sx * RAD_PER_DEGREE)
  251. this._values = multiply([
  252. 1, 0, 0, 0,
  253. t, 1, 0, 0,
  254. 0, 0, 1, 0,
  255. 0, 0, 0, 1
  256. ], this._values)
  257. return this
  258. }
  259. skewY (sy) {
  260. return newInstance(this._values).skewYSelf(sy)
  261. }
  262. skewYSelf (sy) {
  263. if (typeof sy !== 'number') return this
  264. const t = Math.tan(sy * RAD_PER_DEGREE)
  265. this._values = multiply([
  266. 1, t, 0, 0,
  267. 0, 1, 0, 0,
  268. 0, 0, 1, 0,
  269. 0, 0, 0, 1
  270. ], this._values)
  271. return this
  272. }
  273. flipX () {
  274. return newInstance(multiply([
  275. -1, 0, 0, 0,
  276. 0, 1, 0, 0,
  277. 0, 0, 1, 0,
  278. 0, 0, 0, 1
  279. ], this._values))
  280. }
  281. flipY () {
  282. return newInstance(multiply([
  283. 1, 0, 0, 0,
  284. 0, -1, 0, 0,
  285. 0, 0, 1, 0,
  286. 0, 0, 0, 1
  287. ], this._values))
  288. }
  289. inverse () {
  290. return newInstance(this._values).invertSelf()
  291. }
  292. invertSelf () {
  293. const m = this._values
  294. const inv = m.map(v => 0)
  295. inv[0] = m[5] * m[10] * m[15] -
  296. m[5] * m[11] * m[14] -
  297. m[9] * m[6] * m[15] +
  298. m[9] * m[7] * m[14] +
  299. m[13] * m[6] * m[11] -
  300. m[13] * m[7] * m[10]
  301. inv[4] = -m[4] * m[10] * m[15] +
  302. m[4] * m[11] * m[14] +
  303. m[8] * m[6] * m[15] -
  304. m[8] * m[7] * m[14] -
  305. m[12] * m[6] * m[11] +
  306. m[12] * m[7] * m[10]
  307. inv[8] = m[4] * m[9] * m[15] -
  308. m[4] * m[11] * m[13] -
  309. m[8] * m[5] * m[15] +
  310. m[8] * m[7] * m[13] +
  311. m[12] * m[5] * m[11] -
  312. m[12] * m[7] * m[9]
  313. inv[12] = -m[4] * m[9] * m[14] +
  314. m[4] * m[10] * m[13] +
  315. m[8] * m[5] * m[14] -
  316. m[8] * m[6] * m[13] -
  317. m[12] * m[5] * m[10] +
  318. m[12] * m[6] * m[9]
  319. // If the determinant is zero, this matrix cannot be inverted, and all
  320. // values should be set to NaN, with the is2D flag set to false.
  321. const det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
  322. if (det === 0) {
  323. this._values = m.map(v => NaN)
  324. this._is2D = false
  325. return this
  326. }
  327. inv[1] = -m[1] * m[10] * m[15] +
  328. m[1] * m[11] * m[14] +
  329. m[9] * m[2] * m[15] -
  330. m[9] * m[3] * m[14] -
  331. m[13] * m[2] * m[11] +
  332. m[13] * m[3] * m[10]
  333. inv[5] = m[0] * m[10] * m[15] -
  334. m[0] * m[11] * m[14] -
  335. m[8] * m[2] * m[15] +
  336. m[8] * m[3] * m[14] +
  337. m[12] * m[2] * m[11] -
  338. m[12] * m[3] * m[10]
  339. inv[9] = -m[0] * m[9] * m[15] +
  340. m[0] * m[11] * m[13] +
  341. m[8] * m[1] * m[15] -
  342. m[8] * m[3] * m[13] -
  343. m[12] * m[1] * m[11] +
  344. m[12] * m[3] * m[9]
  345. inv[13] = m[0] * m[9] * m[14] -
  346. m[0] * m[10] * m[13] -
  347. m[8] * m[1] * m[14] +
  348. m[8] * m[2] * m[13] +
  349. m[12] * m[1] * m[10] -
  350. m[12] * m[2] * m[9]
  351. inv[2] = m[1] * m[6] * m[15] -
  352. m[1] * m[7] * m[14] -
  353. m[5] * m[2] * m[15] +
  354. m[5] * m[3] * m[14] +
  355. m[13] * m[2] * m[7] -
  356. m[13] * m[3] * m[6]
  357. inv[6] = -m[0] * m[6] * m[15] +
  358. m[0] * m[7] * m[14] +
  359. m[4] * m[2] * m[15] -
  360. m[4] * m[3] * m[14] -
  361. m[12] * m[2] * m[7] +
  362. m[12] * m[3] * m[6]
  363. inv[10] = m[0] * m[5] * m[15] -
  364. m[0] * m[7] * m[13] -
  365. m[4] * m[1] * m[15] +
  366. m[4] * m[3] * m[13] +
  367. m[12] * m[1] * m[7] -
  368. m[12] * m[3] * m[5]
  369. inv[14] = -m[0] * m[5] * m[14] +
  370. m[0] * m[6] * m[13] +
  371. m[4] * m[1] * m[14] -
  372. m[4] * m[2] * m[13] -
  373. m[12] * m[1] * m[6] +
  374. m[12] * m[2] * m[5]
  375. inv[3] = -m[1] * m[6] * m[11] +
  376. m[1] * m[7] * m[10] +
  377. m[5] * m[2] * m[11] -
  378. m[5] * m[3] * m[10] -
  379. m[9] * m[2] * m[7] +
  380. m[9] * m[3] * m[6]
  381. inv[7] = m[0] * m[6] * m[11] -
  382. m[0] * m[7] * m[10] -
  383. m[4] * m[2] * m[11] +
  384. m[4] * m[3] * m[10] +
  385. m[8] * m[2] * m[7] -
  386. m[8] * m[3] * m[6]
  387. inv[11] = -m[0] * m[5] * m[11] +
  388. m[0] * m[7] * m[9] +
  389. m[4] * m[1] * m[11] -
  390. m[4] * m[3] * m[9] -
  391. m[8] * m[1] * m[7] +
  392. m[8] * m[3] * m[5]
  393. inv[15] = m[0] * m[5] * m[10] -
  394. m[0] * m[6] * m[9] -
  395. m[4] * m[1] * m[10] +
  396. m[4] * m[2] * m[9] +
  397. m[8] * m[1] * m[6] -
  398. m[8] * m[2] * m[5]
  399. inv.forEach((v, i) => { inv[i] = v / det })
  400. this._values = inv
  401. return this
  402. }
  403. setMatrixValue (transformList) {
  404. const temp = new DOMMatrix(transformList)
  405. this._values = temp._values
  406. this._is2D = temp._is2D
  407. return this
  408. }
  409. transformPoint (point) {
  410. point = new DOMPoint(point)
  411. const x = point.x
  412. const y = point.y
  413. const z = point.z
  414. const w = point.w
  415. const values = this._values
  416. const nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w
  417. const ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w
  418. const nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w
  419. const nw = values[M14] * x + values[M24] * y + values[M34] * z + values[M44] * w
  420. return new DOMPoint(nx, ny, nz, nw)
  421. }
  422. toFloat32Array () {
  423. return Float32Array.from(this._values)
  424. }
  425. toFloat64Array () {
  426. return this._values.slice(0)
  427. }
  428. static fromMatrix (init) {
  429. if (!(init instanceof DOMMatrix)) throw new TypeError('Expected DOMMatrix')
  430. return new DOMMatrix(init._values)
  431. }
  432. static fromFloat32Array (init) {
  433. if (!(init instanceof Float32Array)) throw new TypeError('Expected Float32Array')
  434. return new DOMMatrix(init)
  435. }
  436. static fromFloat64Array (init) {
  437. if (!(init instanceof Float64Array)) throw new TypeError('Expected Float64Array')
  438. return new DOMMatrix(init)
  439. }
  440. [util.inspect.custom || 'inspect'] (depth, options) {
  441. if (depth < 0) return '[DOMMatrix]'
  442. return `DOMMatrix [
  443. a: ${this.a}
  444. b: ${this.b}
  445. c: ${this.c}
  446. d: ${this.d}
  447. e: ${this.e}
  448. f: ${this.f}
  449. m11: ${this.m11}
  450. m12: ${this.m12}
  451. m13: ${this.m13}
  452. m14: ${this.m14}
  453. m21: ${this.m21}
  454. m22: ${this.m22}
  455. m23: ${this.m23}
  456. m23: ${this.m23}
  457. m31: ${this.m31}
  458. m32: ${this.m32}
  459. m33: ${this.m33}
  460. m34: ${this.m34}
  461. m41: ${this.m41}
  462. m42: ${this.m42}
  463. m43: ${this.m43}
  464. m44: ${this.m44}
  465. is2D: ${this.is2D}
  466. isIdentity: ${this.isIdentity} ]`
  467. }
  468. }
  469. /**
  470. * Checks that `value` is a number and sets the value.
  471. */
  472. function setNumber2D (receiver, index, value) {
  473. if (typeof value !== 'number') throw new TypeError('Expected number')
  474. return (receiver._values[index] = value)
  475. }
  476. /**
  477. * Checks that `value` is a number, sets `_is2D = false` if necessary and sets
  478. * the value.
  479. */
  480. function setNumber3D (receiver, index, value) {
  481. if (typeof value !== 'number') throw new TypeError('Expected number')
  482. if (index === M33 || index === M44) {
  483. if (value !== 1) receiver._is2D = false
  484. } else if (value !== 0) receiver._is2D = false
  485. return (receiver._values[index] = value)
  486. }
  487. Object.defineProperties(DOMMatrix.prototype, {
  488. m11: { get () { return this._values[M11] }, set (v) { return setNumber2D(this, M11, v) } },
  489. m12: { get () { return this._values[M12] }, set (v) { return setNumber2D(this, M12, v) } },
  490. m13: { get () { return this._values[M13] }, set (v) { return setNumber3D(this, M13, v) } },
  491. m14: { get () { return this._values[M14] }, set (v) { return setNumber3D(this, M14, v) } },
  492. m21: { get () { return this._values[M21] }, set (v) { return setNumber2D(this, M21, v) } },
  493. m22: { get () { return this._values[M22] }, set (v) { return setNumber2D(this, M22, v) } },
  494. m23: { get () { return this._values[M23] }, set (v) { return setNumber3D(this, M23, v) } },
  495. m24: { get () { return this._values[M24] }, set (v) { return setNumber3D(this, M24, v) } },
  496. m31: { get () { return this._values[M31] }, set (v) { return setNumber3D(this, M31, v) } },
  497. m32: { get () { return this._values[M32] }, set (v) { return setNumber3D(this, M32, v) } },
  498. m33: { get () { return this._values[M33] }, set (v) { return setNumber3D(this, M33, v) } },
  499. m34: { get () { return this._values[M34] }, set (v) { return setNumber3D(this, M34, v) } },
  500. m41: { get () { return this._values[M41] }, set (v) { return setNumber2D(this, M41, v) } },
  501. m42: { get () { return this._values[M42] }, set (v) { return setNumber2D(this, M42, v) } },
  502. m43: { get () { return this._values[M43] }, set (v) { return setNumber3D(this, M43, v) } },
  503. m44: { get () { return this._values[M44] }, set (v) { return setNumber3D(this, M44, v) } },
  504. a: { get () { return this.m11 }, set (v) { return (this.m11 = v) } },
  505. b: { get () { return this.m12 }, set (v) { return (this.m12 = v) } },
  506. c: { get () { return this.m21 }, set (v) { return (this.m21 = v) } },
  507. d: { get () { return this.m22 }, set (v) { return (this.m22 = v) } },
  508. e: { get () { return this.m41 }, set (v) { return (this.m41 = v) } },
  509. f: { get () { return this.m42 }, set (v) { return (this.m42 = v) } },
  510. is2D: { get () { return this._is2D } }, // read-only
  511. isIdentity: {
  512. get () {
  513. const values = this._values
  514. return (values[M11] === 1 && values[M12] === 0 && values[M13] === 0 && values[M14] === 0 &&
  515. values[M21] === 0 && values[M22] === 1 && values[M23] === 0 && values[M24] === 0 &&
  516. values[M31] === 0 && values[M32] === 0 && values[M33] === 1 && values[M34] === 0 &&
  517. values[M41] === 0 && values[M42] === 0 && values[M43] === 0 && values[M44] === 1)
  518. }
  519. }
  520. })
  521. /**
  522. * Instantiates a DOMMatrix, bypassing the constructor.
  523. * @param {Float64Array} values Value to assign to `_values`. This is assigned
  524. * without copying (okay because all usages are followed by a multiply).
  525. */
  526. function newInstance (values) {
  527. const instance = Object.create(DOMMatrix.prototype)
  528. instance.constructor = DOMMatrix
  529. instance._is2D = true
  530. instance._values = values
  531. return instance
  532. }
  533. function multiply (A, B) {
  534. const dest = new Float64Array(16)
  535. for (let i = 0; i < 4; i++) {
  536. for (let j = 0; j < 4; j++) {
  537. let sum = 0
  538. for (let k = 0; k < 4; k++) {
  539. sum += A[i * 4 + k] * B[k * 4 + j]
  540. }
  541. dest[i * 4 + j] = sum
  542. }
  543. }
  544. return dest
  545. }
  546. module.exports = { DOMMatrix, DOMPoint }