Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d3ef58d73 |
@@ -1,14 +1,12 @@
|
||||
/build/doc.js
|
||||
/build/typescript/typescript.d.ts
|
||||
/build/typescript/*.js
|
||||
build/doc.js
|
||||
build/typescript/typescript.d.ts
|
||||
build/typescript/*.js
|
||||
|
||||
/dist/
|
||||
dist/
|
||||
|
||||
/lib/
|
||||
!/lib/libjass.css
|
||||
lib/
|
||||
!lib/libjass.css
|
||||
|
||||
/node_modules/
|
||||
node_modules/
|
||||
|
||||
/npm-debug.log
|
||||
|
||||
/src/version.ts
|
||||
npm-debug.log
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "7"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "5"
|
||||
before_script:
|
||||
- "node ./build.js doc"
|
||||
sudo: false
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
This project is no longer being worked on.
|
||||
[](https://travis-ci.org/Arnavion/libjass)
|
||||
|
||||
You should probably use something else, like https://github.com/Dador/JavascriptSubtitlesOctopus
|
||||
|
||||
When I started libjass in 2011, I made a bet that offloading rendering to the DOM would eventually be the way to get fast and accurate rendering. CSS filter effects were about to be standardized. Regular JavaScript would've been too slow to do the fancy rendering that ASS requires. Surely letting the browser render text would be faster than parsing fonts in JS, computing the dimensions and margins for every rendered character, and blitting individual outline and shadow pixels to a canvas.
|
||||
|
||||
However CSS filter effects by themselves turned out to be inadequate to accurately render even the basics of ASS. SVG filters are more accurate, but are unoptimized or unsupported in all browsers since nobody really uses them (a vicious cycle). As such, both of them are unable to efficiently render the simplest and most common ASS feature - the elliptical border. The `feMorphology` SVG filter can only dilate to rectangles, so libjass has to stack many such rectangles of different sizes to approximate an ellipse. Big borders end up needing tens of such rectangles and a large gaussian blur, which brings even the mightiest browser's renderer to its single-threaded knees.
|
||||
|
||||
Layout also has problems. CSS doesn't provide an easy to way for a subtitle to push another subtitle away so that they don't overlap. It doesn't provide a line-breaking strategy that tries to equalize the lengths of the broken lines (what ASS calls smart line wrapping). Vertically centering things is still a nightmare - flexbox and CSS grid don't help because subtitles don't follow grids - so `\an4-6` were never properly implemented. These things *could* be solved by positioning the text manually, but this would've brought us back to the problem of parsing fonts and measuring text dimensions in JavaScript instead of letting the DOM handle it.
|
||||
|
||||
In 2013, asm.js became a way to use the original C renderers like libass, compiled to something that's not as fast as native C but still faster than regular JavaScript rendering. More recently, WASM has emerged as a more cross-platform and strongly-guaranteed way of doing this. Parsing fonts and computing dimensions is now a feasible prospect.
|
||||
|
||||
Because of this, I believe libjass's strategy of relying on the browser DOM is a dead end.
|
||||
|
||||
I'm happy to continue providing support and answering questions about the code on Github. Since this repository is archived, please ask by opening an issue at https://github.com/Arnavion/archived-repos-issues instead. The code in this repository is still available under APL-2.0. The ASS parser is functional regardless of the browser renderer. Feel free to fork this project, or incorporate its code into your own projects, under the terms of the license.
|
||||
|
||||
Thank you, the users who used libjass on your websites, opened issues, and contributed fixes. libjass was my first OSS project that I intended to be used by more people than me. I had fun working on it and learning about web dev.
|
||||
|
||||
----
|
||||
|
||||
libjass is a JavaScript library written in TypeScript to render ASS subs in the browser. [Check out the demo.](https://arnavion.github.io/libjass/demo/index.xhtml)
|
||||
libjass is a JavaScript library written in TypeScript to render ASS subs in the browser. [Check out the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
|
||||
|
||||
|
||||
### What's special about libjass?
|
||||
@@ -60,37 +42,37 @@ Only libjass.js and libjass.css are needed to use libjass on your website. The o
|
||||
|
||||
The API documentation is linked in the Links section below. Here's an overview:
|
||||
|
||||
* The [ASS.fromUrl()](https://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) function takes in a URL to an ASS script and returns a promise that resolves to an [ASS](https://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object. This ASS object represents the script properties, the line styles and dialogue lines in it. Alternatively, you can use [ASS.fromString()](https://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) to convert a string of the script contents into an ASS object.
|
||||
* The [ASS.fromUrl()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) function takes in a URL to an ASS script and returns a promise that resolves to an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object. This ASS object represents the script properties, the line styles and dialogue lines in it. Alternatively, you can use [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) to convert a string of the script contents into an ASS object.
|
||||
|
||||
* Next, you initialize a renderer to render the subtitles. libjass ships with an easy-to-use renderer, the [DefaultRenderer](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). It uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
|
||||
* Next, you initialize a renderer to render the subtitles. libjass ships with an easy-to-use renderer, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). It uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
|
||||
|
||||
* The renderer uses [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) as a source of timer ticks. In each tick, it determines the set of dialogues to be shown at the current video time, renders each of them as a div, and appendChild's the div into the appropriate layer+alignment div.
|
||||
|
||||
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize)
|
||||
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize)
|
||||
|
||||
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - this map can be conveniently created from a CSS file containing @font-face rules using [RendererSettings.makeFontMapFromStyleElement()](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.makeFontMapFromStyleElement)
|
||||
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - this map can be conveniently created from a CSS file containing @font-face rules using [libjass.renderers.makeFontMapFromStyleElement()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.makeFontMapFromStyleElement)
|
||||
|
||||
* For an example of using libjass, check out [the demo.](https://arnavion.github.io/libjass/demo/index.xhtml) It has comments explaining basic usage and pointers to some advanced usage.
|
||||
* For an example of using libjass, check out [the demo.](http://arnavion.github.io/libjass/demo/index.xhtml) It has comments explaining basic usage and pointers to some advanced usage.
|
||||
|
||||
|
||||
### What browser and JavaScript features does libjass need?
|
||||
|
||||
* libjass uses some ES5 features like getters and setters (via Object.defineProperty), and assumptions like the behavior of parseInt with leading zeros. It cannot be used with an ES3 environment.
|
||||
|
||||
* libjass will use ES6 Set, Map and Promise if they're available on the global object. If they're not present, it will use its own minimal internal implementations. If you have implementations of these that you would like libjass to use but don't want to register them on the global object, you can provide them to libjass specifically by setting the [libjass.Set](https://arnavion.github.io/libjass/api.xhtml#libjass.Set), [libjass.Map](https://arnavion.github.io/libjass/api.xhtml#libjass.Map) and [libjass.Promise](https://arnavion.github.io/libjass/api.xhtml#libjass.Promise) properties.
|
||||
* libjass will use ES6 Set, Map and Promise if they're available on the global object. If they're not present, it will use its own minimal internal implementations. If you have implementations of these that you would like libjass to use but don't want to register them on the global object, you can provide them to libjass specifically by setting the [libjass.Set](http://arnavion.github.io/libjass/api.xhtml#libjass.Set), [libjass.Map](http://arnavion.github.io/libjass/api.xhtml#libjass.Map) and [libjass.Promise](http://arnavion.github.io/libjass/api.xhtml#libjass.Promise) properties.
|
||||
|
||||
* [AutoClock](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.AutoClock) and [VideoClock](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.VideoClock) use [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to generate clock ticks.
|
||||
* [AutoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.AutoClock) and [VideoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.VideoClock) use [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to generate clock ticks.
|
||||
|
||||
* [WebRenderer](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer) and [DefaultRenderer](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer) use [SVG filter effects for HTML](https://caniuse.com/#feat=svg-html) to render outlines and blur. This feature is not available on all browsers, so you can tell them to fall back to more widely available CSS methods by setting the [RendererSettings.enableSvg](https://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.enableSvg) property to false.
|
||||
* [WebRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer) and [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer) use [SVG filter effects for HTML](http://caniuse.com/#feat=svg-html) to render outlines and blur. This feature is not available on all browsers, so you can tell them to fall back to more widely available CSS methods by setting the [RendererSettings.enableSvg](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.enableSvg) property to false.
|
||||
|
||||
* WebRenderer and DefaultRenderer use [CSS3 animations](https://caniuse.com/#feat=css-animation) for effects like \mov and \fad.
|
||||
* WebRenderer and DefaultRenderer use [CSS3 animations](http://caniuse.com/#feat=css-animation) for effects like \mov and \fad.
|
||||
|
||||
* Using fonts attached to the script requires [ES6 typed arrays](https://caniuse.com/#feat=typedarrays) (ArrayBuffer, DataView, Uint8Array, etc).
|
||||
* Using fonts attached to the script requires [ES6 typed arrays](http://caniuse.com/#feat=typedarrays) (ArrayBuffer, DataView, Uint8Array, etc).
|
||||
|
||||
|
||||
### Can I use libjass in node?
|
||||
|
||||
libjass's parser works in node. Entire scripts can be parsed via [ASS.fromString()](https://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
|
||||
libjass's parser works in node. Entire scripts can be parsed via [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
|
||||
|
||||
```javascript
|
||||
> var libjass = require("libjass")
|
||||
@@ -113,7 +95,7 @@ true
|
||||
'Fade { start: 0.2, end: 0 }'
|
||||
```
|
||||
|
||||
[libjass.parser.parse](https://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. For example, the [dialogueParts](https://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun.parse_dialogueParts) rule can be used to get an array of [libjass.parts](https://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
|
||||
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. For example, the [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun.parse_dialogueParts) rule can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
|
||||
|
||||
```javascript
|
||||
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
|
||||
@@ -130,11 +112,18 @@ true
|
||||
8
|
||||
```
|
||||
|
||||
The rule names are derived from the methods on the [ParserRun class](https://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun).
|
||||
The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun).
|
||||
|
||||
See the tests, particularly the ones in tests/unit/miscellaneous.js, for examples.
|
||||
|
||||
|
||||
### Can I contribute?
|
||||
|
||||
Yes! Feature requests, suggestions, bug reports and pull requests are welcome! I'm especially looking for details and edge-cases of the ASS syntax that libjass doesn't support.
|
||||
|
||||
You can also join the IRC channel in the links section below and ask any questions.
|
||||
|
||||
|
||||
### Supported features
|
||||
|
||||
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
|
||||
@@ -148,13 +137,13 @@ See the tests, particularly the ones in tests/unit/miscellaneous.js, for example
|
||||
* \an4, \an5, \an6 aren't positioned correctly.
|
||||
* Smart line wrapping is not supported. Such lines are rendered as [wrapping style 1 (end-of-line wrapping).](http://docs.aegisub.org/3.0/ASS_Tags/#wrapstyle)
|
||||
* Lines with multiple rotations aren't rotated the same as VSFilter or libass. See [#14](https://github.com/Arnavion/libjass/issues/14)
|
||||
- Desktop renderers include borders when calculating space between adjacent lines. libjass doesn't.
|
||||
|
||||
|
||||
### Links
|
||||
|
||||
* [GitHub](https://github.com/Arnavion/libjass/)
|
||||
* [API documentation](https://arnavion.github.io/libjass/api.xhtml)
|
||||
* IRC channel - #libjass on irc.rizon.net
|
||||
* [API documentation](http://arnavion.github.io/libjass/api.xhtml)
|
||||
* [Aegisub's documentation on ASS](http://docs.aegisub.org/3.0/ASS_Tags/)
|
||||
|
||||
|
||||
|
||||
@@ -67,45 +67,7 @@ task("build-tools", function (callback) {
|
||||
|
||||
task("default", ["libjass.js", "libjass.min.js"]);
|
||||
|
||||
task("version.ts", function (callback) {
|
||||
fs.exists("./src/version.ts", function (exists) {
|
||||
if (exists) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
fs.readFile.bind(fs, "./package.json"),
|
||||
function (buffer, callback) {
|
||||
try {
|
||||
var packageJson = JSON.parse(buffer);
|
||||
var versionString = packageJson.version;
|
||||
var versionParts = versionString.split(".").map(function (num) { return parseInt(num); });
|
||||
var versionFileContents =
|
||||
"/* tslint:disable */\n" +
|
||||
"\n" +
|
||||
"/**\n" +
|
||||
" * The version of libjass. An array like\n" +
|
||||
" *\n" +
|
||||
" * [\"0.12.0\", 0, 12, 0]\n" +
|
||||
" *\n" +
|
||||
" * @type {!Array.<string|number>}\n" +
|
||||
" */\n" +
|
||||
"export const version = " + JSON.stringify([versionString].concat(versionParts)) + ";\n";
|
||||
callback(null, versionFileContents);
|
||||
}
|
||||
catch (ex) {
|
||||
callback(ex);
|
||||
}
|
||||
},
|
||||
function (contents, callback) {
|
||||
fs.writeFile("./src/version.ts", contents, "utf8", callback);
|
||||
}
|
||||
], callback);
|
||||
});
|
||||
});
|
||||
|
||||
task("libjass.js", ["build-tools", "version.ts"], function (callback) {
|
||||
task("libjass.js", ["build-tools"], function (callback) {
|
||||
fs.exists("./lib/libjass.js", function (exists) {
|
||||
if (exists) {
|
||||
callback();
|
||||
@@ -114,7 +76,7 @@ task("libjass.js", ["build-tools", "version.ts"], function (callback) {
|
||||
|
||||
callback(null, task.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.build("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.build("libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(UglifyJS.build("./src/index", "libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(task.dest("./lib")));
|
||||
});
|
||||
});
|
||||
@@ -132,9 +94,9 @@ task("libjass.min.js", ["libjass.js"], function (callback) {
|
||||
});
|
||||
});
|
||||
|
||||
task("clean", task.clean(["./src/version.ts", "./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"]));
|
||||
task("clean", task.clean(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"]));
|
||||
|
||||
task("watch", ["build-tools", "version.ts"], function (callback) {
|
||||
task("watch", ["build-tools"], function (callback) {
|
||||
npm.load(function () {
|
||||
var testRunning = false;
|
||||
var rerunTest = false;
|
||||
@@ -163,7 +125,7 @@ task("watch", ["build-tools", "version.ts"], function (callback) {
|
||||
|
||||
task.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.watch("libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(UglifyJS.watch("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(task.dest("./lib"))
|
||||
.pipe(new task.FileTransform(function (file) {
|
||||
if (file.path === "libjass.js") {
|
||||
@@ -203,7 +165,7 @@ task("demo", ["libjass.js"], function () {
|
||||
|
||||
task("doc", ["make-doc", "test-doc"]);
|
||||
|
||||
task("make-doc", ["build-tools", "version.ts"], function () {
|
||||
task("make-doc", ["build-tools"], function () {
|
||||
var Doc = require("./build/doc.js");
|
||||
|
||||
return task.src("./src/tsconfig.json")
|
||||
|
||||
@@ -18,14 +18,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FileTransform } from "async-build";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Transform } from "stream";
|
||||
|
||||
import { File, FileTransform } from "async-build";
|
||||
|
||||
import * as AST from "./typescript/ast";
|
||||
import { Compiler } from "./typescript/compiler";
|
||||
import { walk } from "./typescript/walker";
|
||||
|
||||
function flatten<T>(arr: T[][]): T[] {
|
||||
let result: T[] = [];
|
||||
var result: T[] = [];
|
||||
|
||||
for (const a of arr) {
|
||||
result = result.concat(a);
|
||||
@@ -34,7 +38,7 @@ function flatten<T>(arr: T[][]): T[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
const sorter = (() => {
|
||||
var sorter = (() => {
|
||||
function visibilitySorter(value1: { isPrivate?: boolean; isProtected?: boolean; }, value2: { isPrivate?: boolean; isProtected?: boolean; }) {
|
||||
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
|
||||
return 0;
|
||||
@@ -59,10 +63,10 @@ const sorter = (() => {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
|
||||
var types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
|
||||
function typeSorter(value1: AST.ModuleMember | AST.NamespaceMember, value2: AST.ModuleMember | AST.NamespaceMember) {
|
||||
let type1Index = -1;
|
||||
let type2Index = -1;
|
||||
var type1Index = -1;
|
||||
var type2Index = -1;
|
||||
|
||||
types.every((type, index) => {
|
||||
if (value1 instanceof type) {
|
||||
@@ -81,11 +85,11 @@ const sorter = (() => {
|
||||
return value1.name.localeCompare(value2.name);
|
||||
}
|
||||
|
||||
const sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
|
||||
var sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
|
||||
|
||||
return (value1: AST.ModuleMember, value2: AST.ModuleMember) => {
|
||||
for (const sorter of sorters) {
|
||||
const result = sorter(value1, value2);
|
||||
for (var i = 0; i < sorters.length; i++) {
|
||||
var result = sorters[i](value1, value2);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
@@ -107,10 +111,10 @@ function sanitize(str: string) {
|
||||
function toVariableName(item: { name: string }) {
|
||||
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
|
||||
|
||||
const name = item.name;
|
||||
let result = "";
|
||||
var name = item.name;
|
||||
var result = "";
|
||||
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
if (name[i] === name[i].toLowerCase()) {
|
||||
// This is lower case. Write it as lower case.
|
||||
result += name[i];
|
||||
@@ -165,15 +169,15 @@ function toUsageName(item: AST.Class | AST.Interface | AST.Function | AST.Proper
|
||||
}
|
||||
|
||||
if (item.parent instanceof AST.Namespace) {
|
||||
if ((item as AST.CanBePrivate).isPrivate) {
|
||||
if ((<AST.Class | AST.Interface | AST.Function | AST.Enum>item).isPrivate) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
return item.fullName;
|
||||
}
|
||||
|
||||
if ((item as AST.CanBeStatic).isStatic) {
|
||||
return toUsageName(item.parent as AST.Class | AST.Interface) + '.' + item.name;
|
||||
if ((<AST.Function>item).isStatic) {
|
||||
return toUsageName(<AST.Class | AST.Interface>item.parent) + '.' + item.name;
|
||||
}
|
||||
|
||||
return toVariableName(item.parent) + '.' + item.name;
|
||||
@@ -184,12 +188,13 @@ function toId(item: { fullName?: string; name: string; }): string {
|
||||
}
|
||||
|
||||
function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): string {
|
||||
let result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
|
||||
var result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
|
||||
|
||||
if (AST.hasGenerics(item) && item.generics.length > 0) {
|
||||
const generics = item.generics as (string | AST.TypeReference | AST.IntrinsicTypeReference)[];
|
||||
var itemWithGenerics = <AST.HasGenerics>item;
|
||||
if (itemWithGenerics.generics !== undefined && itemWithGenerics.generics.length > 0) {
|
||||
var generics = <(string | AST.TypeReference | AST.IntrinsicTypeReference)[]>itemWithGenerics.generics;
|
||||
result += sanitize(`.<${ generics.map(generic =>
|
||||
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : generic
|
||||
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : <string>generic
|
||||
).join(', ') }>`);
|
||||
}
|
||||
|
||||
@@ -199,9 +204,9 @@ function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): st
|
||||
}
|
||||
|
||||
function writeDescription(text: string): string {
|
||||
let result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
|
||||
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
|
||||
|
||||
let inCodeBlock = false;
|
||||
var inCodeBlock = false;
|
||||
result = result.split("\n").map(line => {
|
||||
if (line.substr(0, " ".length) === " ") {
|
||||
line = line.substr(" ".length);
|
||||
@@ -284,7 +289,7 @@ function functionToHtml(func: AST.Function): string[] {
|
||||
}
|
||||
|
||||
function interfaceToHtml(interfase: AST.Interface): string[] {
|
||||
const members: AST.InterfaceMember[] = [];
|
||||
var members: AST.InterfaceMember[] = [];
|
||||
Object.keys(interfase.members).forEach(memberName => members.push(interfase.members[memberName]));
|
||||
|
||||
members.sort(sorter);
|
||||
@@ -306,7 +311,7 @@ function interfaceToHtml(interfase: AST.Interface): string[] {
|
||||
return functionToHtml(member).map(indenter(2));
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
|
||||
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
|
||||
}
|
||||
}))).concat([
|
||||
' </dd>',
|
||||
@@ -316,7 +321,7 @@ function interfaceToHtml(interfase: AST.Interface): string[] {
|
||||
}
|
||||
|
||||
function classToHtml(clazz: AST.Class): string[] {
|
||||
const members: AST.InterfaceMember[] = [];
|
||||
var members: AST.InterfaceMember[] = [];
|
||||
Object.keys(clazz.members).forEach(memberName => members.push(clazz.members[memberName]));
|
||||
|
||||
members.sort(sorter);
|
||||
@@ -326,7 +331,7 @@ function classToHtml(clazz: AST.Class): string[] {
|
||||
clazz.isAbstract ? ' abstract' : ''}${
|
||||
clazz.isPrivate ? ' private' : ''}">`,
|
||||
` <dt class="name">class ${ toLink(clazz) }${
|
||||
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(clazz.baseType) : clazz.baseType.name }` : '' }${
|
||||
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(<AST.TypeReference>clazz.baseType) : clazz.baseType.name }` : '' }${
|
||||
(clazz.interfaces.length > 0) ? ` implements ${ clazz.interfaces.map(interfase => interfase instanceof AST.TypeReference ? toLink(interfase) : interfase.name).join(', ') }` : ''}</dt>`,
|
||||
' <dd class="description">',
|
||||
` ${ writeDescription(clazz.description) }`,
|
||||
@@ -346,7 +351,7 @@ function classToHtml(clazz: AST.Class): string[] {
|
||||
return functionToHtml(member).map(indenter(2));
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
|
||||
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
|
||||
}
|
||||
}))).concat([
|
||||
' </dd>',
|
||||
@@ -402,26 +407,30 @@ function propertyToHtml(property: AST.Property): string[] {
|
||||
}
|
||||
|
||||
export function build(outputFilePath: string, root: string, rootNamespaceName: string): FileTransform {
|
||||
const compiler = new Compiler();
|
||||
var compiler = new Compiler();
|
||||
|
||||
return new FileTransform(function (file: File): void {
|
||||
var self: FileTransform = this;
|
||||
|
||||
return new FileTransform(function (file): void {
|
||||
// Compile
|
||||
compiler.compile(file);
|
||||
|
||||
// Walk
|
||||
const walkResult = walk(compiler, root, rootNamespaceName);
|
||||
const namespaces = walkResult.namespaces;
|
||||
const modules = walkResult.modules;
|
||||
var walkResult = walk(compiler, root, rootNamespaceName);
|
||||
var namespaces = walkResult.namespaces;
|
||||
var modules = walkResult.modules;
|
||||
|
||||
// Make HTML
|
||||
|
||||
const namespaceNames = Object.keys(namespaces)
|
||||
var namespaceNames = Object.keys(namespaces)
|
||||
.filter(namespaceName => namespaceName.substr(0, rootNamespaceName.length) === rootNamespaceName)
|
||||
.sort((ns1, ns2) => ns1.localeCompare(ns2));
|
||||
|
||||
const moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2)).filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
|
||||
var moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2));
|
||||
|
||||
this.push({
|
||||
moduleNames = moduleNames.filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
|
||||
|
||||
self.push({
|
||||
path: outputFilePath,
|
||||
contents: Buffer.concat([new Buffer(
|
||||
`<?xml version="1.0" encoding="utf-8" ?>
|
||||
@@ -581,9 +590,9 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
|
||||
<label><input type="checkbox" id="show-private" />Show private</label>
|
||||
`
|
||||
)].concat(namespaceNames.map(namespaceName => {
|
||||
const namespace = namespaces[namespaceName];
|
||||
var namespace = namespaces[namespaceName];
|
||||
|
||||
const namespaceMembers: AST.NamespaceMember[] = [];
|
||||
var namespaceMembers: AST.NamespaceMember[] = [];
|
||||
for (const memberName of Object.keys(namespace.members)) {
|
||||
namespaceMembers.push(namespace.members[memberName]);
|
||||
}
|
||||
@@ -605,13 +614,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
|
||||
`
|
||||
)]));
|
||||
})).concat(moduleNames.map(moduleName => {
|
||||
const module = modules[moduleName];
|
||||
var module = modules[moduleName];
|
||||
|
||||
const moduleMembers: AST.ModuleMemberWithoutReference[] = [];
|
||||
var moduleMembers: AST.ModuleMemberWithoutReference[] = [];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
const member = module.members[memberName];
|
||||
if ((member as AST.HasParent).parent === module) {
|
||||
moduleMembers.push(member as AST.ModuleMemberWithoutReference);
|
||||
var member = module.members[memberName];
|
||||
if ((<AST.HasParent><any>member).parent === module) {
|
||||
moduleMembers.push(<AST.ModuleMemberWithoutReference>member);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,22 +650,22 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
|
||||
<div class="content">
|
||||
`
|
||||
)]).concat(flatten(namespaceNames.map(namespaceName => {
|
||||
const namespace = namespaces[namespaceName];
|
||||
var namespace = namespaces[namespaceName];
|
||||
|
||||
const namespaceMembers: AST.NamespaceMember[] = [];
|
||||
var namespaceMembers: AST.NamespaceMember[] = [];
|
||||
for (const memberName of Object.keys(namespace.members)) {
|
||||
namespaceMembers.push(namespace.members[memberName]);
|
||||
}
|
||||
|
||||
namespaceMembers.sort(sorter);
|
||||
|
||||
const properties = namespaceMembers.filter(member => member instanceof AST.Property) as AST.Property[];
|
||||
const functions = namespaceMembers.filter(member => member instanceof AST.Function) as AST.Function[];
|
||||
const interfaces = namespaceMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
|
||||
const classes = namespaceMembers.filter(member => member instanceof AST.Class) as AST.Class[];
|
||||
const enums = namespaceMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
|
||||
var properties = <AST.Property[]>namespaceMembers.filter(member => member instanceof AST.Property);
|
||||
var functions = <AST.Function[]>namespaceMembers.filter(member => member instanceof AST.Function);
|
||||
var interfaces = <AST.Interface[]>namespaceMembers.filter(member => member instanceof AST.Interface);
|
||||
var classes = <AST.Class[]>namespaceMembers.filter(member => member instanceof AST.Class);
|
||||
var enums = <AST.Enum[]>namespaceMembers.filter(member => member instanceof AST.Enum);
|
||||
|
||||
const result = [new Buffer(
|
||||
var result = [new Buffer(
|
||||
` <section class="namespace">
|
||||
<h1 id="${ sanitize(namespaceName) }">Namespace ${ sanitize(namespaceName) }</h1>
|
||||
`
|
||||
@@ -754,13 +763,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
|
||||
|
||||
return result;
|
||||
}))).concat(flatten(moduleNames.map(moduleName => {
|
||||
const module = modules[moduleName];
|
||||
var module = modules[moduleName];
|
||||
|
||||
const moduleMembers: AST.ModuleMember[] = [];
|
||||
var moduleMembers: AST.ModuleMember[] = [];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
const member = module.members[memberName];
|
||||
if ((member as AST.HasParent).parent === module) {
|
||||
moduleMembers.push(member);
|
||||
var member = module.members[memberName];
|
||||
if ((<AST.HasParent><any>member).parent === module) {
|
||||
moduleMembers.push(<AST.ModuleMember>member);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,13 +779,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
|
||||
|
||||
moduleMembers.sort(sorter);
|
||||
|
||||
const properties = moduleMembers.filter(member => member instanceof AST.Property) as AST.Property[];
|
||||
const functions = moduleMembers.filter(member => member instanceof AST.Function) as AST.Function[];
|
||||
const interfaces = moduleMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
|
||||
const classes = moduleMembers.filter(member => member instanceof AST.Class) as AST.Class[];
|
||||
const enums = moduleMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
|
||||
var properties = <AST.Property[]>moduleMembers.filter(member => member instanceof AST.Property);
|
||||
var functions = <AST.Function[]>moduleMembers.filter(member => member instanceof AST.Function);
|
||||
var interfaces = <AST.Interface[]>moduleMembers.filter(member => member instanceof AST.Interface);
|
||||
var classes = <AST.Class[]>moduleMembers.filter(member => member instanceof AST.Class);
|
||||
var enums = <AST.Enum[]>moduleMembers.filter(member => member instanceof AST.Enum);
|
||||
|
||||
const result = [new Buffer(
|
||||
var result = [new Buffer(
|
||||
` <section class="module">
|
||||
<h1 id="${ sanitize(moduleName) }">Module ${ sanitize(moduleName) }</h1>
|
||||
`
|
||||
|
||||
@@ -7,12 +7,6 @@ declare var require: {
|
||||
resolve(id: string): string;
|
||||
};
|
||||
|
||||
interface BufferConstructor {
|
||||
new (str: string): Buffer;
|
||||
prototype: Buffer;
|
||||
concat(list: Buffer[]): Buffer;
|
||||
}
|
||||
|
||||
declare module "fs" {
|
||||
export function existsSync(filename: string): boolean;
|
||||
export function readFileSync(filename: string, options: { encoding: string }): string;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": false,
|
||||
"strictNullChecks": false,
|
||||
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "classic",
|
||||
"noImplicitUseStrict": false,
|
||||
"types": []
|
||||
},
|
||||
|
||||
"files": [
|
||||
"./typescript/index.ts",
|
||||
"./doc.ts",
|
||||
"./node.d.ts",
|
||||
"./typescript/typescript.d.ts",
|
||||
"../node_modules/async-build/typings.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ts = require("typescript");
|
||||
import * as ts from "typescript";
|
||||
|
||||
export class HasParent {
|
||||
public parent: HasParent = null;
|
||||
@@ -30,7 +30,7 @@ export class HasParent {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
const parent = this.parent;
|
||||
var parent = this.parent;
|
||||
if (parent instanceof Namespace) {
|
||||
return parent.getMemberFullName(this);
|
||||
}
|
||||
@@ -188,17 +188,7 @@ export class UnresolvedType {
|
||||
}
|
||||
|
||||
export type HasStringGenerics = Class | Interface | Function;
|
||||
|
||||
export function hasStringGenerics(item: NamespaceMember): item is HasStringGenerics {
|
||||
return (item as HasGenerics).generics !== undefined;
|
||||
}
|
||||
|
||||
export type HasGenerics = HasStringGenerics | TypeReference;
|
||||
|
||||
export function hasGenerics(item: ModuleMember | EnumMember | TypeReference): item is HasGenerics {
|
||||
return (item as HasGenerics).generics !== undefined;
|
||||
}
|
||||
|
||||
export type CanBePrivate = Class | Interface | Function | Getter | Setter | Enum | Reference;
|
||||
export type CanBeProtected = Function;
|
||||
export type CanBeStatic = Function;
|
||||
|
||||
@@ -18,70 +18,46 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path = require("path");
|
||||
import ts = require("typescript");
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Transform } from "stream";
|
||||
import * as ts from "typescript";
|
||||
|
||||
import { File, FileTransform } from "async-build";
|
||||
import { File, FileTransform, FileWatcher } from "async-build";
|
||||
|
||||
import * as AST from "./ast";
|
||||
import { walk } from "./walker";
|
||||
|
||||
export interface StreamingCompilerHost extends ts.CompilerHost {
|
||||
export interface StreamingCompilerHost extends ts.CompilerHost, ts.ParseConfigHost {
|
||||
setOutputStream(outputStream: FileTransform): void;
|
||||
}
|
||||
|
||||
function createCompilerHost(options: ts.CompilerOptions): StreamingCompilerHost {
|
||||
const host = ts.createCompilerHost(options) as StreamingCompilerHost;
|
||||
|
||||
let _outputStream: FileTransform = null;
|
||||
host.setOutputStream = outputStream => _outputStream = outputStream;
|
||||
|
||||
host.writeFile = (fileName, data, writeByteOrderMark, onError?, sourceFiles?): void => {
|
||||
_outputStream.push({
|
||||
path: fileName,
|
||||
contents: new Buffer(data)
|
||||
});
|
||||
};
|
||||
|
||||
host.useCaseSensitiveFileNames = () => true;
|
||||
|
||||
host.getNewLine = () => "\n";
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
export class Compiler {
|
||||
private _projectRoot: string = null;
|
||||
private _host: StreamingCompilerHost;
|
||||
private _program: ts.Program = null;
|
||||
|
||||
constructor(private _host: StreamingCompilerHost = new CompilerHost()) { }
|
||||
|
||||
compile(projectConfigFile: File) {
|
||||
this._projectRoot = path.dirname(projectConfigFile.path);
|
||||
|
||||
const projectConfig = ts.parseJsonConfigFileContent(JSON.parse(projectConfigFile.contents.toString()), ts.sys, this._projectRoot);
|
||||
var projectConfig = ts.parseJsonConfigFileContent(JSON.parse(projectConfigFile.contents.toString()), this._host, this._projectRoot);
|
||||
|
||||
this._host = createCompilerHost(projectConfig.options);
|
||||
this._program = ts.createProgram(projectConfig.fileNames, projectConfig.options, this._host);
|
||||
|
||||
const syntacticDiagnostics = this._program.getSyntacticDiagnostics();
|
||||
var syntacticDiagnostics = this._program.getSyntacticDiagnostics();
|
||||
if (syntacticDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(syntacticDiagnostics);
|
||||
throw new Error("There were one or more syntactic diagnostics.");
|
||||
}
|
||||
|
||||
const optionsDiagnostics = this._program.getOptionsDiagnostics();
|
||||
if (optionsDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(optionsDiagnostics);
|
||||
throw new Error("There were one or more options diagnostics.");
|
||||
}
|
||||
|
||||
const globalDiagnostics = this._program.getGlobalDiagnostics();
|
||||
var globalDiagnostics = this._program.getGlobalDiagnostics();
|
||||
if (globalDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(globalDiagnostics);
|
||||
throw new Error("There were one or more global diagnostics.");
|
||||
}
|
||||
|
||||
const semanticDiagnostics = this._program.getSemanticDiagnostics();
|
||||
var semanticDiagnostics = this._program.getSemanticDiagnostics();
|
||||
if (semanticDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(semanticDiagnostics);
|
||||
throw new Error("There were one or more semantic diagnostics.");
|
||||
@@ -91,7 +67,7 @@ export class Compiler {
|
||||
writeFiles(outputStream: FileTransform) {
|
||||
this._host.setOutputStream(outputStream);
|
||||
|
||||
const emitDiagnostics = this._program.emit().diagnostics;
|
||||
var emitDiagnostics = this._program.emit().diagnostics;
|
||||
if (emitDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(emitDiagnostics);
|
||||
throw new Error("There were one or more emit diagnostics.");
|
||||
@@ -112,10 +88,10 @@ export class Compiler {
|
||||
|
||||
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
for (const diagnostic of diagnostics) {
|
||||
let message = "";
|
||||
var message = "";
|
||||
|
||||
if (diagnostic.file) {
|
||||
const location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
||||
var location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
||||
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
|
||||
}
|
||||
|
||||
@@ -129,23 +105,168 @@ export class Compiler {
|
||||
};
|
||||
}
|
||||
|
||||
export function build(root: string, rootNamespaceName: string): FileTransform {
|
||||
const compiler = new Compiler();
|
||||
const typeScriptModulePath = path.dirname(require.resolve("typescript"));
|
||||
|
||||
class CompilerHost implements StreamingCompilerHost {
|
||||
protected _sourceFiles = Object.create(null);
|
||||
|
||||
private _outputStream: FileTransform = null;
|
||||
|
||||
setOutputStream(outputStream: FileTransform): void {
|
||||
this._outputStream = outputStream;
|
||||
}
|
||||
|
||||
// ts.ModuleResolutionHost members
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return fs.existsSync(fileName);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
if (!this.fileExists(fileName)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fs.readFileSync(fileName, { encoding: "utf8" });
|
||||
}
|
||||
|
||||
// ts.CompilerHost members
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
|
||||
if (fileName in this._sourceFiles) {
|
||||
return this._sourceFiles[fileName];
|
||||
}
|
||||
|
||||
try {
|
||||
var text = fs.readFileSync(fileName, { encoding: "utf8" });
|
||||
var result = ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5);
|
||||
this._sourceFiles[fileName] = result;
|
||||
}
|
||||
catch (ex) {
|
||||
if (onError) {
|
||||
onError(ex.message);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getDefaultLibFileName(): string {
|
||||
return path.join(typeScriptModulePath, "lib.dom.d.ts");
|
||||
}
|
||||
|
||||
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError: (message?: string) => void): void {
|
||||
this._outputStream.push({
|
||||
path: fileName,
|
||||
contents: new Buffer(data)
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return path.resolve(".");
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return ts.normalizeSlashes(path.resolve(fileName));
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNewLine(): string {
|
||||
return "\n";
|
||||
}
|
||||
|
||||
// ts.ParseConfigHost members
|
||||
|
||||
readDirectory(rootDir: string, extension: string, exclude: string[]): string[] {
|
||||
return ts.sys.readDirectory(rootDir, extension, exclude).map(fileName => this.getCanonicalFileName(fileName));
|
||||
}
|
||||
}
|
||||
|
||||
class WatchCompilerHost extends CompilerHost {
|
||||
private _fileWatcher = new FileWatcher(fileNames => this._onFilesChanged(fileNames));
|
||||
|
||||
private _filesChangedSinceLast: string[] = [];
|
||||
|
||||
constructor(private _onChangeCallback: () => void) {
|
||||
super();
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
|
||||
var result = super.getSourceFile(fileName, languageVersion, onError);
|
||||
if (result !== undefined) {
|
||||
this._fileWatcher.watchFile(fileName);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private _onFilesChanged(fileNames: string[]) {
|
||||
for (const fileName of fileNames) {
|
||||
delete this._sourceFiles[fileName];
|
||||
}
|
||||
|
||||
this._onChangeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
export function build(root: string, rootNamespaceName: string): FileTransform {
|
||||
var compiler = new Compiler();
|
||||
|
||||
return new FileTransform(function (projectConfigFile: File): void {
|
||||
var self: FileTransform = this;
|
||||
|
||||
return new FileTransform(function (projectConfigFile): void {
|
||||
console.log("Compiling " + projectConfigFile.path + "...");
|
||||
|
||||
compiler.compile(projectConfigFile);
|
||||
|
||||
const walkResult = walk(compiler, root, rootNamespaceName);
|
||||
var walkResult = walk(compiler, root, rootNamespaceName);
|
||||
addJSDocComments(walkResult.modules);
|
||||
|
||||
compiler.writeFiles(this);
|
||||
compiler.writeFiles(self);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
});
|
||||
}
|
||||
|
||||
export function watch(root: string, rootNamespaceName: string): FileTransform {
|
||||
return new FileTransform(function (projectConfigFile: File): void {
|
||||
var self: FileTransform = this;
|
||||
|
||||
function compile() {
|
||||
console.log("Compiling " + projectConfigFile.path + "...");
|
||||
|
||||
compiler.compile(projectConfigFile);
|
||||
|
||||
compiler.writeFiles(self);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
|
||||
self.push({
|
||||
path: "END",
|
||||
contents: ""
|
||||
});
|
||||
};
|
||||
|
||||
var compilerHost = new WatchCompilerHost(() => {
|
||||
try {
|
||||
compile();
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Compile failed." + ex.stack);
|
||||
}
|
||||
});
|
||||
|
||||
var compiler = new Compiler(compilerHost);
|
||||
|
||||
compile();
|
||||
|
||||
console.log("Listening for changes...");
|
||||
}, callback => { });
|
||||
}
|
||||
|
||||
function addJSDocComments(modules: { [name: string]: AST.Module }): void {
|
||||
function visitor(current: AST.Module | AST.ModuleMember | AST.InterfaceMember) {
|
||||
if (current instanceof AST.Module) {
|
||||
@@ -156,18 +277,18 @@ function addJSDocComments(modules: { [name: string]: AST.Module }): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const newComments: string[] = [];
|
||||
var newComments: string[] = [];
|
||||
|
||||
if (current instanceof AST.Class) {
|
||||
newComments.push("@constructor");
|
||||
|
||||
if (current.baseType !== null) {
|
||||
const baseType = current.baseType;
|
||||
var baseType = current.baseType;
|
||||
newComments.push(
|
||||
"@extends {" +
|
||||
baseType.fullName + (
|
||||
(baseType instanceof AST.TypeReference && baseType.generics.length) > 0 ?
|
||||
(".<" + (baseType as AST.TypeReference).generics.map(generic => generic.fullName).join(", ") + ">") :
|
||||
(".<" + (<AST.TypeReference>baseType).generics.map(generic => generic.fullName).join(", ") + ">") :
|
||||
""
|
||||
) +
|
||||
"}"
|
||||
@@ -197,37 +318,37 @@ function addJSDocComments(modules: { [name: string]: AST.Module }): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.parent instanceof AST.Namespace) {
|
||||
newComments.push("@memberOf " + current.parent.fullName);
|
||||
if ((<AST.HasParent><any>current).parent instanceof AST.Namespace) {
|
||||
newComments.push("@memberOf " + (<AST.HasParent><any>current).parent.fullName);
|
||||
}
|
||||
|
||||
if (AST.hasStringGenerics(current) && current.generics.length > 0) {
|
||||
newComments.push("@template " + current.generics.join(", "));
|
||||
if ((<AST.HasStringGenerics>current).generics !== undefined && (<AST.HasStringGenerics>current).generics.length > 0) {
|
||||
newComments.push("@template " + (<AST.HasStringGenerics>current).generics.join(", "));
|
||||
}
|
||||
|
||||
if ((current as AST.CanBePrivate).isPrivate) {
|
||||
if ((<AST.CanBePrivate><any>current).isPrivate) {
|
||||
newComments.push("@private");
|
||||
}
|
||||
|
||||
if ((current as AST.CanBeProtected).isProtected) {
|
||||
if ((<AST.CanBeProtected>current).isProtected) {
|
||||
newComments.push("@protected");
|
||||
}
|
||||
|
||||
if ((current as AST.CanBeStatic).isStatic) {
|
||||
if ((<AST.CanBeStatic>current).isStatic) {
|
||||
newComments.push("@static");
|
||||
}
|
||||
|
||||
if (newComments.length > 0) {
|
||||
if (current instanceof AST.Property) {
|
||||
const nodes: ts.Node[] = [];
|
||||
var nodes: ts.Node[] = [];
|
||||
if (current.getter !== null) { nodes.push(current.getter.astNode); }
|
||||
if (current.setter !== null && nodes[0] !== current.setter.astNode) { nodes.push(current.setter.astNode); }
|
||||
for (const node of nodes) {
|
||||
(node as any)["typescript-new-comment"] = newComments;
|
||||
(<any>node)["typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
else {
|
||||
(current.astNode as any)["typescript-new-comment"] = newComments;
|
||||
(<any>(<AST.Class | AST.Interface | AST.Function | AST.Enum>current).astNode)["typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,14 +399,14 @@ class FakeSourceFile {
|
||||
|
||||
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
|
||||
|
||||
export const oldGetLeadingCommentRangesOfNodeFromText: typeof ts.getLeadingCommentRangesOfNodeFromText = ts.getLeadingCommentRangesOfNodeFromText.bind(ts);
|
||||
export var oldGetLeadingCommentRangesOfNode: typeof ts.getLeadingCommentRangesOfNode = ts.getLeadingCommentRangesOfNode.bind(ts);
|
||||
ts.getLeadingCommentRangesOfNode = (node: ts.Node, sourceFileOfNode: ts.SourceFile) => {
|
||||
sourceFileOfNode = sourceFileOfNode || ts.getSourceFileOfNode(node);
|
||||
|
||||
ts.getLeadingCommentRangesOfNodeFromText = (node: ts.Node, text: string) => {
|
||||
const originalComments = oldGetLeadingCommentRangesOfNodeFromText(node, text);
|
||||
var originalComments = oldGetLeadingCommentRangesOfNode(node, sourceFileOfNode);
|
||||
|
||||
if (originalComments !== undefined && (<any>node)["typescript-new-comment"] !== undefined) {
|
||||
const sourceFileOfNode = ts.getSourceFileOfNode(node);
|
||||
let fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
|
||||
var fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
|
||||
if (fakeSourceFile === undefined) {
|
||||
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
|
||||
}
|
||||
@@ -297,12 +418,10 @@ ts.getLeadingCommentRangesOfNodeFromText = (node: ts.Node, text: string) => {
|
||||
};
|
||||
|
||||
var oldWriteCommentRange: typeof ts.writeCommentRange = ts.writeCommentRange.bind(ts);
|
||||
ts.writeCommentRange = (text: string, lineMap: number[], writer: ts.EmitTextWriter, comment: ts.CommentRange, newLine: string) => {
|
||||
ts.writeCommentRange = (currentSourceFile: ts.SourceFile, writer: ts.EmitTextWriter, comment: ts.CommentRange, newLine: string) => {
|
||||
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
|
||||
const currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
|
||||
text = currentSourceFile.text;
|
||||
lineMap = currentSourceFile.lineMap;
|
||||
currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
|
||||
}
|
||||
|
||||
return oldWriteCommentRange(text, lineMap, writer, comment, newLine);
|
||||
return oldWriteCommentRange(currentSourceFile, writer, comment, newLine);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
declare namespace ts {
|
||||
interface EmitTextWriter { }
|
||||
export interface EmitTextWriter { }
|
||||
|
||||
interface IntrinsicType extends Type {
|
||||
export interface IntrinsicType extends Type {
|
||||
intrinsicName: string;
|
||||
}
|
||||
|
||||
interface SourceFile {
|
||||
lineMap: number[];
|
||||
}
|
||||
|
||||
function forEachProperty<T, U>(map: Map<T>, callback: (value: T, key: string) => U): U;
|
||||
function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments;
|
||||
function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration): ExpressionWithTypeArguments[];
|
||||
function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): ExpressionWithTypeArguments[];
|
||||
function getLeadingCommentRangesOfNodeFromText(node: Node, text: string): CommentRange[];
|
||||
function getLineStarts(sourceFile: SourceFile): number[];
|
||||
function getSourceFileOfNode(node: Node): SourceFile;
|
||||
function getTextOfNode(node: Node, includeTrivia?: boolean): string;
|
||||
function normalizeSlashes(path: string): string;
|
||||
function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
|
||||
export function forEachValue<T, U>(map: Map<T>, callback: (value: T) => U): U;
|
||||
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration): ExpressionWithTypeArguments;
|
||||
export function getClassImplementsHeritageClauseElements(node: ClassDeclaration): NodeArray<ExpressionWithTypeArguments>;
|
||||
export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): NodeArray<ExpressionWithTypeArguments>;
|
||||
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile): CommentRange[];
|
||||
export function getLineStarts(sourceFile: SourceFile): number[];
|
||||
export function getSourceFileOfNode(node: Node): SourceFile;
|
||||
export function getTextOfNode(node: Node, includeTrivia?: boolean): string;
|
||||
export function normalizeSlashes(path: string): string;
|
||||
export function writeCommentRange(currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { build } from "./compiler";
|
||||
export { build, watch } from "./compiler";
|
||||
|
||||
@@ -18,17 +18,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path = require("path");
|
||||
import ts = require("typescript");
|
||||
import * as path from "path";
|
||||
import * as ts from "typescript";
|
||||
|
||||
import { Compiler, oldGetLeadingCommentRangesOfNodeFromText } from "./compiler";
|
||||
import { Compiler, oldGetLeadingCommentRangesOfNode } from "./compiler";
|
||||
|
||||
import * as AST from "./ast";
|
||||
|
||||
function hasModifier(node: ts.Node, flags: ts.NodeFlags): boolean {
|
||||
return (node.flags & flags) !== 0;
|
||||
}
|
||||
|
||||
interface JSDoc {
|
||||
description: string;
|
||||
isAbstract: boolean;
|
||||
@@ -72,13 +68,13 @@ class Walker {
|
||||
}
|
||||
|
||||
walk(sourceFile: ts.SourceFile): void {
|
||||
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
|
||||
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
|
||||
|
||||
if (!(moduleName in this.modules)) {
|
||||
this.modules[moduleName] = new AST.Module(moduleName);
|
||||
}
|
||||
|
||||
const module = this._scope.enter(this.modules[moduleName]);
|
||||
var module = this._scope.enter(this.modules[moduleName]);
|
||||
this._currentSourceFile = sourceFile;
|
||||
|
||||
for (const statement of sourceFile.statements) {
|
||||
@@ -91,31 +87,31 @@ class Walker {
|
||||
private _walk(node: ts.Node, parent: AST.Module): void {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.VariableStatement:
|
||||
this._visitVariableStatement(node as ts.VariableStatement, parent);
|
||||
this._visitVariableStatement(<ts.VariableStatement>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.FunctionDeclaration:
|
||||
this._visitFunctionDeclaration(node as ts.FunctionDeclaration, parent);
|
||||
this._visitFunctionDeclaration(<ts.FunctionDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.ClassDeclaration:
|
||||
this._visitClassDeclaration(node as ts.ClassDeclaration, parent);
|
||||
this._visitClassDeclaration(<ts.ClassDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.InterfaceDeclaration:
|
||||
this._visitInterfaceDeclaration(node as ts.InterfaceDeclaration, parent);
|
||||
this._visitInterfaceDeclaration(<ts.InterfaceDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.EnumDeclaration:
|
||||
this._visitEnumDeclaration(node as ts.EnumDeclaration, parent);
|
||||
this._visitEnumDeclaration(<ts.EnumDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.ImportDeclaration:
|
||||
this._visitImportDeclaration(node as ts.ImportDeclaration, parent);
|
||||
this._visitImportDeclaration(<ts.ImportDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.ExportDeclaration:
|
||||
this._visitExportDeclaration(node as ts.ExportDeclaration, parent);
|
||||
this._visitExportDeclaration(<ts.ExportDeclaration>node, parent);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.ExpressionStatement:
|
||||
@@ -125,7 +121,7 @@ class Walker {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(node.kind, ts.SyntaxKind[node.kind], node);
|
||||
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
|
||||
throw new Error("Unrecognized node.");
|
||||
}
|
||||
}
|
||||
@@ -134,20 +130,20 @@ class Walker {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.PropertySignature:
|
||||
case ts.SyntaxKind.PropertyDeclaration:
|
||||
this._visitProperty(node as ts.PropertyDeclaration, clazz);
|
||||
this._visitProperty(<ts.PropertyDeclaration>node, clazz);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.MethodSignature:
|
||||
case ts.SyntaxKind.MethodDeclaration:
|
||||
this._visitMethod(node as ts.MethodDeclaration, clazz);
|
||||
this._visitMethod(<ts.MethodDeclaration>node, clazz);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.GetAccessor:
|
||||
this._visitGetAccessor(node as ts.AccessorDeclaration, clazz);
|
||||
this._visitGetAccessor(<ts.AccessorDeclaration>node, clazz);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.SetAccessor:
|
||||
this._visitSetAccessor(node as ts.AccessorDeclaration, clazz);
|
||||
this._visitSetAccessor(<ts.AccessorDeclaration>node, clazz);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.TypeParameter:
|
||||
@@ -156,7 +152,7 @@ class Walker {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(node.kind, ts.SyntaxKind[node.kind], node);
|
||||
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
|
||||
throw new Error("Unrecognized node.");
|
||||
}
|
||||
}
|
||||
@@ -165,12 +161,12 @@ class Walker {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.PropertySignature:
|
||||
case ts.SyntaxKind.PropertyDeclaration:
|
||||
this._visitProperty(node as ts.PropertyDeclaration, interfase);
|
||||
this._visitProperty(<ts.PropertyDeclaration>node, interfase);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.MethodSignature:
|
||||
case ts.SyntaxKind.MethodDeclaration:
|
||||
this._visitMethod(node as ts.MethodDeclaration, interfase);
|
||||
this._visitMethod(<ts.MethodDeclaration>node, interfase);
|
||||
break;
|
||||
|
||||
case ts.SyntaxKind.TypeParameter:
|
||||
@@ -180,24 +176,24 @@ class Walker {
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(node.kind, ts.SyntaxKind[node.kind], node);
|
||||
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
|
||||
throw new Error("Unrecognized node.");
|
||||
}
|
||||
}
|
||||
|
||||
private _visitProperty(node: ts.PropertyDeclaration, parent: AST.Class | AST.Interface) {
|
||||
if (hasModifier(node, ts.NodeFlags.Private)) {
|
||||
if ((node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
if (jsDoc.typeAnnotation === null) {
|
||||
this._notifyIncorrectJsDoc(`Field ${ ts.getTextOfNode(node.name) } has no @type annotation.`);
|
||||
jsDoc.typeAnnotation = "*";
|
||||
}
|
||||
|
||||
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
|
||||
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
|
||||
parent.members[property.name] = property;
|
||||
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
|
||||
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
|
||||
@@ -205,9 +201,9 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitMethod(node: ts.MethodDeclaration, parent: AST.Class | AST.Interface) {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
|
||||
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
|
||||
parameterName => `Could not find @param annotation for ${ parameterName } on method ${ ts.getTextOfNode(node.name) }`
|
||||
);
|
||||
|
||||
@@ -216,25 +212,25 @@ class Walker {
|
||||
jsDoc.returnType = new AST.ReturnType("", "*");
|
||||
}
|
||||
|
||||
const isPrivate = hasModifier(node, ts.NodeFlags.Private);
|
||||
const isProtected = hasModifier(node, ts.NodeFlags.Protected);
|
||||
const isStatic = hasModifier(node, ts.NodeFlags.Static);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
|
||||
var isProtected = (node.flags & ts.NodeFlags.Protected) === ts.NodeFlags.Protected;
|
||||
var isStatic = (node.flags & ts.NodeFlags.Static) === ts.NodeFlags.Static;
|
||||
|
||||
const generics = this._getGenericsOfSignatureDeclaration(node);
|
||||
var generics = this._getGenericsOfSignatureDeclaration(node);
|
||||
|
||||
const method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
|
||||
var method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
|
||||
parent.members[method.name] = method;
|
||||
this._scope.leave();
|
||||
}
|
||||
|
||||
private _visitGetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const name = ts.getTextOfNode(node.name);
|
||||
var name = ts.getTextOfNode(node.name);
|
||||
|
||||
const isPrivate = hasModifier(node, ts.NodeFlags.Private);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
|
||||
|
||||
let property = clazz.members[name] as AST.Property;
|
||||
var property = <AST.Property>clazz.members[name];
|
||||
if (property === undefined) {
|
||||
this._scope.enter(property = new AST.Property(name));
|
||||
|
||||
@@ -251,13 +247,13 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitSetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const name = ts.getTextOfNode(node.name);
|
||||
var name = ts.getTextOfNode(node.name);
|
||||
|
||||
const isPrivate = hasModifier(node, ts.NodeFlags.Private);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
|
||||
|
||||
let property = clazz.members[name] as AST.Property;
|
||||
var property = <AST.Property>clazz.members[name];
|
||||
if (property === undefined) {
|
||||
this._scope.enter(property = new AST.Property(name));
|
||||
|
||||
@@ -278,18 +274,19 @@ class Walker {
|
||||
return;
|
||||
}
|
||||
|
||||
const declaration = node.declarationList.declarations[0];
|
||||
if (hasModifier(declaration, ts.NodeFlags.Ambient)) {
|
||||
var declaration = node.declarationList.declarations[0];
|
||||
if ((declaration.flags & ts.NodeFlags.Ambient) === ts.NodeFlags.Ambient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
if (jsDoc.typeAnnotation === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
|
||||
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
|
||||
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
|
||||
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
|
||||
|
||||
parent.members[property.name] = property;
|
||||
|
||||
@@ -297,13 +294,13 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitFunctionDeclaration(node: ts.FunctionDeclaration, parent: AST.Module): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const isPrivate = !hasModifier(node, ts.NodeFlags.Export);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
|
||||
|
||||
const generics = this._getGenericsOfSignatureDeclaration(node);
|
||||
var generics = this._getGenericsOfSignatureDeclaration(node);
|
||||
|
||||
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
|
||||
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
|
||||
parameterName => `Could not find @param annotation for ${ parameterName } on function ${ node.name.text }`
|
||||
);
|
||||
|
||||
@@ -316,7 +313,7 @@ class Walker {
|
||||
jsDoc.returnType = new AST.ReturnType("", "*");
|
||||
}
|
||||
|
||||
const freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
|
||||
var freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
|
||||
|
||||
parent.members[freeFunction.name] = freeFunction;
|
||||
|
||||
@@ -324,32 +321,32 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitClassDeclaration(node: ts.ClassDeclaration, parent: AST.Module): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
|
||||
|
||||
const generics = this._getGenericsOfInterfaceType(type);
|
||||
|
||||
const baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
|
||||
let baseType: AST.UnresolvedType = null;
|
||||
var baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
|
||||
var baseType: AST.UnresolvedType = null;
|
||||
if (baseTypeHeritageClauseElement !== null) {
|
||||
baseType = new AST.UnresolvedType(
|
||||
this._typeChecker.getTypeAtLocation(baseTypeHeritageClauseElement).symbol,
|
||||
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement, generics)
|
||||
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement)
|
||||
);
|
||||
}
|
||||
|
||||
const interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
|
||||
var interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
|
||||
this._typeChecker.getTypeAtLocation(type).symbol,
|
||||
this._getGenericsOfTypeReferenceNode(type, generics)
|
||||
this._getGenericsOfTypeReferenceNode(type)
|
||||
));
|
||||
|
||||
const isPrivate = !hasModifier(node, ts.NodeFlags.Export);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
|
||||
|
||||
let parameters: AST.Parameter[] = [];
|
||||
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
|
||||
|
||||
var generics = this._getGenericsOfInterfaceType(type);
|
||||
|
||||
var parameters: AST.Parameter[] = [];
|
||||
|
||||
if (type.symbol.members["__constructor"] !== undefined) {
|
||||
parameters = this._connectParameters((type.symbol.members["__constructor"].declarations[0] as ts.ConstructorDeclaration).parameters, jsDoc.parameters,
|
||||
parameters = this._connectParameters((<ts.ConstructorDeclaration>type.symbol.members["__constructor"].declarations[0]).parameters, jsDoc.parameters,
|
||||
parameterName => `Could not find @param annotation for ${ parameterName } on constructor in class ${ node.name.text }`
|
||||
);
|
||||
}
|
||||
@@ -357,11 +354,11 @@ class Walker {
|
||||
this._notifyIncorrectJsDoc("There are @param annotations on this class but it has no constructors.");
|
||||
}
|
||||
|
||||
const clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
|
||||
var clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
|
||||
|
||||
parent.members[clazz.name] = clazz;
|
||||
|
||||
ts.forEachProperty(type.symbol.exports, symbol => {
|
||||
ts.forEachValue(type.symbol.exports, symbol => {
|
||||
if (symbol.name === "prototype") {
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +368,7 @@ class Walker {
|
||||
}
|
||||
});
|
||||
|
||||
ts.forEachProperty(type.symbol.members, symbol => {
|
||||
ts.forEachValue(type.symbol.members, symbol => {
|
||||
for (const declaration of symbol.declarations) {
|
||||
this._walkClassMember(declaration, clazz);
|
||||
}
|
||||
@@ -381,28 +378,28 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitInterfaceDeclaration(node: ts.InterfaceDeclaration, parent: AST.Module): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
|
||||
|
||||
const generics = this._getGenericsOfInterfaceType(type);
|
||||
|
||||
const baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
|
||||
var baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
|
||||
this._typeChecker.getTypeAtLocation(type).symbol,
|
||||
this._getGenericsOfTypeReferenceNode(type, generics)
|
||||
this._getGenericsOfTypeReferenceNode(type)
|
||||
));
|
||||
|
||||
const existingInterfaceType = parent.members[node.name.text];
|
||||
var existingInterfaceType = parent.members[node.name.text];
|
||||
if (existingInterfaceType !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPrivate = !hasModifier(node, ts.NodeFlags.Export);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
|
||||
|
||||
const interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
|
||||
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
|
||||
|
||||
var generics = this._getGenericsOfInterfaceType(type);
|
||||
|
||||
var interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
|
||||
parent.members[interfase.name] = interfase;
|
||||
|
||||
ts.forEachProperty(type.symbol.members, symbol => {
|
||||
ts.forEachValue(type.symbol.members, symbol => {
|
||||
for (const declaration of symbol.declarations) {
|
||||
this._walkInterfaceMember(declaration, interfase);
|
||||
}
|
||||
@@ -412,33 +409,33 @@ class Walker {
|
||||
}
|
||||
|
||||
private _visitEnumDeclaration(node: ts.EnumDeclaration, parent: AST.Module): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const existingEnumType = parent.members[node.name.text];
|
||||
var existingEnumType = parent.members[node.name.text];
|
||||
if (existingEnumType !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPrivate = !hasModifier(node, ts.NodeFlags.Export);
|
||||
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
|
||||
|
||||
const type = this._typeChecker.getTypeAtLocation(node);
|
||||
var type = this._typeChecker.getTypeAtLocation(node);
|
||||
|
||||
const enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
|
||||
var enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
|
||||
parent.members[enumType.name] = enumType;
|
||||
|
||||
ts.forEachProperty(type.symbol.exports, symbol => {
|
||||
this._visitEnumMember(symbol.declarations[0] as ts.EnumMember, enumType);
|
||||
ts.forEachValue(type.symbol.exports, symbol => {
|
||||
this._visitEnumMember(<ts.EnumMember>symbol.declarations[0], enumType);
|
||||
});
|
||||
|
||||
this._scope.leave();
|
||||
}
|
||||
|
||||
private _visitEnumMember(node: ts.EnumMember, parent: AST.Enum): void {
|
||||
const jsDoc = this._parseJSDoc(node);
|
||||
var jsDoc = this._parseJSDoc(node);
|
||||
|
||||
const value = (node.initializer === undefined) ? null : parseInt((node.initializer as ts.LiteralExpression).text);
|
||||
var value = (node.initializer === undefined) ? null : parseInt((<ts.LiteralExpression>node.initializer).text);
|
||||
|
||||
const enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
|
||||
var enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
|
||||
|
||||
parent.members.push(enumMember);
|
||||
|
||||
@@ -455,16 +452,16 @@ class Walker {
|
||||
throw new Error("Default import is not supported.");
|
||||
}
|
||||
|
||||
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
|
||||
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
|
||||
|
||||
if ((node.importClause.namedBindings as ts.NamespaceImport).name !== undefined) {
|
||||
if ((<ts.NamespaceImport>node.importClause.namedBindings).name !== undefined) {
|
||||
// import * as foo from "baz";
|
||||
parent.members[(node.importClause.namedBindings as ts.NamespaceImport).name.text] = new AST.Reference(moduleName, "*", true);
|
||||
parent.members[(<ts.NamespaceImport>node.importClause.namedBindings).name.text] = new AST.Reference(moduleName, "*", true);
|
||||
}
|
||||
else if ((node.importClause.namedBindings as ts.NamedImports).elements !== undefined) {
|
||||
else if ((<ts.NamedImports>node.importClause.namedBindings).elements !== undefined) {
|
||||
// import { foo, bar } from "baz";
|
||||
for (const element of (node.importClause.namedBindings as ts.NamedImports).elements) {
|
||||
const importedName = element.propertyName && element.propertyName.text || element.name.text;
|
||||
for (const element of (<ts.NamedImports>node.importClause.namedBindings).elements) {
|
||||
var importedName = element.propertyName && element.propertyName.text || element.name.text;
|
||||
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, true);
|
||||
}
|
||||
}
|
||||
@@ -476,22 +473,22 @@ class Walker {
|
||||
private _visitExportDeclaration(node: ts.ExportDeclaration, parent: AST.Module): void {
|
||||
if (node.moduleSpecifier !== undefined) {
|
||||
// export { foo } from "bar";
|
||||
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
|
||||
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
|
||||
for (const element of node.exportClause.elements) {
|
||||
const importedName = element.propertyName && element.propertyName.text || element.name.text;
|
||||
var importedName = element.propertyName && element.propertyName.text || element.name.text;
|
||||
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// export { foo };
|
||||
for (const element of node.exportClause.elements) {
|
||||
(parent.members[element.name.text] as AST.CanBePrivate).isPrivate = false;
|
||||
(<AST.CanBePrivate><any>parent.members[element.name.text]).isPrivate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _resolve(relativeModuleName: string, currentModule: AST.Module): string {
|
||||
let result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
|
||||
var result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
|
||||
|
||||
if (result[0] !== ".") {
|
||||
result = `./${ result }`;
|
||||
@@ -501,7 +498,7 @@ class Walker {
|
||||
}
|
||||
|
||||
private _parseJSDoc(node: ts.Node): JSDoc {
|
||||
let comments = oldGetLeadingCommentRangesOfNodeFromText(node, this._currentSourceFile.text);
|
||||
var comments = oldGetLeadingCommentRangesOfNode(node, this._currentSourceFile);
|
||||
|
||||
if (comments === undefined) {
|
||||
comments = [];
|
||||
@@ -511,41 +508,41 @@ class Walker {
|
||||
comments = [comments[comments.length - 1]];
|
||||
}
|
||||
|
||||
const comment =
|
||||
var comment =
|
||||
(comments.length === 0) ?
|
||||
"" :
|
||||
this._currentSourceFile.text.substring(comments[0].pos, comments[0].end);
|
||||
|
||||
const commentStartIndex = comment.indexOf("/**");
|
||||
const commentEndIndex = comment.lastIndexOf("*/");
|
||||
var commentStartIndex = comment.indexOf("/**");
|
||||
var commentEndIndex = comment.lastIndexOf("*/");
|
||||
|
||||
const lines =
|
||||
var lines =
|
||||
(commentStartIndex === -1 || commentEndIndex === -1) ?
|
||||
[] :
|
||||
comment.substring(commentStartIndex + 2, commentEndIndex).split("\n").map(line => {
|
||||
const match = line.match(/^[ \t]*\* (.*)/);
|
||||
var match = line.match(/^[ \t]*\* (.*)/);
|
||||
if (match === null) {
|
||||
return "";
|
||||
}
|
||||
return match[1];
|
||||
});
|
||||
|
||||
let rootDescription = "";
|
||||
var rootDescription = "";
|
||||
|
||||
const parameters: { [name: string]: AST.Parameter } = Object.create(null);
|
||||
var parameters: { [name: string]: AST.Parameter } = Object.create(null);
|
||||
|
||||
let typeAnnotation: string = null;
|
||||
var typeAnnotation: string = null;
|
||||
|
||||
let returnType: AST.ReturnType = null;
|
||||
var returnType: AST.ReturnType = null;
|
||||
|
||||
let isAbstract = false;
|
||||
var isAbstract = false;
|
||||
|
||||
let lastRead: { description: string } = null;
|
||||
var lastRead: { description: string } = null;
|
||||
|
||||
for (const line of lines) {
|
||||
const firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
|
||||
const firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
|
||||
let remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
|
||||
var firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
|
||||
var firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
|
||||
var remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
|
||||
|
||||
if (firstWord[0] === "@") {
|
||||
lastRead = null;
|
||||
@@ -556,33 +553,30 @@ class Walker {
|
||||
isAbstract = true;
|
||||
break;
|
||||
|
||||
case "@param": {
|
||||
let type: string;
|
||||
case "@param":
|
||||
var type: string;
|
||||
[type, remainingLine] = this._readType(remainingLine);
|
||||
|
||||
const [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
|
||||
var [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
|
||||
|
||||
const subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
|
||||
var subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
|
||||
if (subParameterMatch === null) {
|
||||
parameters[name] = lastRead = new AST.Parameter(name, description, type);
|
||||
}
|
||||
else {
|
||||
const parentName = subParameterMatch[1] || subParameterMatch[3];
|
||||
const childName = subParameterMatch[2] || subParameterMatch[4];
|
||||
const parentParameter = parameters[parentName];
|
||||
var parentName = subParameterMatch[1] || subParameterMatch[3];
|
||||
var childName = subParameterMatch[2] || subParameterMatch[4];
|
||||
var parentParameter = parameters[parentName];
|
||||
parentParameter.subParameters.push(lastRead = new AST.Parameter(childName, description, type));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "@return": {
|
||||
const [type, description] = this._readType(remainingLine);
|
||||
case "@return":
|
||||
var [type, description] = this._readType(remainingLine);
|
||||
|
||||
returnType = lastRead = new AST.ReturnType(description, type);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "@type":
|
||||
[typeAnnotation] = this._readType(remainingLine);
|
||||
@@ -613,9 +607,9 @@ class Walker {
|
||||
return ["*", remainingLine];
|
||||
}
|
||||
|
||||
let index = -1;
|
||||
let numberOfUnterminatedBraces = 0;
|
||||
for (let i = 0; i < remainingLine.length; i++) {
|
||||
var index = -1;
|
||||
var numberOfUnterminatedBraces = 0;
|
||||
for (var i = 0; i < remainingLine.length; i++) {
|
||||
if (remainingLine[i] === "{") {
|
||||
numberOfUnterminatedBraces++;
|
||||
}
|
||||
@@ -633,7 +627,7 @@ class Walker {
|
||||
throw new Error("Unterminated type specifier.");
|
||||
}
|
||||
|
||||
const type = remainingLine.substr(1, index - 1);
|
||||
var type = remainingLine.substr(1, index - 1);
|
||||
remainingLine = remainingLine.substr(index + 1).replace(/^\s+/, "");
|
||||
|
||||
return [type, remainingLine];
|
||||
@@ -647,24 +641,16 @@ class Walker {
|
||||
return signatureDeclaration.typeParameters.map(typeParameter => typeParameter.name.text);
|
||||
}
|
||||
|
||||
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.ExpressionWithTypeArguments, intrinsicGenerics: string[]): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
|
||||
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.ExpressionWithTypeArguments): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
|
||||
if (typeReferenceNode.typeArguments === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const typeReference = this._typeChecker.getTypeAtLocation(typeReferenceNode) as ts.TypeReference;
|
||||
var typeReference = <ts.TypeReference>this._typeChecker.getTypeAtLocation(typeReferenceNode);
|
||||
|
||||
return typeReference.typeArguments.map(typeArgument => {
|
||||
if ((typeArgument as ts.IntrinsicType).intrinsicName !== undefined) {
|
||||
return new AST.IntrinsicTypeReference((typeArgument as ts.IntrinsicType).intrinsicName);
|
||||
}
|
||||
|
||||
if (typeArgument.flags & ts.TypeFlags.TypeParameter) {
|
||||
if (intrinsicGenerics.indexOf(typeArgument.symbol.name) !== -1) {
|
||||
return new AST.IntrinsicTypeReference(typeArgument.symbol.name);
|
||||
}
|
||||
|
||||
throw new Error(`Unbound type parameter ${ typeArgument.symbol.name }`);
|
||||
if ((<ts.IntrinsicType>typeArgument).intrinsicName !== undefined) {
|
||||
return new AST.IntrinsicTypeReference((<ts.IntrinsicType>typeArgument).intrinsicName);
|
||||
}
|
||||
|
||||
return new AST.UnresolvedType(typeArgument.symbol, []);
|
||||
@@ -683,12 +669,12 @@ class Walker {
|
||||
|
||||
private _connectParameters(astParameters: ts.ParameterDeclaration[], jsDocParameters: { [name: string]: AST.Parameter }, onMissingMessageCallback: (parameterName: string) => string) {
|
||||
return astParameters.map(parameter => {
|
||||
let parameterName = (parameter.name as ts.Identifier).text;
|
||||
var parameterName = (<ts.Identifier>parameter.name).text;
|
||||
if (parameterName[0] === "_") {
|
||||
parameterName = parameterName.substr(1);
|
||||
}
|
||||
|
||||
let jsDocParameter = jsDocParameters[parameterName];
|
||||
var jsDocParameter = jsDocParameters[parameterName];
|
||||
|
||||
if (jsDocParameter === undefined) {
|
||||
this._notifyIncorrectJsDoc(onMissingMessageCallback.call(this, parameterName));
|
||||
@@ -700,8 +686,8 @@ class Walker {
|
||||
}
|
||||
|
||||
private _notifyIncorrectJsDoc(message: string): void {
|
||||
const fileName = path.basename(this._currentSourceFile.fileName);
|
||||
if (fileName === "lib.es5.d.ts" || fileName === "lib.dom.d.ts") {
|
||||
var fileName = path.basename(this._currentSourceFile.fileName);
|
||||
if (fileName === "lib.core.d.ts" || fileName === "lib.dom.d.ts") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -710,17 +696,17 @@ class Walker {
|
||||
|
||||
link(rootNamespaceName: string): void {
|
||||
for (const moduleName of Object.keys(this.modules)) {
|
||||
const module = this.modules[moduleName];
|
||||
var module = this.modules[moduleName];
|
||||
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
const member = module.members[memberName];
|
||||
var member = module.members[memberName];
|
||||
|
||||
if (member instanceof AST.Class) {
|
||||
if (member.unresolvedBaseType instanceof AST.UnresolvedType) {
|
||||
member.baseType = this._resolveTypeReference(member.unresolvedBaseType);
|
||||
member.baseType = this._resolveTypeReference(<AST.UnresolvedType>member.unresolvedBaseType);
|
||||
}
|
||||
else {
|
||||
member.baseType = member.unresolvedBaseType;
|
||||
member.baseType = <AST.TypeReference | AST.IntrinsicTypeReference>member.unresolvedBaseType;
|
||||
}
|
||||
|
||||
member.interfaces = member.unresolvedInterfaces.map(interfase => {
|
||||
@@ -728,7 +714,7 @@ class Walker {
|
||||
return this._resolveTypeReference(interfase);
|
||||
}
|
||||
|
||||
return interfase;
|
||||
return <AST.TypeReference | AST.IntrinsicTypeReference>interfase;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -738,12 +724,12 @@ class Walker {
|
||||
return this._resolveTypeReference(baseType);
|
||||
}
|
||||
|
||||
return baseType;
|
||||
return <AST.TypeReference | AST.IntrinsicTypeReference>baseType;
|
||||
});
|
||||
}
|
||||
|
||||
else if (member instanceof AST.Enum) {
|
||||
let value = 0;
|
||||
var value = 0;
|
||||
for (const enumMember of member.members) {
|
||||
if (enumMember.value === null) {
|
||||
enumMember.value = value;
|
||||
@@ -765,17 +751,17 @@ class Walker {
|
||||
|
||||
private _moduleToNamespace(module: AST.Module): void {
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
let member = module.members[memberName];
|
||||
var member = module.members[memberName];
|
||||
|
||||
if (member instanceof AST.Reference) {
|
||||
if (member.isPrivate) {
|
||||
if ((<AST.Reference>member).isPrivate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member.name === "*") {
|
||||
const newNamespace = this._scope.enter(new AST.Namespace(memberName));
|
||||
var newNamespace = this._scope.enter(new AST.Namespace(memberName));
|
||||
|
||||
const existingNamespace = this.namespaces[newNamespace.fullName];
|
||||
var existingNamespace = this.namespaces[newNamespace.fullName];
|
||||
if (existingNamespace !== undefined) {
|
||||
this._scope.leave();
|
||||
this._scope.enter(existingNamespace);
|
||||
@@ -784,10 +770,10 @@ class Walker {
|
||||
this.namespaces[newNamespace.fullName] = newNamespace;
|
||||
}
|
||||
|
||||
let referencedModuleName = member.moduleName;
|
||||
let referencedModule = this.modules[referencedModuleName];
|
||||
var referencedModuleName = (<AST.Reference>member).moduleName;
|
||||
var referencedModule = this.modules[referencedModuleName];
|
||||
if (referencedModule === undefined && ((referencedModuleName + "/index") in this.modules)) {
|
||||
member.moduleName = referencedModuleName = referencedModuleName + "/index";
|
||||
(<AST.Reference>member).moduleName = referencedModuleName = referencedModuleName + "/index";
|
||||
referencedModule = this.modules[referencedModuleName];
|
||||
}
|
||||
this._moduleToNamespace(referencedModule);
|
||||
@@ -796,61 +782,56 @@ class Walker {
|
||||
}
|
||||
else {
|
||||
while (member instanceof AST.Reference) {
|
||||
member = this.modules[member.moduleName].members[member.name];
|
||||
member = this.modules[(<AST.Reference>member).moduleName].members[member.name];
|
||||
}
|
||||
|
||||
this._scope.enter(member);
|
||||
this._scope.enter(<AST.NamespaceMember><any>member);
|
||||
this._scope.leave();
|
||||
(this._scope.current as AST.Namespace).members[member.name] = member;
|
||||
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
|
||||
}
|
||||
}
|
||||
else if (!(member as AST.CanBePrivate).isPrivate) {
|
||||
this._scope.enter(member);
|
||||
else if (!(<AST.CanBePrivate><any>member).isPrivate) {
|
||||
this._scope.enter(<AST.NamespaceMember>member);
|
||||
this._scope.leave();
|
||||
(this._scope.current as AST.Namespace).members[member.name] = member;
|
||||
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveTypeReference(unresolvedType: AST.UnresolvedType): AST.TypeReference | AST.IntrinsicTypeReference {
|
||||
let node: ts.Node = unresolvedType.symbol.declarations[0];
|
||||
private _resolveTypeReference(unresolvedType: AST.UnresolvedType): AST.TypeReference {
|
||||
var node: ts.Node = unresolvedType.symbol.declarations[0];
|
||||
while (node.kind !== ts.SyntaxKind.SourceFile) {
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
const sourceFile = node as ts.SourceFile;
|
||||
var sourceFile = <ts.SourceFile>node;
|
||||
|
||||
if (sourceFile.fileName.substr(-"globals.d.ts".length) === "globals.d.ts") {
|
||||
return new AST.IntrinsicTypeReference(unresolvedType.symbol.name);
|
||||
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
|
||||
var module = this.modules[moduleName];
|
||||
|
||||
var result = module.members[unresolvedType.symbol.name];
|
||||
|
||||
if (result === undefined) {
|
||||
throw new Error(`Type ${ unresolvedType.symbol.name } could not be resolved.`);
|
||||
}
|
||||
else {
|
||||
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
|
||||
const module = this.modules[moduleName];
|
||||
|
||||
let result = module.members[unresolvedType.symbol.name];
|
||||
while (result instanceof AST.Reference) {
|
||||
result = this.modules[(<AST.Reference>result).moduleName].members[result.name];
|
||||
}
|
||||
|
||||
if (result === undefined) {
|
||||
throw new Error(`Type ${unresolvedType.symbol.name} could not be resolved.`);
|
||||
var resultGenerics = unresolvedType.generics.map(generic => {
|
||||
if (generic instanceof AST.UnresolvedType) {
|
||||
return this._resolveTypeReference(generic);
|
||||
}
|
||||
|
||||
while (result instanceof AST.Reference) {
|
||||
result = this.modules[result.moduleName].members[result.name];
|
||||
}
|
||||
return <AST.IntrinsicTypeReference>generic;
|
||||
});
|
||||
|
||||
const resultGenerics = unresolvedType.generics.map(generic => {
|
||||
if (generic instanceof AST.UnresolvedType) {
|
||||
return this._resolveTypeReference(generic);
|
||||
}
|
||||
|
||||
return generic;
|
||||
});
|
||||
|
||||
return new AST.TypeReference(result, resultGenerics);
|
||||
}
|
||||
return new AST.TypeReference(<AST.NamespaceMember><any>result, resultGenerics);
|
||||
}
|
||||
|
||||
private _moduleNameFromFileName(fileName: string): string {
|
||||
let result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
|
||||
var result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
|
||||
|
||||
result = result.substr(0, result.length - ".ts".length);
|
||||
|
||||
@@ -863,16 +844,18 @@ class Walker {
|
||||
}
|
||||
|
||||
export function walk(compiler: Compiler, root: string, rootNamespaceName: string) {
|
||||
const sourceFiles = compiler.sourceFiles;
|
||||
var sourceFiles = compiler.sourceFiles;
|
||||
var rootFileName = ts.normalizeSlashes(path.resolve(root));
|
||||
var rootSourceFile = sourceFiles.filter(sourceFile => sourceFile.fileName === rootFileName)[0];
|
||||
|
||||
const walker = new Walker(compiler);
|
||||
var walker = new Walker(compiler);
|
||||
|
||||
// Walk
|
||||
for (const sourceFile of sourceFiles) {
|
||||
if (
|
||||
path.basename(sourceFile.fileName) === "lib.es5.d.ts" ||
|
||||
path.basename(sourceFile.fileName) === "lib.core.d.ts" ||
|
||||
path.basename(sourceFile.fileName) === "lib.dom.d.ts" ||
|
||||
sourceFile.fileName.substr(-"globals.d.ts".length) === "globals.d.ts"
|
||||
sourceFile.fileName.substr(-"references.d.ts".length) === "references.d.ts"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -26,52 +26,108 @@ var UglifyJS = require("uglify-js");
|
||||
var FileTransform = require("async-build").FileTransform;
|
||||
|
||||
var Run = (function () {
|
||||
function Run(outputLibraryName, unusedVarsToIgnore) {
|
||||
function Run(entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
this._entry = path.resolve(entry).replace(/\\/g, "/");
|
||||
this._outputLibraryName = outputLibraryName;
|
||||
this._unusedVarsToIgnore = unusedVarsToIgnore;
|
||||
|
||||
this._root = UglifyJS.parse(fs.readFileSync(path.resolve(__filename, "..", "umd-wrapper.js"), "utf8"));
|
||||
this._root = UglifyJS.parse(
|
||||
'(function (root, factory) {\n' +
|
||||
' var global = this;\n' +
|
||||
'\n' +
|
||||
' if (typeof define === "function" && define.amd) {\n' +
|
||||
' define([], function() {\n' +
|
||||
' return factory(global);\n' +
|
||||
' });\n' +
|
||||
' }\n' +
|
||||
' else if (typeof exports === "object" && typeof module === "object") {\n' +
|
||||
' module.exports = factory(global);\n' +
|
||||
' }\n' +
|
||||
' else if (typeof exports === "object") {\n' +
|
||||
' exports.libjass = factory(global);\n' +
|
||||
' }\n' +
|
||||
' else {\n' +
|
||||
' root.libjass = factory(global);\n' +
|
||||
' }\n' +
|
||||
'})(this, function (global) {\n' +
|
||||
' "use strict";\n' +
|
||||
' return (function (modules) {\n' +
|
||||
' var installedModules = Object.create(null);\n' +
|
||||
' function require(moduleId) {\n' +
|
||||
' if (installedModules[moduleId]) {\n' +
|
||||
' return installedModules[moduleId];\n' +
|
||||
' }\n' +
|
||||
'\n' +
|
||||
' var exports = installedModules[moduleId] = Object.create(null);\n' +
|
||||
' modules[moduleId](exports, require);\n' +
|
||||
' return exports;\n' +
|
||||
' }\n' +
|
||||
'\n' +
|
||||
' return require(0);\n' +
|
||||
' })([\n' +
|
||||
' ]);\n' +
|
||||
'});');
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
this._toInsert = null;
|
||||
this._licenseHeader = null;
|
||||
|
||||
this._rootSourceMap = null;
|
||||
this._outputModulesArray = this._root.body[0].body.args[1].body[1].value.args[0].elements;
|
||||
|
||||
this._modules = Object.create(null);
|
||||
|
||||
this._rootSourceMap = UjsSourceMap({ file: this._outputLibraryName + ".js", root: "" });
|
||||
}
|
||||
|
||||
Run.prototype.addFile = function (file) {
|
||||
var _this = this;
|
||||
|
||||
var moduleName = (path.extname(file.path) === ".map") ? file.path.substr(0, file.path.length - ".js.map".length) : file.path.substr(0, file.path.length - ".js".length);
|
||||
if (!(moduleName in this._modules)) {
|
||||
this._modules[moduleName] = { id: null, root: null };
|
||||
}
|
||||
|
||||
var module = this._modules[moduleName];
|
||||
|
||||
var filenameWithoutExtension = path.relative(path.join(this._entry, ".."), moduleName).replace(/\\/g, "/");
|
||||
var tsFilename = filenameWithoutExtension + ".ts";
|
||||
var jsFilename = filenameWithoutExtension + ".js";
|
||||
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
try {
|
||||
this._toInsert = UglifyJS.parse(file.contents.toString(), {
|
||||
filename: path.basename(file.path),
|
||||
toplevel: null,
|
||||
}).body;
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex instanceof UglifyJS.JS_Parse_Error) {
|
||||
throw new Error("UglifyJS parse error: " + ex.toString() + "\n");
|
||||
}
|
||||
module.root = UglifyJS.parse(file.contents.toString(), {
|
||||
filename: jsFilename,
|
||||
toplevel: null,
|
||||
});
|
||||
|
||||
throw ex;
|
||||
if (this._licenseHeader === null) {
|
||||
module.root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (_this._licenseHeader !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.start) {
|
||||
(node.start.comments_before || []).some(function (comment, i) {
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
_this._licenseHeader = comment;
|
||||
_this._licenseHeader.value = _this._licenseHeader.value.split("\n").map(function (line) { return line.replace(/^\t/, ""); }).join("\n");
|
||||
node.start.comments_before.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}));
|
||||
this._root.start.comments_before = [this._licenseHeader];
|
||||
this._root.start.nlb = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case ".map":
|
||||
var rawSourceMap = JSON.parse(file.contents.toString());
|
||||
|
||||
this._rootSourceMap = UglifyJS.SourceMap({
|
||||
file: this._outputLibraryName + ".js",
|
||||
root: "",
|
||||
orig: rawSourceMap,
|
||||
});
|
||||
|
||||
var generator = this._rootSourceMap.get();
|
||||
|
||||
rawSourceMap.sources.forEach(function (sourceRelativePath, index) {
|
||||
generator.setSourceContent(sourceRelativePath, rawSourceMap.sourcesContent[index]);
|
||||
});
|
||||
|
||||
var sourceMapContents = JSON.parse(file.contents.toString());
|
||||
sourceMapContents.sources[0] = tsFilename;
|
||||
sourceMapContents.file = jsFilename;
|
||||
this._rootSourceMap.addInput(sourceMapContents);
|
||||
this._rootSourceMap.get().setSourceContent(tsFilename, fs.readFileSync(moduleName + ".ts", { encoding: "utf8" }));
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -79,72 +135,63 @@ var Run = (function () {
|
||||
Run.prototype.build = function (outputStream) {
|
||||
var _this = this;
|
||||
|
||||
// Assign IDs to all modules
|
||||
var moduleNames = Object.keys(this._modules);
|
||||
moduleNames.sort(function (name1, name2) { return (name1 === name2) ? 0 : (name1 < name2 ? -1 : 1); });
|
||||
moduleNames.unshift.apply(moduleNames, moduleNames.splice(moduleNames.indexOf(this._entry), 1));
|
||||
|
||||
// Splice in the TS output into the UMD wrapper.
|
||||
var insertionParent = this._root.body[0].body.args[1].body;
|
||||
moduleNames.forEach(function (moduleName, index) {
|
||||
_this._modules[moduleName].id = index;
|
||||
});
|
||||
|
||||
this._toInsert.reverse();
|
||||
for (var i = this._toInsert.length - 1; i >= 0; i--) {
|
||||
var node = this._toInsert[i];
|
||||
if (node instanceof UglifyJS.AST_Var) {
|
||||
for (var j = 0; j < node.definitions.length; j++) {
|
||||
var definition = node.definitions[j];
|
||||
if (definition.name.name === "__extends" || definition.name.name === "__decorate") {
|
||||
definition.value = definition.value.right;
|
||||
|
||||
// Merge modules
|
||||
moduleNames.forEach(function (moduleName) {
|
||||
var module = _this._modules[moduleName];
|
||||
|
||||
module.root.body.forEach(function (statement) {
|
||||
if (statement instanceof UglifyJS.AST_Var && statement.definitions.length === 1) {
|
||||
if (
|
||||
statement.definitions[0].value instanceof UglifyJS.AST_Call &&
|
||||
statement.definitions[0].value.expression.name === "require"
|
||||
) {
|
||||
var importRelativePath = statement.definitions[0].value.args[0].value;
|
||||
var importAbsolutePath = path.join(moduleName, "..", importRelativePath).replace(/\\/g, "/");
|
||||
var stringArg = statement.definitions[0].value.args[0];
|
||||
var importedModule = _this._modules[importAbsolutePath];
|
||||
if (importedModule === undefined) {
|
||||
importedModule = _this._modules[importAbsolutePath + "/index"];
|
||||
}
|
||||
statement.definitions[0].value.args[0] = new UglifyJS.AST_Number({ start: stringArg.start, end: stringArg.end, value: importedModule.id });
|
||||
}
|
||||
else if (statement.definitions[0].name.name === "__extends") {
|
||||
var importAbsolutePath = path.join(_this._entry, "..", "utility", "ts-helpers").replace(/\\/g, "/");
|
||||
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__extends').body[0].body;
|
||||
}
|
||||
else if (statement.definitions[0].name.name === "__decorate") {
|
||||
var importAbsolutePath = path.join(_this._entry, "..", "utility", "ts-helpers").replace(/\\/g, "/");
|
||||
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__decorate').body[0].body;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
insertionParent.splice(-1, 0, node);
|
||||
this._toInsert.splice(i, 1);
|
||||
}
|
||||
}
|
||||
var wrapper = UglifyJS.parse('/* ' + module.id + ' ./' + path.relative(path.join(_this._entry, ".."), moduleName).replace(/\\/g, "/") + ' */ function x(exports, require) { }');
|
||||
var func = wrapper.body[0];
|
||||
func.body = module.root.body;
|
||||
func.name = null;
|
||||
_this._outputModulesArray.push(func);
|
||||
});
|
||||
|
||||
insertionParent.splice.apply(insertionParent, [-1, 0].concat(this._toInsert));
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Fixups
|
||||
for (var i = 0; i < this._toInsert.length; i++) {
|
||||
var node = this._toInsert[i];
|
||||
if (node instanceof UglifyJS.AST_Statement && node.body instanceof UglifyJS.AST_Call && node.body.expression.name === "define") {
|
||||
var defineCall = node.body;
|
||||
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
|
||||
var haveUnusedVars = false;
|
||||
|
||||
defineCall.expression.name = "def";
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove;
|
||||
|
||||
if (defineCall.args[1].elements[0].value !== "require") {
|
||||
throw new Error("Expected first dep to be require");
|
||||
}
|
||||
defineCall.args[1].elements.shift();
|
||||
defineCall.args[2].argnames.shift();
|
||||
|
||||
if (defineCall.args[1].elements[0].value !== "exports") {
|
||||
throw new Error("Expected second dep to be exports");
|
||||
}
|
||||
defineCall.args[1].elements.shift();
|
||||
defineCall.args[2].argnames.push(defineCall.args[2].argnames.shift());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove all license headers except the one from the UMD wrapper
|
||||
var firstLicenseHeader = null;
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node.start) {
|
||||
(node.start.comments_before || []).some(function (comment, i) {
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
if (firstLicenseHeader === null) {
|
||||
firstLicenseHeader = comment;
|
||||
}
|
||||
else if (comment !== firstLicenseHeader) {
|
||||
node.start.comments_before.splice(i, 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}));
|
||||
nodesToRemove = [];
|
||||
|
||||
|
||||
// Fixup anonymous functions to print a space after "function"
|
||||
@@ -160,7 +207,7 @@ var Run = (function () {
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node.start && node.start.comments_before) {
|
||||
node.start.comments_before.forEach(function (comment) {
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
if (comment === _this._licenseHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,7 +216,7 @@ var Run = (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
var indent = " "; // 5 spaces
|
||||
var indent = " "; // 9 spaces
|
||||
for (var i = 0; i < comment.col; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
@@ -183,16 +230,8 @@ var Run = (function () {
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove = [];
|
||||
|
||||
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
|
||||
var haveUnusedVars = false;
|
||||
|
||||
// Repeat because removing some declarations may make others unreferenced
|
||||
for (;;) {
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
// Unreferenced variable and function declarations, and unreferenced terminal function arguments
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
|
||||
@@ -227,6 +266,8 @@ var Run = (function () {
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -274,25 +315,39 @@ var Run = (function () {
|
||||
|
||||
// Output
|
||||
var output = {
|
||||
source_map: this._rootSourceMap,
|
||||
ascii_only: true,
|
||||
source_map: _this._rootSourceMap,
|
||||
beautify: true,
|
||||
comments: function (node, comment) {
|
||||
return comment.value.indexOf("tslint") === -1;
|
||||
},
|
||||
// If this is the first license header, keep it.
|
||||
if (comment === _this._licenseHeader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this is a license header, remove it.
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this is a TypeScript reference comment, strip it.
|
||||
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
this._root.print(stream);
|
||||
|
||||
outputStream.push({
|
||||
path: this._outputLibraryName + ".js",
|
||||
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + this._outputLibraryName + ".js.map")])
|
||||
path: _this._outputLibraryName + ".js",
|
||||
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + _this._outputLibraryName + ".js.map")])
|
||||
});
|
||||
|
||||
outputStream.push({
|
||||
path: this._outputLibraryName + ".js.map",
|
||||
contents: new Buffer(this._rootSourceMap.get().toString())
|
||||
path: _this._outputLibraryName + ".js.map",
|
||||
contents: new Buffer(_this._rootSourceMap.get().toString())
|
||||
});
|
||||
|
||||
// Print unused variables
|
||||
@@ -311,8 +366,8 @@ var Run = (function () {
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
build: function (outputLibraryName, unusedVarsToIgnore) {
|
||||
var run = new Run(outputLibraryName, unusedVarsToIgnore);
|
||||
build: function (entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
|
||||
|
||||
return new FileTransform(function (file) {
|
||||
run.addFile(file);
|
||||
@@ -321,7 +376,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
watch: function (outputLibraryName, unusedVarsToIgnore) {
|
||||
watch: function (entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
var files = Object.create(null);
|
||||
|
||||
return new FileTransform(function (file) {
|
||||
@@ -329,7 +384,7 @@ module.exports = {
|
||||
files[file.path] = file;
|
||||
}
|
||||
else {
|
||||
var run = new Run(outputLibraryName, unusedVarsToIgnore);
|
||||
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
|
||||
Object.keys(files).forEach(function (filename) {
|
||||
run.addFile(files[filename]);
|
||||
});
|
||||
@@ -397,7 +452,6 @@ module.exports = {
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
ascii_only: true,
|
||||
comments: function (node, comment) {
|
||||
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
@@ -435,6 +489,53 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
var SourceMap = require.cache[require.resolve("uglify-js")].require("source-map");
|
||||
|
||||
function UjsSourceMap(options) {
|
||||
var orig_maps = Object.create(null);
|
||||
|
||||
var generator = new SourceMap.SourceMapGenerator({
|
||||
file: options.file,
|
||||
sourceRoot: options.root,
|
||||
});
|
||||
|
||||
return {
|
||||
addInput: function (rawSourceMap) {
|
||||
var consumer = new SourceMap.SourceMapConsumer(rawSourceMap);
|
||||
orig_maps[consumer.file] = consumer;
|
||||
},
|
||||
add: function (source, gen_line, gen_col, orig_line, orig_col, name) {
|
||||
var originalMap = orig_maps[source];
|
||||
if (originalMap === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var info = originalMap.originalPositionFor({
|
||||
line: orig_line,
|
||||
column: orig_col
|
||||
});
|
||||
|
||||
if (info.source === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
source = info.source;
|
||||
orig_line = info.line;
|
||||
orig_col = info.column;
|
||||
name = info.name || name;
|
||||
|
||||
generator.addMapping({
|
||||
generated : { line: gen_line, column: gen_col },
|
||||
original : { line: orig_line, column: orig_col },
|
||||
source : source,
|
||||
name : name
|
||||
});
|
||||
},
|
||||
get: function () { return generator; },
|
||||
toString: function () { return generator.toString(); },
|
||||
};
|
||||
}
|
||||
|
||||
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
|
||||
|
||||
// Workaround for https://github.com/mishoo/UglifyJS2/issues/789 - Nodes explicitly marked with ujs:unreferenced will not be warned for.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @license
|
||||
*/
|
||||
|
||||
(function (root, factory) {
|
||||
var global = this;
|
||||
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define([], function() {
|
||||
return factory(global);
|
||||
});
|
||||
}
|
||||
else if (typeof exports === "object" && typeof module === "object") {
|
||||
module.exports = factory(global);
|
||||
}
|
||||
else if (typeof exports === "object") {
|
||||
exports.libjass = factory(global);
|
||||
}
|
||||
else {
|
||||
root.libjass = factory(global);
|
||||
}
|
||||
})(this, function (global) {
|
||||
"use strict";
|
||||
|
||||
var registeredModules = Object.create(null);
|
||||
var installedModules = Object.create(null);
|
||||
|
||||
function def(moduleId, deps, body) {
|
||||
installedModules[moduleId] = { deps: deps, body: body, };
|
||||
}
|
||||
|
||||
function req(moduleId) {
|
||||
if (moduleId in registeredModules) {
|
||||
return registeredModules[moduleId];
|
||||
}
|
||||
|
||||
var exports = registeredModules[moduleId] = Object.create(null);
|
||||
var deps = installedModules[moduleId].deps.map(req);
|
||||
deps.push(exports);
|
||||
|
||||
installedModules[moduleId].body.apply(null, deps);
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
return req("index");
|
||||
});
|
||||
@@ -98,7 +98,3 @@
|
||||
.libjass-filters {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.libjass-filters * {
|
||||
color-interpolation-filters: sRGB;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"main": "lib/libjass.js",
|
||||
"scripts": {
|
||||
"prepublish": "node ./build.js clean default",
|
||||
"build": "tsc -p ./build/tsconfig.json",
|
||||
"build": "tsc ./build/typescript/index.ts ./build/doc.ts ./build/node.d.ts ./build/typescript/typescript.d.ts ./node_modules/async-build/typings.d.ts -m commonjs -t es5 -noImplicitAny --moduleResolution classic",
|
||||
"test": "node ./build.js test",
|
||||
"test-lib": "intern-client config=tests/intern reporters=Pretty",
|
||||
"test-minified": "intern-client config=tests/intern reporters=Pretty minified=true",
|
||||
@@ -26,12 +26,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "1.x >=1.4",
|
||||
"async-build": "0.3.1",
|
||||
"intern": "3.x >=3.2.0",
|
||||
"npm": "4.x",
|
||||
"pngjs": "3.x",
|
||||
"async-build": "0.2.0",
|
||||
"firefox-profile": "0.3.x",
|
||||
"intern": "3.x >=3.0.1",
|
||||
"npm": "3.x",
|
||||
"pngjs": "2.2.0",
|
||||
"sax": "1.x",
|
||||
"typescript": "2.0.10",
|
||||
"typescript": "1.7.5",
|
||||
"uglify-js": "2.x >=2.4.24"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface Array<T> {
|
||||
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
|
||||
}
|
||||
|
||||
interface WorkerGlobalScope {
|
||||
postMessage(message: any): void;
|
||||
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
|
||||
}
|
||||
|
||||
interface FontFace {
|
||||
family: string;
|
||||
load(): Promise<FontFace>;
|
||||
}
|
||||
|
||||
interface FontFaceSet {
|
||||
add(fontFace: FontFace): FontFaceSet;
|
||||
forEach(callbackfn: (fontFace: FontFace, index: FontFace, set: FontFaceSet) => void, thisArg?: any): void;
|
||||
}
|
||||
|
||||
interface Map<K, V> {
|
||||
size: number;
|
||||
get(key: K): V | undefined;
|
||||
has(key: K): boolean;
|
||||
set(key: K, value: V): this;
|
||||
delete(key: K): boolean;
|
||||
clear(): void;
|
||||
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
cloneNode(deep?: boolean): this;
|
||||
}
|
||||
|
||||
interface Promise<T> extends Thenable<T> {
|
||||
then<U>(onFulfilled: (value: T) => Thenable<U>, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
|
||||
/* tslint:disable-next-line:unified-signatures */
|
||||
then<U>(onFulfilled: (value: T) => U, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
|
||||
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T>;
|
||||
}
|
||||
|
||||
interface ReadableStream {
|
||||
getReader(): ReadableStreamReader;
|
||||
}
|
||||
|
||||
interface ReadableStreamReader {
|
||||
read(): Promise<{ value: Uint8Array; done: boolean; }>;
|
||||
}
|
||||
|
||||
interface Set<T> {
|
||||
size: number;
|
||||
add(value: T): this;
|
||||
clear(): void;
|
||||
has(value: T): boolean;
|
||||
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void;
|
||||
}
|
||||
|
||||
interface SVGFEComponentTransferElement {
|
||||
appendChild(newChild: SVGFEFuncAElement): SVGFEFuncAElement;
|
||||
appendChild(newChild: SVGFEFuncBElement): SVGFEFuncBElement;
|
||||
appendChild(newChild: SVGFEFuncGElement): SVGFEFuncGElement;
|
||||
appendChild(newChild: SVGFEFuncRElement): SVGFEFuncRElement;
|
||||
}
|
||||
|
||||
interface SVGFEMergeElement {
|
||||
appendChild(newChild: SVGFEMergeNodeElement): SVGFEMergeNodeElement;
|
||||
}
|
||||
|
||||
interface TextDecoder {
|
||||
decode(input: ArrayBuffer | ArrayBufferView, options: { stream: boolean }): string;
|
||||
}
|
||||
|
||||
interface Thenable<T> {
|
||||
then: ThenableThen<T>;
|
||||
}
|
||||
|
||||
type ThenableThen<T> = (this: Thenable<T>, resolve: ((resolution: T | Thenable<T>) => void) | undefined, reject: ((reason: any) => void) | undefined) => void;
|
||||
|
||||
/**
|
||||
* The interface implemented by a communication channel to the other side.
|
||||
*/
|
||||
interface WorkerCommunication {
|
||||
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
|
||||
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
|
||||
postMessage(message: any): void;
|
||||
}
|
||||
|
||||
declare const exports: any;
|
||||
|
||||
declare const global: (WorkerGlobalScope) & {
|
||||
FontFace?: {
|
||||
new (family: string, source: string): FontFace;
|
||||
};
|
||||
|
||||
Map?: {
|
||||
new <K, V>(iterable?: [K, V][]): Map<K, V>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Map<any, any> | { forEach: undefined };
|
||||
};
|
||||
|
||||
MutationObserver?: typeof MutationObserver;
|
||||
|
||||
Promise?: {
|
||||
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Promise<any>;
|
||||
resolve<T>(value: T | Thenable<T>): Promise<T>;
|
||||
reject<T>(reason: any): Promise<T>;
|
||||
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
|
||||
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
|
||||
};
|
||||
|
||||
ReadableStream?: {
|
||||
prototype: ReadableStream | { getReader: undefined; };
|
||||
};
|
||||
|
||||
Set?: {
|
||||
new <T>(iterable?: T[]): Set<T>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Set<any> | { forEach: undefined };
|
||||
};
|
||||
|
||||
TextDecoder?: { new (encoding: string, options: { ignoreBOM: boolean }): TextDecoder };
|
||||
|
||||
WebkitMutationObserver?: typeof MutationObserver;
|
||||
|
||||
Worker?: typeof Worker,
|
||||
|
||||
WorkerGlobalScope?: {
|
||||
prototype: WorkerGlobalScope;
|
||||
new (): WorkerGlobalScope;
|
||||
};
|
||||
|
||||
document?: {
|
||||
currentScript?: HTMLScriptElement;
|
||||
fonts?: FontFaceSet;
|
||||
};
|
||||
|
||||
fetch?(url: string): Promise<{ body: ReadableStream; ok?: boolean; status?: number; }>;
|
||||
|
||||
process?: {
|
||||
nextTick?(callback: () => void): void;
|
||||
}
|
||||
};
|
||||
@@ -42,8 +42,6 @@ export { parser };
|
||||
import * as renderers from "./renderers";
|
||||
export { renderers };
|
||||
|
||||
export { serialize, deserialize } from "./serialization";
|
||||
|
||||
export { ASS } from "./types/ass";
|
||||
export { Attachment, AttachmentType } from "./types/attachment";
|
||||
export { Dialogue } from "./types/dialogue";
|
||||
@@ -52,86 +50,28 @@ export { Style } from "./types/style";
|
||||
|
||||
export { BorderStyle, Format, WrappingStyle } from "./types/misc";
|
||||
|
||||
export { version } from "./version";
|
||||
|
||||
/**
|
||||
* Configures libjass with the given properties.
|
||||
*
|
||||
* @param {!*} newConfig
|
||||
* @param {?boolean} newConfig["debugMode"] When true, libjass logs some debug messages.
|
||||
* @param {?boolean} newConfig["verboseMode"] When true, libjass logs some more debug messages. This setting is independent of {@link libjass.debugMode}
|
||||
* @param {?function(new:Set, !Array.<T>=)} newConfig["Set"] Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
|
||||
* @param {?function(new:Map, !Array.<!Array.<*>>=)} newConfig["Map"] Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
|
||||
* @param {?function(new:Promise)} newConfig["Promise"] Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
|
||||
*/
|
||||
export function configure(newConfig: {
|
||||
debugMode?: boolean,
|
||||
verboseMode?: boolean,
|
||||
Set?: typeof set.Set | null,
|
||||
Map?: typeof map.Map | null,
|
||||
Promise?: typeof promise.Promise | null,
|
||||
}): void {
|
||||
if (typeof newConfig.debugMode === "boolean") {
|
||||
settings.setDebugMode(newConfig.debugMode);
|
||||
}
|
||||
|
||||
if (typeof newConfig.verboseMode === "boolean") {
|
||||
settings.setVerboseMode(newConfig.verboseMode);
|
||||
}
|
||||
|
||||
if (typeof newConfig.Set === "function" || newConfig.Set === null) {
|
||||
set.setImplementation(newConfig.Set);
|
||||
}
|
||||
|
||||
if (typeof newConfig.Map === "function" || newConfig.Map === null) {
|
||||
map.setImplementation(newConfig.Map);
|
||||
}
|
||||
|
||||
if (typeof newConfig.Promise === "function" || newConfig.Promise === null) {
|
||||
promise.setImplementation(newConfig.Promise);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters below are to work around https://github.com/Microsoft/TypeScript/issues/6366
|
||||
declare const exports: any;
|
||||
|
||||
Object.defineProperties(exports, {
|
||||
debugMode: {
|
||||
get: () => settings.debugMode,
|
||||
set: value => {
|
||||
console.warn("Setter `libjass.debugMode = value` has been deprecated. Use `libjass.configure({ debugMode: value })` instead.");
|
||||
settings.setDebugMode(value);
|
||||
},
|
||||
set: settings.setDebugMode,
|
||||
},
|
||||
|
||||
verboseMode: {
|
||||
get: () => settings.verboseMode,
|
||||
set: value => {
|
||||
console.warn("Setter `libjass.verboseMode = value` has been deprecated. Use `libjass.configure({ verboseMode: value })` instead.");
|
||||
settings.setVerboseMode(value);
|
||||
},
|
||||
set: settings.setVerboseMode,
|
||||
},
|
||||
|
||||
Set: {
|
||||
get: () => set.Set,
|
||||
set: value => {
|
||||
console.warn("Setter `libjass.Set = value` has been deprecated. Use `libjass.configure({ Set: value })` instead.");
|
||||
set.setImplementation(value);
|
||||
},
|
||||
set: set.setImplementation,
|
||||
},
|
||||
|
||||
Map: {
|
||||
get: () => map.Map,
|
||||
set: value => {
|
||||
console.warn("Setter `libjass.Map = value` has been deprecated. Use `libjass.configure({ Map: value })` instead.");
|
||||
map.setImplementation(value);
|
||||
},
|
||||
set: map.setImplementation,
|
||||
},
|
||||
|
||||
Promise: {
|
||||
get: () => promise.Promise,
|
||||
set: value => {
|
||||
console.warn("Setter `libjass.Promise = value` has been deprecated. Use `libjass.configure({ Promise: value })` instead.");
|
||||
promise.setImplementation(value);
|
||||
},
|
||||
set: promise.setImplementation,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,9 +26,9 @@ import { Map } from "../utility/map";
|
||||
* Parses a line into a {@link ./types/misc.Property}.
|
||||
*
|
||||
* @param {string} line
|
||||
* @return {Property}
|
||||
* @return {!Property}
|
||||
*/
|
||||
export function parseLineIntoProperty(line: string): Property | null {
|
||||
export function parseLineIntoProperty(line: string): Property {
|
||||
const colonPos = line.indexOf(":");
|
||||
if (colonPos === -1) {
|
||||
return null;
|
||||
@@ -45,9 +45,9 @@ export function parseLineIntoProperty(line: string): Property | null {
|
||||
*
|
||||
* @param {string} line
|
||||
* @param {!Array.<string>} formatSpecifier
|
||||
* @return {TypedTemplate}
|
||||
* @return {!TypedTemplate}
|
||||
*/
|
||||
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate | null {
|
||||
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate {
|
||||
const property = parseLineIntoProperty(line);
|
||||
if (property === null) {
|
||||
return null;
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
import { debugMode } from "../settings";
|
||||
|
||||
import { ASS } from "../types/ass";
|
||||
import { Attachment, AttachmentType } from "../types/attachment";
|
||||
import { Dialogue } from "../types/dialogue";
|
||||
import { Style } from "../types/style";
|
||||
import { Dialogue } from "../types/dialogue";
|
||||
import { Attachment, AttachmentType } from "../types/attachment";
|
||||
import { Property, TypedTemplate } from "../types/misc";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
import { DeferredPromise } from "../utility/promise";
|
||||
import { Promise, DeferredPromise } from "../utility/promise";
|
||||
|
||||
import { parseLineIntoProperty } from "./misc";
|
||||
import { Stream } from "./streams";
|
||||
@@ -54,10 +55,9 @@ export class StreamParser {
|
||||
|
||||
private _shouldSwallowBom: boolean = true;
|
||||
private _currentSection: Section = Section.ScriptInfo;
|
||||
private _currentAttachment: Attachment | null = null;
|
||||
private _currentAttachment: Attachment = null;
|
||||
|
||||
constructor(private _stream: Stream) {
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._minimalDeferred.reject(reason);
|
||||
this._deferred.reject(reason);
|
||||
@@ -102,7 +102,6 @@ export class StreamParser {
|
||||
|
||||
if (value === Section.EOF) {
|
||||
const scriptProperties = this._ass.properties;
|
||||
/* tslint:disable-next-line:strict-type-predicates */
|
||||
if (scriptProperties.resolutionX === undefined || scriptProperties.resolutionY === undefined) {
|
||||
// Malformed script.
|
||||
this._minimalDeferred.reject("Malformed ASS script.");
|
||||
@@ -120,7 +119,7 @@ export class StreamParser {
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
private _onNextLine(line: string | null): void {
|
||||
private _onNextLine(line: string): void {
|
||||
if (line === null) {
|
||||
this.currentSection = Section.EOF;
|
||||
return;
|
||||
@@ -274,7 +273,6 @@ export class StreamParser {
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._minimalDeferred.reject(reason);
|
||||
this._deferred.reject(reason);
|
||||
@@ -293,13 +291,12 @@ export class SrtStreamParser {
|
||||
|
||||
private _shouldSwallowBom: boolean = true;
|
||||
|
||||
private _currentDialogueNumber: string | null = null;
|
||||
private _currentDialogueStart: string | null = null;
|
||||
private _currentDialogueEnd: string | null = null;
|
||||
private _currentDialogueText: string | null = null;
|
||||
private _currentDialogueNumber: string = null;
|
||||
private _currentDialogueStart: string = null;
|
||||
private _currentDialogueEnd: string = null;
|
||||
private _currentDialogueText: string = null;
|
||||
|
||||
constructor(private _stream: Stream) {
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
@@ -323,7 +320,7 @@ export class SrtStreamParser {
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
private _onNextLine(line: string | null): void {
|
||||
private _onNextLine(line: string): void {
|
||||
if (line === null) {
|
||||
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
|
||||
this._ass.dialogues.push(new Dialogue(new Map([
|
||||
@@ -383,7 +380,7 @@ export class SrtStreamParser {
|
||||
.replace(/<\/u>/g, "{\\u0}").replace(/\{\/u\}/g, "{\\u0}")
|
||||
.replace(
|
||||
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
|
||||
(/* ujs:unreferenced */ _substring: string, red: string, green: string, blue: string) => `{\c&H${ blue }${ green }${ red }&}`,
|
||||
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => `{\c&H${ blue }${ green }${ red }&}`
|
||||
).replace(/<\/font>/g, "{\\c}");
|
||||
|
||||
if (this._currentDialogueText !== null) {
|
||||
@@ -395,7 +392,6 @@ export class SrtStreamParser {
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,45 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DeferredPromise, Promise } from "../utility/promise";
|
||||
import { Promise, DeferredPromise } from "../utility/promise";
|
||||
|
||||
export interface ReadableStream {
|
||||
/**
|
||||
* @return {!ReadableStreamReader}
|
||||
*/
|
||||
getReader(): ReadableStreamReader;
|
||||
}
|
||||
|
||||
interface ReadableStreamReader {
|
||||
/**
|
||||
* @return {!Promise.<{ value?: Uint8Array, done: boolean }>}
|
||||
*/
|
||||
read(): Promise<{ value: Uint8Array; done: boolean; }>;
|
||||
}
|
||||
|
||||
export interface TextDecoder {
|
||||
/**
|
||||
* @param {!ArrayBuffer|!ArrayBufferView} input
|
||||
* @param {{ stream: boolean }} options
|
||||
* @return {string}
|
||||
*/
|
||||
decode(input: ArrayBuffer | ArrayBufferView, options: { stream: boolean }): string;
|
||||
}
|
||||
export interface TextDecoderConstructor {
|
||||
new (encoding: string, options: { ignoreBOM: boolean }): TextDecoder;
|
||||
|
||||
/**
|
||||
* @type {!TextDecoder}
|
||||
*/
|
||||
prototype: TextDecoder;
|
||||
}
|
||||
|
||||
declare const global: {
|
||||
/**
|
||||
* @type {!TextDecoderConstructor}
|
||||
*/
|
||||
TextDecoder?: TextDecoderConstructor;
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface for a stream.
|
||||
@@ -27,7 +65,7 @@ export interface Stream {
|
||||
/**
|
||||
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
|
||||
*/
|
||||
nextLine(): Promise<string | null>;
|
||||
nextLine(): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +81,8 @@ export class StringStream implements Stream {
|
||||
/**
|
||||
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the string has been completely read.
|
||||
*/
|
||||
nextLine(): Promise<string | null> {
|
||||
let result: Promise<string | null>;
|
||||
nextLine(): Promise<string> {
|
||||
let result: Promise<string> = null;
|
||||
|
||||
if (this._readTill < this._str.length) {
|
||||
const nextNewLinePos = this._str.indexOf("\n", this._readTill);
|
||||
@@ -58,7 +96,7 @@ export class StringStream implements Stream {
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = Promise.resolve<string | null>(null);
|
||||
result = Promise.resolve<string>(null);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -73,8 +111,8 @@ export class StringStream implements Stream {
|
||||
*/
|
||||
export class XhrStream implements Stream {
|
||||
private _readTill: number = 0;
|
||||
private _pendingDeferred: DeferredPromise<string | null> | null = null;
|
||||
private _failedError: ErrorEvent | null = null;
|
||||
private _pendingDeferred: DeferredPromise<string> = null;
|
||||
private _failedError: ErrorEvent = null;
|
||||
|
||||
constructor(private _xhr: XMLHttpRequest) {
|
||||
_xhr.addEventListener("progress", () => this._onXhrProgress(), false);
|
||||
@@ -143,7 +181,7 @@ export class XhrStream implements Stream {
|
||||
*/
|
||||
private _tryResolveNextLine(): void {
|
||||
if (this._failedError !== null) {
|
||||
this._pendingDeferred!.reject(this._failedError);
|
||||
this._pendingDeferred.reject(this._failedError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,19 +189,23 @@ export class XhrStream implements Stream {
|
||||
|
||||
const nextNewLinePos = response.indexOf("\n", this._readTill);
|
||||
if (nextNewLinePos !== -1) {
|
||||
this._pendingDeferred!.resolve(response.substring(this._readTill, nextNewLinePos));
|
||||
this._pendingDeferred.resolve(response.substring(this._readTill, nextNewLinePos));
|
||||
this._readTill = nextNewLinePos + 1;
|
||||
this._pendingDeferred = null;
|
||||
}
|
||||
|
||||
else if (this._xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (this._failedError !== null) {
|
||||
this._pendingDeferred.reject(this._failedError);
|
||||
}
|
||||
|
||||
// No more data. This is the last line.
|
||||
if (this._readTill < response.length) {
|
||||
this._pendingDeferred!.resolve(response.substr(this._readTill));
|
||||
else if (this._readTill < response.length) {
|
||||
this._pendingDeferred.resolve(response.substr(this._readTill));
|
||||
this._readTill = response.length;
|
||||
}
|
||||
else {
|
||||
this._pendingDeferred!.resolve(null);
|
||||
this._pendingDeferred.resolve(null);
|
||||
}
|
||||
|
||||
this._pendingDeferred = null;
|
||||
@@ -178,25 +220,14 @@ export class XhrStream implements Stream {
|
||||
* @param {string} encoding
|
||||
*/
|
||||
export class BrowserReadableStream implements Stream {
|
||||
/**
|
||||
* @return {boolean} Whether BrowserReadableStream is supported in this environment.
|
||||
*/
|
||||
static isSupported(): boolean {
|
||||
return (
|
||||
global.ReadableStream !== undefined &&
|
||||
typeof global.ReadableStream.prototype.getReader === "function" &&
|
||||
typeof global.TextDecoder === "function"
|
||||
);
|
||||
}
|
||||
|
||||
private _reader: ReadableStreamReader;
|
||||
private _decoder: TextDecoder;
|
||||
private _buffer: string = "";
|
||||
private _pendingDeferred: DeferredPromise<string | null> | null = null;
|
||||
private _pendingDeferred: DeferredPromise<string> = null;
|
||||
|
||||
constructor(stream: ReadableStream, encoding: string) {
|
||||
this._reader = stream.getReader();
|
||||
this._decoder = new global.TextDecoder!(encoding, { ignoreBOM: true });
|
||||
this._decoder = new global.TextDecoder(encoding, { ignoreBOM: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,13 +250,12 @@ export class BrowserReadableStream implements Stream {
|
||||
private _tryResolveNextLine(): void {
|
||||
const nextNewLinePos = this._buffer.indexOf("\n");
|
||||
if (nextNewLinePos !== -1) {
|
||||
this._pendingDeferred!.resolve(this._buffer.substr(0, nextNewLinePos));
|
||||
this._pendingDeferred.resolve(this._buffer.substr(0, nextNewLinePos));
|
||||
this._buffer = this._buffer.substr(nextNewLinePos + 1);
|
||||
this._pendingDeferred = null;
|
||||
}
|
||||
|
||||
else {
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
this._reader.read().then(next => {
|
||||
const { value, done } = next;
|
||||
|
||||
@@ -236,10 +266,10 @@ export class BrowserReadableStream implements Stream {
|
||||
else {
|
||||
// No more data.
|
||||
if (this._buffer.length === 0) {
|
||||
this._pendingDeferred!.resolve(null);
|
||||
this._pendingDeferred.resolve(null);
|
||||
}
|
||||
else {
|
||||
this._pendingDeferred!.resolve(this._buffer);
|
||||
this._pendingDeferred.resolve(this._buffer);
|
||||
this._buffer = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -31,28 +31,25 @@ enum DataType {
|
||||
Uint32,
|
||||
}
|
||||
|
||||
type StructMemberDefinition = { type: DataType; field: string; };
|
||||
type StructMemberDefinition = { type: DataType; field?: string; };
|
||||
|
||||
const fieldDecorators = new Map<DataType, (proto: any, field: string) => void>();
|
||||
|
||||
@struct
|
||||
class OffsetTable {
|
||||
/** @type {function(!{ dataView: DataView, position: number }): OffsetTable} */
|
||||
static read: (reader: DataReader) => OffsetTable;
|
||||
|
||||
/** @type {number} */ @field(DataType.Uint16) majorVersion: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) minorVersion: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) numTables: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) searchRange: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) entrySelector: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) rangeShift: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): OffsetTable} */
|
||||
static read: (reader: DataReader) => OffsetTable;
|
||||
}
|
||||
|
||||
@struct
|
||||
class TableRecord {
|
||||
/** @type {function(!{ dataView: DataView, position: number }): TableRecord} */
|
||||
static read: (reader: DataReader) => TableRecord;
|
||||
|
||||
/** @type {string} */ @field(DataType.Char) c1: string;
|
||||
/** @type {string} */ @field(DataType.Char) c2: string;
|
||||
/** @type {string} */ @field(DataType.Char) c3: string;
|
||||
@@ -60,29 +57,32 @@ class TableRecord {
|
||||
/** @type {number} */ @field(DataType.Uint32) checksum: number;
|
||||
/** @type {number} */ @field(DataType.Uint32) offset: number;
|
||||
/** @type {number} */ @field(DataType.Uint32) length: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): TableRecord} */
|
||||
static read: (reader: DataReader) => TableRecord;
|
||||
}
|
||||
|
||||
@struct
|
||||
class NameTableHeader {
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameTableHeader} */
|
||||
static read: (reader: DataReader) => NameTableHeader;
|
||||
|
||||
/** @type {number} */ @field(DataType.Uint16) formatSelector: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) count: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) stringOffset: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameTableHeader} */
|
||||
static read: (reader: DataReader) => NameTableHeader;
|
||||
}
|
||||
|
||||
@struct
|
||||
class NameRecord {
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameRecord} */
|
||||
static read: (reader: DataReader) => NameRecord;
|
||||
|
||||
/** @type {number} */ @field(DataType.Uint16) platformId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) encodingId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) languageId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) nameId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) length: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) offset: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameRecord} */
|
||||
static read: (reader: DataReader) => NameRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +103,7 @@ export function getTtfNames(attachment: Attachment): Set<string> {
|
||||
const reader = { dataView: new DataView(bytes.buffer), position: 0 };
|
||||
|
||||
const offsetTable = OffsetTable.read(reader);
|
||||
let nameTableRecord: TableRecord | null = null;
|
||||
let nameTableRecord: TableRecord = null;
|
||||
for (let i = 0; i < offsetTable.numTables; i++) {
|
||||
const tableRecord = TableRecord.read(reader);
|
||||
if (tableRecord.c1 + tableRecord.c2 + tableRecord.c3 + tableRecord.c4 === "name") {
|
||||
@@ -111,9 +111,6 @@ export function getTtfNames(attachment: Attachment): Set<string> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nameTableRecord === null) {
|
||||
throw new Error('Could not find "name" table record.');
|
||||
}
|
||||
|
||||
reader.position = nameTableRecord.offset;
|
||||
const nameTableHeader = NameTableHeader.read(reader);
|
||||
@@ -133,28 +130,24 @@ export function getTtfNames(attachment: Attachment): Set<string> {
|
||||
case 1: {
|
||||
let name = "";
|
||||
|
||||
/* tslint:disable-next-line:prefer-for-of */
|
||||
for (let j = 0; j < nameBytes.length; j++) {
|
||||
name += String.fromCharCode(nameBytes[j]);
|
||||
}
|
||||
|
||||
result.add(name);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
let name = "";
|
||||
|
||||
for (let j = 0; j < nameBytes.length; j += 2) {
|
||||
/* tslint:disable-next-line:no-bitwise */
|
||||
name += String.fromCharCode((nameBytes[j] << 8) + nameBytes[j + 1]);
|
||||
}
|
||||
|
||||
result.add(name);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -172,7 +165,7 @@ export function getTtfNames(attachment: Attachment): Set<string> {
|
||||
* @return {!function(new(): T)}
|
||||
*/
|
||||
function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new (): T; read(reader: DataReader): T; } {
|
||||
const fields: StructMemberDefinition[] = (clazz as any).__fields;
|
||||
const fields: StructMemberDefinition[] = (<any>clazz).__fields;
|
||||
|
||||
clazz.read = (reader: DataReader) => {
|
||||
const result: any = new clazz();
|
||||
@@ -212,7 +205,7 @@ function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new ()
|
||||
function field<T>(type: DataType): (proto: T, field: string) => void {
|
||||
let existingDecorator = fieldDecorators.get(type);
|
||||
if (existingDecorator === undefined) {
|
||||
existingDecorator = (proto: T, field: string) => {
|
||||
existingDecorator =(proto: T, field: string) => {
|
||||
const ctor: { __fields?: StructMemberDefinition[] } = proto.constructor;
|
||||
if (ctor.__fields === undefined) {
|
||||
ctor.__fields = [];
|
||||
|
||||
@@ -77,7 +77,11 @@ export class Color {
|
||||
* @return {!libjass.parts.Color} Returns a new Color instance with the same color but the provided alpha.
|
||||
*/
|
||||
withAlpha(value: number): Color {
|
||||
return new Color(this._red, this._green, this._blue, value);
|
||||
if (value !== null) {
|
||||
return new Color(this._red, this._green, this._blue, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +103,7 @@ export class Color {
|
||||
this._red + progression * (final.red - this._red),
|
||||
this._green + progression * (final.green - this._green),
|
||||
this._blue + progression * (final.blue - this._blue),
|
||||
this._alpha + progression * (final.alpha - this._alpha),
|
||||
this._alpha + progression * (final.alpha - this._alpha)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -158,26 +162,20 @@ export class Text {
|
||||
export class NewLine {
|
||||
}
|
||||
|
||||
/**
|
||||
* A soft newline character \n.
|
||||
*/
|
||||
export class SoftNewLine {
|
||||
}
|
||||
|
||||
/**
|
||||
* An italic tag {\i}
|
||||
*
|
||||
* @param {?boolean} value {\i1} -> true, {\i0} -> false, {\i} -> null
|
||||
*/
|
||||
export class Italic {
|
||||
constructor(private _value: boolean | null) { }
|
||||
constructor(private _value: boolean) { }
|
||||
|
||||
/**
|
||||
* The value of this italic tag.
|
||||
*
|
||||
* @type {?boolean}
|
||||
*/
|
||||
get value(): boolean | null {
|
||||
get value(): boolean {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -185,17 +183,17 @@ export class Italic {
|
||||
/**
|
||||
* A bold tag {\b}
|
||||
*
|
||||
* @param {?boolean|?number} value {\b1} -> true, {\b0} -> false, {\b###} -> weight of the bold (number), {\b} -> null
|
||||
* @param {*} value {\b1} -> true, {\b0} -> false, {\b###} -> weight of the bold (number), {\b} -> null
|
||||
*/
|
||||
export class Bold {
|
||||
constructor(private _value: boolean | number | null) { }
|
||||
constructor(private _value: Object) { }
|
||||
|
||||
/**
|
||||
* The value of this bold tag.
|
||||
*
|
||||
* @type {?boolean|?number}
|
||||
*/
|
||||
get value(): boolean | number | null {
|
||||
get value(): Object {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -206,14 +204,14 @@ export class Bold {
|
||||
* @param {?boolean} value {\u1} -> true, {\u0} -> false, {\u} -> null
|
||||
*/
|
||||
export class Underline {
|
||||
constructor(private _value: boolean | null) { }
|
||||
constructor(private _value: boolean) { }
|
||||
|
||||
/**
|
||||
* The value of this underline tag.
|
||||
*
|
||||
* @type {?boolean}
|
||||
*/
|
||||
get value(): boolean | null {
|
||||
get value(): boolean {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -224,14 +222,14 @@ export class Underline {
|
||||
* @param {?boolean} value {\s1} -> true, {\s0} -> false, {\s} -> null
|
||||
*/
|
||||
export class StrikeThrough {
|
||||
constructor(private _value: boolean | null) { }
|
||||
constructor(private _value: boolean) { }
|
||||
|
||||
/**
|
||||
* The value of this strike-through tag.
|
||||
*
|
||||
* @type {?boolean}
|
||||
*/
|
||||
get value(): boolean | null {
|
||||
get value(): boolean {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -242,14 +240,14 @@ export class StrikeThrough {
|
||||
* @param {?number} value {\bord###} -> width (number), {\bord} -> null
|
||||
*/
|
||||
export class Border {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this border tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -260,14 +258,14 @@ export class Border {
|
||||
* @param {?number} value {\xbord###} -> width (number), {\xbord} -> null
|
||||
*/
|
||||
export class BorderX {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this horizontal border tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -278,14 +276,14 @@ export class BorderX {
|
||||
* @param {?number} value {\ybord###} -> height (number), {\ybord} -> null
|
||||
*/
|
||||
export class BorderY {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this vertical border tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -296,14 +294,14 @@ export class BorderY {
|
||||
* @param {?number} value {\shad###} -> depth (number), {\shad} -> null
|
||||
*/
|
||||
export class Shadow {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this shadow tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -314,14 +312,14 @@ export class Shadow {
|
||||
* @param {?number} value {\xshad###} -> depth (number), {\xshad} -> null
|
||||
*/
|
||||
export class ShadowX {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this horizontal shadow tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -332,14 +330,14 @@ export class ShadowX {
|
||||
* @param {?number} value {\yshad###} -> depth (number), {\yshad} -> null
|
||||
*/
|
||||
export class ShadowY {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this vertical shadow tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -350,14 +348,14 @@ export class ShadowY {
|
||||
* @param {?number} value {\be###} -> strength (number), {\be} -> null
|
||||
*/
|
||||
export class Blur {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this blur tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -368,14 +366,14 @@ export class Blur {
|
||||
* @param {?number} value {\blur###} -> strength (number), {\blur} -> null
|
||||
*/
|
||||
export class GaussianBlur {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this Gaussian blur tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -386,14 +384,14 @@ export class GaussianBlur {
|
||||
* @param {?string} value {\fn###} -> name (string), {\fn} -> null
|
||||
*/
|
||||
export class FontName {
|
||||
constructor(private _value: string | null) { }
|
||||
constructor(private _value: string) { }
|
||||
|
||||
/**
|
||||
* The value of this font name tag.
|
||||
*
|
||||
* @type {?string}
|
||||
*/
|
||||
get value(): string | null {
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -404,14 +402,14 @@ export class FontName {
|
||||
* @param {?number} value {\fs###} -> size (number), {\fs} -> null
|
||||
*/
|
||||
export class FontSize {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this font size tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -419,7 +417,7 @@ export class FontSize {
|
||||
/**
|
||||
* A font size increase tag {\fs+}
|
||||
*
|
||||
* @param {number} value {\fs+###} -> relative difference (number, percentage)
|
||||
* @param {number} value {\fs+###} -> difference (number)
|
||||
*/
|
||||
export class FontSizePlus {
|
||||
constructor(private _value: number) { }
|
||||
@@ -437,7 +435,7 @@ export class FontSizePlus {
|
||||
/**
|
||||
* A font size decrease tag {\fs-}
|
||||
*
|
||||
* @param {number} value {\fs-###} -> relative difference (number, percentage)
|
||||
* @param {number} value {\fs-###} -> difference (number)
|
||||
*/
|
||||
export class FontSizeMinus {
|
||||
constructor(private _value: number) { }
|
||||
@@ -458,14 +456,14 @@ export class FontSizeMinus {
|
||||
* @param {?number} value {\fscx###} -> scale (number), {\fscx} -> null
|
||||
*/
|
||||
export class FontScaleX {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this horizontal font scaling tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -476,14 +474,14 @@ export class FontScaleX {
|
||||
* @param {?number} value {\fscy###} -> scale (number), {\fscy} -> null
|
||||
*/
|
||||
export class FontScaleY {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this vertical font scaling tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -494,14 +492,14 @@ export class FontScaleY {
|
||||
* @param {?number} value {\fsp###} -> spacing (number), {\fsp} -> null
|
||||
*/
|
||||
export class LetterSpacing {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this letter-spacing tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -512,14 +510,14 @@ export class LetterSpacing {
|
||||
* @param {?number} value {\frx###} -> angle (number), {\frx} -> null
|
||||
*/
|
||||
export class RotateX {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this X-axis rotation tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -530,14 +528,14 @@ export class RotateX {
|
||||
* @param {?number} value {\fry###} -> angle (number), {\fry} -> null
|
||||
*/
|
||||
export class RotateY {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this Y-axis rotation tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -548,14 +546,14 @@ export class RotateY {
|
||||
* @param {?number} value {\frz###} -> angle (number), {\frz} -> null
|
||||
*/
|
||||
export class RotateZ {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this Z-axis rotation tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -566,14 +564,14 @@ export class RotateZ {
|
||||
* @param {?number} value {\fax###} -> angle (number), {\fax} -> null
|
||||
*/
|
||||
export class SkewX {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this X-axis shearing tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -584,14 +582,14 @@ export class SkewX {
|
||||
* @param {?number} value {\fay###} -> angle (number), {\fay} -> null
|
||||
*/
|
||||
export class SkewY {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this Y-axis shearing tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -602,14 +600,14 @@ export class SkewY {
|
||||
* @param {libjass.parts.Color} value {\1c###} -> color (Color), {\1c} -> null
|
||||
*/
|
||||
export class PrimaryColor {
|
||||
constructor(private _value: Color | null) { }
|
||||
constructor(private _value: Color) { }
|
||||
|
||||
/**
|
||||
* The value of this primary color tag.
|
||||
*
|
||||
* @type {libjass.parts.Color}
|
||||
*/
|
||||
get value(): Color | null {
|
||||
get value(): Color {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -620,14 +618,14 @@ export class PrimaryColor {
|
||||
* @param {libjass.parts.Color} value {\2c###} -> color (Color), {\2c} -> null
|
||||
*/
|
||||
export class SecondaryColor {
|
||||
constructor(private _value: Color | null) { }
|
||||
constructor(private _value: Color) { }
|
||||
|
||||
/**
|
||||
* The value of this secondary color tag.
|
||||
*
|
||||
* @type {libjass.parts.Color}
|
||||
*/
|
||||
get value(): Color | null {
|
||||
get value(): Color {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -638,14 +636,14 @@ export class SecondaryColor {
|
||||
* @param {libjass.parts.Color} value {\3c###} -> color (Color), {\3c} -> null
|
||||
*/
|
||||
export class OutlineColor {
|
||||
constructor(private _value: Color | null) { }
|
||||
constructor(private _value: Color) { }
|
||||
|
||||
/**
|
||||
* The value of this outline color tag.
|
||||
*
|
||||
* @type {libjass.parts.Color}
|
||||
*/
|
||||
get value(): Color | null {
|
||||
get value(): Color {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -656,14 +654,14 @@ export class OutlineColor {
|
||||
* @param {libjass.parts.Color} value {\4c###} -> color (Color), {\4c} -> null
|
||||
*/
|
||||
export class ShadowColor {
|
||||
constructor(private _value: Color | null) { }
|
||||
constructor(private _value: Color) { }
|
||||
|
||||
/**
|
||||
* The value of this shadow color tag.
|
||||
*
|
||||
* @type {libjass.parts.Color}
|
||||
*/
|
||||
get value(): Color | null {
|
||||
get value(): Color {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -674,14 +672,14 @@ export class ShadowColor {
|
||||
* @param {?number} value {\alpha###} -> alpha (number), {\alpha} -> null
|
||||
*/
|
||||
export class Alpha {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this alpha tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -692,14 +690,14 @@ export class Alpha {
|
||||
* @param {?number} value {\1a###} -> alpha (number), {\1a} -> null
|
||||
*/
|
||||
export class PrimaryAlpha {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this primary alpha tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -710,14 +708,14 @@ export class PrimaryAlpha {
|
||||
* @param {?number} value {\2a###} -> alpha (number), {\2a} -> null
|
||||
*/
|
||||
export class SecondaryAlpha {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this secondary alpha tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -728,14 +726,14 @@ export class SecondaryAlpha {
|
||||
* @param {?number} value {\3a###} -> alpha (number), {\3a} -> null
|
||||
*/
|
||||
export class OutlineAlpha {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this outline alpha tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -746,14 +744,14 @@ export class OutlineAlpha {
|
||||
* @param {?number} value {\4a###} -> alpha (number), {\4a} -> null
|
||||
*/
|
||||
export class ShadowAlpha {
|
||||
constructor(private _value: number | null) { }
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this shadow alpha tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number | null {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -769,7 +767,7 @@ export class Alignment {
|
||||
/**
|
||||
* The value of this alignment tag.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
@@ -854,14 +852,14 @@ export class WrappingStyle {
|
||||
* @param {?string} value {\r###} -> style name (string), {\r} -> null
|
||||
*/
|
||||
export class Reset {
|
||||
constructor(private _value: string | null) { }
|
||||
constructor(private _value: string) { }
|
||||
|
||||
/**
|
||||
* The value of this style reset tag.
|
||||
*
|
||||
* @type {?string}
|
||||
*/
|
||||
get value(): string | null {
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -901,11 +899,11 @@ export class Position {
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {?number} t1
|
||||
* @param {?number} t2
|
||||
* @param {number} t1
|
||||
* @param {number} t2
|
||||
*/
|
||||
export class Move {
|
||||
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _t1: number | null, private _t2: number | null) { }
|
||||
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _t1: number, private _t2: number) { }
|
||||
|
||||
/**
|
||||
* The starting x value of this move tag.
|
||||
@@ -946,18 +944,18 @@ export class Move {
|
||||
/**
|
||||
* The start time of this move tag.
|
||||
*
|
||||
* @type {?number}
|
||||
* @type {number}
|
||||
*/
|
||||
get t1(): number | null {
|
||||
get t1(): number {
|
||||
return this._t1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The end time value of this move tag.
|
||||
*
|
||||
* @type {?number}
|
||||
* @type {number}
|
||||
*/
|
||||
get t2(): number | null {
|
||||
get t2(): number {
|
||||
return this._t2;
|
||||
}
|
||||
}
|
||||
@@ -1032,7 +1030,7 @@ export class Fade {
|
||||
export class ComplexFade {
|
||||
constructor(
|
||||
private _a1: number, private _a2: number, private _a3: number,
|
||||
private _t1: number, private _t2: number, private _t3: number, private _t4: number,
|
||||
private _t1: number, private _t2: number, private _t3: number, private _t4: number
|
||||
) { }
|
||||
|
||||
/**
|
||||
@@ -1102,20 +1100,20 @@ export class ComplexFade {
|
||||
/**
|
||||
* A transform tag {\t}
|
||||
*
|
||||
* @param {?number} start
|
||||
* @param {?number} end
|
||||
* @param {?number} accel
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {number} accel
|
||||
* @param {!Array.<!libjass.parts.Tag>} tags
|
||||
*/
|
||||
export class Transform {
|
||||
constructor(private _start: number | null, private _end: number | null, private _accel: number | null, private _tags: Part[]) { }
|
||||
constructor(private _start: number, private _end: number, private _accel: number, private _tags: Part[]) { }
|
||||
|
||||
/**
|
||||
* The starting time of this transform tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get start(): number | null {
|
||||
get start(): number {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
@@ -1124,7 +1122,7 @@ export class Transform {
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get end(): number | null {
|
||||
get end(): number {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
@@ -1133,7 +1131,7 @@ export class Transform {
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get accel(): number | null {
|
||||
get accel(): number {
|
||||
return this._accel;
|
||||
}
|
||||
|
||||
@@ -1297,30 +1295,37 @@ export class DrawingInstructions {
|
||||
}
|
||||
}
|
||||
|
||||
const addToString = function (ctor: Function, ctorName: string): void {
|
||||
const addToString = function (ctor: Function, ctorName: string) {
|
||||
if (!ctor.prototype.hasOwnProperty("toString")) {
|
||||
const propertyNames = Object.getOwnPropertyNames(ctor.prototype).filter(property => property !== "constructor");
|
||||
|
||||
ctor.prototype.toString = function (this: any): string {
|
||||
return `${ ctorName } { ${ propertyNames.map(name => `${ name }: ${ this[name] }`).join(", ") }${ (propertyNames.length > 0) ? " " : "" }}`;
|
||||
};
|
||||
ctor.prototype.toString = function () {
|
||||
return (
|
||||
ctorName + " { " +
|
||||
propertyNames.map(name => `${ name }: ${ (<any>this)[name] }`).join(", ") +
|
||||
((propertyNames.length > 0) ? " " : "") +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
import { registerClass } from "../serialization";
|
||||
import { registerClassPrototype } from "../webworker/misc";
|
||||
|
||||
declare const exports: any;
|
||||
|
||||
for (const key of Object.keys(exports)) {
|
||||
const value: any = exports[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, key);
|
||||
registerClass(value);
|
||||
registerClassPrototype(value.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(drawing)) {
|
||||
const value: any = (drawing as any)[key];
|
||||
const value: any = (<any>drawing)[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, `Drawing${ key }`);
|
||||
registerClass(value);
|
||||
registerClassPrototype(value.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ import { ManualClock } from "./manual";
|
||||
export class AutoClock implements Clock {
|
||||
private _manualClock: ManualClock = new ManualClock();
|
||||
|
||||
private _nextAnimationFrameRequestId: number | null = null;
|
||||
private _nextAnimationFrameRequestId: number = null;
|
||||
|
||||
private _lastKnownExternalTime: number | null = null;
|
||||
private _lastKnownExternalTimeObtainedAt: number = 0;
|
||||
private _lastKnownExternalTime: number = null;
|
||||
private _lastKnownExternalTimeObtainedAt: number = null;
|
||||
|
||||
constructor(private _getCurrentTime: () => number, private _autoPauseAfter: number) { }
|
||||
|
||||
@@ -193,7 +193,7 @@ export class AutoClock implements Clock {
|
||||
*/
|
||||
addEventListener(type: ClockEvent, listener: Function): void {
|
||||
this._manualClock.addEventListener(type, listener);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} timeStamp
|
||||
@@ -213,8 +213,8 @@ export class AutoClock implements Clock {
|
||||
if (!this._manualClock.paused) {
|
||||
if (this._lastKnownExternalTime !== null && currentExternalTime === this._lastKnownExternalTime) {
|
||||
if (timeStamp - this._lastKnownExternalTimeObtainedAt > this._autoPauseAfter) {
|
||||
this._lastKnownExternalTimeObtainedAt = 0;
|
||||
this._manualClock.seek(currentExternalTime);
|
||||
this._lastKnownExternalTimeObtainedAt = null;
|
||||
this._manualClock.pause();
|
||||
}
|
||||
else {
|
||||
this._manualClock.tick((timeStamp - this._lastKnownExternalTimeObtainedAt) / 1000 * this._manualClock.rate + this._lastKnownExternalTime);
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../../utility/map";
|
||||
|
||||
/**
|
||||
* A mixin class that represents an event source.
|
||||
*/
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../../utility/map";
|
||||
import { mixin } from "../../utility/mixin";
|
||||
import { Map } from "../../utility/map";
|
||||
|
||||
import { Clock, ClockEvent, EventSource } from "./base";
|
||||
|
||||
@@ -226,8 +226,6 @@ export class ManualClock implements Clock, EventSource<ClockEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:member-ordering */
|
||||
|
||||
// EventSource members
|
||||
|
||||
/**
|
||||
@@ -244,7 +242,5 @@ export class ManualClock implements Clock, EventSource<ClockEvent> {
|
||||
* @type {function(number, Array.<*>)}
|
||||
*/
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
|
||||
/* tslint:enable:member-ordering */
|
||||
}
|
||||
mixin(ManualClock, [EventSource]);
|
||||
|
||||
@@ -35,7 +35,7 @@ export class DefaultRenderer extends WebRenderer {
|
||||
constructor(private _video: HTMLVideoElement, ass: ASS, settings?: RendererSettings) {
|
||||
super(ass, new VideoClock(_video), document.createElement("div"), settings);
|
||||
|
||||
this._video.parentElement!.replaceChild(this.libjassSubsWrapper, this._video);
|
||||
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
|
||||
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,4 +26,5 @@ export { VideoClock } from "./clocks/video";
|
||||
export { DefaultRenderer } from "./default";
|
||||
export { NullRenderer } from "./null";
|
||||
export { WebRenderer } from "./web/renderer";
|
||||
export { RendererSettings } from "./settings";
|
||||
|
||||
export { RendererSettings, makeFontMapFromStyleElement } from "./settings";
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { debugMode, verboseMode } from "../settings";
|
||||
import { Clock, ClockEvent } from "./clocks/base";
|
||||
|
||||
import { RendererSettings, toRendererSettings } from "./settings";
|
||||
|
||||
import { verboseMode } from "../settings";
|
||||
|
||||
import { ASS } from "../types/ass";
|
||||
import { Dialogue } from "../types/dialogue";
|
||||
|
||||
import { Clock, ClockEvent } from "./clocks/base";
|
||||
|
||||
import { RendererSettings } from "./settings";
|
||||
|
||||
/**
|
||||
* A renderer implementation that doesn't output anything.
|
||||
*
|
||||
@@ -35,7 +35,7 @@ import { RendererSettings } from "./settings";
|
||||
* @param {libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class NullRenderer {
|
||||
private static _lastRendererId: number = -1;
|
||||
private static _lastRendererId = -1;
|
||||
|
||||
private _id: number;
|
||||
|
||||
@@ -44,7 +44,7 @@ export class NullRenderer {
|
||||
constructor(private _ass: ASS, private _clock: Clock, settings?: RendererSettings) {
|
||||
this._id = ++NullRenderer._lastRendererId;
|
||||
|
||||
this._settings = RendererSettings.from(settings);
|
||||
this._settings = toRendererSettings(settings);
|
||||
|
||||
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
|
||||
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
|
||||
@@ -88,14 +88,14 @@ export class NullRenderer {
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
preRender(_dialogue: Dialogue): void { }
|
||||
preRender(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Draw a dialogue. This is a no-op for this type.
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
draw(_dialogue: Dialogue): void { }
|
||||
draw(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Enable the renderer.
|
||||
@@ -159,21 +159,14 @@ export class NullRenderer {
|
||||
}
|
||||
|
||||
for (const dialogue of this._ass.dialogues) {
|
||||
try {
|
||||
if (dialogue.end > currentTime) {
|
||||
if (dialogue.start <= currentTime) {
|
||||
// This dialogue is visible right now. Draw it.
|
||||
this.draw(dialogue);
|
||||
}
|
||||
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
|
||||
// This dialogue will be visible soon. Pre-render it.
|
||||
this.preRender(dialogue);
|
||||
}
|
||||
if (dialogue.end > currentTime) {
|
||||
if (dialogue.start <= currentTime) {
|
||||
// This dialogue is visible right now. Draw it.
|
||||
this.draw(dialogue);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
if (debugMode) {
|
||||
console.error(`Rendering dialogue ${ dialogue.id } failed.`, ex);
|
||||
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
|
||||
// This dialogue will be visible soon. Pre-render it.
|
||||
this.preRender(dialogue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,85 +18,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { debugMode } from "../settings";
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
/**
|
||||
* Settings for the renderer.
|
||||
*/
|
||||
export class RendererSettings {
|
||||
/**
|
||||
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules. There should be one @font-face rule for each font name, mapping to a font file URL.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* @font-face {
|
||||
* font-family: "Helvetica";
|
||||
* src: url("/fonts/helvetica.ttf"), local("Arial");
|
||||
* }
|
||||
*
|
||||
* More complicated @font-face syntax like format() or multi-line src are not supported.
|
||||
*
|
||||
* @param {!LinkStyle} linkStyle
|
||||
* @return {!Map.<string, string>}
|
||||
*/
|
||||
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string> {
|
||||
const fontMap = new Map<string, string>();
|
||||
|
||||
const styleSheet = linkStyle.sheet as CSSStyleSheet;
|
||||
/* tslint:disable-next-line:prefer-for-of */
|
||||
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||
const rule = styleSheet.cssRules[i];
|
||||
|
||||
if (isFontFaceRule(rule)) {
|
||||
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)![1];
|
||||
|
||||
let src = rule.style.getPropertyValue("src");
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map(line => line.match(/src:\s*([^;]+?)\s*;/))
|
||||
.filter((matches): matches is RegExpMatchArray => matches !== null)
|
||||
.map(matches => matches[1])[0];
|
||||
}
|
||||
|
||||
fontMap.set(name, src);
|
||||
}
|
||||
}
|
||||
|
||||
return fontMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an arbitrary object into a {@link libjass.renderers.RendererSettings} object.
|
||||
*
|
||||
* @param {*} object
|
||||
* @return {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
static from(object?: any): RendererSettings {
|
||||
if (object === undefined || object === null) {
|
||||
object = {};
|
||||
}
|
||||
|
||||
const {
|
||||
fontMap = null,
|
||||
preRenderTime = 5,
|
||||
preciseOutlines = false,
|
||||
enableSvg = testSupportsSvg(),
|
||||
fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"',
|
||||
useAttachedFonts = false,
|
||||
} = object as RendererSettings;
|
||||
|
||||
const result = new RendererSettings();
|
||||
result.fontMap = fontMap;
|
||||
result.preRenderTime = preRenderTime;
|
||||
result.preciseOutlines = preciseOutlines;
|
||||
result.enableSvg = enableSvg;
|
||||
result.fallbackFonts = fallbackFonts;
|
||||
result.useAttachedFonts = useAttachedFonts;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface RendererSettings {
|
||||
/**
|
||||
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
|
||||
*
|
||||
@@ -110,14 +37,14 @@ export class RendererSettings {
|
||||
*
|
||||
* Only the first and second forms allow you to use local fonts. The third form only allows you to use remote fonts.
|
||||
*
|
||||
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.RendererSettings.makeFontMapFromStyleElement}
|
||||
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.makeFontMapFromStyleElement}
|
||||
* convenience method to create a font map.
|
||||
*
|
||||
* Defaults to null.
|
||||
*
|
||||
* @type {Map.<string, (string|!Array.<string>)>}
|
||||
* @type {!Map.<string, (string|!Array.<string>)>}
|
||||
*/
|
||||
fontMap: Map<string, string | string[]> | null;
|
||||
fontMap: Map<string, string | string[]>;
|
||||
|
||||
/**
|
||||
* Subtitles will be pre-rendered for this amount of time (seconds).
|
||||
@@ -171,6 +98,74 @@ export class RendererSettings {
|
||||
useAttachedFonts: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an arbitrary object into a {@link libjass.renderers.RendererSettings} object.
|
||||
*
|
||||
* @param {*} object
|
||||
* @return {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
export function toRendererSettings(object?: any): RendererSettings {
|
||||
if (object === undefined || object === null) {
|
||||
object = {};
|
||||
}
|
||||
|
||||
const {
|
||||
fontMap = null,
|
||||
preRenderTime = 5,
|
||||
preciseOutlines = false,
|
||||
enableSvg = true,
|
||||
fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"',
|
||||
useAttachedFonts = false
|
||||
} = <RendererSettings>object;
|
||||
|
||||
return {
|
||||
fontMap,
|
||||
preRenderTime,
|
||||
preciseOutlines,
|
||||
enableSvg,
|
||||
fallbackFonts,
|
||||
useAttachedFonts,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules. There should be one @font-face rule for each font name, mapping to a font file URL.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* @font-face {
|
||||
* font-family: "Helvetica";
|
||||
* src: url("/fonts/helvetica.ttf"), local("Arial");
|
||||
* }
|
||||
*
|
||||
* @param {!LinkStyle} linkStyle
|
||||
* @return {!Map.<string, string>}
|
||||
*/
|
||||
export function makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string> {
|
||||
const fontMap = new Map<string, string>();
|
||||
|
||||
const styleSheet = <CSSStyleSheet>linkStyle.sheet;
|
||||
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||
const rule = styleSheet.cssRules[i];
|
||||
|
||||
if (isFontFaceRule(rule)) {
|
||||
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)[1];
|
||||
|
||||
let src = rule.style.getPropertyValue("src");
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map(line => line.match(/src:\s*([^;]+?)\s*;/))
|
||||
.filter(matches => matches !== null)
|
||||
.map(matches => matches[1])[0];
|
||||
}
|
||||
|
||||
fontMap.set(name, src);
|
||||
}
|
||||
}
|
||||
|
||||
return fontMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!CSSRule} rule
|
||||
* @return {boolean}
|
||||
@@ -178,63 +173,3 @@ export class RendererSettings {
|
||||
function isFontFaceRule(rule: CSSRule): rule is CSSFontFaceRule {
|
||||
return rule.type === CSSRule.FONT_FACE_RULE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this environment may support SVG filter effects. May return false positives.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
function testSupportsSvg(): boolean {
|
||||
if (debugMode) {
|
||||
console.log("Testing whether SVG filter effects are supported.");
|
||||
}
|
||||
|
||||
if (global.document === undefined) {
|
||||
if (debugMode) {
|
||||
console.log("This doesn't look like a browser. Assuming it doesn't support SVG filter effects.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const morphologyFilter = document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
|
||||
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6618301/
|
||||
try {
|
||||
morphologyFilter.radiusX.baseVal = 1;
|
||||
}
|
||||
catch (ex) {
|
||||
if (debugMode) {
|
||||
if (ex instanceof DOMException) {
|
||||
const domException = ex as DOMException;
|
||||
if (domException.code === DOMException.NO_MODIFICATION_ALLOWED_ERR) {
|
||||
console.log("Setting SVGFEMorphologyElement.radiusX.baseVal threw NoModificationAllowedError. This browser doesn't support SVG DOM correctly.");
|
||||
}
|
||||
else {
|
||||
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected DOMException code ${ domException.code }. This browser doesn't support SVG DOM correctly.`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected exception ${ ex }. This browser doesn't support SVG SVG DOM correctly.`);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6618454/
|
||||
morphologyFilter.setAttribute("radius", "1");
|
||||
if (morphologyFilter.cloneNode().getAttribute("radius") !== "1") {
|
||||
if (debugMode) {
|
||||
console.log("SVGFEMorphologyElement's radius attribute was corrupted when cloned. This browser doesn't support SVG DOM correctly.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (debugMode) {
|
||||
console.log("This browser may support SVG filter effects.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ export class AnimationCollection {
|
||||
* @param {!Array.<!libjass.renderers.Keyframe>} keyframes
|
||||
*/
|
||||
add(timingFunction: string, keyframes: Keyframe[]): void {
|
||||
let start: number | null = null;
|
||||
let end: number | null = null;
|
||||
let start: number = null;
|
||||
let end: number = null;
|
||||
|
||||
for (const keyframe of keyframes) {
|
||||
if (start === null) {
|
||||
@@ -82,10 +82,6 @@ export class AnimationCollection {
|
||||
end = keyframe.time;
|
||||
}
|
||||
|
||||
if (start === null || end === null) {
|
||||
throw new Error("Atleast one keyframe must be provided.");
|
||||
}
|
||||
|
||||
let ruleCssText = "";
|
||||
|
||||
for (const keyframe of keyframes) {
|
||||
|
||||
@@ -54,76 +54,43 @@ export class DrawingStyles {
|
||||
* @return {!SVGSVGElement}
|
||||
*/
|
||||
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGSVGElement {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("version", "1.1");
|
||||
|
||||
if (drawingInstructions.instructions.length === 0) {
|
||||
return svg;
|
||||
}
|
||||
|
||||
const scaleFactor = Math.pow(2, this._scale - 1);
|
||||
const scaleX = this._outputScaleX / scaleFactor;
|
||||
const scaleY = this._outputScaleY / scaleFactor;
|
||||
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
let pathString = "";
|
||||
|
||||
let bboxMinX = Infinity;
|
||||
let bboxMaxX = -Infinity;
|
||||
let bboxMinY = Infinity;
|
||||
let bboxMaxY = -Infinity;
|
||||
let bboxWidth = 0;
|
||||
let bboxHeight = 0;
|
||||
|
||||
for (const instruction of drawingInstructions.instructions) {
|
||||
if (instruction instanceof parts.drawing.MoveInstruction) {
|
||||
pathString += ` M ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
|
||||
|
||||
bboxMinX = Math.min(bboxMinX, instruction.x);
|
||||
bboxMaxX = Math.max(bboxMaxX, instruction.x);
|
||||
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
|
||||
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
|
||||
path.pathSegList.appendItem(path.createSVGPathSegMovetoAbs(instruction.x, instruction.y + this._baselineOffset));
|
||||
bboxWidth = Math.max(bboxWidth, instruction.x);
|
||||
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
|
||||
}
|
||||
else if (instruction instanceof parts.drawing.LineInstruction) {
|
||||
pathString += ` L ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
|
||||
|
||||
bboxMinX = Math.min(bboxMinX, instruction.x);
|
||||
bboxMaxX = Math.max(bboxMaxX, instruction.x);
|
||||
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
|
||||
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
|
||||
path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(instruction.x, instruction.y + this._baselineOffset));
|
||||
bboxWidth = Math.max(bboxWidth, instruction.x);
|
||||
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
|
||||
}
|
||||
else if (instruction instanceof parts.drawing.CubicBezierCurveInstruction) {
|
||||
pathString += ` C ${ instruction.x1.toFixed(3) } ${ (instruction.y1 + this._baselineOffset).toFixed(3) } ${ instruction.x2.toFixed(3) } ${ (instruction.y2 + this._baselineOffset).toFixed(3) } ${ instruction.x3.toFixed(3) } ${ (instruction.y3 + this._baselineOffset).toFixed(3) }`;
|
||||
|
||||
bboxMinX = Math.min(bboxMinX, instruction.x1, instruction.x2, instruction.x3);
|
||||
bboxMaxX = Math.max(bboxMaxX, instruction.x1, instruction.x2, instruction.x3);
|
||||
bboxMinY = Math.min(bboxMinY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
|
||||
bboxMaxY = Math.max(bboxMaxY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
|
||||
path.pathSegList.appendItem(path.createSVGPathSegCurvetoCubicAbs(instruction.x3, instruction.y3 + this._baselineOffset, instruction.x1, instruction.y1 + this._baselineOffset, instruction.x2, instruction.y2 + this._baselineOffset));
|
||||
bboxWidth = Math.max(bboxWidth, instruction.x1, instruction.x2, instruction.x3);
|
||||
bboxHeight = Math.max(bboxHeight, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
|
||||
}
|
||||
}
|
||||
|
||||
bboxMinX *= scaleX;
|
||||
bboxMaxX *= scaleX;
|
||||
bboxMinY *= scaleY;
|
||||
bboxMaxY *= scaleY;
|
||||
|
||||
const bboxWidth = bboxMaxX - bboxMinX;
|
||||
const bboxHeight = bboxMaxY - bboxMinY;
|
||||
|
||||
svg.width.baseVal.valueAsString = `${ bboxWidth.toFixed(3) }px`;
|
||||
svg.height.baseVal.valueAsString = `${ bboxHeight.toFixed(3) }px`;
|
||||
|
||||
// svg.viewBox.baseVal is null in atleast FF. See https://bugzilla.mozilla.org/show_bug.cgi?id=888307 which justifies it with SVG 1.2 spec.
|
||||
svg.setAttribute("viewBox", `${ bboxMinX } ${ bboxMinY } ${ bboxWidth } ${ bboxHeight }`);
|
||||
|
||||
svg.style.position = "relative";
|
||||
svg.style.left = `${ bboxMinX.toFixed(3) }px`;
|
||||
svg.style.top = `${ bboxMinY.toFixed(3) }px`;
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("version", "1.1");
|
||||
svg.width.baseVal.valueAsString = `${ (bboxWidth * scaleX).toFixed(3) }px`;
|
||||
svg.height.baseVal.valueAsString = `${ (bboxHeight * scaleY).toFixed(3) }px`;
|
||||
|
||||
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
svg.appendChild(g);
|
||||
g.setAttribute("transform", `scale(${ scaleX.toFixed(3) } ${ scaleY.toFixed(3) })`);
|
||||
|
||||
g.appendChild(path);
|
||||
path.setAttribute("d", pathString);
|
||||
path.setAttribute("fill", fillColor.toString());
|
||||
|
||||
return svg;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../../utility/map";
|
||||
import { Promise } from "../../utility/promise";
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,7 @@ function prepareFontSizeElement(fontFamily: string, fontSize: number, fallbackFo
|
||||
function lineHeightForFontSize(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<number> {
|
||||
prepareFontSizeElement(fontFamily, fontSize, fallbackFonts, fontSizeElement);
|
||||
|
||||
return new Promise<number>(resolve => setTimeout(() => resolve(fontSizeElement.offsetHeight), 1000));
|
||||
return new Promise(resolve => setTimeout(() => resolve(fontSizeElement.offsetHeight), 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,12 +78,14 @@ function fontMetricsFromLineHeights(lowerLineHeight: number, upperLineHeight: nu
|
||||
* @param {string} fontFamily
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
* @return {!Promise.<[number, number]>}
|
||||
* @return {!Promise.<number>}
|
||||
*/
|
||||
export function calculateFontMetrics(fontFamily: string, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<[number, number]> {
|
||||
return lineHeightForFontSize(fontFamily, 180, fallbackFonts, fontSizeElement).then(lowerLineHeight =>
|
||||
lineHeightForFontSize(fontFamily, 360, fallbackFonts, fontSizeElement).then(upperLineHeight =>
|
||||
fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight)));
|
||||
fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../../utility/map";
|
||||
|
||||
/**
|
||||
* This class represents a single keyframe. It has a list of CSS properties (names and values) associated with a point in time. Multiple keyframes make up an animation.
|
||||
*
|
||||
|
||||
@@ -18,16 +18,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Color } from "../../parts";
|
||||
import { AnimationCollection } from "./animation-collection";
|
||||
|
||||
import { Dialogue } from "../../types/dialogue";
|
||||
import { Style } from "../../types/style";
|
||||
import { fontSizeForLineHeight } from "./font-size";
|
||||
|
||||
import { WebRenderer } from "./renderer";
|
||||
|
||||
import { RendererSettings } from "../settings";
|
||||
|
||||
import { AnimationCollection } from "./animation-collection";
|
||||
import { fontSizeForLineHeight } from "./font-size";
|
||||
import { WebRenderer } from "./renderer";
|
||||
import { Color } from "../../parts";
|
||||
|
||||
import { Style } from "../../types/style";
|
||||
import { Dialogue } from "../../types/dialogue";
|
||||
|
||||
import { Map } from "../../utility/map";
|
||||
|
||||
/**
|
||||
* This class represents the style attribute of a span.
|
||||
@@ -47,7 +51,7 @@ export class SpanStyles {
|
||||
private _defaultStyle: Style;
|
||||
|
||||
private _italic: boolean;
|
||||
private _bold: boolean | number;
|
||||
private _bold: Object;
|
||||
private _underline: boolean;
|
||||
private _strikeThrough: boolean;
|
||||
|
||||
@@ -85,7 +89,7 @@ export class SpanStyles {
|
||||
private _blur: number;
|
||||
private _gaussianBlur: number;
|
||||
|
||||
private _nextFilterId: number = 0;
|
||||
private _nextFilterId = 0;
|
||||
|
||||
constructor(renderer: WebRenderer, dialogue: Dialogue, private _scaleX: number, private _scaleY: number, private _settings: RendererSettings, private _fontSizeElement: HTMLDivElement, private _svgDefsElement: SVGDefsElement, private _fontMetricsCache: Map<string, [number, number]>) {
|
||||
this._id = `${ renderer.id }-${ dialogue.id }`;
|
||||
@@ -99,7 +103,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @param {libjass.Style} newStyle The new defaults to reset the style to. If null, the styles are reset to the default style of the Dialogue.
|
||||
*/
|
||||
reset(newStyle: Style | undefined | null): void {
|
||||
reset(newStyle: Style): void {
|
||||
if (newStyle === undefined || newStyle === null) {
|
||||
newStyle = this._defaultStyle;
|
||||
}
|
||||
@@ -123,12 +127,12 @@ export class SpanStyles {
|
||||
|
||||
this.letterSpacing = newStyle.letterSpacing;
|
||||
|
||||
this.rotationX = 0;
|
||||
this.rotationY = 0;
|
||||
this.rotationZ = newStyle.rotationZ;
|
||||
this._rotationX = null;
|
||||
this._rotationY = null;
|
||||
this._rotationZ = newStyle.rotationZ;
|
||||
|
||||
this.skewX = 0;
|
||||
this.skewY = 0;
|
||||
this._skewX = null;
|
||||
this._skewY = null;
|
||||
|
||||
this.primaryColor = newStyle.primaryColor;
|
||||
this.secondaryColor = newStyle.secondaryColor;
|
||||
@@ -140,8 +144,8 @@ export class SpanStyles {
|
||||
this.outlineAlpha = newStyle.outlineColor.alpha;
|
||||
this.shadowAlpha = newStyle.shadowColor.alpha;
|
||||
|
||||
this.blur = null as any as number;
|
||||
this.gaussianBlur = null as any as number;
|
||||
this.blur = null;
|
||||
this.gaussianBlur = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,11 +166,13 @@ export class SpanStyles {
|
||||
fontStyleOrWeight += "bold ";
|
||||
}
|
||||
else if (this._bold !== false) {
|
||||
fontStyleOrWeight += this._bold.toFixed(0) + " ";
|
||||
fontStyleOrWeight += this._bold + " ";
|
||||
}
|
||||
|
||||
const lineHeight = this._scaleY * (isTextOnlySpan ? this._fontScaleX : 1) * this._fontSize;
|
||||
const fontSize = fontSizeForLineHeight(this._fontName, lineHeight, this._settings.fallbackFonts, this._fontSizeElement, this._fontMetricsCache);
|
||||
const fontSize = (
|
||||
this._scaleY *
|
||||
fontSizeForLineHeight(this._fontName, this._fontSize * (isTextOnlySpan ? this._fontScaleX : 1), this._settings.fallbackFonts, this._fontSizeElement, this._fontMetricsCache)
|
||||
).toFixed(3);
|
||||
const lineHeight = (this._scaleY * this._fontSize).toFixed(3);
|
||||
|
||||
let fonts = this._fontName;
|
||||
|
||||
@@ -187,7 +193,7 @@ export class SpanStyles {
|
||||
fonts += `, ${ this._settings.fallbackFonts }`;
|
||||
}
|
||||
|
||||
span.style.font = `${ fontStyleOrWeight }${ fontSize.toFixed(3) }px/${ lineHeight.toFixed(3) }px ${ fonts }`;
|
||||
span.style.font = `${ fontStyleOrWeight }${ fontSize }px/${ lineHeight }px ${ fonts }`;
|
||||
|
||||
let textDecoration = "";
|
||||
if (this._underline) {
|
||||
@@ -212,8 +218,19 @@ export class SpanStyles {
|
||||
transform += `scaleY(${ this._fontScaleY }) `;
|
||||
}
|
||||
}
|
||||
if (this._skewX !== 0 || this._skewY !== 0) {
|
||||
transform += `matrix(1, ${ this._skewY }, ${ this._skewX }, 1, 0, 0) `;
|
||||
if (this._rotationY !== null) {
|
||||
transform += `rotateY(${ this._rotationY }deg) `;
|
||||
}
|
||||
if (this._rotationX !== null) {
|
||||
transform += `rotateX(${ this._rotationX }deg) `;
|
||||
}
|
||||
if (this._rotationZ !== 0) {
|
||||
transform += `rotateZ(${ -1 * this._rotationZ }deg) `;
|
||||
}
|
||||
if (this._skewX !== null || this._skewY !== null) {
|
||||
const skewX = SpanStyles._valueOrDefault(this._skewX, 0);
|
||||
const skewY = SpanStyles._valueOrDefault(this._skewY, 0);
|
||||
transform += `matrix(1, ${ skewY }, ${ skewX }, 1, 0, 0) `;
|
||||
}
|
||||
if (transform !== "") {
|
||||
span.style.webkitTransform = transform;
|
||||
@@ -227,8 +244,9 @@ export class SpanStyles {
|
||||
|
||||
const outlineWidth = this._scaleX * this._outlineWidth;
|
||||
const outlineHeight = this._scaleY * this._outlineHeight;
|
||||
const shadowDepthX = this._scaleX * this._shadowDepthX;
|
||||
const shadowDepthY = this._scaleY * this._shadowDepthY;
|
||||
|
||||
const filterWrapperSpan = document.createElement("span");
|
||||
filterWrapperSpan.appendChild(span);
|
||||
|
||||
let primaryColor = this._primaryColor.withAlpha(this._primaryAlpha);
|
||||
let outlineColor = this._outlineColor.withAlpha(this._outlineAlpha);
|
||||
@@ -250,83 +268,55 @@ export class SpanStyles {
|
||||
span.style.color = primaryColor.toString();
|
||||
|
||||
if (this._settings.enableSvg) {
|
||||
this._svg(
|
||||
span,
|
||||
outlineWidth, outlineHeight, outlineColor,
|
||||
shadowDepthX, shadowDepthY, shadowColor,
|
||||
);
|
||||
this._setSvgOutlineOnSpan(filterWrapperSpan, outlineWidth, outlineHeight, outlineColor, this._primaryAlpha);
|
||||
}
|
||||
else {
|
||||
this._textShadow(
|
||||
span,
|
||||
outlineWidth, outlineHeight, outlineColor,
|
||||
shadowDepthX, shadowDepthY, shadowColor,
|
||||
);
|
||||
this._setTextShadowOutlineOnSpan(span, outlineWidth, outlineHeight, outlineColor);
|
||||
}
|
||||
|
||||
if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
|
||||
const shadowCssString = `${ shadowColor.toString() } ${ (this._shadowDepthX * this._scaleX).toFixed(3) }px ${ (this._shadowDepthY * this._scaleY).toFixed(3) }px 0px`;
|
||||
if (span.style.textShadow === "") {
|
||||
span.style.textShadow = shadowCssString;
|
||||
}
|
||||
else {
|
||||
span.style.textShadow += ", " + shadowCssString;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._rotationX !== 0 || this._rotationY !== 0) {
|
||||
// Perspective needs to be set on a "transformable element"
|
||||
span.style.display = "inline-block";
|
||||
filterWrapperSpan.style.display = "inline-block";
|
||||
}
|
||||
|
||||
span.style.webkitAnimation = animationCollection.animationStyle;
|
||||
span.style.animation = animationCollection.animationStyle;
|
||||
|
||||
return span;
|
||||
return filterWrapperSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!HTMLBRElement}
|
||||
*/
|
||||
makeNewLine(): HTMLBRElement {
|
||||
const result = document.createElement("br");
|
||||
result.style.lineHeight = `${ (this._scaleY * this._fontSize).toFixed(3) }px`;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!HTMLSpanElement} span
|
||||
* @param {!HTMLSpanElement} filterWrapperSpan
|
||||
* @param {number} outlineWidth
|
||||
* @param {number} outlineHeight
|
||||
* @param {!libjass.parts.Color} outlineColor
|
||||
* @param {number} shadowDepthX
|
||||
* @param {number} shadowDepthY
|
||||
* @param {!libjass.parts.Color} shadowColor
|
||||
* @param {number} primaryAlpha
|
||||
*/
|
||||
private _svg(
|
||||
span: HTMLSpanElement,
|
||||
outlineWidth: number, outlineHeight: number, outlineColor: Color,
|
||||
shadowDepthX: number, shadowDepthY: number, shadowColor: Color,
|
||||
): void {
|
||||
private _setSvgOutlineOnSpan(filterWrapperSpan: HTMLSpanElement, outlineWidth: number, outlineHeight: number, outlineColor: Color, primaryAlpha: number): void {
|
||||
const filterId = `svg-filter-${ this._id }-${ this._nextFilterId++ }`;
|
||||
|
||||
const filterElement = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
||||
filterElement.id = filterId;
|
||||
filterElement.x.baseVal.valueAsString = "-50%";
|
||||
filterElement.width.baseVal.valueAsString = "200%";
|
||||
filterElement.y.baseVal.valueAsString = "-50%";
|
||||
filterElement.height.baseVal.valueAsString = "200%";
|
||||
|
||||
if (outlineWidth > 0 || outlineHeight > 0 || shadowDepthX > 0 || shadowDepthY > 0) {
|
||||
// Start with SourceAlpha. Leave the alpha as 0 if it's 0, and set it to 1 if it's greater than 0
|
||||
const source = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
|
||||
filterElement.appendChild(source);
|
||||
source.in1.baseVal = "SourceAlpha";
|
||||
source.result.baseVal = "source";
|
||||
|
||||
const sourceAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
|
||||
source.appendChild(sourceAlphaTransferNode);
|
||||
sourceAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
sourceAlphaTransferNode.intercept.baseVal = 0;
|
||||
|
||||
/* The alphas of all colored pixels of the SourceAlpha should be made as close to 1 as possible. This way the summed outlines below will be uniformly dark.
|
||||
* Multiply the pixels by 1 / primaryAlpha so that the primaryAlpha pixels become 1. A higher value would make the outline larger and too sharp,
|
||||
* leading to jagged outer edge and transparent space around the inner edge between itself and the SourceGraphic.
|
||||
*/
|
||||
sourceAlphaTransferNode.slope.baseVal = (this._primaryAlpha === 0) ? 1 : (1 / this._primaryAlpha);
|
||||
|
||||
if (outlineWidth > 0 || outlineHeight > 0) {
|
||||
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
|
||||
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
|
||||
*/
|
||||
|
||||
// Merge the individual outlines
|
||||
const mergedOutlines = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
filterElement.appendChild(mergedOutlines);
|
||||
mergedOutlines.result.baseVal = "outline-alpha";
|
||||
|
||||
let outlineNumber = 0;
|
||||
|
||||
const increment = (!this._settings.preciseOutlines && this._gaussianBlur > 0) ? this._gaussianBlur : 1;
|
||||
@@ -361,61 +351,78 @@ export class SpanStyles {
|
||||
}
|
||||
}
|
||||
})((x: number, y: number): void => {
|
||||
const outlineId = `outline${ outlineNumber }`;
|
||||
|
||||
const outlineFilter = document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
|
||||
filterElement.insertBefore(outlineFilter, mergedOutlines);
|
||||
filterElement.appendChild(outlineFilter);
|
||||
outlineFilter.in1.baseVal = "source";
|
||||
outlineFilter.operator.baseVal = SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_DILATE;
|
||||
outlineFilter.radiusX.baseVal = x;
|
||||
outlineFilter.radiusY.baseVal = y;
|
||||
outlineFilter.result.baseVal = outlineId;
|
||||
|
||||
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
mergedOutlines.appendChild(outlineReferenceNode);
|
||||
outlineReferenceNode.in1.baseVal = outlineId;
|
||||
outlineFilter.result.baseVal = `outline${ outlineNumber }`;
|
||||
|
||||
outlineNumber++;
|
||||
});
|
||||
|
||||
((addOutline: (x: number, y: number) => void) => {
|
||||
if ((outlineWidth % 1) > 0) {
|
||||
addOutline(outlineWidth, 0);
|
||||
addOutline(-outlineWidth, 0);
|
||||
}
|
||||
// Start with SourceAlpha. Leave the alpha as 0 if it's 0, and set it to 1 if it's greater than 0
|
||||
const source = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
|
||||
filterElement.insertBefore(source, filterElement.firstElementChild);
|
||||
source.in1.baseVal = "SourceAlpha";
|
||||
source.result.baseVal = "source";
|
||||
|
||||
if ((outlineHeight % 1) > 0) {
|
||||
addOutline(0, outlineHeight);
|
||||
addOutline(0, -outlineHeight);
|
||||
}
|
||||
})((x: number, y: number): void => {
|
||||
const outlineId = `outline${ outlineNumber }`;
|
||||
const sourceAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
|
||||
source.appendChild(sourceAlphaTransferNode);
|
||||
sourceAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
|
||||
const outlineFilter = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
|
||||
filterElement.insertBefore(outlineFilter, mergedOutlines);
|
||||
outlineFilter.in1.baseVal = "source";
|
||||
outlineFilter.dx.baseVal = x;
|
||||
outlineFilter.dy.baseVal = y;
|
||||
outlineFilter.result.baseVal = outlineId;
|
||||
/* The alphas of all colored pixels of the SourceAlpha should be made as close to 1 as possible. This way the summed outlines below will be uniformly dark.
|
||||
* Multiply the pixels by 1 / primaryAlpha so that the primaryAlpha pixels become 1. A higher value would make the outline larger and too sharp,
|
||||
* leading to jagged outer edge and transparent space around the inner edge between itself and the SourceGraphic.
|
||||
*/
|
||||
sourceAlphaTransferNode.slope.baseVal = (primaryAlpha === 0) ? 1 : (1 / primaryAlpha);
|
||||
|
||||
sourceAlphaTransferNode.intercept.baseVal = 0;
|
||||
|
||||
// Merge the individual outlines
|
||||
const mergedOutlines = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
filterElement.appendChild(mergedOutlines);
|
||||
|
||||
for (let i = 0; i < outlineNumber; i++) {
|
||||
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
mergedOutlines.appendChild(outlineReferenceNode);
|
||||
outlineReferenceNode.in1.baseVal = outlineId;
|
||||
|
||||
outlineNumber++;
|
||||
});
|
||||
outlineReferenceNode.in1.baseVal = `outline${ i }`;
|
||||
}
|
||||
|
||||
// Color it with the outline color
|
||||
const coloredOutline = createComponentTransferFilter(outlineColor);
|
||||
filterElement.appendChild(coloredOutline);
|
||||
coloredOutline.in1.baseVal = "outline-alpha";
|
||||
const coloredSource = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
|
||||
filterElement.appendChild(coloredSource);
|
||||
coloredSource.setAttribute("color-interpolation-filters", "sRGB");
|
||||
|
||||
const outlineRedTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncR");
|
||||
coloredSource.appendChild(outlineRedTransferNode);
|
||||
outlineRedTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
outlineRedTransferNode.slope.baseVal = 0;
|
||||
outlineRedTransferNode.intercept.baseVal = outlineColor.red / 255 * outlineColor.alpha;
|
||||
|
||||
const outlineGreenTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncG");
|
||||
coloredSource.appendChild(outlineGreenTransferNode);
|
||||
outlineGreenTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
outlineGreenTransferNode.slope.baseVal = 0;
|
||||
outlineGreenTransferNode.intercept.baseVal = outlineColor.green / 255 * outlineColor.alpha;
|
||||
|
||||
const outlineBlueTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
|
||||
coloredSource.appendChild(outlineBlueTransferNode);
|
||||
outlineBlueTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
outlineBlueTransferNode.slope.baseVal = 0;
|
||||
outlineBlueTransferNode.intercept.baseVal = outlineColor.blue / 255 * outlineColor.alpha;
|
||||
|
||||
const outlineAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
|
||||
coloredSource.appendChild(outlineAlphaTransferNode);
|
||||
outlineAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
outlineAlphaTransferNode.slope.baseVal = outlineColor.alpha;
|
||||
outlineAlphaTransferNode.intercept.baseVal = 0;
|
||||
|
||||
// Blur the merged outline
|
||||
if (this._gaussianBlur > 0) {
|
||||
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
filterElement.appendChild(gaussianBlurFilter);
|
||||
|
||||
// Don't use setStdDeviation - cloneNode() clears it in Chrome
|
||||
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
|
||||
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
|
||||
}
|
||||
@@ -426,65 +433,21 @@ export class SpanStyles {
|
||||
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
|
||||
}
|
||||
|
||||
// Cut out the source, so only the exterior remains
|
||||
const cutoutNode = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
filterElement.appendChild(cutoutNode);
|
||||
cutoutNode.in2.baseVal = "source";
|
||||
cutoutNode.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT;
|
||||
cutoutNode.result.baseVal = "outline-colored";
|
||||
// Cut out the source, so only the outline remains
|
||||
const outlineCutoutNode = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
filterElement.appendChild(outlineCutoutNode);
|
||||
outlineCutoutNode.in2.baseVal = "source";
|
||||
outlineCutoutNode.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT;
|
||||
|
||||
if (shadowDepthX > 0 || shadowDepthY > 0) {
|
||||
const shadowFilter = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
|
||||
filterElement.appendChild(shadowFilter);
|
||||
shadowFilter.in1.baseVal = "outline-alpha";
|
||||
shadowFilter.dx.baseVal = shadowDepthX;
|
||||
shadowFilter.dy.baseVal = shadowDepthY;
|
||||
|
||||
// Color it with the shadow color
|
||||
const coloredShadow = createComponentTransferFilter(shadowColor);
|
||||
filterElement.appendChild(coloredShadow);
|
||||
|
||||
let lastFilter: SVGFEComponentTransferElement | SVGFEGaussianBlurElement | SVGFEConvolveMatrixElement = coloredShadow;
|
||||
|
||||
// Blur the shadow
|
||||
if (this._gaussianBlur > 0) {
|
||||
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
filterElement.appendChild(gaussianBlurFilter);
|
||||
|
||||
// Don't use setStdDeviation - cloneNode() clears it in Chrome
|
||||
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
|
||||
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
|
||||
|
||||
lastFilter = gaussianBlurFilter;
|
||||
}
|
||||
for (let i = 0; i < this._blur; i++) {
|
||||
const blurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
|
||||
filterElement.appendChild(blurFilter);
|
||||
blurFilter.setAttribute("kernelMatrix", "1 2 1 2 4 2 1 2 1");
|
||||
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
|
||||
|
||||
lastFilter = blurFilter;
|
||||
}
|
||||
|
||||
lastFilter.result.baseVal = "shadow";
|
||||
}
|
||||
|
||||
// Merge the main text, outline and shadow
|
||||
const mergedResult = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
filterElement.appendChild(mergedResult);
|
||||
|
||||
if (shadowDepthX > 0 || shadowDepthY > 0) {
|
||||
const shadowReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
mergedResult.appendChild(shadowReferenceNode);
|
||||
shadowReferenceNode.in1.baseVal = "shadow";
|
||||
}
|
||||
// Merge the outline with the SourceGraphic
|
||||
const mergedOutlineAndSourceGraphic = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
filterElement.appendChild(mergedOutlineAndSourceGraphic);
|
||||
|
||||
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
mergedResult.appendChild(outlineReferenceNode);
|
||||
outlineReferenceNode.in1.baseVal = "outline-colored";
|
||||
mergedOutlineAndSourceGraphic.appendChild(outlineReferenceNode);
|
||||
|
||||
const sourceGraphicReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
mergedResult.appendChild(sourceGraphicReferenceNode);
|
||||
mergedOutlineAndSourceGraphic.appendChild(sourceGraphicReferenceNode);
|
||||
sourceGraphicReferenceNode.in1.baseVal = "SourceGraphic";
|
||||
}
|
||||
else {
|
||||
@@ -492,8 +455,6 @@ export class SpanStyles {
|
||||
if (this._gaussianBlur > 0) {
|
||||
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
filterElement.appendChild(gaussianBlurFilter);
|
||||
|
||||
// Don't use setStdDeviation - cloneNode() clears it in Chrome
|
||||
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
|
||||
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
|
||||
}
|
||||
@@ -506,18 +467,10 @@ export class SpanStyles {
|
||||
}
|
||||
|
||||
if (filterElement.childElementCount > 0) {
|
||||
const filterId = `libjass-svg-filter-${ this._id }-${ this._nextFilterId++ }`;
|
||||
|
||||
this._svgDefsElement.appendChild(filterElement);
|
||||
filterElement.id = filterId;
|
||||
filterElement.x.baseVal.valueAsString = "-50%";
|
||||
filterElement.width.baseVal.valueAsString = "200%";
|
||||
filterElement.y.baseVal.valueAsString = "-50%";
|
||||
filterElement.height.baseVal.valueAsString = "200%";
|
||||
|
||||
const filterProperty = `url("#${ filterId }")`;
|
||||
span.style.webkitFilter = filterProperty;
|
||||
span.style.filter = filterProperty;
|
||||
filterWrapperSpan.style.webkitFilter = `url("#${ filterId }")`;
|
||||
filterWrapperSpan.style.filter = `url("#${ filterId }")`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,18 +479,10 @@ export class SpanStyles {
|
||||
* @param {number} outlineWidth
|
||||
* @param {number} outlineHeight
|
||||
* @param {!libjass.parts.Color} outlineColor
|
||||
* @param {number} shadowDepthX
|
||||
* @param {number} shadowDepthY
|
||||
* @param {!libjass.parts.Color} shadowColor
|
||||
*/
|
||||
private _textShadow(
|
||||
span: HTMLSpanElement,
|
||||
outlineWidth: number, outlineHeight: number, outlineColor: Color,
|
||||
shadowDepthX: number, shadowDepthY: number, shadowColor: Color,
|
||||
): void {
|
||||
private _setTextShadowOutlineOnSpan(span: HTMLSpanElement, outlineWidth: number, outlineHeight: number, outlineColor: Color): void {
|
||||
if (outlineWidth > 0 || outlineHeight > 0) {
|
||||
let outlineCssString = "";
|
||||
let shadowCssString = "";
|
||||
|
||||
((addOutline: (x: number, y: number) => void) => {
|
||||
for (let x = 0; x <= outlineWidth; x++) {
|
||||
@@ -558,53 +503,39 @@ export class SpanStyles {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((outlineWidth % 1) > 0) {
|
||||
addOutline(outlineWidth, 0);
|
||||
addOutline(-outlineWidth, 0);
|
||||
}
|
||||
|
||||
if ((outlineHeight % 1) > 0) {
|
||||
addOutline(0, outlineHeight);
|
||||
addOutline(0, -outlineHeight);
|
||||
}
|
||||
})((x: number, y: number): void => {
|
||||
outlineCssString += `, ${ outlineColor.toString() } ${ x.toFixed(3) }px ${ y.toFixed(3) }px ${ this._gaussianBlur.toFixed(3) }px`;
|
||||
|
||||
if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
|
||||
shadowCssString += `, ${ shadowColor.toString() } ${ (x + shadowDepthX).toFixed(3) }px ${ (y + shadowDepthY).toFixed(3) }px ${ this._gaussianBlur.toFixed(3) }px`;
|
||||
}
|
||||
outlineCssString += `, ${ outlineColor.toString() } ${ x }px ${ y }px ${ this._gaussianBlur.toFixed(3) }px`;
|
||||
});
|
||||
|
||||
span.style.textShadow = (outlineCssString + shadowCssString).substr(", ".length);
|
||||
}
|
||||
else if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
|
||||
const shadowCssString = `${ shadowColor.toString() } ${ shadowDepthX.toFixed(3) }px ${ shadowDepthY.toFixed(3) }px 0px`;
|
||||
if (span.style.textShadow === "") {
|
||||
span.style.textShadow = shadowCssString;
|
||||
}
|
||||
else {
|
||||
span.style.textShadow += ", " + shadowCssString;
|
||||
}
|
||||
span.style.textShadow = outlineCssString.substr(", ".length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!HTMLBRElement}
|
||||
*/
|
||||
makeNewLine(): HTMLBRElement {
|
||||
const result = document.createElement("br");
|
||||
result.style.lineHeight = `${ (this._scaleY * this._fontSize).toFixed(3) }px`;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the italic property. null defaults it to the default style's value.
|
||||
*
|
||||
* @type {?boolean}
|
||||
*/
|
||||
set italic(value: boolean) {
|
||||
this._italic = valueOrDefault(value, this._defaultStyle.italic);
|
||||
this._italic = SpanStyles._valueOrDefault(value, this._defaultStyle.italic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bold property. null defaults it to the default style's value.
|
||||
*
|
||||
* @type {(?boolean|?number)}
|
||||
* @type {(?number|?boolean)}
|
||||
*/
|
||||
set bold(value: boolean | number | null) {
|
||||
this._bold = valueOrDefault(value, this._defaultStyle.bold);
|
||||
set bold(value: Object) {
|
||||
this._bold = SpanStyles._valueOrDefault(value, this._defaultStyle.bold);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,7 +544,7 @@ export class SpanStyles {
|
||||
* @type {?boolean}
|
||||
*/
|
||||
set underline(value: boolean) {
|
||||
this._underline = valueOrDefault(value, this._defaultStyle.underline);
|
||||
this._underline = SpanStyles._valueOrDefault(value, this._defaultStyle.underline);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -622,7 +553,7 @@ export class SpanStyles {
|
||||
* @type {?boolean}
|
||||
*/
|
||||
set strikeThrough(value: boolean) {
|
||||
this._strikeThrough = valueOrDefault(value, this._defaultStyle.strikeThrough);
|
||||
this._strikeThrough = SpanStyles._valueOrDefault(value, this._defaultStyle.strikeThrough);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,7 +561,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get outlineWidth(): number {
|
||||
get outlineWidth() {
|
||||
return this._outlineWidth;
|
||||
}
|
||||
|
||||
@@ -640,16 +571,16 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set outlineWidth(value: number) {
|
||||
this._outlineWidth = valueOrDefault(value, this._defaultStyle.outlineThickness);
|
||||
this._outlineWidth = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the outline height property.
|
||||
* Gets the outline width property.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get outlineHeight(): number {
|
||||
return this._outlineHeight;
|
||||
get outlineHeight() {
|
||||
return this._outlineWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -658,7 +589,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set outlineHeight(value: number) {
|
||||
this._outlineHeight = valueOrDefault(value, this._defaultStyle.outlineThickness);
|
||||
this._outlineHeight = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -666,7 +597,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get shadowDepthX(): number {
|
||||
get shadowDepthX() {
|
||||
return this._shadowDepthX;
|
||||
}
|
||||
|
||||
@@ -676,7 +607,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set shadowDepthX(value: number) {
|
||||
this._shadowDepthX = valueOrDefault(value, this._defaultStyle.shadowDepth);
|
||||
this._shadowDepthX = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -684,7 +615,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get shadowDepthY(): number {
|
||||
get shadowDepthY() {
|
||||
return this._shadowDepthY;
|
||||
}
|
||||
|
||||
@@ -694,7 +625,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set shadowDepthY(value: number) {
|
||||
this._shadowDepthY = valueOrDefault(value, this._defaultStyle.shadowDepth);
|
||||
this._shadowDepthY = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,7 +633,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get blur(): number {
|
||||
get blur() {
|
||||
return this._blur;
|
||||
}
|
||||
|
||||
@@ -712,7 +643,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set blur(value: number) {
|
||||
this._blur = valueOrDefault<number>(value, 0);
|
||||
this._blur = SpanStyles._valueOrDefault(value, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,7 +651,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get gaussianBlur(): number {
|
||||
get gaussianBlur() {
|
||||
return this._gaussianBlur;
|
||||
}
|
||||
|
||||
@@ -730,7 +661,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set gaussianBlur(value: number) {
|
||||
this._gaussianBlur = valueOrDefault<number>(value, 0);
|
||||
this._gaussianBlur = SpanStyles._valueOrDefault(value, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,8 +669,8 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {?string}
|
||||
*/
|
||||
set fontName(value: string | null) {
|
||||
this._fontName = valueOrDefault(value, this._defaultStyle.fontName);
|
||||
set fontName(value: string) {
|
||||
this._fontName = SpanStyles._valueOrDefault(value, this._defaultStyle.fontName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -747,7 +678,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontSize(): number {
|
||||
get fontSize() {
|
||||
return this._fontSize;
|
||||
}
|
||||
|
||||
@@ -757,7 +688,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set fontSize(value: number) {
|
||||
this._fontSize = valueOrDefault(value, this._defaultStyle.fontSize);
|
||||
this._fontSize = SpanStyles._valueOrDefault(value, this._defaultStyle.fontSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -765,7 +696,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontScaleX(): number {
|
||||
get fontScaleX() {
|
||||
return this._fontScaleX;
|
||||
}
|
||||
|
||||
@@ -775,7 +706,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set fontScaleX(value: number) {
|
||||
this._fontScaleX = valueOrDefault(value, this._defaultStyle.fontScaleX);
|
||||
this._fontScaleX = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleX);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -783,7 +714,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get fontScaleY(): number {
|
||||
get fontScaleY() {
|
||||
return this._fontScaleY;
|
||||
}
|
||||
|
||||
@@ -793,7 +724,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set fontScaleY(value: number) {
|
||||
this._fontScaleY = valueOrDefault(value, this._defaultStyle.fontScaleY);
|
||||
this._fontScaleY = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -801,7 +732,7 @@ export class SpanStyles {
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get letterSpacing(): number {
|
||||
get letterSpacing() {
|
||||
return this._letterSpacing;
|
||||
}
|
||||
|
||||
@@ -811,15 +742,15 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set letterSpacing(value: number) {
|
||||
this._letterSpacing = valueOrDefault(value, this._defaultStyle.letterSpacing);
|
||||
this._letterSpacing = SpanStyles._valueOrDefault(value, this._defaultStyle.letterSpacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the X-axis rotation property.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get rotationX(): number {
|
||||
get rotationX() {
|
||||
return this._rotationX;
|
||||
}
|
||||
|
||||
@@ -829,15 +760,15 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set rotationX(value: number) {
|
||||
this._rotationX = valueOrDefault<number>(value, 0);
|
||||
this._rotationX = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Y-axis rotation property.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get rotationY(): number {
|
||||
get rotationY() {
|
||||
return this._rotationY;
|
||||
}
|
||||
|
||||
@@ -847,15 +778,15 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set rotationY(value: number) {
|
||||
this._rotationY = valueOrDefault<number>(value, 0);
|
||||
this._rotationY = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Z-axis rotation property.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get rotationZ(): number {
|
||||
get rotationZ() {
|
||||
return this._rotationZ;
|
||||
}
|
||||
|
||||
@@ -865,15 +796,15 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set rotationZ(value: number) {
|
||||
this._rotationZ = valueOrDefault(value, this._defaultStyle.rotationZ);
|
||||
this._rotationZ = SpanStyles._valueOrDefault(value, this._defaultStyle.rotationZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the X-axis skew property.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get skewX(): number {
|
||||
get skewX() {
|
||||
return this._skewX;
|
||||
}
|
||||
|
||||
@@ -883,15 +814,15 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set skewX(value: number) {
|
||||
this._skewX = valueOrDefault<number>(value, 0);
|
||||
this._skewX = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Y-axis skew property.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get skewY(): number {
|
||||
get skewY() {
|
||||
return this._skewY;
|
||||
}
|
||||
|
||||
@@ -901,7 +832,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set skewY(value: number) {
|
||||
this._skewY = valueOrDefault<number>(value, 0);
|
||||
this._skewY = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -919,7 +850,7 @@ export class SpanStyles {
|
||||
* @type {libjass.Color}
|
||||
*/
|
||||
set primaryColor(value: Color) {
|
||||
this._primaryColor = valueOrDefault(value, this._defaultStyle.primaryColor);
|
||||
this._primaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.primaryColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -937,7 +868,7 @@ export class SpanStyles {
|
||||
* @type {libjass.Color}
|
||||
*/
|
||||
set secondaryColor(value: Color) {
|
||||
this._secondaryColor = valueOrDefault(value, this._defaultStyle.secondaryColor);
|
||||
this._secondaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.secondaryColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -955,7 +886,7 @@ export class SpanStyles {
|
||||
* @type {libjass.Color}
|
||||
*/
|
||||
set outlineColor(value: Color) {
|
||||
this._outlineColor = valueOrDefault(value, this._defaultStyle.outlineColor);
|
||||
this._outlineColor = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -973,7 +904,7 @@ export class SpanStyles {
|
||||
* @type {libjass.Color}
|
||||
*/
|
||||
set shadowColor(value: Color) {
|
||||
this._shadowColor = valueOrDefault(value, this._defaultStyle.shadowColor);
|
||||
this._shadowColor = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowColor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -991,7 +922,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set primaryAlpha(value: number) {
|
||||
this._primaryAlpha = valueOrDefault(value, this._defaultStyle.primaryColor.alpha);
|
||||
this._primaryAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.primaryColor.alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1009,7 +940,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set secondaryAlpha(value: number) {
|
||||
this._secondaryAlpha = valueOrDefault(value, this._defaultStyle.secondaryColor.alpha);
|
||||
this._secondaryAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.secondaryColor.alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1027,7 +958,7 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set outlineAlpha(value: number) {
|
||||
this._outlineAlpha = valueOrDefault(value, this._defaultStyle.outlineColor.alpha);
|
||||
this._outlineAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineColor.alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1045,49 +976,8 @@ export class SpanStyles {
|
||||
* @type {?number}
|
||||
*/
|
||||
set shadowAlpha(value: number) {
|
||||
this._shadowAlpha = valueOrDefault(value, this._defaultStyle.shadowColor.alpha);
|
||||
this._shadowAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowColor.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!libjass.parts.Color} color
|
||||
* @return {!SVGFEComponentTransferElement}
|
||||
*/
|
||||
function createComponentTransferFilter(color: Color): SVGFEComponentTransferElement {
|
||||
const result = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
|
||||
|
||||
const redTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncR");
|
||||
result.appendChild(redTransferNode);
|
||||
redTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
redTransferNode.slope.baseVal = 0;
|
||||
redTransferNode.intercept.baseVal = color.red / 255;
|
||||
|
||||
const greenTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncG");
|
||||
result.appendChild(greenTransferNode);
|
||||
greenTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
greenTransferNode.slope.baseVal = 0;
|
||||
greenTransferNode.intercept.baseVal = color.green / 255;
|
||||
|
||||
const blueTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
|
||||
result.appendChild(blueTransferNode);
|
||||
blueTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
blueTransferNode.slope.baseVal = 0;
|
||||
blueTransferNode.intercept.baseVal = color.blue / 255;
|
||||
|
||||
const alphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
|
||||
result.appendChild(alphaTransferNode);
|
||||
alphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
|
||||
alphaTransferNode.slope.baseVal = color.alpha;
|
||||
alphaTransferNode.intercept.baseVal = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?T} newValue
|
||||
* @param {!T} defaultValue
|
||||
* @return {!T}
|
||||
*/
|
||||
function valueOrDefault<T>(newValue: T | null, defaultValue: T): T {
|
||||
return ((newValue !== null) ? newValue : defaultValue);
|
||||
|
||||
private static _valueOrDefault = <T>(newValue: T, defaultValue: T): T => ((newValue !== null) ? newValue : defaultValue);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface Document {
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "defs"): SVGDefsElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feComponentTransfer"): SVGFEComponentTransferElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feComposite"): SVGFECompositeElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feConvolveMatrix"): SVGFEConvolveMatrixElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncA"): SVGFEFuncAElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncB"): SVGFEFuncBElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncG"): SVGFEFuncGElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncR"): SVGFEFuncRElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feGaussianBlur"): SVGFEGaussianBlurElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMerge"): SVGFEMergeElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMergeNode"): SVGFEMergeNodeElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMorphology"): SVGFEMorphologyElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "filter"): SVGFilterElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "g"): SVGGElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "path"): SVGPathElement;
|
||||
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "svg"): SVGSVGElement;
|
||||
}
|
||||
|
||||
interface SVGFEComponentTransferElement {
|
||||
appendChild(newChild: SVGFEFuncAElement): SVGFEFuncAElement;
|
||||
appendChild(newChild: SVGFEFuncBElement): SVGFEFuncBElement;
|
||||
appendChild(newChild: SVGFEFuncGElement): SVGFEFuncGElement;
|
||||
appendChild(newChild: SVGFEFuncRElement): SVGFEFuncRElement;
|
||||
}
|
||||
|
||||
interface SVGFEMergeElement {
|
||||
appendChild(newChild: SVGFEMergeNodeElement): SVGFEMergeNodeElement;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "./utility/map";
|
||||
|
||||
const classes = new Map<number, Function & { fromJSON?(obj: any): any }>();
|
||||
|
||||
/**
|
||||
* Registers a class as a serializable type.
|
||||
*
|
||||
* @param {function(new:*)} clazz
|
||||
*/
|
||||
export function registerClass(clazz: Function & { fromJSON?(obj: any): any }): void {
|
||||
clazz.prototype._classTag = classes.size;
|
||||
classes.set(clazz.prototype._classTag, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given object.
|
||||
*
|
||||
* @param {*} obj
|
||||
* @return {string}
|
||||
*/
|
||||
export function serialize(obj: any): string {
|
||||
return JSON.stringify(obj, (/* ujs:unreferenced */ _key: string, value: any) => {
|
||||
if (value && (value._classTag !== undefined) && !Object.prototype.hasOwnProperty.call(value, "_classTag")) {
|
||||
// Copy the _classTag from this object's prototype to itself, so that it will be serialized.
|
||||
value._classTag = value._classTag;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {*}
|
||||
*/
|
||||
export function deserialize(str: string): any {
|
||||
return JSON.parse(str, (/* ujs:unreferenced */ _key: string, value: any) => {
|
||||
if (value && (value._classTag !== undefined)) {
|
||||
const clazz = classes.get(value._classTag);
|
||||
if (clazz === undefined) {
|
||||
throw new Error(`Unknown class of tag ${ value._classTag } cannot be deserialized.`);
|
||||
}
|
||||
|
||||
if (typeof clazz.fromJSON === "function") {
|
||||
value = clazz.fromJSON(value);
|
||||
}
|
||||
else {
|
||||
const hydratedValue = Object.create(clazz.prototype);
|
||||
for (const key of Object.keys(value)) {
|
||||
hydratedValue[key] = value[key];
|
||||
}
|
||||
value = hydratedValue;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
@@ -1,24 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "dom"],
|
||||
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "CommonJS",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": false,
|
||||
"strictNullChecks": true,
|
||||
|
||||
"target": "es5",
|
||||
"module": "amd",
|
||||
"moduleResolution": "node",
|
||||
"outFile": "../lib/libjass.js",
|
||||
"noImplicitUseStrict": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
"target": "ES5",
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tslint",
|
||||
"rules": {
|
||||
"adjacent-overload-signatures": true,
|
||||
"ban-types": false,
|
||||
"member-access": false,
|
||||
"member-ordering": [true, { "order": [
|
||||
"public-static-field",
|
||||
"public-static-method",
|
||||
|
||||
"protected-static-field",
|
||||
"protected-static-method",
|
||||
|
||||
"private-static-field",
|
||||
"private-static-method",
|
||||
|
||||
"public-instance-field",
|
||||
"protected-instance-field",
|
||||
"private-instance-field",
|
||||
|
||||
"public-constructor",
|
||||
"public-instance-method",
|
||||
|
||||
"protected-constructor",
|
||||
"protected-instance-method",
|
||||
|
||||
"private-constructor",
|
||||
"private-instance-method"
|
||||
]}],
|
||||
"no-any": false,
|
||||
"no-empty-interface": false,
|
||||
"no-import-side-effect": true,
|
||||
"no-inferrable-types": false,
|
||||
"no-internal-module": true,
|
||||
"no-magic-numbers": false,
|
||||
"no-namespace": true,
|
||||
"no-non-null-assertion": false,
|
||||
"no-reference": true,
|
||||
"no-var-requires": true,
|
||||
"only-arrow-functions": false,
|
||||
"prefer-for-of": true,
|
||||
"promise-function-async": false,
|
||||
"typedef": [true, "call-signature", "parameter", "property-declaration", "member-variable-declaration"],
|
||||
"typedef-whitespace": [true, {
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}, {
|
||||
"call-signature": "space",
|
||||
"index-signature": "space",
|
||||
"parameter": "space",
|
||||
"property-declaration": "space",
|
||||
"variable-declaration": "space"
|
||||
}],
|
||||
"unified-signatures": true,
|
||||
|
||||
"await-promise": true,
|
||||
"ban": false,
|
||||
"curly": true,
|
||||
"forin": true,
|
||||
"import-blacklist": false,
|
||||
"label-position": true,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-console": false,
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-super": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-floating-promises": true,
|
||||
"no-for-in-array": true,
|
||||
"no-inferred-empty-object-type": true,
|
||||
"no-invalid-template-strings": true,
|
||||
"no-invalid-this": false,
|
||||
"no-misused-new": true,
|
||||
"no-null-keyword": false,
|
||||
"no-shadowed-variable": false,
|
||||
"no-sparse-arrays": true,
|
||||
"no-string-literal": true,
|
||||
"no-string-throw": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unbound-method": false,
|
||||
"no-unsafe-any": false,
|
||||
"no-unsafe-finally": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": [true, "check-parameters"],
|
||||
"no-use-before-declare": false,
|
||||
"no-var-keyword": true,
|
||||
"no-void-expression": [true, "ignore-arrow-function-shorthand"],
|
||||
"radix": false,
|
||||
"restrict-plus-operands": true,
|
||||
"strict-boolean-expressions": false,
|
||||
"strict-type-predicates": true,
|
||||
"switch-default": false,
|
||||
"triple-equals": true,
|
||||
"typeof-compare": true,
|
||||
"use-isnan": true,
|
||||
|
||||
"cyclomatic-complexity": false,
|
||||
"eofline": true,
|
||||
"indent": [true, "tabs"],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"max-classes-per-file": false,
|
||||
"max-file-line-count": false,
|
||||
"max-line-length": false,
|
||||
"no-default-export": true,
|
||||
"no-mergeable-namespace": true,
|
||||
"no-require-imports": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"prefer-const": [true, { "destructuring": "all" }],
|
||||
"trailing-comma": [true, { "multiline": "always", "singleline": "never" }],
|
||||
|
||||
"align": [true, "statements"],
|
||||
"array-type": [true, "array"],
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
"arrow-return-shorthand": true,
|
||||
"callable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [true, "check-space"],
|
||||
"completed-docs": false,
|
||||
"file-header": ["true", "Copyright \\d{4}"],
|
||||
"import-spacing": true,
|
||||
"interface-name": [true, "never-prefix"],
|
||||
"interface-over-type-literal": false,
|
||||
"jsdoc-format": true,
|
||||
"match-default-export-name": true,
|
||||
"newline-before-return": false,
|
||||
"new-parens": true,
|
||||
"no-angle-bracket-type-assertion": true,
|
||||
"no-boolean-literal-compare": true,
|
||||
"no-consecutive-blank-lines": true,
|
||||
"no-parameter-properties": false,
|
||||
"no-reference-import": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unnecessary-callback-wrapper": true,
|
||||
"no-unnecessary-initializer": true,
|
||||
"no-unnecessary-qualifier": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-shorthand": true,
|
||||
"one-line": [true, "check-open-brace", "check-whitespace"],
|
||||
"one-variable-per-declaration": true,
|
||||
"ordered-imports": [true, { "import-sources-order": "case-insensitive", "named-imports-order": "case-insensitive" }],
|
||||
"prefer-function-over-method": [true, "allow-public", "allow-protected"],
|
||||
"prefer-method-signature": true,
|
||||
"prefer-template": [true, "allow-single-concat"],
|
||||
"quotemark": [true, "double", "avoid-escape"],
|
||||
"return-undefined": true,
|
||||
"space-before-function-paren": [true, {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always",
|
||||
"method": "never",
|
||||
"constructor": "never"
|
||||
}],
|
||||
"semicolon": [true, "always"],
|
||||
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"],
|
||||
"whitespace": [true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-type", "check-typecast", "check-preblock"]
|
||||
}
|
||||
}
|
||||
@@ -18,139 +18,39 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Attachment } from "./attachment";
|
||||
import { Dialogue } from "./dialogue";
|
||||
import { Style } from "./style";
|
||||
import { ScriptProperties } from "./script-properties";
|
||||
|
||||
import { Format } from "./misc";
|
||||
|
||||
import { verboseMode } from "../settings";
|
||||
|
||||
import * as parser from "../parser";
|
||||
import { parseLineIntoTypedTemplate } from "../parser/misc";
|
||||
import { SrtStreamParser, StreamParser } from "../parser/stream-parsers";
|
||||
import { BrowserReadableStream, Stream, StringStream, XhrStream } from "../parser/streams";
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
import { debugMode, verboseMode } from "../settings";
|
||||
import { ReadableStream, TextDecoder, TextDecoderConstructor } from "../parser/streams";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
import { Promise } from "../utility/promise";
|
||||
|
||||
import { Attachment } from "./attachment";
|
||||
import { Dialogue } from "./dialogue";
|
||||
import { Format } from "./misc";
|
||||
import { ScriptProperties } from "./script-properties";
|
||||
import { Style } from "./style";
|
||||
declare const global: {
|
||||
fetch?(url: string): Promise<{ body: ReadableStream; ok?: boolean; status?: number; }>;
|
||||
ReadableStream?: { prototype: ReadableStream; };
|
||||
TextDecoder?: TextDecoderConstructor;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class represents an ASS script. It contains the {@link libjass.ScriptProperties}, an array of {@link libjass.Style}s, and an array of {@link libjass.Dialogue}s.
|
||||
*/
|
||||
@serializable
|
||||
export class ASS {
|
||||
/**
|
||||
* Creates an ASS object from the raw text of an ASS script.
|
||||
*
|
||||
* @param {string} raw The raw text of the script.
|
||||
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
|
||||
* @return {!Promise.<!libjass.ASS>}
|
||||
*/
|
||||
static fromString(raw: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
|
||||
return ASS.fromStream(new StringStream(raw), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the given {@link libjass.parser.Stream}.
|
||||
*
|
||||
* @param {!libjass.parser.Stream} stream The stream to parse the script from
|
||||
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromStream(stream: Stream, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
|
||||
switch (type) {
|
||||
case Format.ASS:
|
||||
case "ass":
|
||||
return new StreamParser(stream).ass;
|
||||
case Format.SRT:
|
||||
case "srt":
|
||||
return new SrtStreamParser(stream).ass;
|
||||
default:
|
||||
throw new Error(`Invalid value of type: ${ type }`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the given URL.
|
||||
*
|
||||
* @param {string} url The URL of the script.
|
||||
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromUrl(url: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
|
||||
let fetchPromise: Promise<ASS>;
|
||||
|
||||
if (typeof global.fetch === "function" && BrowserReadableStream.isSupported()) {
|
||||
fetchPromise = global.fetch(url).then(response => {
|
||||
if (response.ok === false || (response.ok === undefined && (response.status === undefined || response.status < 200 || response.status > 299))) {
|
||||
throw new Error(`HTTP request for ${ url } failed with status code ${ response.status }`);
|
||||
}
|
||||
|
||||
return ASS.fromReadableStream(response.body, "utf-8", type);
|
||||
});
|
||||
}
|
||||
else {
|
||||
fetchPromise = Promise.reject<ASS>(new Error("Not supported."));
|
||||
}
|
||||
|
||||
return fetchPromise.catch(reason => {
|
||||
if (debugMode) {
|
||||
console.log("fetch() failed, falling back to XHR: %o", reason);
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
const result = ASS.fromStream(new XhrStream(xhr), type);
|
||||
xhr.open("GET", url, true);
|
||||
xhr.send();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the given ReadableStream.
|
||||
*
|
||||
* @param {!ReadableStream} stream
|
||||
* @param {string="utf-8"} encoding
|
||||
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
|
||||
return ASS.fromStream(new BrowserReadableStream(stream, encoding), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserialization for ASS objects.
|
||||
*
|
||||
* @param {!*} obj
|
||||
* @return {!libjass.ASS}
|
||||
*/
|
||||
static fromJSON(obj: any): ASS {
|
||||
const result: ASS = Object.create(ASS.prototype);
|
||||
|
||||
result._properties = obj._properties;
|
||||
|
||||
result._styles = new Map<string, Style>();
|
||||
for (const name of Object.keys(obj._styles)) {
|
||||
const style = obj._styles[name];
|
||||
result._styles.set(name, style);
|
||||
}
|
||||
|
||||
result._dialogues = obj._dialogues;
|
||||
result._attachments = obj._attachments;
|
||||
result._stylesFormatSpecifier = obj._stylesFormatSpecifier;
|
||||
result._dialoguesFormatSpecifier = obj._dialoguesFormatSpecifier;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _properties: ScriptProperties = new ScriptProperties();
|
||||
private _styles: Map<string, Style> = new Map<string, Style>();
|
||||
private _dialogues: Dialogue[] = [];
|
||||
private _attachments: Attachment[] = [];
|
||||
|
||||
private _stylesFormatSpecifier: string[] | null = null;
|
||||
private _dialoguesFormatSpecifier: string[] | null = null;
|
||||
private _stylesFormatSpecifier: string[] = null;
|
||||
private _dialoguesFormatSpecifier: string[] = null;
|
||||
|
||||
/**
|
||||
* The properties of this script.
|
||||
@@ -191,36 +91,36 @@ export class ASS {
|
||||
/**
|
||||
* The format specifier for the styles section.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
get stylesFormatSpecifier(): string[] | null {
|
||||
get stylesFormatSpecifier(): string[] {
|
||||
return this._stylesFormatSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The format specifier for the events section.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
set stylesFormatSpecifier(value: string[] | null) {
|
||||
this._stylesFormatSpecifier = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The format specifier for the styles section.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
get dialoguesFormatSpecifier(): string[] | null {
|
||||
get dialoguesFormatSpecifier(): string[] {
|
||||
return this._dialoguesFormatSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The format specifier for the events section.
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
set dialoguesFormatSpecifier(value: string[] | null) {
|
||||
set stylesFormatSpecifier(value: string[]) {
|
||||
this._stylesFormatSpecifier = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The format specifier for the events section.
|
||||
*
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
set dialoguesFormatSpecifier(value: string[]) {
|
||||
this._dialoguesFormatSpecifier = value;
|
||||
}
|
||||
|
||||
@@ -239,10 +139,6 @@ export class ASS {
|
||||
* @param {string} line The line from the script that contains the new style.
|
||||
*/
|
||||
addStyle(line: string): void {
|
||||
if (this._stylesFormatSpecifier === null) {
|
||||
throw new Error("stylesFormatSpecifier is not set.");
|
||||
}
|
||||
|
||||
const styleLine = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
|
||||
if (styleLine === null || styleLine.type !== "Style") {
|
||||
return;
|
||||
@@ -267,10 +163,6 @@ export class ASS {
|
||||
* @param {string} line The line from the script that contains the new event.
|
||||
*/
|
||||
addEvent(line: string): void {
|
||||
if (this._dialoguesFormatSpecifier === null) {
|
||||
throw new Error("dialoguesFormatSpecifier is not set.");
|
||||
}
|
||||
|
||||
const dialogueLine = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
|
||||
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
|
||||
return;
|
||||
@@ -298,25 +190,81 @@ export class ASS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom JSON serialization for ASS objects.
|
||||
* Creates an ASS object from the raw text of an ASS script.
|
||||
*
|
||||
* @return {!*}
|
||||
* @param {string} raw The raw text of the script.
|
||||
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
|
||||
* @return {!Promise.<!libjass.ASS>}
|
||||
*/
|
||||
toJSON(): any {
|
||||
const result = Object.create(null);
|
||||
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
|
||||
return ASS.fromStream(new parser.StringStream(raw), type);
|
||||
}
|
||||
|
||||
result._properties = this._properties;
|
||||
/**
|
||||
* Creates an ASS object from the given {@link libjass.parser.Stream}.
|
||||
*
|
||||
* @param {!libjass.parser.Stream} stream The stream to parse the script from
|
||||
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromStream(stream: parser.Stream, type: Format = Format.ASS): Promise<ASS> {
|
||||
switch (type) {
|
||||
case Format.ASS:
|
||||
return new parser.StreamParser(stream).ass;
|
||||
case Format.SRT:
|
||||
return new parser.SrtStreamParser(stream).ass;
|
||||
default:
|
||||
throw new Error(`Illegal value of type: ${ type }`);
|
||||
}
|
||||
}
|
||||
|
||||
result._styles = Object.create(null);
|
||||
this._styles.forEach((style, name) => result._styles[name] = style);
|
||||
/**
|
||||
* Creates an ASS object from the given URL.
|
||||
*
|
||||
* @param {string} url The URL of the script.
|
||||
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromUrl(url: string, type: Format = Format.ASS): Promise<ASS> {
|
||||
let fetchPromise: Promise<ASS>;
|
||||
|
||||
result._dialogues = this._dialogues;
|
||||
result._attachments = this._attachments;
|
||||
result._stylesFormatSpecifier = this._stylesFormatSpecifier;
|
||||
result._dialoguesFormatSpecifier = this._dialoguesFormatSpecifier;
|
||||
if (
|
||||
typeof global.fetch === "function" &&
|
||||
typeof global.ReadableStream === "function" && typeof global.ReadableStream.prototype.getReader === "function" &&
|
||||
typeof global.TextDecoder === "function"
|
||||
) {
|
||||
fetchPromise = global.fetch(url).then(response => {
|
||||
if (response.ok === false || (response.ok === undefined && (response.status < 200 || response.status > 299))) {
|
||||
throw new Error(`HTTP request for ${ url } failed with status code ${ response.status }`);
|
||||
}
|
||||
|
||||
result._classTag = (ASS.prototype as any)._classTag;
|
||||
return ASS.fromReadableStream(response.body, "utf-8", type);
|
||||
});
|
||||
}
|
||||
else {
|
||||
fetchPromise = Promise.reject<ASS>(new Error("Not supported."));
|
||||
}
|
||||
|
||||
return result;
|
||||
return fetchPromise.catch(reason => {
|
||||
console.warn("fetch() failed, falling back to XHR: %o", reason);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
const result = ASS.fromStream(new parser.XhrStream(xhr), type);
|
||||
xhr.open("GET", url, true);
|
||||
xhr.send();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the given ReadableStream.
|
||||
*
|
||||
* @param {!ReadableStream} stream
|
||||
* @param {string="utf-8"} encoding
|
||||
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
|
||||
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
|
||||
*/
|
||||
static fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format = Format.ASS): Promise<ASS> {
|
||||
return ASS.fromStream(new parser.BrowserReadableStream(stream, encoding), type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
/**
|
||||
* The type of an attachment.
|
||||
*/
|
||||
@@ -34,7 +32,6 @@ export enum AttachmentType {
|
||||
* @param {string} filename The filename of this attachment.
|
||||
* @param {number} type The type of this attachment.
|
||||
*/
|
||||
@serializable
|
||||
export class Attachment {
|
||||
private _contents: string = "";
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@ import { Style } from "./style";
|
||||
|
||||
import { valueOrDefault } from "./misc";
|
||||
|
||||
import { parseLineIntoTypedTemplate } from "../parser/misc";
|
||||
|
||||
import { parse } from "../parser/parse";
|
||||
|
||||
import * as parts from "../parts";
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
import { debugMode } from "../settings";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
@@ -44,9 +44,8 @@ import { Map } from "../utility/map";
|
||||
* @param {string} template["Text"] The text of this dialogue
|
||||
* @param {!libjass.ASS} ass The ASS object to which this dialogue belongs
|
||||
*/
|
||||
@serializable
|
||||
export class Dialogue {
|
||||
private static _lastDialogueId: number = -1;
|
||||
private static _lastDialogueId = -1;
|
||||
|
||||
private _id: number;
|
||||
|
||||
@@ -59,7 +58,7 @@ export class Dialogue {
|
||||
private _alignment: number;
|
||||
|
||||
private _rawPartsString: string;
|
||||
private _parts: parts.Part[] | null = null;
|
||||
private _parts: parts.Part[] = null;
|
||||
|
||||
private _containsTransformTag: boolean = false;
|
||||
|
||||
@@ -75,45 +74,34 @@ export class Dialogue {
|
||||
this._id = ++Dialogue._lastDialogueId;
|
||||
|
||||
let styleName = template.get("style");
|
||||
if (typeof styleName === "string") {
|
||||
if (styleName !== undefined && styleName !== null && styleName.constructor === String) {
|
||||
styleName = styleName.replace(/^\*+/, "");
|
||||
if (styleName.match(/^Default$/i) !== null) {
|
||||
styleName = "Default";
|
||||
}
|
||||
}
|
||||
|
||||
let style = (styleName !== undefined) ? ass.styles.get(styleName) : undefined;
|
||||
if (style === undefined) {
|
||||
this._style = ass.styles.get(styleName);
|
||||
if (this._style === undefined) {
|
||||
if (debugMode) {
|
||||
console.warn(`Unrecognized style ${ styleName }. Falling back to "Default"`);
|
||||
}
|
||||
|
||||
style = ass.styles.get("Default");
|
||||
if (style === undefined) {
|
||||
throw new Error(`Unrecognized style ${ styleName }. Could not fall back to "Default" style since it doesn't exist.`);
|
||||
}
|
||||
this._style = ass.styles.get("Default");
|
||||
}
|
||||
if (this._style === undefined) {
|
||||
throw new Error(`Unrecognized style ${ styleName }`);
|
||||
}
|
||||
this._style = style;
|
||||
|
||||
const start = template.get("start");
|
||||
if (typeof start !== "string") {
|
||||
throw new Error(`Dialogue start time ${ start } is not a string.`);
|
||||
}
|
||||
this._start = toTime(start);
|
||||
|
||||
const end = template.get("end");
|
||||
if (typeof end !== "string") {
|
||||
throw new Error(`Dialogue end time ${ end } is not a string.`);
|
||||
}
|
||||
this._end = toTime(end);
|
||||
this._start = Dialogue._toTime(template.get("start"));
|
||||
this._end = Dialogue._toTime(template.get("end"));
|
||||
|
||||
this._layer = Math.max(valueOrDefault(template, "layer", parseInt, value => !isNaN(value), "0"), 0);
|
||||
|
||||
const text = template.get("text");
|
||||
if (typeof text !== "string") {
|
||||
throw new Error(`Dialogue text ${ text } is not a string.`);
|
||||
this._rawPartsString = template.get("text");
|
||||
if (this._rawPartsString === undefined || this._rawPartsString === null || this._rawPartsString.constructor !== String) {
|
||||
throw new Error("Dialogue doesn't have any text.");
|
||||
}
|
||||
this._rawPartsString = text;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +172,7 @@ export class Dialogue {
|
||||
this._parsePartsString();
|
||||
}
|
||||
|
||||
return this._parts!;
|
||||
return this._parts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +199,7 @@ export class Dialogue {
|
||||
* Parses this dialogue's parts from the raw parts string.
|
||||
*/
|
||||
private _parsePartsString(): void {
|
||||
this._parts = parse(this._rawPartsString, "dialogueParts") as parts.Part[];
|
||||
this._parts = <parts.Part[]>parse(this._rawPartsString, "dialogueParts");
|
||||
|
||||
this._alignment = this._style.alignment;
|
||||
|
||||
@@ -221,21 +209,21 @@ export class Dialogue {
|
||||
}
|
||||
else if (part instanceof parts.Move) {
|
||||
if (part.t1 === null || part.t2 === null) {
|
||||
this._parts![index] =
|
||||
this._parts[index] =
|
||||
new parts.Move(
|
||||
part.x1, part.y1, part.x2, part.y2,
|
||||
0, this._end - this._start,
|
||||
0, this._end - this._start
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (part instanceof parts.Transform) {
|
||||
if (part.start === null || part.end === null || part.accel === null) {
|
||||
this._parts![index] =
|
||||
this._parts[index] =
|
||||
new parts.Transform(
|
||||
(part.start === null) ? 0 : part.start,
|
||||
(part.end === null) ? (this._end - this._start) : part.end,
|
||||
(part.accel === null) ? 1 : part.accel,
|
||||
part.tags,
|
||||
part.tags
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,19 +240,19 @@ ${ this._rawPartsString }
|
||||
was parsed as
|
||||
${ this.toString() }
|
||||
The possibly incorrect parses are:
|
||||
${ possiblyIncorrectParses.join("\n") }`,
|
||||
${ possiblyIncorrectParses.join("\n") }`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {number}
|
||||
*/
|
||||
function toTime(str: string): number {
|
||||
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
|
||||
/**
|
||||
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {number}
|
||||
*/
|
||||
private static _toTime(str: string): number {
|
||||
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
/**
|
||||
* The format of the string passed to {@link libjass.ASS.fromString}
|
||||
*/
|
||||
@@ -82,7 +84,7 @@ export interface TypedTemplate {
|
||||
* @param {T} defaultValue
|
||||
* @return {T}
|
||||
*/
|
||||
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: ((value: T) => boolean) | null, defaultValue: string): T {
|
||||
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: (value: T) => boolean, defaultValue: string): T {
|
||||
const value = template.get(key);
|
||||
if (value === undefined) {
|
||||
return converter(defaultValue);
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
import { WrappingStyle } from "./misc";
|
||||
|
||||
/**
|
||||
* This class represents the properties of a {@link libjass.ASS} script.
|
||||
*/
|
||||
@serializable
|
||||
export class ScriptProperties {
|
||||
private _resolutionX: number;
|
||||
private _resolutionY: number;
|
||||
|
||||
@@ -18,16 +18,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { valueOrDefault, BorderStyle } from "./misc";
|
||||
|
||||
import { parse } from "../parser/parse";
|
||||
|
||||
import { Color } from "../parts";
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
import { BorderStyle, valueOrDefault } from "./misc";
|
||||
|
||||
/**
|
||||
* This class represents a single global style declaration in a {@link libjass.ASS} script. The styles can be obtained via the {@link libjass.ASS.styles} property.
|
||||
*
|
||||
@@ -52,7 +50,6 @@ import { BorderStyle, valueOrDefault } from "./misc";
|
||||
* @param {string} template["MarginR"] The right margin
|
||||
* @param {string} template["MarginV"] The vertical margin
|
||||
*/
|
||||
@serializable
|
||||
export class Style {
|
||||
private _name: string;
|
||||
|
||||
@@ -96,11 +93,11 @@ export class Style {
|
||||
template = normalizedTemplate;
|
||||
}
|
||||
|
||||
const name = template.get("name");
|
||||
if (typeof name !== "string") {
|
||||
throw new Error(`Style name ${ name } is not a string.`);
|
||||
this._name = template.get("name");
|
||||
if (this._name === undefined || this._name === null || this._name.constructor !== String) {
|
||||
throw new Error("Style doesn't have a name.");
|
||||
}
|
||||
this._name = name.replace(/^\*+/, "");
|
||||
this._name = this._name.replace(/^\*+/, "");
|
||||
|
||||
this._italic = !!valueOrDefault(template, "italic", parseFloat, value => !isNaN(value), "0");
|
||||
this._bold = !!valueOrDefault(template, "bold", parseFloat, value => !isNaN(value), "0");
|
||||
@@ -117,13 +114,13 @@ export class Style {
|
||||
|
||||
this._rotationZ = valueOrDefault(template, "angle", parseFloat, value => !isNaN(value), "0");
|
||||
|
||||
this._primaryColor = valueOrDefault(template, "primarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFFFF");
|
||||
this._secondaryColor = valueOrDefault(template, "secondarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFF00");
|
||||
this._outlineColor = valueOrDefault(template, "outlinecolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00000000");
|
||||
this._shadowColor = valueOrDefault(template, "backcolour", str => parse(str, "colorWithAlpha") as Color, null, "&H80000000");
|
||||
this._primaryColor = valueOrDefault(template, "primarycolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00FFFFFF");
|
||||
this._secondaryColor = valueOrDefault(template, "secondarycolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00FFFF00");
|
||||
this._outlineColor = valueOrDefault(template, "outlinecolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
|
||||
this._shadowColor = valueOrDefault(template, "backcolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H80000000");
|
||||
|
||||
this._outlineThickness = valueOrDefault(template, "outline", parseFloat, value => value >= 0, "2");
|
||||
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (BorderStyle as any)[(BorderStyle as any)[value]] === value, "1");
|
||||
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (<any>BorderStyle)[(<any>BorderStyle)[value]] === value, "1");
|
||||
|
||||
this._shadowDepth = valueOrDefault(template, "shadow", parseFloat, value => value >= 0, "3");
|
||||
|
||||
|
||||
@@ -18,6 +18,64 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare const global: {
|
||||
Map?: typeof Map;
|
||||
};
|
||||
|
||||
export interface Map<K, V> {
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {V}
|
||||
*/
|
||||
get(key: K): V;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value?: V): Map<K, V>;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
|
||||
*
|
||||
* @type {function(new:Map, !Array.<!Array.<*>>=)}
|
||||
*/
|
||||
export var Map: {
|
||||
new <K, V>(iterable?: [K, V][]): Map<K, V>;
|
||||
prototype: Map<any, any>;
|
||||
} = global.Map;
|
||||
|
||||
/**
|
||||
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
|
||||
*
|
||||
@@ -25,7 +83,7 @@
|
||||
*
|
||||
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
|
||||
*/
|
||||
class SimpleMap<K, V> implements Map<K, V> {
|
||||
class SimpleMap<K, V> {
|
||||
private _keys: { [key: string]: K };
|
||||
private _values: { [key: string]: V };
|
||||
private _size: number;
|
||||
@@ -48,10 +106,10 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {?V}
|
||||
* @return {V}
|
||||
*/
|
||||
get(key: K): V | undefined {
|
||||
const property = keyToProperty(key);
|
||||
get(key: K): V {
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return undefined;
|
||||
@@ -65,7 +123,7 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean {
|
||||
const property = keyToProperty(key);
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
@@ -79,8 +137,8 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value: V): this {
|
||||
const property = keyToProperty(key);
|
||||
set(key: K, value: V): Map<K, V> {
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
|
||||
@@ -101,7 +159,7 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean {
|
||||
const property = keyToProperty(key);
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
@@ -130,7 +188,7 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void {
|
||||
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
|
||||
for (const property of Object.keys(this._keys)) {
|
||||
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
|
||||
}
|
||||
@@ -142,54 +200,47 @@ class SimpleMap<K, V> implements Map<K, V> {
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given key into a property name for the internal map.
|
||||
*
|
||||
* @param {K} key
|
||||
* @return {string}
|
||||
*/
|
||||
private _keyToProperty(key: K): string {
|
||||
if (typeof key === "number") {
|
||||
return `#${ key }`;
|
||||
}
|
||||
|
||||
if (typeof key === "string") {
|
||||
return `'${ key }`;
|
||||
}
|
||||
|
||||
if ((<any>key).id !== undefined) {
|
||||
return `!${ (<any>key).id }`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:variable-name */
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
|
||||
*
|
||||
* Can be set to a value using {@link libjass.configure}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
|
||||
*
|
||||
* @type {function(new:Map, !Array.<!Array.<*>>=)}
|
||||
*/
|
||||
export let Map: {
|
||||
new <K, V>(iterable?: [K, V][]): Map<K, V>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Map<any, any>;
|
||||
} = (() => {
|
||||
const globalMap = global.Map;
|
||||
|
||||
if (globalMap === undefined) {
|
||||
return SimpleMap;
|
||||
}
|
||||
|
||||
if (typeof globalMap.prototype.forEach !== "function") {
|
||||
return SimpleMap;
|
||||
}
|
||||
|
||||
if (Map === undefined || typeof Map.prototype.forEach !== "function" || (() => {
|
||||
try {
|
||||
if (new globalMap([[1, "foo"], [2, "bar"]]).size !== 2) {
|
||||
return SimpleMap;
|
||||
}
|
||||
return new Map([[1, "foo"], [2, "bar"]]).size !== 2;
|
||||
}
|
||||
catch (ex) {
|
||||
return SimpleMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
return globalMap as any;
|
||||
})();
|
||||
|
||||
/* tslint:enable:variable-name */
|
||||
})()) {
|
||||
Map = SimpleMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
|
||||
*
|
||||
* @param {?function(new:Map, !Array.<!Array.<*>>=)} value
|
||||
*/
|
||||
export function setImplementation(value: typeof Map | null): void {
|
||||
export function setImplementation(value: typeof Map): void {
|
||||
if (value !== null) {
|
||||
Map = value;
|
||||
}
|
||||
@@ -197,25 +248,3 @@ export function setImplementation(value: typeof Map | null): void {
|
||||
Map = SimpleMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given key into a property name for the internal map.
|
||||
*
|
||||
* @param {*} key
|
||||
* @return {?string}
|
||||
*/
|
||||
function keyToProperty(key: any): string | null {
|
||||
if (typeof key === "number") {
|
||||
return `#${ key }`;
|
||||
}
|
||||
|
||||
if (typeof key === "string") {
|
||||
return `'${ key }`;
|
||||
}
|
||||
|
||||
if ((key as any).id !== undefined) {
|
||||
return `!${ (key as any).id }`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,69 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
|
||||
const enqueueJob = (function (): (callback: () => void) => void {
|
||||
/* tslint:disable-next-line:variable-name */
|
||||
const MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
|
||||
declare const global: {
|
||||
Promise?: typeof Promise;
|
||||
MutationObserver?: typeof MutationObserver;
|
||||
WebkitMutationObserver?: typeof MutationObserver;
|
||||
process?: {
|
||||
nextTick(callback: () => void): void;
|
||||
}
|
||||
};
|
||||
|
||||
export interface Thenable<T> {
|
||||
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
|
||||
then: ThenableThen<T>;
|
||||
}
|
||||
|
||||
export interface ThenableThen<T> {
|
||||
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
|
||||
(resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void): void;
|
||||
}
|
||||
|
||||
export interface Promise<T> extends Thenable<T> {
|
||||
/**
|
||||
* @param {?function(T):!Thenable.<U>} onFulfilled
|
||||
* @param {?function(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(onFulfilled?: (value: T) => Thenable<U>, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
|
||||
|
||||
/**
|
||||
* @param {?function(T):U} onFulfilled
|
||||
* @param {?function(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(onFulfilled?: (value: T) => U, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
|
||||
|
||||
/**
|
||||
* @param {function(*):(T|!Thenable.<T>)} onRejected
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
catch(onRejected?: (reason: any) => T | Thenable<T>): Promise<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
|
||||
*
|
||||
* @type {function(new:Promise)}
|
||||
*/
|
||||
export var Promise: {
|
||||
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
|
||||
prototype: Promise<any>;
|
||||
resolve<T>(value: T | Thenable<T>): Promise<T>;
|
||||
reject<T>(reason: any): Promise<T>;
|
||||
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
|
||||
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
|
||||
} = global.Promise;
|
||||
|
||||
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
|
||||
const enqueueJob: (callback: () => void) => void = (function () {
|
||||
const MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
|
||||
if (global.process !== undefined && typeof global.process.nextTick === "function") {
|
||||
const nextTick = global.process.nextTick;
|
||||
return (callback: () => void) => {
|
||||
nextTick(callback);
|
||||
global.process.nextTick(callback);
|
||||
};
|
||||
}
|
||||
else if (MutationObserver !== undefined) {
|
||||
@@ -72,6 +126,80 @@ const enqueueJob = (function (): (callback: () => void) => void {
|
||||
* @param {function(function(T|!Thenable.<T>), function(*))} executor
|
||||
*/
|
||||
class SimplePromise<T> {
|
||||
private _state: SimplePromiseState = SimplePromiseState.PENDING;
|
||||
|
||||
private _fulfillReactions: FulfilledPromiseReaction<T, any>[] = [];
|
||||
private _rejectReactions: RejectedPromiseReaction<any>[] = [];
|
||||
|
||||
private _fulfilledValue: T = null;
|
||||
private _rejectedReason: any = null;
|
||||
|
||||
constructor(executor: (resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void) => void) {
|
||||
if (typeof executor !== "function") {
|
||||
throw new TypeError(`typeof executor !== "function"`);
|
||||
}
|
||||
|
||||
const { resolve, reject } = this._createResolvingFunctions();
|
||||
try {
|
||||
executor(resolve, reject);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?function(T):(U|!Thenable.<U>)} onFulfilled
|
||||
* @param {?function(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(onFulfilled: (value: T) => U | Thenable<U>, onRejected: (reason: any) => U | Thenable<U>): Promise<U> {
|
||||
const resultCapability = new DeferredPromise<U>();
|
||||
|
||||
if (typeof onFulfilled !== "function") {
|
||||
onFulfilled = (value: T) => <U><any>value;
|
||||
}
|
||||
|
||||
if (typeof onRejected !== "function") {
|
||||
onRejected = (reason: any): U => { throw reason; };
|
||||
}
|
||||
|
||||
const fulfillReaction: FulfilledPromiseReaction<T, U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onFulfilled,
|
||||
};
|
||||
|
||||
const rejectReaction: RejectedPromiseReaction<U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onRejected,
|
||||
};
|
||||
|
||||
switch (this._state) {
|
||||
case SimplePromiseState.PENDING:
|
||||
this._fulfillReactions.push(fulfillReaction);
|
||||
this._rejectReactions.push(rejectReaction);
|
||||
break;
|
||||
|
||||
case SimplePromiseState.FULFILLED:
|
||||
this._enqueueFulfilledReactionJob(fulfillReaction, this._fulfilledValue);
|
||||
break;
|
||||
|
||||
case SimplePromiseState.REJECTED:
|
||||
this._enqueueRejectedReactionJob(rejectReaction, this._rejectedReason);
|
||||
break;
|
||||
}
|
||||
|
||||
return resultCapability.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(*):(T|!Thenable.<T>)} onRejected
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
catch(onRejected?: (reason: any) => T | Thenable<T>): Promise<T> {
|
||||
return this.then(null, onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T|!Thenable.<T>} value
|
||||
* @return {!Promise.<T>}
|
||||
@@ -89,7 +217,7 @@ class SimplePromise<T> {
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
static reject<T>(reason: any): Promise<T> {
|
||||
return new Promise<T>((/* ujs:unreferenced */ _resolve, reject) => reject(reason));
|
||||
return new Promise<T>((/* ujs:unreferenced */ resolve, reject) => reject(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,83 +252,11 @@ class SimplePromise<T> {
|
||||
static race<T>(values: (T | Thenable<T>)[]): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
for (const value of values) {
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
Promise.resolve(value).then(resolve, reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _state: SimplePromiseState<T> = { state: "pending" };
|
||||
private _fulfillReactions: FulfilledPromiseReaction<T, any>[] = [];
|
||||
private _rejectReactions: RejectedPromiseReaction<any>[] = [];
|
||||
|
||||
constructor(executor: (resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void) => void) {
|
||||
/* tslint:disable-next-line:strict-type-predicates */
|
||||
if (typeof executor !== "function") {
|
||||
throw new TypeError(`typeof executor !== "function"`);
|
||||
}
|
||||
|
||||
const { resolve, reject } = this._createResolvingFunctions();
|
||||
try {
|
||||
executor(resolve, reject);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?function(T):(U|!Thenable.<U>)} onFulfilled
|
||||
* @param {?function(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(onFulfilled: ((value: T) => U | Thenable<U>) | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U> {
|
||||
const resultCapability = new DeferredPromise<U>();
|
||||
|
||||
if (typeof onFulfilled !== "function") {
|
||||
onFulfilled = (value: T) => value as any as U;
|
||||
}
|
||||
|
||||
if (typeof onRejected !== "function") {
|
||||
onRejected = (reason: any): U => { throw reason; };
|
||||
}
|
||||
|
||||
const fulfillReaction: FulfilledPromiseReaction<T, U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onFulfilled,
|
||||
};
|
||||
|
||||
const rejectReaction: RejectedPromiseReaction<U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onRejected,
|
||||
};
|
||||
|
||||
switch (this._state.state) {
|
||||
case "pending":
|
||||
this._fulfillReactions.push(fulfillReaction);
|
||||
this._rejectReactions.push(rejectReaction);
|
||||
break;
|
||||
|
||||
case "fulfilled":
|
||||
enqueueFulfilledReactionJob(fulfillReaction, this._state.value);
|
||||
break;
|
||||
|
||||
case "rejected":
|
||||
enqueueRejectedReactionJob(rejectReaction, this._state.reason);
|
||||
break;
|
||||
}
|
||||
|
||||
return resultCapability.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(*):(T|!Thenable.<T>)} onRejected
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T> {
|
||||
return this.then(undefined, onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {{ resolve(T|!Thenable.<T>), reject(*) }}
|
||||
*/
|
||||
@@ -219,29 +275,25 @@ class SimplePromise<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
/* tslint:disable-next-line:strict-type-predicates */
|
||||
if (resolution === null || (typeof resolution !== "object" && typeof resolution !== "function")) {
|
||||
this._fulfill(resolution as T);
|
||||
this._fulfill(<T>resolution);
|
||||
return;
|
||||
}
|
||||
|
||||
let then: ThenableThen<T>;
|
||||
|
||||
try {
|
||||
then = (resolution as Thenable<T>).then;
|
||||
var then = (<Thenable<T>>resolution).then;
|
||||
}
|
||||
catch (ex) {
|
||||
this._reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
/* tslint:disable-next-line:strict-type-predicates */
|
||||
if (typeof then !== "function") {
|
||||
this._fulfill(resolution as T);
|
||||
this._fulfill(<T>resolution);
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueJob(() => this._resolveWithThenable(resolution as Thenable<T>, then));
|
||||
enqueueJob(() => this._resolveWithThenable(<Thenable<T>>resolution, then));
|
||||
};
|
||||
|
||||
const reject = (reason: any): void => {
|
||||
@@ -278,12 +330,13 @@ class SimplePromise<T> {
|
||||
private _fulfill(value: T): void {
|
||||
const reactions = this._fulfillReactions;
|
||||
|
||||
this._state = { state: "fulfilled", value };
|
||||
this._fulfillReactions = [];
|
||||
this._rejectReactions = [];
|
||||
this._fulfilledValue = value;
|
||||
this._fulfillReactions = undefined;
|
||||
this._rejectReactions = undefined;
|
||||
this._state = SimplePromiseState.FULFILLED;
|
||||
|
||||
for (const reaction of reactions) {
|
||||
enqueueFulfilledReactionJob(reaction, value);
|
||||
this._enqueueFulfilledReactionJob(reaction, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,38 +346,64 @@ class SimplePromise<T> {
|
||||
private _reject(reason: any): void {
|
||||
const reactions = this._rejectReactions;
|
||||
|
||||
this._state = { state: "rejected", reason };
|
||||
this._fulfillReactions = [];
|
||||
this._rejectReactions = [];
|
||||
this._rejectedReason = reason;
|
||||
this._fulfillReactions = undefined;
|
||||
this._rejectReactions = undefined;
|
||||
this._state = SimplePromiseState.REJECTED;
|
||||
|
||||
for (const reaction of reactions) {
|
||||
enqueueRejectedReactionJob(reaction, reason);
|
||||
this._enqueueRejectedReactionJob(reaction, reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!FulfilledPromiseReaction.<T, *>} reaction
|
||||
* @param {T} value
|
||||
*/
|
||||
private _enqueueFulfilledReactionJob(reaction: FulfilledPromiseReaction<T, any>, value: T): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
try {
|
||||
handlerResult = handler(value);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!RejectedPromiseReaction.<*>} reaction
|
||||
* @param {*} reason
|
||||
*/
|
||||
private _enqueueRejectedReactionJob(reaction: RejectedPromiseReaction<any>, reason: any): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
try {
|
||||
handlerResult = handler(reason);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:variable-name */
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
|
||||
*
|
||||
* Can be set to a value using {@link libjass.configure}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
|
||||
*
|
||||
* @type {function(new:Promise)}
|
||||
*/
|
||||
export let Promise: {
|
||||
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Promise<any>;
|
||||
resolve<T>(value: T | Thenable<T>): Promise<T>;
|
||||
reject<T>(reason: any): Promise<T>;
|
||||
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
|
||||
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
|
||||
} = global.Promise || SimplePromise;
|
||||
|
||||
/* tslint:enable:variable-name */
|
||||
if (Promise === undefined) {
|
||||
Promise = SimplePromise;
|
||||
}
|
||||
|
||||
interface FulfilledPromiseReaction<T, U> {
|
||||
/** @type {!libjass.DeferredPromise.<U>} */
|
||||
@@ -351,14 +430,18 @@ interface RejectedPromiseReaction<U> {
|
||||
/**
|
||||
* The state of the {@link ./utility/promise.SimplePromise}
|
||||
*/
|
||||
type SimplePromiseState<T> = { state: "pending" } | { state: "fulfilled"; value: T; } | { state: "rejected"; reason: any; };
|
||||
enum SimplePromiseState {
|
||||
PENDING = 0,
|
||||
FULFILLED = 1,
|
||||
REJECTED = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
|
||||
*
|
||||
* @param {?function(new:Promise)} value
|
||||
*/
|
||||
export function setImplementation(value: typeof Promise | null): void {
|
||||
export function setImplementation(value: typeof Promise): void {
|
||||
if (value !== null) {
|
||||
Promise = value;
|
||||
}
|
||||
@@ -371,6 +454,8 @@ export function setImplementation(value: typeof Promise | null): void {
|
||||
* A deferred promise.
|
||||
*/
|
||||
export class DeferredPromise<T> {
|
||||
private _promise: Promise<T>;
|
||||
|
||||
/**
|
||||
* @type {function(T|!Thenable.<T>)}
|
||||
*/
|
||||
@@ -381,8 +466,6 @@ export class DeferredPromise<T> {
|
||||
*/
|
||||
reject: (reason: any) => void;
|
||||
|
||||
private _promise: Promise<T>;
|
||||
|
||||
constructor() {
|
||||
this._promise = new Promise<T>((resolve, reject) => {
|
||||
Object.defineProperties(this, {
|
||||
@@ -451,47 +534,3 @@ export function lastly<T>(promise: Promise<T>, body: () => void): Promise<T> {
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!FulfilledPromiseReaction.<T, *>} reaction
|
||||
* @param {T} value
|
||||
*/
|
||||
function enqueueFulfilledReactionJob<T>(reaction: FulfilledPromiseReaction<T, any>, value: T): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
try {
|
||||
handlerResult = handler(value);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!RejectedPromiseReaction.<*>} reaction
|
||||
* @param {*} reason
|
||||
*/
|
||||
function enqueueRejectedReactionJob(reaction: RejectedPromiseReaction<any>, reason: any): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
try {
|
||||
handlerResult = handler(reason);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,51 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare const global: {
|
||||
Set?: typeof Set;
|
||||
};
|
||||
|
||||
export interface Set<T> {
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): Set<T>;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean;
|
||||
|
||||
/**
|
||||
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
|
||||
*
|
||||
* @type {function(new:Set, !Array.<T>=)}
|
||||
*/
|
||||
export var Set: {
|
||||
new <T>(iterable?: T[]): Set<T>;
|
||||
prototype: Set<any>;
|
||||
} = global.Set;
|
||||
|
||||
/**
|
||||
* Set implementation for browsers that don't support it. Only supports Number and String elements.
|
||||
*
|
||||
@@ -25,7 +70,7 @@
|
||||
*
|
||||
* @param {!Array.<T>=} iterable Only an array of values is supported.
|
||||
*/
|
||||
class SimpleSet<T> implements Set<T> {
|
||||
class SimpleSet<T> {
|
||||
private _elements: { [key: string]: T };
|
||||
private _size: number;
|
||||
|
||||
@@ -49,8 +94,8 @@ class SimpleSet<T> implements Set<T> {
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): this {
|
||||
const property = toProperty(value);
|
||||
add(value: T): Set<T> {
|
||||
const property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Set implementation only supports Number and String values.");
|
||||
@@ -77,7 +122,7 @@ class SimpleSet<T> implements Set<T> {
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean {
|
||||
const property = toProperty(value);
|
||||
const property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
@@ -90,7 +135,7 @@ class SimpleSet<T> implements Set<T> {
|
||||
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void {
|
||||
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
for (const property of Object.keys(this._elements)) {
|
||||
const element = this._elements[property];
|
||||
callbackfn.call(thisArg, element, element, this);
|
||||
@@ -103,54 +148,43 @@ class SimpleSet<T> implements Set<T> {
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value into a property name for the internal map.
|
||||
*
|
||||
* @param {T} value
|
||||
* @return {string}
|
||||
*/
|
||||
private _toProperty(value: T): string {
|
||||
if (typeof value === "number") {
|
||||
return `#${ value }`;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return `'${ value }`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:variable-name */
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
|
||||
*
|
||||
* Can be set to a value using {@link libjass.configure}
|
||||
*
|
||||
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
|
||||
*
|
||||
* @type {function(new:Set, !Array.<T>=)}
|
||||
*/
|
||||
export let Set: {
|
||||
new <T>(iterable?: T[]): Set<T>;
|
||||
/* tslint:disable-next-line:member-ordering */
|
||||
prototype: Set<any>;
|
||||
} = (() => {
|
||||
const globalSet = global.Set;
|
||||
|
||||
if (globalSet === undefined) {
|
||||
return SimpleSet;
|
||||
}
|
||||
|
||||
if (typeof globalSet.prototype.forEach !== "function") {
|
||||
return SimpleSet;
|
||||
}
|
||||
|
||||
if (Set === undefined || typeof Set.prototype.forEach !== "function" || (() => {
|
||||
try {
|
||||
if ((new globalSet([1, 2])).size !== 2) {
|
||||
return SimpleSet;
|
||||
}
|
||||
return new Set([1, 2]).size !== 2;
|
||||
}
|
||||
catch (ex) {
|
||||
return SimpleSet;
|
||||
return true;
|
||||
}
|
||||
|
||||
return globalSet as any;
|
||||
})();
|
||||
|
||||
/* tslint:enable:variable-name */
|
||||
})()) {
|
||||
Set = SimpleSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
|
||||
*
|
||||
* @param {?function(new:Set, !Array.<T>=)} value
|
||||
*/
|
||||
export function setImplementation(value: typeof Set | null): void {
|
||||
export function setImplementation(value: typeof Set): void {
|
||||
if (value !== null) {
|
||||
Set = value;
|
||||
}
|
||||
@@ -158,21 +192,3 @@ export function setImplementation(value: typeof Set | null): void {
|
||||
Set = SimpleSet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value into a property name for the internal map.
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {?string}
|
||||
*/
|
||||
function toProperty(value: any): string | null {
|
||||
if (typeof value === "number") {
|
||||
return `#${ value }`;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return `'${ value }`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class inheritance shim.
|
||||
*
|
||||
* @param {!Function} derived
|
||||
* @param {!Function} base
|
||||
*/
|
||||
export function __extends(derived: any, base: any): void {
|
||||
for (const property in base) {
|
||||
if (base.hasOwnProperty(property)) {
|
||||
derived[property] = base[property];
|
||||
}
|
||||
}
|
||||
|
||||
function __() {
|
||||
this.constructor = derived;
|
||||
}
|
||||
|
||||
__.prototype = base.prototype;
|
||||
|
||||
derived.prototype = new (<any>__)();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator shim.
|
||||
*
|
||||
* @param {!Array.<!Function>} decorators
|
||||
* @param {!*} target
|
||||
* @param {string=} key
|
||||
* @return {*}
|
||||
*/
|
||||
export function __decorate(decorators: Function[], target: any, key?: string): any {
|
||||
if (arguments.length < 3) {
|
||||
return decorateClass(<any>decorators.reverse(), target);
|
||||
}
|
||||
else {
|
||||
decorateField(<any>decorators.reverse(), target, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class decorator shim.
|
||||
*
|
||||
* @param {!Array.<function(function(new(): T)): function(new(): T)>} decorators
|
||||
* @param {function(new(): T)} clazz
|
||||
* @return {function(new(): T)}
|
||||
*/
|
||||
function decorateClass<T>(decorators: ((clazz: { new (...args: any[]): T }) => { new (...args: any[]): T })[], clazz: { new (...args: any[]): T }): { new (...args: any[]): T } {
|
||||
for (const decorator of decorators) {
|
||||
clazz = decorator(clazz) || clazz;
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class member decorator shim.
|
||||
*
|
||||
* @param {!Array.<function(T, string)>} decorators
|
||||
* @param {!T} proto
|
||||
* @param {string} name
|
||||
*/
|
||||
function decorateField<T>(decorators: ((proto: T, name: string) => void)[], proto: T, name: string): void {
|
||||
for (const decorator of decorators) {
|
||||
decorator(proto, name);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { deserialize, serialize } from "../serialization";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
import { DeferredPromise, Promise } from "../utility/promise";
|
||||
import { Promise, DeferredPromise } from "../utility/promise";
|
||||
|
||||
import { WorkerCommands } from "./commands";
|
||||
import { getWorkerCommandHandler, registerWorkerCommand } from "./misc";
|
||||
|
||||
import { getWorkerCommandHandler, serialize, deserialize, registerWorkerCommand } from "./misc";
|
||||
|
||||
/**
|
||||
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling {@link libjass.webworker.createWorker}
|
||||
@@ -44,7 +43,33 @@ export interface WorkerChannel {
|
||||
/**
|
||||
* The signature of a handler registered to handle a particular command in {@link libjass.webworker.WorkerCommands}
|
||||
*/
|
||||
export type WorkerCommandHandler = (parameters: any) => Promise<any>;
|
||||
export interface WorkerCommandHandler {
|
||||
(parameters: any): Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface implemented by a communication channel to the other side.
|
||||
*/
|
||||
interface WorkerCommunication {
|
||||
/**
|
||||
* @param {"message"} type
|
||||
* @param {function(!MessageEvent): *} listener
|
||||
* @param {?boolean} useCapture
|
||||
*/
|
||||
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {!EventListener} listener
|
||||
* @param {?boolean} useCapture
|
||||
*/
|
||||
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
|
||||
|
||||
/**
|
||||
* @param {*} message
|
||||
*/
|
||||
postMessage(message: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface implemented by a request sent to the other side of the communication channel.
|
||||
@@ -107,10 +132,10 @@ interface WorkerResponseMessage {
|
||||
export class WorkerChannelImpl implements WorkerChannel {
|
||||
private static _lastRequestId: number = -1;
|
||||
|
||||
private _pendingRequests: Map<number, DeferredPromise<any>> = new Map<number, DeferredPromise<any>>();
|
||||
private _pendingRequests = new Map<number, DeferredPromise<any>>();
|
||||
|
||||
constructor(private _comm: WorkerCommunication) {
|
||||
this._comm.addEventListener("message", ev => this._onMessage(ev.data as string), false);
|
||||
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,10 +182,10 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
* @param {string} rawMessage
|
||||
*/
|
||||
private _onMessage(rawMessage: string): void {
|
||||
const message = deserialize(rawMessage) as { command: WorkerCommands };
|
||||
const message = <{ command: WorkerCommands }>deserialize(rawMessage);
|
||||
|
||||
if (message.command === WorkerCommands.Response) {
|
||||
const responseMessage = message as any as WorkerResponseMessage;
|
||||
const responseMessage = <WorkerResponseMessage><any>message;
|
||||
|
||||
const deferred = this._pendingRequests.get(responseMessage.requestId);
|
||||
if (deferred !== undefined) {
|
||||
@@ -174,7 +199,7 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const requestMessage = message as WorkerRequestMessage;
|
||||
const requestMessage = <WorkerRequestMessage>message;
|
||||
const requestId = requestMessage.requestId;
|
||||
|
||||
const commandCallback = getWorkerCommandHandler(requestMessage.command);
|
||||
@@ -183,13 +208,12 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
return;
|
||||
}
|
||||
|
||||
/* tslint:disable-next-line:no-floating-promises */
|
||||
commandCallback(requestMessage.parameters).then<WorkerResponseMessage>(
|
||||
result => ({ requestId, error: null, result }),
|
||||
error => ({ requestId, error, result: null }),
|
||||
error => ({ requestId, error, result: null })
|
||||
).then(responseMessage => this._respond(responseMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkerCommand(WorkerCommands.Ping, () => Promise.resolve(null));
|
||||
registerWorkerCommand(WorkerCommands.Ping, parameters => new Promise<void>(resolve => resolve(null)));
|
||||
|
||||
@@ -18,19 +18,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
import { Promise, DeferredPromise } from "../utility/promise";
|
||||
|
||||
import { WorkerChannel, WorkerChannelImpl } from "./channel";
|
||||
export { WorkerChannel } from "./channel";
|
||||
|
||||
export { WorkerCommands } from "./commands";
|
||||
|
||||
declare const exports: any;
|
||||
|
||||
/**
|
||||
* Indicates whether web workers are supposed in this environment or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export const supported = global.Worker !== undefined;
|
||||
export const supported = typeof Worker !== "undefined";
|
||||
|
||||
const _scriptNode = (global.document !== undefined && global.document.currentScript !== undefined) ? global.document.currentScript : null;
|
||||
const _scriptNode = (typeof document !== "undefined" && document.currentScript !== undefined) ? document.currentScript : null;
|
||||
|
||||
/**
|
||||
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
|
||||
@@ -39,21 +45,13 @@ const _scriptNode = (global.document !== undefined && global.document.currentScr
|
||||
* the path will be determined from the src attribute of the <script> element that contains the currently running copy of libjass.js
|
||||
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
|
||||
*/
|
||||
export function createWorker(scriptPath?: string): WorkerChannel {
|
||||
if (scriptPath === undefined) {
|
||||
if (_scriptNode === null) {
|
||||
throw new Error("Could not auto-detect path of libjass.js, and explicit path was not passed in.");
|
||||
}
|
||||
|
||||
scriptPath = _scriptNode.src;
|
||||
}
|
||||
|
||||
export function createWorker(scriptPath: string = _scriptNode.src): WorkerChannel {
|
||||
return new WorkerChannelImpl(new Worker(scriptPath));
|
||||
}
|
||||
|
||||
if (global.WorkerGlobalScope !== undefined && global instanceof global.WorkerGlobalScope) {
|
||||
// This is a web worker. Set up a channel to talk back to the main thread.
|
||||
declare const global: any;
|
||||
|
||||
/* tslint:disable-next-line:no-unused-expression */
|
||||
if (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope) {
|
||||
// This is a web worker. Set up a channel to talk back to the main thread.
|
||||
new WorkerChannelImpl(global);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
import { WorkerCommandHandler } from "./channel";
|
||||
import { WorkerCommands } from "./commands";
|
||||
import { WorkerCommandHandler } from "./channel";
|
||||
|
||||
const workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
|
||||
|
||||
const classPrototypes = new Map<number, any>();
|
||||
|
||||
/**
|
||||
* Registers a handler for the given worker command.
|
||||
*
|
||||
@@ -41,6 +43,50 @@ export function registerWorkerCommand(command: WorkerCommands, handler: WorkerCo
|
||||
* @param {number} command
|
||||
* @return {?function(*, function(*, *))}
|
||||
*/
|
||||
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler | undefined {
|
||||
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler {
|
||||
return workerCommands.get(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a prototype as a deserializable type.
|
||||
*
|
||||
* @param {!*} prototype
|
||||
*/
|
||||
export function registerClassPrototype(prototype: any): void {
|
||||
prototype._classTag = classPrototypes.size;
|
||||
classPrototypes.set(prototype._classTag, prototype);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} obj
|
||||
* @return {string}
|
||||
*/
|
||||
export function serialize(obj: any): string {
|
||||
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
value._classTag = value._classTag;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {*}
|
||||
*/
|
||||
export function deserialize(str: string): any {
|
||||
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
const hydratedValue = Object.create(classPrototypes.get(value._classTag));
|
||||
for (const key of Object.keys(value)) {
|
||||
if (key !== "_classTag") {
|
||||
hydratedValue[key] = value[key];
|
||||
}
|
||||
}
|
||||
value = hydratedValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface Document {
|
||||
/**
|
||||
* @type {!HTMLScriptElement}
|
||||
*/
|
||||
currentScript: HTMLScriptElement;
|
||||
}
|
||||
|
||||
interface WorkerGlobalScope {
|
||||
/**
|
||||
* @param {*} message
|
||||
*/
|
||||
postMessage(message: any): void;
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {function(*)} listener
|
||||
* @param {boolean} useCapture
|
||||
*/
|
||||
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
|
||||
}
|
||||
declare var WorkerGlobalScope: {
|
||||
prototype: WorkerGlobalScope;
|
||||
new (): WorkerGlobalScope;
|
||||
};
|
||||
@@ -2,8 +2,8 @@
|
||||
Title:
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
PlayResX: 256
|
||||
PlayResY: 144
|
||||
Scroll Position: 0
|
||||
Active Line: 0
|
||||
Video Zoom Percent: 1
|
||||
@@ -11,7 +11,7 @@ ScaledBorderAndShadow: yes
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Arial,500,&H7F0000FF,&H00FFFFFF,&H7F000000,&H7F000000,0,0,0,0,100,100,0,0,1,10,0,2,75,75,75,1
|
||||
Style: Default,Arial,100,&H7F0000FF,&H00FFFFFF,&H7F000000,&H7F000000,0,0,0,0,100,100,0,0,1,2,0,2,15,15,15,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
|
||||
@@ -21,11 +21,10 @@
|
||||
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
|
||||
tdd.suite("alpha", function () {
|
||||
tdd.test("Basic", function () {
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 1280, 720, "rgb(47, 163, 254)");
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 256, 144, "rgb(47, 163, 254)");
|
||||
return testPage
|
||||
.prepare()
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); })
|
||||
.then(function (testPage) { return testPage.done(); });
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,32 +0,0 @@
|
||||
[Script Info]
|
||||
Title:
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
Scroll Position: 0
|
||||
Active Line: 0
|
||||
Video Zoom Percent: 1
|
||||
ScaledBorderAndShadow: yes
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Arial,90,&H00FFFFFF,&H00000000,&H00000000,&H96000000,0,0,0,0,100,100,0,0,1,0,0,2,75,75,75,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an1\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an2\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an3\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an7\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an8\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an9\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:01.00,0:00:02.00,Default,,0,0,0,,{\an1\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:02.00,0:00:03.00,Default,,0,0,0,,{\an2\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:03.00,0:00:04.00,Default,,0,0,0,,{\an3\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:04.00,0:00:05.00,Default,,0,0,0,,{\an4\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:05.00,0:00:06.00,Default,,0,0,0,,{\an5\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:06.00,0:00:07.00,Default,,0,0,0,,{\an6\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:07.00,0:00:08.00,Default,,0,0,0,,{\an7\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:08.00,0:00:09.00,Default,,0,0,0,,{\an8\pos(640,360)\frz45}MM\NMM
|
||||
Dialogue: 1,0:00:09.00,0:00:10.00,Default,,0,0,0,,{\an9\pos(640,360)\frz45}MM\NMM
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
|
||||
tdd.suite("fr", function () {
|
||||
tdd.test("frz", function () {
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fr/frz.ass", 1280, 720);
|
||||
return testPage
|
||||
.prepare()
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(0.5, require.toUrl("./frz-01.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1.5, require.toUrl("./frz-02.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(2.5, require.toUrl("./frz-03.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(3.5, require.toUrl("./frz-04.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(4.5, require.toUrl("./frz-05.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(5.5, require.toUrl("./frz-06.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(6.5, require.toUrl("./frz-07.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(7.5, require.toUrl("./frz-08.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(8.5, require.toUrl("./frz-09.png")); })
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(9.5, require.toUrl("./frz-10.png")); })
|
||||
.then(function (testPage) { return testPage.done(); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
@@ -2,8 +2,8 @@
|
||||
Title:
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
PlayResX: 1280
|
||||
PlayResY: 720
|
||||
PlayResX: 256
|
||||
PlayResY: 144
|
||||
Scroll Position: 0
|
||||
Active Line: 0
|
||||
Video Zoom Percent: 1
|
||||
@@ -11,7 +11,7 @@ ScaledBorderAndShadow: yes
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Arial,180,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,10,6,2,75,75,75,1
|
||||
Style: Default,Arial,36,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
|
||||
@@ -21,11 +21,10 @@
|
||||
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
|
||||
tdd.suite("fscx and fscy", function () {
|
||||
tdd.test("Basic", function () {
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fsc/fsc.ass", 1280, 720);
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fsc/fsc.ass", 256, 144);
|
||||
return testPage
|
||||
.prepare()
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./fsc-1.png")); })
|
||||
.then(function (testPage) { return testPage.done(); });
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./fsc-1.png")); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 2.9 KiB |