js-draw 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -197,7 +197,9 @@ export declare class Editor {
197
197
  addStyleSheet(content: string): HTMLStyleElement;
198
198
  sendKeyboardEvent(eventType: InputEvtType.KeyPressEvent | InputEvtType.KeyUpEvent, key: string, ctrlKey?: boolean, altKey?: boolean): void;
199
199
  sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
200
+ toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
200
201
  toSVG(): SVGElement;
202
+ private renderAllWithTransform;
201
203
  loadFrom(loader: ImageLoader): Promise<void>;
202
204
  getImportExportRect(): Rect2;
203
205
  setImportExportRect(imageRect: Rect2): Command;
@@ -43,6 +43,7 @@ import Mat33 from './math/Mat33';
43
43
  import getLocalizationTable from './localizations/getLocalizationTable';
44
44
  import IconProvider from './toolbar/IconProvider';
45
45
  import { toRoundedString } from './math/rounding';
46
+ import CanvasRenderer from './rendering/renderers/CanvasRenderer';
46
47
  // { @inheritDoc Editor! }
47
48
  export class Editor {
48
49
  /**
@@ -626,17 +627,29 @@ export class Editor {
626
627
  current: mainPointer,
627
628
  });
628
629
  }
630
+ // Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
631
+ // If `format` is not `image/png`, a PNG image URL may still be returned (as in the
632
+ // case of `HTMLCanvasElement::toDataURL`).
633
+ //
634
+ // The export resolution is the same as the size of the drawing canvas.
635
+ toDataURL(format = 'image/png') {
636
+ const canvas = document.createElement('canvas');
637
+ const resolution = this.importExportViewport.getResolution();
638
+ canvas.width = resolution.x;
639
+ canvas.height = resolution.y;
640
+ const ctx = canvas.getContext('2d');
641
+ const renderer = new CanvasRenderer(ctx, this.importExportViewport);
642
+ // Render everything with no transform (0,0) should be (0,0) in the output image
643
+ this.renderAllWithTransform(renderer, this.importExportViewport, Mat33.identity);
644
+ const dataURL = canvas.toDataURL(format);
645
+ return dataURL;
646
+ }
629
647
  toSVG() {
630
648
  const importExportViewport = this.importExportViewport;
631
649
  const svgNameSpace = 'http://www.w3.org/2000/svg';
632
650
  const result = document.createElementNS(svgNameSpace, 'svg');
633
651
  const renderer = new SVGRenderer(result, importExportViewport);
634
- const origTransform = importExportViewport.canvasToScreenTransform;
635
- // Reset the transform to ensure that (0, 0) is (0, 0)
636
- importExportViewport.resetTransform(Mat33.identity);
637
- // Render **all** elements.
638
- this.image.renderAll(renderer);
639
- importExportViewport.resetTransform(origTransform);
652
+ this.renderAllWithTransform(renderer, importExportViewport);
640
653
  // Just show the main region
641
654
  const rect = importExportViewport.visibleRect;
642
655
  result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
@@ -649,6 +662,18 @@ export class Editor {
649
662
  result.setAttribute('xmlns', svgNameSpace);
650
663
  return result;
651
664
  }
665
+ // Renders everything in this' image to `renderer`, but first transforming the given `viewport`
666
+ // such that its transform is `transform`. The given `viewport`'s transform is restored before this method
667
+ // returns.
668
+ //
669
+ // For example, rendering with `transform = Mat33.identity` *sets* `viewport`'s transform to `Mat33.identity`,
670
+ // renders everything in this' image to `renderer`, then restores `viewport`'s transform to whatever it was before.
671
+ renderAllWithTransform(renderer, viewport, transform = Mat33.identity) {
672
+ const origTransform = this.importExportViewport.canvasToScreenTransform;
673
+ viewport.resetTransform(transform);
674
+ this.image.renderAll(renderer);
675
+ viewport.resetTransform(origTransform);
676
+ }
652
677
  loadFrom(loader) {
653
678
  return __awaiter(this, void 0, void 0, function* () {
654
679
  this.showLoadingWarning(0);
@@ -103,8 +103,8 @@ export class Viewport {
103
103
  }
104
104
  // Represent as k 10ⁿ for some n, k ∈ ℤ.
105
105
  const decimalComponent = Math.pow(10, Math.floor(Math.log10(Math.abs(scaleRatio))));
106
- const roundAnountFactor = Math.pow(2, roundAmount);
107
- scaleRatio = Math.round(scaleRatio / decimalComponent * roundAnountFactor) / roundAnountFactor * decimalComponent;
106
+ const roundAmountFactor = Math.pow(2, roundAmount);
107
+ scaleRatio = Math.round(scaleRatio / decimalComponent * roundAmountFactor) / roundAmountFactor * decimalComponent;
108
108
  return scaleRatio;
109
109
  }
110
110
  // Computes and returns an affine transformation that makes `toMakeVisible` visible and roughly centered on the screen.
@@ -37,6 +37,10 @@ export default class LineBuilder {
37
37
  kind: PathCommandType.LineTo,
38
38
  point: endPoint.minus(scaledEndNormal),
39
39
  },
40
+ {
41
+ kind: PathCommandType.LineTo,
42
+ point: startPoint.minus(scaledStartNormal),
43
+ },
40
44
  ],
41
45
  style: {
42
46
  fill: this.startPoint.color,
@@ -167,7 +167,7 @@ export class StrokeSmoother {
167
167
  if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
168
168
  // Position the control point closer to the first -- the connecting
169
169
  // segment will be roughly a line.
170
- controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 3));
170
+ controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
171
171
  }
172
172
  console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
173
173
  console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
@@ -79,7 +79,12 @@ export default class SVGRenderer extends AbstractRenderer {
79
79
  const pathElem = document.createElementNS(svgNameSpace, 'path');
80
80
  pathElem.setAttribute('d', this.lastPathString.join(' '));
81
81
  const style = this.lastPathStyle;
82
- pathElem.setAttribute('fill', style.fill.toHexString());
82
+ if (style.fill.a > 0) {
83
+ pathElem.setAttribute('fill', style.fill.toHexString());
84
+ }
85
+ else {
86
+ pathElem.setAttribute('fill', 'none');
87
+ }
83
88
  if (style.stroke) {
84
89
  pathElem.setAttribute('stroke', style.stroke.color.toHexString());
85
90
  pathElem.setAttribute('stroke-width', style.stroke.width.toString());
@@ -287,7 +287,7 @@ export default class Selection {
287
287
  }
288
288
  setSelectedObjects(objects, bbox) {
289
289
  this.originalRegion = bbox;
290
- this.selectedElems = objects;
290
+ this.selectedElems = objects.filter(object => object.isSelectable());
291
291
  this.updateUI();
292
292
  }
293
293
  getSelectedObjects() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
package/src/Editor.ts CHANGED
@@ -27,7 +27,7 @@ import EventDispatcher from './EventDispatcher';
27
27
  import { Point2, Vec2 } from './math/Vec2';
28
28
  import Vec3 from './math/Vec3';
29
29
  import HTMLToolbar from './toolbar/HTMLToolbar';
30
- import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
30
+ import AbstractRenderer, { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
31
31
  import Display, { RenderingMode } from './rendering/Display';
32
32
  import SVGRenderer from './rendering/renderers/SVGRenderer';
33
33
  import Color4 from './Color4';
@@ -39,6 +39,7 @@ import { EditorLocalization } from './localization';
39
39
  import getLocalizationTable from './localizations/getLocalizationTable';
40
40
  import IconProvider from './toolbar/IconProvider';
41
41
  import { toRoundedString } from './math/rounding';
42
+ import CanvasRenderer from './rendering/renderers/CanvasRenderer';
42
43
 
43
44
  type HTMLPointerEventType = 'pointerdown'|'pointermove'|'pointerup'|'pointercancel';
44
45
  type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent)=>boolean;
@@ -823,20 +824,36 @@ export class Editor {
823
824
  });
824
825
  }
825
826
 
827
+ // Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
828
+ // If `format` is not `image/png`, a PNG image URL may still be returned (as in the
829
+ // case of `HTMLCanvasElement::toDataURL`).
830
+ //
831
+ // The export resolution is the same as the size of the drawing canvas.
832
+ public toDataURL(format: 'image/png'|'image/jpeg'|'image/webp' = 'image/png'): string {
833
+ const canvas = document.createElement('canvas');
834
+
835
+ const resolution = this.importExportViewport.getResolution();
836
+
837
+ canvas.width = resolution.x;
838
+ canvas.height = resolution.y;
839
+
840
+ const ctx = canvas.getContext('2d')!;
841
+ const renderer = new CanvasRenderer(ctx, this.importExportViewport);
842
+
843
+ // Render everything with no transform (0,0) should be (0,0) in the output image
844
+ this.renderAllWithTransform(renderer, this.importExportViewport, Mat33.identity);
845
+
846
+ const dataURL = canvas.toDataURL(format);
847
+ return dataURL;
848
+ }
849
+
826
850
  public toSVG(): SVGElement {
827
851
  const importExportViewport = this.importExportViewport;
828
852
  const svgNameSpace = 'http://www.w3.org/2000/svg';
829
853
  const result = document.createElementNS(svgNameSpace, 'svg');
830
854
  const renderer = new SVGRenderer(result, importExportViewport);
831
855
 
832
- const origTransform = importExportViewport.canvasToScreenTransform;
833
- // Reset the transform to ensure that (0, 0) is (0, 0)
834
- importExportViewport.resetTransform(Mat33.identity);
835
-
836
- // Render **all** elements.
837
- this.image.renderAll(renderer);
838
-
839
- importExportViewport.resetTransform(origTransform);
856
+ this.renderAllWithTransform(renderer, importExportViewport);
840
857
 
841
858
  // Just show the main region
842
859
  const rect = importExportViewport.visibleRect;
@@ -854,6 +871,23 @@ export class Editor {
854
871
  return result;
855
872
  }
856
873
 
874
+ // Renders everything in this' image to `renderer`, but first transforming the given `viewport`
875
+ // such that its transform is `transform`. The given `viewport`'s transform is restored before this method
876
+ // returns.
877
+ //
878
+ // For example, rendering with `transform = Mat33.identity` *sets* `viewport`'s transform to `Mat33.identity`,
879
+ // renders everything in this' image to `renderer`, then restores `viewport`'s transform to whatever it was before.
880
+ private renderAllWithTransform(
881
+ renderer: AbstractRenderer, viewport: Viewport, transform: Mat33 = Mat33.identity
882
+ ): void {
883
+ const origTransform = this.importExportViewport.canvasToScreenTransform;
884
+ viewport.resetTransform(transform);
885
+
886
+ this.image.renderAll(renderer);
887
+
888
+ viewport.resetTransform(origTransform);
889
+ }
890
+
857
891
  public async loadFrom(loader: ImageLoader) {
858
892
  this.showLoadingWarning(0);
859
893
  this.display.setDraftMode(true);
package/src/Viewport.ts CHANGED
@@ -190,8 +190,8 @@ export class Viewport {
190
190
 
191
191
  // Represent as k 10ⁿ for some n, k ∈ ℤ.
192
192
  const decimalComponent = 10 ** Math.floor(Math.log10(Math.abs(scaleRatio)));
193
- const roundAnountFactor = 2 ** roundAmount;
194
- scaleRatio = Math.round(scaleRatio / decimalComponent * roundAnountFactor) / roundAnountFactor * decimalComponent;
193
+ const roundAmountFactor = 2 ** roundAmount;
194
+ scaleRatio = Math.round(scaleRatio / decimalComponent * roundAmountFactor) / roundAmountFactor * decimalComponent;
195
195
 
196
196
  return scaleRatio;
197
197
  }
@@ -51,6 +51,10 @@ export default class LineBuilder implements ComponentBuilder {
51
51
  kind: PathCommandType.LineTo,
52
52
  point: endPoint.minus(scaledEndNormal),
53
53
  },
54
+ {
55
+ kind: PathCommandType.LineTo,
56
+ point: startPoint.minus(scaledStartNormal),
57
+ },
54
58
  ],
55
59
  style: {
56
60
  fill: this.startPoint.color,
@@ -238,7 +238,7 @@ export class StrokeSmoother {
238
238
  if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
239
239
  // Position the control point closer to the first -- the connecting
240
240
  // segment will be roughly a line.
241
- controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 3));
241
+ controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
242
242
  }
243
243
 
244
244
  console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
@@ -94,7 +94,11 @@ export default class SVGRenderer extends AbstractRenderer {
94
94
  pathElem.setAttribute('d', this.lastPathString.join(' '));
95
95
 
96
96
  const style = this.lastPathStyle;
97
- pathElem.setAttribute('fill', style.fill.toHexString());
97
+ if (style.fill.a > 0) {
98
+ pathElem.setAttribute('fill', style.fill.toHexString());
99
+ } else {
100
+ pathElem.setAttribute('fill', 'none');
101
+ }
98
102
 
99
103
  if (style.stroke) {
100
104
  pathElem.setAttribute('stroke', style.stroke.color.toHexString());
@@ -453,7 +453,7 @@ export default class Selection {
453
453
 
454
454
  public setSelectedObjects(objects: AbstractComponent[], bbox: Rect2) {
455
455
  this.originalRegion = bbox;
456
- this.selectedElems = objects;
456
+ this.selectedElems = objects.filter(object => object.isSelectable());
457
457
  this.updateUI();
458
458
  }
459
459