Socket
Socket
Sign inDemoInstall

js-draw

Package Overview
Dependencies
Maintainers
1
Versions
117
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

js-draw - npm Package Compare versions

Comparing version 0.1.11 to 0.1.12

dist/src/math/LineSegment2.d.ts

7

CHANGELOG.md

@@ -0,1 +1,8 @@

# 0.1.12
* Add icons to the selection menu.
* Screen-reader-related bug fixes.
* Fix bug where parent cache nodes were not fully re-rendered after erasing a stroke and replacing it with more, larger strokes.
* Generate strokes with single paths, instead of one path for each segment.
* This should make new strokes take less space when saving to SVG because we don't need to store the edges for each part of the stroke.
# 0.1.11

@@ -2,0 +9,0 @@ * Fix 'Enter' key not toggling toolbar buttons.

4

dist/src/commands/Command.d.ts

@@ -7,6 +7,6 @@ import Editor from '../Editor';

onDrop(_editor: Editor): void;
abstract description(localizationTable: EditorLocalization): string;
abstract description(editor: Editor, localizationTable: EditorLocalization): string;
static union(a: Command, b: Command): Command;
static readonly empty: {
description(_localizationTable: EditorLocalization): string;
description(_editor: Editor, _localizationTable: EditorLocalization): string;
apply(_editor: Editor): void;

@@ -13,0 +13,0 @@ unapply(_editor: Editor): void;

@@ -14,5 +14,5 @@ export class Command {

}
description(localizationTable) {
const aDescription = a.description(localizationTable);
const bDescription = b.description(localizationTable);
description(editor, localizationTable) {
const aDescription = a.description(editor, localizationTable);
const bDescription = b.description(editor, localizationTable);
if (aDescription === bDescription) {

@@ -27,3 +27,3 @@ return aDescription;

Command.empty = new class extends Command {
description(_localizationTable) { return ''; }
description(_editor, _localizationTable) { return ''; }
apply(_editor) { }

@@ -30,0 +30,0 @@ unapply(_editor) { }

@@ -12,4 +12,4 @@ import AbstractComponent from '../components/AbstractComponent';

unapply(editor: Editor): void;
description(localizationTable: EditorLocalization): string;
description(_editor: Editor, localizationTable: EditorLocalization): string;
protected serializeToString(): string;
}

@@ -17,3 +17,3 @@ import describeComponentList from '../components/util/describeComponentList';

}
description(localizationTable) {
description(_editor, localizationTable) {
var _a;

@@ -20,0 +20,0 @@ if (this.duplicates.length === 0) {

@@ -12,4 +12,4 @@ import AbstractComponent from '../components/AbstractComponent';

onDrop(editor: Editor): void;
description(localizationTable: EditorLocalization): string;
description(_editor: Editor, localizationTable: EditorLocalization): string;
protected serializeToString(): string;
}

@@ -37,3 +37,3 @@ import describeComponentList from '../components/util/describeComponentList';

}
description(localizationTable) {
description(_editor, localizationTable) {
var _a;

@@ -40,0 +40,0 @@ if (this.toRemove.length === 0) {

@@ -1,2 +0,2 @@

import Rect2 from '../geometry/Rect2';
import Rect2 from '../math/Rect2';
export interface CommandLocalization {

@@ -3,0 +3,0 @@ movedLeft: string;

import Command from '../commands/Command';
import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -6,0 +6,0 @@ import { ImageComponentLocalization } from './localization';

var _a;
import SerializableCommand from '../commands/SerializableCommand';
import EditorImage from '../EditorImage';
import Mat33 from '../geometry/Mat33';
import Mat33 from '../math/Mat33';
export default class AbstractComponent {

@@ -135,3 +135,3 @@ constructor(

}
description(localizationTable) {
description(_editor, localizationTable) {
return localizationTable.transformedElements(1);

@@ -138,0 +138,0 @@ }

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,2 +0,2 @@

import { PathCommandType } from '../../geometry/Path';
import { PathCommandType } from '../../math/Path';
import Stroke from '../Stroke';

@@ -3,0 +3,0 @@ export const makeArrowBuilder = (initialPoint, _viewport) => {

import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import Stroke from '../Stroke';

@@ -11,3 +11,7 @@ import { StrokeDataPoint } from '../../types';

private maxFitAllowed;
private segments;
private isFirstSegment;
private pathStartConnector;
private mostRecentConnector;
private upperSegments;
private lowerSegments;
private buffer;

@@ -24,3 +28,4 @@ private lastPoint;

private getRenderingStyle;
private getPreview;
private previewPath;
private previewStroke;
preview(renderer: AbstractRenderer): void;

@@ -27,0 +32,0 @@ build(): Stroke;

import { Bezier } from 'bezier-js';
import { Vec2 } from '../../geometry/Vec2';
import Rect2 from '../../geometry/Rect2';
import { PathCommandType } from '../../geometry/Path';
import LineSegment2 from '../../geometry/LineSegment2';
import { Vec2 } from '../../math/Vec2';
import Rect2 from '../../math/Rect2';
import { PathCommandType } from '../../math/Path';
import LineSegment2 from '../../math/LineSegment2';
import Stroke from '../Stroke';

@@ -27,5 +27,9 @@ import Viewport from '../../Viewport';

this.maxFitAllowed = maxFitAllowed;
this.isFirstSegment = true;
this.pathStartConnector = null;
this.mostRecentConnector = null;
this.currentCurve = null;
this.lastPoint = this.startPoint;
this.segments = [];
this.upperSegments = [];
this.lowerSegments = [];
this.buffer = [this.startPoint.pos];

@@ -45,13 +49,70 @@ this.momentum = Vec2.zero;

}
// Get the segments that make up this' path. Can be called after calling build()
getPreview() {
if (this.currentCurve && this.lastPoint) {
const currentPath = this.currentSegmentToPath();
return this.segments.concat(currentPath);
previewPath() {
var _a;
let upperPath;
let lowerPath;
let lowerToUpperCap;
let pathStartConnector;
if (this.currentCurve) {
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
upperPath = this.upperSegments.concat(upperCurve);
lowerPath = this.lowerSegments.concat(lowerCurve);
lowerToUpperCap = lowerToUpperConnector;
pathStartConnector = (_a = this.pathStartConnector) !== null && _a !== void 0 ? _a : upperToLowerConnector;
}
return this.segments;
else {
if (this.mostRecentConnector === null || this.pathStartConnector === null) {
return null;
}
upperPath = this.upperSegments.slice();
lowerPath = this.lowerSegments.slice();
lowerToUpperCap = this.mostRecentConnector;
pathStartConnector = this.pathStartConnector;
}
const startPoint = lowerPath[lowerPath.length - 1].endPoint;
return {
// Start at the end of the lower curve:
// Start point
// ↓
// __/ __/ ← Most recent points on this end
// /___ /
// ↑
// Oldest points
startPoint,
commands: [
// Move to the most recent point on the upperPath:
// ----→•
// __/ __/
// /___ /
lowerToUpperCap,
// Move to the beginning of the upperPath:
// __/ __/
// /___ /
// • ←-
...upperPath.reverse(),
// Move to the beginning of the lowerPath:
// __/ __/
// /___ /
// •
pathStartConnector,
// Move back to the start point:
// •
// __/ __/
// /___ /
...lowerPath,
],
style: this.getRenderingStyle(),
};
}
previewStroke() {
const pathPreview = this.previewPath();
if (pathPreview) {
return new Stroke([pathPreview]);
}
return null;
}
preview(renderer) {
for (const part of this.getPreview()) {
renderer.drawPath(part);
const path = this.previewPath();
if (path) {
renderer.drawPath(path);
}

@@ -63,3 +124,3 @@ }

}
return new Stroke(this.segments);
return this.previewStroke();
}

@@ -73,49 +134,57 @@ roundPoint(point) {

// Don't create a circle around the initial point if the stroke has more than one point.
if (this.segments.length > 0) {
if (!this.isFirstSegment) {
return;
}
const width = Viewport.roundPoint(this.startPoint.width / 3, this.minFitAllowed);
const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
const center = this.roundPoint(this.startPoint.pos);
// Start on the right, cycle clockwise:
// |
// ----- ←
// |
const startPoint = this.startPoint.pos.plus(Vec2.of(width, 0));
// Draw a circle-ish shape around the start point
this.segments.push({
// Start on the right, cycle clockwise:
this.lowerSegments.push({
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, width)),
// Bottom of the circle
// |
// ----- ←
// -----
// |
startPoint: this.startPoint.pos.plus(Vec2.of(width, 0)),
commands: [
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, width)),
// Bottom of the circle
// |
// -----
// |
// ↑
endPoint: center.plus(Vec2.of(0, width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, width)),
endPoint: center.plus(Vec2.of(-width, 0)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, -width)),
endPoint: center.plus(Vec2.of(0, -width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, -width)),
endPoint: center.plus(Vec2.of(width, 0)),
},
],
style: this.getRenderingStyle(),
// ↑
endPoint: center.plus(Vec2.of(0, width)),
}, {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, width)),
endPoint: center.plus(Vec2.of(-width, 0)),
}, {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, -width)),
endPoint: center.plus(Vec2.of(0, -width)),
}, {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, -width)),
endPoint: center.plus(Vec2.of(width, 0)),
});
this.pathStartConnector = {
kind: PathCommandType.LineTo,
point: startPoint,
};
this.mostRecentConnector = this.pathStartConnector;
return;
}
this.segments.push(this.currentSegmentToPath());
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
if (this.isFirstSegment) {
// We draw the upper path (reversed), then the lower path, so we need the
// upperToLowerConnector to join the two paths.
this.pathStartConnector = upperToLowerConnector;
this.isFirstSegment = false;
}
// With the most recent connector, we're joining the end of the lowerPath to the most recent
// upperPath:
this.mostRecentConnector = lowerToUpperConnector;
this.upperSegments.push(upperCurve);
this.lowerSegments.push(lowerCurve);
const lastPoint = this.buffer[this.buffer.length - 1];
this.lastExitingVec = Vec2.ofXY(this.currentCurve.points[2]).minus(Vec2.ofXY(this.currentCurve.points[1]));
console.assert(this.lastExitingVec.magnitude() !== 0);
console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
// Use the last two points to start a new curve (the last point isn't used

@@ -128,2 +197,3 @@ // in the current curve and we want connected curves to share end points)

}
// Returns [upper curve, connector, lower curve]
currentSegmentToPath() {

@@ -171,23 +241,24 @@ if (this.currentCurve == null) {

}
const pathCommands = [
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
endPoint: this.roundPoint(endPt.plus(endVec)),
},
{
kind: PathCommandType.LineTo,
point: this.roundPoint(endPt.minus(endVec)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
endPoint: this.roundPoint(startPt.minus(startVec)),
},
];
return {
startPoint: this.roundPoint(startPt.plus(startVec)),
commands: pathCommands,
style: this.getRenderingStyle(),
// Each starts at startPt ± startVec
const lowerCurve = {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
endPoint: this.roundPoint(endPt.plus(endVec)),
};
// From the end of the upperCurve to the start of the lowerCurve:
const upperToLowerConnector = {
kind: PathCommandType.LineTo,
point: this.roundPoint(startPt.plus(startVec)),
};
// From the end of lowerCurve to the start of upperCurve:
const lowerToUpperConnector = {
kind: PathCommandType.LineTo,
point: this.roundPoint(endPt.minus(endVec))
};
const upperCurve = {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
endPoint: this.roundPoint(startPt.minus(startVec)),
};
return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
}

@@ -214,3 +285,3 @@ // Compute the direction of the velocity at the end of this.buffer

const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
&& this.segments.length === 0;
&& this.isFirstSegment;
// Snap to the starting point if the stroke is contained within a small ball centered

@@ -217,0 +288,0 @@ // at the starting point.

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,2 +0,2 @@

import { PathCommandType } from '../../geometry/Path';
import { PathCommandType } from '../../math/Path';
import Stroke from '../Stroke';

@@ -3,0 +3,0 @@ export const makeLineBuilder = (initialPoint, _viewport) => {

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,4 +0,4 @@

import Mat33 from '../../geometry/Mat33';
import Path from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import Mat33 from '../../math/Mat33';
import Path from '../../math/Path';
import Rect2 from '../../math/Rect2';
import Stroke from '../Stroke';

@@ -5,0 +5,0 @@ export const makeFilledRectangleBuilder = (initialPoint, viewport) => {

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,5 +0,5 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Path from '../geometry/Path';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Path from '../math/Path';
import Rect2 from '../math/Rect2';
import AbstractRenderer, { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';

@@ -6,0 +6,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,3 +0,3 @@

import Path from '../geometry/Path';
import Rect2 from '../geometry/Rect2';
import Path from '../math/Path';
import Rect2 from '../math/Rect2';
import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';

@@ -4,0 +4,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,2 +0,2 @@

import Rect2 from '../geometry/Rect2';
import Rect2 from '../math/Rect2';
import SVGRenderer from '../rendering/renderers/SVGRenderer';

@@ -3,0 +3,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import RenderingStyle from '../rendering/RenderingStyle';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';

@@ -5,0 +5,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import AbstractComponent from './AbstractComponent';

@@ -1,2 +0,2 @@

import Rect2 from '../geometry/Rect2';
import Rect2 from '../math/Rect2';
import SVGRenderer from '../rendering/renderers/SVGRenderer';

@@ -3,0 +3,0 @@ import AbstractComponent from './AbstractComponent';

@@ -7,3 +7,3 @@ import EditorImage from './EditorImage';

import Viewport from './Viewport';
import { Point2 } from './geometry/Vec2';
import { Point2 } from './math/Vec2';
import HTMLToolbar from './toolbar/HTMLToolbar';

@@ -13,3 +13,3 @@ import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';

import Pointer from './Pointer';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import { EditorLocalization } from './localization';

@@ -36,2 +36,3 @@ export interface EditorSettings {

private accessibilityAnnounceArea;
private accessibilityControlArea;
private settings;

@@ -42,2 +43,3 @@ constructor(parent: HTMLElement, settings?: Partial<EditorSettings>);

hideLoadingWarning(): void;
private previousAccessibilityAnnouncement;
announceForAccessibility(message: string): void;

@@ -44,0 +46,0 @@ addToolbar(defaultLayout?: boolean): HTMLToolbar;

@@ -17,4 +17,4 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import EventDispatcher from './EventDispatcher';
import { Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import { Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import HTMLToolbar from './toolbar/HTMLToolbar';

@@ -26,3 +26,3 @@ import Display, { RenderingMode } from './rendering/Display';

import Pointer from './Pointer';
import Mat33 from './geometry/Mat33';
import Mat33 from './math/Mat33';
import getLocalizationTable from './localizations/getLocalizationTable';

@@ -32,7 +32,8 @@ export class Editor {

var _a, _b, _c, _d;
this.previousAccessibilityAnnouncement = '';
this.announceUndoCallback = (command) => {
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this, this.localization)));
};
this.announceRedoCallback = (command) => {
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this.localization)));
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this, this.localization)));
};

@@ -57,4 +58,10 @@ this.rerenderQueued = false;

this.container.appendChild(this.loadingWarning);
this.accessibilityControlArea = document.createElement('textarea');
this.accessibilityControlArea.setAttribute('placeholder', this.localization.accessibilityInputInstructions);
this.accessibilityControlArea.style.opacity = '0';
this.accessibilityControlArea.style.width = '0';
this.accessibilityControlArea.style.height = '0';
this.accessibilityControlArea.style.position = 'absolute';
this.accessibilityAnnounceArea = document.createElement('div');
this.accessibilityAnnounceArea.ariaLive = 'assertive';
this.accessibilityAnnounceArea.setAttribute('aria-live', 'assertive');
this.accessibilityAnnounceArea.className = 'accessibilityAnnouncement';

@@ -64,4 +71,5 @@ this.container.appendChild(this.accessibilityAnnounceArea);

this.renderingRegion.className = 'imageEditorRenderArea';
this.renderingRegion.appendChild(this.accessibilityControlArea);
this.renderingRegion.setAttribute('tabIndex', '0');
this.renderingRegion.ariaLabel = this.localization.imageEditor;
this.renderingRegion.setAttribute('alt', '');
this.notifier = new EventDispatcher();

@@ -113,3 +121,8 @@ this.importExportViewport = new Viewport(this.notifier);

announceForAccessibility(message) {
// Force re-announcing an announcement if announced again.
if (message === this.previousAccessibilityAnnouncement) {
message = message + '. ';
}
this.accessibilityAnnounceArea.innerText = message;
this.previousAccessibilityAnnouncement = message;
}

@@ -246,2 +259,5 @@ addToolbar(defaultLayout = true) {

});
this.accessibilityControlArea.addEventListener('input', () => {
this.accessibilityControlArea.value = '';
});
}

@@ -252,3 +268,7 @@ // Adds event listners for keypresses to [elem] and forwards those events to the

elem.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
if (evt.key === 't' || evt.key === 'T') {
evt.preventDefault();
this.display.rerenderAsText();
}
else if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,

@@ -283,3 +303,3 @@ key: evt.key,

}
this.announceForAccessibility(command.description(this.localization));
this.announceForAccessibility(command.description(this, this.localization));
}

@@ -461,3 +481,3 @@ // Dispatches a command without announcing it. By default, does not add to history.

}
description(localizationTable) {
description(_editor, localizationTable) {
return localizationTable.resizeOutputCommand(imageRect);

@@ -464,0 +484,0 @@ }

@@ -5,3 +5,3 @@ import AbstractRenderer from './rendering/renderers/AbstractRenderer';

import AbstractComponent from './components/AbstractComponent';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import RenderingCache from './rendering/caching/RenderingCache';

@@ -8,0 +8,0 @@ export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;

var _a;
import AbstractComponent from './components/AbstractComponent';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import SerializableCommand from './commands/SerializableCommand';

@@ -86,3 +86,3 @@ export const sortLeavesByZIndex = (leaves) => {

}
description(localization) {
description(editor, localization) {
return localization.addElementAction(this.element.description(localization));

@@ -89,0 +89,0 @@ }

@@ -7,2 +7,3 @@ import { CommandLocalization } from './commands/localization';

export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization, TextRendererLocalization {
accessibilityInputInstructions: string;
undoAnnouncement: (actionDescription: string) => string;

@@ -9,0 +10,0 @@ redoAnnouncement: (actionDescription: string) => string;

@@ -6,2 +6,6 @@ import { defaultCommandLocalization } from './commands/localization';

import { defaultToolLocalization } from './tools/localization';
export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultToolbarLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), defaultTextRendererLocalization), { loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });
export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultToolbarLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), defaultTextRendererLocalization), { accessibilityInputInstructions: [
'Press "t" to read the contents of the viewport as text.',
'Use the arrow keys to move the viewport, click and drag to draw strokes.',
'Press "w" to zoom in and "s" to zoom out.',
].join(' '), loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });

@@ -9,3 +9,3 @@ import { defaultEditorLocalization } from '../localization';

// (see src/toolbar/localization.ts)
pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectObjectType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFronScreen: 'Selecciona un color de la pantalla', dropdownShown(toolName) {
pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectObjectType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFromScreen: 'Selecciona un color de la pantalla', clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color', dropdownShown(toolName) {
return `Menú por ${toolName} es visible`;

@@ -12,0 +12,0 @@ }, dropdownHidden: function (toolName) {

@@ -1,2 +0,2 @@

import { Point2 } from './geometry/Vec2';
import { Point2 } from './math/Vec2';
import Viewport from './Viewport';

@@ -3,0 +3,0 @@ export declare enum PointerDevice {

@@ -1,2 +0,2 @@

import { Vec2 } from './geometry/Vec2';
import { Vec2 } from './math/Vec2';
export var PointerDevice;

@@ -3,0 +3,0 @@ (function (PointerDevice) {

@@ -1,3 +0,3 @@

import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../renderers/AbstractRenderer';

@@ -4,0 +4,0 @@ import { BeforeDeallocCallback, CacheState } from './types';

@@ -1,2 +0,2 @@

import Mat33 from '../../geometry/Mat33';
import Mat33 from '../../math/Mat33';
// Represents a cached renderer/canvas

@@ -3,0 +3,0 @@ // This is not a [CacheNode] -- it handles cached renderers and does not have sub-renderers.

import { BeforeDeallocCallback, PartialCacheState } from './types';
import CacheRecord from './CacheRecord';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
export declare class CacheRecordManager {

@@ -5,0 +5,0 @@ private readonly cacheState;

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import RenderingCacheNode from './RenderingCacheNode';

@@ -3,0 +3,0 @@ import { CacheRecordManager } from './CacheRecordManager';

import { ImageNode } from '../../EditorImage';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import Viewport from '../../Viewport';

@@ -23,2 +23,3 @@ import AbstractRenderer from '../renderers/AbstractRenderer';

private idsOfIntersecting;
private allRenderedIdsIn;
private renderingIsUpToDate;

@@ -25,0 +26,0 @@ renderItems(screenRenderer: AbstractRenderer, items: ImageNode[], viewport: Viewport): void;

// A cache record with sub-nodes.
import Color4 from '../../Color4';
import { sortLeavesByZIndex } from '../../EditorImage';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
// 3x3 divisions for each node.

@@ -119,7 +119,10 @@ const cacheDivisionSize = 3;

}
renderingIsUpToDate(sortedIds) {
if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
// Returns true iff all elems of this.renderedIds are in sortedIds.
// sortedIds should be sorted by z-index (or some other order, so long as they are
// sorted by the same thing as this.renderedIds.)
allRenderedIdsIn(sortedIds) {
if (this.renderedIds.length > sortedIds.length) {
return false;
}
for (let i = 0; i < sortedIds.length; i++) {
for (let i = 0; i < this.renderedIds.length; i++) {
if (sortedIds[i] !== this.renderedIds[i]) {

@@ -131,2 +134,8 @@ return false;

}
renderingIsUpToDate(sortedIds) {
if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
return false;
}
return this.allRenderedIdsIn(sortedIds);
}
// Render all [items] within [viewport]

@@ -193,4 +202,3 @@ renderItems(screenRenderer, items, viewport) {

// Is it worth it to render the items?
// TODO: Replace this with something performace based.
// TODO: Determine whether it is 'worth it' to cache this depending on rendering time.
// TODO: Consider replacing this with something performace based.
if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {

@@ -201,3 +209,3 @@ let fullRerenderNeeded = true;

}
else if (leavesByIds.length > this.renderedIds.length && this.renderedMaxZIndex !== null) {
else if (leavesByIds.length > this.renderedIds.length && this.allRenderedIdsIn(leafIds) && this.renderedMaxZIndex !== null) {
// We often don't need to do a full re-render even if something's changed.

@@ -235,2 +243,5 @@ // Check whether we can just draw on top of the existing cache.

}
else if (debugMode) {
console.log('Decided on a full re-render. Reason: At least one of the following is false:', '\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length, '\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds), '\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null, '\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
}
if (fullRerenderNeeded) {

@@ -237,0 +248,0 @@ thisRenderer = this.cachedRenderer.startRender();

@@ -1,2 +0,2 @@

import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import DummyRenderer from '../renderers/DummyRenderer';

@@ -3,0 +3,0 @@ import createEditor from '../../testing/createEditor';

@@ -1,2 +0,2 @@

import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import AbstractRenderer from '../renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { CacheRecordManager } from './CacheRecordManager';

import AbstractRenderer from './renderers/AbstractRenderer';
import { Editor } from '../Editor';
import { Point2 } from '../geometry/Vec2';
import { Point2 } from '../math/Vec2';
import RenderingCache from './caching/RenderingCache';

@@ -16,2 +16,3 @@ import Color4 from '../Color4';

private textRenderer;
private textRerenderOutput;
private cache;

@@ -27,2 +28,3 @@ private resizeSurfacesCallback?;

private initializeTextRendering;
rerenderAsText(): void;
startRerender(): AbstractRenderer;

@@ -29,0 +31,0 @@ setDraftMode(draftMode: boolean): void;

import CanvasRenderer from './renderers/CanvasRenderer';
import { EditorEventType } from '../types';
import DummyRenderer from './renderers/DummyRenderer';
import { Vec2 } from '../geometry/Vec2';
import { Vec2 } from '../math/Vec2';
import RenderingCache from './caching/RenderingCache';

@@ -18,2 +18,3 @@ import TextOnlyRenderer from './renderers/TextOnlyRenderer';

this.parent = parent;
this.textRerenderOutput = null;
this.getColorAt = (_screenPos) => {

@@ -55,6 +56,6 @@ return null;

blockResolution: cacheBlockResolution,
cacheSize: 500 * 500 * 4 * 200,
cacheSize: 500 * 500 * 4 * 220,
maxScale: 1.5,
minComponentsPerCache: 50,
minComponentsToUseCache: 120,
minComponentsPerCache: 45,
minComponentsToUseCache: 105,
});

@@ -131,12 +132,17 @@ this.editor.notifier.on(EditorEventType.DisplayResized, event => {

rerenderButton.innerText = this.editor.localization.rerenderAsText;
const rerenderOutput = document.createElement('div');
rerenderOutput.ariaLive = 'polite';
this.textRerenderOutput = document.createElement('div');
this.textRerenderOutput.setAttribute('aria-live', 'polite');
rerenderButton.onclick = () => {
this.textRenderer.clear();
this.editor.image.render(this.textRenderer, this.editor.viewport);
rerenderOutput.innerText = this.textRenderer.getDescription();
this.rerenderAsText();
};
textRendererOutputContainer.replaceChildren(rerenderButton, rerenderOutput);
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
this.editor.createHTMLOverlay(textRendererOutputContainer);
}
rerenderAsText() {
this.textRenderer.clear();
this.editor.image.render(this.textRenderer, this.editor.viewport);
if (this.textRerenderOutput) {
this.textRerenderOutput.innerText = this.textRenderer.getDescription();
}
}
// Clears the drawing surfaces and otherwise prepares for a rerender.

@@ -143,0 +149,0 @@ startRerender() {

export interface TextRendererLocalization {
pathNodeCount(pathCount: number): string;
textNodeCount(nodeCount: number): string;
textNode(content: string): string;

@@ -3,0 +5,0 @@ rerenderAsText: string;

export const defaultTextRendererLocalization = {
pathNodeCount: (count) => `There are ${count} visible path objects.`,
textNodeCount: (count) => `There are ${count} visible text nodes.`,
textNode: (content) => `Text: ${content}`,
rerenderAsText: 'Re-render as text',
};
import { LoadSaveDataTable } from '../../components/AbstractComponent';
import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import { PathCommand } from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import { PathCommand } from '../../math/Path';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Viewport from '../../Viewport';

@@ -8,0 +8,0 @@ import RenderingStyle from '../RenderingStyle';

@@ -1,3 +0,3 @@

import Path, { PathCommandType } from '../../geometry/Path';
import { Vec2 } from '../../geometry/Vec2';
import Path, { PathCommandType } from '../../math/Path';
import { Vec2 } from '../../math/Vec2';
import { stylesEqual } from '../RenderingStyle';

@@ -4,0 +4,0 @@ export default class AbstractRenderer {

import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -7,0 +7,0 @@ import RenderingStyle from '../RenderingStyle';

import Color4 from '../../Color4';
import Text from '../../components/Text';
import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import AbstractRenderer from './AbstractRenderer';

@@ -5,0 +5,0 @@ export default class CanvasRenderer extends AbstractRenderer {

import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -7,0 +7,0 @@ import RenderingStyle from '../RenderingStyle';

// Renderer that outputs nothing. Useful for automated tests.
import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import AbstractRenderer from './AbstractRenderer';

@@ -4,0 +4,0 @@ export default class DummyRenderer extends AbstractRenderer {

import { LoadSaveDataTable } from '../../components/AbstractComponent';
import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Viewport from '../../Viewport';

@@ -7,0 +7,0 @@ import RenderingStyle from '../RenderingStyle';

@@ -1,3 +0,5 @@

import Path, { PathCommandType } from '../../geometry/Path';
import { Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import Path, { PathCommandType } from '../../math/Path';
import { toRoundedString } from '../../math/rounding';
import { Vec2 } from '../../math/Vec2';
import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';

@@ -92,2 +94,4 @@ import AbstractRenderer from './AbstractRenderer';

transform = this.getCanvasToScreenTransform().rightMul(transform);
const translation = transform.transformVec2(Vec2.zero);
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
const textElem = document.createElementNS(svgNameSpace, 'text');

@@ -105,2 +109,4 @@ textElem.appendChild(document.createTextNode(text));

textElem.style.fill = style.renderingStyle.fill.toHexString();
textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
if (style.renderingStyle.stroke) {

@@ -107,0 +113,0 @@ const strokeStyle = style.renderingStyle.stroke;

import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -12,2 +12,4 @@ import { TextRendererLocalization } from '../localization';

private descriptionBuilder;
private pathCount;
private textNodeCount;
constructor(viewport: Viewport, localizationTable: TextRendererLocalization);

@@ -14,0 +16,0 @@ displaySize(): Vec3;

@@ -1,2 +0,2 @@

import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import AbstractRenderer from './AbstractRenderer';

@@ -9,2 +9,4 @@ // Outputs a description of what was rendered.

this.descriptionBuilder = [];
this.pathCount = 0;
this.textNodeCount = 0;
}

@@ -17,5 +19,11 @@ displaySize() {

this.descriptionBuilder = [];
this.pathCount = 0;
this.textNodeCount = 0;
}
getDescription() {
return this.descriptionBuilder.join('\n');
return [
this.localizationTable.pathNodeCount(this.pathCount),
this.localizationTable.textNodeCount(this.textNodeCount),
...this.descriptionBuilder
].join('\n');
}

@@ -25,2 +33,3 @@ beginPath(_startPoint) {

endPath(_style) {
this.pathCount++;
}

@@ -37,5 +46,6 @@ lineTo(_point) {

this.descriptionBuilder.push(this.localizationTable.textNode(text));
this.textNodeCount++;
}
isTooSmallToRender(rect) {
return rect.maxDimension < 10 / this.getSizeOfCanvasPixelOnScreen();
return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
}

@@ -42,0 +52,0 @@ drawPoints(..._points) {

@@ -1,2 +0,2 @@

import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';

@@ -3,0 +3,0 @@ export declare const defaultSVGViewRect: Rect2;

@@ -15,6 +15,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import UnknownSVGObject from './components/UnknownSVGObject';
import Mat33 from './geometry/Mat33';
import Path from './geometry/Path';
import Rect2 from './geometry/Rect2';
import { Vec2 } from './geometry/Vec2';
import Mat33 from './math/Mat33';
import Path from './math/Path';
import Rect2 from './math/Rect2';
import { Vec2 } from './math/Vec2';
// Size of a loaded image if no size is specified.

@@ -175,4 +175,12 @@ export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);

}
// Compute transform matrix
let transform = Mat33.fromCSSMatrix(transformProperty);
// Compute transform matrix. Prefer the actual .style.transform
// to the computed stylesheet -- in some browsers, the computedStyles version
// can have lower precision.
let transform;
try {
transform = Mat33.fromCSSMatrix(elem.style.transform);
}
catch (_e) {
transform = Mat33.fromCSSMatrix(transformProperty);
}
const supportedAttrs = [];

@@ -179,0 +187,0 @@ const elemX = elem.getAttribute('x');

@@ -18,1 +18,4 @@ import Color4 from '../Color4';

export declare const makePipetteIcon: (color?: Color4) => SVGSVGElement;
export declare const makeResizeViewportIcon: () => SVGSVGElement;
export declare const makeDuplicateSelectionIcon: () => SVGSVGElement;
export declare const makeDeleteSelectionIcon: () => SVGSVGElement;
import EventDispatcher from '../EventDispatcher';
import { Vec2 } from '../geometry/Vec2';
import { Vec2 } from '../math/Vec2';
import SVGRenderer from '../rendering/renderers/SVGRenderer';

@@ -91,143 +91,126 @@ import Viewport from '../Viewport';

};
export const makeHandToolIcon = () => {
const pathIcon = (pathData, fill = 'var(--icon-color)', strokeColor = 'none', strokeWidth = '0px') => {
const icon = document.createElementNS(svgNamespace, 'svg');
// Draw a cursor-like shape (like some of the other icons, made with Inkscape)
icon.innerHTML = `
<g>
<path d='
m 10,60
5,30
H 90
V 30
C 90,20 75,20 75,30
V 60
20
C 75,10 60,10 60,20
V 60
15
C 60,5 45,5 45,15
V 60
25
C 45,15 30,15 30,25
V 60
75
L 25,60
C 20,45 10,50 10,60
Z'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
</g>
`;
const path = document.createElementNS(svgNamespace, 'path');
path.setAttribute('d', pathData);
path.style.fill = fill;
path.style.stroke = strokeColor;
path.style.strokeWidth = strokeWidth;
icon.appendChild(path);
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
};
export const makeHandToolIcon = () => {
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
// Draw a cursor-like shape (like some of the other icons, made with Inkscape)
return pathIcon(`
m 10,60
5,30
H 90
V 30
C 90,20 75,20 75,30
V 60
20
C 75,10 60,10 60,20
V 60
15
C 60,5 45,5 45,15
V 60
25
C 45,15 30,15 30,25
V 60
75
L 25,60
C 20,45 10,50 10,60
Z
`, fill, strokeColor, strokeWidth);
};
export const makeTouchPanningIcon = () => {
const icon = document.createElementNS(svgNamespace, 'svg');
icon.innerHTML = `
<path
d='
M 5,5.5
V 17.2
L 16.25,5.46
Z
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
return pathIcon(`
M 5,5.5
V 17.2
L 16.25,5.46
Z
m 33.75,0
L 50,17
V 5.5
Z
m 33.75,0
L 50,17
V 5.5
Z
M 5,40.7
v 11.7
h 11.25
z
M 26,19
C 19.8,19.4 17.65,30.4 21.9,34.8
L 50,70
H 27.5
c -11.25,0 -11.25,17.6 0,17.6
H 61.25
C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
L 33.1,23
C 30.3125,20.128192 27.9,19 25.830078,19.119756
Z
'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
`;
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
M 5,40.7
v 11.7
h 11.25
z
M 26,19
C 19.8,19.4 17.65,30.4 21.9,34.8
L 50,70
H 27.5
c -11.25,0 -11.25,17.6 0,17.6
H 61.25
C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
L 33.1,23
C 30.3125,20.128192 27.9,19 25.830078,19.119756
Z
`, fill, strokeColor, strokeWidth);
};
export const makeAllDevicePanningIcon = () => {
const icon = document.createElementNS(svgNamespace, 'svg');
icon.innerHTML = `
<path
d='
M 5 5
L 5 17.5
17.5 5
5 5
z
M 42.5 5
L 55 17.5
55 5
42.5 5
z
M 70 10
L 70 21
61 15
55.5 23
66 30
56 37
61 45
70 39
70 50
80 50
80 39
89 45
95 36
84 30
95 23
89 15
80 21
80 10
70 10
z
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
return pathIcon(`
M 5 5
L 5 17.5
17.5 5
5 5
z
M 27.5 26.25
L 27.5 91.25
L 43.75 83.125
L 52 99
L 68 91
L 60 75
L 76.25 66.875
L 27.5 26.25
z
M 5 42.5
L 5 55
L 17.5 55
L 5 42.5
z
'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
`;
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
M 42.5 5
L 55 17.5
55 5
42.5 5
z
M 70 10
L 70 21
61 15
55.5 23
66 30
56 37
61 45
70 39
70 50
80 50
80 39
89 45
95 36
84 30
95 23
89 15
80 21
80 10
70 10
z
M 27.5 26.25
L 27.5 91.25
L 43.75 83.125
L 52 99
L 68 91
L 60 75
L 76.25 66.875
L 27.5 26.25
z
M 5 42.5
L 5 55
L 17.5 55
L 5 42.5
z
`, fill, strokeColor, strokeWidth);
};

@@ -373,1 +356,28 @@ export const makeZoomIcon = () => {

};
export const makeResizeViewportIcon = () => {
return pathIcon(`
M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
M 15 15 15 30 20 30 20 20 30 20 30 15 15 15 z
M 84 15 82 17 81 16 81 20 85 20 84 19 86 17 84 15 z
M 26 24 24 26 26 28 25 29 29 29 29 25 28 26 26 24 z
M 25 71 26 72 24 74 26 76 28 74 29 75 29 71 25 71 z
M 15 75 15 85 25 85 25 80 20 80 20 75 15 75 z
M 90 75 90 90 75 90 75 95 95 95 95 75 90 75 z
M 81 81 81 85 82 84 84 86 86 84 84 82 85 81 81 81 z
`);
};
export const makeDuplicateSelectionIcon = () => {
return pathIcon(`
M 45,10 45,55 90,55 90,10 45,10 z
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
`);
};
export const makeDeleteSelectionIcon = () => {
const strokeWidth = '5px';
const strokeColor = 'var(--icon-color)';
const fillColor = 'none';
return pathIcon(`
M 10,10 90,90
M 10,90 90,10
`, fillColor, strokeColor, strokeWidth);
};

@@ -20,3 +20,4 @@ export interface ToolbarLocalization {

duplicateSelection: string;
pickColorFronScreen: string;
pickColorFromScreen: string;
clickToPickColorAnnouncement: string;
undo: string;

@@ -23,0 +24,0 @@ redo: string;

@@ -17,3 +17,4 @@ export const defaultToolbarLocalization = {

selectObjectType: 'Object type: ',
pickColorFronScreen: 'Pick color from screen',
pickColorFromScreen: 'Pick color from screen',
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',

@@ -20,0 +21,0 @@ touchPanning: 'Touchscreen panning',

@@ -56,3 +56,3 @@ import Color4 from '../Color4';

pipetteButton.classList.add('pipetteButton');
pipetteButton.title = editor.localization.pickColorFronScreen;
pipetteButton.title = editor.localization.pickColorFromScreen;
pipetteButton.setAttribute('alt', pipetteButton.title);

@@ -92,2 +92,3 @@ const updatePipetteIcon = (color) => {

pipetteButton.classList.add('active');
editor.announceForAccessibility(editor.localization.clickToPickColorAnnouncement);
}

@@ -94,0 +95,0 @@ };

@@ -117,5 +117,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {

this.button.classList.add('disabled');
this.button.setAttribute('aria-disabled', 'true');
}
else {
this.button.classList.remove('disabled');
this.button.removeAttribute('aria-disabled');
}

@@ -122,0 +124,0 @@ }

@@ -1,2 +0,2 @@

import Mat33 from '../../geometry/Mat33';
import Mat33 from '../../math/Mat33';
import { PanZoomMode } from '../../tools/PanZoom';

@@ -46,3 +46,3 @@ import { EditorEventType } from '../../types';

const transformUpdate = Mat33.scaling2D(factor, screenCenter);
editor.dispatch(new Viewport.ViewportTransform(transformUpdate), false);
editor.dispatch(Viewport.transformBy(transformUpdate), false);
};

@@ -56,3 +56,3 @@ increaseButton.onclick = () => {

resetViewButton.onclick = () => {
editor.dispatch(new Viewport.ViewportTransform(editor.viewport.canvasToScreenTransform.inverse()), true);
editor.dispatch(Viewport.transformBy(editor.viewport.canvasToScreenTransform.inverse()), true);
};

@@ -59,0 +59,0 @@ return zoomLevelRow;

@@ -10,3 +10,2 @@ import Editor from '../../Editor';

protected createIcon(): Element;
protected fillDropdown(dropdown: HTMLElement): boolean;
}
import { EditorEventType } from '../../types';
import { makeSelectionIcon } from '../icons';
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
import ActionButtonWidget from './ActionButtonWidget';
import BaseToolWidget from './BaseToolWidget';

@@ -8,33 +9,24 @@ export class SelectionWidget extends BaseToolWidget {

this.tool = tool;
}
getTitle() {
return this.localizationTable.select;
}
createIcon() {
return makeSelectionIcon();
}
fillDropdown(dropdown) {
const container = document.createElement('div');
const resizeButton = document.createElement('button');
const duplicateButton = document.createElement('button');
const deleteButton = document.createElement('button');
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
resizeButton.disabled = true;
deleteButton.innerText = this.localizationTable.deleteSelection;
deleteButton.disabled = true;
duplicateButton.innerText = this.localizationTable.duplicateSelection;
duplicateButton.disabled = true;
resizeButton.onclick = () => {
const resizeButton = new ActionButtonWidget(editor, localization, makeResizeViewportIcon, this.localizationTable.resizeImageToSelection, () => {
const selection = this.tool.getSelection();
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
};
deleteButton.onclick = () => {
});
const deleteButton = new ActionButtonWidget(editor, localization, makeDeleteSelectionIcon, this.localizationTable.deleteSelection, () => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection.deleteSelectedObjects());
this.tool.clearSelection();
};
duplicateButton.onclick = () => {
});
const duplicateButton = new ActionButtonWidget(editor, localization, makeDuplicateSelectionIcon, this.localizationTable.duplicateSelection, () => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection.duplicateSelectedObjects());
});
this.addSubWidget(resizeButton);
this.addSubWidget(deleteButton);
this.addSubWidget(duplicateButton);
const updateDisabled = (disabled) => {
resizeButton.setDisabled(disabled);
deleteButton.setDisabled(disabled);
duplicateButton.setDisabled(disabled);
};
updateDisabled(true);
// Enable/disable actions based on whether items are selected

@@ -48,11 +40,12 @@ this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {

const hasSelection = selection && selection.region.area > 0;
resizeButton.disabled = !hasSelection;
deleteButton.disabled = resizeButton.disabled;
duplicateButton.disabled = resizeButton.disabled;
updateDisabled(!hasSelection);
}
});
container.replaceChildren(resizeButton, duplicateButton, deleteButton);
dropdown.appendChild(container);
return true;
}
getTitle() {
return this.localizationTable.select;
}
createIcon() {
return makeSelectionIcon();
}
}
import BaseTool from './BaseTool';
import LineSegment2 from '../geometry/LineSegment2';
import LineSegment2 from '../math/LineSegment2';
import Erase from '../commands/Erase';

@@ -4,0 +4,0 @@ import { ToolType } from './ToolController';

import { Editor } from '../Editor';
import { Point2 } from '../geometry/Vec2';
import { Point2 } from '../math/Vec2';
import Pointer from '../Pointer';

@@ -4,0 +4,0 @@ import { KeyPressEvent, PointerEvt, WheelEvt } from '../types';

@@ -1,4 +0,4 @@

import Mat33 from '../geometry/Mat33';
import { Vec2 } from '../geometry/Vec2';
import Vec3 from '../geometry/Vec3';
import Mat33 from '../math/Mat33';
import { Vec2 } from '../math/Vec2';
import Vec3 from '../math/Vec3';
import { PointerDevice } from '../Pointer';

@@ -56,3 +56,3 @@ import { EditorEventType } from '../types';

if (handlingGesture) {
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = Viewport.transformBy(Mat33.identity));
this.editor.display.setDraftMode(true);

@@ -78,7 +78,7 @@ }

this.lastAngle = angle;
this.transform = new Viewport.ViewportTransform(this.transform.transform.rightMul(transformUpdate));
this.transform = Viewport.transformBy(this.transform.transform.rightMul(transformUpdate));
}
handleOneFingerMove(pointer) {
const delta = this.getCenterDelta(pointer.screenPos);
this.transform = new Viewport.ViewportTransform(this.transform.transform.rightMul(Mat33.translation(delta)));
this.transform = Viewport.transformBy(this.transform.transform.rightMul(Mat33.translation(delta)));
this.lastScreenCenter = pointer.screenPos;

@@ -88,3 +88,3 @@ }

var _a;
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = Viewport.transformBy(Mat33.identity));
const lastTransform = this.transform;

@@ -116,3 +116,3 @@ if (allPointers.length === 2) {

// current transformation, if it exists.
updateTransform(transformUpdate) {
updateTransform(transformUpdate, announce = false) {
var _a;

@@ -124,4 +124,7 @@ let newTransform = transformUpdate;

(_a = this.transform) === null || _a === void 0 ? void 0 : _a.unapply(this.editor);
this.transform = new Viewport.ViewportTransform(newTransform);
this.transform = Viewport.transformBy(newTransform);
this.transform.apply(this.editor);
if (announce) {
this.editor.announceForAccessibility(this.transform.description(this.editor, this.editor.localization));
}
}

@@ -131,3 +134,3 @@ onWheel({ delta, screenPos }) {

// need to unapply/reapply.
this.transform = new Viewport.ViewportTransform(Mat33.identity);
this.transform = Viewport.transformBy(Mat33.identity);
const canvasPos = this.editor.viewport.screenToCanvas(screenPos);

@@ -139,3 +142,3 @@ const toCanvas = this.editor.viewport.screenToCanvasTransform;

const transformUpdate = Mat33.scaling2D(Math.max(0.25, Math.min(Math.pow(pinchZoomScaleFactor, -delta.z), 4)), canvasPos).rightMul(Mat33.translation(translation));
this.updateTransform(transformUpdate);
this.updateTransform(transformUpdate, true);
return true;

@@ -148,3 +151,3 @@ }

// No need to keep the same the transform for keyboard events.
this.transform = new Viewport.ViewportTransform(Mat33.identity);
this.transform = Viewport.transformBy(Mat33.identity);
let translation = Vec2.zero;

@@ -165,2 +168,3 @@ let scale = 1;

break;
case 'q':
case 'k':

@@ -170,2 +174,3 @@ case 'ArrowUp':

break;
case 'e':
case 'j':

@@ -192,3 +197,3 @@ case 'ArrowDown':

translation = translation.times(30); // Move at most 30 units
rotation *= Math.PI / 8; // Rotate at most a sixteenth of a rotation
rotation *= Math.PI / 8; // Rotate at least a sixteenth of a rotation
// Transform the canvas, not the viewport:

@@ -198,2 +203,7 @@ translation = translation.times(-1);

scale = 1 / scale;
// Work around an issue that seems to be related to rotation matricies losing precision on inversion.
// TODO: Figure out why and implement a better solution.
if (rotation !== 0) {
rotation += 0.0001;
}
const toCanvas = this.editor.viewport.screenToCanvasTransform;

@@ -206,3 +216,3 @@ // Transform without translating (treat toCanvas as a linear instead of

const transformUpdate = Mat33.scaling2D(scale, transformCenter).rightMul(Mat33.zRotation(rotation, transformCenter)).rightMul(Mat33.translation(translation));
this.updateTransform(transformUpdate);
this.updateTransform(transformUpdate, true);
return true;

@@ -209,0 +219,0 @@ }

import Command from '../commands/Command';
import Editor from '../Editor';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import { Point2, Vec2 } from '../geometry/Vec2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import { Point2, Vec2 } from '../math/Vec2';
import { KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';

@@ -7,0 +7,0 @@ import BaseTool from './BaseTool';

@@ -13,6 +13,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

import Erase from '../commands/Erase';
import Mat33 from '../geometry/Mat33';
import Mat33 from '../math/Mat33';
// import Mat33 from "../geometry/Mat33";
import Rect2 from '../geometry/Rect2';
import { Vec2 } from '../geometry/Vec2';
import Rect2 from '../math/Rect2';
import { Vec2 } from '../math/Vec2';
import { EditorEventType } from '../types';

@@ -346,3 +346,3 @@ import Viewport from '../Viewport';

const delta = this.region.center.minus(closestPoint);
this.editor.dispatchNoAnnounce(new Viewport.ViewportTransform(Mat33.translation(delta.times(-1))), false);
this.editor.dispatchNoAnnounce(Viewport.transformBy(Mat33.translation(delta.times(-1))), false);
}

@@ -387,3 +387,3 @@ }

}
description(localizationTable) {
description(_editor, localizationTable) {
return localizationTable.transformedElements(this.currentTransfmCommands.length);

@@ -541,3 +541,3 @@ }

this.handleOverlay.tabIndex = 0;
this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
}

@@ -544,0 +544,0 @@ else {

import Color4 from '../Color4';
import Text from '../components/Text';
import EditorImage from '../EditorImage';
import Mat33 from '../geometry/Mat33';
import Mat33 from '../math/Mat33';
import { PointerDevice } from '../Pointer';

@@ -6,0 +6,0 @@ import { EditorEventType } from '../types';

import EventDispatcher from './EventDispatcher';
import Mat33 from './geometry/Mat33';
import { Point2, Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import Mat33 from './math/Mat33';
import { Point2, Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import BaseTool from './tools/BaseTool';
import AbstractComponent from './components/AbstractComponent';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import Pointer from './Pointer';

@@ -9,0 +9,0 @@ import Color4 from './Color4';

import Command from './commands/Command';
import { CommandLocalization } from './commands/localization';
import Editor from './Editor';
import Mat33 from './geometry/Mat33';
import Rect2 from './geometry/Rect2';
import { Point2, Vec2 } from './geometry/Vec2';
import Mat33 from './math/Mat33';
import Rect2 from './math/Rect2';
import { Point2, Vec2 } from './math/Vec2';
import { StrokeDataPoint } from './types';
import { EditorNotifier } from './types';
declare type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
export declare abstract class ViewportTransform extends Command {
abstract readonly transform: Mat33;
}
export declare class Viewport {
private notifier;
static ViewportTransform: {
new (transform: Mat33): {
readonly "__#679@#inverseTransform": Mat33;
readonly transform: Mat33;
apply(editor: Editor): void;
unapply(editor: Editor): void;
description(localizationTable: CommandLocalization): string;
onDrop(_editor: Editor): void;
};
union(a: Command, b: Command): Command;
readonly empty: {
description(_localizationTable: import("./localization").EditorLocalization): string;
apply(_editor: Editor): void;
unapply(_editor: Editor): void;
onDrop(_editor: Editor): void;
};
};
private static ViewportTransform;
private transform;

@@ -37,2 +22,3 @@ private inverseTransform;

canvasToScreen(canvasPoint: Point2): Point2;
static transformBy(transform: Mat33): ViewportTransform;
resetTransform(newTransform?: Mat33): void;

@@ -49,5 +35,2 @@ get screenToCanvasTransform(): Mat33;

}
export declare namespace Viewport {
type ViewportTransform = typeof Viewport.ViewportTransform.prototype;
}
export default Viewport;

@@ -14,7 +14,9 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {

import Command from './commands/Command';
import Mat33 from './geometry/Mat33';
import Rect2 from './geometry/Rect2';
import { Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import Mat33 from './math/Mat33';
import Rect2 from './math/Rect2';
import { Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import { EditorEventType } from './types';
export class ViewportTransform extends Command {
}
export class Viewport {

@@ -39,3 +41,6 @@ constructor(notifier) {

}
// Updates the transformation directly. Using ViewportTransform is preferred.
static transformBy(transform) {
return new Viewport.ViewportTransform(transform);
}
// Updates the transformation directly. Using transformBy is preferred.
// [newTransform] should map from canvas coordinates to screen coordinates.

@@ -137,3 +142,3 @@ resetTransform(newTransform = Mat33.identity) {

// Command that translates/scales the viewport.
Viewport.ViewportTransform = (_a = class extends Command {
Viewport.ViewportTransform = (_a = class extends ViewportTransform {
constructor(transform) {

@@ -155,9 +160,9 @@ super();

}
description(localizationTable) {
description(editor, localizationTable) {
const result = [];
// Describe the transformation's affect on the viewport (note that transformation transforms
// the **elements** within the viewport). Assumes the transformation only does rotation/scale/translation.
const origVec = Vec2.unitX;
const origVec = editor.viewport.visibleRect.center;
const linearTransformedVec = this.transform.transformVec3(Vec2.unitX);
const affineTransformedVec = this.transform.transformVec2(Vec2.unitX);
const affineTransformedVec = this.transform.transformVec2(origVec);
const scale = linearTransformedVec.magnitude();

@@ -182,3 +187,3 @@ const rotation = 180 / Math.PI * linearTransformedVec.angle();

}
if (translation.y < minTranslation) {
if (translation.y < -minTranslation) {
result.push(localizationTable.movedDown);

@@ -185,0 +190,0 @@ }

{
"name": "js-draw",
"version": "0.1.11",
"version": "0.1.12",
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",

@@ -5,0 +5,0 @@ "main": "dist/src/Editor.js",

@@ -11,3 +11,3 @@ import Editor from '../Editor';

public abstract description(localizationTable: EditorLocalization): string;
public abstract description(editor: Editor, localizationTable: EditorLocalization): string;

@@ -26,5 +26,5 @@ public static union(a: Command, b: Command): Command {

public description(localizationTable: EditorLocalization) {
const aDescription = a.description(localizationTable);
const bDescription = b.description(localizationTable);
public description(editor: Editor, localizationTable: EditorLocalization) {
const aDescription = a.description(editor, localizationTable);
const bDescription = b.description(editor, localizationTable);

@@ -41,3 +41,3 @@ if (aDescription === bDescription) {

public static readonly empty = new class extends Command {
public description(_localizationTable: EditorLocalization) { return ''; }
public description(_editor: Editor, _localizationTable: EditorLocalization) { return ''; }
public apply(_editor: Editor) { }

@@ -44,0 +44,0 @@ public unapply(_editor: Editor) { }

@@ -27,3 +27,3 @@ import AbstractComponent from '../components/AbstractComponent';

public description(localizationTable: EditorLocalization): string {
public description(_editor: Editor, localizationTable: EditorLocalization): string {
if (this.duplicates.length === 0) {

@@ -30,0 +30,0 @@ return localizationTable.duplicatedNoElements;

@@ -52,3 +52,3 @@ import AbstractComponent from '../components/AbstractComponent';

public description(localizationTable: EditorLocalization): string {
public description(_editor: Editor, localizationTable: EditorLocalization): string {
if (this.toRemove.length === 0) {

@@ -55,0 +55,0 @@ return localizationTable.erasedNoElements;

@@ -1,2 +0,2 @@

import Rect2 from '../geometry/Rect2';
import Rect2 from '../math/Rect2';

@@ -3,0 +3,0 @@ export interface CommandLocalization {

@@ -5,5 +5,5 @@ import Command from '../commands/Command';

import EditorImage from '../EditorImage';
import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import { EditorLocalization } from '../localization';

@@ -132,3 +132,3 @@ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

public description(localizationTable: EditorLocalization) {
public description(_editor: Editor, localizationTable: EditorLocalization) {
return localizationTable.transformedElements(1);

@@ -135,0 +135,0 @@ }

@@ -1,3 +0,3 @@

import { PathCommandType } from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import { PathCommandType } from '../../math/Path';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -4,0 +4,0 @@ import { StrokeDataPoint } from '../../types';

import { Bezier } from 'bezier-js';
import AbstractRenderer, { RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Rect2 from '../../geometry/Rect2';
import { PathCommand, PathCommandType } from '../../geometry/Path';
import LineSegment2 from '../../geometry/LineSegment2';
import { Point2, Vec2 } from '../../math/Vec2';
import Rect2 from '../../math/Rect2';
import { LinePathCommand, PathCommandType, QuadraticBezierPathCommand } from '../../math/Path';
import LineSegment2 from '../../math/LineSegment2';
import Stroke from '../Stroke';

@@ -25,5 +25,31 @@ import Viewport from '../../Viewport';

type CurrentSegmentToPathResult = {
upperCurve: QuadraticBezierPathCommand,
lowerToUpperConnector: LinePathCommand,
upperToLowerConnector: LinePathCommand,
lowerCurve: QuadraticBezierPathCommand,
};
// Handles stroke smoothing and creates Strokes from user/stylus input.
export default class FreehandLineBuilder implements ComponentBuilder {
private segments: RenderablePathSpec[];
private isFirstSegment: boolean = true;
private pathStartConnector: LinePathCommand|null = null;
private mostRecentConnector: LinePathCommand|null = null;
// Beginning of the list of lower parts
// ↓
// /---pathStartConnector---/ ← Beginning of the list of upper parts
// ___/ __/
// / /
// /--Most recent connector--/ ← most recent upper part goes here
// ↑
// most recent lower part goes here
//
// The upperSegments form a path that goes in reverse from the most recent edge to the
// least recent edge.
// The lowerSegments form a path that goes from the least recent edge to the most
// recent edge.
private upperSegments: QuadraticBezierPathCommand[];
private lowerSegments: QuadraticBezierPathCommand[];
private buffer: Point2[];

@@ -51,3 +77,5 @@ private lastPoint: StrokeDataPoint;

this.lastPoint = this.startPoint;
this.segments = [];
this.upperSegments = [];
this.lowerSegments = [];
this.buffer = [this.startPoint.pos];

@@ -70,15 +98,78 @@ this.momentum = Vec2.zero;

// Get the segments that make up this' path. Can be called after calling build()
private getPreview(): RenderablePathSpec[] {
if (this.currentCurve && this.lastPoint) {
const currentPath = this.currentSegmentToPath();
return this.segments.concat(currentPath);
private previewPath(): RenderablePathSpec|null {
let upperPath: QuadraticBezierPathCommand[];
let lowerPath: QuadraticBezierPathCommand[];
let lowerToUpperCap: LinePathCommand;
let pathStartConnector: LinePathCommand;
if (this.currentCurve) {
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
upperPath = this.upperSegments.concat(upperCurve);
lowerPath = this.lowerSegments.concat(lowerCurve);
lowerToUpperCap = lowerToUpperConnector;
pathStartConnector = this.pathStartConnector ?? upperToLowerConnector;
} else {
if (this.mostRecentConnector === null || this.pathStartConnector === null) {
return null;
}
upperPath = this.upperSegments.slice();
lowerPath = this.lowerSegments.slice();
lowerToUpperCap = this.mostRecentConnector;
pathStartConnector = this.pathStartConnector;
}
const startPoint = lowerPath[lowerPath.length - 1].endPoint;
return this.segments;
return {
// Start at the end of the lower curve:
// Start point
// ↓
// __/ __/ ← Most recent points on this end
// /___ /
// ↑
// Oldest points
startPoint,
commands: [
// Move to the most recent point on the upperPath:
// ----→•
// __/ __/
// /___ /
lowerToUpperCap,
// Move to the beginning of the upperPath:
// __/ __/
// /___ /
// • ←-
...upperPath.reverse(),
// Move to the beginning of the lowerPath:
// __/ __/
// /___ /
// •
pathStartConnector,
// Move back to the start point:
// •
// __/ __/
// /___ /
...lowerPath,
],
style: this.getRenderingStyle(),
};
}
private previewStroke(): Stroke|null {
const pathPreview = this.previewPath();
if (pathPreview) {
return new Stroke([ pathPreview ]);
}
return null;
}
public preview(renderer: AbstractRenderer) {
for (const part of this.getPreview()) {
renderer.drawPath(part);
const path = this.previewPath();
if (path) {
renderer.drawPath(path);
}

@@ -91,5 +182,3 @@ }

}
return new Stroke(
this.segments,
);
return this.previewStroke()!;
}

@@ -105,50 +194,68 @@

// Don't create a circle around the initial point if the stroke has more than one point.
if (this.segments.length > 0) {
if (!this.isFirstSegment) {
return;
}
const width = Viewport.roundPoint(this.startPoint.width / 3, this.minFitAllowed);
const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
const center = this.roundPoint(this.startPoint.pos);
// Start on the right, cycle clockwise:
// |
// ----- ←
// |
const startPoint = this.startPoint.pos.plus(Vec2.of(width, 0));
// Draw a circle-ish shape around the start point
this.segments.push({
// Start on the right, cycle clockwise:
// |
// ----- ←
// |
startPoint: this.startPoint.pos.plus(Vec2.of(width, 0)),
commands: [
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, width)),
this.lowerSegments.push(
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, width)),
// Bottom of the circle
// |
// -----
// |
// ↑
endPoint: center.plus(Vec2.of(0, width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, width)),
endPoint: center.plus(Vec2.of(-width, 0)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, -width)),
endPoint: center.plus(Vec2.of(0, -width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, -width)),
endPoint: center.plus(Vec2.of(width, 0)),
},
],
style: this.getRenderingStyle(),
});
// Bottom of the circle
// |
// -----
// |
// ↑
endPoint: center.plus(Vec2.of(0, width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, width)),
endPoint: center.plus(Vec2.of(-width, 0)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(-width, -width)),
endPoint: center.plus(Vec2.of(0, -width)),
},
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: center.plus(Vec2.of(width, -width)),
endPoint: center.plus(Vec2.of(width, 0)),
}
);
this.pathStartConnector = {
kind: PathCommandType.LineTo,
point: startPoint,
};
this.mostRecentConnector = this.pathStartConnector;
return;
}
this.segments.push(this.currentSegmentToPath());
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
if (this.isFirstSegment) {
// We draw the upper path (reversed), then the lower path, so we need the
// upperToLowerConnector to join the two paths.
this.pathStartConnector = upperToLowerConnector;
this.isFirstSegment = false;
}
// With the most recent connector, we're joining the end of the lowerPath to the most recent
// upperPath:
this.mostRecentConnector = lowerToUpperConnector;
this.upperSegments.push(upperCurve);
this.lowerSegments.push(lowerCurve);
const lastPoint = this.buffer[this.buffer.length - 1];

@@ -158,3 +265,3 @@ this.lastExitingVec = Vec2.ofXY(

).minus(Vec2.ofXY(this.currentCurve.points[1]));
console.assert(this.lastExitingVec.magnitude() !== 0);
console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');

@@ -169,3 +276,4 @@ // Use the last two points to start a new curve (the last point isn't used

private currentSegmentToPath(): RenderablePathSpec {
// Returns [upper curve, connector, lower curve]
private currentSegmentToPath(): CurrentSegmentToPathResult {
if (this.currentCurve == null) {

@@ -227,27 +335,29 @@ throw new Error('Invalid State: currentCurve is null!');

// Each starts at startPt ± startVec
const pathCommands: PathCommand[] = [
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
endPoint: this.roundPoint(endPt.plus(endVec)),
},
const lowerCurve: QuadraticBezierPathCommand = {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
endPoint: this.roundPoint(endPt.plus(endVec)),
};
{
kind: PathCommandType.LineTo,
point: this.roundPoint(endPt.minus(endVec)),
},
// From the end of the upperCurve to the start of the lowerCurve:
const upperToLowerConnector: LinePathCommand = {
kind: PathCommandType.LineTo,
point: this.roundPoint(startPt.plus(startVec)),
};
{
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
endPoint: this.roundPoint(startPt.minus(startVec)),
},
];
// From the end of lowerCurve to the start of upperCurve:
const lowerToUpperConnector: LinePathCommand = {
kind: PathCommandType.LineTo,
point: this.roundPoint(endPt.minus(endVec))
};
return {
startPoint: this.roundPoint(startPt.plus(startVec)),
commands: pathCommands,
style: this.getRenderingStyle(),
const upperCurve: QuadraticBezierPathCommand = {
kind: PathCommandType.QuadraticBezierTo,
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
endPoint: this.roundPoint(startPt.minus(startVec)),
};
return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
}

@@ -275,3 +385,3 @@

const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
&& this.segments.length === 0;
&& this.isFirstSegment;

@@ -278,0 +388,0 @@ // Snap to the starting point if the stroke is contained within a small ball centered

@@ -1,3 +0,3 @@

import { PathCommandType } from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import { PathCommandType } from '../../math/Path';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -4,0 +4,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,4 +0,4 @@

import Mat33 from '../../geometry/Mat33';
import Path from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import Mat33 from '../../math/Mat33';
import Path from '../../math/Path';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,2 +0,2 @@

import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { StrokeDataPoint } from '../../types';

@@ -1,10 +0,8 @@

/* @jest-environment jsdom */
import Color4 from '../Color4';
import Path from '../geometry/Path';
import { Vec2 } from '../geometry/Vec2';
import Path from '../math/Path';
import { Vec2 } from '../math/Vec2';
import Stroke from './Stroke';
import { loadExpectExtensions } from '../testing/loadExpectExtensions';
import createEditor from '../testing/createEditor';
import Mat33 from '../geometry/Mat33';
import Mat33 from '../math/Mat33';

@@ -11,0 +9,0 @@ loadExpectExtensions();

@@ -1,5 +0,5 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Path from '../geometry/Path';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Path from '../math/Path';
import Rect2 from '../math/Rect2';
import AbstractRenderer, { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';

@@ -6,0 +6,0 @@ import RenderingStyle, { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import SVGRenderer from '../rendering/renderers/SVGRenderer';

import Color4 from '../Color4';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractComponent from './AbstractComponent';

@@ -5,0 +5,0 @@ import Text, { TextStyle } from './Text';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import RenderingStyle, { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';

@@ -1,4 +0,4 @@

import LineSegment2 from '../geometry/LineSegment2';
import Mat33 from '../geometry/Mat33';
import Rect2 from '../geometry/Rect2';
import LineSegment2 from '../math/LineSegment2';
import Mat33 from '../math/Mat33';
import Rect2 from '../math/Rect2';
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';

@@ -5,0 +5,0 @@ import SVGRenderer from '../rendering/renderers/SVGRenderer';

@@ -9,4 +9,4 @@

import EventDispatcher from './EventDispatcher';
import { Point2, Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import { Point2, Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import HTMLToolbar from './toolbar/HTMLToolbar';

@@ -19,4 +19,4 @@ import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';

import Pointer from './Pointer';
import Mat33 from './geometry/Mat33';
import Rect2 from './geometry/Rect2';
import Mat33 from './math/Mat33';
import Rect2 from './math/Rect2';
import { EditorLocalization } from './localization';

@@ -60,2 +60,3 @@ import getLocalizationTable from './localizations/getLocalizationTable';

private accessibilityAnnounceArea: HTMLElement;
private accessibilityControlArea: HTMLTextAreaElement;

@@ -92,4 +93,11 @@ private settings: EditorSettings;

this.accessibilityControlArea = document.createElement('textarea');
this.accessibilityControlArea.setAttribute('placeholder', this.localization.accessibilityInputInstructions);
this.accessibilityControlArea.style.opacity = '0';
this.accessibilityControlArea.style.width = '0';
this.accessibilityControlArea.style.height = '0';
this.accessibilityControlArea.style.position = 'absolute';
this.accessibilityAnnounceArea = document.createElement('div');
this.accessibilityAnnounceArea.ariaLive = 'assertive';
this.accessibilityAnnounceArea.setAttribute('aria-live', 'assertive');
this.accessibilityAnnounceArea.className = 'accessibilityAnnouncement';

@@ -100,4 +108,5 @@ this.container.appendChild(this.accessibilityAnnounceArea);

this.renderingRegion.className = 'imageEditorRenderArea';
this.renderingRegion.appendChild(this.accessibilityControlArea);
this.renderingRegion.setAttribute('tabIndex', '0');
this.renderingRegion.ariaLabel = this.localization.imageEditor;
this.renderingRegion.setAttribute('alt', '');

@@ -164,4 +173,10 @@ this.notifier = new EventDispatcher();

private previousAccessibilityAnnouncement: string = '';
public announceForAccessibility(message: string) {
// Force re-announcing an announcement if announced again.
if (message === this.previousAccessibilityAnnouncement) {
message = message + '. ';
}
this.accessibilityAnnounceArea.innerText = message;
this.previousAccessibilityAnnouncement = message;
}

@@ -328,2 +343,6 @@

});
this.accessibilityControlArea.addEventListener('input', () => {
this.accessibilityControlArea.value = '';
});
}

@@ -335,3 +354,6 @@

elem.addEventListener('keydown', evt => {
if (this.toolController.dispatchInputEvent({
if (evt.key === 't' || evt.key === 'T') {
evt.preventDefault();
this.display.rerenderAsText();
} else if (this.toolController.dispatchInputEvent({
kind: InputEvtType.KeyPressEvent,

@@ -344,3 +366,3 @@ key: evt.key,

this.renderingRegion.blur();
}
}
});

@@ -368,3 +390,3 @@

this.announceForAccessibility(command.description(this.localization));
this.announceForAccessibility(command.description(this, this.localization));
}

@@ -423,7 +445,7 @@

private announceUndoCallback = (command: Command) => {
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this, this.localization)));
};
private announceRedoCallback = (command: Command) => {
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this.localization)));
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this, this.localization)));
};

@@ -601,3 +623,3 @@

public description(localizationTable: EditorLocalization) {
public description(_editor: Editor, localizationTable: EditorLocalization) {
return localizationTable.resizeOutputCommand(imageRect);

@@ -604,0 +626,0 @@ }

@@ -1,7 +0,5 @@

/* @jest-environment jsdom */
import EditorImage from './EditorImage';
import Stroke from './components/Stroke';
import { Vec2 } from './geometry/Vec2';
import Path, { PathCommandType } from './geometry/Path';
import { Vec2 } from './math/Vec2';
import Path, { PathCommandType } from './math/Path';
import Color4 from './Color4';

@@ -8,0 +6,0 @@ import DummyRenderer from './rendering/renderers/DummyRenderer';

@@ -6,3 +6,3 @@ import Editor from './Editor';

import AbstractComponent from './components/AbstractComponent';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import { EditorLocalization } from './localization';

@@ -112,3 +112,3 @@ import RenderingCache from './rendering/caching/RenderingCache';

public description(localization: EditorLocalization) {
public description(editor: Editor, localization: EditorLocalization) {
return localization.addElementAction(this.element.description(localization));

@@ -115,0 +115,0 @@ }

@@ -9,2 +9,3 @@ import { CommandLocalization, defaultCommandLocalization } from './commands/localization';

export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization, TextRendererLocalization {
accessibilityInputInstructions: string;
undoAnnouncement: (actionDescription: string)=> string;

@@ -23,2 +24,7 @@ redoAnnouncement: (actionDescription: string)=> string;

...defaultTextRendererLocalization,
accessibilityInputInstructions: [
'Press "t" to read the contents of the viewport as text.',
'Use the arrow keys to move the viewport, click and drag to draw strokes.',
'Press "w" to zoom in and "s" to zoom out.',
].join(' '),
loading: (percentage: number) => `Loading ${percentage}%...`,

@@ -25,0 +31,0 @@ imageEditor: 'Image Editor',

@@ -41,3 +41,4 @@ import { defaultEditorLocalization, EditorLocalization } from '../localization';

duplicateSelection: 'Duplica la selección',
pickColorFronScreen: 'Selecciona un color de la pantalla',
pickColorFromScreen: 'Selecciona un color de la pantalla',
clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color',
dropdownShown(toolName: string): string {

@@ -44,0 +45,0 @@ return `Menú por ${toolName} es visible`;

@@ -1,2 +0,2 @@

import { Point2, Vec2 } from './geometry/Vec2';
import { Point2, Vec2 } from './math/Vec2';
import Viewport from './Viewport';

@@ -3,0 +3,0 @@

/* @jest-environment jsdom */
import Rect2 from '../../geometry/Rect2';
import { Vec2 } from '../../geometry/Vec2';
import Rect2 from '../../math/Rect2';
import { Vec2 } from '../../math/Vec2';
import CacheRecord from './CacheRecord';

@@ -6,0 +6,0 @@ import { createCache } from './testUtils';

@@ -1,3 +0,3 @@

import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import AbstractRenderer from '../renderers/AbstractRenderer';

@@ -4,0 +4,0 @@ import { BeforeDeallocCallback, CacheState } from './types';

import { BeforeDeallocCallback, PartialCacheState } from './types';
import CacheRecord from './CacheRecord';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';

@@ -5,0 +5,0 @@

@@ -6,7 +6,7 @@ /* @jest-environment jsdom */

import Stroke from '../../components/Stroke';
import Path from '../../geometry/Path';
import Path from '../../math/Path';
import Color4 from '../../Color4';
import EditorImage from '../../EditorImage';
import Viewport from '../../Viewport';
import Mat33 from '../../geometry/Mat33';
import Mat33 from '../../math/Mat33';

@@ -39,3 +39,3 @@ describe('RenderingCache', () => {

editor.dispatch(new Viewport.ViewportTransform(Mat33.scaling2D(0.1)));
editor.dispatch(Viewport.transformBy(Mat33.scaling2D(0.1)));
editor.image.renderWithCache(screenRenderer, cache, editor.viewport);

@@ -42,0 +42,0 @@ expect(allocdRenderers).toBe(1);

import { ImageNode } from '../../EditorImage';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import Viewport from '../../Viewport';

@@ -4,0 +4,0 @@ import AbstractRenderer from '../renderers/AbstractRenderer';

@@ -6,3 +6,3 @@

import { ImageNode, sortLeavesByZIndex } from '../../EditorImage';
import Rect2 from '../../geometry/Rect2';
import Rect2 from '../../math/Rect2';
import Viewport from '../../Viewport';

@@ -155,8 +155,11 @@ import AbstractRenderer from '../renderers/AbstractRenderer';

private renderingIsUpToDate(sortedIds: number[]) {
if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
// Returns true iff all elems of this.renderedIds are in sortedIds.
// sortedIds should be sorted by z-index (or some other order, so long as they are
// sorted by the same thing as this.renderedIds.)
private allRenderedIdsIn(sortedIds: number[]) {
if (this.renderedIds.length > sortedIds.length) {
return false;
}
for (let i = 0; i < sortedIds.length; i++) {
for (let i = 0; i < this.renderedIds.length; i++) {
if (sortedIds[i] !== this.renderedIds[i]) {

@@ -170,2 +173,10 @@ return false;

private renderingIsUpToDate(sortedIds: number[]) {
if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
return false;
}
return this.allRenderedIdsIn(sortedIds);
}
// Render all [items] within [viewport]

@@ -244,4 +255,3 @@ public renderItems(screenRenderer: AbstractRenderer, items: ImageNode[], viewport: Viewport) {

// Is it worth it to render the items?
// TODO: Replace this with something performace based.
// TODO: Determine whether it is 'worth it' to cache this depending on rendering time.
// TODO: Consider replacing this with something performace based.
if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {

@@ -254,3 +264,3 @@ let fullRerenderNeeded = true;

);
} else if (leavesByIds.length > this.renderedIds.length && this.renderedMaxZIndex !== null) {
} else if (leavesByIds.length > this.renderedIds.length && this.allRenderedIdsIn(leafIds) && this.renderedMaxZIndex !== null) {
// We often don't need to do a full re-render even if something's changed.

@@ -295,2 +305,8 @@ // Check whether we can just draw on top of the existing cache.

}
} else if (debugMode) {
console.log('Decided on a full re-render. Reason: At least one of the following is false:',
'\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length,
'\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds),
'\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null,
'\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
}

@@ -297,0 +313,0 @@

@@ -1,2 +0,2 @@

import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import DummyRenderer from '../renderers/DummyRenderer';

@@ -3,0 +3,0 @@ import createEditor from '../../testing/createEditor';

@@ -1,2 +0,2 @@

import { Vec2 } from '../../geometry/Vec2';
import { Vec2 } from '../../math/Vec2';
import AbstractRenderer from '../renderers/AbstractRenderer';

@@ -3,0 +3,0 @@ import { CacheRecordManager } from './CacheRecordManager';

@@ -6,3 +6,3 @@ import AbstractRenderer from './renderers/AbstractRenderer';

import DummyRenderer from './renderers/DummyRenderer';
import { Point2, Vec2 } from '../geometry/Vec2';
import { Point2, Vec2 } from '../math/Vec2';
import RenderingCache from './caching/RenderingCache';

@@ -22,2 +22,3 @@ import TextOnlyRenderer from './renderers/TextOnlyRenderer';

private textRenderer: TextOnlyRenderer;
private textRerenderOutput: HTMLElement|null = null;
private cache: RenderingCache;

@@ -64,6 +65,6 @@ private resizeSurfacesCallback?: ()=> void;

blockResolution: cacheBlockResolution,
cacheSize: 500 * 500 * 4 * 200,
cacheSize: 500 * 500 * 4 * 220,
maxScale: 1.5,
minComponentsPerCache: 50,
minComponentsToUseCache: 120,
minComponentsPerCache: 45,
minComponentsToUseCache: 105,
});

@@ -164,15 +165,22 @@

const rerenderOutput = document.createElement('div');
rerenderOutput.ariaLive = 'polite';
this.textRerenderOutput = document.createElement('div');
this.textRerenderOutput.setAttribute('aria-live', 'polite');
rerenderButton.onclick = () => {
this.textRenderer.clear();
this.editor.image.render(this.textRenderer, this.editor.viewport);
rerenderOutput.innerText = this.textRenderer.getDescription();
this.rerenderAsText();
};
textRendererOutputContainer.replaceChildren(rerenderButton, rerenderOutput);
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
this.editor.createHTMLOverlay(textRendererOutputContainer);
}
public rerenderAsText() {
this.textRenderer.clear();
this.editor.image.render(this.textRenderer, this.editor.viewport);
if (this.textRerenderOutput) {
this.textRerenderOutput.innerText = this.textRenderer.getDescription();
}
}
// Clears the drawing surfaces and otherwise prepares for a rerender.

@@ -179,0 +187,0 @@ public startRerender(): AbstractRenderer {

export interface TextRendererLocalization {
pathNodeCount(pathCount: number): string;
textNodeCount(nodeCount: number): string;
textNode(content: string): string;

@@ -8,4 +10,6 @@ rerenderAsText: string;

export const defaultTextRendererLocalization: TextRendererLocalization = {
pathNodeCount: (count: number) => `There are ${count} visible path objects.`,
textNodeCount: (count: number) => `There are ${count} visible text nodes.`,
textNode: (content: string) => `Text: ${content}`,
rerenderAsText: 'Re-render as text',
};
import { LoadSaveDataTable } from '../../components/AbstractComponent';
import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import Path, { PathCommand, PathCommandType } from '../../math/Path';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Viewport from '../../Viewport';

@@ -8,0 +8,0 @@ import RenderingStyle, { stylesEqual } from '../RenderingStyle';

import Color4 from '../../Color4';
import Text, { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -8,0 +8,0 @@ import RenderingStyle from '../RenderingStyle';

import EventDispatcher from '../../EventDispatcher';
import Mat33 from '../../geometry/Mat33';
import { Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import { Vec2 } from '../../math/Vec2';
import Viewport from '../../Viewport';

@@ -6,0 +6,0 @@ import DummyRenderer from './DummyRenderer';

// Renderer that outputs nothing. Useful for automated tests.
import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Point2, Vec2 } from '../../math/Vec2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -9,0 +9,0 @@ import RenderingStyle from '../RenderingStyle';

import { LoadSaveDataTable } from '../../components/AbstractComponent';
import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
import Rect2 from '../../geometry/Rect2';
import { Point2, Vec2 } from '../../geometry/Vec2';
import Mat33 from '../../math/Mat33';
import Path, { PathCommand, PathCommandType } from '../../math/Path';
import Rect2 from '../../math/Rect2';
import { toRoundedString } from '../../math/rounding';
import { Point2, Vec2 } from '../../math/Vec2';
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';

@@ -116,2 +117,5 @@ import Viewport from '../../Viewport';

const translation = transform.transformVec2(Vec2.zero);
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
const textElem = document.createElementNS(svgNameSpace, 'text');

@@ -129,2 +133,4 @@ textElem.appendChild(document.createTextNode(text));

textElem.style.fill = style.renderingStyle.fill.toHexString();
textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
textElem.setAttribute('y', `${toRoundedString(translation.y)}`);

@@ -131,0 +137,0 @@ if (style.renderingStyle.stroke) {

import { TextStyle } from '../../components/Text';
import Mat33 from '../../geometry/Mat33';
import Rect2 from '../../geometry/Rect2';
import { Vec2 } from '../../geometry/Vec2';
import Vec3 from '../../geometry/Vec3';
import Mat33 from '../../math/Mat33';
import Rect2 from '../../math/Rect2';
import { Vec2 } from '../../math/Vec2';
import Vec3 from '../../math/Vec3';
import Viewport from '../../Viewport';

@@ -15,2 +15,5 @@ import { TextRendererLocalization } from '../localization';

private descriptionBuilder: string[] = [];
private pathCount: number = 0;
private textNodeCount: number = 0;
public constructor(viewport: Viewport, private localizationTable: TextRendererLocalization) {

@@ -27,6 +30,12 @@ super(viewport);

this.descriptionBuilder = [];
this.pathCount = 0;
this.textNodeCount = 0;
}
public getDescription(): string {
return this.descriptionBuilder.join('\n');
return [
this.localizationTable.pathNodeCount(this.pathCount),
this.localizationTable.textNodeCount(this.textNodeCount),
...this.descriptionBuilder
].join('\n');
}

@@ -37,2 +46,3 @@

protected endPath(_style: RenderingStyle): void {
this.pathCount ++;
}

@@ -49,5 +59,6 @@ protected lineTo(_point: Vec3): void {

this.descriptionBuilder.push(this.localizationTable.textNode(text));
this.textNodeCount ++;
}
public isTooSmallToRender(rect: Rect2): boolean {
return rect.maxDimension < 10 / this.getSizeOfCanvasPixelOnScreen();
return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
}

@@ -54,0 +65,0 @@ public drawPoints(..._points: Vec3[]): void {

@@ -7,6 +7,6 @@ import Color4 from './Color4';

import UnknownSVGObject from './components/UnknownSVGObject';
import Mat33 from './geometry/Mat33';
import Path from './geometry/Path';
import Rect2 from './geometry/Rect2';
import { Vec2 } from './geometry/Vec2';
import Mat33 from './math/Mat33';
import Path from './math/Path';
import Rect2 from './math/Rect2';
import { Vec2 } from './math/Vec2';
import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';

@@ -214,4 +214,12 @@ import RenderingStyle from './rendering/RenderingStyle';

// Compute transform matrix
let transform = Mat33.fromCSSMatrix(transformProperty);
// Compute transform matrix. Prefer the actual .style.transform
// to the computed stylesheet -- in some browsers, the computedStyles version
// can have lower precision.
let transform;
try {
transform = Mat33.fromCSSMatrix(elem.style.transform);
} catch(_e) {
transform = Mat33.fromCSSMatrix(transformProperty);
}
const supportedAttrs = [];

@@ -218,0 +226,0 @@ const elemX = elem.getAttribute('x');

@@ -5,3 +5,3 @@ import Color4 from '../Color4';

import EventDispatcher from '../EventDispatcher';
import { Vec2 } from '../geometry/Vec2';
import { Vec2 } from '../math/Vec2';
import SVGRenderer from '../rendering/renderers/SVGRenderer';

@@ -107,148 +107,137 @@ import Pen from '../tools/Pen';

export const makeHandToolIcon = () => {
const pathIcon = (
pathData: string,
fill: string = 'var(--icon-color)',
strokeColor: string = 'none',
strokeWidth: string = '0px',
) => {
const icon = document.createElementNS(svgNamespace, 'svg');
const path = document.createElementNS(svgNamespace, 'path');
path.setAttribute('d', pathData);
path.style.fill = fill;
path.style.stroke = strokeColor;
path.style.strokeWidth = strokeWidth;
icon.appendChild(path);
icon.setAttribute('viewBox', '0 0 100 100');
// Draw a cursor-like shape (like some of the other icons, made with Inkscape)
icon.innerHTML = `
<g>
<path d='
m 10,60
5,30
H 90
V 30
C 90,20 75,20 75,30
V 60
20
C 75,10 60,10 60,20
V 60
15
C 60,5 45,5 45,15
V 60
25
C 45,15 30,15 30,25
V 60
75
L 25,60
C 20,45 10,50 10,60
Z'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
</g>
`;
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
};
export const makeHandToolIcon = () => {
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
// Draw a cursor-like shape (like some of the other icons, made with Inkscape)
return pathIcon(`
m 10,60
5,30
H 90
V 30
C 90,20 75,20 75,30
V 60
20
C 75,10 60,10 60,20
V 60
15
C 60,5 45,5 45,15
V 60
25
C 45,15 30,15 30,25
V 60
75
L 25,60
C 20,45 10,50 10,60
Z
`, fill, strokeColor, strokeWidth);
};
export const makeTouchPanningIcon = () => {
const icon = document.createElementNS(svgNamespace, 'svg');
icon.innerHTML = `
<path
d='
M 5,5.5
V 17.2
L 16.25,5.46
Z
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
m 33.75,0
L 50,17
V 5.5
Z
return pathIcon(`
M 5,5.5
V 17.2
L 16.25,5.46
Z
M 5,40.7
v 11.7
h 11.25
z
M 26,19
C 19.8,19.4 17.65,30.4 21.9,34.8
L 50,70
H 27.5
c -11.25,0 -11.25,17.6 0,17.6
H 61.25
C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
L 33.1,23
C 30.3125,20.128192 27.9,19 25.830078,19.119756
Z
'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
`;
m 33.75,0
L 50,17
V 5.5
Z
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
M 5,40.7
v 11.7
h 11.25
z
M 26,19
C 19.8,19.4 17.65,30.4 21.9,34.8
L 50,70
H 27.5
c -11.25,0 -11.25,17.6 0,17.6
H 61.25
C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
L 33.1,23
C 30.3125,20.128192 27.9,19 25.830078,19.119756
Z
`, fill, strokeColor, strokeWidth);
};
export const makeAllDevicePanningIcon = () => {
const icon = document.createElementNS(svgNamespace, 'svg');
icon.innerHTML = `
<path
d='
M 5 5
L 5 17.5
17.5 5
5 5
z
M 42.5 5
L 55 17.5
55 5
42.5 5
z
M 70 10
L 70 21
61 15
55.5 23
66 30
56 37
61 45
70 39
70 50
80 50
80 39
89 45
95 36
84 30
95 23
89 15
80 21
80 10
70 10
z
const fill = 'none';
const strokeColor = 'var(--icon-color)';
const strokeWidth = '3';
return pathIcon(`
M 5 5
L 5 17.5
17.5 5
5 5
z
M 27.5 26.25
L 27.5 91.25
L 43.75 83.125
L 52 99
L 68 91
L 60 75
L 76.25 66.875
L 27.5 26.25
z
M 5 42.5
L 5 55
L 17.5 55
L 5 42.5
z
'
fill='none'
style='
stroke: var(--icon-color);
stroke-width: 2;
'
/>
`;
M 42.5 5
L 55 17.5
55 5
42.5 5
z
icon.setAttribute('viewBox', '0 0 100 100');
return icon;
M 70 10
L 70 21
61 15
55.5 23
66 30
56 37
61 45
70 39
70 50
80 50
80 39
89 45
95 36
84 30
95 23
89 15
80 21
80 10
70 10
z
M 27.5 26.25
L 27.5 91.25
L 43.75 83.125
L 52 99
L 68 91
L 60 75
L 76.25 66.875
L 27.5 26.25
z
M 5 42.5
L 5 55
L 17.5 55
L 5 42.5
z
`, fill, strokeColor, strokeWidth);
};

@@ -422,1 +411,32 @@

};
export const makeResizeViewportIcon = () => {
return pathIcon(`
M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
M 15 15 15 30 20 30 20 20 30 20 30 15 15 15 z
M 84 15 82 17 81 16 81 20 85 20 84 19 86 17 84 15 z
M 26 24 24 26 26 28 25 29 29 29 29 25 28 26 26 24 z
M 25 71 26 72 24 74 26 76 28 74 29 75 29 71 25 71 z
M 15 75 15 85 25 85 25 80 20 80 20 75 15 75 z
M 90 75 90 90 75 90 75 95 95 95 95 75 90 75 z
M 81 81 81 85 82 84 84 86 86 84 84 82 85 81 81 81 z
`);
};
export const makeDuplicateSelectionIcon = () => {
return pathIcon(`
M 45,10 45,55 90,55 90,10 45,10 z
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
`);
};
export const makeDeleteSelectionIcon = () => {
const strokeWidth = '5px';
const strokeColor = 'var(--icon-color)';
const fillColor = 'none';
return pathIcon(`
M 10,10 90,90
M 10,90 90,10
`, fillColor, strokeColor, strokeWidth);
};

@@ -22,3 +22,4 @@

duplicateSelection: string;
pickColorFronScreen: string;
pickColorFromScreen: string;
clickToPickColorAnnouncement: string;
undo: string;

@@ -52,3 +53,4 @@ redo: string;

selectObjectType: 'Object type: ',
pickColorFronScreen: 'Pick color from screen',
pickColorFromScreen: 'Pick color from screen',
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',

@@ -55,0 +57,0 @@

@@ -72,3 +72,3 @@ import Color4 from '../Color4';

pipetteButton.classList.add('pipetteButton');
pipetteButton.title = editor.localization.pickColorFronScreen;
pipetteButton.title = editor.localization.pickColorFromScreen;
pipetteButton.setAttribute('alt', pipetteButton.title);

@@ -115,2 +115,3 @@

pipetteButton.classList.add('active');
editor.announceForAccessibility(editor.localization.clickToPickColorAnnouncement);
}

@@ -117,0 +118,0 @@ };

@@ -140,4 +140,6 @@ import Editor from '../../Editor';

this.button.classList.add('disabled');
this.button.setAttribute('aria-disabled', 'true');
} else {
this.button.classList.remove('disabled');
this.button.removeAttribute('aria-disabled');
}

@@ -144,0 +146,0 @@ }

import Editor from '../../Editor';
import Mat33 from '../../geometry/Mat33';
import Mat33 from '../../math/Mat33';
import PanZoom, { PanZoomMode } from '../../tools/PanZoom';

@@ -56,3 +56,3 @@ import { EditorEventType } from '../../types';

const transformUpdate = Mat33.scaling2D(factor, screenCenter);
editor.dispatch(new Viewport.ViewportTransform(transformUpdate), false);
editor.dispatch(Viewport.transformBy(transformUpdate), false);
};

@@ -69,3 +69,3 @@

resetViewButton.onclick = () => {
editor.dispatch(new Viewport.ViewportTransform(
editor.dispatch(Viewport.transformBy(
editor.viewport.canvasToScreenTransform.inverse()

@@ -72,0 +72,0 @@ ), true);

import Editor from '../../Editor';
import SelectionTool from '../../tools/SelectionTool';
import { EditorEventType } from '../../types';
import { makeSelectionIcon } from '../icons';
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
import { ToolbarLocalization } from '../localization';
import ActionButtonWidget from './ActionButtonWidget';
import BaseToolWidget from './BaseToolWidget';

@@ -13,41 +14,43 @@

super(editor, tool, localization);
}
protected getTitle(): string {
return this.localizationTable.select;
}
const resizeButton = new ActionButtonWidget(
editor, localization,
makeResizeViewportIcon,
this.localizationTable.resizeImageToSelection,
() => {
const selection = this.tool.getSelection();
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
},
);
const deleteButton = new ActionButtonWidget(
editor, localization,
makeDeleteSelectionIcon,
this.localizationTable.deleteSelection,
() => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection!.deleteSelectedObjects());
this.tool.clearSelection();
},
);
const duplicateButton = new ActionButtonWidget(
editor, localization,
makeDuplicateSelectionIcon,
this.localizationTable.duplicateSelection,
() => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection!.duplicateSelectedObjects());
},
);
protected createIcon(): Element {
return makeSelectionIcon();
}
this.addSubWidget(resizeButton);
this.addSubWidget(deleteButton);
this.addSubWidget(duplicateButton);
protected fillDropdown(dropdown: HTMLElement): boolean {
const container = document.createElement('div');
const resizeButton = document.createElement('button');
const duplicateButton = document.createElement('button');
const deleteButton = document.createElement('button');
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
resizeButton.disabled = true;
deleteButton.innerText = this.localizationTable.deleteSelection;
deleteButton.disabled = true;
duplicateButton.innerText = this.localizationTable.duplicateSelection;
duplicateButton.disabled = true;
resizeButton.onclick = () => {
const selection = this.tool.getSelection();
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
const updateDisabled = (disabled: boolean) => {
resizeButton.setDisabled(disabled);
deleteButton.setDisabled(disabled);
duplicateButton.setDisabled(disabled);
};
updateDisabled(true);
deleteButton.onclick = () => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection!.deleteSelectedObjects());
this.tool.clearSelection();
};
duplicateButton.onclick = () => {
const selection = this.tool.getSelection();
this.editor.dispatch(selection!.duplicateSelectedObjects());
};
// Enable/disable actions based on whether items are selected

@@ -63,12 +66,14 @@ this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {

resizeButton.disabled = !hasSelection;
deleteButton.disabled = resizeButton.disabled;
duplicateButton.disabled = resizeButton.disabled;
updateDisabled(!hasSelection);
}
});
}
container.replaceChildren(resizeButton, duplicateButton, deleteButton);
dropdown.appendChild(container);
return true;
protected getTitle(): string {
return this.localizationTable.select;
}
protected createIcon(): Element {
return makeSelectionIcon();
}
}
import { PointerEvt } from '../types';
import BaseTool from './BaseTool';
import Editor from '../Editor';
import { Point2 } from '../geometry/Vec2';
import LineSegment2 from '../geometry/LineSegment2';
import { Point2 } from '../math/Vec2';
import LineSegment2 from '../math/LineSegment2';
import Erase from '../commands/Erase';

@@ -7,0 +7,0 @@ import { ToolType } from './ToolController';

import { Editor } from '../Editor';
import Mat33 from '../geometry/Mat33';
import { Point2, Vec2 } from '../geometry/Vec2';
import Vec3 from '../geometry/Vec3';
import Mat33 from '../math/Mat33';
import { Point2, Vec2 } from '../math/Vec2';
import Vec3 from '../math/Vec3';
import Pointer, { PointerDevice } from '../Pointer';
import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
import { Viewport } from '../Viewport';
import { Viewport, ViewportTransform } from '../Viewport';
import BaseTool from './BaseTool';

@@ -30,3 +30,3 @@ import { ToolType } from './ToolController';

public readonly kind: ToolType.PanZoom = ToolType.PanZoom;
private transform: Viewport.ViewportTransform|null = null;
private transform: ViewportTransform|null = null;

@@ -78,3 +78,3 @@ private lastAngle: number;

if (handlingGesture) {
this.transform ??= new Viewport.ViewportTransform(Mat33.identity);
this.transform ??= Viewport.transformBy(Mat33.identity);
this.editor.display.setDraftMode(true);

@@ -105,3 +105,3 @@ }

this.lastAngle = angle;
this.transform = new Viewport.ViewportTransform(
this.transform = Viewport.transformBy(
this.transform!.transform.rightMul(transformUpdate)

@@ -113,3 +113,3 @@ );

const delta = this.getCenterDelta(pointer.screenPos);
this.transform = new Viewport.ViewportTransform(
this.transform = Viewport.transformBy(
this.transform!.transform.rightMul(

@@ -123,3 +123,3 @@ Mat33.translation(delta)

public onPointerMove({ allPointers }: PointerEvt): void {
this.transform ??= new Viewport.ViewportTransform(Mat33.identity);
this.transform ??= Viewport.transformBy(Mat33.identity);

@@ -154,3 +154,3 @@ const lastTransform = this.transform;

// current transformation, if it exists.
private updateTransform(transformUpdate: Mat33) {
private updateTransform(transformUpdate: Mat33, announce: boolean = false) {
let newTransform = transformUpdate;

@@ -162,4 +162,8 @@ if (this.transform) {

this.transform?.unapply(this.editor);
this.transform = new Viewport.ViewportTransform(newTransform);
this.transform = Viewport.transformBy(newTransform);
this.transform.apply(this.editor);
if (announce) {
this.editor.announceForAccessibility(this.transform.description(this.editor, this.editor.localization));
}
}

@@ -170,3 +174,3 @@

// need to unapply/reapply.
this.transform = new Viewport.ViewportTransform(Mat33.identity);
this.transform = Viewport.transformBy(Mat33.identity);

@@ -187,3 +191,3 @@ const canvasPos = this.editor.viewport.screenToCanvas(screenPos);

);
this.updateTransform(transformUpdate);
this.updateTransform(transformUpdate, true);

@@ -199,3 +203,3 @@ return true;

// No need to keep the same the transform for keyboard events.
this.transform = new Viewport.ViewportTransform(Mat33.identity);
this.transform = Viewport.transformBy(Mat33.identity);

@@ -218,2 +222,3 @@ let translation = Vec2.zero;

break;
case 'q':
case 'k':

@@ -223,2 +228,3 @@ case 'ArrowUp':

break;
case 'e':
case 'j':

@@ -246,3 +252,3 @@ case 'ArrowDown':

translation = translation.times(30); // Move at most 30 units
rotation *= Math.PI / 8; // Rotate at most a sixteenth of a rotation
rotation *= Math.PI / 8; // Rotate at least a sixteenth of a rotation

@@ -254,2 +260,8 @@ // Transform the canvas, not the viewport:

// Work around an issue that seems to be related to rotation matricies losing precision on inversion.
// TODO: Figure out why and implement a better solution.
if (rotation !== 0) {
rotation += 0.0001;
}
const toCanvas = this.editor.viewport.screenToCanvasTransform;

@@ -270,3 +282,3 @@

));
this.updateTransform(transformUpdate);
this.updateTransform(transformUpdate, true);

@@ -273,0 +285,0 @@ return true;

@@ -1,3 +0,1 @@

/* @jest-environment jsdom */
import Color4 from '../Color4';

@@ -7,4 +5,4 @@ import Stroke from '../components/Stroke';

import EditorImage from '../EditorImage';
import Path from '../geometry/Path';
import { Vec2 } from '../geometry/Vec2';
import Path from '../math/Path';
import { Vec2 } from '../math/Vec2';
import { InputEvtType } from '../types';

@@ -11,0 +9,0 @@ import SelectionTool from './SelectionTool';

@@ -6,6 +6,6 @@ import Command from '../commands/Command';

import Editor from '../Editor';
import Mat33 from '../geometry/Mat33';
import Mat33 from '../math/Mat33';
// import Mat33 from "../geometry/Mat33";
import Rect2 from '../geometry/Rect2';
import { Point2, Vec2 } from '../geometry/Vec2';
import Rect2 from '../math/Rect2';
import { Point2, Vec2 } from '../math/Vec2';
import { EditorLocalization } from '../localization';

@@ -314,3 +314,3 @@ import { EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';

public description(localizationTable: EditorLocalization) {
public description(_editor: Editor, localizationTable: EditorLocalization) {
return localizationTable.transformedElements(this.currentTransfmCommands.length);

@@ -465,3 +465,3 @@ }

this.editor.dispatchNoAnnounce(
new Viewport.ViewportTransform(Mat33.translation(delta.times(-1))), false
Viewport.transformBy(Mat33.translation(delta.times(-1))), false
);

@@ -677,3 +677,3 @@ }

this.handleOverlay.tabIndex = 0;
this.handleOverlay.ariaLabel = this.editor.localization.selectionToolKeyboardShortcuts;
this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
} else {

@@ -680,0 +680,0 @@ this.handleOverlay.tabIndex = -1;

@@ -5,4 +5,4 @@ import Color4 from '../Color4';

import EditorImage from '../EditorImage';
import Mat33 from '../geometry/Mat33';
import { Vec2 } from '../geometry/Vec2';
import Mat33 from '../math/Mat33';
import { Vec2 } from '../math/Vec2';
import { PointerDevice } from '../Pointer';

@@ -9,0 +9,0 @@ import { EditorEventType, PointerEvt } from '../types';

@@ -6,3 +6,3 @@ /* @jest-environment jsdom */

import EditorImage from '../EditorImage';
import Path from '../geometry/Path';
import Path from '../math/Path';
import createEditor from '../testing/createEditor';

@@ -9,0 +9,0 @@ import { InputEvtType } from '../types';

// Types related to the image editor
import EventDispatcher from './EventDispatcher';
import Mat33 from './geometry/Mat33';
import { Point2, Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import Mat33 from './math/Mat33';
import { Point2, Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import BaseTool from './tools/BaseTool';
import AbstractComponent from './components/AbstractComponent';
import Rect2 from './geometry/Rect2';
import Rect2 from './math/Rect2';
import Pointer from './Pointer';

@@ -11,0 +11,0 @@ import Color4 from './Color4';

import Command from './commands/Command';
import { CommandLocalization } from './commands/localization';
import Editor from './Editor';
import Mat33 from './geometry/Mat33';
import Rect2 from './geometry/Rect2';
import { Point2, Vec2 } from './geometry/Vec2';
import Vec3 from './geometry/Vec3';
import Mat33 from './math/Mat33';
import Rect2 from './math/Rect2';
import { Point2, Vec2 } from './math/Vec2';
import Vec3 from './math/Vec3';
import { StrokeDataPoint } from './types';

@@ -14,5 +14,9 @@ import { EditorEventType, EditorNotifier } from './types';

export abstract class ViewportTransform extends Command {
public abstract readonly transform: Mat33;
}
export class Viewport {
// Command that translates/scales the viewport.
public static ViewportTransform = class extends Command {
private static ViewportTransform = class extends ViewportTransform {
readonly #inverseTransform: Mat33;

@@ -37,3 +41,3 @@

public description(localizationTable: CommandLocalization): string {
public description(editor: Editor, localizationTable: CommandLocalization): string {
const result: string[] = [];

@@ -43,5 +47,5 @@

// the **elements** within the viewport). Assumes the transformation only does rotation/scale/translation.
const origVec = Vec2.unitX;
const origVec = editor.viewport.visibleRect.center;
const linearTransformedVec = this.transform.transformVec3(Vec2.unitX);
const affineTransformedVec = this.transform.transformVec2(Vec2.unitX);
const affineTransformedVec = this.transform.transformVec2(origVec);

@@ -54,4 +58,3 @@ const scale = linearTransformedVec.magnitude();

result.push(localizationTable.zoomedIn);
}
else if (scale < 0.8) {
} else if (scale < 0.8) {
result.push(localizationTable.zoomedOut);

@@ -71,3 +74,3 @@ }

if (translation.y < minTranslation) {
if (translation.y < -minTranslation) {
result.push(localizationTable.movedDown);

@@ -108,3 +111,7 @@ } else if (translation.y > minTranslation) {

// Updates the transformation directly. Using ViewportTransform is preferred.
public static transformBy(transform: Mat33): ViewportTransform {
return new Viewport.ViewportTransform(transform);
}
// Updates the transformation directly. Using transformBy is preferred.
// [newTransform] should map from canvas coordinates to screen coordinates.

@@ -240,7 +247,2 @@ public resetTransform(newTransform: Mat33 = Mat33.identity) {

export namespace Viewport { // eslint-disable-line
// Needed to allow accessing as a type. See https://stackoverflow.com/a/68201883
export type ViewportTransform = typeof Viewport.ViewportTransform.prototype;
}
export default Viewport;

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc