etro 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.husky/pre-commit CHANGED
@@ -2,6 +2,9 @@
2
2
  . "$(dirname -- "$0")/_/husky.sh"
3
3
 
4
4
  npm run lint
5
- npm run test:unit
6
- npm run test:smoke
7
- npm run test:integration
5
+
6
+ if [ -z "$SKIP_TESTS" ]; then
7
+ npm run test:unit
8
+ npm run test:smoke
9
+ npm run test:integration
10
+ fi
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [0.12.0] - 2024-01-15
9
+ ### Added
10
+ - `stroke` option for `Text` layer ([#239](https://github.com/etro-js/etro/pull/239)).
11
+
12
+ ### Security
13
+ - Bump @babel/traverse from 7.18.13 to 7.23.2 ([#244](https://github.com/etro-js/etro/pull/244)).
14
+ - Bump browserify-sign from 4.2.1 to 4.2.2 ([#245](https://github.com/etro-js/etro/pull/245)).
15
+ - Bump follow-redirects from 1.14.9 to 1.15.4 ([#247](https://github.com/etro-js/etro/pull/247)).
16
+ - Bump gitmoji-cli from 8.4.0 to 9.0.0.
17
+
8
18
  ## [0.11.0] - 2023-08-05
9
19
  ### Added
10
20
  - `duration` option for `Movie#play` ([#208](https://github.com/etro-js/etro/pull/208)).
@@ -292,6 +302,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
292
302
  - Gaussian blur
293
303
  - Transform
294
304
 
305
+ [0.12.0]: https://github.com/etro-js/etro/compare/v0.11.0...v0.12.0
295
306
  [0.11.0]: https://github.com/etro-js/etro/compare/v0.10.1...v0.11.0
296
307
  [0.10.1]: https://github.com/etro-js/etro/compare/v0.10.0...v0.10.1
297
308
  [0.10.0]: https://github.com/etro-js/etro/compare/v0.9.1...v0.10.0
package/CONTRIBUTING.md CHANGED
@@ -33,13 +33,12 @@ Thank you for considering contributing to Etro! There are many ways you can cont
33
33
  ```
34
34
  npm run fix
35
35
  npm run build
36
- npm test:unit
37
- npm test:smoke
38
- npm test:integration
36
+ npm run test:unit
37
+ npm run test:smoke
38
+ npm run test:integration
39
39
  ```
40
40
 
41
41
  to lint and compile the code and run the tests on them. Husky will run these commands automatically when you commit.
42
- - *Note: Unit tests validate the logic of the code in etro, with the DOM and any other external dependencies mocked. Because audio cannot be captured in the GitHub Actions runner, the end-to-end tests are divided into two suites. All end-to-end tests that need to validate audio output should be placed in **spec/integration/**. All end-to-end tests that do **not** require an audio device should be placed in **spec/smoke/**. The integration tests can only be run locally, but the other two suites can be run anywhere.*
43
42
 
44
43
  - Please commit to a new branch, not master
45
44
 
@@ -53,11 +52,11 @@ Thank you for considering contributing to Etro! There are many ways you can cont
53
52
  - Open a pull request from the branch in your fork to the main repository. If you changed any core functionality, make sure you explain your motives for those changes
54
53
  - A large part of the submission process is receiving feedback on how you can improve you pull request. If you need to change your pull request, feel free to push more commits.
55
54
 
56
- ## Code overview
55
+ ## Tests
57
56
 
58
- ### Etro Overview
57
+ Automated tests are run with [KarmaJS](https://karma-runner.github.io/) in a headless Firefox session. Unit tests validate the logic of the code in etro, with the DOM and any other external dependencies mocked. Because audio cannot be captured in the GitHub Actions runner, the end-to-end tests are divided into two suites. All end-to-end tests that need to validate audio output should be placed in **spec/integration/**. All end-to-end tests that do **not** require an audio device should be placed in **spec/smoke/**. The integration tests can only be run locally, but the other two suites can be run anywhere.
59
58
 
60
- Check out [the user docs](https://etrojs.dev/docs/intro) for a high-level overview of Etro.
59
+ ## Code overview
61
60
 
62
61
  ### Events
63
62
 
package/dist/etro-cjs.js CHANGED
@@ -667,9 +667,11 @@ function AudioSourceMixin(superclass) {
667
667
  _this._sourceStartTime = options.sourceStartTime || 0;
668
668
  applyOptions(options, _this);
669
669
  var load = function () {
670
- // TODO: && ?
671
- if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0) {
672
- throw new Error('Invalid options.duration or options.sourceStartTime');
670
+ if (options.duration < 0) {
671
+ throw new Error('Invalid options.duration. It must be a non-negative value.');
672
+ }
673
+ if (_this.sourceStartTime > _this.source.duration) {
674
+ throw new Error('options.sourceStartTime cannot exceed options.source.duration');
673
675
  }
674
676
  _this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
675
677
  _this.duration = _this._unstretchedDuration / (_this.playbackRate);
@@ -1432,6 +1434,12 @@ var Image = /** @class */ (function (_super) {
1432
1434
  return Image;
1433
1435
  }(VisualSourceMixin(Visual)));
1434
1436
 
1437
+ var TextStrokePosition;
1438
+ (function (TextStrokePosition) {
1439
+ TextStrokePosition[TextStrokePosition["Inside"] = 0] = "Inside";
1440
+ TextStrokePosition[TextStrokePosition["Center"] = 1] = "Center";
1441
+ TextStrokePosition[TextStrokePosition["Outside"] = 2] = "Outside";
1442
+ })(TextStrokePosition || (TextStrokePosition = {}));
1435
1443
  var Text = /** @class */ (function (_super) {
1436
1444
  __extends(Text, _super);
1437
1445
  /**
@@ -1456,6 +1464,7 @@ var Text = /** @class */ (function (_super) {
1456
1464
  // this._prevMaxWidth = undefined;
1457
1465
  }
1458
1466
  Text.prototype.doRender = function () {
1467
+ var _a, _b;
1459
1468
  _super.prototype.doRender.call(this);
1460
1469
  var text = val(this, 'text', this.currentTime);
1461
1470
  var font = val(this, 'font', this.currentTime);
@@ -1469,6 +1478,28 @@ var Text = /** @class */ (function (_super) {
1469
1478
  this.cctx.textBaseline = val(this, 'textBaseline', this.currentTime);
1470
1479
  this.cctx.direction = val(this, 'textDirection', this.currentTime);
1471
1480
  this.cctx.fillText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1481
+ var textStroke = val(this, 'textStroke', this.currentTime);
1482
+ if (textStroke) {
1483
+ this.cctx.strokeStyle = textStroke.color;
1484
+ this.cctx.lineWidth = (_a = textStroke.thickness) !== null && _a !== void 0 ? _a : 1;
1485
+ var position = (_b = textStroke.position) !== null && _b !== void 0 ? _b : 'outer';
1486
+ // Save the globalCompositeOperation, we have to revert it after stroking the text.
1487
+ var globalCompositionOperation = this.cctx.globalCompositeOperation;
1488
+ switch (position) {
1489
+ case TextStrokePosition.Inside:
1490
+ this.cctx.globalCompositeOperation = 'source-atop';
1491
+ this.cctx.lineWidth *= 2;
1492
+ break;
1493
+ case TextStrokePosition.Center:
1494
+ break;
1495
+ case TextStrokePosition.Outside:
1496
+ this.cctx.globalCompositeOperation = 'destination-over';
1497
+ this.cctx.lineWidth *= 2;
1498
+ break;
1499
+ }
1500
+ this.cctx.strokeText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1501
+ this.cctx.globalCompositeOperation = globalCompositionOperation;
1502
+ }
1472
1503
  this._prevText = text;
1473
1504
  this._prevFont = font;
1474
1505
  this._prevMaxWidth = maxWidth;
@@ -1497,7 +1528,7 @@ var Text = /** @class */ (function (_super) {
1497
1528
  * @deprecated See {@link https://github.com/etro-js/etro/issues/131}
1498
1529
  */
1499
1530
  Text.prototype.getDefaultOptions = function () {
1500
- return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr' });
1531
+ return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr', textStroke: null });
1501
1532
  };
1502
1533
  return Text;
1503
1534
  }(Visual));
@@ -1535,6 +1566,7 @@ var index = /*#__PURE__*/Object.freeze({
1535
1566
  Base: Base,
1536
1567
  Image: Image,
1537
1568
  Text: Text,
1569
+ get TextStrokePosition () { return TextStrokePosition; },
1538
1570
  Video: Video,
1539
1571
  VisualSourceMixin: VisualSourceMixin,
1540
1572
  Visual: Visual
package/dist/etro-iife.js CHANGED
@@ -668,9 +668,11 @@ var etro = (function () {
668
668
  _this._sourceStartTime = options.sourceStartTime || 0;
669
669
  applyOptions(options, _this);
670
670
  var load = function () {
671
- // TODO: && ?
672
- if ((options.duration || (_this.source.duration - _this.sourceStartTime)) < 0) {
673
- throw new Error('Invalid options.duration or options.sourceStartTime');
671
+ if (options.duration < 0) {
672
+ throw new Error('Invalid options.duration. It must be a non-negative value.');
673
+ }
674
+ if (_this.sourceStartTime > _this.source.duration) {
675
+ throw new Error('options.sourceStartTime cannot exceed options.source.duration');
674
676
  }
675
677
  _this._unstretchedDuration = options.duration || (_this.source.duration - _this.sourceStartTime);
676
678
  _this.duration = _this._unstretchedDuration / (_this.playbackRate);
@@ -1433,6 +1435,12 @@ var etro = (function () {
1433
1435
  return Image;
1434
1436
  }(VisualSourceMixin(Visual)));
1435
1437
 
1438
+ var TextStrokePosition;
1439
+ (function (TextStrokePosition) {
1440
+ TextStrokePosition[TextStrokePosition["Inside"] = 0] = "Inside";
1441
+ TextStrokePosition[TextStrokePosition["Center"] = 1] = "Center";
1442
+ TextStrokePosition[TextStrokePosition["Outside"] = 2] = "Outside";
1443
+ })(TextStrokePosition || (TextStrokePosition = {}));
1436
1444
  var Text = /** @class */ (function (_super) {
1437
1445
  __extends(Text, _super);
1438
1446
  /**
@@ -1457,6 +1465,7 @@ var etro = (function () {
1457
1465
  // this._prevMaxWidth = undefined;
1458
1466
  }
1459
1467
  Text.prototype.doRender = function () {
1468
+ var _a, _b;
1460
1469
  _super.prototype.doRender.call(this);
1461
1470
  var text = val(this, 'text', this.currentTime);
1462
1471
  var font = val(this, 'font', this.currentTime);
@@ -1470,6 +1479,28 @@ var etro = (function () {
1470
1479
  this.cctx.textBaseline = val(this, 'textBaseline', this.currentTime);
1471
1480
  this.cctx.direction = val(this, 'textDirection', this.currentTime);
1472
1481
  this.cctx.fillText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1482
+ var textStroke = val(this, 'textStroke', this.currentTime);
1483
+ if (textStroke) {
1484
+ this.cctx.strokeStyle = textStroke.color;
1485
+ this.cctx.lineWidth = (_a = textStroke.thickness) !== null && _a !== void 0 ? _a : 1;
1486
+ var position = (_b = textStroke.position) !== null && _b !== void 0 ? _b : 'outer';
1487
+ // Save the globalCompositeOperation, we have to revert it after stroking the text.
1488
+ var globalCompositionOperation = this.cctx.globalCompositeOperation;
1489
+ switch (position) {
1490
+ case TextStrokePosition.Inside:
1491
+ this.cctx.globalCompositeOperation = 'source-atop';
1492
+ this.cctx.lineWidth *= 2;
1493
+ break;
1494
+ case TextStrokePosition.Center:
1495
+ break;
1496
+ case TextStrokePosition.Outside:
1497
+ this.cctx.globalCompositeOperation = 'destination-over';
1498
+ this.cctx.lineWidth *= 2;
1499
+ break;
1500
+ }
1501
+ this.cctx.strokeText(text, val(this, 'textX', this.currentTime), val(this, 'textY', this.currentTime), maxWidth);
1502
+ this.cctx.globalCompositeOperation = globalCompositionOperation;
1503
+ }
1473
1504
  this._prevText = text;
1474
1505
  this._prevFont = font;
1475
1506
  this._prevMaxWidth = maxWidth;
@@ -1498,7 +1529,7 @@ var etro = (function () {
1498
1529
  * @deprecated See {@link https://github.com/etro-js/etro/issues/131}
1499
1530
  */
1500
1531
  Text.prototype.getDefaultOptions = function () {
1501
- return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr' });
1532
+ return __assign(__assign({}, Visual.prototype.getDefaultOptions()), { background: null, text: undefined, font: '10px sans-serif', color: parseColor('#fff'), textX: 0, textY: 0, maxWidth: null, textAlign: 'start', textBaseline: 'top', textDirection: 'ltr', textStroke: null });
1502
1533
  };
1503
1534
  return Text;
1504
1535
  }(Visual));
@@ -1536,6 +1567,7 @@ var etro = (function () {
1536
1567
  Base: Base,
1537
1568
  Image: Image,
1538
1569
  Text: Text,
1570
+ get TextStrokePosition () { return TextStrokePosition; },
1539
1571
  Video: Video,
1540
1572
  VisualSourceMixin: VisualSourceMixin,
1541
1573
  Visual: Visual
@@ -1,5 +1,10 @@
1
1
  import { Dynamic, Color } from '../util';
2
2
  import { Visual, VisualOptions } from './visual';
3
+ declare enum TextStrokePosition {
4
+ Inside = 0,
5
+ Center = 1,
6
+ Outside = 2
7
+ }
3
8
  interface TextOptions extends VisualOptions {
4
9
  text: Dynamic<string>;
5
10
  font?: Dynamic<string>;
@@ -23,6 +28,11 @@ interface TextOptions extends VisualOptions {
23
28
  * @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
24
29
  */
25
30
  textDirection?: Dynamic<string>;
31
+ textStroke?: Dynamic<{
32
+ color: Color;
33
+ position?: TextStrokePosition;
34
+ thickness?: number;
35
+ }>;
26
36
  }
27
37
  declare class Text extends Visual {
28
38
  text: Dynamic<string>;
@@ -47,6 +57,11 @@ declare class Text extends Visual {
47
57
  * @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
48
58
  */
49
59
  textDirection: Dynamic<string>;
60
+ textStroke?: Dynamic<{
61
+ color: Color;
62
+ position?: TextStrokePosition;
63
+ thickness?: number;
64
+ }>;
50
65
  private _prevText;
51
66
  private _prevFont;
52
67
  private _prevMaxWidth;
@@ -60,4 +75,4 @@ declare class Text extends Visual {
60
75
  */
61
76
  getDefaultOptions(): TextOptions;
62
77
  }
63
- export { Text, TextOptions };
78
+ export { Text, TextOptions, TextStrokePosition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "etro",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "An extendable video-editing framework for the browser",
5
5
  "browser": "dist/etro-cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "@typescript-eslint/eslint-plugin": "^5.30.7",
18
18
  "@typescript-eslint/parser": "^5.30.7",
19
19
  "canvas": "^2.11.2",
20
+ "cross-env": "^7.0.3",
20
21
  "docdash": "^1.1.1",
21
22
  "ecstatic": ">=4.1.3",
22
23
  "eslint": "^8.20.0",
@@ -27,7 +28,7 @@
27
28
  "eslint-plugin-promise": "^6.0.0",
28
29
  "eslint-plugin-standard": "^5.0.0",
29
30
  "ev": "0.0.7",
30
- "gitmoji-cli": "^8.4.0",
31
+ "gitmoji-cli": "^9.0.0",
31
32
  "http-server": "^14.1.1",
32
33
  "husky": "^8.0.3",
33
34
  "jasmine": "^3.4.0",
@@ -60,9 +61,9 @@
60
61
  "lint:test": "eslint -c eslint.test-conf.js --ext .ts spec",
61
62
  "lint:examples": "eslint -c eslint.example-conf.js --ext .html examples",
62
63
  "start": "http-server",
63
- "test:unit": "TEST_SUITE=unit karma start",
64
- "test:smoke": "TEST_SUITE=smoke karma start",
65
- "test:integration": "TEST_SUITE=integration karma start",
64
+ "test:unit": "cross-env TEST_SUITE=unit karma start",
65
+ "test:smoke": "cross-env TEST_SUITE=smoke karma start",
66
+ "test:integration": "cross-env TEST_SUITE=integration karma start",
66
67
  "release": "shipjs prepare"
67
68
  },
68
69
  "repository": {
package/ship.config.js CHANGED
@@ -24,7 +24,18 @@ module.exports = {
24
24
  release.setDate(new Date()) // today
25
25
  const newChangelog = parsed.toString()
26
26
  fs.writeFileSync(changelogFile, newChangelog, 'utf8')
27
- }
27
+ },
28
+ releases: [
29
+ {
30
+ extractChangelog: ({ version, dir }) => {
31
+ const changelogFile = `${dir}/CHANGELOG.md`
32
+ const changelog = fs.readFileSync(changelogFile, 'utf8')
33
+ const parsed = parser(changelog)
34
+ const release = parsed.findRelease(version)
35
+ return `${release.toString()}\n${release.getCompareLink()}`
36
+ },
37
+ }
38
+ ]
28
39
  }
29
40
 
30
41
  class Changelog {
@@ -85,9 +85,12 @@ function AudioSourceMixin<OptionsSuperclass extends BaseOptions> (superclass: Co
85
85
  applyOptions(options, this)
86
86
 
87
87
  const load = () => {
88
- // TODO: && ?
89
- if ((options.duration || (this.source.duration - this.sourceStartTime)) < 0) {
90
- throw new Error('Invalid options.duration or options.sourceStartTime')
88
+ if (options.duration < 0) {
89
+ throw new Error('Invalid options.duration. It must be a non-negative value.')
90
+ }
91
+
92
+ if (this.sourceStartTime > this.source.duration) {
93
+ throw new Error('options.sourceStartTime cannot exceed options.source.duration')
91
94
  }
92
95
 
93
96
  this._unstretchedDuration = options.duration || (this.source.duration - this.sourceStartTime)
package/src/layer/text.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { Dynamic, val, applyOptions, Color, parseColor } from '../util'
2
2
  import { Visual, VisualOptions } from './visual'
3
3
 
4
+ enum TextStrokePosition {
5
+ Inside,
6
+ Center,
7
+ Outside,
8
+ }
9
+
4
10
  interface TextOptions extends VisualOptions {
5
11
  text: Dynamic<string>
6
12
  font?: Dynamic<string>
@@ -24,6 +30,12 @@ interface TextOptions extends VisualOptions {
24
30
  * @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
25
31
  */
26
32
  textDirection?: Dynamic<string>
33
+
34
+ textStroke?: Dynamic<{
35
+ color: Color,
36
+ position?: TextStrokePosition
37
+ thickness?: number
38
+ }>
27
39
  }
28
40
 
29
41
  class Text extends Visual {
@@ -49,6 +61,11 @@ class Text extends Visual {
49
61
  * @see [`CanvasRenderingContext2D#direction`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)
50
62
  */
51
63
  textDirection: Dynamic<string>
64
+ textStroke?: Dynamic<{
65
+ color: Color,
66
+ position?: TextStrokePosition
67
+ thickness?: number
68
+ }>
52
69
 
53
70
  private _prevText: string
54
71
  private _prevFont: string
@@ -94,6 +111,34 @@ class Text extends Visual {
94
111
  maxWidth
95
112
  )
96
113
 
114
+ const textStroke = val(this, 'textStroke', this.currentTime)
115
+ if (textStroke) {
116
+ this.cctx.strokeStyle = textStroke.color
117
+ this.cctx.lineWidth = textStroke.thickness ?? 1
118
+ const position = textStroke.position ?? 'outer'
119
+ // Save the globalCompositeOperation, we have to revert it after stroking the text.
120
+ const globalCompositionOperation = this.cctx.globalCompositeOperation
121
+ switch (position) {
122
+ case TextStrokePosition.Inside:
123
+ this.cctx.globalCompositeOperation = 'source-atop'
124
+ this.cctx.lineWidth *= 2
125
+ break
126
+ case TextStrokePosition.Center:
127
+ break
128
+ case TextStrokePosition.Outside:
129
+ this.cctx.globalCompositeOperation = 'destination-over'
130
+ this.cctx.lineWidth *= 2
131
+ break
132
+ }
133
+ this.cctx.strokeText(
134
+ text,
135
+ val(this, 'textX', this.currentTime),
136
+ val(this, 'textY', this.currentTime),
137
+ maxWidth
138
+ )
139
+ this.cctx.globalCompositeOperation = globalCompositionOperation
140
+ }
141
+
97
142
  this._prevText = text
98
143
  this._prevFont = font
99
144
  this._prevMaxWidth = maxWidth
@@ -137,9 +182,10 @@ class Text extends Visual {
137
182
  maxWidth: null,
138
183
  textAlign: 'start',
139
184
  textBaseline: 'top',
140
- textDirection: 'ltr'
185
+ textDirection: 'ltr',
186
+ textStroke: null
141
187
  }
142
188
  }
143
189
  }
144
190
 
145
- export { Text, TextOptions }
191
+ export { Text, TextOptions, TextStrokePosition }