openchemlib 8.14.0 → 8.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/full.d.ts CHANGED
@@ -5,5 +5,12 @@ export {
5
5
  SVGRenderer,
6
6
  AtomHighlightCallback,
7
7
  BondHighlightCallback,
8
- ChangeListenerCallback
8
+ ChangeListenerCallback,
9
+ OnChangeEventType,
10
+ OnChangeEvent,
11
+ OnChangeListenerCallback,
12
+ CanvasEditorMode,
13
+ CanvasEditorOptions,
14
+ CanvasEditor,
15
+ registerCustomElement,
9
16
  } from './types';
package/full.js CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  const OCL = require('./dist/openchemlib-full.js');
4
4
 
5
+ require('./lib/canvas_editor/init')(OCL);
6
+
7
+ exports.CanvasEditor = OCL.CanvasEditor;
8
+ exports.registerCustomElement = OCL.registerCustomElement;
9
+
5
10
  exports.default = OCL;
6
11
  exports.CanonizerUtil = OCL.CanonizerUtil;
7
12
  exports.ConformerGenerator = OCL.ConformerGenerator;
@@ -0,0 +1 @@
1
+ export * from './full';
package/full.pretty.js CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  const OCL = require('./dist/openchemlib-full.pretty.js');
4
4
 
5
+ require('./lib/canvas_editor/init')(OCL);
6
+
7
+ exports.CanvasEditor = OCL.CanvasEditor;
8
+ exports.registerCustomElement = OCL.registerCustomElement;
9
+
5
10
  exports.default = OCL;
6
11
  exports.CanonizerUtil = OCL.CanonizerUtil;
7
12
  exports.ConformerGenerator = OCL.ConformerGenerator;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ class ClipboardHandler {
4
+ copyMolecule(molecule) {
5
+ const data = molecule.getIDCodeAndCoordinates();
6
+ navigator.clipboard.writeText(`${data.idCode} ${data.coordinates}`);
7
+ }
8
+
9
+ pasteMolecule() {
10
+ // TODO: find a way to implement this in a synchronous way.
11
+ return null;
12
+ }
13
+ }
14
+
15
+ module.exports = ClipboardHandler;
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ const EditorArea = require('./editor_area');
4
+ const getEditorStylesheet = require('./editor_stylesheet');
5
+ const { addPointerListeners, addKeyboardListeners } = require('./events');
6
+ const Toolbar = require('./toolbar');
7
+ const UIHelper = require('./ui_helper');
8
+
9
+ function createEditor(
10
+ parentElement,
11
+ options,
12
+ onChange,
13
+ JavaEditorArea,
14
+ JavaEditorToolbar,
15
+ JavaUIHelper,
16
+ Molecule,
17
+ Reaction,
18
+ ) {
19
+ const {
20
+ readOnly = false,
21
+ initialMode = 'molecule',
22
+ initialFragment = false,
23
+ } = options;
24
+
25
+ const rootElement = document.createElement('div');
26
+ Object.assign(rootElement.style, {
27
+ width: '100%',
28
+ height: '100%',
29
+ display: 'flex',
30
+ flexDirection: 'row',
31
+ alignItems: 'start',
32
+ backgroundColor: 'white',
33
+ // Prevent side effects of pointer events, like scrolling the page or text
34
+ // selection.
35
+ touchAction: 'none',
36
+ userSelect: 'none',
37
+ webkitUserSelect: 'none',
38
+ });
39
+
40
+ const shadowRoot = rootElement.attachShadow({ mode: 'closed' });
41
+
42
+ shadowRoot.adoptedStyleSheets = [getEditorStylesheet()];
43
+
44
+ let toolbarCanvas = null;
45
+ if (!readOnly) {
46
+ toolbarCanvas = document.createElement('canvas');
47
+ shadowRoot.append(toolbarCanvas);
48
+ }
49
+
50
+ const editorContainer = document.createElement('div');
51
+ Object.assign(editorContainer.style, {
52
+ width: '100%',
53
+ height: '100%',
54
+ });
55
+ shadowRoot.append(editorContainer);
56
+
57
+ const editorCanvas = document.createElement('canvas');
58
+ editorCanvas.tabIndex = 0;
59
+ Object.assign(editorCanvas.style, {
60
+ outline: 'none',
61
+ });
62
+ editorContainer.append(editorCanvas);
63
+
64
+ parentElement.append(rootElement);
65
+
66
+ const uiHelper = new JavaUIHelper(
67
+ new UIHelper(editorCanvas, editorContainer, JavaEditorArea),
68
+ );
69
+
70
+ const editorArea = new JavaEditorArea(
71
+ computeMode(initialMode, JavaEditorArea),
72
+ new EditorArea(editorCanvas, onChange),
73
+ uiHelper,
74
+ );
75
+ if (initialFragment) {
76
+ if (initialMode === 'molecule') {
77
+ const fragmentMolecule = new Molecule(0, 0);
78
+ fragmentMolecule.setFragment(true);
79
+ editorArea.setMolecule(fragmentMolecule);
80
+ } else {
81
+ const fragmentReaction = Reaction.create();
82
+ fragmentReaction.setFragment(true);
83
+ editorArea.setReaction(fragmentReaction);
84
+ }
85
+ }
86
+
87
+ uiHelper.setEditorArea(editorArea);
88
+
89
+ const toolbar = readOnly
90
+ ? null
91
+ : new JavaEditorToolbar(editorArea, new Toolbar(toolbarCanvas));
92
+
93
+ const containerSize = editorContainer.getBoundingClientRect();
94
+ editorCanvas.width = containerSize.width;
95
+ editorCanvas.height = containerSize.height;
96
+
97
+ editorArea.repaint();
98
+
99
+ const resizeObserver = new ResizeObserver(([entry]) => {
100
+ editorCanvas.width = entry.contentRect.width;
101
+ editorCanvas.height = entry.contentRect.height;
102
+ editorArea.repaint();
103
+ });
104
+ resizeObserver.observe(editorContainer);
105
+
106
+ let removePointerListeners = null;
107
+ let removeKeyboardListeners = null;
108
+ let removeToolbarPointerListeners = null;
109
+
110
+ if (!readOnly) {
111
+ removePointerListeners = addPointerListeners(
112
+ editorCanvas,
113
+ editorArea,
114
+ JavaEditorArea,
115
+ );
116
+ removeKeyboardListeners = addKeyboardListeners(
117
+ editorCanvas,
118
+ editorArea,
119
+ JavaEditorArea,
120
+ );
121
+ removeToolbarPointerListeners = addPointerListeners(
122
+ toolbarCanvas,
123
+ toolbar,
124
+ JavaEditorArea,
125
+ );
126
+ }
127
+
128
+ function destroy() {
129
+ rootElement.remove();
130
+ resizeObserver.disconnect();
131
+ removePointerListeners?.();
132
+ removeKeyboardListeners?.();
133
+ removeToolbarPointerListeners?.();
134
+ }
135
+
136
+ return { editorArea, toolbar, uiHelper, destroy };
137
+ }
138
+
139
+ function computeMode(initialMode, JavaEditorArea) {
140
+ switch (initialMode) {
141
+ case 'molecule':
142
+ return 0;
143
+ case 'reaction':
144
+ return (
145
+ JavaEditorArea.MODE_REACTION | JavaEditorArea.MODE_MULTIPLE_FRAGMENTS
146
+ );
147
+ default:
148
+ throw new Error(`Invalid initial mode: ${initialMode}`);
149
+ }
150
+ }
151
+
152
+ module.exports = createEditor;
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ // This code is unused for now and is kept for reference in case we want to
4
+ // support low definition cursors in the future.
5
+
6
+ const computedCursors = Object.create(null);
7
+
8
+ class CursorManager {
9
+ constructor(JavaEditorArea, javaUiHelper) {
10
+ this.IMAGE_DATA_16 = JavaEditorArea.IMAGE_DATA_16;
11
+ this.HOTSPOT_16 = JavaEditorArea.HOTSPOT_16;
12
+ this.cPointerCursor = JavaEditorArea.cPointerCursor;
13
+ this.cTextCursor = JavaEditorArea.cTextCursor;
14
+ this.javaUiHelper = javaUiHelper;
15
+ }
16
+
17
+ getCursor(cursor) {
18
+ if (computedCursors[cursor]) {
19
+ return computedCursors[cursor];
20
+ }
21
+ if (this.IMAGE_DATA_16[cursor]) {
22
+ return this.buildCursor(cursor);
23
+ }
24
+ switch (cursor) {
25
+ case this.cPointerCursor:
26
+ return 'default';
27
+ case this.cTextCursor:
28
+ return 'text';
29
+ default:
30
+ throw new Error(`Unknown cursor: ${cursor}`);
31
+ }
32
+ }
33
+
34
+ buildCursor(cursor) {
35
+ const cursorImage = this.javaUiHelper.build16x16CursorImage(cursor);
36
+ const dataURL = cursorImage.toDataURL();
37
+ return `url(${dataURL}) ${this.HOTSPOT_16[cursor * 2]} ${this.HOTSPOT_16[cursor * 2 + 1]}, default`;
38
+ }
39
+ }
40
+
41
+ module.exports = CursorManager;
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const cursors = require('./cursors_24px');
4
+
5
+ const computedCursors = Object.create(null);
6
+
7
+ const scaleFactor = 3 / 4;
8
+
9
+ class CursorManager {
10
+ constructor(JavaEditorArea) {
11
+ this.HOTSPOT_32 = JavaEditorArea.HOTSPOT_32;
12
+ this.IMAGE_NAME_32 = JavaEditorArea.IMAGE_NAME_32;
13
+ this.cPointerCursor = JavaEditorArea.cPointerCursor;
14
+ this.cTextCursor = JavaEditorArea.cTextCursor;
15
+ }
16
+
17
+ getCursor(cursor) {
18
+ if (computedCursors[cursor]) {
19
+ return computedCursors[cursor];
20
+ }
21
+ if (this.IMAGE_NAME_32[cursor]) {
22
+ return this.buildCursor(cursor);
23
+ }
24
+ switch (cursor) {
25
+ case this.cPointerCursor:
26
+ return 'default';
27
+ case this.cTextCursor:
28
+ return 'text';
29
+ default:
30
+ throw new Error(`Unknown cursor: ${cursor}`);
31
+ }
32
+ }
33
+
34
+ buildCursor(cursor) {
35
+ const cursorName = this.IMAGE_NAME_32[cursor];
36
+ const cursorUrl = cursors[cursorName];
37
+ const builtCursor = `${cursorUrl} ${this.HOTSPOT_32[cursor * 2] * scaleFactor} ${this.HOTSPOT_32[cursor * 2 + 1] * scaleFactor}, default`;
38
+ computedCursors[cursor] = builtCursor;
39
+ return builtCursor;
40
+ }
41
+ }
42
+
43
+ module.exports = CursorManager;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ // This file is generated by scripts/create_24px_cursors.js
4
+ // Do not modify it manually
5
+
6
+ const cursors = {
7
+ 'chain.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAB+klEQVR4XmNgGCaAkQEbZGJAhyBVQMDEwMrAhgHZGbBBVogGNrgkF4MkoyyTEAMHFijHKMfIh6qBiyGB8bHeg6hdwtJoGngY8hnfOn2wXQxyE8IGScYnurN3m7+50HCJG9WOBsb3oQHP5pz6bwbXwM2gxPhLYfEmsd8M/+3e3Sq7ySEOtyWB9WEo50+2v8UX/ptCNXACHfNEZ8lGlZ/c/9OOM/93fH2larcQSAsPQynjjTDDhwz/M8+/DJoPc5IgwyvN+VuEfnP+X9r+TSNzD8N/49dXK25ycDDUMn4OkH/D+D/n9vugFez8DAw6DD84fon9UF22Rvwnz5+2mff5ZBmf6PmdZfzv9OJe4T/+B8FCX3j+Vl744TeZCRT4DP85rxQ3Hyg6o/Cd8X/f9LtixkBn2DI+1k8/yPjf4cWeacb3mf8XXnjptwionAOswTrmOes/5v8M/xf3vuaUYeACe1WJ8atm8hGG/4I/Gf7n3/vgv5ZVlIETqsFb9wcDUDnDf+9bzEihr8f4wtj3AvvfjMtfA6cATeeEa9CsvST0j++//Z0HpuxwDZwM/AzZTG8NZ0x+5t/AAlEOs4Hpo+uROYcWfXT4yIKILJA0L4MsoxybHDM3VDlUAwODE8Nexn2MZUiJDVtKAomxA5MpGDBiJGTMpA0RASfvIQ8AAq+nV0XS18QAAAAASUVORK5CYII=)',
8
+ 'eraser.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAC1klEQVR4Xn2TS0wTURSGz1gRikIRAy5ooUpijEYDgQULA1sfSxJDYoyLGvEdtupa1FATFBKiEVERShR5yDNYQdpqVKhEEEsk0UIl5RHBUoZ2aOcezx1apCD2djGT+b7z/3MnFxCmwLXBCgBuwZiJiKeEd0I9NEATtEA7dEAXdEMPWMAGg+BL/VRuN8o6JzjgG4yCk1T610MVPIYaqIPnJDZDG40wwyR40q11Ob6D8+8rAvpxwr/DOExwoYHwagV/QTmtSkovBJNt1XsCGtyOm7C/Qk4ZgzHC3Vxopumm0PRWmt4NQyCmWqpyxVgsRhMmYLrXfFdOmaHUX1xooenPKOcllemAV/AO/Lutpky/Gp8i/1kwihR7RVA/B3NcaFfKhLvbeJlHB5bisVTBl5UEKvaxXNZ6udChvCrvboYBQdTbHvIyN1dwfmHCbbjX01OKySR0KWU6qYwVRL21hpepicD5zVbcgde/YDYJZqV7N3UPJtkqM/0aLFmDL2IBJRx1/zyPAgk9NL2LPpOU8rYyd0G9Dkc8jdGYP/Xjsj+O8XewUEYv+BOXCo5JGqxbVyYP1XhictLA1AyQCzbaXzmp/071m69PtGIcjq5SRDxJ7Y+4XRdRJROuCIOwsMt+L2teYGcdTW1pXh2+XlFO4WY8Pu28FKQyHCchAJLW/mCnJKCAKnbGYTepUBdKycVYLJhyG1h0GCcBEz6U7VvguLLYlc92U5o3DvvQQPhht7OIRf3FuZB1bXgFV5RCR2ObVtQQTmWK5PjlKuEFGDNyK2NOoPuwpmLnhoefCpg/M1HIoiNxSvAD0w4Z93siU64OjN+fNPhj1uLKLrmFRd1ISd70agXkkTKWuBhRJrRLLjp8XsB4R8nqlKq+QAbjPdctSuCHzweSbvB29qwgCZLad6PXk/MvOPThnMpZ/S0w7diFWmOtsbFYPCSp/iO4lLM6A7MgAwutjXCEP+qfgSn0LjMHAAAAAElFTkSuQmCC)',
9
+ 'fist.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAABmUlEQVR4Xu2SXStlYRTH1+O84IyLIXEhHaZIzc3IkblUknIjczGdKxk+gpIbTVMuTxHl5QMopWSapiivKVdyQ804hdyMlzBjJiPj2D9rnzlMs59NPoC12k/tp/9vrWe9iDxZugMheSsJ6XpMN4w8V38VpoDIhemXN9Ii0QdA05affJE0u3H26CdMDS+vA6OSfS9iVuLMq6ybCz4SZZVZSk6l8D6g2tDFH+rpUeATFXxjhxghcjCfJWxjGaCHYcWW6eCYXc33jj7ykWc+QIiESn/wHYffKk+xzWsWWafUAkoinTkfshniGvRzT0fPSzY5Z40yL2AmamkiNwOo+s5cbIMqD1AaPBrQ6yiD6dj/A3CgwTw1BJbec8acFul4APf3nLhVw3izxkn5yB29O6PVAoqLmPGJ7TbA4ZBGCwhIolNT+z3I0fHVWYCReOHJolXy3+auajvswWXJQox9K4dbwwjBKV16yyrLf47ps67Sg7v1FL9ov8rrlSwbCJrJmO7nF5Js8TXjW4xStB9q8N/YiEwLXs9D5v7JbwDwWqDxPOOKJwAAAABJRU5ErkJggg==)',
10
+ 'hand.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAB8klEQVR4XqWTvWuTURTGnzeviEWpQjBSbWot2EwOTiIiATcLxcXBFq2aiOig4Og/IEVcFOnkYMFBBIfuDmp1qKhUK61INo2g2KKt0TRfP8+NSfX9iIvncuHl5fye59xzz5X+J3ylHL5Jd/ewl250USPK/EPRV3JYU3og7jPHYUQGzelcZySVYB8H8XjKV46S5QtDaLojkEhlTLnIANMsc8yAn5xCM+rRdnkxmJfazRsDBteAMjm0ohd6qa4OwDwfDHjUciiT5wAT9KGHscAg83w04MkakOMQJU50BhaawOMWsMoZA1Y5iRdw6FU+gS57vW2HP0DegO+MhRxuD3CW/op3K2MlfWaYZ6wwwiXTdkA5AkxdsL5P2EXtMqBKwTQrvOWTpbqSqgYkAiVN7jfFIjvYaUDNVoN6c9dM6FvzDMEuZbusLyUrywEutcHvqPLDvqsGBB02+wtXzX7SgNdNIBiViIN0J2slLZt++S99Wqg7Q7CtUl/30j3Tjqo7qMJx1odu2teNnI1aXDTs/yhbI6MxlF56ZQ7haFinFu1mdD08S2nNjlmnguFaW2eWfrQxOnyj22yGgh1y6fCcdCzQs+79ebN3SBtzV1djnA3F2PegI8nFmzYSBdvvWqvAFbbUvWvy4x6qp9MqyWYqtGaUbKf/ArRKFFCzSnv0AAAAAElFTkSuQmCC)',
11
+ 'handPlus.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAACDUlEQVR4XqWTz2sTQRiGv01ELEoVgpFqU2vB5uTBk6hIwJuF4sWDLRo1EdGDisf+AYqIBxXpyYMFDyJ46F2wWj1UVKqVViQ3jaDYUltj06Tp4zubbGXzw4sz7OzsMs/7fvPNN2b/06IWd/gme7iHvbRjl2zAkv9QjFqs30btifGYKY5gJLEpO98aiUfYxyE8XjLPMVL8oA8bbwlE4kkp5+lhnAWOC1jiNDZhHbbdvCaYF9/NBwG9a0CRDLZob+yttbUApvki4FnNoUiWgwzThY01BXqZ5quAF2tAhsMUONkamPGB5zVgmbMCljmFF3LotGwEG/I6A4e/QFbAL9J1Dvd7OEd3ybuXVEjf6ecViwxwRdoOKDYAoxeV92Ed1C4BZXLSLPGRb1rqQioLiIRCGtkvxTw72ClgRX3Vfyoa5/np7yGcpVSb8lJQWA6oaOkqQzwFaf/WvCwg7LA5OnND9iMC3vsACu+qxmorNTiYPUgppAXpF339AHAz55OuS6tZV/vcI2k79ct+Nw747zH9KXGC9XUnHbU7GZWaaxbqtwQsMcjWhtLoS8y9k0PQqntwjivM6mTsdn0tJWwyrUyFAZfaCpN0Yxsbi29wm2qous1g0245vCbRFOhY9/mC7B0SYO7oVrjOhnzT+2BHY7N3VRI5PZ9qPcc1tlS8mxZtdlE9O2OFcJb8rwmLBcv/ADEL/+BUjuGhAAAAAElFTkSuQmCC)',
12
+ 'invisible.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAACgklEQVR4XnWU3W9LYRzHv/+Gt7AEwY0bErELN/RGQuzCW7igtcy8X7pzLUiwZRIXIiIWZjGbdanqsq1cYEaXYF1ss+6YpUGLrrPurD7P01Nrl8qv6Xn7fV9+3+c5R3lNKfGfyilPOWVPae9Si1r1SO3qVFAhRdStXkUVU1Zv1C9XY/qguIY5JsSvRbd0W3fVrAcA2/QEirC+Kq0+bZNPr5TTOO0j/DsG0Er7Hdv+EJ0Oq9KjOTQ2aqmWaBM6rj5TjiYNoA32ex57B+wRDSoDu0/rdFkXtVrbAbtKovrNANphv4/OY8wE9VQvNEP7Vm3A6rR+qlFrtEMDaP6gAHRaM0XvUWtmC003NKuUJpigEZXNdvxfBhC0oxrvYXgytPu0Xtd5mNIQyUyhcsWqPCdkACFrpgszfdZ7tTWTRT5OMjFo8nrP3VW6VACErfcI3ufgqIargfbftn2YsY2to1quWo4W0A17CKY/QHywGzNJzJjcm0nGUR0kpzRKGPMG0ItGDxezukDuO2nOkfmI3uo8tiZ0QFU6B3CesgpRLly9Y5JRBbRMx1keV5/0jIkcHdNK7iRode3O4jSG3wGdVA1mXsqvFToLJG03Qp3Wwj7GdIbdAnJ479ch7aL26CqjBoDUA3C0n2QWzHiAPOy1tt1UjW4C8WPMryN4PwF7wXuxOGnS7n8AA7mGsQCbrkpnaC84LwMMY6CoUDDWQEb1tH8p4/YsmWwHS0wVjY2T3cwidi+lSfbkEMOVqhzUR4imKwPirGweyMLoe9nyuQp2PAXz8mUJN6bTBLlPh3k70hW4vRkSJGGWKAWjw4q8Zs0zwMuzKUnJfGLMu5rUdyI0mZfnvhj4F66Aq+VLQn5cAAAAAElFTkSuQmCC)',
13
+ 'lasso.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAADf0lEQVR4Xl1Uf0xTZxS90MfWUqiLYzhRNhIhTdmyP5iiYVv/IM7pIIuQyBLnogmrxhmCJBqDYZtzJhijWxeMW2Lm5jAsmomQTYLGDCYMqAM26/hlRFCU+kClBR99r+17Z/f1h3R79yXfe989537nO7m5RGQkUzSQjU2olp0P6icbJhse1MtOVPNOdixvJH6MdIdmyGvGGu+BzuZDI5vlJaBoLMFm+dBIZ7P3AGfNM4xkionhyNDK+5rLxOWaDk2CXXXMO+btalKYuFwrE/uatXJkzDCaX2+6WtnqXsQpk1YmdbgkZ6hGcSiOUI3k7HBtlcxcZhFa3WqlN50JOwXFcbKfkIoNouuKUiHbnyy7KNRQDTUIvcuG7EqF68oGMZXLnexXHDsFQt618ysCBuybmKrzri6yrE0ooS4ajca9BFiweqpu34QBKwLXziOPULVHJKyX5475bU30N4ebfCz9DrVRIHZ729yx9TJhj4gq8n9lZY03Lo0VNgrtMQCv7VREF0iN7AgovHGJYNX8TvKcYVWaenDAMkhhk6LRTsmUT0MkRf4t6sFVnGa0p56Qr4W+QNoCWP9qJ4GK6T3qoyApJFvUz9+KEPxfWvnctm6UwhRP0Ql1NEZOGqThpHtvX79MyNYloeKTMUJWcLgR7/6fcJxFPiIpMfDmwxMFfr60B5X0j3X0xzf4p0AaOhf60F8kFSvvaK8grfNZgY4TDHhB3Xrzpw8eJ8Ia7P+ZbTWRWnK1JY/1FchV7vLxbXd3jXzzx+1T06W1L/a+hHWPa8/0rGJ/c/FDl7L9s0Qm+FLULb2tcQbBiKXyb33YLx359XrhkxQuVqQMnwvt8KWFe4mbL79xlJAzm6MYsNf3Z9eR+8+pRqRH/ESu9P3A7GEUwxhtPp1QP0n4+la3a91cJo4O/vJXRpCQgrXB3Xcbfp/4VtuILCSAS8dOWHmzKRNm7XklmS1+BilaIqq8Q2dnjwa2a68zU4j4t0BI1Xb0tJR6Xpu3BazBpaw5F94TsHMLM2z26fUWCHp3589/NH54uG7gu6aOl5nS04Y1ugyQ+LRl4gmEBLehW+DDzfKnu8YJJdOB92NSYj32X8KCr6+2XF6s5cxNV7O/4d1peki93LlhQmQIxIcvdfBUZiBLvl3rYyP1eERT5OE1PATix0xknNiSQx+Lp8XTIYfNGBswkVWH/wt8N0KJ2YuJdAAAAABJRU5ErkJggg==)',
14
+ 'lassoPlus.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAADo0lEQVR4Xm1UbUxTZxQ+0EJaCnXZHCpShxHSlJD96AY0uPYH+Dn8sZoME7ZFllqNM4SRuBiM29SZaYwfNSzOxKhzGMzM5CMzBI2ROYxQLVWrlI8MReuoF6S04KX33t57z85tqZhl99zk3Pc953nf5zz35ACABrRzhvn4KTZwrhdNY81jzS+aOBc20E5+Mq4BejTwFKYgrENLeO+t9gND1dwihDlbhNXcgaFb7eG9FNVNUSZBtJSOObKjr72KyZWV1DS0Sc5Z56xNSosDc+Uqpq9ddmDOFGXTG86W6jp9CyiklavYbjfrEvfwTt4p7mFd3e7NrI6OWYCdPqkunE2A7WreedoLmIXrGfd1vpazvVral9tqbjXfyPUsHbDxte7r65ksOu60l3duVwOa71xeIahwV2C8MVxaqV+VYge/1eF1eCetI/A8BfVYOt64K6DCFcKdy2gGrN/JAK7jZo5GTW1wn8wHEWsRU8RMWYVk9aaZo+s4wJ0M1kP0uJE4Prz6pLxF/SfgYrSQVSuAUDWnfC8mNmosf3gV0ChHXRC8QKxkaX+/3g+EtDi8pYHSQIaQISje4SWIco9e2l9MYcoONgGWyOIPuDAesNj9y0PLQ+liuqh4u5+1xIAHTi/tsyYA0WNGCbCrBzeilgAGrCCrUSh11XgqpIpWgx8G056vfnANMF+hhLXfPgHMiw224Mevf3G86N3Wn4jkJLCpwkcvT5ZFqegg1sEj48ivK2lRxg5cEr+IVrIb+LXyl/an9mEFgCp8V9o8fPGzUCoaY97fSVYtSPa/OszEr4yr9zlGa57tGPr50eOb+GNXsWcZrgkdvNBbTPoW4i+3+a3fpxIgkil97ulMSq54DS7hbvThbvbwlQflrzLpsEp+8JK4LbIw3kvUfCUtI4AF0wW8Cr+J3L19+J+3JA1mJ/TEQvZc//Qh3ICaueZTAE1jgCf+7nGvmTHgEf8f93JigJm4Kvb1s+abgVPyJ5iHKUhHJ2/4cLjNgDr5HT6DJE7HTDkV68MDv00fEbbKHxBSndBvHpAlb+vt2Bh8f9YkGGNLiHMhhk+ijVqY0qZflzcPULq7ZHbL6KHBxv4zbd3vEaS3i5qCaCAwSsv85wZlmeJT9ajpch333Y5RQPuEsClJ5f8B87oWdVx7Wy6YmWggfeO7E/ASPCAlakgMgTctkuU/axDyuMcHIySkYpMwDkHy8SHw5phJjBNThvgVc545LzpNmuSASXgl/V9J4ld/LrRv/QAAAABJRU5ErkJggg==)',
15
+ 'pointingHand.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADfElEQVR4Xq2WSUhbURSGbdI0mgoOlSg2deEiomidxTorVcEBJYJEQbAiKG4Uq4ILLSIOqAuRglsFxV1t6qYIETFI6coEsdVVHSG4EI1xjv4950IkyEsaqRceeXm593z3nPPf/8XHx81QqVTq/Px8REREICgo6GpgYOAj3cvdzX/085aWltni4mJsbGygp6cHCQkJsy9pPDqQuwVFRUWm1tZWXFxcYGFhAfHx8euBgYGaJwNQeUwdHR24vLyE0WhEQUEBwsLCYp8MkJWVZerq6roH5ObmoqKiIik9PV3xJBAKKDK4urrC0tIS4uLiUF1d/aWsrOyzWq1O/G/IQwCpCjqdjrOARqMxlpeX65OTk6vGxsbSAgICHq8uVwD3gIJhc3MT6+vriI2NBUuYYaWlpcdRUVFvQ0JC3tBngEwm8y65h4Ds7GxYrVbs7u4iJSUFc3Nz2NraAqmNgVuU1S+a810ulyslCUR+3d3d/buxsdFMkxPy8vJMnZ2dQqaLi4tixwzY398Hw8xmM87OzlBXVycUNjMzg8zMTDQ3Ny9LApRK5RQ1ELW1tYiOjrbk5OT8YQDLlJvM5WDA3t4eSkpKsLa2JgD19fUYGhoSG+nv70doaKg0gKgGXrSzswMKDmocGHB+fo6joyNYLBYRhIPy7k9OTmC32wVgfHxcbIQaDn9/f2mAr6+vYXBwEA6HA5OTk6BagmXKAfkZX3d3d+K6vb3Fzc0Nrq+vsb29jcPDQyFnBpCbSAOojlNarRYHBweieeHh4fcZcECpwTDn4AxGRkbg5+cnDaCyvIuJiYHBYIDNZkNDQwPa29tFWVwDSZLoIWcwOjoKOi9ue+BDjrnc1taG09NTkQXrnhd6CxgeHkZwcLB7QGJiYi032BmY687l8QbAJerr6+NT7h5AjX1BEv02MTEhgnITvQHwXFYbZ0+bdA9QKBSympqaT3q93nF8fHwf3JsMeH5VVRWampo8ZuBTWVn5njzFOj8/LwBOebprLj/nDbCFZGRkIC0tTefRjMgyZHTkl9jcWN9O7XsC8G8mkwn0rgAt//drlXxHS5K1TU9Pi9o6++CEuUL5ng9db28v28kPKrO02bmmFRkZ+Zz+SejZc1ZWVoR9sMm5XlwS/s6fq6urSE1NtSclJX14RsMrvybrUBUWFn6low9aA1rk8SIX/Uku++ph8L9NTKvfLVkZnAAAAABJRU5ErkJggg==)',
16
+ 'rect.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAACqklEQVR4XoWUTUhUURTHf4SQoGFBBSVhb1GL8LnqYxvlolrcVQQ3ESntQxAjC6OFNAWh5KJUCCktXAghgh+VlTpk9uFXswgizdBMVBwzM0NtnGZe572ZsVER5888Dufc33vnnHvupZJORx1MM0Q75VwnGy3KFqucV3yTSEd4VSViWASZpZcKWZRCnCiRnaJEx04Rb4VEZ2WdJattICmo5/Wo7tGNukH+LbpLe0RdYoU8PRKd15a2khzAb6o+B19Dqs9vCjDIsAAxgSzPWiprGjYHkULrBYjzWWl+w23ki24Y40amcdrwG9Z/pVkuy1VvDkEteQ4wo/Lw4HMUJJl46dFvsew8gwTVRPF0UZ5ZCwVoB/iqXjIpwUfcEW1jHXvJpzpSmIrzqT5tFkB6GPiuZpzgAaJ/exgJIWEgHXuTQjWo6CYlEyN7cIxc6pjBj0/ZNWjzHNzm1KrABX7yQJL8Qr8DZJjF8JbSVYFcSWaODzxkSu0fyekqNN3gxeMAo2qMZ/RIAnOSQoB3kpINBPnLKGNODe3mOBKYdIBGdYkSaaWLUp7wiW52CLAgL3xDDg3qVnNNzZQ5bI9GwAHuq1jpfTwb2cAWTvJCMv/Iawpl/GKpdGoI2qMhwxf+Qipb2U0R97jMJlK5SCb7SJCpPUOvk5IVDfQrN1Uc5AStNLNLNi5B9vuowI8ZkLauAGpVhmR/hc2SwiHWc5y7tPCZP1K0DIgzGku+0Jp2xDgcVqpx1eg2fEZw2fAtAeyi7AEfKLFcT6uzPNfaIp6ZQstVVZflKWtaAdhD01lhuW66sZK9NmB7vMX2qySrxaKT5PBFKaDH9HPdpid0IOxd0LP6h/4VOaLLT6ZfurRdOtQm2xaK+eRkeJkKXQKRayZ02djyyICc5bw83y/6IrFK/gE6XvK0B9+OJQAAAABJRU5ErkJggg==)',
17
+ 'rectPlus.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAACwElEQVR4XoWUXUhUQRTH/4mhsIZFFpSE3SKJ2OtTH69RPlTQPEUwikhpH4IUWRg9SFsQSj6UCiHlFj5cCBH8qCzUJbMPNb0PcQNN0Ux0cc3MNmxbt73Tmdld84Pw/tnlcM787j3nzJmBG91KXZjFKDpRjRsoACcVkFWNV/hCka7oKjfIELAxh37U0KIMOEip2E5KVXYGeWsoOkfrBK2WQJrNA3yC9/Jm3kS/Nt7DTVIPWRFPL0UDXHCRpoCQzgYUvorYQEgnYARjBMSH883VVNUypo+ACm0kwBEU2SHNoxWTbmqTWp52Wgtp4p+yhUu4GvVRoB5FCvCzIpgIKtlwIol69JMsmacNm02Vz5YV6fVACbgCPrOXmKbgY9wlbUEc9qIYRqww5giyAa6XADlR4Cvzq+ABqMcg0bMH4xEkCuRAblKkBra4SU4r3nLgGC6gAX6EEGSyBq6fA+7g1H+Bi/iOh5TkEAYVkKuXA29RuQTIMpyW00oMrAnEWRutLOMXPuARZtj+8cKeUt0D+GAqYIJ58Ry9OG7stHZbCQuAjT+YgFfV0KlPAu8wrYBmdhkV1EoXKvEUOyyHtY0qmKcXvkEhmtjt1rq6GX1MjkZYAQ9YIvU+CeuxDpuw1kq3hvARr1FK45cIt6rBlqNBwxf9QiY2Ix1luI8r2GCkGJeQh31Ipqk9g36VklgMDDIPanEQJ9GOVuyijUum/T5K8BMMU1tXAPUsl7K/ihRK4RAScAL30IZP+E1F04Co0VjyhfbsI9rhqDK1a9p7LajZy4ZvCSCLkgM+XCFcz4x883pHzOMvFa7ahnyzqmUFIIemu0a4bnkgnD4JSI+vXL6KslooOo0O3yKFuZe/4B18ioej3nk+x7/xH7EjuvxkhqhLW6lDHbRtkViQToYPM5FLIHbNRC4bKZMG5CzO03/fgi8Wc+MvzYn905eo3YcAAAAASUVORK5CYII=)',
18
+ 'zoom.png': 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAACVklEQVR4XpWU32uSURjHv2pqFngpLIyGUoiFMBZ4IUIgQSMEu4mi4f8QBLKLwMuuFL0JuxrVFCyMupCwaKUbW744KAZDF06HmkPe2tT54833PT3vfpT9ovk+vBfn8HzOeZ5zvt+DFbyieI03eIs0xSKWsIwsOHzEV2yjjj79BazjE4oog5IfUjxGDE+QVW+amu6Cn4tkopkoFyn4m+6SqaHeQRV5gorYPATmMI+qUfAmkp6yrT0+GJPGpPGBre0pJ5KCt2/sYQsblF4BFTNH8UwhWUvB6S4YmIppJK2oFTWSikZg091SULIKCp72+QysKd+bs2ek8+lZRwtMLzn5QDYV58JcOBUPZJ28XgJztNKzhFBHDYCZF6PPk6sPHC0V04mhXMVXs7gMLj2FoWap+EI5nagipBTsG1toEWC9t6zc2xosmhNv2o9j6JvU7E69eKcT5cIEb0PdB6razKNTNCHHrWbljv3EMABcU+9OhXJyYYlkybQGLGBjZmJHTj8nJV72rtiP/QoAk5qKz8mDecpN9wroorqXrxcV7GJnITa4dFf5e7o8rlkCWRWztQt+6mEJW2dj81f53H1xYuZv2TTnMqTiGroXLiKARLB6sn2Dv/309IXh1RNI/KRdei6sFcekTFQCaSY/AsAgS2yEkgjYHqlpAr6MdKwENEa4OGYiYBRpMLUMHFl8zCjrBx1lz8wfSd77CiWnVtHD+n8NxBQ/gDI6EMiC/7bofjEHQJG8Wqf3oY0P4NT5Px4BOhlqdQgo71m7TmV9o51kT3UwwIFBhhIPke9kQenbD+jECQAAAABJRU5ErkJggg==)',
19
+ };
20
+
21
+ module.exports = cursors;
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ const { toHex } = require('./utils');
4
+
5
+ class DrawContext {
6
+ /**
7
+ *
8
+ * @param {CanvasRenderingContext2D} ctx
9
+ */
10
+ constructor(ctx) {
11
+ this.ctx = ctx;
12
+
13
+ this.ctx.textAlign = 'left';
14
+ this.ctx.textBaseline = 'top';
15
+
16
+ this.currentFontSize = 12;
17
+ this.currentFont = '12px sans-serif';
18
+ this.ctx.font = this.currentFont;
19
+
20
+ this.currentColor = '#000000';
21
+ this.currentLineWidth = 1;
22
+ }
23
+
24
+ clearRect(x, y, w, h) {
25
+ this.ctx.clearRect(x, y, w, h);
26
+ }
27
+
28
+ getBackgroundRGB() {
29
+ return 0xffffff;
30
+ }
31
+
32
+ getForegroundRGB() {
33
+ return 0x000000;
34
+ }
35
+
36
+ getSelectionBackgroundRGB() {
37
+ // return 0xeb8282; // arbitrary color
38
+ // return 0x000080; // color from java default AquaLookAndFeel "TextArea.selectionBackground"
39
+ return 0xbbd6fc; // color from default macos highlight-accent color (blue)
40
+ }
41
+
42
+ getLineWidth() {
43
+ return this.currentLineWidth;
44
+ }
45
+
46
+ setRGB(rgb) {
47
+ const r = (rgb >>> 16) & 0xff;
48
+ const g = (rgb >>> 8) & 0xff;
49
+ const b = (rgb >>> 0) & 0xff;
50
+ this.currentColor = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
51
+ this.ctx.fillStyle = this.currentColor;
52
+ this.ctx.strokeStyle = this.currentColor;
53
+ }
54
+
55
+ setFont(size, isBold, isItalic) {
56
+ this.currentFontSize = size;
57
+ this.currentFont = `${isBold ? 'bold' : ''} ${
58
+ isItalic ? 'italic' : ''
59
+ } ${size}px sans-serif`;
60
+ this.ctx.font = this.currentFont;
61
+ }
62
+
63
+ getFontSize() {
64
+ return this.currentFontSize;
65
+ }
66
+
67
+ getBounds(s, rect) {
68
+ const metrics = this.ctx.measureText(s);
69
+ rect.set(
70
+ metrics.actualBoundingBoxLeft,
71
+ metrics.actualBoundingBoxAscent,
72
+ metrics.actualBoundingBoxRight,
73
+ metrics.actualBoundingBoxAscent,
74
+ );
75
+ }
76
+
77
+ drawString(x, y, s) {
78
+ this.ctx.fillText(s, x, y);
79
+ }
80
+
81
+ drawCenteredString(x, y, s) {
82
+ this.ctx.textAlign = 'center';
83
+ this.ctx.textBaseline = 'middle';
84
+ this.ctx.fillText(s, x, y);
85
+ this.ctx.textAlign = 'left';
86
+ this.ctx.textBaseline = 'top';
87
+ }
88
+
89
+ setLineWidth(lineWidth) {
90
+ this.currentLineWidth = lineWidth;
91
+ this.ctx.lineWidth = lineWidth;
92
+ }
93
+
94
+ fillRectangle(x, y, w, h) {
95
+ this.ctx.fillRect(x, y, w, h);
96
+ }
97
+
98
+ fillCircle(x, y, d) {
99
+ const r = d / 2;
100
+ this.ctx.beginPath();
101
+ this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
102
+ this.ctx.fill();
103
+ }
104
+
105
+ drawLine(x1, y1, x2, y2) {
106
+ this.ctx.beginPath();
107
+ this.ctx.moveTo(x1, y1);
108
+ this.ctx.lineTo(x2, y2);
109
+ this.ctx.stroke();
110
+ }
111
+
112
+ drawPolygon(p) {
113
+ this.ctx.beginPath();
114
+ this.ctx.moveTo(p.getX(0), p.getY(0));
115
+ for (let i = 1; i < p.getSize(); i++) {
116
+ this.ctx.lineTo(p.getX(i), p.getY(i));
117
+ }
118
+ this.ctx.stroke();
119
+ }
120
+
121
+ drawRectangle(x, y, w, h) {
122
+ this.ctx.strokeRect(x, y, w, h);
123
+ }
124
+
125
+ fillPolygon(p) {
126
+ this.ctx.beginPath();
127
+ this.ctx.moveTo(p.getX(0), p.getY(0));
128
+ for (let i = 1; i < p.getSize(); i++) {
129
+ this.ctx.lineTo(p.getX(i), p.getY(i));
130
+ }
131
+ this.ctx.fill();
132
+ }
133
+
134
+ drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
135
+ if (arguments.length !== 9) {
136
+ throw new Error(
137
+ `drawImage call with ${arguments.length} arguments unimplemented`,
138
+ );
139
+ }
140
+ const fullScaleCanvas = document.createElement('canvas');
141
+ /** @type {ImageData} */
142
+ const imageData = image.imageData;
143
+ fullScaleCanvas.width = imageData.width;
144
+ fullScaleCanvas.height = imageData.height;
145
+ const fullScaleContext = fullScaleCanvas.getContext('2d');
146
+ fullScaleContext.globalAlpha = 0;
147
+ fullScaleContext.putImageData(imageData, 0, 0);
148
+ this.ctx.drawImage(fullScaleCanvas, sx, sy, sw, sh, dx, dy, dw, dh);
149
+ }
150
+
151
+ isDarkBackground() {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ module.exports = DrawContext;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const ClipboardHandler = require('./clipboard_handler');
4
+ const DrawContext = require('./draw_context');
5
+
6
+ class EditorArea {
7
+ constructor(canvasElement, onChange) {
8
+ this.canvasElement = canvasElement;
9
+ this.changeListener = onChange;
10
+ }
11
+ getBackgroundRGB() {
12
+ return 0xffffff;
13
+ }
14
+ getCanvasWidth() {
15
+ return this.canvasElement.width;
16
+ }
17
+ getCanvasHeight() {
18
+ return this.canvasElement.height;
19
+ }
20
+ getDrawContext() {
21
+ return new DrawContext(this.canvasElement.getContext('2d'));
22
+ }
23
+ onChange(what, isUserEvent) {
24
+ this.changeListener?.({ what, isUserEvent });
25
+ }
26
+ getClipboardHandler() {
27
+ return new ClipboardHandler();
28
+ }
29
+ }
30
+
31
+ module.exports = EditorArea;