Compare commits
86
Commits
v0.6.0
...
new-rotation
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6053f59a1 | ||
|
|
73755e0c8a | ||
|
|
f951475103 | ||
|
|
77474771e6 | ||
|
|
55f9cb42fe | ||
|
|
a8addddbd6 | ||
|
|
ccd4a53cfb | ||
|
|
3928bf57f0 | ||
|
|
dd6d008b74 | ||
|
|
45aabac358 | ||
|
|
55c9dc10ca | ||
|
|
32c232308f | ||
|
|
2eaa6e451d | ||
|
|
03ed6091c2 | ||
|
|
43c3e26596 | ||
|
|
9e584bb75b | ||
|
|
e62f150f32 | ||
|
|
24ccc5200c | ||
|
|
c627e72b03 | ||
|
|
6eeb634a36 | ||
|
|
2349b82f0f | ||
|
|
443714878b | ||
|
|
7a6f956b49 | ||
|
|
73e464e357 | ||
|
|
71d58e686c | ||
|
|
adaba54be0 | ||
|
|
5be310a6ba | ||
|
|
ee45e0379e | ||
|
|
848898c8d0 | ||
|
|
deda90698b | ||
|
|
774b218aff | ||
|
|
6bb4873d67 | ||
|
|
54b4245482 | ||
|
|
39d75c189d | ||
|
|
ae56620f91 | ||
|
|
ee07ca1d7c | ||
|
|
b6ca859f4b | ||
|
|
c4f87d54bf | ||
|
|
b662698e0c | ||
|
|
0198fb235c | ||
|
|
fc5dce86ea | ||
|
|
d918b1256d | ||
|
|
c65305fbe8 | ||
|
|
111c4199ff | ||
|
|
8bc205fc01 | ||
|
|
e69221af7d | ||
|
|
6ba048e55a | ||
|
|
8dda6d67e6 | ||
|
|
7c6793be8c | ||
|
|
123339519d | ||
|
|
12374765ed | ||
|
|
255447b147 | ||
|
|
32507ad644 | ||
|
|
d06777d2b3 | ||
|
|
b727f3c6e2 | ||
|
|
140393fcca | ||
|
|
2d308b7130 | ||
|
|
6a282416a8 | ||
|
|
40ebcfc6b7 | ||
|
|
d23e57d215 | ||
|
|
77ca69e1d3 | ||
|
|
c2ff6dc369 | ||
|
|
af9ea7c886 | ||
|
|
e1eb4f8e6f | ||
|
|
7303fd0f53 | ||
|
|
96bf8d7f6c | ||
|
|
74de507782 | ||
|
|
0155cd7392 | ||
|
|
974c8f70ff | ||
|
|
e4cd237779 | ||
|
|
4dec3b36f4 | ||
|
|
e275cfbf2e | ||
|
|
4d25127617 | ||
|
|
8f6deb2f8e | ||
|
|
3958d9f504 | ||
|
|
120a8e8cbd | ||
|
|
99dfafeafd | ||
|
|
379265414a | ||
|
|
e09a34cca6 | ||
|
|
b49bd14e55 | ||
|
|
febd4f18ca | ||
|
|
a633963b9f | ||
|
|
b0db48f28e | ||
|
|
c893984115 | ||
|
|
a4ef8b7ef8 | ||
|
|
a26a238b09 |
@@ -4,22 +4,28 @@
|
||||
|
||||
1. Run the following command
|
||||
|
||||
npm install
|
||||
```shell
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install the dependencies - [Gulp](http://gulpjs.com/), [Mocha](http://visionmedia.github.io/mocha/), [TypeScript](http://www.typescriptlang.org/), [UglifyJS2](https://github.com/mishoo/UglifyJS2) and [Vinyl](https://github.com/wearefractal/vinyl). It will then run gulp to build libjass.js and minify it into libjass.min.js
|
||||
This will install the dependencies - [Gulp](http://gulpjs.com/), [Mocha](http://visionmedia.github.io/mocha/), [TypeScript](http://www.typescriptlang.org/), [UglifyJS2](https://github.com/mishoo/UglifyJS2) and [Vinyl](https://github.com/wearefractal/vinyl). It will then run gulp to build libjass.js and minify it into libjass.min.js
|
||||
|
||||
1. Set the URLs of the video and the ASS file in index.xhtml
|
||||
|
||||
<video id="video" src=" <URL OF VIDEO HERE> " controls="">
|
||||
<track src=" <URL OF SCRIPT HERE> " kind="metadata" data-format="ass" />
|
||||
</video>
|
||||
```html
|
||||
<video id="video" src="<URL OF VIDEO HERE>" controls="">
|
||||
<track src="<URL OF SCRIPT HERE>" kind="metadata" data-format="ass" />
|
||||
</video>
|
||||
```
|
||||
|
||||
1. Set the URLs of any fonts you want to make available in fonts.css
|
||||
|
||||
@font-face {
|
||||
font-family: " <NAME OF FONT HERE> ";
|
||||
src: url(" <URL OF FONT HERE> ");
|
||||
}
|
||||
for each font. The name of the font is what it's called in the ASS file.
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "<NAME OF FONT HERE>";
|
||||
src: url("<URL OF FONT HERE>");
|
||||
}
|
||||
```
|
||||
for each font. The name of the font is what it's called in the ASS file.
|
||||
|
||||
1. Start your web server and navigate to index.xhtml in a browser.
|
||||
|
||||
@@ -45,6 +45,30 @@ The API documentation is linked in the Links section below. Here's an overview:
|
||||
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - index.js creates this map from the @font-face rules in fonts.css.
|
||||
|
||||
|
||||
### Can I use libjass in node?
|
||||
|
||||
libjass's parser works in node.
|
||||
|
||||
```javascript
|
||||
> var libjass = require("./libjass.js")
|
||||
undefined
|
||||
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
|
||||
undefined
|
||||
> parts.join(" ")
|
||||
'Alignment { value: 8 } Text { value: Are } Italic { value: true } Text { value: you } Italic { value: false } Text { value: the one who stole the clock?! }'
|
||||
> parts.length
|
||||
6
|
||||
> parts[0].toString()
|
||||
'Alignment { value: 8 }'
|
||||
> parts[0] instanceof libjass.parts.Alignment
|
||||
true
|
||||
> parts[0].value
|
||||
8
|
||||
```
|
||||
|
||||
[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. The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun). For example, [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun.parse_dialogueParts) 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.
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
+59
-39
@@ -18,30 +18,70 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var childProcess = require("child_process");
|
||||
var fs = require("fs");
|
||||
var gulp = require("gulp");
|
||||
var path = require("path");
|
||||
|
||||
var gulp = require("gulp");
|
||||
var Vinyl = require("vinyl");
|
||||
|
||||
var Transform = require("./gulplib/helpers.js").Transform;
|
||||
var helpers = require("./gulplib/helpers.js");
|
||||
var Transform = helpers.Transform;
|
||||
var SingletonChildProcess = helpers.SingletonChildProcess;
|
||||
|
||||
Object.defineProperties(global, {
|
||||
Doc: { get: function () { return require("./gulplib/doc.js"); } },
|
||||
Mocha: { get: function () { return require("mocha"); } },
|
||||
TypeScript: { get: function () { return require("./gulplib/typescript.js"); } },
|
||||
UglifyJS: { get: function () { return require("./gulplib/uglify.js"); } },
|
||||
});
|
||||
|
||||
gulp.task("default", ["libjass.js", "libjass.min.js"]);
|
||||
|
||||
function ASTModifer(namespaces) {
|
||||
var visitor = function (current) {
|
||||
var newText = [];
|
||||
|
||||
if (current instanceof TypeScript.AST.Namespace) {
|
||||
current.members.forEach(visitor);
|
||||
}
|
||||
else if (current instanceof TypeScript.AST.Constructor) {
|
||||
newText.push("@constructor");
|
||||
|
||||
if (current.baseType !== null) {
|
||||
newText.push("@extends {" + current.baseType.fullName + "}");
|
||||
}
|
||||
|
||||
if (current.parent !== null) {
|
||||
newText.push("@memberOf " + current.parent.fullName);
|
||||
}
|
||||
|
||||
current.members.forEach(visitor);
|
||||
}
|
||||
|
||||
if (current.isPrivate) {
|
||||
newText.push("@private");
|
||||
}
|
||||
|
||||
if (current.isStatic) {
|
||||
newText.push("@static");
|
||||
}
|
||||
|
||||
if (newText.length > 0) {
|
||||
current.astNode["gulp-typescript-new-comment"] = newText;
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(namespaces).forEach(function (namespaceName) { visitor(namespaces[namespaceName]); });
|
||||
}
|
||||
|
||||
gulp.task("libjass.js", function (callback) {
|
||||
if (fs.existsSync("./libjass.js")) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
return gulp.src("./libjass.ts").pipe(TypeScript.gulp("/libjass.js", "/libjass.js.map")).pipe(UglifyJS.fixup()).pipe(gulp.dest("."));
|
||||
return gulp.src("./libjass.ts", { read: false })
|
||||
.pipe(TypeScript.gulp("/libjass.js", "/libjass.js.map", ASTModifer))
|
||||
.pipe(UglifyJS.fixup())
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
gulp.task("libjass.min.js", ["libjass.js"], function (callback) {
|
||||
@@ -67,44 +107,24 @@ gulp.task("clean", function () {
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("test", ["libjass.js"], function (callback) {
|
||||
var mocha = new Mocha({
|
||||
ui: "tdd",
|
||||
reporter: "spec"
|
||||
});
|
||||
gulp.task("watch", ["clean"], function (callback) {
|
||||
var gulpTestProcess = SingletonChildProcess(path.resolve("./gulplib/test-runner.js"));
|
||||
|
||||
gulp.src("./tests/test-*.js").pipe(Transform(function (file) {
|
||||
mocha.addFile(file.path);
|
||||
}, function () {
|
||||
mocha.run(function (failures) {
|
||||
if (failures > 0) {
|
||||
callback(new Error(failures + " test(s) failed."));
|
||||
}
|
||||
else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
gulp.src("./libjass.ts", { read: false })
|
||||
.pipe(TypeScript.watch("/libjass.js", "/libjass.js.map"))
|
||||
.pipe(UglifyJS.fixup())
|
||||
.pipe(gulp.dest("."))
|
||||
.pipe(Transform(function (file) {
|
||||
if (path.basename(file.path) === "libjass.js") {
|
||||
gulpTestProcess();
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task("watch", function (callback) {
|
||||
var spawnSubProcess = function () {
|
||||
var pathToGulp = path.resolve("./node_modules/.bin/gulp");
|
||||
var subProcess = childProcess.exec(pathToGulp + " _watch");
|
||||
subProcess.stdout.pipe(process.stdout);
|
||||
subProcess.stderr.pipe(process.stderr);
|
||||
subProcess.addListener("exit", function (code, signal) {
|
||||
spawnSubProcess();
|
||||
});
|
||||
};
|
||||
|
||||
spawnSubProcess();
|
||||
});
|
||||
|
||||
gulp.task("_watch", function () {
|
||||
return gulp.watch(["./*.ts", "./tests/*.js"], ["clean", "libjass.js", "test"]);
|
||||
gulp.watch(["./tests/test-*.js"], { debounceDelay: 1 }, function () {
|
||||
gulpTestProcess();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("doc", ["libjass.js"], function () {
|
||||
return gulp.src("./libjass.js").pipe(Doc("/api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
|
||||
return gulp.src("./libjass.ts").pipe(Doc("/api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
|
||||
});
|
||||
|
||||
+149
-615
@@ -18,16 +18,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// .\node_modules\.bin\jake doc[..\libjass-gh-pages\api.xhtml]
|
||||
|
||||
var assert = require("assert");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var vm = require("vm");
|
||||
|
||||
var UglifyJS = require("uglify-js");
|
||||
var Vinyl = require("vinyl");
|
||||
|
||||
var Transform = require("./helpers.js").Transform;
|
||||
var TypeScript = require("./typescript.js");
|
||||
|
||||
Array.prototype.concatMany = function (arr) {
|
||||
var result = this;
|
||||
@@ -40,403 +38,19 @@ Array.prototype.concatMany = function (arr) {
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return Transform(function (file) {
|
||||
UglifyJS.base54.reset();
|
||||
var compiler = new TypeScript.Compiler();
|
||||
|
||||
var filenames = [];
|
||||
var allFiles = [];
|
||||
|
||||
// Parse
|
||||
var root = UglifyJS.parse(file.contents.toString(), {
|
||||
filename: path.basename(file.path),
|
||||
toplevel: null
|
||||
});
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Read comments
|
||||
var allNames = Object.create(null);
|
||||
|
||||
var NodeType = {
|
||||
FUNCTION: 0,
|
||||
CONSTRUCTOR: 1,
|
||||
GETTER: 2,
|
||||
SETTER: 3,
|
||||
};
|
||||
|
||||
var readComment = function (node, comment, treeWalker) {
|
||||
var nodeType = null;
|
||||
|
||||
if (comment.substr(0, 2) !== "*\n") {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = comment.split("\n").map(function (line) {
|
||||
var match = line.match(/^ *\* (.*)*/);
|
||||
if (match === null) {
|
||||
return "";
|
||||
}
|
||||
return match[1];
|
||||
}).filter(function (line) {
|
||||
return line.length > 0;
|
||||
});
|
||||
|
||||
var nameParts = [];
|
||||
|
||||
if (node instanceof UglifyJS.AST_Defun) {
|
||||
nameParts.unshift(node.name.name);
|
||||
nodeType = NodeType.FUNCTION;
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_Assign) {
|
||||
if (node.left instanceof UglifyJS.AST_Dot && node.left.expression.name === "this") {
|
||||
return;
|
||||
}
|
||||
|
||||
var walkUp = function (node) {
|
||||
if (node.property !== undefined) {
|
||||
nameParts.unshift(node.property);
|
||||
walkUp(node.expression);
|
||||
}
|
||||
else {
|
||||
nameParts.unshift(node.name);
|
||||
}
|
||||
};
|
||||
walkUp(node.left);
|
||||
|
||||
if (node.right instanceof UglifyJS.AST_Function) {
|
||||
nodeType = NodeType.FUNCTION;
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_Var) {
|
||||
// Might be a Constructor if the comment contains @constructor
|
||||
nameParts.unshift(node.definitions[0].name.name);
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_ObjectKeyVal) {
|
||||
var definePropertyNode = treeWalker.find_parent(UglifyJS.AST_Call);
|
||||
if (
|
||||
(node.key === "get" || node.key === "set") &&
|
||||
definePropertyNode.expression.property === "defineProperty" &&
|
||||
definePropertyNode.expression.expression.name === "Object"
|
||||
) {
|
||||
nameParts.unshift(definePropertyNode.args[1].value);
|
||||
|
||||
var walkUp = function (node) {
|
||||
if (node.property !== undefined) {
|
||||
nameParts.unshift(node.property);
|
||||
walkUp(node.expression);
|
||||
}
|
||||
else {
|
||||
nameParts.unshift(node.name);
|
||||
}
|
||||
};
|
||||
walkUp(definePropertyNode.args[0]);
|
||||
|
||||
nodeType = (node.key === "get") ? NodeType.GETTER : NodeType.SETTER;
|
||||
}
|
||||
}
|
||||
|
||||
if (nameParts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isAbstract = false;
|
||||
var isPrivate = false;
|
||||
var isStatic = false;
|
||||
|
||||
var generics = [];
|
||||
var parameters = [];
|
||||
var returnType = null;
|
||||
|
||||
var baseType = null;
|
||||
|
||||
var rootDescription = "";
|
||||
|
||||
var lastRead = null;
|
||||
|
||||
lines.forEach(function (line) {
|
||||
var readType = function (remainingLine) {
|
||||
if (remainingLine[0] !== "{") {
|
||||
throw new Error("Missing type in line: [" + line + "]");
|
||||
}
|
||||
|
||||
var index = -1;
|
||||
var numberOfUnterminatedBraces = 0;
|
||||
for (var i = 0; i < remainingLine.length; i++) {
|
||||
if (remainingLine[i] === "{") {
|
||||
numberOfUnterminatedBraces++;
|
||||
}
|
||||
else if (remainingLine[i] === "}") {
|
||||
numberOfUnterminatedBraces--;
|
||||
|
||||
if (numberOfUnterminatedBraces === 0) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error("Unterminated type specifier.");
|
||||
}
|
||||
|
||||
var type = remainingLine.substr(1, index - 1);
|
||||
remainingLine = remainingLine.substr(index + 1).trimLeft();
|
||||
|
||||
return [type, remainingLine];
|
||||
};
|
||||
|
||||
var firstWordMatch = line.match(/^(\S+)(\s*)/);
|
||||
var firstWord = firstWordMatch[1];
|
||||
var remainingLine = line.substring(firstWordMatch[0].length);
|
||||
|
||||
if (firstWord[0] === "@") {
|
||||
lastRead = null;
|
||||
}
|
||||
|
||||
switch (firstWord) {
|
||||
case "@abstract":
|
||||
isAbstract = true;
|
||||
break;
|
||||
|
||||
case "@constructor":
|
||||
nodeType = NodeType.CONSTRUCTOR;
|
||||
break;
|
||||
|
||||
case "@extends":
|
||||
var result = readType(remainingLine);
|
||||
baseType = result[0];
|
||||
break;
|
||||
|
||||
case "@memberof":
|
||||
nameParts.unshift.apply(nameParts, remainingLine.split("."));
|
||||
break;
|
||||
|
||||
case "@param":
|
||||
var result = readType(remainingLine);
|
||||
var type = result[0];
|
||||
remainingLine = result[1];
|
||||
|
||||
var nameMatch = remainingLine.match(/(\S+)\s*/);
|
||||
var name = nameMatch[1];
|
||||
|
||||
var description = remainingLine.substr(nameMatch[0].length);
|
||||
|
||||
var subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
|
||||
if (subParameterMatch === null) {
|
||||
parameters.push(lastRead = new Parameter(name, type, description));
|
||||
}
|
||||
else {
|
||||
var parentName = subParameterMatch[1] || subParameterMatch[3];
|
||||
var childName = subParameterMatch[2] || subParameterMatch[4];
|
||||
var parentParameter = parameters.filter(function (parameter) { return parameter.name === parentName; })[0];
|
||||
parentParameter.subParameters.push(new Parameter(childName, type, description));
|
||||
}
|
||||
break;
|
||||
|
||||
case "@private":
|
||||
isPrivate = true;
|
||||
break;
|
||||
|
||||
case "@return":
|
||||
var result = readType(remainingLine);
|
||||
var type = result[0];
|
||||
var description = result[1];
|
||||
|
||||
returnType = lastRead = new ReturnType(type, description);
|
||||
|
||||
break;
|
||||
|
||||
case "@static":
|
||||
isStatic = true;
|
||||
break;
|
||||
|
||||
case "@template":
|
||||
generics.push.apply(generics, remainingLine.split(/,/).map(function (word) { return word.trim(); }));
|
||||
break;
|
||||
|
||||
case "@type":
|
||||
switch (nodeType) {
|
||||
case NodeType.GETTER:
|
||||
var result = readType(remainingLine);
|
||||
var type = result[0];
|
||||
|
||||
returnType = lastRead = type;
|
||||
break;
|
||||
|
||||
case NodeType.SETTER:
|
||||
var result = readType(remainingLine);
|
||||
var type = result[0];
|
||||
|
||||
parameters.push(lastRead = new Parameter("value", type, ""));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (lastRead !== null) {
|
||||
lastRead.description += " " + line;
|
||||
}
|
||||
else {
|
||||
rootDescription += ((rootDescription.length > 0) ? " " : "") + line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var name = new Name(nameParts);
|
||||
|
||||
switch (nodeType) {
|
||||
case NodeType.FUNCTION:
|
||||
allNames[name] = new Function(name, rootDescription, generics, parameters, returnType, isAbstract, isPrivate, isStatic);
|
||||
break;
|
||||
|
||||
case NodeType.CONSTRUCTOR:
|
||||
allNames[name] = new Constructor(name, rootDescription, generics, parameters, baseType, isAbstract, isPrivate);
|
||||
break;
|
||||
|
||||
case NodeType.GETTER:
|
||||
if (allNames[name] === undefined) {
|
||||
allNames[name] = new Property(name);
|
||||
}
|
||||
allNames[name].getter = new Getter(rootDescription, returnType);
|
||||
break;
|
||||
|
||||
case NodeType.SETTER:
|
||||
if (allNames[name] === undefined) {
|
||||
allNames[name] = new Property(name);
|
||||
}
|
||||
allNames[name].setter = new Setter(rootDescription, parameters);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var treeWalker = new UglifyJS.TreeWalker(function (node, descend) {
|
||||
node.start.comments_before.forEach(function (comment) {
|
||||
readComment(node, comment.value, treeWalker);
|
||||
});
|
||||
node.end.comments_before.forEach(function (comment) {
|
||||
readComment(node, comment.value, treeWalker);
|
||||
});
|
||||
});
|
||||
root.walk(treeWalker);
|
||||
|
||||
|
||||
// Link
|
||||
var findType = function (nameParts) {
|
||||
var result = null;
|
||||
|
||||
Object.keys(allNames).some(function (key) {
|
||||
if (!(allNames[key] instanceof Constructor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
assert.deepEqual(nameParts, allNames[key].name.parts);
|
||||
result = allNames[key];
|
||||
return true;
|
||||
}
|
||||
catch (ex) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var findTypeEndingWith = function (nameParts) {
|
||||
var result = null;
|
||||
|
||||
Object.keys(allNames).some(function (key) {
|
||||
if (!(allNames[key] instanceof Constructor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
assert.deepEqual(nameParts, allNames[key].name.parts.slice(-nameParts.length));
|
||||
result = allNames[key];
|
||||
return true;
|
||||
}
|
||||
catch (ex) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Object.keys(allNames).map(function (key) {
|
||||
return [key, allNames[key]];
|
||||
}).forEach(function (keyValuePair) {
|
||||
var key = keyValuePair[0];
|
||||
var value = keyValuePair[1];
|
||||
|
||||
if (value instanceof Constructor) {
|
||||
if (value.baseType !== null) {
|
||||
value.baseType = findType(value.baseType.split("."));
|
||||
}
|
||||
}
|
||||
|
||||
else if (value instanceof Function || value instanceof Property) {
|
||||
var thisTypeNameParts = null;
|
||||
|
||||
if (value.thisType === null) {
|
||||
thisTypeNameParts = value.name.parts.slice(0, value.name.parts.length - 1);
|
||||
if (thisTypeNameParts[thisTypeNameParts.length - 1] === "prototype") {
|
||||
thisTypeNameParts = thisTypeNameParts.slice(0, thisTypeNameParts.length - 1);
|
||||
}
|
||||
|
||||
var firstCharacter = thisTypeNameParts[thisTypeNameParts.length - 1][0];
|
||||
if (firstCharacter.toLowerCase() === firstCharacter) {
|
||||
thisTypeNameParts = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
thisTypeNameParts = value.thisType.split(".");
|
||||
}
|
||||
|
||||
if (thisTypeNameParts === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var thisType = findTypeEndingWith(thisTypeNameParts);
|
||||
if (thisType === null) {
|
||||
var thisTypeName = new Name(thisTypeNameParts);
|
||||
allNames[thisTypeName] = thisType = new Constructor(thisTypeName, "", [], [], null, false, false);
|
||||
}
|
||||
|
||||
value.name = new Name(thisType.name.parts.concat(value.name.toShortString()));
|
||||
value.thisType = thisType;
|
||||
thisType.members.push(value);
|
||||
|
||||
delete allNames[key];
|
||||
}
|
||||
});
|
||||
|
||||
return Transform(function (file, encoding) {
|
||||
filenames.push(file.path);
|
||||
allFiles.push.apply(allFiles, compiler.addFile(file));
|
||||
}, function () {
|
||||
// Walk
|
||||
var namespaces = TypeScript.AST.walk(compiler, allFiles);
|
||||
|
||||
// Make HTML
|
||||
var namespaces = Object.create(null);
|
||||
|
||||
Object.keys(allNames).forEach(function (key) {
|
||||
var value = allNames[key];
|
||||
|
||||
var nameParts = allNames[key].name.parts;
|
||||
|
||||
if (nameParts.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var namespaceName = new Name(nameParts.slice(0, nameParts.length - 1)).toString();
|
||||
var namespace = namespaces[namespaceName];
|
||||
if (namespace === undefined) {
|
||||
namespace = namespaces[namespaceName] = [];
|
||||
}
|
||||
|
||||
namespace.push(value);
|
||||
});
|
||||
|
||||
var namespaceNames = Object.keys(namespaces).sort(function (ns1, ns2) {
|
||||
return ns1.toString().localeCompare(ns2.toString());
|
||||
});
|
||||
|
||||
var sorter = (function () {
|
||||
var visibilitySorter = function (value1, value2) {
|
||||
if (value1.isPrivate === value2.isPrivate) {
|
||||
@@ -451,7 +65,7 @@ module.exports = function () {
|
||||
return 0;
|
||||
};
|
||||
|
||||
var types = [Property, Function, Constructor];
|
||||
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.Interface, TypeScript.AST.Constructor];
|
||||
var typeSorter = function (value1, value2) {
|
||||
var type1Index = -1;
|
||||
var type2Index = -1;
|
||||
@@ -470,7 +84,7 @@ module.exports = function () {
|
||||
};
|
||||
|
||||
var nameSorter = function (value1, value2) {
|
||||
return value1.name.toShortString().localeCompare(value2.name.toShortString());
|
||||
return value1.name.localeCompare(value2.name);
|
||||
};
|
||||
|
||||
var sorters = [visibilitySorter, typeSorter, nameSorter];
|
||||
@@ -488,18 +102,22 @@ module.exports = function () {
|
||||
};
|
||||
})();
|
||||
|
||||
namespaceNames.forEach(function (namespaceName) {
|
||||
namespaces[namespaceName].sort(sorter);
|
||||
var namespaceNames = Object.keys(namespaces).sort(function (ns1, ns2) {
|
||||
return ns1.localeCompare(ns2);
|
||||
});
|
||||
|
||||
namespaces[namespaceName].filter(function (value) {
|
||||
return value instanceof Constructor;
|
||||
}).forEach(function (constructor) {
|
||||
constructor.members.sort(sorter);
|
||||
namespaceNames.forEach(function (namespaceName) {
|
||||
namespaces[namespaceName].members.sort(sorter);
|
||||
|
||||
namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Interface || value instanceof TypeScript.AST.Constructor;
|
||||
}).forEach(function (interfaceOrConstructor) {
|
||||
interfaceOrConstructor.members.sort(sorter);
|
||||
});
|
||||
});
|
||||
|
||||
var sanitize = function (string) {
|
||||
return string.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
return string.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
};
|
||||
|
||||
var indenter = function (indent) {
|
||||
@@ -515,11 +133,11 @@ module.exports = function () {
|
||||
return [
|
||||
'<span class="namespace"><a href="#' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</a></span>',
|
||||
'<ul class="namespace-elements">'
|
||||
].concatMany(namespace.map(function (value) {
|
||||
].concatMany(namespace.members.map(function (value) {
|
||||
return (
|
||||
' <li' +
|
||||
((value.isPrivate === true) ? ' class="private"' : '') +
|
||||
'><a href="#' + sanitize(value.name.toString()) + '">' + sanitize(value.name.toShortString()) + '</a></li>'
|
||||
'><a href="#' + sanitize(value.fullName) + '">' + sanitize(value.name) + '</a></li>'
|
||||
);
|
||||
})).concat([
|
||||
'</ul>',
|
||||
@@ -534,7 +152,7 @@ module.exports = function () {
|
||||
(func.isAbstract ? ' abstract' : '') +
|
||||
(func.isPrivate ? ' private' : '') +
|
||||
(func.isStatic ? ' static' : '') +
|
||||
'" id="' + sanitize(func.name.toString()) +
|
||||
'" id="' + sanitize(func.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeFunctionName(func) + '</dt>',
|
||||
' <dd class="description">',
|
||||
@@ -546,13 +164,42 @@ module.exports = function () {
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeInterface = function (interface, indent) {
|
||||
return [
|
||||
'<dl class="interface' +
|
||||
(interface.isPrivate ? ' private' : '') +
|
||||
'" id="' +
|
||||
sanitize(interface.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeInterfaceName(interface) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(interface.description) + '</p>',
|
||||
' </dd>'
|
||||
].concat([
|
||||
' <dd class="members">'
|
||||
]).concatMany(interface.members.map(function (member) {
|
||||
if (member instanceof TypeScript.AST.Variable) {
|
||||
return writeVariable(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Function) {
|
||||
return writeFunction(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Property) {
|
||||
return writeProperty(member, 2);
|
||||
}
|
||||
})).concat([
|
||||
' </dd>',
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeConstructor = function (constructor, indent) {
|
||||
return [
|
||||
'<dl class="constructor' +
|
||||
(constructor.isAbstract ? ' abstract' : '') +
|
||||
(constructor.isPrivate ? ' private' : '') +
|
||||
'" id="' +
|
||||
sanitize(constructor.name.toString()) +
|
||||
sanitize(constructor.fullName) +
|
||||
'">',
|
||||
' <dt class="name">' + writeConstructorName(constructor) + '</dt>',
|
||||
' <dd class="description">',
|
||||
@@ -562,10 +209,13 @@ module.exports = function () {
|
||||
].concat(writeParameters(constructor, 1)).concat([
|
||||
' <dd class="members">'
|
||||
]).concatMany(constructor.members.map(function (member) {
|
||||
if (member instanceof Function) {
|
||||
if (member instanceof TypeScript.AST.Variable) {
|
||||
return writeVariable(member, 2);
|
||||
}
|
||||
if (member instanceof TypeScript.AST.Function) {
|
||||
return writeFunction(member, 2);
|
||||
}
|
||||
if (member instanceof Property) {
|
||||
if (member instanceof TypeScript.AST.Property) {
|
||||
return writeProperty(member, 2);
|
||||
}
|
||||
})).concat([
|
||||
@@ -574,11 +224,26 @@ module.exports = function () {
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeVariable = function (variable, indent) {
|
||||
return [
|
||||
'<dl class="variable" id="' + sanitize(variable.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(variable) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(variable.description) + '</p>',
|
||||
' </dd>',
|
||||
' <dd class="usage"><fieldset><legend />' + writeVariableUsage(variable) + '</fieldset></dd>'
|
||||
].concat([
|
||||
' <dd class="return type">' + sanitize(variable.type) + '</dd>',
|
||||
]).concat([
|
||||
'</dl>'
|
||||
]).map(indenter(indent));
|
||||
};
|
||||
|
||||
var writeCallableName = function (callable) {
|
||||
return (
|
||||
'<a href="#' + sanitize(callable.name.toString()) + '">' +
|
||||
'<a href="#' + sanitize(callable.fullName) + '">' +
|
||||
sanitize(
|
||||
callable.name.toShortString() +
|
||||
callable.name +
|
||||
((callable.generics.length > 0) ? ('.<' + callable.generics.join(', ') + '>') : '')
|
||||
) +
|
||||
'</a>'
|
||||
@@ -589,11 +254,29 @@ module.exports = function () {
|
||||
return writeCallableName(func);
|
||||
};
|
||||
|
||||
var writeInterfaceName = function (interface) {
|
||||
return (
|
||||
'interface ' +
|
||||
writeCallableName(interface) +
|
||||
((interface.baseType !== null) ? (' extends <a href="#' + sanitize(interface.baseType.fullName) + '">' + sanitize(interface.baseType.name) + '</a>') : '')
|
||||
);
|
||||
};
|
||||
|
||||
var writeConstructorName = function (constructor) {
|
||||
return (
|
||||
'class ' +
|
||||
writeCallableName(constructor) +
|
||||
((constructor.baseType !== null) ? (' extends <a href="#' + sanitize(constructor.baseType.name.toString()) + '">' + sanitize(constructor.baseType.name.toShortString()) + '</a>') : '')
|
||||
((constructor.baseType !== null) ? (' extends <a href="#' + sanitize(constructor.baseType.fullName) + '">' + sanitize(constructor.baseType.name) + '</a>') : '')
|
||||
);
|
||||
};
|
||||
|
||||
var writeVariableUsage = function (variable) {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize('var result = ' + toVariableName(variable.parent) + '.' + variable.name + ';' +
|
||||
'\n' +
|
||||
toVariableName(variable.parent) + '.' + variable.name + ' = value;') +
|
||||
'</code></pre>'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -602,8 +285,8 @@ module.exports = function () {
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
((func.returnType !== null) ? 'var result = ' : '') +
|
||||
((func.thisType !== null) ? ((func.isStatic ? func.thisType.name.toShortString() : toVariableName(func.thisType)) + '.') : '') +
|
||||
func.name.toShortString() + '(' +
|
||||
((func.parent !== null) ? ((func.isStatic ? func.parent.name : toVariableName(func.parent)) + '.') : '') +
|
||||
func.name + '(' +
|
||||
func.parameters.map(function (parameter) {
|
||||
return parameter.name;
|
||||
}).join(', ') + ');') +
|
||||
@@ -616,7 +299,7 @@ module.exports = function () {
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
'var ' + toVariableName(constructor) + ' = ' +
|
||||
'new ' + constructor.name.toShortString() + '(' +
|
||||
'new ' + constructor.name + '(' +
|
||||
constructor.parameters.map(function (parameter) {
|
||||
return parameter.name;
|
||||
}).join(', ') + ');') +
|
||||
@@ -678,7 +361,7 @@ module.exports = function () {
|
||||
var getter = property.getter;
|
||||
|
||||
return [
|
||||
'<dl class="getter" id="' + sanitize(property.name.toString()) + '">',
|
||||
'<dl class="getter" id="' + sanitize(property.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(property) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(getter.description) + '</p>',
|
||||
@@ -695,7 +378,7 @@ module.exports = function () {
|
||||
var setter = property.setter;
|
||||
|
||||
return [
|
||||
'<dl class="setter" id="' + sanitize(property.name.toString()) + '">',
|
||||
'<dl class="setter" id="' + sanitize(property.fullName) + '">',
|
||||
' <dt class="name">' + writePropertyName(property) + '</dt>',
|
||||
' <dd class="description">',
|
||||
' <p>' + sanitize(setter.description) + '</p>',
|
||||
@@ -708,8 +391,8 @@ module.exports = function () {
|
||||
|
||||
var writePropertyName = function (property) {
|
||||
return (
|
||||
'<a href="#' + sanitize(property.name.toString()) + '">' +
|
||||
sanitize(property.name.toShortString()) +
|
||||
'<a href="#' + sanitize(property.fullName) + '">' +
|
||||
sanitize(property.name) +
|
||||
'</a>'
|
||||
);
|
||||
};
|
||||
@@ -719,7 +402,7 @@ module.exports = function () {
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
'var result = ' +
|
||||
toVariableName(property.thisType) + '.' + property.name.toShortString() +
|
||||
toVariableName(property.parent) + '.' + property.name +
|
||||
';') +
|
||||
'</code></pre>'
|
||||
);
|
||||
@@ -729,7 +412,7 @@ module.exports = function () {
|
||||
return (
|
||||
'<pre><code>' +
|
||||
sanitize(
|
||||
toVariableName(property.thisType) + '.' + property.name.toShortString() +
|
||||
toVariableName(property.parent) + '.' + property.name +
|
||||
' = ' +
|
||||
property.setter.parameters[0].name +
|
||||
';') +
|
||||
@@ -740,7 +423,7 @@ module.exports = function () {
|
||||
var toVariableName = function (constructor) {
|
||||
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
|
||||
|
||||
var name = constructor.name.toShortString();
|
||||
var name = constructor.name;
|
||||
var result = "";
|
||||
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
@@ -813,16 +496,16 @@ module.exports = function () {
|
||||
' border-bottom: 1px solid black;',
|
||||
' }',
|
||||
'',
|
||||
' .function, .constructor, .getter, .setter {',
|
||||
' .variable, .function, .interface, .constructor, .getter, .setter {',
|
||||
' margin-left: 30px;',
|
||||
' padding: 10px;',
|
||||
' }',
|
||||
'',
|
||||
' section > .function:nth-child(2n), section > .constructor:nth-child(2n) {',
|
||||
' section > .variable:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .constructor:nth-child(2n) {',
|
||||
' background-color: rgb(221, 250, 238);',
|
||||
' }',
|
||||
'',
|
||||
' section > .function:nth-child(2n + 1), section > .constructor:nth-child(2n + 1) {',
|
||||
' section > .variable:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .constructor:nth-child(2n + 1) {',
|
||||
' background-color: rgb(244, 250, 221);',
|
||||
' }',
|
||||
'',
|
||||
@@ -843,7 +526,8 @@ module.exports = function () {
|
||||
' margin: 0;',
|
||||
' }',
|
||||
'',
|
||||
' .constructor .function, .constructor .getter, .constructor .setter {',
|
||||
' .interface .variable, .interface .function, .interface .getter, .interface .setter,',
|
||||
' .constructor .variable, .constructor .function, .constructor .getter, .constructor .setter {',
|
||||
' background-color: rgb(250, 241, 221);',
|
||||
' }',
|
||||
'',
|
||||
@@ -907,12 +591,20 @@ module.exports = function () {
|
||||
' <div class="content">',
|
||||
''
|
||||
]).concatMany(namespaceNames.map(function (namespaceName) {
|
||||
var functions = namespaces[namespaceName].filter(function (value) {
|
||||
return value instanceof Function;
|
||||
var variables = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Variable;
|
||||
});
|
||||
|
||||
var constructors = namespaces[namespaceName].filter(function (value) {
|
||||
return value instanceof Constructor;
|
||||
var functions = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Function;
|
||||
});
|
||||
|
||||
var interfaces = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Interface;
|
||||
});
|
||||
|
||||
var constructors = namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Constructor;
|
||||
});
|
||||
|
||||
var result = [
|
||||
@@ -921,6 +613,18 @@ module.exports = function () {
|
||||
''
|
||||
];
|
||||
|
||||
if (variables.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Variables</h2>'
|
||||
]).concatMany(variables.map(function (value) {
|
||||
return writeVariable(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
if (functions.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
@@ -933,6 +637,18 @@ module.exports = function () {
|
||||
]);
|
||||
}
|
||||
|
||||
if (interfaces.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
' <h2>Interfaces</h2>'
|
||||
]).concatMany(interfaces.map(function (value) {
|
||||
return writeInterface(value, 5);
|
||||
})).concat([
|
||||
' </section>',
|
||||
''
|
||||
]);
|
||||
}
|
||||
|
||||
if (constructors.length > 0) {
|
||||
result = result.concat([
|
||||
' <section>',
|
||||
@@ -960,185 +676,3 @@ module.exports = function () {
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
var Name = function (parts) {
|
||||
this.type = "Name";
|
||||
|
||||
this.parts = parts;
|
||||
};
|
||||
|
||||
Name.prototype.toShortString = function () {
|
||||
return this.parts[this.parts.length - 1];
|
||||
};
|
||||
|
||||
Name.prototype.toString = function () {
|
||||
return this.parts.join(".");
|
||||
};
|
||||
|
||||
Name.prototype.inspect = Name.prototype.toString;
|
||||
|
||||
var Function = function (name, description, generics, parameters, returnType, isAbstract, isPrivate, isStatic) {
|
||||
this.name = name;
|
||||
|
||||
this.description = description;
|
||||
|
||||
this.generics = generics;
|
||||
|
||||
this.parameters = parameters;
|
||||
|
||||
this.returnType = returnType;
|
||||
|
||||
this.isAbstract = isAbstract;
|
||||
this.isPrivate = isPrivate;
|
||||
this.isStatic = isStatic;
|
||||
|
||||
this.thisType = null;
|
||||
};
|
||||
|
||||
var Constructor = function (name, description, generics, parameters, baseType, isAbstract, isPrivate) {
|
||||
this.name = name;
|
||||
|
||||
this.description = description;
|
||||
|
||||
this.generics = generics;
|
||||
|
||||
this.parameters = parameters;
|
||||
|
||||
this.baseType = baseType;
|
||||
|
||||
this.isAbstract = isAbstract;
|
||||
this.isPrivate = isPrivate;
|
||||
|
||||
this.members = [];
|
||||
};
|
||||
|
||||
var Property = function (name) {
|
||||
this.name = name;
|
||||
|
||||
this.getter = null;
|
||||
|
||||
this.setter = null;
|
||||
|
||||
this.thisType = null;
|
||||
};
|
||||
|
||||
var Getter = function (description, type) {
|
||||
this.description = description;
|
||||
|
||||
this.type = type;
|
||||
|
||||
this.thisType = null;
|
||||
};
|
||||
|
||||
var Setter = function (description, parameters) {
|
||||
this.description = description;
|
||||
|
||||
this.parameters = parameters;
|
||||
|
||||
this.thisType = null;
|
||||
};
|
||||
|
||||
var Parameter = function (name, type, description) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
|
||||
this.subParameters = [];
|
||||
};
|
||||
|
||||
var ReturnType = function (type, description) {
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
};
|
||||
|
||||
var getNodeType = function (node) {
|
||||
var nodeType = "";
|
||||
|
||||
if (node instanceof UglifyJS.AST_Node) { nodeType = "AST_Node"; }
|
||||
if (node instanceof UglifyJS.AST_Statement) { nodeType = "AST_Statement"; }
|
||||
if (node instanceof UglifyJS.AST_Debugger) { nodeType = "AST_Debugger"; }
|
||||
if (node instanceof UglifyJS.AST_Directive) { nodeType = "AST_Directive"; }
|
||||
if (node instanceof UglifyJS.AST_SimpleStatement) { nodeType = "AST_SimpleStatement"; }
|
||||
if (node instanceof UglifyJS.AST_Block) { nodeType = "AST_Block"; }
|
||||
if (node instanceof UglifyJS.AST_BlockStatement) { nodeType = "AST_BlockStatement"; }
|
||||
if (node instanceof UglifyJS.AST_Scope) { nodeType = "AST_Scope"; }
|
||||
if (node instanceof UglifyJS.AST_Toplevel) { nodeType = "AST_Toplevel"; }
|
||||
if (node instanceof UglifyJS.AST_Lambda) { nodeType = "AST_Lambda"; }
|
||||
if (node instanceof UglifyJS.AST_Accessor) { nodeType = "AST_Accessor"; }
|
||||
if (node instanceof UglifyJS.AST_Function) { nodeType = "AST_Function"; }
|
||||
if (node instanceof UglifyJS.AST_Defun) { nodeType = "AST_Defun"; }
|
||||
if (node instanceof UglifyJS.AST_Switch) { nodeType = "AST_Switch"; }
|
||||
if (node instanceof UglifyJS.AST_SwitchBranch) { nodeType = "AST_SwitchBranch"; }
|
||||
if (node instanceof UglifyJS.AST_Default) { nodeType = "AST_Default"; }
|
||||
if (node instanceof UglifyJS.AST_Case) { nodeType = "AST_Case"; }
|
||||
if (node instanceof UglifyJS.AST_Try) { nodeType = "AST_Try"; }
|
||||
if (node instanceof UglifyJS.AST_Catch) { nodeType = "AST_Catch"; }
|
||||
if (node instanceof UglifyJS.AST_Finally) { nodeType = "AST_Finally"; }
|
||||
if (node instanceof UglifyJS.AST_EmptyStatement) { nodeType = "AST_EmptyStatement"; }
|
||||
if (node instanceof UglifyJS.AST_StatementWithBody) { nodeType = "AST_StatementWithBody"; }
|
||||
if (node instanceof UglifyJS.AST_LabeledStatement) { nodeType = "AST_LabeledStatement"; }
|
||||
if (node instanceof UglifyJS.AST_DWLoop) { nodeType = "AST_DWLoop"; }
|
||||
if (node instanceof UglifyJS.AST_Do) { nodeType = "AST_Do"; }
|
||||
if (node instanceof UglifyJS.AST_While) { nodeType = "AST_While"; }
|
||||
if (node instanceof UglifyJS.AST_For) { nodeType = "AST_For"; }
|
||||
if (node instanceof UglifyJS.AST_ForIn) { nodeType = "AST_ForIn"; }
|
||||
if (node instanceof UglifyJS.AST_With) { nodeType = "AST_With"; }
|
||||
if (node instanceof UglifyJS.AST_If) { nodeType = "AST_If"; }
|
||||
if (node instanceof UglifyJS.AST_Jump) { nodeType = "AST_Jump"; }
|
||||
if (node instanceof UglifyJS.AST_Exit) { nodeType = "AST_Exit"; }
|
||||
if (node instanceof UglifyJS.AST_Return) { nodeType = "AST_Return"; }
|
||||
if (node instanceof UglifyJS.AST_Throw) { nodeType = "AST_Throw"; }
|
||||
if (node instanceof UglifyJS.AST_LoopControl) { nodeType = "AST_LoopControl"; }
|
||||
if (node instanceof UglifyJS.AST_Break) { nodeType = "AST_Break"; }
|
||||
if (node instanceof UglifyJS.AST_Continue) { nodeType = "AST_Continue"; }
|
||||
if (node instanceof UglifyJS.AST_Definitions) { nodeType = "AST_Definitions"; }
|
||||
if (node instanceof UglifyJS.AST_Var) { nodeType = "AST_Var"; }
|
||||
if (node instanceof UglifyJS.AST_Const) { nodeType = "AST_Const"; }
|
||||
if (node instanceof UglifyJS.AST_VarDef) { nodeType = "AST_VarDef"; }
|
||||
if (node instanceof UglifyJS.AST_Call) { nodeType = "AST_Call"; }
|
||||
if (node instanceof UglifyJS.AST_New) { nodeType = "AST_New"; }
|
||||
if (node instanceof UglifyJS.AST_Seq) { nodeType = "AST_Seq"; }
|
||||
if (node instanceof UglifyJS.AST_PropAccess) { nodeType = "AST_PropAccess"; }
|
||||
if (node instanceof UglifyJS.AST_Dot) { nodeType = "AST_Dot"; }
|
||||
if (node instanceof UglifyJS.AST_Sub) { nodeType = "AST_Sub"; }
|
||||
if (node instanceof UglifyJS.AST_Unary) { nodeType = "AST_Unary"; }
|
||||
if (node instanceof UglifyJS.AST_UnaryPrefix) { nodeType = "AST_UnaryPrefix"; }
|
||||
if (node instanceof UglifyJS.AST_UnaryPostfix) { nodeType = "AST_UnaryPostfix"; }
|
||||
if (node instanceof UglifyJS.AST_Binary) { nodeType = "AST_Binary"; }
|
||||
if (node instanceof UglifyJS.AST_Assign) { nodeType = "AST_Assign"; }
|
||||
if (node instanceof UglifyJS.AST_Conditional) { nodeType = "AST_Conditional"; }
|
||||
if (node instanceof UglifyJS.AST_Array) { nodeType = "AST_Array"; }
|
||||
if (node instanceof UglifyJS.AST_Object) { nodeType = "AST_Object"; }
|
||||
if (node instanceof UglifyJS.AST_ObjectProperty) { nodeType = "AST_ObjectProperty"; }
|
||||
if (node instanceof UglifyJS.AST_ObjectKeyVal) { nodeType = "AST_ObjectKeyVal"; }
|
||||
if (node instanceof UglifyJS.AST_ObjectSetter) { nodeType = "AST_ObjectSetter"; }
|
||||
if (node instanceof UglifyJS.AST_ObjectGetter) { nodeType = "AST_ObjectGetter"; }
|
||||
if (node instanceof UglifyJS.AST_Symbol) { nodeType = "AST_Symbol"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolAccessor) { nodeType = "AST_SymbolAccessor"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolDeclaration) { nodeType = "AST_SymbolDeclaration"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolVar) { nodeType = "AST_SymbolVar"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) { nodeType = "AST_SymbolFunarg"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolConst) { nodeType = "AST_SymbolConst"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolDefun) { nodeType = "AST_SymbolDefun"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolLambda) { nodeType = "AST_SymbolLambda"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolCatch) { nodeType = "AST_SymbolCatch"; }
|
||||
if (node instanceof UglifyJS.AST_Label) { nodeType = "AST_Label"; }
|
||||
if (node instanceof UglifyJS.AST_SymbolRef) { nodeType = "AST_SymbolRef"; }
|
||||
if (node instanceof UglifyJS.AST_LabelRef) { nodeType = "AST_LabelRef"; }
|
||||
if (node instanceof UglifyJS.AST_This) { nodeType = "AST_This"; }
|
||||
if (node instanceof UglifyJS.AST_Constant) { nodeType = "AST_Constant"; }
|
||||
if (node instanceof UglifyJS.AST_String) { nodeType = "AST_String"; }
|
||||
if (node instanceof UglifyJS.AST_Number) { nodeType = "AST_Number"; }
|
||||
if (node instanceof UglifyJS.AST_RegExp) { nodeType = "AST_RegExp"; }
|
||||
if (node instanceof UglifyJS.AST_Atom) { nodeType = "AST_Atom"; }
|
||||
if (node instanceof UglifyJS.AST_Null) { nodeType = "AST_Null"; }
|
||||
if (node instanceof UglifyJS.AST_NaN) { nodeType = "AST_NaN"; }
|
||||
if (node instanceof UglifyJS.AST_Undefined) { nodeType = "AST_Undefined"; }
|
||||
if (node instanceof UglifyJS.AST_Hole) { nodeType = "AST_Hole"; }
|
||||
if (node instanceof UglifyJS.AST_Infinity) { nodeType = "AST_Infinity"; }
|
||||
if (node instanceof UglifyJS.AST_Boolean) { nodeType = "AST_Boolean"; }
|
||||
if (node instanceof UglifyJS.AST_False) { nodeType = "AST_False"; }
|
||||
if (node instanceof UglifyJS.AST_True) { nodeType = "AST_True"; }
|
||||
|
||||
return nodeType;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var childProcess = require("child_process");
|
||||
var stream = require("stream");
|
||||
var util = require("util");
|
||||
|
||||
@@ -57,3 +58,31 @@ var Transform = function (transform, flush) {
|
||||
util.inherits(Transform, stream.Transform);
|
||||
|
||||
exports.Transform = Transform;
|
||||
|
||||
exports.SingletonChildProcess = function (filename) {
|
||||
var subProcess = null;
|
||||
var rerun = false;
|
||||
|
||||
var spawnSubProcess = function () {
|
||||
subProcess = childProcess.fork(filename);
|
||||
|
||||
subProcess.addListener("exit", function (code, signal) {
|
||||
subProcess = null;
|
||||
|
||||
if (rerun) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
|
||||
rerun = false;
|
||||
});
|
||||
};
|
||||
|
||||
return function () {
|
||||
if (subProcess === null) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
else {
|
||||
rerun = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var Mocha = require("mocha");
|
||||
|
||||
console.log("Running tests...");
|
||||
|
||||
var mocha = new Mocha({
|
||||
ui: "tdd",
|
||||
reporter: "spec"
|
||||
});
|
||||
|
||||
fs.readdirSync("./tests/")
|
||||
.filter(function (filename) { return filename.match(/^test-.*\.js$/) !== null; })
|
||||
.forEach(function (filename) { mocha.addFile(path.resolve("./tests", filename)); });
|
||||
|
||||
mocha.run(function (failures) {
|
||||
if (failures > 0) {
|
||||
throw new Error(failures + " test(s) failed.");
|
||||
}
|
||||
});
|
||||
+904
-54
File diff suppressed because it is too large
Load Diff
+243
-234
@@ -39,149 +39,153 @@ module.exports = {
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
}, function () {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: null
|
||||
});
|
||||
// Parse
|
||||
var root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: null
|
||||
});
|
||||
|
||||
root = root.wrap_enclose(['((typeof module !== "undefined") && module.exports) || (this.libjass = {}):libjass', '((typeof global !== "undefined") && global) || this:global']);
|
||||
root = root.wrap_enclose(['(typeof module !== "undefined") ? module.exports : (this.libjass = {}):libjass', '(typeof global !== "undefined") ? global : this:global']);
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove;
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove;
|
||||
|
||||
nodesToRemove = [];
|
||||
nodesToRemove = [];
|
||||
|
||||
// 1. All the top-level "var libjass;"
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().body });
|
||||
}
|
||||
}));
|
||||
|
||||
// 2. All but the first top-level "var __extends = ..."
|
||||
var firstVarExtendsFound = false;
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "__extends") {
|
||||
if (firstVarExtendsFound === false) {
|
||||
firstVarExtendsFound = true;
|
||||
// Remove the check for this.__extends
|
||||
node.definitions[0].value = node.definitions[0].value.right;
|
||||
}
|
||||
else {
|
||||
// 1. All the top-level "var libjass;"
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
// Repeat because removing some declarations may make others unreferenced
|
||||
for (;;) {
|
||||
// 3. Unreferenced variable and function declarations, and unreferenced terminal function arguments
|
||||
// 2. All but the first top-level "var __extends = ..."
|
||||
var firstVarExtendsFound = false;
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) {
|
||||
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().argnames });
|
||||
}
|
||||
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "__extends") {
|
||||
if (firstVarExtendsFound === false) {
|
||||
firstVarExtendsFound = true;
|
||||
// Remove the check for this.__extends
|
||||
node.definitions[0].value = node.definitions[0].value.right;
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolVar) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
|
||||
if (this.parent(1).definitions.length === 1) {
|
||||
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolDefun) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
|
||||
else {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (nodesToRemove.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach(function (tuple) {
|
||||
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
}
|
||||
|
||||
// 4. Rename all function arguments that begin with _ to not have the _.
|
||||
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
|
||||
// so that it matches the JSDoc (and looks nicer).
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_SymbolFunarg &&
|
||||
node.thedef.name[0] === "_" &&
|
||||
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
|
||||
) {
|
||||
node.thedef.name = node.thedef.name.slice(1);
|
||||
}
|
||||
}));
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString(),
|
||||
root: ""
|
||||
}),
|
||||
beautify: true,
|
||||
comments: function (node, comment) {
|
||||
// If this is a license header
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
if (!firstLicenseHeaderFound) {
|
||||
firstLicenseHeaderFound = true;
|
||||
// Repeat because removing some declarations may make others unreferenced
|
||||
for (;;) {
|
||||
// 3. Unreferenced variable and function declarations, and unreferenced terminal function arguments
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) {
|
||||
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().argnames });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolVar) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
|
||||
if (this.parent(1).definitions.length === 1) {
|
||||
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolDefun) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This isn't the first license header. Strip it.
|
||||
}));
|
||||
|
||||
if (nodesToRemove.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach(function (tuple) {
|
||||
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
}
|
||||
|
||||
// 4. Rename all function arguments that begin with _ to not have the _.
|
||||
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
|
||||
// so that it matches the JSDoc (and looks nicer).
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_SymbolFunarg &&
|
||||
node.thedef.name[0] === "_" &&
|
||||
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
|
||||
) {
|
||||
node.thedef.name = node.thedef.name.slice(1);
|
||||
}
|
||||
}));
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
beautify: true,
|
||||
comments: function (node, comment) {
|
||||
// If this is a license header
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
if (!firstLicenseHeaderFound) {
|
||||
firstLicenseHeaderFound = true;
|
||||
}
|
||||
else {
|
||||
// This isn't the first license header. Strip it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a TypeScript reference comment, strip it.
|
||||
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Align multi-line comments correctly.
|
||||
* TypeScript shifts them one space left, and UJS shifts them four spaces left,
|
||||
* so shift each line except the first five spaces right.
|
||||
*/
|
||||
if (comment.type === "comment2") {
|
||||
var lines = comment.value.split("\n");
|
||||
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
|
||||
comment.value = lines.join('\n');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// If this is a TypeScript reference comment, strip it.
|
||||
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
|
||||
return false;
|
||||
}
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
/* Align multi-line comments correctly.
|
||||
* TypeScript shifts them one space left, and UJS shifts them four spaces left,
|
||||
* so shift each line except the first five spaces right.
|
||||
*/
|
||||
if (comment.type === "comment2") {
|
||||
var lines = comment.value.split("\n");
|
||||
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
|
||||
comment.value = lines.join('\n');
|
||||
}
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=libjass.js.map")]);
|
||||
this.push(codeFile);
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -198,141 +202,146 @@ module.exports = {
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
}, function () {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = null;
|
||||
root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: root
|
||||
});
|
||||
// Parse
|
||||
var root = null;
|
||||
root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: root
|
||||
});
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Suppress some warnings
|
||||
var originalWarn = UglifyJS.AST_Node.warn;
|
||||
UglifyJS.AST_Node.warn = function (text, properties) {
|
||||
if (
|
||||
(text === "{type} {name} is declared but not referenced [{file}:{line},{col}]" && properties.type === "Symbol" && properties.name === "substring" && properties.file === "libjass.js" && properties.line === 5917)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
originalWarn.call(UglifyJS.AST_Node, text, properties);
|
||||
};
|
||||
|
||||
// Warnings
|
||||
root.scope_warnings({
|
||||
func_arguments: false
|
||||
});
|
||||
// Warnings
|
||||
root.scope_warnings({
|
||||
func_arguments: false
|
||||
});
|
||||
|
||||
|
||||
// Compress
|
||||
var compressor = UglifyJS.Compressor({
|
||||
warnings: true,
|
||||
screw_ie8: true
|
||||
});
|
||||
root = root.transform(compressor);
|
||||
// Compress
|
||||
var compressor = UglifyJS.Compressor({
|
||||
warnings: true,
|
||||
screw_ie8: true
|
||||
});
|
||||
root = root.transform(compressor);
|
||||
|
||||
|
||||
// Mangle
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.compute_char_frequency();
|
||||
root.mangle_names({ screw_ie8: true });
|
||||
// Mangle
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.compute_char_frequency();
|
||||
root.mangle_names({ screw_ie8: true });
|
||||
|
||||
|
||||
// Mangle private members
|
||||
var occurrences = Object.create(null);
|
||||
// Mangle private members
|
||||
var occurrences = Object.create(null);
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property[0] === "_" && node.property[1] !== "_"
|
||||
) {
|
||||
var occurrence = occurrences[node.property];
|
||||
if (occurrence === undefined) {
|
||||
occurrences[node.property] = 1;
|
||||
}
|
||||
else {
|
||||
occurrences[node.property]++;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
var identifiers = Object.keys(occurrences);
|
||||
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
|
||||
|
||||
var generatedIdentifiers = occurrences;
|
||||
|
||||
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
|
||||
var toIdentifier = function (index) {
|
||||
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
|
||||
while (index > 0) {
|
||||
index--;
|
||||
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
}
|
||||
|
||||
return "_" + result;
|
||||
};
|
||||
|
||||
identifiers.forEach(function (identifier, index) {
|
||||
generatedIdentifiers[identifier] = toIdentifier(index);
|
||||
});
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property in generatedIdentifiers
|
||||
) {
|
||||
node.property = generatedIdentifiers[node.property];
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString(),
|
||||
root: ""
|
||||
}),
|
||||
comments: {
|
||||
test: function (comment) {
|
||||
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
return true;
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property[0] === "_" &&
|
||||
node.property[1] !== "_" && // Doesn't start with two leading underscores
|
||||
node.property !== "_classTag" // webworker serializer uses this property by name, so it shouldn't be changed.
|
||||
) {
|
||||
var occurrence = occurrences[node.property];
|
||||
if (occurrence === undefined) {
|
||||
occurrences[node.property] = 1;
|
||||
}
|
||||
else {
|
||||
occurrences[node.property]++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
screw_ie8: true
|
||||
};
|
||||
}));
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
var identifiers = Object.keys(occurrences);
|
||||
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
|
||||
|
||||
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=libjass.js.map")]);
|
||||
this.push(codeFile);
|
||||
var generatedIdentifiers = occurrences;
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
|
||||
var toIdentifier = function (index) {
|
||||
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
while (index > 0) {
|
||||
index--;
|
||||
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
}
|
||||
|
||||
return "_" + result;
|
||||
};
|
||||
|
||||
identifiers.forEach(function (identifier, index) {
|
||||
generatedIdentifiers[identifier] = toIdentifier(index);
|
||||
});
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property in generatedIdentifiers
|
||||
) {
|
||||
node.property = generatedIdentifiers[node.property];
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
comments: {
|
||||
test: function (comment) {
|
||||
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
screw_ie8: true
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
|
||||
|
||||
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
|
||||
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalSymbolUnreferenced.call(this);
|
||||
};
|
||||
|
||||
@@ -36,102 +36,129 @@ addEventListener("DOMContentLoaded", function () {
|
||||
var ass = null;
|
||||
|
||||
var testVideoAndASSLoaded = function () {
|
||||
if (videoMetadataLoaded && ass) {
|
||||
var renderer = new libjass.renderers.DefaultRenderer(video, ass, {
|
||||
fontMap: libjass.renderers.RendererSettings.makeFontMapFromStyleElement(document.querySelector("#font-map"))
|
||||
});
|
||||
if (videoMetadataLoaded === false || ass === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (libjass.debugMode) {
|
||||
window.renderer = renderer;
|
||||
// Both video metadata and ASS are loaded
|
||||
|
||||
// Create a DefaultRenderer using the video element, the ASS object, and the font map
|
||||
var renderer = new libjass.renderers.DefaultRenderer(video, ass, {
|
||||
fontMap: libjass.renderers.RendererSettings.makeFontMapFromStyleElement(document.querySelector("#font-map"))
|
||||
});
|
||||
|
||||
if (libjass.debugMode) {
|
||||
// Export the renderer for debugging
|
||||
window.renderer = renderer;
|
||||
}
|
||||
|
||||
// Autoplay the video when the renderer is ready
|
||||
renderer.addEventListener("ready", function () {
|
||||
debug("All fonts have been preloaded. Beginning autoplay.");
|
||||
|
||||
video.play();
|
||||
});
|
||||
|
||||
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
|
||||
renderer.setEnabled(event.target.checked);
|
||||
}, false);
|
||||
|
||||
// Run when the video/script resolution needs to be changed, either because the user clicked one of the resolution options,
|
||||
// or because they exited full screen and their selection needs to be restored
|
||||
var changeVideoSizeSelection = function (id) {
|
||||
if (typeof id === "undefined") {
|
||||
// Find which option is selected
|
||||
|
||||
[].slice.call(videoSizeSelector.querySelectorAll("input[name='video-size']")).some(function (option) {
|
||||
if (option.checked) {
|
||||
id = option.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
renderer.addEventListener("ready", function () {
|
||||
debug("All fonts have been preloaded. Beginning autoplay.");
|
||||
video.play();
|
||||
if (id === "video-size-video-radio") {
|
||||
// Resize to video resolution
|
||||
|
||||
subToggleSpanTextNode.textContent = renderer.enabled ? "off" : "on";
|
||||
});
|
||||
video.style.width = video.videoWidth + "px";
|
||||
video.style.height = video.videoHeight + "px";
|
||||
renderer.resize(video.videoWidth, video.videoHeight);
|
||||
}
|
||||
|
||||
var subToggleSpanTextNode = document.createTextNode("");
|
||||
document.querySelector("#enable-disable-subs-span").appendChild(subToggleSpanTextNode);
|
||||
else if (id === "video-size-script-radio") {
|
||||
// Resize to script resolution
|
||||
|
||||
document.querySelector("#enable-disable-button").addEventListener("click", function (event) {
|
||||
renderer.toggle();
|
||||
subToggleSpanTextNode.textContent = renderer.enabled ? "off" : "on";
|
||||
}, false);
|
||||
|
||||
var changeVideoSizeSelection = function (id) {
|
||||
if (typeof id === "undefined") {
|
||||
[].some.call(videoSizeSelector.querySelectorAll("input[name='video-size']"), function (option) {
|
||||
if (option.checked) {
|
||||
id = option.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (id === "video-size-video-radio") {
|
||||
video.style.width = video.videoWidth + "px";
|
||||
video.style.height = video.videoHeight + "px";
|
||||
renderer.resize(video.videoWidth, video.videoHeight);
|
||||
}
|
||||
|
||||
else if (id === "video-size-script-radio") {
|
||||
video.style.width = ass.properties.resolutionX + "px";
|
||||
video.style.height = ass.properties.resolutionY + "px";
|
||||
renderer.resize(ass.properties.resolutionX, ass.properties.resolutionY);
|
||||
}
|
||||
};
|
||||
|
||||
var videoSizeSelector = document.querySelector("#video-size-selector");
|
||||
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
|
||||
|
||||
renderer.addEventListener("fullScreenChange", function (newState) {
|
||||
if (newState === false) {
|
||||
changeVideoSizeSelection();
|
||||
}
|
||||
});
|
||||
|
||||
changeVideoSizeSelection();
|
||||
video.style.width = ass.properties.resolutionX + "px";
|
||||
video.style.height = ass.properties.resolutionY + "px";
|
||||
renderer.resize(ass.properties.resolutionX, ass.properties.resolutionY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
|
||||
video.addEventListener("loadedmetadata", function () {
|
||||
debug("Video metadata loaded.");
|
||||
videoMetadataLoaded = true;
|
||||
var videoSizeSelector = document.querySelector("#video-size-selector");
|
||||
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
|
||||
|
||||
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
|
||||
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
|
||||
// When video exits full screen, restore the size according to the resolution option that's selected
|
||||
renderer.addEventListener("fullScreenChange", function (newState) {
|
||||
if (newState === false) {
|
||||
changeVideoSizeSelection();
|
||||
}
|
||||
});
|
||||
|
||||
testVideoAndASSLoaded();
|
||||
}, false);
|
||||
}
|
||||
else {
|
||||
// Set the resolution to whatever's selected by default
|
||||
changeVideoSizeSelection();
|
||||
};
|
||||
|
||||
(function (onVideoMetadataLoaded) {
|
||||
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
|
||||
// Video metadata isn't available yet. Register an event handler for it.
|
||||
video.addEventListener("loadedmetadata", onVideoMetadataLoaded, false);
|
||||
}
|
||||
else {
|
||||
// Video metadata is already available.
|
||||
onVideoMetadataLoaded();
|
||||
}
|
||||
})(function () {
|
||||
debug("Video metadata loaded.");
|
||||
videoMetadataLoaded = true;
|
||||
testVideoAndASSLoaded();
|
||||
}
|
||||
|
||||
videoMetadataLoaded = true;
|
||||
|
||||
// Prepare the "Video resolution" option label
|
||||
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
|
||||
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
|
||||
|
||||
// Test if everything is loaded
|
||||
testVideoAndASSLoaded();
|
||||
});
|
||||
|
||||
// Find the ASS or SRT track and load it
|
||||
var track = document.querySelector("#video > track[data-format='ass'], #video > track[data-format='srt']");
|
||||
var subsRequest = new XMLHttpRequest();
|
||||
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
|
||||
subsRequest.addEventListener("readystatechange", function () {
|
||||
if (subsRequest.readyState === XMLHttpRequest.DONE) {
|
||||
debug("Script received.");
|
||||
subsRequest.addEventListener("load", function () {
|
||||
debug("Script received.");
|
||||
|
||||
ass = libjass.ASS.fromString(subsRequest.responseText, libjass.Format[track.getAttribute("data-format").toUpperCase()]);
|
||||
if (libjass.debugMode) {
|
||||
window.ass = ass;
|
||||
}
|
||||
|
||||
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
|
||||
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
|
||||
|
||||
testVideoAndASSLoaded();
|
||||
if (libjass.debugMode) {
|
||||
console.time("Parsing ASS took");
|
||||
}
|
||||
|
||||
// Parse the response string into an ASS object
|
||||
ass = libjass.ASS.fromString(subsRequest.responseText, libjass.Format[track.getAttribute("data-format").toUpperCase()]);
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.timeEnd("Parsing ASS took");
|
||||
|
||||
// Export the ASS object for debugging
|
||||
window.ass = ass;
|
||||
}
|
||||
|
||||
// Prepare the "Script resolution" option label
|
||||
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
|
||||
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
|
||||
|
||||
// Test if everything is loaded
|
||||
testVideoAndASSLoaded();
|
||||
}, false);
|
||||
subsRequest.send(null);
|
||||
}, false);
|
||||
|
||||
+4
-3
@@ -42,9 +42,6 @@
|
||||
<track src="/video/gc-01.ass" kind="metadata" data-format="ass" />
|
||||
</video>
|
||||
<form class="settings-form">
|
||||
<div>
|
||||
<button type="button" id="enable-disable-button">Turn subs <span id="enable-disable-subs-span" /></button>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>Video size</legend>
|
||||
<div id="video-size-selector">
|
||||
@@ -52,6 +49,10 @@
|
||||
<input type="radio" name="video-size" id="video-size-script-radio" /><label for="video-size-script-radio">Script resolution <span id="script-resolution-label-width" />x<span id="script-resolution-label-height" /></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Subtitles</legend>
|
||||
<input type="checkbox" id="enable-disable-subs" checked="checked" /><label for="enable-disable-subs">Subtitles</label>
|
||||
</fieldset>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-2
@@ -64,12 +64,24 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.libjass-subs div {
|
||||
.libjass-subs .an1, .libjass-subs .an4, .libjass-subs .an7 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.libjass-subs .an2, .libjass-subs .an5, .libjass-subs .an8 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.libjass-subs .an3, .libjass-subs .an6, .libjass-subs .an9 {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.libjass-subs {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* Filter wrapper span */
|
||||
.libjass-subs div[data-dialogue-id] > span {
|
||||
.libjass-subs div[data-dialogue-id] {
|
||||
-webkit-perspective-origin: center;
|
||||
-webkit-perspective: 200px;
|
||||
perspective-origin: center;
|
||||
|
||||
+5
-1
@@ -19,7 +19,11 @@
|
||||
*/
|
||||
|
||||
///<reference path="utility.ts" />
|
||||
///<reference path="web-worker.ts" />
|
||||
///<reference path="parts.ts" />
|
||||
///<reference path="parser.ts" />
|
||||
///<reference path="renderer.ts" />
|
||||
///<reference path="renderers-clocks.ts" />
|
||||
///<reference path="renderers-null.ts" />
|
||||
///<reference path="renderers-web.ts" />
|
||||
///<reference path="renderers-default.ts" />
|
||||
///<reference path="types.ts" />
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libjass",
|
||||
"version": "0.6.0",
|
||||
"version": "0.9.0",
|
||||
"description": "A library to render ASS subtitles on HTML5 video in the browser.",
|
||||
"keywords": ["browser", "html5", "subtitles"],
|
||||
"homepage": "https://github.com/Arnavion/libjass",
|
||||
@@ -8,7 +8,7 @@
|
||||
"url": "https://github.com/Arnavion/libjass/issues"
|
||||
},
|
||||
"licenses": [{
|
||||
"type": "Apache 2.0",
|
||||
"type": "Apache-2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
}],
|
||||
"maintainers": [{
|
||||
@@ -20,14 +20,14 @@
|
||||
"url": "https://github.com/Arnavion/libjass"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "gulp clean && gulp --trace",
|
||||
"test": "gulp test"
|
||||
"postinstall": "gulp clean default",
|
||||
"test": "node ./gulplib/test-runner.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"gulp": "latest",
|
||||
"mocha": "1.18.x",
|
||||
"typescript": "0.9.7",
|
||||
"uglify-js": "2.x >=2.4.13",
|
||||
"mocha": "1.21.x",
|
||||
"typescript": "1.0.1",
|
||||
"uglify-js": "2.x >=2.4.14",
|
||||
"vinyl": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,13 @@ module libjass.parser {
|
||||
/**
|
||||
* Parses a given string with the specified rule.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} rule
|
||||
* @return {*}
|
||||
*
|
||||
* @memberof libjass.parser
|
||||
* @param {string} input The string to be parsed.
|
||||
* @param {string} rule The rule to parse the string with
|
||||
* @param {libjass.parser.ParseNode=null} customParseTree If provided, this is used as the root of the parse tree instead of a new object.
|
||||
* @return {*} The value returned depends on the rule used.
|
||||
*/
|
||||
export function parse(input: string, rule: string): any {
|
||||
var run = new ParserRun(input, rule);
|
||||
export function parse(input: string, rule: string, customParseTree: ParseNode = null): any {
|
||||
var run = new ParserRun(input, rule, customParseTree);
|
||||
|
||||
if (run.result === null || run.result.end !== input.length) {
|
||||
if (libjass.debugMode) {
|
||||
@@ -47,19 +46,21 @@ module libjass.parser {
|
||||
/**
|
||||
* This class represents a single run of the parser.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} rule
|
||||
*
|
||||
* @private
|
||||
* @memberof libjass.parser
|
||||
*/
|
||||
class ParserRun {
|
||||
private _parseTree: ParseNode = new ParseNode(null);
|
||||
private _parseTree: ParseNode;
|
||||
private _result: ParseNode;
|
||||
|
||||
constructor(private _input: string, rule: string) {
|
||||
constructor(private _input: string, rule: string, customParseTree: ParseNode) {
|
||||
if (customParseTree === null) {
|
||||
this._parseTree = new ParseNode(null);
|
||||
}
|
||||
else {
|
||||
this._parseTree = customParseTree;
|
||||
}
|
||||
|
||||
this._result = rules.get(rule).call(this, this._parseTree);
|
||||
}
|
||||
|
||||
@@ -77,13 +78,14 @@ module libjass.parser {
|
||||
parse_assScript(parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
|
||||
current.value = Object.create(null);
|
||||
var sections = new Map<string, any>();
|
||||
current.value = sections;
|
||||
|
||||
while (this._haveMore()) {
|
||||
var scriptSectionNode = this.parse_assScriptSection(current);
|
||||
|
||||
if (scriptSectionNode !== null) {
|
||||
current.value[scriptSectionNode.value.name] = scriptSectionNode.value.contents;
|
||||
sections.set(scriptSectionNode.value.name, scriptSectionNode.value.contents);
|
||||
}
|
||||
else if (this.read(current, "\n") === null) {
|
||||
parent.pop();
|
||||
@@ -101,20 +103,25 @@ module libjass.parser {
|
||||
parse_assScriptSection(parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
|
||||
current.value = Object.create(null);
|
||||
current.value.contents = null;
|
||||
|
||||
var sectionHeaderNode = this.parse_assScriptSectionHeader(current);
|
||||
if (sectionHeaderNode === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
current.value = Object.create(null);
|
||||
current.value.name = sectionHeaderNode.value;
|
||||
current.value.contents = null;
|
||||
|
||||
var formatSpecifier: string[] = null;
|
||||
var sectionTemplate: Map<string, string> = null;
|
||||
|
||||
var sectionTemplateArray: TypedTemplateArray = null;
|
||||
|
||||
while (this._haveMore() && this._peek() !== "[") {
|
||||
if (this.read(current, "\n") !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.parse_assScriptComment(current) !== null) {
|
||||
continue;
|
||||
}
|
||||
@@ -122,44 +129,46 @@ module libjass.parser {
|
||||
var propertyNode = this.parse_assScriptProperty(current);
|
||||
|
||||
if (propertyNode !== null) {
|
||||
var property = propertyNode.value;
|
||||
if (sectionTemplateArray === null) {
|
||||
var property: { key: string; value: string } = propertyNode.value;
|
||||
|
||||
if (property.key === "Format") {
|
||||
formatSpecifier = property.value.split(",").map((formatPart: string) => formatPart.trim());
|
||||
}
|
||||
if (sectionTemplateArray === null && property.key === "Format") {
|
||||
// The first line of this section is a format specifier. This section will be an array of templates.
|
||||
sectionTemplateArray = <TypedTemplateArray>[];
|
||||
sectionTemplateArray.formatSpecifier = property.value.split(",").map(formatPart => formatPart.trim());
|
||||
|
||||
else if (formatSpecifier !== null) {
|
||||
if (current.value.contents === null) {
|
||||
current.value.contents = <any[]>[];
|
||||
current.value.contents = sectionTemplateArray;
|
||||
}
|
||||
|
||||
var template = Object.create(null);
|
||||
var value = property.value.split(",");
|
||||
else {
|
||||
// This section is a single template. Each line is a property of this template.
|
||||
if (sectionTemplate === null) {
|
||||
sectionTemplate = new Map<string, string>();
|
||||
current.value.contents = sectionTemplate;
|
||||
}
|
||||
|
||||
if (value.length > formatSpecifier.length) {
|
||||
value[formatSpecifier.length - 1] = value.slice(formatSpecifier.length - 1).join(",");
|
||||
sectionTemplate.set(property.key, property.value);
|
||||
}
|
||||
|
||||
formatSpecifier.forEach((formatKey, index) => {
|
||||
template[formatKey] = value[index];
|
||||
});
|
||||
|
||||
current.value.contents.push({ type: property.key, template: template });
|
||||
}
|
||||
|
||||
else {
|
||||
if (current.value.contents === null) {
|
||||
current.value.contents = Object.create(null);
|
||||
}
|
||||
// Each line in this section is a template of a particular type, and the section is an array of these templates.
|
||||
|
||||
current.value.contents[property.key] = property.value;
|
||||
sectionTemplateArray.push(<TypedTemplate>propertyNode.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Empty line
|
||||
else if (this.read(current, "\n") === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
else {
|
||||
// Not a property line. Ignore.
|
||||
|
||||
if (this._haveMore() && this.read(current, "\n") === null) {
|
||||
var garbageNode = new ParseNode(current, "");
|
||||
for (var next = this._peek(); this._haveMore() && next !== "\n"; next = this._peek()) {
|
||||
garbageNode.value += next;
|
||||
}
|
||||
|
||||
this.read(current, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +205,8 @@ module libjass.parser {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.read(current, "\n");
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
@@ -206,8 +217,6 @@ module libjass.parser {
|
||||
parse_assScriptProperty(parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
|
||||
current.value = Object.create(null);
|
||||
|
||||
var keyNode = new ParseNode(current, "");
|
||||
|
||||
var next: string;
|
||||
@@ -238,8 +247,36 @@ module libjass.parser {
|
||||
valueNode.value += next;
|
||||
}
|
||||
|
||||
current.value.key = keyNode.value;
|
||||
current.value.value = valueNode.value;
|
||||
this.read(current, "\n");
|
||||
|
||||
var property: { key: string; value: string } = Object.create(null);
|
||||
property.key = keyNode.value;
|
||||
property.value = valueNode.value;
|
||||
|
||||
current.value = property;
|
||||
|
||||
if (parent.value !== null) {
|
||||
var scriptSectionContents = parent.value.contents;
|
||||
if (scriptSectionContents !== undefined && scriptSectionContents !== null) {
|
||||
var formatSpecifier = (<TypedTemplateArray>scriptSectionContents).formatSpecifier;
|
||||
if (formatSpecifier !== undefined) {
|
||||
// This line is a template of a particular type, and its parent section is an array of these templates.
|
||||
|
||||
var template = new Map<string, string>();
|
||||
var value = property.value.split(",");
|
||||
|
||||
if (value.length > formatSpecifier.length) {
|
||||
value[formatSpecifier.length - 1] = value.slice(formatSpecifier.length - 1).join(",");
|
||||
}
|
||||
|
||||
formatSpecifier.forEach((formatKey, index) => {
|
||||
template.set(formatKey, value[index]);
|
||||
});
|
||||
|
||||
current.value = <TypedTemplate>{ type: property.key, template: template };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
@@ -261,6 +298,8 @@ module libjass.parser {
|
||||
valueNode.value += next;
|
||||
}
|
||||
|
||||
this.read(current, "\n");
|
||||
|
||||
current.value = valueNode.value;
|
||||
|
||||
return current;
|
||||
@@ -283,30 +322,20 @@ module libjass.parser {
|
||||
}
|
||||
|
||||
else {
|
||||
var newLineNode = this.parse_newline(current);
|
||||
var whiteSpaceOrTextNode = this.parse_newline(current) || this.parse_hardspace(current) || this.parse_text(current);
|
||||
|
||||
if (newLineNode !== null) {
|
||||
current.value.push(newLineNode.value);
|
||||
if (whiteSpaceOrTextNode === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (whiteSpaceOrTextNode.value instanceof parts.Text && current.value[current.value.length - 1] instanceof parts.Text) {
|
||||
// Merge consecutive text parts into one part
|
||||
var previousTextPart = <parts.Text>current.value[current.value.length - 1];
|
||||
current.value[current.value.length - 1] = new parts.Text(previousTextPart.value + (<parts.Text>whiteSpaceOrTextNode.value).value);
|
||||
}
|
||||
else {
|
||||
var textNode = this.parse_newline(current) || this.parse_hardspace(current) || this.parse_text(current);
|
||||
|
||||
if (textNode !== null) {
|
||||
if (current.value[current.value.length - 1] instanceof parts.Text) {
|
||||
// Merge consecutive text parts into one part
|
||||
var previousTextPart = <parts.Text>current.value[current.value.length - 1];
|
||||
current.value[current.value.length - 1] = new parts.Text(previousTextPart.value + (<parts.Text>textNode.value).value);
|
||||
}
|
||||
else {
|
||||
current.value.push(textNode.value);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
current.value.push(whiteSpaceOrTextNode.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,6 +397,8 @@ module libjass.parser {
|
||||
this.parse_tag_fry(current) ||
|
||||
this.parse_tag_frz(current) ||
|
||||
this.parse_tag_fsp(current) ||
|
||||
this.parse_tag_fsplus(current) ||
|
||||
this.parse_tag_fsminus(current) ||
|
||||
this.parse_tag_org(current) ||
|
||||
this.parse_tag_pbo(current) ||
|
||||
this.parse_tag_pos(current) ||
|
||||
@@ -930,6 +961,54 @@ module libjass.parser {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ParseNode} parent
|
||||
* @return {ParseNode}
|
||||
*/
|
||||
parse_tag_fsplus(parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
|
||||
if (this.read(current, "fs+") === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
var valueNode = this.parse_decimal(current);
|
||||
|
||||
if (valueNode === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
current.value = new parts.FontSizePlus(valueNode.value);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ParseNode} parent
|
||||
* @return {ParseNode}
|
||||
*/
|
||||
parse_tag_fsminus(parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
|
||||
if (this.read(current, "fs-") === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
var valueNode = this.parse_decimal(current);
|
||||
|
||||
if (valueNode === null) {
|
||||
parent.pop();
|
||||
return null;
|
||||
}
|
||||
|
||||
current.value = new parts.FontSizeMinus(valueNode.value);
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ParseNode} parent
|
||||
* @return {ParseNode}
|
||||
@@ -1384,6 +1463,8 @@ module libjass.parser {
|
||||
this.parse_tag_fry(current) ||
|
||||
this.parse_tag_frz(current) ||
|
||||
this.parse_tag_fsp(current) ||
|
||||
this.parse_tag_fsplus(current) ||
|
||||
this.parse_tag_fsminus(current) ||
|
||||
|
||||
this.parse_tag_be(current) ||
|
||||
this.parse_tag_fr(current) ||
|
||||
@@ -2080,7 +2161,7 @@ module libjass.parser {
|
||||
|
||||
var minuteDigitNodes = new Array<ParseNode>(2);
|
||||
|
||||
for (var i = 0; i < minuteDigitNodes.length; i++) {
|
||||
for (i = 0; i < minuteDigitNodes.length; i++) {
|
||||
if (!this._haveMore()) {
|
||||
parent.pop();
|
||||
return null;
|
||||
@@ -2103,7 +2184,7 @@ module libjass.parser {
|
||||
|
||||
var secondDigitNodes = new Array<ParseNode>(2);
|
||||
|
||||
for (var i = 0; i < secondDigitNodes.length; i++) {
|
||||
for (i = 0; i < secondDigitNodes.length; i++) {
|
||||
if (!this._haveMore()) {
|
||||
parent.pop();
|
||||
return null;
|
||||
@@ -2126,7 +2207,7 @@ module libjass.parser {
|
||||
|
||||
var millisecondDigitNodes = new Array<ParseNode>(3);
|
||||
|
||||
for (var i = 0; i < millisecondDigitNodes.length; i++) {
|
||||
for (i = 0; i < millisecondDigitNodes.length; i++) {
|
||||
if (!this._haveMore()) {
|
||||
parent.pop();
|
||||
return null;
|
||||
@@ -2166,17 +2247,16 @@ module libjass.parser {
|
||||
|
||||
/**
|
||||
* @param {number=1} count
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _peek(count: number = 1): string {
|
||||
// Fastpath for count === 1. http://jsperf.com/substr-vs-indexer
|
||||
if (count === 1) { return this._input[this._parseTree.end]; }
|
||||
|
||||
return this._input.substr(this._parseTree.end, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _haveMore(): boolean {
|
||||
return this._parseTree.end < this._input.length;
|
||||
@@ -2186,8 +2266,6 @@ module libjass.parser {
|
||||
* @param {string} tagName One of "clip" and "iclip"
|
||||
* @param {!ParseNode} parent
|
||||
* @return {ParseNode}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _parse_tag_clip_or_iclip(tagName: string, parent: ParseNode): ParseNode {
|
||||
var current = new ParseNode(parent);
|
||||
@@ -2284,7 +2362,7 @@ module libjass.parser {
|
||||
if (valueNode !== null) {
|
||||
current.value = new tagConstructor(valueNode.value);
|
||||
}
|
||||
else if (required) {
|
||||
else if (!required) {
|
||||
current.value = new tagConstructor(null);
|
||||
}
|
||||
else {
|
||||
@@ -2342,33 +2420,25 @@ module libjass.parser {
|
||||
/**
|
||||
* This class represents a single parse node. It has a start and end position, and an optional value object.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ParseNode} parent The parent of this parse node. The parent's end position will be updated to the end position of this node whenever the latter changes.
|
||||
* @param {?string=null} value A shortcut to assign a string to the value property.
|
||||
*
|
||||
* @private
|
||||
* @memberof libjass.parser
|
||||
* @param {ParseNode} parent The parent of this parse node.
|
||||
* @param {*=null} value If provided, it is assigned as the value of the node.
|
||||
*/
|
||||
class ParseNode {
|
||||
export class ParseNode {
|
||||
private _children: ParseNode[] = [];
|
||||
|
||||
private _start: number;
|
||||
private _end: number;
|
||||
private _value: any;
|
||||
|
||||
constructor(private _parent: ParseNode, value: string = null) {
|
||||
constructor(private _parent: ParseNode, value: any = null) {
|
||||
if (_parent !== null) {
|
||||
_parent._children.push(this);
|
||||
_parent.children.push(this);
|
||||
}
|
||||
|
||||
this._start = ((_parent !== null) ? _parent.end : 0);
|
||||
this._end = this._start;
|
||||
|
||||
if (value !== null) {
|
||||
this.value = value;
|
||||
}
|
||||
else {
|
||||
this._setEnd(this._start);
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2422,7 +2492,7 @@ module libjass.parser {
|
||||
set value(newValue: any) {
|
||||
this._value = newValue;
|
||||
|
||||
if (this._value.constructor === String && this._children.length === 0) {
|
||||
if (this._value !== null && this._value.constructor === String && this._children.length === 0) {
|
||||
this._setEnd(this._start + this._value.length);
|
||||
}
|
||||
}
|
||||
@@ -2445,8 +2515,6 @@ module libjass.parser {
|
||||
* Updates the end property of this node and its parent recursively to the root node.
|
||||
*
|
||||
* @param {number} newEnd
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _setEnd(newEnd: number): void {
|
||||
this._end = newEnd;
|
||||
@@ -2457,3 +2525,18 @@ module libjass.parser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module libjass.webworker {
|
||||
export enum WorkerCommands {
|
||||
Parse = 1
|
||||
}
|
||||
|
||||
libjass.webworker._registerWorkerCommand(WorkerCommands.Parse, (parameters: any, response: WorkerResultCallback): void => {
|
||||
try {
|
||||
response(null, libjass.parser.parse(parameters.input, parameters.rule));
|
||||
}
|
||||
catch (ex) {
|
||||
response(ex, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,13 +26,10 @@ module libjass.parts {
|
||||
*
|
||||
* Instances of this class are immutable.
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} red
|
||||
* @param {number} green
|
||||
* @param {number} blue
|
||||
* @param {number=1} alpha
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Color {
|
||||
constructor(private _red: number, private _green: number, private _blue: number, private _alpha: number = 1) { }
|
||||
@@ -101,10 +98,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A comment, i.e., any text enclosed in {} that is not understood as an ASS tag.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} value The text of this comment
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Comment {
|
||||
constructor(private _value: string) { }
|
||||
@@ -122,10 +116,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A block of text, i.e., any text not enclosed in {}. Also includes \h.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} value The content of this block of text
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Text {
|
||||
constructor(private _value: string) { }
|
||||
@@ -146,10 +137,6 @@ module libjass.parts {
|
||||
|
||||
/**
|
||||
* A newline character \N.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class NewLine {
|
||||
}
|
||||
@@ -157,10 +144,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An italic tag {\i}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?boolean} value {\i1} -> true, {\i0} -> false, {\i} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Italic {
|
||||
constructor(private _value: boolean) { }
|
||||
@@ -178,10 +162,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A bold tag {\b}
|
||||
*
|
||||
* @constructor
|
||||
* @param {*} value {\b1} -> true, {\b0} -> false, {\b###} -> weight of the bold (number), {\b} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Bold {
|
||||
constructor(private _value: Object) { }
|
||||
@@ -199,10 +180,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An underline tag {\u}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?boolean} value {\u1} -> true, {\u0} -> false, {\u} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Underline {
|
||||
constructor(private _value: boolean) { }
|
||||
@@ -220,10 +198,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A strike-through tag {\s}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?boolean} value {\s1} -> true, {\s0} -> false, {\s} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class StrikeThrough {
|
||||
constructor(private _value: boolean) { }
|
||||
@@ -241,10 +216,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A border tag {\bord}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\bord###} -> width (number), {\bord} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Border {
|
||||
constructor(private _value: number) { }
|
||||
@@ -262,10 +234,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A horizontal border tag {\xbord}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\xbord###} -> width (number), {\xbord} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class BorderX {
|
||||
constructor(private _value: number) { }
|
||||
@@ -283,10 +252,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A vertical border tag {\ybord}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\ybord###} -> height (number), {\ybord} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class BorderY {
|
||||
constructor(private _value: number) { }
|
||||
@@ -304,10 +270,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A shadow tag {\shad}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\shad###} -> depth (number), {\shad} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Shadow {
|
||||
constructor(private _value: number) { }
|
||||
@@ -325,10 +288,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A horizontal shadow tag {\xshad}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\xshad###} -> depth (number), {\xshad} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ShadowX {
|
||||
constructor(private _value: number) { }
|
||||
@@ -346,10 +306,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A vertical shadow tag {\yshad}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\yshad###} -> depth (number), {\yshad} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ShadowY {
|
||||
constructor(private _value: number) { }
|
||||
@@ -367,10 +324,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A blur tag {\be}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\be###} -> strength (number), {\be} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Blur {
|
||||
constructor(private _value: number) { }
|
||||
@@ -388,10 +342,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A Gaussian blur tag {\blur}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\blur###} -> strength (number), {\blur} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class GaussianBlur {
|
||||
constructor(private _value: number) { }
|
||||
@@ -409,10 +360,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A font name tag {\fn}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?string} value {\fn###} -> name (string), {\fn} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class FontName {
|
||||
constructor(private _value: string) { }
|
||||
@@ -430,10 +378,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A font size tag {\fs}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fs###} -> size (number), {\fs} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class FontSize {
|
||||
constructor(private _value: number) { }
|
||||
@@ -448,13 +393,46 @@ module libjass.parts {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A font size increase tag {\fs+}
|
||||
*
|
||||
* @param {?number} value {\fs+###} -> difference (number)
|
||||
*/
|
||||
export class FontSizePlus {
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this font size increase tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A font size decrease tag {\fs-}
|
||||
*
|
||||
* @param {?number} value {\fs-###} -> difference (number)
|
||||
*/
|
||||
export class FontSizeMinus {
|
||||
constructor(private _value: number) { }
|
||||
|
||||
/**
|
||||
* The value of this font size decrease tag.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A horizontal font scaling tag {\fscx}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fscx###} -> scale (number), {\fscx} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class FontScaleX {
|
||||
constructor(private _value: number) { }
|
||||
@@ -472,10 +450,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A vertical font scaling tag {\fscy}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fscy###} -> scale (number), {\fscy} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class FontScaleY {
|
||||
constructor(private _value: number) { }
|
||||
@@ -493,10 +468,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A letter-spacing tag {\fsp}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fsp###} -> spacing (number), {\fsp} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class LetterSpacing {
|
||||
constructor(private _value: number) { }
|
||||
@@ -514,10 +486,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An X-axis rotation tag {\frx}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\frx###} -> angle (number), {\frx} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class RotateX {
|
||||
constructor(private _value: number) { }
|
||||
@@ -535,10 +504,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A Y-axis rotation tag {\fry}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fry###} -> angle (number), {\fry} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class RotateY {
|
||||
constructor(private _value: number) { }
|
||||
@@ -556,10 +522,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A Z-axis rotation tag {\fr} or {\frz}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\frz###} -> angle (number), {\frz} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class RotateZ {
|
||||
constructor(private _value: number) { }
|
||||
@@ -577,10 +540,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An X-axis shearing tag {\fax}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fax###} -> angle (number), {\fax} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class SkewX {
|
||||
constructor(private _value: number) { }
|
||||
@@ -598,10 +558,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A Y-axis shearing tag {\fay}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\fay###} -> angle (number), {\fay} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class SkewY {
|
||||
constructor(private _value: number) { }
|
||||
@@ -619,10 +576,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A primary color tag {\c} or {\1c}
|
||||
*
|
||||
* @constructor
|
||||
* @param {libjass.parts.Color} value {\1c###} -> color (Color), {\1c} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class PrimaryColor {
|
||||
constructor(private _value: Color) { }
|
||||
@@ -640,10 +594,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A secondary color tag {\2c}
|
||||
*
|
||||
* @constructor
|
||||
* @param {libjass.parts.Color} value {\2c###} -> color (Color), {\2c} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class SecondaryColor {
|
||||
constructor(private _value: Color) { }
|
||||
@@ -661,10 +612,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An outline color tag {\3c}
|
||||
*
|
||||
* @constructor
|
||||
* @param {libjass.parts.Color} value {\3c###} -> color (Color), {\3c} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class OutlineColor {
|
||||
constructor(private _value: Color) { }
|
||||
@@ -682,10 +630,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A shadow color tag {\4c}
|
||||
*
|
||||
* @constructor
|
||||
* @param {libjass.parts.Color} value {\4c###} -> color (Color), {\4c} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ShadowColor {
|
||||
constructor(private _value: Color) { }
|
||||
@@ -703,10 +648,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An alpha tag {\alpha}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\alpha###} -> alpha (number), {\alpha} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Alpha {
|
||||
constructor(private _value: number) { }
|
||||
@@ -724,10 +666,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A primary alpha tag {\1a}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\1a###} -> alpha (number), {\1a} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class PrimaryAlpha {
|
||||
constructor(private _value: number) { }
|
||||
@@ -745,10 +684,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A secondary alpha tag {\2a}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\2a###} -> alpha (number), {\2a} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class SecondaryAlpha {
|
||||
constructor(private _value: number) { }
|
||||
@@ -766,10 +702,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An outline alpha tag {\3a}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\3a###} -> alpha (number), {\3a} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class OutlineAlpha {
|
||||
constructor(private _value: number) { }
|
||||
@@ -787,10 +720,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A shadow alpha tag {\4a}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?number} value {\4a###} -> alpha (number), {\4a} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ShadowAlpha {
|
||||
constructor(private _value: number) { }
|
||||
@@ -808,10 +738,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An alignment tag {\an} or {\a}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} value {\an###} -> alignment (number)
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Alignment {
|
||||
constructor(private _value: number) { }
|
||||
@@ -829,10 +756,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A color karaoke tag {\k}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} duration {\k###} -> duration (number)
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ColorKaraoke {
|
||||
constructor(private _duration: number) { }
|
||||
@@ -850,10 +774,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A sweeping color karaoke tag {\K} or {\kf}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} duration {\kf###} -> duration (number)
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class SweepingColorKaraoke {
|
||||
constructor(private _duration: number) { }
|
||||
@@ -871,10 +792,7 @@ module libjass.parts {
|
||||
/**
|
||||
* An outline karaoke tag {\ko}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} duration {\ko###} -> duration (number)
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class OutlineKaraoke {
|
||||
constructor(private _duration: number) { }
|
||||
@@ -892,10 +810,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A wrapping style tag {\q}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} value {\q###} -> style (number)
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class WrappingStyle {
|
||||
constructor(private _value: libjass.WrappingStyle) { }
|
||||
@@ -913,10 +828,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A style reset tag {\r}
|
||||
*
|
||||
* @constructor
|
||||
* @param {?string} value {\r###} -> style name (string), {\r} -> null
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Reset {
|
||||
constructor(private _value: string) { }
|
||||
@@ -934,11 +846,8 @@ module libjass.parts {
|
||||
/**
|
||||
* A position tag {\pos}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Position {
|
||||
constructor(private _x: number, private _y: number) { }
|
||||
@@ -965,15 +874,12 @@ module libjass.parts {
|
||||
/**
|
||||
* A movement tag {\move}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} t1
|
||||
* @param {number} t2
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Move {
|
||||
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _t1: number, private _t2: number) { }
|
||||
@@ -1036,11 +942,8 @@ module libjass.parts {
|
||||
/**
|
||||
* A rotation origin tag {\org}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class RotationOrigin {
|
||||
constructor(private _x: number, private _y: number) { }
|
||||
@@ -1067,11 +970,8 @@ module libjass.parts {
|
||||
/**
|
||||
* A simple fade tag {\fad}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Fade {
|
||||
constructor(private _start: number, private _end: number) { }
|
||||
@@ -1098,7 +998,6 @@ module libjass.parts {
|
||||
/**
|
||||
* A complex fade tag {\fade}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} a1
|
||||
* @param {number} a2
|
||||
* @param {number} a3
|
||||
@@ -1106,8 +1005,6 @@ module libjass.parts {
|
||||
* @param {number} t2
|
||||
* @param {number} t3
|
||||
* @param {number} t4
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class ComplexFade {
|
||||
constructor(
|
||||
@@ -1182,13 +1079,10 @@ module libjass.parts {
|
||||
/**
|
||||
* A transform tag {\t}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param {number} accel
|
||||
* @param {!Array.<!libjass.parts.Tag>} tags
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class Transform {
|
||||
constructor(private _start: number, private _end: number, private _accel: number, private _tags: Part[]) { }
|
||||
@@ -1233,14 +1127,11 @@ module libjass.parts {
|
||||
/**
|
||||
* A rectangular clip tag {\clip} or {\iclip}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {boolean} inside
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class RectangularClip {
|
||||
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _inside: boolean) { }
|
||||
@@ -1294,12 +1185,9 @@ module libjass.parts {
|
||||
/**
|
||||
* A vector clip tag {\clip} or {\iclip}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} scale
|
||||
* @param {!Array.<!libjass.parts.drawing.Instruction>} instructions
|
||||
* @param {boolean} inside
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class VectorClip {
|
||||
constructor(private _scale: number, private _instructions: drawing.Instruction[], private _inside: boolean) { }
|
||||
@@ -1335,10 +1223,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A drawing mode tag {\p}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} scale
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class DrawingMode {
|
||||
constructor(private _scale: number) { }
|
||||
@@ -1356,10 +1241,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A drawing mode baseline offset tag {\pbo}
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} value
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class DrawingBaselineOffset {
|
||||
constructor(private _value: number) { }
|
||||
@@ -1377,10 +1259,7 @@ module libjass.parts {
|
||||
/**
|
||||
* A pseudo-part representing text interpreted as drawing instructions
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Array.<!libjass.parts.drawing.Instruction>} instructions
|
||||
*
|
||||
* @memberof libjass.parts
|
||||
*/
|
||||
export class DrawingInstructions {
|
||||
constructor(private _instructions: drawing.Instruction[]) { }
|
||||
@@ -1404,11 +1283,8 @@ module libjass.parts {
|
||||
/**
|
||||
* An instruction to move to a particular position.
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*
|
||||
* @memberof libjass.parts.drawing
|
||||
*/
|
||||
export class MoveInstruction implements Instruction {
|
||||
constructor(private _x: number, private _y: number) { }
|
||||
@@ -1435,11 +1311,8 @@ module libjass.parts {
|
||||
/**
|
||||
* An instruction to draw a line to a particular position.
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*
|
||||
* @memberof libjass.parts.drawing
|
||||
*/
|
||||
export class LineInstruction implements Instruction {
|
||||
constructor(private _x: number, private _y: number) { }
|
||||
@@ -1466,15 +1339,12 @@ module libjass.parts {
|
||||
/**
|
||||
* An instruction to draw a cubic bezier curve to a particular position, with two given control points.
|
||||
*
|
||||
* @constructor
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @param {number} x3
|
||||
* @param {number} y3
|
||||
*
|
||||
* @memberof libjass.parts.drawing
|
||||
*/
|
||||
export class CubicBezierCurveInstruction implements Instruction {
|
||||
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _x3: number, private _y3: number) { }
|
||||
@@ -1554,6 +1424,7 @@ module libjass.parts {
|
||||
var value = (<any>libjass.parts)[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, key);
|
||||
libjass.webworker._registerClassPrototype(value.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1561,6 +1432,7 @@ module libjass.parts {
|
||||
var value = (<any>libjass.parts.drawing)[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, "Drawing" + key);
|
||||
libjass.webworker._registerClassPrototype(value.prototype);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A mixin class that represents an event source.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
export class EventSource<T> {
|
||||
_eventListeners: Map<T, Function[]>;
|
||||
|
||||
/**
|
||||
* Add a listener for the given event.
|
||||
*
|
||||
* @param {!T} type The type of event to attach the listener for
|
||||
* @param {!Function} listener The listener
|
||||
*/
|
||||
addEventListener(type: T, listener: Function): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
|
||||
if (listeners === undefined) {
|
||||
this._eventListeners.set(type, listeners = []);
|
||||
}
|
||||
|
||||
listeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!T} type The type of event to dispatch
|
||||
* @param {!Array.<*>} args Arguments for the listeners of the event
|
||||
*/
|
||||
_dispatchEvent(type: T, args: Object[]): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
if (listeners !== undefined) {
|
||||
listeners.forEach((listener: Function) => {
|
||||
listener.apply(this, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum ClockEvent {
|
||||
Play,
|
||||
Tick,
|
||||
Pause,
|
||||
Stop,
|
||||
}
|
||||
|
||||
export interface Clock extends EventSource<ClockEvent> {
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
enabled: boolean;
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean;
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean;
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void;
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean;
|
||||
|
||||
// EventSource members
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of libjass.renderers.Clock that allows user script to manually trigger play, pause and timeUpdate events.
|
||||
*/
|
||||
export class ManualClock implements Clock {
|
||||
private _currentTime: number = -1;
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Play
|
||||
*/
|
||||
play(): void {
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Tick
|
||||
*
|
||||
* @param {number} currentTime
|
||||
*/
|
||||
tick(currentTime: number): void {
|
||||
this._currentTime = currentTime;
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Pause
|
||||
*/
|
||||
pause(): void {
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a ClockEvent.Stop
|
||||
*/
|
||||
stop(): void {
|
||||
this._dispatchEvent(ClockEvent.Stop, []);
|
||||
}
|
||||
|
||||
// Clock members
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get currentTime(): number {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
if (this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
if (!this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
if (this._enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
// EventSource members
|
||||
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
}
|
||||
mixin(ManualClock, [EventSource]);
|
||||
|
||||
enum VideoState {
|
||||
Playing = 0,
|
||||
Paused = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of libjass.renderers.Clock that generates play, pause and timeUpdate events according to the state of a <video> element.
|
||||
*
|
||||
* @param {!HTMLVideoElement} video
|
||||
*/
|
||||
export class VideoClock implements Clock {
|
||||
private _currentTime: number = -1;
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
private _videoState: VideoState;
|
||||
|
||||
private _nextAnimationFrameRequestId: number = null;
|
||||
|
||||
constructor(private _video: HTMLVideoElement) {
|
||||
this._video.addEventListener("playing", () => this._onVideoPlaying(), false);
|
||||
this._video.addEventListener("pause", () => this._onVideoPause(), false);
|
||||
this._video.addEventListener("seeking", () => this._onVideoSeeking(), false);
|
||||
}
|
||||
|
||||
// Clock members
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get currentTime(): number {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
if (this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._videoState !== VideoState.Paused) {
|
||||
if (libjass.debugMode) {
|
||||
console.warn("VideoClock.enable: Abnormal state detected. VideoClock._videoState should have been VideoState.Paused");
|
||||
}
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._startTicking();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
if (!this._enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Stop, []);
|
||||
|
||||
this._stopTicking();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
if (this._enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the clock.
|
||||
*
|
||||
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private _onVideoPlaying(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._startTicking();
|
||||
}
|
||||
|
||||
private _onVideoPause(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._videoState = VideoState.Paused;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
|
||||
if (this._nextAnimationFrameRequestId === null) {
|
||||
if (libjass.debugMode) {
|
||||
console.warn("VideoClock._onVideoPause: Abnormal state detected. VideoClock._nextAnimationFrameRequestId should not have been null");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._stopTicking();
|
||||
}
|
||||
|
||||
private _onVideoSeeking(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
if (this._currentTime === this._video.currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTime = this._video.currentTime;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
|
||||
private _onTimerTick(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
if (this._currentTime !== this._video.currentTime) {
|
||||
this._currentTime = this._video.currentTime;
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
else {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._currentTime !== this._video.currentTime) {
|
||||
this._currentTime = this._video.currentTime;
|
||||
this._videoState = VideoState.Playing;
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
}
|
||||
|
||||
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
|
||||
}
|
||||
|
||||
private _startTicking(): void {
|
||||
if (this._nextAnimationFrameRequestId === null) {
|
||||
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
|
||||
}
|
||||
}
|
||||
|
||||
private _stopTicking(): void {
|
||||
if (this._nextAnimationFrameRequestId !== null) {
|
||||
cancelAnimationFrame(this._nextAnimationFrameRequestId);
|
||||
this._nextAnimationFrameRequestId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// EventSource members
|
||||
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
|
||||
addEventListener: (type: ClockEvent, listener: Function) => void;
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
}
|
||||
mixin(VideoClock, [EventSource]);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
interface Document {
|
||||
fullscreenElement: Element;
|
||||
mozFullScreenElement: Element;
|
||||
webkitFullscreenElement: Element;
|
||||
}
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A default renderer implementation.
|
||||
*
|
||||
* @param {!HTMLVideoElement} video
|
||||
* @param {!libjass.ASS} ass
|
||||
* @param {!libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class DefaultRenderer extends WebRenderer {
|
||||
private _videoIsFullScreen: boolean = false;
|
||||
|
||||
constructor(private _video: HTMLVideoElement, ass: ASS, settings: RendererSettings) {
|
||||
super(ass, new VideoClock(_video), settings, document.createElement("div"));
|
||||
|
||||
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
|
||||
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
resizeVideo(width: number, height: number): void {
|
||||
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize(width, height)` instead.");
|
||||
this.resize(width, height);
|
||||
}
|
||||
|
||||
_ready(): void {
|
||||
document.addEventListener("mozfullscreenchange", event => this._onFullScreenChange(document.mozFullScreenElement), false);
|
||||
document.addEventListener("webkitfullscreenchange", event => this._onFullScreenChange(document.webkitFullscreenElement), false);
|
||||
document.addEventListener("fullscreenchange", event => this._onFullScreenChange(document.fullscreenElement), false);
|
||||
|
||||
this.resize(this._video.offsetWidth, this._video.offsetHeight);
|
||||
|
||||
super._ready();
|
||||
}
|
||||
|
||||
private _onFullScreenChange(fullScreenElement: Element) {
|
||||
if (fullScreenElement === undefined) {
|
||||
fullScreenElement = document.msFullscreenElement;
|
||||
}
|
||||
|
||||
if (fullScreenElement === this._video) {
|
||||
this.libjassSubsWrapper.classList.add("libjass-full-screen");
|
||||
|
||||
this.resize(screen.width, screen.height);
|
||||
|
||||
this._videoIsFullScreen = true;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
else if (fullScreenElement === null && this._videoIsFullScreen) {
|
||||
this.libjassSubsWrapper.classList.remove("libjass-full-screen");
|
||||
|
||||
this._videoIsFullScreen = false;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass.renderers {
|
||||
/**
|
||||
* A renderer implementation that doesn't output anything.
|
||||
*
|
||||
* @param {!libjass.ASS} ass
|
||||
* @param {!libjass.renderers.Clock} clock
|
||||
* @param {!libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class NullRenderer {
|
||||
private static _lastRendererId = -1;
|
||||
|
||||
private _id: number;
|
||||
|
||||
private _settings: RendererSettings;
|
||||
|
||||
constructor(private _ass: ASS, private _clock: Clock, settings: RendererSettings) {
|
||||
this._id = ++NullRenderer._lastRendererId;
|
||||
|
||||
this._settings = RendererSettings.from(settings);
|
||||
|
||||
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
|
||||
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
|
||||
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
|
||||
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique ID of this renderer. Auto-generated.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.ASS}
|
||||
*/
|
||||
get ass(): ASS {
|
||||
return this._ass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.renderers.Clock}
|
||||
*/
|
||||
get clock(): Clock {
|
||||
return this._clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
get settings(): RendererSettings {
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-render a dialogue. This is a no-op for this type.
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
preRender(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Draw a dialogue. This is a no-op for this type.
|
||||
*
|
||||
* @param {!libjass.Dialogue} dialogue
|
||||
*/
|
||||
draw(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Enable the renderer.
|
||||
*
|
||||
* @return {boolean} True if the renderer is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): boolean {
|
||||
return this._clock.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the renderer.
|
||||
*
|
||||
* @return {boolean} True if the renderer is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): boolean {
|
||||
return this._clock.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the renderer.
|
||||
*/
|
||||
toggle(): void {
|
||||
this._clock.toggle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the renderer.
|
||||
*
|
||||
* @param {boolean} enabled If true, the renderer is enabled, otherwise it's disabled.
|
||||
* @return {boolean} True if the renderer is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
return this._clock.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._clock.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
|
||||
*/
|
||||
_onClockPlay(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockPlay");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
|
||||
*/
|
||||
_onClockTick(): void {
|
||||
var currentTime = this._clock.currentTime;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockTick: currentTime = " + currentTime);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._ass.dialogues.length; i++) {
|
||||
var dialogue = this._ass.dialogues[i];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is paused.
|
||||
*/
|
||||
_onClockPause(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockPause");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is disabled.
|
||||
*/
|
||||
_onClockStop(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockStop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for the renderer.
|
||||
*/
|
||||
export class 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.
|
||||
*
|
||||
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the RendererSettings.makeFontMapFromStyleElement() convenience method to create a font map.
|
||||
*
|
||||
* @type {!Map.<string, !Array.<string>>}
|
||||
*/
|
||||
fontMap: Map<string, string[]>;
|
||||
|
||||
/**
|
||||
* Subtitles will be pre-rendered for this amount of time (seconds).
|
||||
*
|
||||
* Defaults to 5.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
preRenderTime: number;
|
||||
|
||||
/**
|
||||
* Subtitle outlines will be rendered in full detail. When false, the value of blur is used to draw less outlines for better performance and (hopefully) similar output.
|
||||
*
|
||||
* Defaults to false.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
preciseOutlines: boolean;
|
||||
|
||||
/**
|
||||
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules.
|
||||
*
|
||||
* @param {!LinkStyle} linkStyle
|
||||
* @return {!Map.<string, !Array.<string>>}
|
||||
*/
|
||||
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string[]> {
|
||||
var map = new Map<string, string[]>();
|
||||
|
||||
var styleSheet = <CSSStyleSheet>linkStyle.sheet;
|
||||
var rules: CSSFontFaceRule[] = Array.prototype.filter.call(styleSheet.cssRules, (rule: CSSRule) => rule.type === CSSRule.FONT_FACE_RULE);
|
||||
rules.forEach((rule: CSSFontFaceRule) => {
|
||||
var src = rule.style.getPropertyValue("src");
|
||||
var urls: string[] = [];
|
||||
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map((line: string) => line.match(/src: ([^;]+);/))
|
||||
.filter((matches: string[]) => matches !== null)
|
||||
.map((matches: string[]) => matches[1])[0];
|
||||
}
|
||||
|
||||
urls = src.split(/,\s*/).map((url: string) => url.match(/^url\((.+)\)$/)[1]);
|
||||
|
||||
if (urls.length > 0) {
|
||||
var name = RendererSettings._stripQuotes(rule.style.getPropertyValue("font-family"));
|
||||
var existingList = map.get(name);
|
||||
if (existingList === undefined) {
|
||||
existingList = [];
|
||||
map.set(name, existingList);
|
||||
}
|
||||
existingList.unshift.apply(existingList, urls.map(RendererSettings._stripQuotes));
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an arbitrary object into a RendererSettings object.
|
||||
*
|
||||
* @param {!*} object
|
||||
* @return {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
static from(object: any): RendererSettings {
|
||||
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines);
|
||||
}
|
||||
|
||||
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false): RendererSettings {
|
||||
var result = new RendererSettings();
|
||||
result.fontMap = fontMap;
|
||||
result.preRenderTime = preRenderTime;
|
||||
result.preciseOutlines = preciseOutlines;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _stripQuotes(str: string): string {
|
||||
return str.match(/^["']?(.*?)["']?$/)[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
+330
-716
File diff suppressed because it is too large
Load Diff
+70
-15
@@ -48,6 +48,8 @@ var require = function (name) {
|
||||
var Suite = function (name, parent) {
|
||||
this.name = name;
|
||||
|
||||
this.setup = function () { };
|
||||
|
||||
this.children = [];
|
||||
|
||||
this.parent = parent;
|
||||
@@ -98,12 +100,25 @@ var require = function (name) {
|
||||
}, enumerable: true },
|
||||
});
|
||||
|
||||
Suite.prototype.run = function (logger) {
|
||||
Suite.prototype.run = function (logger, done) {
|
||||
var _this = this;
|
||||
|
||||
logger.beginSuite(this);
|
||||
|
||||
this.children.forEach(function (child) { child.run(logger); });
|
||||
this.setup();
|
||||
|
||||
logger.endSuite(this);
|
||||
var remainingChildren = this.children.slice();
|
||||
var runChild = function () {
|
||||
if (remainingChildren.length > 0) {
|
||||
var nextChild = remainingChildren.shift();
|
||||
nextChild.run(logger, runChild);
|
||||
}
|
||||
else {
|
||||
logger.endSuite(_this);
|
||||
done();
|
||||
}
|
||||
};
|
||||
runChild();
|
||||
};
|
||||
|
||||
var Test = function (description, body, parent) {
|
||||
@@ -118,21 +133,49 @@ var require = function (name) {
|
||||
this.exception = null;
|
||||
};
|
||||
|
||||
Test.prototype.run = function (logger) {
|
||||
Test.prototype.run = function (logger, done) {
|
||||
if (this.body) {
|
||||
this.passed = false;
|
||||
|
||||
try {
|
||||
this.body();
|
||||
if (this.body.length === 0) {
|
||||
try {
|
||||
this.body();
|
||||
|
||||
this.passed = true;
|
||||
this.passed = true;
|
||||
}
|
||||
catch (testException) {
|
||||
this.exception = testException;
|
||||
}
|
||||
|
||||
logger.writeTest(this);
|
||||
|
||||
done();
|
||||
}
|
||||
catch (testException) {
|
||||
this.exception = testException;
|
||||
else {
|
||||
try {
|
||||
var _this = this;
|
||||
this.body(function (testException) {
|
||||
if (testException !== undefined && testException !== null) {
|
||||
_this.exception = testException;
|
||||
}
|
||||
else {
|
||||
_this.passed = true;
|
||||
}
|
||||
|
||||
logger.writeTest(_this);
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
catch (testException) {
|
||||
this.exception = testException;
|
||||
|
||||
logger.writeTest(this);
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.writeTest(this);
|
||||
};
|
||||
|
||||
var currentSuite = null;
|
||||
@@ -147,6 +190,10 @@ var require = function (name) {
|
||||
return newSuite;
|
||||
};
|
||||
|
||||
setup = function (body) {
|
||||
currentSuite.setup = body;
|
||||
}
|
||||
|
||||
test = function (description, body) {
|
||||
return new Test(description, body, currentSuite);
|
||||
};
|
||||
@@ -284,7 +331,7 @@ var Logger = function (outputDiv) {
|
||||
|
||||
this.writeTest = function (test) {
|
||||
if (test.passed === true) {
|
||||
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] ";
|
||||
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ]";
|
||||
console.log(message);
|
||||
append(testDiv, message).className += " passed";
|
||||
}
|
||||
@@ -345,7 +392,15 @@ var Logger = function (outputDiv) {
|
||||
addEventListener("DOMContentLoaded", function () {
|
||||
var logger = new Logger(document.querySelector("#output"));
|
||||
|
||||
rootSuites.forEach(function (suite) { suite.run(logger); });
|
||||
|
||||
logger.writeTotal(rootSuites);
|
||||
var remainingSuites = rootSuites.slice();
|
||||
var runSuite = function () {
|
||||
if (remainingSuites.length > 0) {
|
||||
var nextSuite = remainingSuites.shift();
|
||||
nextSuite.run(logger, runSuite);
|
||||
}
|
||||
else {
|
||||
logger.writeTotal(rootSuites);
|
||||
}
|
||||
};
|
||||
runSuite();
|
||||
}, false);
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<script src="test-primitives.js" />
|
||||
<script src="test-tags.js" />
|
||||
<script src="test-miscellaneous.js" />
|
||||
<script src="test-webworker.js" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="output" />
|
||||
|
||||
@@ -56,6 +56,14 @@ suite("Tags", function () {
|
||||
parserTest("Enclosed tag", "{\\b150}", "enclosedTags", [new libjass.parts.Bold(true), new libjass.parts.Comment("50")]);
|
||||
});
|
||||
|
||||
suite("Italics tag - \\i", function () {
|
||||
parserTest("True", "i1", "tag_i", new libjass.parts.Italic(true));
|
||||
|
||||
parserTest("False", "i0", "tag_i", new libjass.parts.Italic(false));
|
||||
|
||||
parserTest("null", "i", "tag_i", new libjass.parts.Italic(null));
|
||||
});
|
||||
|
||||
suite("Primary color tags - \\c and \\1c", function () {
|
||||
parserTest("Just the tag", "1c&H3F171F&", "tag_1c", new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)));
|
||||
|
||||
@@ -72,6 +80,20 @@ suite("Tags", function () {
|
||||
parserTest("Just the tag", "alpha00", "tag_alpha", new libjass.parts.Alpha(1));
|
||||
});
|
||||
|
||||
suite("Font size tag - \\fs", function () {
|
||||
parserTest("5", "fs5", "tag_fs", new libjass.parts.FontSize(5));
|
||||
|
||||
parserTest("null", "fs", "tag_fs", new libjass.parts.FontSize(null));
|
||||
|
||||
parserTest("Plus 5", "fs+5", "tag_fsplus", new libjass.parts.FontSizePlus(5));
|
||||
|
||||
parserTest("Plus null", "fs+", "tag_fsplus", null);
|
||||
|
||||
parserTest("Minus 5", "fs-5", "tag_fsminus", new libjass.parts.FontSizeMinus(5));
|
||||
|
||||
parserTest("Minus null", "fs-", "tag_fsminus", null);
|
||||
});
|
||||
|
||||
suite("Transform tag - \\t", function () {
|
||||
parserTest("No tags", "{\\t(100,200)}", "enclosedTags", [new libjass.parts.Comment("\\t(100,200)")]);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var libjass = require("../libjass.js");
|
||||
var assert = require("assert");
|
||||
|
||||
suite("Web worker", function () {
|
||||
var workerChannel = null;
|
||||
|
||||
setup(function () {
|
||||
workerChannel = libjass.webworker.createWorker();
|
||||
});
|
||||
|
||||
suite("Parse", function () {
|
||||
if (libjass.webworker.supported) {
|
||||
test("Parse", function (done) {
|
||||
this.customProperties = {
|
||||
rule: "dialogueParts",
|
||||
input: "{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!"
|
||||
};
|
||||
|
||||
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
|
||||
input: this.customProperties.input,
|
||||
rule: this.customProperties.rule
|
||||
}).then(function (promise) {
|
||||
try {
|
||||
assert.deepEqual(promise.result, [
|
||||
new libjass.parts.Alignment(8),
|
||||
new libjass.parts.Text("Are "),
|
||||
new libjass.parts.Italic(true),
|
||||
new libjass.parts.Text("you"),
|
||||
new libjass.parts.Italic(false),
|
||||
new libjass.parts.Text(" the one who stole the clock?!")
|
||||
]);
|
||||
|
||||
done();
|
||||
}
|
||||
catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
test("Parse: Web workers not supported in this environment.");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -21,21 +21,14 @@
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
module libjass {
|
||||
export interface StyleMap {
|
||||
[name: string]: Style;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an ASS script. It contains the script properties, an array of Styles, and an array of Dialogues.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @memberof libjass
|
||||
*/
|
||||
export class ASS {
|
||||
private _properties: ScriptProperties = new ScriptProperties();
|
||||
private _styles: StyleMap = Object.create(null);
|
||||
private _styles: Map<string, Style> = new Map<string, Style>();
|
||||
private _dialogues: Dialogue[] = [];
|
||||
private _dialoguesFormatSpecifier: string[];
|
||||
|
||||
/**
|
||||
* The properties of this script.
|
||||
@@ -49,9 +42,9 @@ module libjass {
|
||||
/**
|
||||
* The styles in this script.
|
||||
*
|
||||
* @type {!Object.<string, !libjass.Style>}
|
||||
* @type {!Map.<string, !libjass.Style>}
|
||||
*/
|
||||
get styles(): StyleMap {
|
||||
get styles(): Map<string, Style> {
|
||||
return this._styles;
|
||||
}
|
||||
|
||||
@@ -71,14 +64,38 @@ module libjass {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event to this ASS script.
|
||||
*
|
||||
* @param {string} line The line from the script that contains the new event.
|
||||
*/
|
||||
addEvent(line: string): void {
|
||||
var typedTemplateArray = <TypedTemplateArray>[];
|
||||
typedTemplateArray.formatSpecifier = this._dialoguesFormatSpecifier;
|
||||
var typedTemplate = <TypedTemplate>parser.parse(line, "assScriptProperty", new parser.ParseNode(null, { contents: typedTemplateArray }));
|
||||
|
||||
this._addEvent(typedTemplate);
|
||||
}
|
||||
|
||||
private _addEvent(line: TypedTemplate) {
|
||||
if (line.type === "Dialogue") {
|
||||
var dialogueTemplate = line.template;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
|
||||
}
|
||||
|
||||
// Create the dialogue and add it to the dialogues array
|
||||
this.dialogues.push(new Dialogue(dialogueTemplate, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ASS object from the raw text of an ASS script.
|
||||
*
|
||||
* @param {string} rawASS The raw text of the script.
|
||||
* @param {string} raw The raw text of the script.
|
||||
* @param {number=0} type The type of the script. One of the libjass.Format constants.
|
||||
* @return {!libjass.ASS}
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static fromString(raw: string, type: Format = Format.ASS): ASS {
|
||||
raw = raw.replace(/\r$/gm, "");
|
||||
@@ -94,27 +111,27 @@ module libjass {
|
||||
}
|
||||
|
||||
private static _fromASSString(rawASS: string): ASS {
|
||||
var script = parser.parse(rawASS, "assScript");
|
||||
var script = <Map<string, any>>parser.parse(rawASS, "assScript");
|
||||
|
||||
var result = new ASS();
|
||||
|
||||
// Get the script info template
|
||||
var infoTemplate: Template = script["Script Info"];
|
||||
var infoTemplate: Map<string, string> = script.get("Script Info");
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read script info: " + JSON.stringify(infoTemplate), infoTemplate);
|
||||
}
|
||||
|
||||
// Parse the script properties
|
||||
result.properties.resolutionX = parseInt(infoTemplate["PlayResX"]);
|
||||
result.properties.resolutionY = parseInt(infoTemplate["PlayResY"]);
|
||||
result.properties.wrappingStyle = parseInt(infoTemplate["WrapStyle"]);
|
||||
result.properties.scaleBorderAndShadow = (infoTemplate["ScaledBorderAndShadow"] === "yes");
|
||||
result.properties.resolutionX = parseInt(infoTemplate.get("PlayResX"));
|
||||
result.properties.resolutionY = parseInt(infoTemplate.get("PlayResY"));
|
||||
result.properties.wrappingStyle = parseInt(infoTemplate.get("WrapStyle"));
|
||||
result.properties.scaleBorderAndShadow = (infoTemplate.get("ScaledBorderAndShadow") === "yes");
|
||||
|
||||
// Get styles from the styles section
|
||||
script["V4+ Styles"].forEach((line: any) => {
|
||||
(<TypedTemplateArray>script.get("V4+ Styles")).forEach(line => {
|
||||
if (line.type === "Style") {
|
||||
var styleTemplate: Template = line.template;
|
||||
var styleTemplate = line.template;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read style: " + JSON.stringify(styleTemplate), styleTemplate);
|
||||
@@ -122,23 +139,14 @@ module libjass {
|
||||
|
||||
// Create the style and add it to the styles map
|
||||
var newStyle = new Style(styleTemplate);
|
||||
result.styles[newStyle.name] = newStyle;
|
||||
result.styles.set(newStyle.name, newStyle);
|
||||
}
|
||||
});
|
||||
|
||||
// Get dialogues from the events section
|
||||
script["Events"].forEach((line: any) => {
|
||||
if (line.type === "Dialogue") {
|
||||
var dialogueTemplate: Template = line.template;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
|
||||
}
|
||||
|
||||
// Create the dialogue and add it to the dialogues array
|
||||
result.dialogues.push(new Dialogue(dialogueTemplate, result));
|
||||
}
|
||||
});
|
||||
var events = <TypedTemplateArray>script.get("Events");
|
||||
result._dialoguesFormatSpecifier = events.formatSpecifier;
|
||||
events.forEach(line => result._addEvent(line));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -154,36 +162,36 @@ module libjass {
|
||||
result.properties.wrappingStyle = 1;
|
||||
result.properties.scaleBorderAndShadow = true;
|
||||
|
||||
var newStyle = new Style({
|
||||
"Name": "Default",
|
||||
"Italic": "0", "Bold": "0", "Underline": "0", "StrikeOut": "0",
|
||||
"Fontname": "", "Fontsize": "50",
|
||||
"ScaleX": "100", "ScaleY": "100",
|
||||
"Spacing": "0",
|
||||
"Angle": "0",
|
||||
"PrimaryColour": "&H0000FFFF", "SecondaryColour": "&H00000000", "OutlineColour": "&H00000000", "BackColour": "&H00000000",
|
||||
"Outline": "1", "BorderStyle": "1",
|
||||
"Shadow": "1",
|
||||
"Alignment": "2",
|
||||
"MarginL": "80", "MarginR": "80", "MarginV": "35"
|
||||
});
|
||||
result.styles[newStyle.name] = newStyle;
|
||||
var newStyle = new Style(new Map<string, string>()
|
||||
.set("Name", "Default")
|
||||
.set("Italic", "0").set("Bold", "0").set("Underline", "0").set("StrikeOut", "0")
|
||||
.set("Fontname", "").set("Fontsize", "50")
|
||||
.set("ScaleX", "100").set("ScaleY", "100")
|
||||
.set("Spacing", "0")
|
||||
.set("Angle", "0")
|
||||
.set("PrimaryColour", "&H0000FFFF").set("SecondaryColour", "&H00000000").set("OutlineColour", "&H00000000").set("BackColour", "&H00000000")
|
||||
.set("Outline", "1").set("BorderStyle", "1")
|
||||
.set("Shadow", "1")
|
||||
.set("Alignment", "2")
|
||||
.set("MarginL", "80").set("MarginR", "80").set("MarginV", "35")
|
||||
);
|
||||
result.styles.set(newStyle.name, newStyle);
|
||||
|
||||
script.sort((line1, line2) => line1["number"] - line2["number"]).forEach(line => {
|
||||
result.dialogues.push(new Dialogue({
|
||||
"Style": "Default",
|
||||
"Start": line.start, "End": line.end,
|
||||
"Layer": "0",
|
||||
"Text":
|
||||
result.dialogues.push(new Dialogue(new Map<string, string>()
|
||||
.set("Style", "Default")
|
||||
.set("Start", line.start).set("End", line.end)
|
||||
.set("Layer", "0")
|
||||
.set("Text",
|
||||
(<string>line.text)
|
||||
.replace("<b>", "{\\b1}").replace("</b>", "{\\b0}")
|
||||
.replace("<i>", "{\\i1}").replace("</i>", "{\\i0}")
|
||||
.replace("<u>", "{\\u1}").replace("</u>", "{\\u0}")
|
||||
.replace(/<b>/g, "{\\b1}").replace(/<\/b>/g, "{\\b0}")
|
||||
.replace(/<i>/g, "{\\i1}").replace(/<\/i>/g, "{\\i0}")
|
||||
.replace(/<u>/g, "{\\u1}").replace(/<\/u>/g, "{\\u0}")
|
||||
.replace(
|
||||
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
|
||||
(substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}").replace("<b>", "{\b1}"
|
||||
).replace("</font>", "{\\c}")
|
||||
}, result));
|
||||
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}"
|
||||
).replace(/<\/font>/g, "{\\c}")
|
||||
), result));
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -204,10 +212,6 @@ module libjass {
|
||||
|
||||
/**
|
||||
* This class represents the properties of an ASS script.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @memberof libjass
|
||||
*/
|
||||
export class ScriptProperties {
|
||||
private _resolutionX: number;
|
||||
@@ -296,8 +300,7 @@ module libjass {
|
||||
/**
|
||||
* This class represents a single global style declaration in an ASS script. The styles can be obtained via the ASS.styles property.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Object} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {!Map.<string, string>} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {string} template["Name"] The name of the style
|
||||
* @param {string} template["Italic"] -1 if the style is italicized
|
||||
* @param {string} template["Bold"] -1 if the style is bold
|
||||
@@ -317,8 +320,6 @@ module libjass {
|
||||
* @param {string} template["MarginL"] The left margin
|
||||
* @param {string} template["MarginR"] The right margin
|
||||
* @param {string} template["MarginV"] The vertical margin
|
||||
*
|
||||
* @memberof libjass
|
||||
*/
|
||||
export class Style {
|
||||
private _name: string;
|
||||
@@ -354,39 +355,39 @@ module libjass {
|
||||
private _marginRight: number;
|
||||
private _marginVertical: number;
|
||||
|
||||
constructor(template: Template) {
|
||||
this._name = template["Name"];
|
||||
constructor(template: Map<string, string>) {
|
||||
this._name = template.get("Name");
|
||||
|
||||
this._italic = template["Italic"] === "-1";
|
||||
this._bold = template["Bold"] === "-1";
|
||||
this._underline = template["Underline"] === "-1";
|
||||
this._strikeThrough = template["StrikeOut"] === "-1";
|
||||
this._italic = template.get("Italic") === "-1";
|
||||
this._bold = template.get("Bold") === "-1";
|
||||
this._underline = template.get("Underline") === "-1";
|
||||
this._strikeThrough = template.get("StrikeOut") === "-1";
|
||||
|
||||
this._fontName = template["Fontname"];
|
||||
this._fontSize = parseFloat(template["Fontsize"]);
|
||||
this._fontName = template.get("Fontname");
|
||||
this._fontSize = parseFloat(template.get("Fontsize"));
|
||||
|
||||
this._fontScaleX = parseFloat(template["ScaleX"]) / 100;
|
||||
this._fontScaleY = parseFloat(template["ScaleY"]) / 100;
|
||||
this._fontScaleX = parseFloat(template.get("ScaleX")) / 100;
|
||||
this._fontScaleY = parseFloat(template.get("ScaleY")) / 100;
|
||||
|
||||
this._letterSpacing = parseFloat(template["Spacing"]);
|
||||
this._letterSpacing = parseFloat(template.get("Spacing"));
|
||||
|
||||
this._rotationZ = parseFloat(template["Angle"]);
|
||||
this._rotationZ = parseFloat(template.get("Angle"));
|
||||
|
||||
this._primaryColor = <parts.Color>parser.parse(template["PrimaryColour"], "colorWithAlpha");
|
||||
this._secondaryColor = <parts.Color>parser.parse(template["SecondaryColour"], "colorWithAlpha");
|
||||
this._outlineColor = <parts.Color>parser.parse(template["OutlineColour"], "colorWithAlpha");
|
||||
this._shadowColor = <parts.Color>parser.parse(template["BackColour"], "colorWithAlpha");
|
||||
this._primaryColor = <parts.Color>parser.parse(template.get("PrimaryColour"), "colorWithAlpha");
|
||||
this._secondaryColor = <parts.Color>parser.parse(template.get("SecondaryColour"), "colorWithAlpha");
|
||||
this._outlineColor = <parts.Color>parser.parse(template.get("OutlineColour"), "colorWithAlpha");
|
||||
this._shadowColor = <parts.Color>parser.parse(template.get("BackColour"), "colorWithAlpha");
|
||||
|
||||
this._outlineThickness = parseFloat(template["Outline"]);
|
||||
this._borderStyle = parseInt(template["BorderStyle"]);
|
||||
this._outlineThickness = parseFloat(template.get("Outline"));
|
||||
this._borderStyle = parseInt(template.get("BorderStyle"));
|
||||
|
||||
this._shadowDepth = parseFloat(template["Shadow"]);
|
||||
this._shadowDepth = parseFloat(template.get("Shadow"));
|
||||
|
||||
this._alignment = parseInt(template["Alignment"]);
|
||||
this._alignment = parseInt(template.get("Alignment"));
|
||||
|
||||
this._marginLeft = parseFloat(template["MarginL"]);
|
||||
this._marginRight = parseFloat(template["MarginR"]);
|
||||
this._marginVertical = parseFloat(template["MarginV"]);
|
||||
this._marginLeft = parseFloat(template.get("MarginL"));
|
||||
this._marginRight = parseFloat(template.get("MarginR"));
|
||||
this._marginVertical = parseFloat(template.get("MarginV"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -591,16 +592,13 @@ module libjass {
|
||||
/**
|
||||
* This class represents a dialogue in an ASS script.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!Object} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {!Map.<string, string>} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
|
||||
* @param {string} template["Style"] The name of the default style of this dialogue
|
||||
* @param {string} template["Start"] The start time
|
||||
* @param {string} template["End"] The end time
|
||||
* @param {string} template["Layer"] The layer number
|
||||
* @param {string} template["Text"] The text of this dialogue
|
||||
* @param {ASS} ass The ASS object to which this dialogue belongs
|
||||
*
|
||||
* @memberof libjass
|
||||
*/
|
||||
export class Dialogue {
|
||||
private static _lastDialogueId = -1;
|
||||
@@ -620,17 +618,17 @@ module libjass {
|
||||
|
||||
private _sub: HTMLDivElement = null;
|
||||
|
||||
constructor(template: Template, ass: ASS) {
|
||||
constructor(template: Map<string, string>, ass: ASS) {
|
||||
this._id = ++Dialogue._lastDialogueId;
|
||||
|
||||
this._style = ass.styles[template["Style"]];
|
||||
this._style = ass.styles.get(template.get("Style"));
|
||||
|
||||
this._start = Dialogue._toTime(template["Start"]);
|
||||
this._end = Dialogue._toTime(template["End"]);
|
||||
this._start = Dialogue._toTime(template.get("Start"));
|
||||
this._end = Dialogue._toTime(template.get("End"));
|
||||
|
||||
this._layer = Math.max(parseInt(template["Layer"]), 0);
|
||||
this._layer = Math.max(parseInt(template.get("Layer")), 0);
|
||||
|
||||
this._rawPartsString = template["Text"];
|
||||
this._rawPartsString = template.get("Text");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,8 +706,6 @@ module libjass {
|
||||
|
||||
/**
|
||||
* Parses this dialogue's parts from the raw parts string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _parsePartsString(): void {
|
||||
this._parts = <parts.Part[]>parser.parse(this._rawPartsString, "dialogueParts");
|
||||
@@ -766,9 +762,6 @@ module libjass {
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {number}
|
||||
*
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
private static _toTime(str: string): number {
|
||||
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
|
||||
@@ -776,10 +769,18 @@ module libjass {
|
||||
}
|
||||
|
||||
/**
|
||||
* A template object. It is a map of string keys and string values.
|
||||
* A template object with a particular type.
|
||||
*/
|
||||
export interface Template {
|
||||
[key: string]: string;
|
||||
export interface TypedTemplate {
|
||||
type: string;
|
||||
template: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of TypedTemplate objects with the format specifier that was used to parse them.
|
||||
*/
|
||||
export interface TypedTemplateArray extends Array<TypedTemplate> {
|
||||
formatSpecifier: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+41
-31
@@ -22,20 +22,24 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
interface Global {
|
||||
Set: { new <T>(): Set<T>; prototype: Set<any> }
|
||||
Map: { new <K, V>(): Map<K, V>; prototype: Map<any, any> }
|
||||
}
|
||||
|
||||
declare var global: Global; // Defined as a parameter of the anonymous function wrapper
|
||||
|
||||
module libjass {
|
||||
/**
|
||||
* Set implementation for browsers that don't support it. Only supports Number and String elements.
|
||||
*
|
||||
* Elements are stored as properties of an object, with names derived from their type.
|
||||
*
|
||||
* @constructor
|
||||
* @template T
|
||||
*
|
||||
* @private
|
||||
* @memberof libjass
|
||||
*/
|
||||
class SimpleSet<T> implements Set<T> {
|
||||
private _elements: { [key: string]: T };
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
@@ -52,6 +56,10 @@ module libjass {
|
||||
throw new Error("This Set implementation only supports Number and String values.");
|
||||
}
|
||||
|
||||
if (!(property in this._elements)) {
|
||||
this._size++;
|
||||
}
|
||||
|
||||
this._elements[property] = value;
|
||||
|
||||
return this;
|
||||
@@ -61,6 +69,7 @@ module libjass {
|
||||
*/
|
||||
clear(): void {
|
||||
this._elements = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,8 +100,11 @@ module libjass {
|
||||
throw new Error("This Set implementation doesn't support delete().");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get size(): number {
|
||||
throw new Error("This Set implementation doesn't support size.");
|
||||
return this._size;
|
||||
}
|
||||
|
||||
private _toProperty(value: T): string {
|
||||
@@ -113,16 +125,9 @@ module libjass {
|
||||
*
|
||||
* @type {function(new:Set)}
|
||||
*/
|
||||
export var Set: {
|
||||
new <T>(): Set<T>;
|
||||
} = null;
|
||||
|
||||
// Use this browser's implementation of Set if it has one
|
||||
if (global.Set !== undefined && typeof global.Set.prototype.forEach === "function") {
|
||||
Set = global.Set;
|
||||
}
|
||||
else {
|
||||
Set = SimpleSet;
|
||||
export var Set = global.Set;
|
||||
if (Set === undefined || typeof Set.prototype.forEach !== "function") {
|
||||
Set = <any>SimpleSet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,15 +135,12 @@ module libjass {
|
||||
*
|
||||
* Keys and values are stored as properties of an object, with property names derived from the key type.
|
||||
*
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*
|
||||
* @private
|
||||
* @memberof libjass
|
||||
*/
|
||||
class SimpleMap<K, V> implements Map<K, V> {
|
||||
private _keys: { [key: string]: K };
|
||||
private _values: { [key: string]: V };
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
@@ -184,6 +186,10 @@ module libjass {
|
||||
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
|
||||
}
|
||||
|
||||
if (!(property in this._keys)) {
|
||||
this._size++;
|
||||
}
|
||||
|
||||
this._keys[property] = key;
|
||||
this._values[property] = value;
|
||||
|
||||
@@ -206,6 +212,7 @@ module libjass {
|
||||
if (result) {
|
||||
delete this._keys[property];
|
||||
delete this._values[property];
|
||||
this._size--;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -216,6 +223,7 @@ module libjass {
|
||||
clear(): void {
|
||||
this._keys = Object.create(null);
|
||||
this._values = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,8 +237,11 @@ module libjass {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get size(): number {
|
||||
throw new Error("This Map implementation doesn't support size.");
|
||||
return this._size;
|
||||
}
|
||||
|
||||
private _keyToProperty(key: K): string {
|
||||
@@ -255,17 +266,16 @@ module libjass {
|
||||
*
|
||||
* @type {function(new:Map)}
|
||||
*/
|
||||
export var Map: {
|
||||
new <K, V>(): Map<K, V>;
|
||||
} = null;
|
||||
|
||||
declare var global: any; // Defined as a parameter of the anonymous function wrapper
|
||||
|
||||
// Use this browser's implementation of Map if it has one
|
||||
if (global.Map !== undefined && typeof global.Map.prototype.forEach === "function") {
|
||||
Map = global.Map;
|
||||
}
|
||||
else {
|
||||
export var Map = global.Map;
|
||||
if (Map === undefined || typeof Map.prototype.forEach !== "function") {
|
||||
Map = <any>SimpleMap;
|
||||
}
|
||||
|
||||
export function mixin(derived: any, mixins: any[]) {
|
||||
mixins.forEach((mixin: any) => {
|
||||
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
|
||||
derived.prototype[name] = mixin.prototype[name];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
///<reference path="libjass.ts" />
|
||||
|
||||
interface Document {
|
||||
currentScript: HTMLScriptElement;
|
||||
}
|
||||
|
||||
interface WorkerGlobalScope {
|
||||
postMessage(message: any): void;
|
||||
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
|
||||
}
|
||||
declare var WorkerGlobalScope: {
|
||||
prototype: WorkerGlobalScope;
|
||||
new (): WorkerGlobalScope;
|
||||
};
|
||||
|
||||
module libjass.webworker {
|
||||
Object.defineProperty(webworker, "supported", {
|
||||
value: typeof Worker !== "undefined",
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling libjass.webworker.setup()
|
||||
*/
|
||||
export interface WorkerChannel {
|
||||
/**
|
||||
* Sends a request to the other side to execute the given command with the given parameters.
|
||||
*
|
||||
* @param {number} command
|
||||
* @param {*} parameters
|
||||
* @return {!libjass.webworker.WorkerPromise} A promise that will get resolved when the other side computes the result
|
||||
*/
|
||||
request(command: WorkerCommands, parameters: any): WorkerPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new web worker.
|
||||
*
|
||||
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
|
||||
*/
|
||||
export function createWorker(): WorkerChannel {
|
||||
return new WorkerChannelImpl(new Worker(_scriptNode.src));
|
||||
}
|
||||
|
||||
/**
|
||||
* A promise returned by libjass.webworker.WorkerChannel.request()
|
||||
*/
|
||||
export interface WorkerPromise {
|
||||
resolved: boolean;
|
||||
result: any;
|
||||
|
||||
/**
|
||||
* Registers a callback to run when this promise is resolved. If the promise is already resolved, the callback is called immediately.
|
||||
*
|
||||
* @param {function(*) } callback A function of the form (promise: *): void
|
||||
*/
|
||||
then(callback: WorkerPromiseCallback): void;
|
||||
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a callback called by WorkerPromise when it is resolved.
|
||||
*/
|
||||
export interface WorkerPromiseCallback {
|
||||
(promise: WorkerPromise): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The commands that can be sent to or from a web worker.
|
||||
*/
|
||||
export enum WorkerCommands {
|
||||
Response = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a handler registered to handle a particular WorkerCommand.
|
||||
*/
|
||||
export interface WorkerCommandHandler {
|
||||
(parameters: any, response: WorkerResultCallback): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of a callback called by a WorkerCommandHandler to report its result back to the caller.
|
||||
*/
|
||||
export interface WorkerResultCallback {
|
||||
(error: any, result: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a handler for the given worker command.
|
||||
*
|
||||
* @param {number} command The command that this handler will handle. Valid values are the values of libjass.webworker.WorkerCommands
|
||||
* @param {function(*, function(*, *))} handler The handler. A function of the form (parameters: *, response: function(error: *, result: *): void): void
|
||||
*/
|
||||
export function _registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler) {
|
||||
workerCommands.set(command, handler);
|
||||
}
|
||||
|
||||
export function _registerClassPrototype(prototype: any): void {
|
||||
prototype._classTag = classPrototypes.size;
|
||||
classPrototypes.set(prototype._classTag, prototype);
|
||||
}
|
||||
|
||||
var _scriptNode: HTMLScriptElement = null;
|
||||
if (typeof document !== "undefined" && document.currentScript !== undefined) {
|
||||
_scriptNode = document.currentScript;
|
||||
}
|
||||
|
||||
var workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
|
||||
|
||||
var classPrototypes = new Map<number, any>();
|
||||
|
||||
interface WorkerCommunication {
|
||||
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
|
||||
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
|
||||
postMessage(message: any): void;
|
||||
}
|
||||
|
||||
interface WorkerRequestMessage {
|
||||
requestId: number;
|
||||
command: WorkerCommands;
|
||||
parameters: any;
|
||||
}
|
||||
|
||||
interface WorkerResponseMessage {
|
||||
requestId: number;
|
||||
error: any;
|
||||
result: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of libjass.webworker.WorkerChannel
|
||||
*
|
||||
* @param {!*} comm The other side of the channel. When created by the host, this is the web worker. When created by the web worker, this is its global object.
|
||||
*/
|
||||
class WorkerChannelImpl implements WorkerChannel {
|
||||
private _pendingRequests = new Map<number, WorkerPromiseImpl>();
|
||||
|
||||
constructor(private _comm: WorkerCommunication) {
|
||||
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
|
||||
}
|
||||
|
||||
request(command: WorkerCommands, parameters: any): WorkerPromise {
|
||||
var promise = new WorkerPromiseImpl(this);
|
||||
var requestId = promise.id;
|
||||
this._pendingRequests.set(requestId, promise);
|
||||
|
||||
var requestMessage: WorkerRequestMessage = { requestId: requestId, command: command, parameters: parameters };
|
||||
this._comm.postMessage(WorkerChannelImpl._toJSON(requestMessage));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
cancelRequest(requestId: number): void {
|
||||
this._pendingRequests.delete(requestId);
|
||||
}
|
||||
|
||||
private _respond(message: WorkerResponseMessage): void {
|
||||
this._comm.postMessage(WorkerChannelImpl._toJSON({ command: WorkerCommands.Response, requestId: message.requestId, error: message.error, result: message.result }));
|
||||
}
|
||||
|
||||
private _onMessage(rawMessage: string): void {
|
||||
var message = <{ command: WorkerCommands }>WorkerChannelImpl._fromJSON(rawMessage);
|
||||
|
||||
if (message.command === WorkerCommands.Response) {
|
||||
var responseMessage = <WorkerResponseMessage><any>message;
|
||||
|
||||
var promise = this._pendingRequests.get(responseMessage.requestId);
|
||||
if (promise !== undefined) {
|
||||
this._pendingRequests.delete(responseMessage.requestId);
|
||||
promise.resolve(responseMessage.error, responseMessage.result);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var requestMessage = <WorkerRequestMessage>message;
|
||||
|
||||
var commandCallback = workerCommands.get(requestMessage.command);
|
||||
if (commandCallback === undefined) {
|
||||
this._respond({ requestId: requestMessage.requestId, error: new Error("Unrecognized command: " + requestMessage.command), result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
commandCallback(requestMessage.parameters, (error: any, result: any) => this._respond({ requestId: requestMessage.requestId, error: error, result: result }));
|
||||
}
|
||||
|
||||
private static _toJSON(obj: any): string {
|
||||
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
value._classTag = value._classTag;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
private static _fromJSON(str: string): any {
|
||||
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
|
||||
if (value && value._classTag !== undefined) {
|
||||
var hydratedValue = Object.create(classPrototypes.get(value._classTag));
|
||||
Object.keys(value).forEach(key => {
|
||||
if (key !== "_classTag") {
|
||||
hydratedValue[key] = value[key];
|
||||
}
|
||||
});
|
||||
value = hydratedValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var inWorker = (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope);
|
||||
if (inWorker) {
|
||||
new WorkerChannelImpl(<WorkerGlobalScope><any>global);
|
||||
}
|
||||
|
||||
class WorkerPromiseImpl implements WorkerPromise {
|
||||
private static _lastPromiseId: number = -1;
|
||||
|
||||
private _id: number = ++WorkerPromiseImpl._lastPromiseId;
|
||||
private _resolved: boolean = false;
|
||||
private _result: any = null;
|
||||
private _error: any = null;
|
||||
private _callback: WorkerPromiseCallback = null;
|
||||
|
||||
constructor(private _channel: WorkerChannelImpl) { }
|
||||
|
||||
get resolved(): boolean {
|
||||
return this._resolved;
|
||||
}
|
||||
|
||||
get result(): any {
|
||||
if (!this._resolved) {
|
||||
throw new Error("Unresolved promise.");
|
||||
}
|
||||
|
||||
if (this._error !== null) {
|
||||
throw this._error;
|
||||
}
|
||||
|
||||
return this._result;
|
||||
}
|
||||
|
||||
then(callback: WorkerPromiseCallback): void {
|
||||
this._callback = callback;
|
||||
if (this._resolved) {
|
||||
setTimeout(() => this._callback(this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this._resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._channel.cancelRequest(this._id);
|
||||
}
|
||||
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
resolve(error: any, result: any): void {
|
||||
this._resolved = true;
|
||||
this._error = error;
|
||||
this._result = result;
|
||||
|
||||
if (this._callback !== null) {
|
||||
this._callback(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user