js-draw 0.0.1
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/.eslintrc.js +57 -0
- package/.husky/pre-commit +4 -0
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/__mocks__/coloris.ts +8 -0
- package/__mocks__/styleMock.js +1 -0
- package/dist/__mocks__/coloris.d.ts +2 -0
- package/dist/__mocks__/coloris.js +5 -0
- package/dist/build_tools/BundledFile.d.ts +12 -0
- package/dist/build_tools/BundledFile.js +153 -0
- package/dist/scripts/bundle.d.ts +1 -0
- package/dist/scripts/bundle.js +19 -0
- package/dist/scripts/watchBundle.d.ts +1 -0
- package/dist/scripts/watchBundle.js +9 -0
- package/dist/src/Color4.d.ts +23 -0
- package/dist/src/Color4.js +102 -0
- package/dist/src/Display.d.ts +22 -0
- package/dist/src/Display.js +93 -0
- package/dist/src/Editor.d.ts +55 -0
- package/dist/src/Editor.js +366 -0
- package/dist/src/EditorImage.d.ts +44 -0
- package/dist/src/EditorImage.js +243 -0
- package/dist/src/EventDispatcher.d.ts +11 -0
- package/dist/src/EventDispatcher.js +39 -0
- package/dist/src/Pointer.d.ts +22 -0
- package/dist/src/Pointer.js +57 -0
- package/dist/src/SVGLoader.d.ts +21 -0
- package/dist/src/SVGLoader.js +204 -0
- package/dist/src/StrokeBuilder.d.ts +35 -0
- package/dist/src/StrokeBuilder.js +275 -0
- package/dist/src/UndoRedoHistory.d.ts +17 -0
- package/dist/src/UndoRedoHistory.js +46 -0
- package/dist/src/Viewport.d.ts +39 -0
- package/dist/src/Viewport.js +134 -0
- package/dist/src/commands/Command.d.ts +15 -0
- package/dist/src/commands/Command.js +29 -0
- package/dist/src/commands/Erase.d.ts +11 -0
- package/dist/src/commands/Erase.js +37 -0
- package/dist/src/commands/localization.d.ts +19 -0
- package/dist/src/commands/localization.js +17 -0
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +46 -0
- package/dist/src/components/Stroke.d.ts +16 -0
- package/dist/src/components/Stroke.js +79 -0
- package/dist/src/components/UnknownSVGObject.d.ts +15 -0
- package/dist/src/components/UnknownSVGObject.js +25 -0
- package/dist/src/components/localization.d.ts +5 -0
- package/dist/src/components/localization.js +4 -0
- package/dist/src/geometry/LineSegment2.d.ts +19 -0
- package/dist/src/geometry/LineSegment2.js +100 -0
- package/dist/src/geometry/Mat33.d.ts +31 -0
- package/dist/src/geometry/Mat33.js +187 -0
- package/dist/src/geometry/Path.d.ts +55 -0
- package/dist/src/geometry/Path.js +364 -0
- package/dist/src/geometry/Rect2.d.ts +47 -0
- package/dist/src/geometry/Rect2.js +148 -0
- package/dist/src/geometry/Vec2.d.ts +13 -0
- package/dist/src/geometry/Vec2.js +13 -0
- package/dist/src/geometry/Vec3.d.ts +32 -0
- package/dist/src/geometry/Vec3.js +98 -0
- package/dist/src/localization.d.ts +12 -0
- package/dist/src/localization.js +5 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +4 -0
- package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
- package/dist/src/rendering/AbstractRenderer.js +108 -0
- package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
- package/dist/src/rendering/CanvasRenderer.js +108 -0
- package/dist/src/rendering/DummyRenderer.d.ts +25 -0
- package/dist/src/rendering/DummyRenderer.js +65 -0
- package/dist/src/rendering/SVGRenderer.d.ts +27 -0
- package/dist/src/rendering/SVGRenderer.js +122 -0
- package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
- package/dist/src/testing/loadExpectExtensions.js +27 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
- package/dist/src/toolbar/HTMLToolbar.js +444 -0
- package/dist/src/toolbar/types.d.ts +17 -0
- package/dist/src/toolbar/types.js +5 -0
- package/dist/src/tools/BaseTool.d.ts +20 -0
- package/dist/src/tools/BaseTool.js +44 -0
- package/dist/src/tools/Eraser.d.ts +16 -0
- package/dist/src/tools/Eraser.js +53 -0
- package/dist/src/tools/PanZoom.d.ts +40 -0
- package/dist/src/tools/PanZoom.js +191 -0
- package/dist/src/tools/Pen.d.ts +25 -0
- package/dist/src/tools/Pen.js +97 -0
- package/dist/src/tools/SelectionTool.d.ts +49 -0
- package/dist/src/tools/SelectionTool.js +437 -0
- package/dist/src/tools/ToolController.d.ts +18 -0
- package/dist/src/tools/ToolController.js +110 -0
- package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
- package/dist/src/tools/ToolEnabledGroup.js +11 -0
- package/dist/src/tools/localization.d.ts +10 -0
- package/dist/src/tools/localization.js +9 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.js +20 -0
- package/jest.config.js +22 -0
- package/lint-staged.config.js +6 -0
- package/package.json +82 -0
- package/src/Color4.test.ts +12 -0
- package/src/Color4.ts +122 -0
- package/src/Display.ts +118 -0
- package/src/Editor.css +58 -0
- package/src/Editor.ts +469 -0
- package/src/EditorImage.test.ts +90 -0
- package/src/EditorImage.ts +297 -0
- package/src/EventDispatcher.test.ts +123 -0
- package/src/EventDispatcher.ts +53 -0
- package/src/Pointer.ts +93 -0
- package/src/SVGLoader.ts +230 -0
- package/src/StrokeBuilder.ts +362 -0
- package/src/UndoRedoHistory.ts +61 -0
- package/src/Viewport.ts +168 -0
- package/src/commands/Command.ts +43 -0
- package/src/commands/Erase.ts +52 -0
- package/src/commands/localization.ts +38 -0
- package/src/components/AbstractComponent.ts +73 -0
- package/src/components/Stroke.test.ts +18 -0
- package/src/components/Stroke.ts +102 -0
- package/src/components/UnknownSVGObject.ts +36 -0
- package/src/components/localization.ts +9 -0
- package/src/editorStyles.js +3 -0
- package/src/geometry/LineSegment2.test.ts +77 -0
- package/src/geometry/LineSegment2.ts +127 -0
- package/src/geometry/Mat33.test.ts +144 -0
- package/src/geometry/Mat33.ts +268 -0
- package/src/geometry/Path.fromString.test.ts +146 -0
- package/src/geometry/Path.test.ts +96 -0
- package/src/geometry/Path.toString.test.ts +31 -0
- package/src/geometry/Path.ts +456 -0
- package/src/geometry/Rect2.test.ts +121 -0
- package/src/geometry/Rect2.ts +215 -0
- package/src/geometry/Vec2.test.ts +32 -0
- package/src/geometry/Vec2.ts +18 -0
- package/src/geometry/Vec3.test.ts +29 -0
- package/src/geometry/Vec3.ts +133 -0
- package/src/localization.ts +27 -0
- package/src/rendering/AbstractRenderer.ts +164 -0
- package/src/rendering/CanvasRenderer.ts +141 -0
- package/src/rendering/DummyRenderer.ts +80 -0
- package/src/rendering/SVGRenderer.ts +159 -0
- package/src/testing/loadExpectExtensions.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +551 -0
- package/src/toolbar/toolbar.css +110 -0
- package/src/toolbar/types.ts +20 -0
- package/src/tools/BaseTool.ts +58 -0
- package/src/tools/Eraser.ts +67 -0
- package/src/tools/PanZoom.ts +253 -0
- package/src/tools/Pen.ts +121 -0
- package/src/tools/SelectionTool.test.ts +85 -0
- package/src/tools/SelectionTool.ts +545 -0
- package/src/tools/ToolController.ts +126 -0
- package/src/tools/ToolEnabledGroup.ts +14 -0
- package/src/tools/localization.ts +22 -0
- package/src/types.ts +133 -0
- package/tsconfig.json +28 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
import EventDispatcher from './EventDispatcher';
|
2
|
+
import Mat33 from './geometry/Mat33';
|
3
|
+
import { Point2, Vec2 } from './geometry/Vec2';
|
4
|
+
import Vec3 from './geometry/Vec3';
|
5
|
+
import BaseTool from './tools/BaseTool';
|
6
|
+
import AbstractComponent from './components/AbstractComponent';
|
7
|
+
import Rect2 from './geometry/Rect2';
|
8
|
+
import Pointer from './Pointer';
|
9
|
+
export interface PointerEvtListener {
|
10
|
+
onPointerDown(event: PointerEvt): boolean;
|
11
|
+
onPointerMove(event: PointerEvt): void;
|
12
|
+
onPointerUp(event: PointerEvt): void;
|
13
|
+
onGestureCancel(): void;
|
14
|
+
}
|
15
|
+
export declare enum InputEvtType {
|
16
|
+
PointerDownEvt = 0,
|
17
|
+
PointerMoveEvt = 1,
|
18
|
+
PointerUpEvt = 2,
|
19
|
+
GestureCancelEvt = 3,
|
20
|
+
WheelEvt = 4,
|
21
|
+
KeyPressEvent = 5
|
22
|
+
}
|
23
|
+
export interface WheelEvt {
|
24
|
+
readonly kind: InputEvtType.WheelEvt;
|
25
|
+
readonly delta: Vec3;
|
26
|
+
readonly screenPos: Point2;
|
27
|
+
}
|
28
|
+
export interface KeyPressEvent {
|
29
|
+
readonly kind: InputEvtType.KeyPressEvent;
|
30
|
+
readonly key: string;
|
31
|
+
}
|
32
|
+
export interface GestureCancelEvt {
|
33
|
+
readonly kind: InputEvtType.GestureCancelEvt;
|
34
|
+
}
|
35
|
+
interface PointerEvtBase {
|
36
|
+
readonly current: Pointer;
|
37
|
+
readonly allPointers: Pointer[];
|
38
|
+
}
|
39
|
+
export interface PointerDownEvt extends PointerEvtBase {
|
40
|
+
readonly kind: InputEvtType.PointerDownEvt;
|
41
|
+
}
|
42
|
+
export interface PointerMoveEvt extends PointerEvtBase {
|
43
|
+
readonly kind: InputEvtType.PointerMoveEvt;
|
44
|
+
}
|
45
|
+
export interface PointerUpEvt extends PointerEvtBase {
|
46
|
+
readonly kind: InputEvtType.PointerUpEvt;
|
47
|
+
}
|
48
|
+
export declare type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
|
49
|
+
export declare type InputEvt = KeyPressEvent | WheelEvt | GestureCancelEvt | PointerEvt;
|
50
|
+
export declare type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
|
51
|
+
export declare enum EditorEventType {
|
52
|
+
ToolEnabled = 0,
|
53
|
+
ToolDisabled = 1,
|
54
|
+
ToolUpdated = 2,
|
55
|
+
UndoRedoStackUpdated = 3,
|
56
|
+
ObjectAdded = 4,
|
57
|
+
ViewportChanged = 5,
|
58
|
+
DisplayResized = 6
|
59
|
+
}
|
60
|
+
declare type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
|
61
|
+
export interface EditorToolEvent {
|
62
|
+
readonly kind: EditorToolEventType;
|
63
|
+
readonly tool: BaseTool;
|
64
|
+
}
|
65
|
+
export interface EditorObjectEvent {
|
66
|
+
readonly kind: EditorEventType.ObjectAdded;
|
67
|
+
readonly object: AbstractComponent;
|
68
|
+
}
|
69
|
+
export interface EditorViewportChangedEvent {
|
70
|
+
readonly kind: EditorEventType.ViewportChanged;
|
71
|
+
readonly newTransform: Mat33;
|
72
|
+
}
|
73
|
+
export interface DisplayResizedEvent {
|
74
|
+
readonly kind: EditorEventType.DisplayResized;
|
75
|
+
readonly newSize: Vec2;
|
76
|
+
}
|
77
|
+
export interface EditorUndoStackUpdated {
|
78
|
+
readonly kind: EditorEventType.UndoRedoStackUpdated;
|
79
|
+
readonly undoStackSize: number;
|
80
|
+
readonly redoStackSize: number;
|
81
|
+
}
|
82
|
+
export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated;
|
83
|
+
export declare type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
|
84
|
+
export declare type ComponentAddedListener = (component: AbstractComponent) => void;
|
85
|
+
export interface ImageLoader {
|
86
|
+
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener): Promise<Rect2>;
|
87
|
+
}
|
88
|
+
export {};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// Types related to the image editor
|
2
|
+
export var InputEvtType;
|
3
|
+
(function (InputEvtType) {
|
4
|
+
InputEvtType[InputEvtType["PointerDownEvt"] = 0] = "PointerDownEvt";
|
5
|
+
InputEvtType[InputEvtType["PointerMoveEvt"] = 1] = "PointerMoveEvt";
|
6
|
+
InputEvtType[InputEvtType["PointerUpEvt"] = 2] = "PointerUpEvt";
|
7
|
+
InputEvtType[InputEvtType["GestureCancelEvt"] = 3] = "GestureCancelEvt";
|
8
|
+
InputEvtType[InputEvtType["WheelEvt"] = 4] = "WheelEvt";
|
9
|
+
InputEvtType[InputEvtType["KeyPressEvent"] = 5] = "KeyPressEvent";
|
10
|
+
})(InputEvtType || (InputEvtType = {}));
|
11
|
+
export var EditorEventType;
|
12
|
+
(function (EditorEventType) {
|
13
|
+
EditorEventType[EditorEventType["ToolEnabled"] = 0] = "ToolEnabled";
|
14
|
+
EditorEventType[EditorEventType["ToolDisabled"] = 1] = "ToolDisabled";
|
15
|
+
EditorEventType[EditorEventType["ToolUpdated"] = 2] = "ToolUpdated";
|
16
|
+
EditorEventType[EditorEventType["UndoRedoStackUpdated"] = 3] = "UndoRedoStackUpdated";
|
17
|
+
EditorEventType[EditorEventType["ObjectAdded"] = 4] = "ObjectAdded";
|
18
|
+
EditorEventType[EditorEventType["ViewportChanged"] = 5] = "ViewportChanged";
|
19
|
+
EditorEventType[EditorEventType["DisplayResized"] = 6] = "DisplayResized";
|
20
|
+
})(EditorEventType || (EditorEventType = {}));
|
package/jest.config.js
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
// Test configuration
|
2
|
+
// See https://jestjs.io/docs/configuration#testenvironment-string
|
3
|
+
|
4
|
+
const config = {
|
5
|
+
preset: 'ts-jest',
|
6
|
+
|
7
|
+
// File extensions for imports, in order of precedence:
|
8
|
+
moduleFileExtensions: [
|
9
|
+
'ts',
|
10
|
+
'js',
|
11
|
+
],
|
12
|
+
|
13
|
+
// Mocks.
|
14
|
+
// See https://jestjs.io/docs/webpack#handling-static-assets
|
15
|
+
moduleNameMapper: {
|
16
|
+
// Webpack allows importing CSS files. Mock it.
|
17
|
+
'\\.(css|lessc)': '<rootDir>/__mocks__/styleMock.js',
|
18
|
+
'@melloware/coloris': '<rootDir>/__mocks__/coloris.ts',
|
19
|
+
},
|
20
|
+
};
|
21
|
+
|
22
|
+
module.exports = config;
|
package/package.json
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
{
|
2
|
+
"name": "js-draw",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
|
+
"main": "dist/src/Editor.js",
|
6
|
+
"types": "dist/src/Editor.d.ts",
|
7
|
+
"exports": {
|
8
|
+
".": {
|
9
|
+
"types": "./dist/src/Editor.d.ts",
|
10
|
+
"default": "./dist/src/Editor.js"
|
11
|
+
},
|
12
|
+
"./styles": {
|
13
|
+
"default": "./src/editorStyles.js"
|
14
|
+
},
|
15
|
+
"./Editor": {
|
16
|
+
"types": "./dist/src/Editor.d.ts",
|
17
|
+
"default": "./dist/src/Editor.js"
|
18
|
+
},
|
19
|
+
"./localization": {
|
20
|
+
"types": "./dist/src/localization.d.ts",
|
21
|
+
"default": "./dist/src/localization.js"
|
22
|
+
},
|
23
|
+
"./toolbar/HTMLToolbar": {
|
24
|
+
"types": "./dist/src/toolbar/HTMLToolbar.d.ts",
|
25
|
+
"default": "./dist/src/toolbar/HTMLToolbar.js"
|
26
|
+
},
|
27
|
+
"./Editor.css": {
|
28
|
+
"default": "./src/Editor.css"
|
29
|
+
},
|
30
|
+
"./toolbar/toolbar.css": {
|
31
|
+
"default": "./src/toolbar/toolbar.css"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"repository": {
|
35
|
+
"type": "git",
|
36
|
+
"url": "git+https://github.com/personalizedrefrigerator/js-draw.git"
|
37
|
+
},
|
38
|
+
"author": "Henry Heino",
|
39
|
+
"license": "MIT",
|
40
|
+
"private": false,
|
41
|
+
"scripts": {
|
42
|
+
"test": "jest",
|
43
|
+
"build": "yarn tsc",
|
44
|
+
"lint": "eslint .",
|
45
|
+
"linter-precommit": "eslint --fix --ext .js --ext .ts",
|
46
|
+
"lint-staged": "lint-staged",
|
47
|
+
"_postinstall": "husky install",
|
48
|
+
"prepack": "pinst --disable",
|
49
|
+
"postpack": "pinst --enable"
|
50
|
+
},
|
51
|
+
"dependencies": {
|
52
|
+
"@melloware/coloris": "^0.16.0",
|
53
|
+
"bezier-js": "^6.1.0"
|
54
|
+
},
|
55
|
+
"devDependencies": {
|
56
|
+
"@types/bezier-js": "^4.1.0",
|
57
|
+
"@types/jest": "^28.1.7",
|
58
|
+
"@types/jsdom": "^20.0.0",
|
59
|
+
"@types/node": "^18.7.9",
|
60
|
+
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
61
|
+
"@typescript-eslint/parser": "^5.33.1",
|
62
|
+
"eslint": "^8.22.0",
|
63
|
+
"husky": "^8.0.1",
|
64
|
+
"jest": "^28.1.3",
|
65
|
+
"jest-environment-jsdom": "^28.1.3",
|
66
|
+
"jsdom": "^20.0.0",
|
67
|
+
"lint-staged": "^13.0.3",
|
68
|
+
"pinst": "^3.0.0",
|
69
|
+
"ts-jest": "^28.0.8",
|
70
|
+
"typescript": "^4.7.4"
|
71
|
+
},
|
72
|
+
"bugs": {
|
73
|
+
"url": "https://github.com/personalizedrefrigerator/js-draw/issues"
|
74
|
+
},
|
75
|
+
"homepage": "https://github.com/personalizedrefrigerator/js-draw#readme",
|
76
|
+
"directories": {
|
77
|
+
"doc": "doc"
|
78
|
+
},
|
79
|
+
"keywords": [
|
80
|
+
"ink"
|
81
|
+
]
|
82
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import Color4 from './Color4';
|
2
|
+
|
3
|
+
describe('Color4', () => {
|
4
|
+
it('should convert to #RRGGBB-format hex strings (when no alpha)', () => {
|
5
|
+
expect(Color4.black.toHexString()).toBe('#000000');
|
6
|
+
expect(Color4.fromHex('#f0f').toHexString()).toBe('#f000f0');
|
7
|
+
});
|
8
|
+
|
9
|
+
it('should create #RRGGBBAA-format hex strings when there is an alpha component', () => {
|
10
|
+
expect(Color4.ofRGBA(1, 1, 1, 0.5).toHexString()).toBe('#ffffff80');
|
11
|
+
});
|
12
|
+
});
|
package/src/Color4.ts
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
export default class Color4 {
|
3
|
+
private constructor(
|
4
|
+
public readonly r: number,
|
5
|
+
public readonly g: number,
|
6
|
+
public readonly b: number,
|
7
|
+
public readonly a: number
|
8
|
+
) { }
|
9
|
+
|
10
|
+
// Each component should be in the range [0, 1]
|
11
|
+
public static ofRGB(red: number, green: number, blue: number): Color4 {
|
12
|
+
return new Color4(red, green, blue, 1.0);
|
13
|
+
}
|
14
|
+
|
15
|
+
public static ofRGBA(red: number, green: number, blue: number, alpha: number): Color4 {
|
16
|
+
return new Color4(red, green, blue, alpha);
|
17
|
+
}
|
18
|
+
|
19
|
+
public static fromHex(hexString: string): Color4 {
|
20
|
+
// Remove starting '#' (if present)
|
21
|
+
hexString = (hexString.match(/^[#]?(.*)$/) ?? [])[1];
|
22
|
+
hexString = hexString.toUpperCase();
|
23
|
+
|
24
|
+
if (!hexString.match(/^[0-9A-F]+$/)) {
|
25
|
+
throw new Error(`${hexString} is not in a valid format.`);
|
26
|
+
}
|
27
|
+
|
28
|
+
// RGBA or RGB
|
29
|
+
if (hexString.length === 3 || hexString.length === 4) {
|
30
|
+
// Each character is a component
|
31
|
+
const components = hexString.split('');
|
32
|
+
|
33
|
+
// Convert to RRGGBBAA or RRGGBB format
|
34
|
+
hexString = components.map(component => `${component}0`).join('');
|
35
|
+
}
|
36
|
+
|
37
|
+
if (hexString.length === 6) {
|
38
|
+
// Alpha component
|
39
|
+
hexString += 'FF';
|
40
|
+
}
|
41
|
+
|
42
|
+
const components: number[] = [];
|
43
|
+
for (let i = 2; i <= hexString.length; i += 2) {
|
44
|
+
const chunk = hexString.substring(i - 2, i);
|
45
|
+
components.push(parseInt(chunk, 16) / 255);
|
46
|
+
}
|
47
|
+
|
48
|
+
if (components.length !== 4) {
|
49
|
+
throw new Error(`Unable to parse ${hexString}: Wrong number of components.`);
|
50
|
+
}
|
51
|
+
|
52
|
+
return new Color4(components[0], components[1], components[2], components[3]);
|
53
|
+
}
|
54
|
+
|
55
|
+
// Like fromHex, but can handle additional colors if an HTML5Canvas is available.
|
56
|
+
public static fromString(text: string): Color4 {
|
57
|
+
if (text.startsWith('#')) {
|
58
|
+
return Color4.fromHex(text);
|
59
|
+
} else {
|
60
|
+
// Otherwise, try to use an HTML5Canvas to determine the color
|
61
|
+
const canvas = document.createElement('canvas');
|
62
|
+
canvas.width = 1;
|
63
|
+
canvas.height = 1;
|
64
|
+
|
65
|
+
const ctx = canvas.getContext('2d')!;
|
66
|
+
ctx.fillStyle = text;
|
67
|
+
ctx.fillRect(0, 0, 1, 1);
|
68
|
+
|
69
|
+
const data = ctx.getImageData(0, 0, 1, 1);
|
70
|
+
const red = data.data[0] / 255;
|
71
|
+
const green = data.data[1] / 255;
|
72
|
+
const blue = data.data[2] / 255;
|
73
|
+
const alpha = data.data[3] / 255;
|
74
|
+
|
75
|
+
return Color4.ofRGBA(red, green, blue, alpha);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
public eq(other: Color4|null|undefined): boolean {
|
80
|
+
if (other == null) {
|
81
|
+
return false;
|
82
|
+
}
|
83
|
+
|
84
|
+
return this.toHexString() === other.toHexString();
|
85
|
+
}
|
86
|
+
|
87
|
+
private hexString: string|null = null;
|
88
|
+
public toHexString(): string {
|
89
|
+
if (this.hexString) {
|
90
|
+
return this.hexString;
|
91
|
+
}
|
92
|
+
|
93
|
+
const componentToHex = (component: number): string => {
|
94
|
+
const res = Math.round(255 * component).toString(16);
|
95
|
+
|
96
|
+
if (res.length === 1) {
|
97
|
+
return `0${res}`;
|
98
|
+
}
|
99
|
+
return res;
|
100
|
+
};
|
101
|
+
|
102
|
+
const alpha = componentToHex(this.a);
|
103
|
+
const red = componentToHex(this.r);
|
104
|
+
const green = componentToHex(this.g);
|
105
|
+
const blue = componentToHex(this.b);
|
106
|
+
if (alpha === 'ff') {
|
107
|
+
return `#${red}${green}${blue}`;
|
108
|
+
}
|
109
|
+
this.hexString = `#${red}${green}${blue}${alpha}`;
|
110
|
+
return this.hexString;
|
111
|
+
}
|
112
|
+
|
113
|
+
public static transparent = Color4.ofRGBA(0, 0, 0, 0);
|
114
|
+
public static red = Color4.ofRGB(1.0, 0.0, 0.0);
|
115
|
+
public static green = Color4.ofRGB(0.0, 1.0, 0.0);
|
116
|
+
public static blue = Color4.ofRGB(0.0, 0.0, 1.0);
|
117
|
+
public static purple = Color4.ofRGB(0.5, 0.2, 0.5);
|
118
|
+
public static yellow = Color4.ofRGB(1, 1, 0.1);
|
119
|
+
public static clay = Color4.ofRGB(0.8, 0.4, 0.2);
|
120
|
+
public static black = Color4.ofRGB(0, 0, 0);
|
121
|
+
public static white = Color4.ofRGB(1, 1, 1);
|
122
|
+
}
|
package/src/Display.ts
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
import AbstractRenderer from './rendering/AbstractRenderer';
|
2
|
+
import CanvasRenderer from './rendering/CanvasRenderer';
|
3
|
+
import { Editor } from './Editor';
|
4
|
+
import { EditorEventType } from './types';
|
5
|
+
import DummyRenderer from './rendering/DummyRenderer';
|
6
|
+
import { Vec2 } from './geometry/Vec2';
|
7
|
+
|
8
|
+
export enum RenderingMode {
|
9
|
+
DummyRenderer,
|
10
|
+
CanvasRenderer,
|
11
|
+
// SVGRenderer is not supported by the main display
|
12
|
+
}
|
13
|
+
|
14
|
+
export default class Display {
|
15
|
+
private dryInkRenderer: AbstractRenderer;
|
16
|
+
private wetInkRenderer: AbstractRenderer;
|
17
|
+
private resizeSurfacesCallback?: ()=> void;
|
18
|
+
private flattenCallback?: ()=> void;
|
19
|
+
|
20
|
+
public constructor(
|
21
|
+
private editor: Editor, mode: RenderingMode, private parent: HTMLElement|null
|
22
|
+
) {
|
23
|
+
if (mode === RenderingMode.CanvasRenderer) {
|
24
|
+
this.initializeCanvasRendering();
|
25
|
+
} else {
|
26
|
+
this.dryInkRenderer = new DummyRenderer(editor.viewport);
|
27
|
+
this.wetInkRenderer = new DummyRenderer(editor.viewport);
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
32
|
+
if (event.kind !== EditorEventType.DisplayResized) {
|
33
|
+
throw new Error('Mismatched event.kinds!');
|
34
|
+
}
|
35
|
+
|
36
|
+
this.resizeSurfacesCallback?.();
|
37
|
+
});
|
38
|
+
}
|
39
|
+
|
40
|
+
// Returns the visible width of the display (e.g. how much
|
41
|
+
// space the display's element takes up in the x direction
|
42
|
+
// in the DOM).
|
43
|
+
public get width(): number {
|
44
|
+
return this.dryInkRenderer.displaySize().x;
|
45
|
+
}
|
46
|
+
|
47
|
+
public get height(): number {
|
48
|
+
return this.dryInkRenderer.displaySize().y;
|
49
|
+
}
|
50
|
+
|
51
|
+
private initializeCanvasRendering() {
|
52
|
+
const dryInkCanvas = document.createElement('canvas');
|
53
|
+
const wetInkCanvas = document.createElement('canvas');
|
54
|
+
const dryInkCtx = dryInkCanvas.getContext('2d')!;
|
55
|
+
const wetInkCtx = wetInkCanvas.getContext('2d')!;
|
56
|
+
|
57
|
+
this.dryInkRenderer = new CanvasRenderer(dryInkCtx, this.editor.viewport);
|
58
|
+
this.wetInkRenderer = new CanvasRenderer(wetInkCtx, this.editor.viewport);
|
59
|
+
|
60
|
+
dryInkCanvas.className = 'dryInkCanvas';
|
61
|
+
wetInkCanvas.className = 'wetInkCanvas';
|
62
|
+
|
63
|
+
if (this.parent) {
|
64
|
+
this.parent.appendChild(dryInkCanvas);
|
65
|
+
this.parent.appendChild(wetInkCanvas);
|
66
|
+
}
|
67
|
+
|
68
|
+
this.resizeSurfacesCallback = () => {
|
69
|
+
const hasSizeMismatch = (canvas: HTMLCanvasElement): boolean => {
|
70
|
+
return canvas.clientHeight !== canvas.height || canvas.clientWidth !== canvas.width;
|
71
|
+
};
|
72
|
+
|
73
|
+
// Ensure that the drawing surfaces sizes match the
|
74
|
+
// canvas' sizes to prevent stretching.
|
75
|
+
if (hasSizeMismatch(dryInkCanvas) || hasSizeMismatch(wetInkCanvas)) {
|
76
|
+
dryInkCanvas.width = dryInkCanvas.clientWidth;
|
77
|
+
dryInkCanvas.height = dryInkCanvas.clientHeight;
|
78
|
+
wetInkCanvas.width = wetInkCanvas.clientWidth;
|
79
|
+
wetInkCanvas.height = wetInkCanvas.clientHeight;
|
80
|
+
|
81
|
+
this.editor.notifier.dispatch(EditorEventType.DisplayResized, {
|
82
|
+
kind: EditorEventType.DisplayResized,
|
83
|
+
newSize: Vec2.of(
|
84
|
+
this.width,
|
85
|
+
this.height,
|
86
|
+
),
|
87
|
+
});
|
88
|
+
}
|
89
|
+
};
|
90
|
+
this.resizeSurfacesCallback();
|
91
|
+
|
92
|
+
this.flattenCallback = () => {
|
93
|
+
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
94
|
+
};
|
95
|
+
}
|
96
|
+
|
97
|
+
// Clears the drawing surfaces and otherwise prepares for a rerender.
|
98
|
+
public startRerender(): AbstractRenderer {
|
99
|
+
this.resizeSurfacesCallback?.();
|
100
|
+
this.wetInkRenderer.clear();
|
101
|
+
this.dryInkRenderer.clear();
|
102
|
+
|
103
|
+
return this.dryInkRenderer;
|
104
|
+
}
|
105
|
+
|
106
|
+
public getDryInkRenderer(): AbstractRenderer {
|
107
|
+
return this.dryInkRenderer;
|
108
|
+
}
|
109
|
+
|
110
|
+
public getWetInkRenderer(): AbstractRenderer {
|
111
|
+
return this.wetInkRenderer;
|
112
|
+
}
|
113
|
+
|
114
|
+
// Re-renders the contents of the wetInkRenderer onto the dryInkRenderer
|
115
|
+
public flatten() {
|
116
|
+
this.flattenCallback?.();
|
117
|
+
}
|
118
|
+
}
|
package/src/Editor.css
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
@import url('./toolbar/toolbar.css');
|
3
|
+
|
4
|
+
.imageEditorContainer {
|
5
|
+
/* Deafult colors for the editor */
|
6
|
+
--primary-background-color: white;
|
7
|
+
--primary-background-color-transparent: rgba(255, 255, 255, 0.5);
|
8
|
+
--secondary-background-color: #faf;
|
9
|
+
--primary-foreground-color: black;
|
10
|
+
--secondary-foreground-color: black;
|
11
|
+
}
|
12
|
+
|
13
|
+
.imageEditorContainer {
|
14
|
+
color: var(--primary-foreground-color);
|
15
|
+
font-family: system-ui, -apple-system, sans-serif;
|
16
|
+
background-color: var(--primary-background-color);
|
17
|
+
|
18
|
+
display: flex;
|
19
|
+
flex-direction: column-reverse;
|
20
|
+
}
|
21
|
+
|
22
|
+
.imageEditorContainer .imageEditorRenderArea {
|
23
|
+
display: grid;
|
24
|
+
grid-template-columns: 1fr;
|
25
|
+
flex-grow: 2;
|
26
|
+
flex-shrink: 1;
|
27
|
+
min-height: 100px;
|
28
|
+
}
|
29
|
+
|
30
|
+
.imageEditorContainer .imageEditorRenderArea canvas {
|
31
|
+
/* Stack all canvases on top of each other */
|
32
|
+
grid-row: 1 / 1;
|
33
|
+
grid-column: 1 / 1;
|
34
|
+
touch-action: none;
|
35
|
+
|
36
|
+
/* Fill the container */
|
37
|
+
box-sizing: border-box;
|
38
|
+
width: 100%;
|
39
|
+
height: 100%;
|
40
|
+
}
|
41
|
+
|
42
|
+
.imageEditorContainer .loadingMessage {
|
43
|
+
position: fixed;
|
44
|
+
text-align: center;
|
45
|
+
font-size: 2em;
|
46
|
+
|
47
|
+
bottom: 0;
|
48
|
+
left: 0;
|
49
|
+
right: 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
.imageEditorContainer .accessibilityAnnouncement {
|
53
|
+
opacity: 0;
|
54
|
+
width: 0;
|
55
|
+
height: 0;
|
56
|
+
overflow: hidden;
|
57
|
+
pointer-events: none;
|
58
|
+
}
|