Compare commits
62
Commits
| 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 |
@@ -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.
|
||||
|
||||
+57
-86
@@ -18,64 +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", function (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]); });
|
||||
})).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) {
|
||||
@@ -101,56 +107,21 @@ 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 commandLine = path.resolve("./node_modules/.bin/gulp") + " clean libjass.js test";
|
||||
|
||||
var subProcess = null;
|
||||
var rerun = false;
|
||||
|
||||
var spawnSubProcess = function () {
|
||||
subProcess = childProcess.exec(commandLine);
|
||||
|
||||
subProcess.stdout.pipe(process.stdout);
|
||||
subProcess.stderr.pipe(process.stderr);
|
||||
|
||||
subProcess.addListener("exit", function (code, signal) {
|
||||
subProcess = null;
|
||||
|
||||
if (rerun) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
|
||||
rerun = false;
|
||||
});
|
||||
};
|
||||
|
||||
gulp.watch(["./*.ts", "./tests/*.js"], { debounceDelay: 1 }, function () {
|
||||
if (subProcess === null) {
|
||||
spawnSubProcess();
|
||||
}
|
||||
else {
|
||||
rerun = true;
|
||||
}
|
||||
gulp.watch(["./tests/test-*.js"], { debounceDelay: 1 }, function () {
|
||||
gulpTestProcess();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+70
-12
@@ -38,13 +38,17 @@ Array.prototype.concatMany = function (arr) {
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return Transform(function (file) {
|
||||
// Parse
|
||||
var compiler = new TypeScript.Compiler();
|
||||
|
||||
var compiler = new TypeScript.Compiler();
|
||||
var resolvedFiles = compiler.addFiles([file.path]);
|
||||
var filenames = [];
|
||||
var allFiles = [];
|
||||
|
||||
var namespaces = TypeScript.AST.walk(compiler, resolvedFiles);
|
||||
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 sorter = (function () {
|
||||
@@ -61,7 +65,7 @@ module.exports = function () {
|
||||
return 0;
|
||||
};
|
||||
|
||||
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.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;
|
||||
@@ -106,9 +110,9 @@ module.exports = function () {
|
||||
namespaces[namespaceName].members.sort(sorter);
|
||||
|
||||
namespaces[namespaceName].members.filter(function (value) {
|
||||
return value instanceof TypeScript.AST.Constructor;
|
||||
}).forEach(function (constructor) {
|
||||
constructor.members.sort(sorter);
|
||||
return value instanceof TypeScript.AST.Interface || value instanceof TypeScript.AST.Constructor;
|
||||
}).forEach(function (interfaceOrConstructor) {
|
||||
interfaceOrConstructor.members.sort(sorter);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,6 +164,35 @@ 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' +
|
||||
@@ -221,6 +254,14 @@ 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 ' +
|
||||
@@ -455,16 +496,16 @@ module.exports = function () {
|
||||
' border-bottom: 1px solid black;',
|
||||
' }',
|
||||
'',
|
||||
' .variable, .function, .constructor, .getter, .setter {',
|
||||
' .variable, .function, .interface, .constructor, .getter, .setter {',
|
||||
' margin-left: 30px;',
|
||||
' padding: 10px;',
|
||||
' }',
|
||||
'',
|
||||
' section > .variable:nth-child(2n), 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 > .variable:nth-child(2n + 1), 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);',
|
||||
' }',
|
||||
'',
|
||||
@@ -485,6 +526,7 @@ module.exports = function () {
|
||||
' margin: 0;',
|
||||
' }',
|
||||
'',
|
||||
' .interface .variable, .interface .function, .interface .getter, .interface .setter,',
|
||||
' .constructor .variable, .constructor .function, .constructor .getter, .constructor .setter {',
|
||||
' background-color: rgb(250, 241, 221);',
|
||||
' }',
|
||||
@@ -557,6 +599,10 @@ module.exports = function () {
|
||||
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;
|
||||
});
|
||||
@@ -591,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>',
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
});
|
||||
+246
-63
@@ -53,48 +53,70 @@ innerCompilerSettings = TypeScript.ImmutableCompilationSettings.fromCompilationS
|
||||
|
||||
var Compiler = function () {
|
||||
function Compiler() {
|
||||
this._diagnostics = [];
|
||||
|
||||
this._scriptSnapshots = Object.create(null);
|
||||
};
|
||||
|
||||
Compiler.prototype.addFiles = function (files) {
|
||||
var _this = this;
|
||||
|
||||
var resolutionResults = TypeScript.ReferenceResolver.resolve(files, {
|
||||
resolveRelativePath: function (file, directory) {
|
||||
return path.resolve(directory, file);
|
||||
},
|
||||
fileExists: fs.existsSync.bind(fs),
|
||||
getScriptSnapshot: this._getScriptSnapshot.bind(this),
|
||||
getParentDirectory: path.dirname.bind(path)
|
||||
}, true);
|
||||
|
||||
resolutionResults.resolvedFiles.unshift({
|
||||
path: path.resolve(typeScriptModulePath, "lib.d.ts"),
|
||||
referencedFiles: [],
|
||||
importedFiles: []
|
||||
});
|
||||
|
||||
this._addDiagnostics(resolutionResults.diagnostics);
|
||||
|
||||
this._innerCompiler = new TypeScript.TypeScriptCompiler(new TypeScript.NullLogger(), innerCompilerSettings);
|
||||
|
||||
resolutionResults.resolvedFiles.forEach(function (resolvedFile) {
|
||||
_this._innerCompiler.addFile(resolvedFile.path, _this._getScriptSnapshot(resolvedFile.path), 0, 0, false, resolvedFile.referencedFiles);
|
||||
});
|
||||
this.addFile(path.resolve(typeScriptModulePath, "lib.d.ts"));
|
||||
};
|
||||
|
||||
return resolutionResults.resolvedFiles;
|
||||
Compiler.prototype.addFile = function (file) {
|
||||
var _this = this;
|
||||
|
||||
var filename = file;
|
||||
if (file.constructor !== String) {
|
||||
filename = file.path;
|
||||
}
|
||||
|
||||
var newFiles = [];
|
||||
|
||||
if (this._innerCompiler.getDocument(filename) === null) {
|
||||
// This is a new file. Add all the resolutionResults that are new files (including this one).
|
||||
|
||||
var resolutionResults = this._resolve(filename);
|
||||
|
||||
resolutionResults.resolvedFiles.forEach(function (resolvedFile) {
|
||||
if (_this._innerCompiler.getDocument(resolvedFile.path) === null) {
|
||||
|
||||
_this._innerCompiler.addFile(resolvedFile.path, _this._getScriptSnapshot(resolvedFile.path), 0, 0, false, resolvedFile.referencedFiles);
|
||||
|
||||
newFiles.push(resolvedFile.path);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// This is an existing file that's being updated.
|
||||
|
||||
this._innerCompiler.updateFile(filename, this._newScriptSnapshot(filename), 0, false, null);
|
||||
|
||||
var resolutionResults = this._resolve(filename);
|
||||
|
||||
resolutionResults.resolvedFiles.forEach(function (resolvedFile) {
|
||||
if (_this._innerCompiler.getDocument(resolvedFile.path) === null) {
|
||||
_this._innerCompiler.addFile(resolvedFile.path, _this._getScriptSnapshot(resolvedFile.path), 0, 0, false, resolvedFile.referencedFiles);
|
||||
|
||||
newFiles.push(resolvedFile.path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return newFiles;
|
||||
};
|
||||
|
||||
Compiler.prototype.removeFile = function (filename) {
|
||||
this._innerCompiler.removeFile(filename);
|
||||
};
|
||||
|
||||
Compiler.prototype.compile = function (outputCodePath, outputSourceMapPath) {
|
||||
var iterator = this._innerCompiler.compile(path.resolve.bind(path));
|
||||
var outputFiles = [];
|
||||
|
||||
var error = false;
|
||||
|
||||
while (iterator.moveNext()) {
|
||||
var result = iterator.current();
|
||||
|
||||
this._addDiagnostics(result.diagnostics);
|
||||
error = error || this._writeDiagnostics(result.diagnostics);
|
||||
|
||||
result.outputFiles.forEach(function (outputFile) {
|
||||
var outputPath = null;
|
||||
@@ -120,19 +142,36 @@ var Compiler = function () {
|
||||
});
|
||||
}
|
||||
|
||||
if (this._diagnostics.some(function (diagnostic) { return diagnostic.info().category === 1; })) {
|
||||
if (error) {
|
||||
throw new Error("There were one or more errors.");
|
||||
}
|
||||
|
||||
return outputFiles;
|
||||
};
|
||||
|
||||
Compiler.prototype.getDocument = function (resolvedFilePath) {
|
||||
return this._innerCompiler.getDocument(resolvedFilePath);
|
||||
Compiler.prototype.getDocument = function (filename) {
|
||||
return this._innerCompiler.getDocument(filename);
|
||||
};
|
||||
|
||||
Compiler.prototype._addDiagnostics = function (newDiagnostics) {
|
||||
newDiagnostics.forEach(function (diagnostic) {
|
||||
Compiler.prototype._resolve = function (filename) {
|
||||
var resolutionResults = TypeScript.ReferenceResolver.resolve([filename], {
|
||||
resolveRelativePath: function (file, directory) {
|
||||
return path.resolve(directory, file);
|
||||
},
|
||||
fileExists: fs.existsSync.bind(fs),
|
||||
getScriptSnapshot: this._getScriptSnapshot.bind(this),
|
||||
getParentDirectory: path.dirname.bind(path)
|
||||
}, true);
|
||||
|
||||
if (this._writeDiagnostics(resolutionResults.diagnostics)) {
|
||||
throw new Error("File resolution failed for " + JSON.stringify(filename));
|
||||
}
|
||||
|
||||
return resolutionResults;
|
||||
};
|
||||
|
||||
Compiler.prototype._writeDiagnostics = function (diagnostics) {
|
||||
diagnostics.forEach(function (diagnostic) {
|
||||
var message = diagnostic.message();
|
||||
|
||||
if (diagnostic.fileName()) {
|
||||
@@ -142,27 +181,23 @@ var Compiler = function () {
|
||||
console.error(message);
|
||||
});
|
||||
|
||||
this._diagnostics.push.apply(this._diagnostics, newDiagnostics);
|
||||
return diagnostics.some(function (diagnostic) { return diagnostic.info().category === TypeScript.DiagnosticCategory.Error; });
|
||||
};
|
||||
|
||||
Compiler.prototype._getScriptSnapshot = function (filename) {
|
||||
var scriptSnapshot = this._scriptSnapshots[filename];
|
||||
|
||||
if (!scriptSnapshot) {
|
||||
var fileContents;
|
||||
|
||||
try {
|
||||
fileContents = fs.readFileSync(filename, { encoding: "utf8" });
|
||||
}
|
||||
catch (ex) {
|
||||
this._addDiagnostics([new TypeScript.Diagnostic(null, null, 0, 0, TypeScript.DiagnosticCode.Cannot_read_file_0_1, [filename, ex.message])]);
|
||||
fileContents = "";
|
||||
}
|
||||
|
||||
scriptSnapshot = this._scriptSnapshots[filename] = TypeScript.ScriptSnapshot.fromString(fileContents);
|
||||
if (scriptSnapshot) {
|
||||
return scriptSnapshot;
|
||||
}
|
||||
|
||||
return scriptSnapshot;
|
||||
return this._newScriptSnapshot(filename);
|
||||
};
|
||||
|
||||
Compiler.prototype._newScriptSnapshot = function (filename) {
|
||||
var fileContents = fs.readFileSync(filename, { encoding: "utf8" });
|
||||
|
||||
return this._scriptSnapshots[filename] = TypeScript.ScriptSnapshot.fromString(fileContents);
|
||||
};
|
||||
|
||||
return Compiler;
|
||||
@@ -170,23 +205,27 @@ var Compiler = function () {
|
||||
|
||||
exports.Compiler = Compiler;
|
||||
|
||||
function flatten(arrays) {
|
||||
return arrays.reduce(function (previous, current) { return previous.concat(current); }, []);
|
||||
}
|
||||
|
||||
exports.gulp = function (outputCodePath, outputSourceMapPath, astModifier) {
|
||||
var files = [];
|
||||
var compiler = new Compiler();
|
||||
|
||||
var filenames = [];
|
||||
var allFiles = [];
|
||||
|
||||
return Transform(function (file, encoding) {
|
||||
files.push(file.path);
|
||||
filenames.push(file.path);
|
||||
allFiles.push.apply(allFiles, compiler.addFile(file));
|
||||
}, function () {
|
||||
var _this = this;
|
||||
|
||||
try {
|
||||
console.log("Compiling " + JSON.stringify(files) + "...");
|
||||
|
||||
var compiler = new Compiler();
|
||||
|
||||
var resolvedFiles = compiler.addFiles(files);
|
||||
console.log("Compiling " + JSON.stringify(filenames) + "...");
|
||||
|
||||
if (astModifier !== undefined) {
|
||||
astModifier(exports.AST.walk(compiler, resolvedFiles));
|
||||
astModifier(exports.AST.walk(compiler, allFiles));
|
||||
}
|
||||
|
||||
var outputFiles = compiler.compile(outputCodePath, outputSourceMapPath);
|
||||
@@ -208,7 +247,91 @@ exports.gulp = function (outputCodePath, outputSourceMapPath, astModifier) {
|
||||
});
|
||||
};
|
||||
|
||||
var __extends = function(d, b, m) {
|
||||
exports.watch = function (outputCodePath, outputSourceMapPath) {
|
||||
var compiler = new Compiler();
|
||||
|
||||
var filenames = [];
|
||||
var allFiles = [];
|
||||
|
||||
return Transform(function (file) {
|
||||
filenames.push(file.path);
|
||||
allFiles.push.apply(allFiles, compiler.addFile(file));
|
||||
}, function (callback) {
|
||||
var _this = this;
|
||||
|
||||
var outputFiles = compiler.compile(outputCodePath, outputSourceMapPath);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
|
||||
console.log("Listening for changes...");
|
||||
|
||||
allFiles.forEach(function (filename) {
|
||||
watchFile(filename, fileChangedCallback, fileDeletedCallback);
|
||||
});
|
||||
|
||||
function fileChangedCallback(filename) {
|
||||
try {
|
||||
compiler.addFile(filename).forEach(function (changed) {
|
||||
if (changed !== filename) {
|
||||
watchFile(changed, fileChangedCallback, fileDeletedCallback);
|
||||
}
|
||||
});
|
||||
|
||||
compile();
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Compile failed." + ex.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function fileDeletedCallback(filename) {
|
||||
try {
|
||||
compiler.removeFile(filename);
|
||||
compile();
|
||||
}
|
||||
catch (ex) {
|
||||
console.error("Compile failed." + ex.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function compile() {
|
||||
console.log("Compiling " + JSON.stringify(filenames) + "...");
|
||||
|
||||
var outputFiles = compiler.compile(outputCodePath, outputSourceMapPath);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
|
||||
outputFiles.forEach(function (file) {
|
||||
_this.push(file);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var watchFile = function (filename, onChange, onDelete) {
|
||||
var alreadyCalled = false;
|
||||
|
||||
function watchFileCallback(currentFile, previousFile) {
|
||||
if (currentFile.mtime >= previousFile.mtime) {
|
||||
if (alreadyCalled) { return; }
|
||||
|
||||
alreadyCalled = true;
|
||||
|
||||
setTimeout(function () { alreadyCalled = false; }, 100);
|
||||
|
||||
onChange(filename);
|
||||
}
|
||||
else {
|
||||
fs.unwatchFile(filename, watchFileCallback);
|
||||
|
||||
onDelete(filename);
|
||||
}
|
||||
}
|
||||
|
||||
fs.watchFile(filename, { interval: 500 }, watchFileCallback);
|
||||
};
|
||||
|
||||
var __extends = function(d, b) {
|
||||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
|
||||
function __() {
|
||||
this.constructor = d;
|
||||
@@ -275,6 +398,28 @@ var Function = function (_super) {
|
||||
return Function;
|
||||
}(Scoped);
|
||||
|
||||
var Interface = function (_super) {
|
||||
__extends(Interface, _super);
|
||||
|
||||
function Interface(name, astNode, description, generics, baseType, isPrivate) {
|
||||
_super.call(this, name);
|
||||
|
||||
this.astNode = astNode;
|
||||
|
||||
this.description = description;
|
||||
|
||||
this.generics = generics;
|
||||
|
||||
this.baseType = baseType;
|
||||
|
||||
this.isPrivate = isPrivate;
|
||||
|
||||
this.members = [];
|
||||
};
|
||||
|
||||
return Interface;
|
||||
}(Scoped);
|
||||
|
||||
var Constructor = function (_super) {
|
||||
__extends(Constructor, _super);
|
||||
|
||||
@@ -383,6 +528,7 @@ var NodeType = function () {
|
||||
NodeType[NodeType["Getter"] = 2] = "Getter";
|
||||
NodeType[NodeType["Setter"] = 3] = "Setter";
|
||||
NodeType[NodeType["Variable"] = 4] = "Variable";
|
||||
NodeType[NodeType["Interface"] = 5] = "Interface";
|
||||
|
||||
return NodeType;
|
||||
}();
|
||||
@@ -451,6 +597,36 @@ var Walker = function (_super) {
|
||||
});
|
||||
};
|
||||
|
||||
Walker.prototype.visitInterfaceDeclaration = function (node) {
|
||||
var name = node.identifier.text();
|
||||
|
||||
var docNode = node.modifiers.item || node.interfaceKeyword;
|
||||
var doc = this._parseJSDoc(docNode, NodeType.Interface);
|
||||
if (doc === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var extendsClause = node.heritageClauses.toArray().filter(function (heritageClause) {
|
||||
return heritageClause.extendsOrImplementsKeyword.tokenKind === TypeScript.SyntaxKind.ExtendsKeyword;
|
||||
})[0] || null;
|
||||
var baseType = null;
|
||||
if (extendsClause !== null) {
|
||||
baseType = extendsClause.typeNames.item.fullText().trim();
|
||||
}
|
||||
var modifiers = ((node.modifiers.item ? [node.modifiers.item] : node.modifiers.nodeOrTokens) || []).map(function (modifier) { return modifier.tokenKind; });
|
||||
var isPrivate = !modifiers.some(function (modifier) { return modifier === TypeScript.SyntaxKind["ExportKeyword"]; });
|
||||
|
||||
var interface = this._scope.enter(new Interface(name, node, doc.rootDescription, doc.generics, baseType, isPrivate));
|
||||
|
||||
if (interface.parent !== null) {
|
||||
interface.parent.members.push(interface);
|
||||
}
|
||||
|
||||
_super.prototype.visitInterfaceDeclaration.call(this, node);
|
||||
|
||||
this._scope.leave();
|
||||
};
|
||||
|
||||
Walker.prototype.visitClassDeclaration = function (node) {
|
||||
var name = node.identifier.text();
|
||||
|
||||
@@ -472,7 +648,9 @@ var Walker = function (_super) {
|
||||
|
||||
var clazz = this._scope.enter(new Constructor(name, node, doc.rootDescription, doc.generics, doc.parameters, baseType, doc.isAbstract, isPrivate));
|
||||
|
||||
clazz.parent.members.push(clazz);
|
||||
if (clazz.parent !== null) {
|
||||
clazz.parent.members.push(clazz);
|
||||
}
|
||||
|
||||
_super.prototype.visitClassDeclaration.call(this, node);
|
||||
|
||||
@@ -514,7 +692,9 @@ var Walker = function (_super) {
|
||||
|
||||
var freeFunction = this._scope.enter(new Function(name, node, doc.rootDescription, doc.generics, doc.parameters, doc.returnType, doc.isAbstract, isPrivate, isStatic));
|
||||
|
||||
freeFunction.parent.members.push(freeFunction);
|
||||
if (freeFunction.parent !== null) {
|
||||
freeFunction.parent.members.push(freeFunction);
|
||||
}
|
||||
|
||||
this._scope.leave();
|
||||
};
|
||||
@@ -671,7 +851,7 @@ var Walker = function (_super) {
|
||||
return [type, remainingLine];
|
||||
};
|
||||
|
||||
var firstWordMatch = line.match(/^(\S+)(\s*)/);
|
||||
var firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
|
||||
var firstWord = firstWordMatch[1];
|
||||
var remainingLine = line.substring(firstWordMatch[0].length);
|
||||
|
||||
@@ -761,19 +941,19 @@ var Walker = function (_super) {
|
||||
return Walker;
|
||||
}(TypeScript.PositionTrackingWalker);
|
||||
|
||||
var walk = function (compiler, resolvedFiles) {
|
||||
var walk = function (compiler, resolvedFilenames) {
|
||||
var walker = new Walker();
|
||||
|
||||
// Walk
|
||||
resolvedFiles.slice(1).forEach(function (resolvedFile) {
|
||||
var document = compiler.getDocument(resolvedFile.path);
|
||||
resolvedFilenames.forEach(function (resolvedFilename) {
|
||||
var document = compiler.getDocument(resolvedFilename);
|
||||
var sourceUnit = document.syntaxTree().sourceUnit();
|
||||
sourceUnit.accept(walker);
|
||||
});
|
||||
|
||||
// Link base types
|
||||
var linkVisitor = function (current) {
|
||||
if (current instanceof Constructor) {
|
||||
if (current instanceof Interface || current instanceof Constructor) {
|
||||
if (current.baseType !== null && current.baseType.constructor === String) {
|
||||
var existingBaseType = null;
|
||||
for (var ns = current.parent; existingBaseType === null; ns = ns.parent) {
|
||||
@@ -798,6 +978,7 @@ var walk = function (compiler, resolvedFiles) {
|
||||
|
||||
if (existingBaseType === null) {
|
||||
console.warn("Base type [" + current.baseType + "] of type [" + current.fullName + "] not found.");
|
||||
current.baseType = { name: current.baseType, fullName: current.baseType };
|
||||
}
|
||||
else {
|
||||
current.baseType = existingBaseType;
|
||||
@@ -817,6 +998,7 @@ var walk = function (compiler, resolvedFiles) {
|
||||
exports.AST = {
|
||||
Namespace: Namespace,
|
||||
Function: Function,
|
||||
Interface: Interface,
|
||||
Constructor: Constructor,
|
||||
Property: Property,
|
||||
Getter: Getter,
|
||||
@@ -863,6 +1045,7 @@ TypeScript.Emitter.prototype.emitComments = function (ast, pre, onlyPinnedOrTrip
|
||||
ast["gulp-typescript-new-comment"].forEach(function (line) {
|
||||
fullText += "* " + line + "\n";
|
||||
});
|
||||
ast["gulp-typescript-new-comment"] = undefined;
|
||||
|
||||
trivia._fullText = fullText + originalFullText.substr(commentEndIndex);
|
||||
trivia._fullWidth = trivia._fullText.length;
|
||||
|
||||
+242
-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="), 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;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -198,142 +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")
|
||||
) {
|
||||
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");
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
var generatedIdentifiers = occurrences;
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
|
||||
var toIdentifier = function (index) {
|
||||
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
|
||||
output.source_map.get()._sources.toArray().forEach(function (filename) {
|
||||
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
|
||||
});
|
||||
while (index > 0) {
|
||||
index--;
|
||||
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
}
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
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>
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
///<reference path="utility.ts" />
|
||||
///<reference path="web-worker.ts" />
|
||||
///<reference path="parts.ts" />
|
||||
///<reference path="parser.ts" />
|
||||
///<reference path="renderers-clocks.ts" />
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libjass",
|
||||
"version": "0.7.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",
|
||||
@@ -21,13 +21,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "gulp clean default",
|
||||
"test": "gulp test"
|
||||
"test": "node ./gulplib/test-runner.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"gulp": "latest",
|
||||
"mocha": "1.18.x",
|
||||
"mocha": "1.21.x",
|
||||
"typescript": "1.0.1",
|
||||
"uglify-js": "2.x >=2.4.13",
|
||||
"uglify-js": "2.x >=2.4.14",
|
||||
"vinyl": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ module libjass.parser {
|
||||
/**
|
||||
* Parses a given string with the specified rule.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} rule
|
||||
* @return {*}
|
||||
* @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) {
|
||||
@@ -49,10 +50,17 @@ module libjass.parser {
|
||||
* @param {string} rule
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -70,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();
|
||||
@@ -94,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;
|
||||
}
|
||||
@@ -115,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +205,8 @@ module libjass.parser {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.read(current, "\n");
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
@@ -199,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;
|
||||
@@ -231,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;
|
||||
}
|
||||
@@ -254,6 +298,8 @@ module libjass.parser {
|
||||
valueNode.value += next;
|
||||
}
|
||||
|
||||
this.read(current, "\n");
|
||||
|
||||
current.value = valueNode.value;
|
||||
|
||||
return current;
|
||||
@@ -276,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,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) ||
|
||||
@@ -923,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}
|
||||
@@ -1377,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) ||
|
||||
@@ -2073,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;
|
||||
@@ -2096,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;
|
||||
@@ -2119,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;
|
||||
@@ -2161,6 +2249,9 @@ module libjass.parser {
|
||||
* @param {number=1} count
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2271,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 {
|
||||
@@ -2329,29 +2420,25 @@ module libjass.parser {
|
||||
/**
|
||||
* This class represents a single parse node. It has a start and end position, and an optional value object.
|
||||
*
|
||||
* @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.
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2405,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);
|
||||
}
|
||||
}
|
||||
@@ -2438,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -393,6 +393,42 @@ 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}
|
||||
*
|
||||
@@ -1388,6 +1424,7 @@ module libjass.parts {
|
||||
var value = (<any>libjass.parts)[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, key);
|
||||
libjass.webworker._registerClassPrototype(value.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1395,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+228
-115
@@ -61,8 +61,9 @@ module libjass.renderers {
|
||||
|
||||
export enum ClockEvent {
|
||||
Play,
|
||||
Tick,
|
||||
Pause,
|
||||
TimeUpdate,
|
||||
Stop,
|
||||
}
|
||||
|
||||
export interface Clock extends EventSource<ClockEvent> {
|
||||
@@ -78,22 +79,33 @@ module libjass.renderers {
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): void;
|
||||
enable(): boolean;
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): void;
|
||||
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;
|
||||
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,91 +113,40 @@ module libjass.renderers {
|
||||
*/
|
||||
export class ManualClock implements Clock {
|
||||
private _currentTime: number = -1;
|
||||
private _enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Trigger a play event.
|
||||
* Trigger a ClockEvent.Play
|
||||
*/
|
||||
play(): void {
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a pause event.
|
||||
* 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 timeUpdate event with the given current time.
|
||||
*
|
||||
* @param {number} currentTime
|
||||
* Trigger a ClockEvent.Stop
|
||||
*/
|
||||
timeUpdate(currentTime: number): void {
|
||||
this._currentTime = currentTime;
|
||||
this._dispatchEvent(ClockEvent.TimeUpdate, []);
|
||||
stop(): void {
|
||||
this._dispatchEvent(ClockEvent.Stop, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get currentTime(): number {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the clock. This is a no-op for this type.
|
||||
*/
|
||||
enable(): void { }
|
||||
|
||||
/**
|
||||
* Disable the clock. This is a no-op for this type.
|
||||
*/
|
||||
disable(): void { }
|
||||
|
||||
/**
|
||||
* Toggle the clock. This is a no-op for this type.
|
||||
*/
|
||||
toggle(): void { }
|
||||
|
||||
// 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 VideoClockState {
|
||||
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;
|
||||
|
||||
private _state: VideoClockState;
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
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}
|
||||
@@ -203,28 +164,32 @@ module libjass.renderers {
|
||||
|
||||
/**
|
||||
* Enable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
|
||||
*/
|
||||
enable(): void {
|
||||
enable(): boolean {
|
||||
if (this._enabled) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
this._onVideoPlaying();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the clock.
|
||||
*
|
||||
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
|
||||
*/
|
||||
disable(): void {
|
||||
disable(): boolean {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._onVideoPause();
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,22 +204,153 @@ module libjass.renderers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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._state === VideoClockState.Playing) {
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = VideoClockState.Playing;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
|
||||
if (this._nextAnimationFrameRequestId === null) {
|
||||
this._timerTick();
|
||||
}
|
||||
this._startTicking();
|
||||
}
|
||||
|
||||
private _onVideoPause(): void {
|
||||
@@ -262,7 +358,11 @@ module libjass.renderers {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = VideoClockState.Paused;
|
||||
if (this._videoState === VideoState.Paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._videoState = VideoState.Paused;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
|
||||
@@ -270,14 +370,11 @@ module libjass.renderers {
|
||||
if (libjass.debugMode) {
|
||||
console.warn("VideoClock._onVideoPause: Abnormal state detected. VideoClock._nextAnimationFrameRequestId should not have been null");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cancelAnimationFrame(this._nextAnimationFrameRequestId);
|
||||
this._nextAnimationFrameRequestId = null;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("VideoClock._onVideoPause: Cancelled VideoClock._nextAnimationFrameRequestId");
|
||||
}
|
||||
this._stopTicking();
|
||||
}
|
||||
|
||||
private _onVideoSeeking(): void {
|
||||
@@ -285,42 +382,58 @@ module libjass.renderers {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentTime === this._video.currentTime) {
|
||||
return;
|
||||
if (this._videoState === VideoState.Playing) {
|
||||
this._videoState = VideoState.Paused;
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
}
|
||||
|
||||
if (this._state !== VideoClockState.Paused) {
|
||||
if (this._currentTime === this._video.currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTime = this._video.currentTime;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
this._dispatchEvent(ClockEvent.TimeUpdate, []);
|
||||
this._dispatchEvent(ClockEvent.Pause, []);
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
|
||||
private _timerTick(): void {
|
||||
if (this._currentTime !== this._video.currentTime) {
|
||||
this._currentTime = this._video.currentTime;
|
||||
|
||||
if (this._state !== VideoClockState.Playing) {
|
||||
this._state = VideoClockState.Playing;
|
||||
|
||||
this._dispatchEvent(ClockEvent.Play, []);
|
||||
}
|
||||
|
||||
this._dispatchEvent(ClockEvent.TimeUpdate, []);
|
||||
private _onTimerTick(): void {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (this._state !== VideoClockState.Paused) {
|
||||
this._state = VideoClockState.Paused;
|
||||
|
||||
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._timerTick());
|
||||
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
|
||||
|
||||
+81
-17
@@ -41,8 +41,9 @@ module libjass.renderers {
|
||||
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.TimeUpdate, () => this._onClockTimeUpdate());
|
||||
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +91,49 @@ module libjass.renderers {
|
||||
draw(dialogue: Dialogue): void { }
|
||||
|
||||
/**
|
||||
* Runs when the clock starts playing, or is resumed from pause.
|
||||
* 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) {
|
||||
@@ -98,23 +141,14 @@ module libjass.renderers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock is paused.
|
||||
*/
|
||||
_onClockPause(): void {
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockPause");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
|
||||
*/
|
||||
_onClockTimeUpdate(): void {
|
||||
var currentTime = this.clock.currentTime;
|
||||
_onClockTick(): void {
|
||||
var currentTime = this._clock.currentTime;
|
||||
|
||||
if (libjass.verboseMode) {
|
||||
console.log("NullRenderer._onClockTimeUpdate: currentTime = " + currentTime);
|
||||
console.log("NullRenderer._onClockTick: currentTime = " + currentTime);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._ass.dialogues.length; i++) {
|
||||
@@ -132,6 +166,24 @@ module libjass.renderers {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,12 +200,23 @@ module libjass.renderers {
|
||||
fontMap: Map<string, string[]>;
|
||||
|
||||
/**
|
||||
* Subtitles will be pre-rendered for this amount of time (seconds)
|
||||
* 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.
|
||||
*
|
||||
@@ -199,13 +262,14 @@ module libjass.renderers {
|
||||
* @return {!libjass.renderers.RendererSettings}
|
||||
*/
|
||||
static from(object: any): RendererSettings {
|
||||
return RendererSettings._from(object.fontMap, object.preRenderTime);
|
||||
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines);
|
||||
}
|
||||
|
||||
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5): RendererSettings {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+232
-178
@@ -46,13 +46,11 @@ module libjass.renderers {
|
||||
private _svgDefsElement: SVGDefsElement = null;
|
||||
|
||||
private _currentSubs: Map<Dialogue, HTMLDivElement> = new Map<Dialogue, HTMLDivElement>();
|
||||
private _preRenderedSubs: Map<number, HTMLDivElement> = new Map<number, HTMLDivElement>();
|
||||
private _preRenderedSubs: Map<number, { sub: HTMLDivElement; animationDelays: number[] }> = new Map<number, { sub: HTMLDivElement; animationDelays: number[] }>();
|
||||
|
||||
private _scaleX: number;
|
||||
private _scaleY: number;
|
||||
|
||||
private _enabled: boolean = true;
|
||||
|
||||
constructor(ass: ASS, clock: Clock, settings: RendererSettings, private _libjassSubsWrapper: HTMLDivElement) {
|
||||
super(ass, clock, settings);
|
||||
|
||||
@@ -84,44 +82,41 @@ module libjass.renderers {
|
||||
else {
|
||||
// Preload fonts
|
||||
|
||||
var urlsToPreload: string[] = [];
|
||||
this.settings.fontMap.forEach((src: string[], name: string) => {
|
||||
urlsToPreload.unshift.apply(urlsToPreload, src);
|
||||
var urlsToPreload = new Set<string>();
|
||||
this.settings.fontMap.forEach(srcs => {
|
||||
srcs.forEach(src => urlsToPreload.add(src));
|
||||
});
|
||||
|
||||
var urlsLeftToPreload = urlsToPreload.length;
|
||||
var urlsLeftToPreload = urlsToPreload.size;
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.log("Preloading fonts...");
|
||||
}
|
||||
|
||||
urlsToPreload.forEach((url: string) => {
|
||||
urlsToPreload.forEach(url => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open("GET", url, true);
|
||||
xhr.addEventListener("readystatechange", () => {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
xhr.addEventListener("load", () => {
|
||||
if (libjass.debugMode) {
|
||||
console.log("Preloaded " + url + ".");
|
||||
}
|
||||
|
||||
--urlsLeftToPreload;
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.log(urlsLeftToPreload + " fonts left to preload.");
|
||||
}
|
||||
|
||||
if (urlsLeftToPreload === 0) {
|
||||
if (libjass.debugMode) {
|
||||
console.log("Preloaded " + url + ".");
|
||||
console.log("All fonts have been preloaded.");
|
||||
}
|
||||
|
||||
--urlsLeftToPreload;
|
||||
|
||||
if (libjass.debugMode) {
|
||||
console.log(urlsLeftToPreload + " fonts left to preload.");
|
||||
}
|
||||
|
||||
if (urlsLeftToPreload === 0) {
|
||||
if (libjass.debugMode) {
|
||||
console.log("All fonts have been preloaded.");
|
||||
}
|
||||
|
||||
this._ready();
|
||||
}
|
||||
this._ready();
|
||||
}
|
||||
}, false);
|
||||
xhr.send(null);
|
||||
return xhr;
|
||||
});
|
||||
|
||||
if (libjass.debugMode) {
|
||||
@@ -144,13 +139,6 @@ module libjass.renderers {
|
||||
return this._libjassSubsWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the subtitles to the given new dimensions.
|
||||
*
|
||||
@@ -186,32 +174,7 @@ module libjass.renderers {
|
||||
|
||||
// this.currentTime will be -1 if resize() is called before the clock begins playing for the first time. In this situation, there is no need to force a redraw.
|
||||
if (this.clock.currentTime !== -1) {
|
||||
this._onClockTimeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
enable(): void {
|
||||
this.clock.enable();
|
||||
|
||||
this._subsWrapper.style.display = "";
|
||||
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
disable(): void {
|
||||
this.clock.disable();
|
||||
|
||||
this._subsWrapper.style.display = "none";
|
||||
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
if (this._enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
this._onClockTick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,20 +193,41 @@ module libjass.renderers {
|
||||
sub.style.marginLeft = (this._scaleX * dialogue.style.marginLeft).toFixed(3) + "px";
|
||||
sub.style.marginRight = (this._scaleX * dialogue.style.marginRight).toFixed(3) + "px";
|
||||
sub.style.marginTop = sub.style.marginBottom = (this._scaleY * dialogue.style.marginVertical).toFixed(3) + "px";
|
||||
sub.style.minWidth = (this._subsWrapper.offsetWidth - 2 * (this._scaleX * dialogue.style.marginLeft)).toFixed(3) + "px";
|
||||
sub.style.minWidth = (this._subsWrapper.offsetWidth - this._scaleX * (dialogue.style.marginLeft + dialogue.style.marginRight)).toFixed(3) + "px";
|
||||
|
||||
var animationCollection = new AnimationCollection(this, dialogue);
|
||||
|
||||
var currentSpan: HTMLSpanElement = null;
|
||||
var currentSpanStyles = new SpanStyles(this, dialogue, this._scaleX, this._scaleY, this._fontSizeElement, this._svgDefsElement);
|
||||
var currentSpanStyles = new SpanStyles(this, dialogue, this._scaleX, this._scaleY, this.settings, this._fontSizeElement, this._svgDefsElement);
|
||||
var nextSpanParent: HTMLElement = sub;
|
||||
var rotationX = 0;
|
||||
var rotationY = 0;
|
||||
var rotationZ = 0;
|
||||
|
||||
var startNewSpan = (addNewLine: boolean): void => {
|
||||
if (currentSpan !== null && currentSpan.textContent !== "") {
|
||||
sub.appendChild(currentSpanStyles.setStylesOnSpan(currentSpan));
|
||||
var newSpan = currentSpanStyles.setStylesOnSpan(currentSpan, rotationX, rotationY, rotationZ);
|
||||
|
||||
if (currentSpanStyles.rotationX !== rotationX || currentSpanStyles.rotationY !== rotationY || currentSpanStyles.rotationZ !== rotationZ) {
|
||||
nextSpanParent.appendChild(newSpan);
|
||||
nextSpanParent = newSpan;
|
||||
|
||||
rotationX = currentSpanStyles.rotationX;
|
||||
rotationY = currentSpanStyles.rotationY;
|
||||
rotationZ = currentSpanStyles.rotationZ;
|
||||
}
|
||||
else {
|
||||
nextSpanParent.appendChild(newSpan.firstElementChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (addNewLine) {
|
||||
sub.appendChild(document.createElement("br"));
|
||||
nextSpanParent = sub;
|
||||
nextSpanParent.appendChild(document.createElement("br"));
|
||||
|
||||
rotationX = 0;
|
||||
rotationY = 0;
|
||||
rotationZ = 0;
|
||||
}
|
||||
|
||||
currentSpan = document.createElement("span");
|
||||
@@ -313,6 +297,14 @@ module libjass.renderers {
|
||||
currentSpanStyles.fontSize = (<parts.FontSize>part).value;
|
||||
}
|
||||
|
||||
else if (part instanceof parts.FontSizePlus) {
|
||||
currentSpanStyles.fontSize += (<parts.FontSizePlus>part).value;
|
||||
}
|
||||
|
||||
else if (part instanceof parts.FontSizeMinus) {
|
||||
currentSpanStyles.fontSize -= (<parts.FontSizeMinus>part).value;
|
||||
}
|
||||
|
||||
else if (part instanceof parts.FontScaleX) {
|
||||
currentSpanStyles.fontScaleX = (<parts.FontScaleX>part).value;
|
||||
}
|
||||
@@ -396,7 +388,7 @@ module libjass.renderers {
|
||||
var newStyleName = (<parts.Reset>part).value;
|
||||
var newStyle: Style = null;
|
||||
if (newStyleName !== null) {
|
||||
newStyle = this.ass.styles[newStyleName];
|
||||
newStyle = this.ass.styles.get(newStyleName);
|
||||
}
|
||||
currentSpanStyles.reset(newStyle);
|
||||
}
|
||||
@@ -413,51 +405,51 @@ module libjass.renderers {
|
||||
var movePart = <parts.Move>part;
|
||||
|
||||
sub.style.position = "absolute";
|
||||
animationCollection.add("linear", [new Keyframe(0, {
|
||||
left: (this._scaleX * movePart.x1).toFixed(3) + "px",
|
||||
top: (this._scaleY * movePart.y1).toFixed(3) + "px"
|
||||
}), new Keyframe(movePart.t1, {
|
||||
left: (this._scaleX * movePart.x1).toFixed(3) + "px",
|
||||
top: (this._scaleY * movePart.y1).toFixed(3) + "px"
|
||||
}), new Keyframe(movePart.t2, {
|
||||
left: (this._scaleX * movePart.x2).toFixed(3) + "px",
|
||||
top: (this._scaleY * movePart.y2).toFixed(3) + "px"
|
||||
}), new Keyframe(dialogue.end - dialogue.start, {
|
||||
left: (this._scaleX * movePart.x2).toFixed(3) + "px",
|
||||
top: (this._scaleY * movePart.y2).toFixed(3) + "px"
|
||||
})]);
|
||||
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
|
||||
.set("left", (this._scaleX * movePart.x1).toFixed(3) + "px")
|
||||
.set("top", (this._scaleY * movePart.y1).toFixed(3) + "px")
|
||||
), new Keyframe(movePart.t1, new Map<string, string>()
|
||||
.set("left", (this._scaleX * movePart.x1).toFixed(3) + "px")
|
||||
.set("top", (this._scaleY * movePart.y1).toFixed(3) + "px")
|
||||
), new Keyframe(movePart.t2, new Map<string, string>()
|
||||
.set("left", (this._scaleX * movePart.x2).toFixed(3) + "px")
|
||||
.set("top", (this._scaleY * movePart.y2).toFixed(3) + "px")
|
||||
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
|
||||
.set("left", (this._scaleX * movePart.x2).toFixed(3) + "px")
|
||||
.set("top", (this._scaleY * movePart.y2).toFixed(3) + "px")
|
||||
)]);
|
||||
}
|
||||
|
||||
else if (part instanceof parts.Fade) {
|
||||
var fadePart = <parts.Fade>part;
|
||||
|
||||
animationCollection.add("linear", [new Keyframe(0, {
|
||||
opacity: "0"
|
||||
}), new Keyframe(fadePart.start, {
|
||||
opacity: "1"
|
||||
}), new Keyframe(dialogue.end - dialogue.start - fadePart.end, {
|
||||
opacity: String("1")
|
||||
}), new Keyframe(dialogue.end - dialogue.start, {
|
||||
opacity: "0"
|
||||
})]);
|
||||
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
|
||||
.set("opacity", "0")
|
||||
), new Keyframe(fadePart.start, new Map<string, string>()
|
||||
.set("opacity", "1")
|
||||
), new Keyframe(dialogue.end - dialogue.start - fadePart.end, new Map<string, string>()
|
||||
.set("opacity", "1")
|
||||
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
|
||||
.set("opacity", "0")
|
||||
)]);
|
||||
}
|
||||
|
||||
else if (part instanceof parts.ComplexFade) {
|
||||
var complexFadePart = <parts.ComplexFade>part;
|
||||
|
||||
animationCollection.add("linear", [new Keyframe(0, {
|
||||
opacity: String(complexFadePart.a1)
|
||||
}), new Keyframe(complexFadePart.t1, {
|
||||
opacity: String(complexFadePart.a1)
|
||||
}), new Keyframe(complexFadePart.t2, {
|
||||
opacity: String(complexFadePart.a2)
|
||||
}), new Keyframe(complexFadePart.t3, {
|
||||
opacity: String(complexFadePart.a2)
|
||||
}), new Keyframe(complexFadePart.t4, {
|
||||
opacity: String(complexFadePart.a3)
|
||||
}), new Keyframe(dialogue.end - dialogue.start, {
|
||||
opacity: String(complexFadePart.a3)
|
||||
})]);
|
||||
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a1.toFixed(3))
|
||||
), new Keyframe(complexFadePart.t1, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a1.toFixed(3))
|
||||
), new Keyframe(complexFadePart.t2, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a2.toFixed(3))
|
||||
), new Keyframe(complexFadePart.t3, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a2.toFixed(3))
|
||||
), new Keyframe(complexFadePart.t4, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a3.toFixed(3))
|
||||
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
|
||||
.set("opacity", complexFadePart.a3.toFixed(3))
|
||||
)]);
|
||||
}
|
||||
|
||||
else if (part instanceof parts.DrawingMode) {
|
||||
@@ -493,7 +485,7 @@ module libjass.renderers {
|
||||
|
||||
dialogue.parts.some(part => {
|
||||
if (part instanceof parts.Position || part instanceof parts.Move) {
|
||||
var transformOrigin = DefaultRenderer._transformOrigins[dialogue.alignment];
|
||||
var transformOrigin = WebRenderer._transformOrigins[dialogue.alignment];
|
||||
|
||||
var divTransformStyle = "translate(" + (-transformOrigin[0]) + "%, " + (-transformOrigin[1]) + "%) translate(-" + sub.style.marginLeft + ", -" + sub.style.marginTop + ")";
|
||||
var transformOriginString = transformOrigin[0] + "% " + transformOrigin[1] + "%";
|
||||
@@ -522,6 +514,15 @@ module libjass.renderers {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sub.style.position !== "") {
|
||||
// Explicitly set text alignment on absolutely-positioned subs because they'll go in a .an0 <div> and so won't get alignment CSS text-align.
|
||||
switch (dialogue.alignment) {
|
||||
case 1: case 4: case 7: sub.style.textAlign = "left"; break;
|
||||
case 2: case 5: case 8: sub.style.textAlign = "center"; break;
|
||||
case 3: case 6: case 9: sub.style.textAlign = "right"; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._animationStyleElement === null) {
|
||||
this._animationStyleElement = document.createElement("style");
|
||||
this._animationStyleElement.id = "libjass-animation-styles-" + this.id;
|
||||
@@ -536,7 +537,7 @@ module libjass.renderers {
|
||||
|
||||
sub.setAttribute("data-dialogue-id", this.id + "-" + dialogue.id);
|
||||
|
||||
this._preRenderedSubs.set(dialogue.id, sub);
|
||||
this._preRenderedSubs.set(dialogue.id, { sub: sub, animationDelays: animationCollection.animationDelays });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,22 +570,11 @@ module libjass.renderers {
|
||||
}
|
||||
}
|
||||
|
||||
var result = <HTMLDivElement>preRenderedSub.cloneNode(true);
|
||||
var result = <HTMLDivElement>preRenderedSub.sub.cloneNode(true);
|
||||
|
||||
var defaultAnimationDelay = result.style.webkitAnimationDelay;
|
||||
if (defaultAnimationDelay === undefined) {
|
||||
defaultAnimationDelay = result.style.animationDelay;
|
||||
}
|
||||
if (defaultAnimationDelay !== "") {
|
||||
var animationDelay =
|
||||
defaultAnimationDelay
|
||||
.split(",")
|
||||
.map(delay => (parseFloat(delay) + dialogue.start - this.clock.currentTime).toFixed(3) + "s")
|
||||
.join(",");
|
||||
|
||||
result.style.webkitAnimationDelay = animationDelay;
|
||||
result.style.animationDelay = animationDelay;
|
||||
}
|
||||
var animationDelay = preRenderedSub.animationDelays.map(delay => (delay + dialogue.start - this.clock.currentTime).toFixed(3) + "s").join(", ");
|
||||
result.style.webkitAnimationDelay = animationDelay;
|
||||
result.style.animationDelay = animationDelay;
|
||||
|
||||
var layer = dialogue.layer;
|
||||
var alignment = (result.style.position === "absolute") ? 0 : dialogue.alignment; // Alignment 0 is for absolutely-positioned subs
|
||||
@@ -636,26 +626,36 @@ module libjass.renderers {
|
||||
|
||||
this._removeAllSubs();
|
||||
|
||||
this._subsWrapper.style.display = "";
|
||||
|
||||
this._subsWrapper.classList.remove("paused");
|
||||
}
|
||||
|
||||
_onClockTick(): void {
|
||||
// Remove dialogues that should be removed before adding new ones via super._onClockTick()
|
||||
|
||||
var currentTime = this.clock.currentTime;
|
||||
|
||||
this._currentSubs.forEach((sub: HTMLDivElement, dialogue: Dialogue) => {
|
||||
if (dialogue.start > currentTime || dialogue.end < currentTime) {
|
||||
this._currentSubs.delete(dialogue);
|
||||
this._removeSub(sub);
|
||||
}
|
||||
});
|
||||
|
||||
super._onClockTick();
|
||||
}
|
||||
|
||||
_onClockPause(): void {
|
||||
super._onClockPause();
|
||||
|
||||
this._subsWrapper.classList.add("paused");
|
||||
}
|
||||
|
||||
_onClockTimeUpdate(): void {
|
||||
var currentTime = this.clock.currentTime;
|
||||
_onClockStop(): void {
|
||||
super._onClockStop();
|
||||
|
||||
super._onClockTimeUpdate();
|
||||
|
||||
this._currentSubs.forEach((sub: HTMLDivElement, dialogue: Dialogue) => {
|
||||
if (dialogue.start > currentTime || dialogue.end < currentTime) {
|
||||
this._currentSubs.delete(dialogue);
|
||||
this._removeSub(sub);
|
||||
}
|
||||
});
|
||||
this._subsWrapper.style.display = "none";
|
||||
}
|
||||
|
||||
_ready(): void {
|
||||
@@ -685,10 +685,6 @@ module libjass.renderers {
|
||||
}
|
||||
mixin(WebRenderer, [EventSource]);
|
||||
|
||||
interface KeyframePropertiesMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a single keyframe. It has a list of CSS properties (names and values) associated with a point in time. Multiple keyframes make up an animation.
|
||||
*
|
||||
@@ -696,7 +692,7 @@ module libjass.renderers {
|
||||
* @param {!Object.<string, string>} properties
|
||||
*/
|
||||
class Keyframe {
|
||||
constructor(private _time: number, private _properties: KeyframePropertiesMap) { }
|
||||
constructor(private _time: number, private _properties: Map<string, string>) { }
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
@@ -708,7 +704,7 @@ module libjass.renderers {
|
||||
/**
|
||||
* @type {!Object.<string, string>}
|
||||
*/
|
||||
get properties(): KeyframePropertiesMap {
|
||||
get properties(): Map<string, string> {
|
||||
return this._properties;
|
||||
}
|
||||
}
|
||||
@@ -727,6 +723,7 @@ module libjass.renderers {
|
||||
|
||||
private _cssText: string = "";
|
||||
private _animationStyle: string = "";
|
||||
private _animationDelays: number[] = [];
|
||||
private _numAnimations: number = 0;
|
||||
|
||||
constructor(renderer: NullRenderer, dialogue: Dialogue) {
|
||||
@@ -753,6 +750,13 @@ module libjass.renderers {
|
||||
return this._animationStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* This array should be used to set the "animation-delay" CSS property of the target element.
|
||||
*/
|
||||
get animationDelays(): number[] {
|
||||
return this._animationDelays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an animation to this collection. The given keyframes together make one animation.
|
||||
*
|
||||
@@ -774,8 +778,8 @@ module libjass.renderers {
|
||||
|
||||
ruleCssText += "\t" + (100 * keyframe.time / (this._end - this._start)).toFixed(3) + "% {\n";
|
||||
|
||||
Object.keys(keyframe.properties).forEach(propertyName => {
|
||||
ruleCssText += "\t\t" + propertyName + ": " + keyframe.properties[propertyName] + ";\n";
|
||||
keyframe.properties.forEach((value, name) => {
|
||||
ruleCssText += "\t\t" + name + ": " + value + ";\n";
|
||||
});
|
||||
|
||||
ruleCssText += "\t}\n";
|
||||
@@ -791,7 +795,8 @@ module libjass.renderers {
|
||||
this._animationStyle += ",";
|
||||
}
|
||||
|
||||
this._animationStyle += animationName + " " + (endTime - startTime).toFixed(3) + "s " + timingFunction + " " + startTime.toFixed(3) + "s";
|
||||
this._animationStyle += animationName + " " + (endTime - startTime).toFixed(3) + "s " + timingFunction;
|
||||
this._animationDelays.push(startTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,6 +808,7 @@ module libjass.renderers {
|
||||
* @param {!libjass.Dialogue} dialogue The Dialogue that this set of styles is associated with
|
||||
* @param {number} scaleX The horizontal scaling of the subtitles
|
||||
* @param {number} scaleY The vertical scaling of the subtitles
|
||||
* @param {!libjass.renderers.RendererSettings} settings The renderer settings
|
||||
* @param {!HTMLDivElement} fontSizeElement A <div> element to measure font sizes with
|
||||
* @param {!SVGDefsElement} svgDefsElement An SVG <defs> element to append filter definitions to
|
||||
*/
|
||||
@@ -853,7 +859,7 @@ module libjass.renderers {
|
||||
|
||||
private _nextFilterId = 0;
|
||||
|
||||
constructor(renderer: NullRenderer, dialogue: Dialogue, private _scaleX: number, private _scaleY: number, private _fontSizeElement: HTMLDivElement, private _svgDefsElement: SVGDefsElement) {
|
||||
constructor(renderer: WebRenderer, dialogue: Dialogue, private _scaleX: number, private _scaleY: number, private _settings: RendererSettings, private _fontSizeElement: HTMLDivElement, private _svgDefsElement: SVGDefsElement) {
|
||||
this._id = renderer.id + "-" + dialogue.id;
|
||||
this._defaultStyle = dialogue.style;
|
||||
|
||||
@@ -889,8 +895,8 @@ module libjass.renderers {
|
||||
|
||||
this.letterSpacing = newStyle.letterSpacing;
|
||||
|
||||
this._rotationX = null;
|
||||
this._rotationY = null;
|
||||
this._rotationX = 0;
|
||||
this._rotationY = 0;
|
||||
this._rotationZ = newStyle.rotationZ;
|
||||
|
||||
this._skewX = null;
|
||||
@@ -916,7 +922,7 @@ module libjass.renderers {
|
||||
* @param {!HTMLSpanElement} span
|
||||
* @return {!HTMLSpanElement} The resulting <span> with the CSS styles applied. This may be a wrapper around the input <span> if the styles were applied using SVG filters.
|
||||
*/
|
||||
setStylesOnSpan(span: HTMLSpanElement): HTMLSpanElement {
|
||||
setStylesOnSpan(span: HTMLSpanElement, previousRotationX: number, previousRotationY: number, previousRotationZ: number): HTMLSpanElement {
|
||||
var isTextOnlySpan = span.childNodes[0] instanceof Text;
|
||||
|
||||
var fontStyleOrWeight = "";
|
||||
@@ -962,15 +968,6 @@ module libjass.renderers {
|
||||
transform += "scaleY(" + this._fontScaleY + ") ";
|
||||
}
|
||||
}
|
||||
if (this._rotationY !== null) {
|
||||
transform += "rotateY(" + this._rotationY + "deg) ";
|
||||
}
|
||||
if (this._rotationX !== null) {
|
||||
transform += "rotateX(" + this._rotationX + "deg) ";
|
||||
}
|
||||
if (this._rotationZ !== 0) {
|
||||
transform += "rotateZ(" + (-1 * this._rotationZ) + "deg) ";
|
||||
}
|
||||
if (this._skewX !== null || this._skewY !== null) {
|
||||
var skewX = SpanStyles._valueOrDefault(this._skewX, 0);
|
||||
var skewY = SpanStyles._valueOrDefault(this._skewY, 0);
|
||||
@@ -991,53 +988,66 @@ module libjass.renderers {
|
||||
|
||||
var outlineColor = this._outlineColor.withAlpha(this._outlineAlpha);
|
||||
|
||||
var outlineWidth = (this._scaleX * this._outlineWidth);
|
||||
var outlineHeight = (this._scaleY * this._outlineHeight);
|
||||
var outlineWidth = this._scaleX * this._outlineWidth;
|
||||
var outlineHeight = this._scaleY * this._outlineHeight;
|
||||
|
||||
var filterId = "svg-filter-" + this._id + "-" + this._nextFilterId++;
|
||||
|
||||
var outlineFilter = '';
|
||||
if (outlineWidth > 0 || outlineHeight > 0) {
|
||||
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
|
||||
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
|
||||
*/
|
||||
|
||||
var mergeOutlinesFilter = '';
|
||||
|
||||
var radiiPairs: number[][] = [];
|
||||
var outlineNumber = 0;
|
||||
|
||||
if (outlineWidth >= outlineHeight) {
|
||||
if (outlineHeight > 0) {
|
||||
for (var y = 0; y <= outlineHeight; y++) {
|
||||
radiiPairs.push([outlineWidth / outlineHeight * Math.sqrt(outlineHeight * outlineHeight - y * y), y]);
|
||||
var increment = (!this._settings.preciseOutlines && this._gaussianBlur > 0) ? this._gaussianBlur : 1;
|
||||
|
||||
((addOutline: (x: number, y: number) => void) => {
|
||||
if (outlineWidth <= outlineHeight) {
|
||||
if (outlineWidth > 0) {
|
||||
for (var x = 0; x <= outlineWidth; x += increment) {
|
||||
addOutline(x, outlineHeight / outlineWidth * Math.sqrt(outlineWidth * outlineWidth - x * x));
|
||||
}
|
||||
if (x !== outlineWidth + increment) {
|
||||
addOutline(outlineWidth, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addOutline(0, outlineHeight);
|
||||
}
|
||||
}
|
||||
else {
|
||||
radiiPairs.push([outlineWidth, 0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (outlineWidth > 0) {
|
||||
for (var x = 0; x <= outlineWidth; x++) {
|
||||
radiiPairs.push([x, outlineHeight / outlineWidth * Math.sqrt(outlineWidth * outlineWidth - x * x)]);
|
||||
if (outlineHeight > 0) {
|
||||
for (var y = 0; y <= outlineHeight; y += increment) {
|
||||
addOutline(outlineWidth / outlineHeight * Math.sqrt(outlineHeight * outlineHeight - y * y), y);
|
||||
}
|
||||
if (y !== outlineHeight + increment) {
|
||||
addOutline(0, outlineHeight);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addOutline(outlineWidth, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
radiiPairs.push([0, outlineHeight]);
|
||||
}
|
||||
}
|
||||
|
||||
radiiPairs.forEach((radii, index) => {
|
||||
})((x: number, y: number): void => {
|
||||
outlineFilter +=
|
||||
'\t<feMorphology in="SourceAlpha" operator="dilate" radius="' + radii[0].toFixed(3) + ' ' + radii[1].toFixed(3) + '" result="outline' + index + '" />\n';
|
||||
'\t<feMorphology in="SourceAlpha" operator="dilate" radius="' + x.toFixed(3) + ' ' + y.toFixed(3) + '" result="outline' + outlineNumber + '" />\n';
|
||||
|
||||
mergeOutlinesFilter +=
|
||||
'\t\t<feMergeNode in="outline' + index + '" />\n';
|
||||
'\t\t<feMergeNode in="outline' + outlineNumber + '" />\n';
|
||||
|
||||
outlineNumber++;
|
||||
});
|
||||
|
||||
outlineFilter =
|
||||
'\t<feFlood flood-color="' + outlineColor.toString() + '" result="outlineColor" />' +
|
||||
outlineFilter +
|
||||
'\t<feMerge>\n' +
|
||||
outlineFilter +=
|
||||
'\t<feMerge result="outline">\n' +
|
||||
mergeOutlinesFilter +
|
||||
'\t</feMerge>\n' +
|
||||
'\t<feComposite operator="in" in="outlineColor" />';
|
||||
'\t<feFlood flood-color="' + outlineColor.toString() + '" />' +
|
||||
'\t<feComposite operator="in" in2="outline" />';
|
||||
}
|
||||
|
||||
var blurFilter = '';
|
||||
@@ -1055,7 +1065,7 @@ module libjass.renderers {
|
||||
|
||||
if (outlineFilter !== '' || blurFilter !== '') {
|
||||
var filterString =
|
||||
'<filter xmlns="http://www.w3.org/2000/svg" id="' + filterId + '">\n' +
|
||||
'<filter xmlns="http://www.w3.org/2000/svg" id="' + filterId + '" x="-50%" width="200%" y="-50%" height="200%">\n' +
|
||||
outlineFilter +
|
||||
blurFilter +
|
||||
'\t<feMerge>\n' +
|
||||
@@ -1082,7 +1092,39 @@ module libjass.renderers {
|
||||
filterWrapperSpan.style.display = "inline-block";
|
||||
}
|
||||
|
||||
return filterWrapperSpan;
|
||||
var rotationWrapperSpan = document.createElement("span");
|
||||
rotationWrapperSpan.appendChild(filterWrapperSpan);
|
||||
|
||||
var rotationTransform = "";
|
||||
if (this._rotationX !== previousRotationX || this._rotationY !== previousRotationY || this._rotationZ !== previousRotationZ) {
|
||||
if (previousRotationZ !== 0) {
|
||||
rotationTransform += "rotateZ(" + (previousRotationZ) + "deg) ";
|
||||
}
|
||||
if (previousRotationX !== 0) {
|
||||
rotationTransform += "rotateX(" + (-previousRotationX) + "deg) ";
|
||||
}
|
||||
if (previousRotationY !== 0) {
|
||||
rotationTransform += "rotateY(" + (-previousRotationY) + "deg) ";
|
||||
}
|
||||
if (this._rotationY !== 0) {
|
||||
rotationTransform += "rotateY(" + this._rotationY + "deg) ";
|
||||
}
|
||||
if (this._rotationX !== 0) {
|
||||
rotationTransform += "rotateX(" + this._rotationX + "deg) ";
|
||||
}
|
||||
if (this._rotationZ !== 0) {
|
||||
rotationTransform += "rotateZ(" + (-this._rotationZ) + "deg) ";
|
||||
}
|
||||
}
|
||||
if (rotationTransform !== "") {
|
||||
rotationWrapperSpan.style.webkitTransform = rotationTransform;
|
||||
rotationWrapperSpan.style.webkitTransformOrigin = "50% 50%";
|
||||
rotationWrapperSpan.style.transform = rotationTransform;
|
||||
rotationWrapperSpan.style.transformOrigin = "50% 50%";
|
||||
rotationWrapperSpan.style.display = "inline-block";
|
||||
}
|
||||
|
||||
return rotationWrapperSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1220,13 +1262,21 @@ module libjass.renderers {
|
||||
this._letterSpacing = SpanStyles._valueOrDefault(value, this._defaultStyle.letterSpacing);
|
||||
}
|
||||
|
||||
get rotationX() {
|
||||
return this._rotationX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the X-axis rotation property.
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
set rotationX(value: number) {
|
||||
this._rotationX = value;
|
||||
this._rotationX = SpanStyles._valueOrDefault(value, 0);
|
||||
}
|
||||
|
||||
get rotationY() {
|
||||
return this._rotationY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1235,7 +1285,11 @@ module libjass.renderers {
|
||||
* @type {?number}
|
||||
*/
|
||||
set rotationY(value: number) {
|
||||
this._rotationY = value;
|
||||
this._rotationY = SpanStyles._valueOrDefault(value, 0);
|
||||
}
|
||||
|
||||
get rotationZ() {
|
||||
return this._rotationZ;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+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,17 +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.
|
||||
*/
|
||||
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.
|
||||
@@ -45,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;
|
||||
}
|
||||
|
||||
@@ -67,6 +64,32 @@ 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.
|
||||
*
|
||||
@@ -88,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);
|
||||
@@ -116,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;
|
||||
}
|
||||
@@ -148,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("</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;
|
||||
@@ -286,7 +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.
|
||||
*
|
||||
* @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
|
||||
@@ -341,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"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,7 +592,7 @@ module libjass {
|
||||
/**
|
||||
* This class represents a dialogue in an ASS script.
|
||||
*
|
||||
* @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
|
||||
@@ -604,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");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+33
-23
@@ -22,6 +22,13 @@
|
||||
|
||||
"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.
|
||||
@@ -32,6 +39,7 @@ module libjass {
|
||||
*/
|
||||
class SimpleSet<T> implements Set<T> {
|
||||
private _elements: { [key: string]: T };
|
||||
private _size: number;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
@@ -48,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;
|
||||
@@ -57,6 +69,7 @@ module libjass {
|
||||
*/
|
||||
clear(): void {
|
||||
this._elements = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,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 {
|
||||
@@ -109,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,6 +140,7 @@ module 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();
|
||||
@@ -176,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;
|
||||
|
||||
@@ -198,6 +212,7 @@ module libjass {
|
||||
if (result) {
|
||||
delete this._keys[property];
|
||||
delete this._values[property];
|
||||
this._size--;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -208,6 +223,7 @@ module libjass {
|
||||
clear(): void {
|
||||
this._keys = Object.create(null);
|
||||
this._values = Object.create(null);
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,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 {
|
||||
@@ -247,17 +266,8 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
+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