ink.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.InkEditor = void 0;
  27. Object.defineProperty(exports, "fitCurve", {
  28. enumerable: true,
  29. get: function () {
  30. return _fit_curve.fitCurve;
  31. }
  32. });
  33. var _util = require("../../shared/util.js");
  34. var _editor = require("./editor.js");
  35. var _fit_curve = require("./fit_curve");
  36. var _tools = require("./tools.js");
  37. const RESIZER_SIZE = 16;
  38. class InkEditor extends _editor.AnnotationEditor {
  39. #aspectRatio = 0;
  40. #baseHeight = 0;
  41. #baseWidth = 0;
  42. #boundCanvasPointermove = this.canvasPointermove.bind(this);
  43. #boundCanvasPointerleave = this.canvasPointerleave.bind(this);
  44. #boundCanvasPointerup = this.canvasPointerup.bind(this);
  45. #boundCanvasPointerdown = this.canvasPointerdown.bind(this);
  46. #disableEditing = false;
  47. #isCanvasInitialized = false;
  48. #lastPoint = null;
  49. #observer = null;
  50. #realWidth = 0;
  51. #realHeight = 0;
  52. #requestFrameCallback = null;
  53. static _defaultColor = null;
  54. static _defaultOpacity = 1;
  55. static _defaultThickness = 1;
  56. static _l10nPromise;
  57. static _type = "ink";
  58. constructor(params) {
  59. super({ ...params,
  60. name: "inkEditor"
  61. });
  62. this.color = params.color || null;
  63. this.thickness = params.thickness || null;
  64. this.opacity = params.opacity || null;
  65. this.paths = [];
  66. this.bezierPath2D = [];
  67. this.currentPath = [];
  68. this.scaleFactor = 1;
  69. this.translationX = this.translationY = 0;
  70. this.x = 0;
  71. this.y = 0;
  72. }
  73. static initialize(l10n) {
  74. this._l10nPromise = new Map(["editor_ink_canvas_aria_label", "editor_ink_aria_label"].map(str => [str, l10n.get(str)]));
  75. }
  76. static updateDefaultParams(type, value) {
  77. switch (type) {
  78. case _util.AnnotationEditorParamsType.INK_THICKNESS:
  79. InkEditor._defaultThickness = value;
  80. break;
  81. case _util.AnnotationEditorParamsType.INK_COLOR:
  82. InkEditor._defaultColor = value;
  83. break;
  84. case _util.AnnotationEditorParamsType.INK_OPACITY:
  85. InkEditor._defaultOpacity = value / 100;
  86. break;
  87. }
  88. }
  89. updateParams(type, value) {
  90. switch (type) {
  91. case _util.AnnotationEditorParamsType.INK_THICKNESS:
  92. this.#updateThickness(value);
  93. break;
  94. case _util.AnnotationEditorParamsType.INK_COLOR:
  95. this.#updateColor(value);
  96. break;
  97. case _util.AnnotationEditorParamsType.INK_OPACITY:
  98. this.#updateOpacity(value);
  99. break;
  100. }
  101. }
  102. static get defaultPropertiesToUpdate() {
  103. return [[_util.AnnotationEditorParamsType.INK_THICKNESS, InkEditor._defaultThickness], [_util.AnnotationEditorParamsType.INK_COLOR, InkEditor._defaultColor || _editor.AnnotationEditor._defaultLineColor], [_util.AnnotationEditorParamsType.INK_OPACITY, Math.round(InkEditor._defaultOpacity * 100)]];
  104. }
  105. get propertiesToUpdate() {
  106. return [[_util.AnnotationEditorParamsType.INK_THICKNESS, this.thickness || InkEditor._defaultThickness], [_util.AnnotationEditorParamsType.INK_COLOR, this.color || InkEditor._defaultColor || _editor.AnnotationEditor._defaultLineColor], [_util.AnnotationEditorParamsType.INK_OPACITY, Math.round(100 * (this.opacity ?? InkEditor._defaultOpacity))]];
  107. }
  108. #updateThickness(thickness) {
  109. const savedThickness = this.thickness;
  110. this.parent.addCommands({
  111. cmd: () => {
  112. this.thickness = thickness;
  113. this.#fitToContent();
  114. },
  115. undo: () => {
  116. this.thickness = savedThickness;
  117. this.#fitToContent();
  118. },
  119. mustExec: true,
  120. type: _util.AnnotationEditorParamsType.INK_THICKNESS,
  121. overwriteIfSameType: true,
  122. keepUndo: true
  123. });
  124. }
  125. #updateColor(color) {
  126. const savedColor = this.color;
  127. this.parent.addCommands({
  128. cmd: () => {
  129. this.color = color;
  130. this.#redraw();
  131. },
  132. undo: () => {
  133. this.color = savedColor;
  134. this.#redraw();
  135. },
  136. mustExec: true,
  137. type: _util.AnnotationEditorParamsType.INK_COLOR,
  138. overwriteIfSameType: true,
  139. keepUndo: true
  140. });
  141. }
  142. #updateOpacity(opacity) {
  143. opacity /= 100;
  144. const savedOpacity = this.opacity;
  145. this.parent.addCommands({
  146. cmd: () => {
  147. this.opacity = opacity;
  148. this.#redraw();
  149. },
  150. undo: () => {
  151. this.opacity = savedOpacity;
  152. this.#redraw();
  153. },
  154. mustExec: true,
  155. type: _util.AnnotationEditorParamsType.INK_OPACITY,
  156. overwriteIfSameType: true,
  157. keepUndo: true
  158. });
  159. }
  160. rebuild() {
  161. super.rebuild();
  162. if (this.div === null) {
  163. return;
  164. }
  165. if (!this.canvas) {
  166. this.#createCanvas();
  167. this.#createObserver();
  168. }
  169. if (!this.isAttachedToDOM) {
  170. this.parent.add(this);
  171. this.#setCanvasDims();
  172. }
  173. this.#fitToContent();
  174. }
  175. remove() {
  176. if (this.canvas === null) {
  177. return;
  178. }
  179. if (!this.isEmpty()) {
  180. this.commit();
  181. }
  182. this.canvas.width = this.canvas.height = 0;
  183. this.canvas.remove();
  184. this.canvas = null;
  185. this.#observer.disconnect();
  186. this.#observer = null;
  187. super.remove();
  188. }
  189. enableEditMode() {
  190. if (this.#disableEditing || this.canvas === null) {
  191. return;
  192. }
  193. super.enableEditMode();
  194. this.div.draggable = false;
  195. this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown);
  196. this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup);
  197. }
  198. disableEditMode() {
  199. if (!this.isInEditMode() || this.canvas === null) {
  200. return;
  201. }
  202. super.disableEditMode();
  203. this.div.draggable = !this.isEmpty();
  204. this.div.classList.remove("editing");
  205. this.canvas.removeEventListener("pointerdown", this.#boundCanvasPointerdown);
  206. this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup);
  207. }
  208. onceAdded() {
  209. this.div.draggable = !this.isEmpty();
  210. }
  211. isEmpty() {
  212. return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0;
  213. }
  214. #getInitialBBox() {
  215. const {
  216. width,
  217. height,
  218. rotation
  219. } = this.parent.viewport;
  220. switch (rotation) {
  221. case 90:
  222. return [0, width, width, height];
  223. case 180:
  224. return [width, height, width, height];
  225. case 270:
  226. return [height, 0, width, height];
  227. default:
  228. return [0, 0, width, height];
  229. }
  230. }
  231. #setStroke() {
  232. this.ctx.lineWidth = this.thickness * this.parent.scaleFactor / this.scaleFactor;
  233. this.ctx.lineCap = "round";
  234. this.ctx.lineJoin = "round";
  235. this.ctx.miterLimit = 10;
  236. this.ctx.strokeStyle = `${this.color}${(0, _tools.opacityToHex)(this.opacity)}`;
  237. }
  238. #startDrawing(x, y) {
  239. this.isEditing = true;
  240. if (!this.#isCanvasInitialized) {
  241. this.#isCanvasInitialized = true;
  242. this.#setCanvasDims();
  243. this.thickness ||= InkEditor._defaultThickness;
  244. this.color ||= InkEditor._defaultColor || _editor.AnnotationEditor._defaultLineColor;
  245. this.opacity ??= InkEditor._defaultOpacity;
  246. }
  247. this.currentPath.push([x, y]);
  248. this.#lastPoint = null;
  249. this.#setStroke();
  250. this.ctx.beginPath();
  251. this.ctx.moveTo(x, y);
  252. this.#requestFrameCallback = () => {
  253. if (!this.#requestFrameCallback) {
  254. return;
  255. }
  256. if (this.#lastPoint) {
  257. if (this.isEmpty()) {
  258. this.ctx.setTransform(1, 0, 0, 1, 0, 0);
  259. this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  260. } else {
  261. this.#redraw();
  262. }
  263. this.ctx.lineTo(...this.#lastPoint);
  264. this.#lastPoint = null;
  265. this.ctx.stroke();
  266. }
  267. window.requestAnimationFrame(this.#requestFrameCallback);
  268. };
  269. window.requestAnimationFrame(this.#requestFrameCallback);
  270. }
  271. #draw(x, y) {
  272. const [lastX, lastY] = this.currentPath.at(-1);
  273. if (x === lastX && y === lastY) {
  274. return;
  275. }
  276. this.currentPath.push([x, y]);
  277. this.#lastPoint = [x, y];
  278. }
  279. #stopDrawing(x, y) {
  280. this.ctx.closePath();
  281. this.#requestFrameCallback = null;
  282. x = Math.min(Math.max(x, 0), this.canvas.width);
  283. y = Math.min(Math.max(y, 0), this.canvas.height);
  284. const [lastX, lastY] = this.currentPath.at(-1);
  285. if (x !== lastX || y !== lastY) {
  286. this.currentPath.push([x, y]);
  287. }
  288. let bezier;
  289. if (this.currentPath.length !== 1) {
  290. bezier = (0, _fit_curve.fitCurve)(this.currentPath, 30, null);
  291. } else {
  292. const xy = [x, y];
  293. bezier = [[xy, xy.slice(), xy.slice(), xy]];
  294. }
  295. const path2D = InkEditor.#buildPath2D(bezier);
  296. this.currentPath.length = 0;
  297. const cmd = () => {
  298. this.paths.push(bezier);
  299. this.bezierPath2D.push(path2D);
  300. this.rebuild();
  301. };
  302. const undo = () => {
  303. this.paths.pop();
  304. this.bezierPath2D.pop();
  305. if (this.paths.length === 0) {
  306. this.remove();
  307. } else {
  308. if (!this.canvas) {
  309. this.#createCanvas();
  310. this.#createObserver();
  311. }
  312. this.#fitToContent();
  313. }
  314. };
  315. this.parent.addCommands({
  316. cmd,
  317. undo,
  318. mustExec: true
  319. });
  320. }
  321. #redraw() {
  322. if (this.isEmpty()) {
  323. this.#updateTransform();
  324. return;
  325. }
  326. this.#setStroke();
  327. const {
  328. canvas,
  329. ctx
  330. } = this;
  331. ctx.setTransform(1, 0, 0, 1, 0, 0);
  332. ctx.clearRect(0, 0, canvas.width, canvas.height);
  333. this.#updateTransform();
  334. for (const path of this.bezierPath2D) {
  335. ctx.stroke(path);
  336. }
  337. }
  338. commit() {
  339. if (this.#disableEditing) {
  340. return;
  341. }
  342. super.commit();
  343. this.isEditing = false;
  344. this.disableEditMode();
  345. this.setInForeground();
  346. this.#disableEditing = true;
  347. this.div.classList.add("disabled");
  348. this.#fitToContent(true);
  349. this.parent.addInkEditorIfNeeded(true);
  350. this.parent.moveEditorInDOM(this);
  351. this.div.focus();
  352. }
  353. focusin(event) {
  354. super.focusin(event);
  355. this.enableEditMode();
  356. }
  357. canvasPointerdown(event) {
  358. if (event.button !== 0 || !this.isInEditMode() || this.#disableEditing) {
  359. return;
  360. }
  361. this.setInForeground();
  362. if (event.type !== "mouse") {
  363. this.div.focus();
  364. }
  365. event.stopPropagation();
  366. this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave);
  367. this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove);
  368. this.#startDrawing(event.offsetX, event.offsetY);
  369. }
  370. canvasPointermove(event) {
  371. event.stopPropagation();
  372. this.#draw(event.offsetX, event.offsetY);
  373. }
  374. canvasPointerup(event) {
  375. if (event.button !== 0) {
  376. return;
  377. }
  378. if (this.isInEditMode() && this.currentPath.length !== 0) {
  379. event.stopPropagation();
  380. this.#endDrawing(event);
  381. this.setInBackground();
  382. }
  383. }
  384. canvasPointerleave(event) {
  385. this.#endDrawing(event);
  386. this.setInBackground();
  387. }
  388. #endDrawing(event) {
  389. this.#stopDrawing(event.offsetX, event.offsetY);
  390. this.canvas.removeEventListener("pointerleave", this.#boundCanvasPointerleave);
  391. this.canvas.removeEventListener("pointermove", this.#boundCanvasPointermove);
  392. this.parent.addToAnnotationStorage(this);
  393. }
  394. #createCanvas() {
  395. this.canvas = document.createElement("canvas");
  396. this.canvas.width = this.canvas.height = 0;
  397. this.canvas.className = "inkEditorCanvas";
  398. InkEditor._l10nPromise.get("editor_ink_canvas_aria_label").then(msg => this.canvas?.setAttribute("aria-label", msg));
  399. this.div.append(this.canvas);
  400. this.ctx = this.canvas.getContext("2d");
  401. }
  402. #createObserver() {
  403. this.#observer = new ResizeObserver(entries => {
  404. const rect = entries[0].contentRect;
  405. if (rect.width && rect.height) {
  406. this.setDimensions(rect.width, rect.height);
  407. }
  408. });
  409. this.#observer.observe(this.div);
  410. }
  411. render() {
  412. if (this.div) {
  413. return this.div;
  414. }
  415. let baseX, baseY;
  416. if (this.width) {
  417. baseX = this.x;
  418. baseY = this.y;
  419. }
  420. super.render();
  421. InkEditor._l10nPromise.get("editor_ink_aria_label").then(msg => this.div?.setAttribute("aria-label", msg));
  422. const [x, y, w, h] = this.#getInitialBBox();
  423. this.setAt(x, y, 0, 0);
  424. this.setDims(w, h);
  425. this.#createCanvas();
  426. if (this.width) {
  427. const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
  428. this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
  429. this.#isCanvasInitialized = true;
  430. this.#setCanvasDims();
  431. this.setDims(this.width * parentWidth, this.height * parentHeight);
  432. this.#redraw();
  433. this.#setMinDims();
  434. this.div.classList.add("disabled");
  435. } else {
  436. this.div.classList.add("editing");
  437. this.enableEditMode();
  438. }
  439. this.#createObserver();
  440. return this.div;
  441. }
  442. #setCanvasDims() {
  443. if (!this.#isCanvasInitialized) {
  444. return;
  445. }
  446. const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
  447. this.canvas.width = Math.ceil(this.width * parentWidth);
  448. this.canvas.height = Math.ceil(this.height * parentHeight);
  449. this.#updateTransform();
  450. }
  451. setDimensions(width, height) {
  452. const roundedWidth = Math.round(width);
  453. const roundedHeight = Math.round(height);
  454. if (this.#realWidth === roundedWidth && this.#realHeight === roundedHeight) {
  455. return;
  456. }
  457. this.#realWidth = roundedWidth;
  458. this.#realHeight = roundedHeight;
  459. this.canvas.style.visibility = "hidden";
  460. if (this.#aspectRatio && Math.abs(this.#aspectRatio - width / height) > 1e-2) {
  461. height = Math.ceil(width / this.#aspectRatio);
  462. this.setDims(width, height);
  463. }
  464. const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
  465. this.width = width / parentWidth;
  466. this.height = height / parentHeight;
  467. if (this.#disableEditing) {
  468. this.#setScaleFactor(width, height);
  469. }
  470. this.#setCanvasDims();
  471. this.#redraw();
  472. this.canvas.style.visibility = "visible";
  473. }
  474. #setScaleFactor(width, height) {
  475. const padding = this.#getPadding();
  476. const scaleFactorW = (width - padding) / this.#baseWidth;
  477. const scaleFactorH = (height - padding) / this.#baseHeight;
  478. this.scaleFactor = Math.min(scaleFactorW, scaleFactorH);
  479. }
  480. #updateTransform() {
  481. const padding = this.#getPadding() / 2;
  482. this.ctx.setTransform(this.scaleFactor, 0, 0, this.scaleFactor, this.translationX * this.scaleFactor + padding, this.translationY * this.scaleFactor + padding);
  483. }
  484. static #buildPath2D(bezier) {
  485. const path2D = new Path2D();
  486. for (let i = 0, ii = bezier.length; i < ii; i++) {
  487. const [first, control1, control2, second] = bezier[i];
  488. if (i === 0) {
  489. path2D.moveTo(...first);
  490. }
  491. path2D.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], second[0], second[1]);
  492. }
  493. return path2D;
  494. }
  495. #serializePaths(s, tx, ty, h) {
  496. const NUMBER_OF_POINTS_ON_BEZIER_CURVE = 4;
  497. const paths = [];
  498. const padding = this.thickness / 2;
  499. let buffer, points;
  500. for (const bezier of this.paths) {
  501. buffer = [];
  502. points = [];
  503. for (let i = 0, ii = bezier.length; i < ii; i++) {
  504. const [first, control1, control2, second] = bezier[i];
  505. const p10 = s * (first[0] + tx) + padding;
  506. const p11 = h - s * (first[1] + ty) - padding;
  507. const p20 = s * (control1[0] + tx) + padding;
  508. const p21 = h - s * (control1[1] + ty) - padding;
  509. const p30 = s * (control2[0] + tx) + padding;
  510. const p31 = h - s * (control2[1] + ty) - padding;
  511. const p40 = s * (second[0] + tx) + padding;
  512. const p41 = h - s * (second[1] + ty) - padding;
  513. if (i === 0) {
  514. buffer.push(p10, p11);
  515. points.push(p10, p11);
  516. }
  517. buffer.push(p20, p21, p30, p31, p40, p41);
  518. this.#extractPointsOnBezier(p10, p11, p20, p21, p30, p31, p40, p41, NUMBER_OF_POINTS_ON_BEZIER_CURVE, points);
  519. }
  520. paths.push({
  521. bezier: buffer,
  522. points
  523. });
  524. }
  525. return paths;
  526. }
  527. #extractPointsOnBezier(p10, p11, p20, p21, p30, p31, p40, p41, n, points) {
  528. if (this.#isAlmostFlat(p10, p11, p20, p21, p30, p31, p40, p41)) {
  529. points.push(p40, p41);
  530. return;
  531. }
  532. for (let i = 1; i < n - 1; i++) {
  533. const t = i / n;
  534. const mt = 1 - t;
  535. let q10 = t * p10 + mt * p20;
  536. let q11 = t * p11 + mt * p21;
  537. let q20 = t * p20 + mt * p30;
  538. let q21 = t * p21 + mt * p31;
  539. const q30 = t * p30 + mt * p40;
  540. const q31 = t * p31 + mt * p41;
  541. q10 = t * q10 + mt * q20;
  542. q11 = t * q11 + mt * q21;
  543. q20 = t * q20 + mt * q30;
  544. q21 = t * q21 + mt * q31;
  545. q10 = t * q10 + mt * q20;
  546. q11 = t * q11 + mt * q21;
  547. points.push(q10, q11);
  548. }
  549. points.push(p40, p41);
  550. }
  551. #isAlmostFlat(p10, p11, p20, p21, p30, p31, p40, p41) {
  552. const tol = 10;
  553. const ax = (3 * p20 - 2 * p10 - p40) ** 2;
  554. const ay = (3 * p21 - 2 * p11 - p41) ** 2;
  555. const bx = (3 * p30 - p10 - 2 * p40) ** 2;
  556. const by = (3 * p31 - p11 - 2 * p41) ** 2;
  557. return Math.max(ax, bx) + Math.max(ay, by) <= tol;
  558. }
  559. #getBbox() {
  560. let xMin = Infinity;
  561. let xMax = -Infinity;
  562. let yMin = Infinity;
  563. let yMax = -Infinity;
  564. for (const path of this.paths) {
  565. for (const [first, control1, control2, second] of path) {
  566. const bbox = _util.Util.bezierBoundingBox(...first, ...control1, ...control2, ...second);
  567. xMin = Math.min(xMin, bbox[0]);
  568. yMin = Math.min(yMin, bbox[1]);
  569. xMax = Math.max(xMax, bbox[2]);
  570. yMax = Math.max(yMax, bbox[3]);
  571. }
  572. }
  573. return [xMin, yMin, xMax, yMax];
  574. }
  575. #getPadding() {
  576. return this.#disableEditing ? Math.ceil(this.thickness * this.parent.scaleFactor) : 0;
  577. }
  578. #fitToContent(firstTime = false) {
  579. if (this.isEmpty()) {
  580. return;
  581. }
  582. if (!this.#disableEditing) {
  583. this.#redraw();
  584. return;
  585. }
  586. const bbox = this.#getBbox();
  587. const padding = this.#getPadding();
  588. this.#baseWidth = Math.max(RESIZER_SIZE, bbox[2] - bbox[0]);
  589. this.#baseHeight = Math.max(RESIZER_SIZE, bbox[3] - bbox[1]);
  590. const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor);
  591. const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor);
  592. const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
  593. this.width = width / parentWidth;
  594. this.height = height / parentHeight;
  595. this.#aspectRatio = width / height;
  596. this.#setMinDims();
  597. const prevTranslationX = this.translationX;
  598. const prevTranslationY = this.translationY;
  599. this.translationX = -bbox[0];
  600. this.translationY = -bbox[1];
  601. this.#setCanvasDims();
  602. this.#redraw();
  603. this.#realWidth = width;
  604. this.#realHeight = height;
  605. this.setDims(width, height);
  606. const unscaledPadding = firstTime ? padding / this.scaleFactor / 2 : 0;
  607. this.translate(prevTranslationX - this.translationX - unscaledPadding, prevTranslationY - this.translationY - unscaledPadding);
  608. }
  609. #setMinDims() {
  610. const {
  611. style
  612. } = this.div;
  613. if (this.#aspectRatio >= 1) {
  614. style.minHeight = `${RESIZER_SIZE}px`;
  615. style.minWidth = `${Math.round(this.#aspectRatio * RESIZER_SIZE)}px`;
  616. } else {
  617. style.minWidth = `${RESIZER_SIZE}px`;
  618. style.minHeight = `${Math.round(RESIZER_SIZE / this.#aspectRatio)}px`;
  619. }
  620. }
  621. static deserialize(data, parent) {
  622. const editor = super.deserialize(data, parent);
  623. editor.thickness = data.thickness;
  624. editor.color = _util.Util.makeHexColor(...data.color);
  625. editor.opacity = data.opacity;
  626. const [pageWidth, pageHeight] = parent.pageDimensions;
  627. const width = editor.width * pageWidth;
  628. const height = editor.height * pageHeight;
  629. const scaleFactor = parent.scaleFactor;
  630. const padding = data.thickness / 2;
  631. editor.#aspectRatio = width / height;
  632. editor.#disableEditing = true;
  633. editor.#realWidth = Math.round(width);
  634. editor.#realHeight = Math.round(height);
  635. for (const {
  636. bezier
  637. } of data.paths) {
  638. const path = [];
  639. editor.paths.push(path);
  640. let p0 = scaleFactor * (bezier[0] - padding);
  641. let p1 = scaleFactor * (height - bezier[1] - padding);
  642. for (let i = 2, ii = bezier.length; i < ii; i += 6) {
  643. const p10 = scaleFactor * (bezier[i] - padding);
  644. const p11 = scaleFactor * (height - bezier[i + 1] - padding);
  645. const p20 = scaleFactor * (bezier[i + 2] - padding);
  646. const p21 = scaleFactor * (height - bezier[i + 3] - padding);
  647. const p30 = scaleFactor * (bezier[i + 4] - padding);
  648. const p31 = scaleFactor * (height - bezier[i + 5] - padding);
  649. path.push([[p0, p1], [p10, p11], [p20, p21], [p30, p31]]);
  650. p0 = p30;
  651. p1 = p31;
  652. }
  653. const path2D = this.#buildPath2D(path);
  654. editor.bezierPath2D.push(path2D);
  655. }
  656. const bbox = editor.#getBbox();
  657. editor.#baseWidth = Math.max(RESIZER_SIZE, bbox[2] - bbox[0]);
  658. editor.#baseHeight = Math.max(RESIZER_SIZE, bbox[3] - bbox[1]);
  659. editor.#setScaleFactor(width, height);
  660. return editor;
  661. }
  662. serialize() {
  663. if (this.isEmpty()) {
  664. return null;
  665. }
  666. const rect = this.getRect(0, 0);
  667. const height = this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0];
  668. const color = _editor.AnnotationEditor._colorManager.convert(this.ctx.strokeStyle);
  669. return {
  670. annotationType: _util.AnnotationEditorType.INK,
  671. color,
  672. thickness: this.thickness,
  673. opacity: this.opacity,
  674. paths: this.#serializePaths(this.scaleFactor / this.parent.scaleFactor, this.translationX, this.translationY, height),
  675. pageIndex: this.parent.pageIndex,
  676. rect,
  677. rotation: this.rotation
  678. };
  679. }
  680. }
  681. exports.InkEditor = InkEditor;