65 Commits

Author SHA1 Message Date
Arnavion e9c3ee5edf tsnext 2016-09-16 09:45:16 -07:00
Arnavion 41d8516449 Fixed uglify script to not remove the wrong nodes when shuffling around the TS built-ins. 2016-09-10 12:48:38 -07:00
Arnavion 8b7ef6201e Updated pngjs to v2.3.1 2016-05-28 15:27:54 -07:00
Arnavion 5fb5a4d73d Added note to readme that margins and line height aren't identical to desktop renderers. 2016-05-18 00:04:41 -07:00
Arnavion 86971d1902 Minor functional test updates due to latest IE 11. 2016-05-18 00:00:32 -07:00
Arnavion 9071332e1e Updated intern to v3.x >= v3.2.0 2016-05-17 23:12:05 -07:00
Arnavion ae62c0c89f Log errors from pre-render and draw, and continue rendering other dialogues in the same tick even if one errors.
Only enabled in debugMode to avoid flooding console with one new message every tick while the bad dialogue is visible.

Fixes #76
2016-05-04 00:09:43 -07:00
Arnavion 027a8d69f5 SVG 1.2 implementations have null viewBox properties if "viewBox" attribute is not explicitly set on the DOM element.
Fixes #77
2016-04-29 12:30:36 -07:00
Arnavion 4c4ef34e8b Fixed rotation for the special case of only one rotation on the whole dialogue, by applying the rotation transform on the sub div instead of the individual spans.
The implementation is still broken for the general case of multiple rotations (see #14), but one rotation is the much more common case.

Fixes #75
2016-04-29 00:34:54 -07:00
Arnavion 4d519fd6c9 Made functional test screenshots bigger. 2016-04-27 22:40:57 -07:00
Arnavion 4ffa5499a4 Removed firefox tests.
Will regenerate them when firefox works with functional tests again.
2016-04-27 22:38:27 -07:00
Arnavion 6e7e7a005e Aggregate all the failures of a functional test instead of failing at the first mismatching screenshot. 2016-04-27 20:46:13 -07:00
Arnavion 20a0d8ee64 Also test on node v6 2016-04-27 20:45:05 -07:00
Arnavion 56fbd7a9b2 Updated TypeScript to v1.8.10 2016-04-27 13:06:09 -07:00
Arnavion 293617aa22 Updated async-build to v0.3.0 2016-04-27 13:05:46 -07:00
Arnavion 7ae40e7164 Seek to external time when auto-pausing.
Otherwise the current time remains larger than the external time by the auto-pause-after interval, and thus keeps triggering clock ticks despite being paused.
2016-04-08 02:09:05 -07:00
Arnavion 361bc707d3 Restrict intern to v3.0.x
v3.1.x updated istanbul dependency to v0.4.1 which has bug https://github.com/gotwarlost/istanbul/issues/591
2016-04-06 14:58:03 -07:00
Arnavion 04dc60f128 Fixed some issues found by tslint. 2016-04-06 03:16:40 -07:00
Arnavion 70537168a0 Removed unused code. 2016-03-26 22:57:16 -07:00
Arnavion 6e33697c5c Added a comment about the getters for the dynamic properties on the export object. 2016-03-26 18:47:00 -07:00
Arnavion 8680e884a1 Fixed behavior of tags with default values inside \t
The progression is ignored and the default value of the tag is applied as if it was before the \t
2016-03-22 04:31:32 -07:00
Arnavion ec693190b5 Fixed interpretation of \fs+ and \fs-
\fs+1 is a 10% increase, not a 1px increase.

Ref https://github.com/libass/libass/commit/9f2ffc03574ae323867fba00f8aaacc637bb0aa1
2016-03-22 04:29:17 -07:00
Arnavion 4dfdaf6010 Some tiny fixes found by TS's upcoming strictNullChecks feature. 2016-03-21 23:30:37 -07:00
Arnavion 9540b2c00a Updated TypeScript to v1.8.9 2016-03-21 03:16:34 -07:00
Arnavion 9445d04efa Insert new subs above existing subs in alignments 1-3
Fixes #49
2016-03-11 23:23:45 -08:00
Arnavion 7a7ee02905 Added top-level version property.
`libjass.version` is `["0.12.0", 0, 12, 0]`

Fixes #57
2016-03-11 12:15:21 -08:00
Arnavion a6605064c1 Exported variables are not setters, and the few that were (debugMode, etc.) now have an alternative (libjass.configure). 2016-03-11 12:14:50 -08:00
Arnavion 4438caae92 Made paths in .gitignore absolute. 2016-03-11 12:13:51 -08:00
Arnavion 212f376b77 Changed <>-style type assertions to as.
Easier to grep for and remove as newer versions of TS make them unnecessary.
2016-03-10 13:06:57 -08:00
Arnavion 35310a5980 Changed libjass.parts.Bold.value to boolean | number type. 2016-03-10 00:44:34 -08:00
Arnavion 4ccb76f53c Set line-height correctly for scaled text. 2016-03-09 22:28:03 -08:00
Arnavion 1ac4fb24aa Handle negative positions in SVG drawings. 2016-03-09 22:06:10 -08:00
Arnavion 1aef4ef1c9 Fixed shadows to include outlines.
Reported in #70
2016-03-09 12:40:16 -08:00
Arnavion c421cb10b6 Check whether to skip pre-rendering a sub before looking up any existing pre-rendered sub. 2016-03-09 12:39:55 -08:00
Arnavion 739ae86ffe Removed document.createElementNS overloads since they're in lib.d.ts now. 2016-03-07 23:51:55 -08:00
Arnavion 3dc4a78812 Render fractional outlines.
The SVG renderer uses feMorphology, but atleast on some browsers the filter's radius property doesn't support fractional radii. The renderer now adds four corner outlines using feOffset; feOffset doesn't appear to support fractional offsets either on the same browsers, but it does get rounded up or down automatically to give a close approximation.

The text shadow renderer now adds four corner shadows at the fractional distances.

Reported in #70
2016-03-07 23:11:39 -08:00
Arnavion 97c0691994 Updated TypeScript to v1.8.7 2016-03-04 01:41:10 -08:00
Arnavion 79c9b73e0d Stop passing in enableSvg explicitly to functional tests. 2016-03-02 11:44:07 -08:00
Arnavion 1e20e498c4 Removed unnecessary parameter. 2016-03-02 11:03:34 -08:00
Arnavion 5e6f3c8ba4 Some small optimizations for SVG filter effects. 2016-03-02 10:57:07 -08:00
Arnavion c15bd7391d Do some basic tests for SVG filter effects support if RendererSettings object does not specify a value for enableSvg, and default to that.
Technically this checks for whether the browser interprets the readonly WebIDL property correctly, which is arguably completely unrelated. However FF and Chrome support both, IE supports neither, and there's no other non-manual check for this feature, so it seems to be a reasonable way.
2016-03-01 23:50:18 -08:00
Arnavion 66f8ffc4c2 Added proper typed overload of Node.cloneNode() 2016-03-01 23:49:23 -08:00
Arnavion 93c2428626 Deprecated pathSegList API has been removed from some browsers. Switch back to setting d attribute. 2016-03-01 23:48:52 -08:00
Arnavion c04d0b6631 Minor typo. 2016-02-24 00:17:15 -08:00
Arnavion 05fc742588 Stronger type for stringly-typed parameters. 2016-02-22 23:20:35 -08:00
Arnavion d590984eb6 Updated TypeScript to v1.8.0 2016-02-22 23:20:33 -08:00
Arnavion a67ec2948f Exposed serialization API as libjass.serialize and libjass.deserialize. 2016-02-19 21:18:33 -08:00
Arnavion c432f33149 Implemented support for serializing ASS objects. 2016-02-19 21:18:03 -08:00
Arnavion 1bc6e63d18 Moved serialize, deserialize and registerClassPrototype to their own module with minor tweaks. 2016-02-19 21:16:19 -08:00
Arnavion d5831e65a1 Added note that RendererSettings.makeFontMapFromStyleElement doesn't support complicated @font-face declarations. 2016-02-18 03:02:04 -08:00
Arnavion 15d13e2fa2 Minor formatting. 2016-02-18 00:51:57 -08:00
Arnavion 5e100e88fb Name SVG filter IDs starting with "libjass-" 2016-02-18 00:50:15 -08:00
Arnavion 18b5d88f07 Switch to single-file AMD output similar to TS 1.8's concatenated output. 2016-02-14 02:53:03 -08:00
Arnavion 201fc044ee Use inlineSources compiler option instead of re-reading the files explicitly. 2016-02-13 22:59:28 -08:00
Arnavion bb28a85d97 Added @license to license header so minification tools preserve it. 2016-02-13 20:29:47 -08:00
Arnavion 414e48c022 Moved UMD wrapper to its own file. 2016-02-13 20:28:56 -08:00
Arnavion 7a5957cb27 Fixed JSDoc on libjass.configure() 2016-02-13 20:27:23 -08:00
Arnavion 2ae4ba0a09 Convert UJS parser errors to real Errors so that the async-build pipeline detects them. 2016-02-13 19:26:15 -08:00
Arnavion 40ac799134 tsconfig JSON schema specifies lower-case values. 2016-02-13 18:30:06 -08:00
Arnavion 990ca04065 Added libjass.configure() and deprecated libjass.{debugMode, verboseMode, Set, Map, Promise} setters. 2016-02-13 17:39:04 -08:00
Arnavion efa2fa7ade Support "ass" and "srt" strings as the type parameter of the ASS.from* functions in addition to the libjass.Format constants. 2016-02-06 01:19:38 -08:00
Arnavion 1de86c435a Change "fetch() failed" warning to a debug-mode log message, since it's not really a bad thing. 2016-02-06 01:19:36 -08:00
Arnavion df3ddb214e Whitespace. 2016-01-29 15:04:12 -08:00
Arnavion fa3467c649 Exported some interfaces that should've been because another exported interface references them. 2016-01-29 15:04:10 -08:00
Arnavion bba93dad9f Don't replace unicode escapes with the actual UTF-8 character.
Caters for servers that don't set a charset for libjass.js in the Content-Type header or HTML tag, and clients that end up interpreting libjass.js as system encoding and showing mojibake.

Fixes #65
2016-01-26 23:54:54 -08:00
150 changed files with 2203 additions and 1600 deletions
+10 -8
View File
@@ -1,12 +1,14 @@
build/doc.js
build/typescript/typescript.d.ts
build/typescript/*.js
/build/doc.js
/build/typescript/typescript.d.ts
/build/typescript/*.js
dist/
/dist/
lib/
!lib/libjass.css
/lib/
!/lib/libjass.css
node_modules/
/node_modules/
npm-debug.log
/npm-debug.log
/src/version.ts
+1
View File
@@ -4,6 +4,7 @@ node_js:
- "0.12"
- "4"
- "5"
- "6"
before_script:
- "node ./build.js doc"
sudo: false
+1
View File
@@ -137,6 +137,7 @@ You can also join the IRC channel in the links section below and ask any questio
* \an4, \an5, \an6 aren't positioned correctly.
* Smart line wrapping is not supported. Such lines are rendered as [wrapping style 1 (end-of-line wrapping).](http://docs.aegisub.org/3.0/ASS_Tags/#wrapstyle)
* Lines with multiple rotations aren't rotated the same as VSFilter or libass. See [#14](https://github.com/Arnavion/libjass/issues/14)
- Desktop renderers include borders when calculating space between adjacent lines. libjass doesn't.
### Links
+42 -6
View File
@@ -67,7 +67,43 @@ task("build-tools", function (callback) {
task("default", ["libjass.js", "libjass.min.js"]);
task("libjass.js", ["build-tools"], function (callback) {
task("version.ts", function (callback) {
fs.exists("./src/version.ts", function (exists) {
if (exists) {
callback();
return;
}
async.waterfall([
fs.readFile.bind(fs, "./package.json"),
function (buffer, callback) {
try {
var packageJson = JSON.parse(buffer);
var versionString = packageJson.version;
var versionParts = versionString.split(".").map(function (num) { return parseInt(num); });
var versionFileContents =
"/**\n" +
" * The version of libjass. An array like\n" +
" *\n" +
" * [\"0.12.0\", 0, 12, 0]\n" +
" *\n" +
" * @type {!Array.<string|number>}\n" +
" */\n" +
"export const version = " + JSON.stringify([versionString].concat(versionParts)) + ";\n";
callback(null, versionFileContents);
}
catch (ex) {
callback(ex);
}
},
function (contents, callback) {
fs.writeFile("./src/version.ts", contents, "utf8", callback);
}
], callback);
});
});
task("libjass.js", ["build-tools", "version.ts"], function (callback) {
fs.exists("./lib/libjass.js", function (exists) {
if (exists) {
callback();
@@ -76,7 +112,7 @@ task("libjass.js", ["build-tools"], function (callback) {
callback(null, task.src("./src/tsconfig.json")
.pipe(TypeScript.build("./src/index.ts", "libjass"))
.pipe(UglifyJS.build("./src/index", "libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(UglifyJS.build("libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(task.dest("./lib")));
});
});
@@ -94,9 +130,9 @@ task("libjass.min.js", ["libjass.js"], function (callback) {
});
});
task("clean", task.clean(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"]));
task("clean", task.clean(["./src/version.ts", "./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"]));
task("watch", ["build-tools"], function (callback) {
task("watch", ["build-tools", "version.ts"], function (callback) {
npm.load(function () {
var testRunning = false;
var rerunTest = false;
@@ -125,7 +161,7 @@ task("watch", ["build-tools"], function (callback) {
task.src("./src/tsconfig.json")
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
.pipe(UglifyJS.watch("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(UglifyJS.watch("libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(task.dest("./lib"))
.pipe(new task.FileTransform(function (file) {
if (file.path === "libjass.js") {
@@ -165,7 +201,7 @@ task("demo", ["libjass.js"], function () {
task("doc", ["make-doc", "test-doc"]);
task("make-doc", ["build-tools"], function () {
task("make-doc", ["build-tools", "version.ts"], function () {
var Doc = require("./build/doc.js");
return task.src("./src/tsconfig.json")
+60 -69
View File
@@ -18,18 +18,14 @@
* limitations under the License.
*/
import * as fs from "fs";
import * as path from "path";
import { Transform } from "stream";
import { File, FileTransform } from "async-build";
import { FileTransform } from "async-build";
import * as AST from "./typescript/ast";
import { Compiler } from "./typescript/compiler";
import { walk } from "./typescript/walker";
function flatten<T>(arr: T[][]): T[] {
var result: T[] = [];
let result: T[] = [];
for (const a of arr) {
result = result.concat(a);
@@ -38,7 +34,7 @@ function flatten<T>(arr: T[][]): T[] {
return result;
}
var sorter = (() => {
const sorter = (() => {
function visibilitySorter(value1: { isPrivate?: boolean; isProtected?: boolean; }, value2: { isPrivate?: boolean; isProtected?: boolean; }) {
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
return 0;
@@ -63,10 +59,10 @@ var sorter = (() => {
return 0;
}
var types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
const types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
function typeSorter(value1: AST.ModuleMember | AST.NamespaceMember, value2: AST.ModuleMember | AST.NamespaceMember) {
var type1Index = -1;
var type2Index = -1;
let type1Index = -1;
let type2Index = -1;
types.every((type, index) => {
if (value1 instanceof type) {
@@ -85,11 +81,11 @@ var sorter = (() => {
return value1.name.localeCompare(value2.name);
}
var sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
const sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
return (value1: AST.ModuleMember, value2: AST.ModuleMember) => {
for (var i = 0; i < sorters.length; i++) {
var result = sorters[i](value1, value2);
for (const sorter of sorters) {
const result = sorter(value1, value2);
if (result !== 0) {
return result;
@@ -111,10 +107,10 @@ function sanitize(str: string) {
function toVariableName(item: { name: string }) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
var name = item.name;
var result = "";
const name = item.name;
let result = "";
for (var i = 0; i < name.length; i++) {
for (let i = 0; i < name.length; i++) {
if (name[i] === name[i].toLowerCase()) {
// This is lower case. Write it as lower case.
result += name[i];
@@ -169,15 +165,15 @@ function toUsageName(item: AST.Class | AST.Interface | AST.Function | AST.Proper
}
if (item.parent instanceof AST.Namespace) {
if ((<AST.Class | AST.Interface | AST.Function | AST.Enum>item).isPrivate) {
if ((item as AST.CanBePrivate).isPrivate) {
return item.name;
}
return item.fullName;
}
if ((<AST.Function>item).isStatic) {
return toUsageName(<AST.Class | AST.Interface>item.parent) + '.' + item.name;
if ((item as AST.CanBeStatic).isStatic) {
return toUsageName(item.parent as AST.Class | AST.Interface) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
@@ -188,13 +184,12 @@ function toId(item: { fullName?: string; name: string; }): string {
}
function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): string {
var result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
let result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
var itemWithGenerics = <AST.HasGenerics>item;
if (itemWithGenerics.generics !== undefined && itemWithGenerics.generics.length > 0) {
var generics = <(string | AST.TypeReference | AST.IntrinsicTypeReference)[]>itemWithGenerics.generics;
if (AST.hasGenerics(item) && item.generics.length > 0) {
const generics = item.generics as (string | AST.TypeReference | AST.IntrinsicTypeReference)[];
result += sanitize(`.<${ generics.map(generic =>
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : <string>generic
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : generic
).join(', ') }>`);
}
@@ -204,9 +199,9 @@ function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): st
}
function writeDescription(text: string): string {
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
let result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
var inCodeBlock = false;
let inCodeBlock = false;
result = result.split("\n").map(line => {
if (line.substr(0, " ".length) === " ") {
line = line.substr(" ".length);
@@ -289,7 +284,7 @@ function functionToHtml(func: AST.Function): string[] {
}
function interfaceToHtml(interfase: AST.Interface): string[] {
var members: AST.InterfaceMember[] = [];
const members: AST.InterfaceMember[] = [];
Object.keys(interfase.members).forEach(memberName => members.push(interfase.members[memberName]));
members.sort(sorter);
@@ -311,7 +306,7 @@ function interfaceToHtml(interfase: AST.Interface): string[] {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
}
}))).concat([
' </dd>',
@@ -321,7 +316,7 @@ function interfaceToHtml(interfase: AST.Interface): string[] {
}
function classToHtml(clazz: AST.Class): string[] {
var members: AST.InterfaceMember[] = [];
const members: AST.InterfaceMember[] = [];
Object.keys(clazz.members).forEach(memberName => members.push(clazz.members[memberName]));
members.sort(sorter);
@@ -331,7 +326,7 @@ function classToHtml(clazz: AST.Class): string[] {
clazz.isAbstract ? ' abstract' : ''}${
clazz.isPrivate ? ' private' : ''}">`,
` <dt class="name">class ${ toLink(clazz) }${
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(<AST.TypeReference>clazz.baseType) : clazz.baseType.name }` : '' }${
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(clazz.baseType) : clazz.baseType.name }` : '' }${
(clazz.interfaces.length > 0) ? ` implements ${ clazz.interfaces.map(interfase => interfase instanceof AST.TypeReference ? toLink(interfase) : interfase.name).join(', ') }` : ''}</dt>`,
' <dd class="description">',
` ${ writeDescription(clazz.description) }`,
@@ -351,7 +346,7 @@ function classToHtml(clazz: AST.Class): string[] {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
}
}))).concat([
' </dd>',
@@ -407,30 +402,26 @@ function propertyToHtml(property: AST.Property): string[] {
}
export function build(outputFilePath: string, root: string, rootNamespaceName: string): FileTransform {
var compiler = new Compiler();
return new FileTransform(function (file: File): void {
var self: FileTransform = this;
const compiler = new Compiler();
return new FileTransform(function (file): void {
// Compile
compiler.compile(file);
// Walk
var walkResult = walk(compiler, root, rootNamespaceName);
var namespaces = walkResult.namespaces;
var modules = walkResult.modules;
const walkResult = walk(compiler, root, rootNamespaceName);
const namespaces = walkResult.namespaces;
const modules = walkResult.modules;
// Make HTML
var namespaceNames = Object.keys(namespaces)
const namespaceNames = Object.keys(namespaces)
.filter(namespaceName => namespaceName.substr(0, rootNamespaceName.length) === rootNamespaceName)
.sort((ns1, ns2) => ns1.localeCompare(ns2));
var moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2));
const moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2)).filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
moduleNames = moduleNames.filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
self.push({
this.push({
path: outputFilePath,
contents: Buffer.concat([new Buffer(
`<?xml version="1.0" encoding="utf-8" ?>
@@ -590,9 +581,9 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
<label><input type="checkbox" id="show-private" />Show private</label>
`
)].concat(namespaceNames.map(namespaceName => {
var namespace = namespaces[namespaceName];
const namespace = namespaces[namespaceName];
var namespaceMembers: AST.NamespaceMember[] = [];
const namespaceMembers: AST.NamespaceMember[] = [];
for (const memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
@@ -614,13 +605,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
`
)]));
})).concat(moduleNames.map(moduleName => {
var module = modules[moduleName];
const module = modules[moduleName];
var moduleMembers: AST.ModuleMemberWithoutReference[] = [];
const moduleMembers: AST.ModuleMemberWithoutReference[] = [];
for (const memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if ((<AST.HasParent><any>member).parent === module) {
moduleMembers.push(<AST.ModuleMemberWithoutReference>member);
const member = module.members[memberName];
if ((member as AST.HasParent).parent === module) {
moduleMembers.push(member as AST.ModuleMemberWithoutReference);
}
}
@@ -650,22 +641,22 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
<div class="content">
`
)]).concat(flatten(namespaceNames.map(namespaceName => {
var namespace = namespaces[namespaceName];
const namespace = namespaces[namespaceName];
var namespaceMembers: AST.NamespaceMember[] = [];
const namespaceMembers: AST.NamespaceMember[] = [];
for (const memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
var properties = <AST.Property[]>namespaceMembers.filter(member => member instanceof AST.Property);
var functions = <AST.Function[]>namespaceMembers.filter(member => member instanceof AST.Function);
var interfaces = <AST.Interface[]>namespaceMembers.filter(member => member instanceof AST.Interface);
var classes = <AST.Class[]>namespaceMembers.filter(member => member instanceof AST.Class);
var enums = <AST.Enum[]>namespaceMembers.filter(member => member instanceof AST.Enum);
const properties = namespaceMembers.filter(member => member instanceof AST.Property) as AST.Property[];
const functions = namespaceMembers.filter(member => member instanceof AST.Function) as AST.Function[];
const interfaces = namespaceMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
const classes = namespaceMembers.filter(member => member instanceof AST.Class) as AST.Class[];
const enums = namespaceMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
var result = [new Buffer(
const result = [new Buffer(
` <section class="namespace">
<h1 id="${ sanitize(namespaceName) }">Namespace ${ sanitize(namespaceName) }</h1>
`
@@ -763,13 +754,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
return result;
}))).concat(flatten(moduleNames.map(moduleName => {
var module = modules[moduleName];
const module = modules[moduleName];
var moduleMembers: AST.ModuleMember[] = [];
const moduleMembers: AST.ModuleMember[] = [];
for (const memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if ((<AST.HasParent><any>member).parent === module) {
moduleMembers.push(<AST.ModuleMember>member);
const member = module.members[memberName];
if ((member as AST.HasParent).parent === module) {
moduleMembers.push(member);
}
}
@@ -779,13 +770,13 @@ export function build(outputFilePath: string, root: string, rootNamespaceName: s
moduleMembers.sort(sorter);
var properties = <AST.Property[]>moduleMembers.filter(member => member instanceof AST.Property);
var functions = <AST.Function[]>moduleMembers.filter(member => member instanceof AST.Function);
var interfaces = <AST.Interface[]>moduleMembers.filter(member => member instanceof AST.Interface);
var classes = <AST.Class[]>moduleMembers.filter(member => member instanceof AST.Class);
var enums = <AST.Enum[]>moduleMembers.filter(member => member instanceof AST.Enum);
const properties = moduleMembers.filter(member => member instanceof AST.Property) as AST.Property[];
const functions = moduleMembers.filter(member => member instanceof AST.Function) as AST.Function[];
const interfaces = moduleMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
const classes = moduleMembers.filter(member => member instanceof AST.Class) as AST.Class[];
const enums = moduleMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
var result = [new Buffer(
const result = [new Buffer(
` <section class="module">
<h1 id="${ sanitize(moduleName) }">Module ${ sanitize(moduleName) }</h1>
`
+6
View File
@@ -7,6 +7,12 @@ declare var require: {
resolve(id: string): string;
};
interface BufferConstructor {
new (str: string): Buffer;
prototype: Buffer;
concat(list: Buffer[]): Buffer;
}
declare module "fs" {
export function existsSync(filename: string): boolean;
export function readFileSync(filename: string, options: { encoding: string }): string;
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"strictNullChecks": false,
"target": "es5",
"module": "commonjs",
"moduleResolution": "classic",
"noImplicitUseStrict": false
},
"files": [
"./typescript/index.ts",
"./doc.ts",
"./node.d.ts",
"./typescript/typescript.d.ts",
"../node_modules/async-build/typings.d.ts"
]
}
+12 -2
View File
@@ -18,7 +18,7 @@
* limitations under the License.
*/
import * as ts from "typescript";
import ts = require("typescript");
export class HasParent {
public parent: HasParent = null;
@@ -30,7 +30,7 @@ export class HasParent {
return this.name;
}
var parent = this.parent;
const parent = this.parent;
if (parent instanceof Namespace) {
return parent.getMemberFullName(this);
}
@@ -188,7 +188,17 @@ export class UnresolvedType {
}
export type HasStringGenerics = Class | Interface | Function;
export function hasStringGenerics(item: NamespaceMember): item is HasStringGenerics {
return (item as HasGenerics).generics !== undefined;
}
export type HasGenerics = HasStringGenerics | TypeReference;
export function hasGenerics(item: ModuleMember | EnumMember | TypeReference): item is HasGenerics {
return (item as HasGenerics).generics !== undefined;
}
export type CanBePrivate = Class | Interface | Function | Getter | Setter | Enum | Reference;
export type CanBeProtected = Function;
export type CanBeStatic = Function;
+70 -185
View File
@@ -18,46 +18,70 @@
* limitations under the License.
*/
import * as fs from "fs";
import * as path from "path";
import { Transform } from "stream";
import * as ts from "typescript";
import path = require("path");
import ts = require("typescript");
import { File, FileTransform, FileWatcher } from "async-build";
import { File, FileTransform } from "async-build";
import * as AST from "./ast";
import { walk } from "./walker";
export interface StreamingCompilerHost extends ts.CompilerHost, ts.ParseConfigHost {
export interface StreamingCompilerHost extends ts.CompilerHost {
setOutputStream(outputStream: FileTransform): void;
}
function createCompilerHost(options: ts.CompilerOptions): StreamingCompilerHost {
const host = ts.createCompilerHost(options) as StreamingCompilerHost;
let _outputStream: FileTransform = null;
host.setOutputStream = outputStream => _outputStream = outputStream;
host.writeFile = (fileName, data, writeByteOrderMark, onError?, sourceFiles?): void => {
_outputStream.push({
path: fileName,
contents: new Buffer(data)
});
};
host.useCaseSensitiveFileNames = () => true;
host.getNewLine = () => "\n";
return host;
}
export class Compiler {
private _projectRoot: string = null;
private _host: StreamingCompilerHost;
private _program: ts.Program = null;
constructor(private _host: StreamingCompilerHost = new CompilerHost()) { }
compile(projectConfigFile: File) {
this._projectRoot = path.dirname(projectConfigFile.path);
var projectConfig = ts.parseJsonConfigFileContent(JSON.parse(projectConfigFile.contents.toString()), this._host, this._projectRoot);
const projectConfig = ts.parseJsonConfigFileContent(JSON.parse(projectConfigFile.contents.toString()), ts.sys, this._projectRoot);
this._host = createCompilerHost(projectConfig.options);
this._program = ts.createProgram(projectConfig.fileNames, projectConfig.options, this._host);
var syntacticDiagnostics = this._program.getSyntacticDiagnostics();
const syntacticDiagnostics = this._program.getSyntacticDiagnostics();
if (syntacticDiagnostics.length > 0) {
this._reportDiagnostics(syntacticDiagnostics);
throw new Error("There were one or more syntactic diagnostics.");
}
var globalDiagnostics = this._program.getGlobalDiagnostics();
const optionsDiagnostics = this._program.getOptionsDiagnostics();
if (optionsDiagnostics.length > 0) {
this._reportDiagnostics(optionsDiagnostics);
throw new Error("There were one or more options diagnostics.");
}
const globalDiagnostics = this._program.getGlobalDiagnostics();
if (globalDiagnostics.length > 0) {
this._reportDiagnostics(globalDiagnostics);
throw new Error("There were one or more global diagnostics.");
}
var semanticDiagnostics = this._program.getSemanticDiagnostics();
const semanticDiagnostics = this._program.getSemanticDiagnostics();
if (semanticDiagnostics.length > 0) {
this._reportDiagnostics(semanticDiagnostics);
throw new Error("There were one or more semantic diagnostics.");
@@ -67,7 +91,7 @@ export class Compiler {
writeFiles(outputStream: FileTransform) {
this._host.setOutputStream(outputStream);
var emitDiagnostics = this._program.emit().diagnostics;
const emitDiagnostics = this._program.emit().diagnostics;
if (emitDiagnostics.length > 0) {
this._reportDiagnostics(emitDiagnostics);
throw new Error("There were one or more emit diagnostics.");
@@ -88,10 +112,10 @@ export class Compiler {
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
for (const diagnostic of diagnostics) {
var message = "";
let message = "";
if (diagnostic.file) {
var location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
const location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
}
@@ -105,168 +129,23 @@ export class Compiler {
};
}
const typeScriptModulePath = path.dirname(require.resolve("typescript"));
class CompilerHost implements StreamingCompilerHost {
protected _sourceFiles = Object.create(null);
private _outputStream: FileTransform = null;
setOutputStream(outputStream: FileTransform): void {
this._outputStream = outputStream;
}
// ts.ModuleResolutionHost members
fileExists(fileName: string): boolean {
return fs.existsSync(fileName);
}
readFile(fileName: string): string {
if (!this.fileExists(fileName)) {
return undefined;
}
return fs.readFileSync(fileName, { encoding: "utf8" });
}
// ts.CompilerHost members
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
if (fileName in this._sourceFiles) {
return this._sourceFiles[fileName];
}
try {
var text = fs.readFileSync(fileName, { encoding: "utf8" });
var result = ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5);
this._sourceFiles[fileName] = result;
}
catch (ex) {
if (onError) {
onError(ex.message);
}
}
return result;
}
getDefaultLibFileName(): string {
return path.join(typeScriptModulePath, "lib.dom.d.ts");
}
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError: (message?: string) => void): void {
this._outputStream.push({
path: fileName,
contents: new Buffer(data)
});
}
getCurrentDirectory(): string {
return path.resolve(".");
}
getCanonicalFileName(fileName: string): string {
return ts.normalizeSlashes(path.resolve(fileName));
}
useCaseSensitiveFileNames(): boolean {
return true;
}
getNewLine(): string {
return "\n";
}
// ts.ParseConfigHost members
readDirectory(rootDir: string, extension: string, exclude: string[]): string[] {
return ts.sys.readDirectory(rootDir, extension, exclude).map(fileName => this.getCanonicalFileName(fileName));
}
}
class WatchCompilerHost extends CompilerHost {
private _fileWatcher = new FileWatcher(fileNames => this._onFilesChanged(fileNames));
private _filesChangedSinceLast: string[] = [];
constructor(private _onChangeCallback: () => void) {
super();
}
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
var result = super.getSourceFile(fileName, languageVersion, onError);
if (result !== undefined) {
this._fileWatcher.watchFile(fileName);
}
return result;
};
private _onFilesChanged(fileNames: string[]) {
for (const fileName of fileNames) {
delete this._sourceFiles[fileName];
}
this._onChangeCallback();
}
}
export function build(root: string, rootNamespaceName: string): FileTransform {
var compiler = new Compiler();
return new FileTransform(function (projectConfigFile: File): void {
var self: FileTransform = this;
const compiler = new Compiler();
return new FileTransform(function (projectConfigFile): void {
console.log("Compiling " + projectConfigFile.path + "...");
compiler.compile(projectConfigFile);
var walkResult = walk(compiler, root, rootNamespaceName);
const walkResult = walk(compiler, root, rootNamespaceName);
addJSDocComments(walkResult.modules);
compiler.writeFiles(self);
compiler.writeFiles(this);
console.log("Compile succeeded.");
});
}
export function watch(root: string, rootNamespaceName: string): FileTransform {
return new FileTransform(function (projectConfigFile: File): void {
var self: FileTransform = this;
function compile() {
console.log("Compiling " + projectConfigFile.path + "...");
compiler.compile(projectConfigFile);
compiler.writeFiles(self);
console.log("Compile succeeded.");
self.push({
path: "END",
contents: ""
});
};
var compilerHost = new WatchCompilerHost(() => {
try {
compile();
}
catch (ex) {
console.error("Compile failed." + ex.stack);
}
});
var compiler = new Compiler(compilerHost);
compile();
console.log("Listening for changes...");
}, callback => { });
}
function addJSDocComments(modules: { [name: string]: AST.Module }): void {
function visitor(current: AST.Module | AST.ModuleMember | AST.InterfaceMember) {
if (current instanceof AST.Module) {
@@ -277,18 +156,18 @@ function addJSDocComments(modules: { [name: string]: AST.Module }): void {
return;
}
var newComments: string[] = [];
const newComments: string[] = [];
if (current instanceof AST.Class) {
newComments.push("@constructor");
if (current.baseType !== null) {
var baseType = current.baseType;
const baseType = current.baseType;
newComments.push(
"@extends {" +
baseType.fullName + (
(baseType instanceof AST.TypeReference && baseType.generics.length) > 0 ?
(".<" + (<AST.TypeReference>baseType).generics.map(generic => generic.fullName).join(", ") + ">") :
(".<" + (baseType as AST.TypeReference).generics.map(generic => generic.fullName).join(", ") + ">") :
""
) +
"}"
@@ -318,37 +197,37 @@ function addJSDocComments(modules: { [name: string]: AST.Module }): void {
return;
}
if ((<AST.HasParent><any>current).parent instanceof AST.Namespace) {
newComments.push("@memberOf " + (<AST.HasParent><any>current).parent.fullName);
if (current.parent instanceof AST.Namespace) {
newComments.push("@memberOf " + current.parent.fullName);
}
if ((<AST.HasStringGenerics>current).generics !== undefined && (<AST.HasStringGenerics>current).generics.length > 0) {
newComments.push("@template " + (<AST.HasStringGenerics>current).generics.join(", "));
if (AST.hasStringGenerics(current) && current.generics.length > 0) {
newComments.push("@template " + current.generics.join(", "));
}
if ((<AST.CanBePrivate><any>current).isPrivate) {
if ((current as AST.CanBePrivate).isPrivate) {
newComments.push("@private");
}
if ((<AST.CanBeProtected>current).isProtected) {
if ((current as AST.CanBeProtected).isProtected) {
newComments.push("@protected");
}
if ((<AST.CanBeStatic>current).isStatic) {
if ((current as AST.CanBeStatic).isStatic) {
newComments.push("@static");
}
if (newComments.length > 0) {
if (current instanceof AST.Property) {
var nodes: ts.Node[] = [];
const nodes: ts.Node[] = [];
if (current.getter !== null) { nodes.push(current.getter.astNode); }
if (current.setter !== null && nodes[0] !== current.setter.astNode) { nodes.push(current.setter.astNode); }
for (const node of nodes) {
(<any>node)["typescript-new-comment"] = newComments;
(node as any)["typescript-new-comment"] = newComments;
}
}
else {
(<any>(<AST.Class | AST.Interface | AST.Function | AST.Enum>current).astNode)["typescript-new-comment"] = newComments;
(current.astNode as any)["typescript-new-comment"] = newComments;
}
}
}
@@ -358,6 +237,7 @@ function addJSDocComments(modules: { [name: string]: AST.Module }): void {
}
}
/*
class FakeSourceFile {
public text: string;
public lineMap: number[];
@@ -398,15 +278,17 @@ class FakeSourceFile {
}
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
*/
export var oldGetLeadingCommentRangesOfNode: typeof ts.getLeadingCommentRangesOfNode = ts.getLeadingCommentRangesOfNode.bind(ts);
ts.getLeadingCommentRangesOfNode = (node: ts.Node, sourceFileOfNode: ts.SourceFile) => {
sourceFileOfNode = sourceFileOfNode || ts.getSourceFileOfNode(node);
export const oldGetLeadingCommentRangesOfNodeFromText: typeof ts.getLeadingCommentRangesOfNodeFromText = ts.getLeadingCommentRangesOfNodeFromText.bind(ts);
var originalComments = oldGetLeadingCommentRangesOfNode(node, sourceFileOfNode);
/*
ts.getLeadingCommentRangesOfNodeFromText = (node: ts.Node, text: string) => {
const originalComments = oldGetLeadingCommentRangesOfNodeFromText(node, text);
if (originalComments !== undefined && (<any>node)["typescript-new-comment"] !== undefined) {
var fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
const sourceFileOfNode = ts.getSourceFileOfNode(node);
let fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
if (fakeSourceFile === undefined) {
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
}
@@ -418,10 +300,13 @@ ts.getLeadingCommentRangesOfNode = (node: ts.Node, sourceFileOfNode: ts.SourceFi
};
var oldWriteCommentRange: typeof ts.writeCommentRange = ts.writeCommentRange.bind(ts);
ts.writeCommentRange = (currentSourceFile: ts.SourceFile, writer: ts.EmitTextWriter, comment: ts.CommentRange, newLine: string) => {
ts.writeCommentRange = (text: string, lineMap: number[], writer: ts.EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => {
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
const currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
text = currentSourceFile.text;
lineMap = currentSourceFile.lineMap;
}
return oldWriteCommentRange(currentSourceFile, writer, comment, newLine);
return oldWriteCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
};
*/
+17 -12
View File
@@ -1,18 +1,23 @@
declare namespace ts {
export interface EmitTextWriter { }
interface EmitTextWriter { }
export interface IntrinsicType extends Type {
interface IntrinsicType extends Type {
intrinsicName: string;
}
export function forEachValue<T, U>(map: Map<T>, callback: (value: T) => U): U;
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration): ExpressionWithTypeArguments;
export function getClassImplementsHeritageClauseElements(node: ClassDeclaration): NodeArray<ExpressionWithTypeArguments>;
export function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): NodeArray<ExpressionWithTypeArguments>;
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile): CommentRange[];
export function getLineStarts(sourceFile: SourceFile): number[];
export function getSourceFileOfNode(node: Node): SourceFile;
export function getTextOfNode(node: Node, includeTrivia?: boolean): string;
export function normalizeSlashes(path: string): string;
export function writeCommentRange(currentSourceFile: SourceFile, writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
interface SourceFile {
lineMap: number[];
}
function forEachProperty<T, U>(map: Map<T>, callback: (value: T, key: string) => U): U;
function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments;
function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration): ExpressionWithTypeArguments[];
function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): ExpressionWithTypeArguments[];
function getLeadingCommentRangesOfNodeFromText(node: Node, text: string): CommentRange[];
function getLineStarts(sourceFile: SourceFile): number[];
function getSourceFileOfNode(node: Node): SourceFile;
function getTextOfNode(node: Node, includeTrivia?: boolean): string;
function normalizeSlashes(path: string): string;
function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
function hasModifier(node: Node, flags: ModifierFlags): boolean;
}
+1 -1
View File
@@ -18,4 +18,4 @@
* limitations under the License.
*/
export { build, watch } from "./compiler";
export { build } from "./compiler";
+176 -168
View File
@@ -18,10 +18,10 @@
* limitations under the License.
*/
import * as path from "path";
import * as ts from "typescript";
import path = require("path");
import ts = require("typescript");
import { Compiler, oldGetLeadingCommentRangesOfNode } from "./compiler";
import { Compiler, oldGetLeadingCommentRangesOfNodeFromText } from "./compiler";
import * as AST from "./ast";
@@ -68,13 +68,13 @@ class Walker {
}
walk(sourceFile: ts.SourceFile): void {
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
if (!(moduleName in this.modules)) {
this.modules[moduleName] = new AST.Module(moduleName);
}
var module = this._scope.enter(this.modules[moduleName]);
const module = this._scope.enter(this.modules[moduleName]);
this._currentSourceFile = sourceFile;
for (const statement of sourceFile.statements) {
@@ -87,31 +87,31 @@ class Walker {
private _walk(node: ts.Node, parent: AST.Module): void {
switch (node.kind) {
case ts.SyntaxKind.VariableStatement:
this._visitVariableStatement(<ts.VariableStatement>node, parent);
this._visitVariableStatement(node as ts.VariableStatement, parent);
break;
case ts.SyntaxKind.FunctionDeclaration:
this._visitFunctionDeclaration(<ts.FunctionDeclaration>node, parent);
this._visitFunctionDeclaration(node as ts.FunctionDeclaration, parent);
break;
case ts.SyntaxKind.ClassDeclaration:
this._visitClassDeclaration(<ts.ClassDeclaration>node, parent);
this._visitClassDeclaration(node as ts.ClassDeclaration, parent);
break;
case ts.SyntaxKind.InterfaceDeclaration:
this._visitInterfaceDeclaration(<ts.InterfaceDeclaration>node, parent);
this._visitInterfaceDeclaration(node as ts.InterfaceDeclaration, parent);
break;
case ts.SyntaxKind.EnumDeclaration:
this._visitEnumDeclaration(<ts.EnumDeclaration>node, parent);
this._visitEnumDeclaration(node as ts.EnumDeclaration, parent);
break;
case ts.SyntaxKind.ImportDeclaration:
this._visitImportDeclaration(<ts.ImportDeclaration>node, parent);
this._visitImportDeclaration(node as ts.ImportDeclaration, parent);
break;
case ts.SyntaxKind.ExportDeclaration:
this._visitExportDeclaration(<ts.ExportDeclaration>node, parent);
this._visitExportDeclaration(node as ts.ExportDeclaration, parent);
break;
case ts.SyntaxKind.ExpressionStatement:
@@ -121,7 +121,7 @@ class Walker {
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
@@ -130,20 +130,20 @@ class Walker {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(<ts.PropertyDeclaration>node, clazz);
this._visitProperty(node as ts.PropertyDeclaration, clazz);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(<ts.MethodDeclaration>node, clazz);
this._visitMethod(node as ts.MethodDeclaration, clazz);
break;
case ts.SyntaxKind.GetAccessor:
this._visitGetAccessor(<ts.AccessorDeclaration>node, clazz);
this._visitGetAccessor(node as ts.AccessorDeclaration, clazz);
break;
case ts.SyntaxKind.SetAccessor:
this._visitSetAccessor(<ts.AccessorDeclaration>node, clazz);
this._visitSetAccessor(node as ts.AccessorDeclaration, clazz);
break;
case ts.SyntaxKind.TypeParameter:
@@ -152,7 +152,7 @@ class Walker {
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
@@ -161,12 +161,12 @@ class Walker {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(<ts.PropertyDeclaration>node, interfase);
this._visitProperty(node as ts.PropertyDeclaration, interfase);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(<ts.MethodDeclaration>node, interfase);
this._visitMethod(node as ts.MethodDeclaration, interfase);
break;
case ts.SyntaxKind.TypeParameter:
@@ -176,24 +176,24 @@ class Walker {
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _visitProperty(node: ts.PropertyDeclaration, parent: AST.Class | AST.Interface) {
if ((node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private) {
if (ts.hasModifier(node, ts.ModifierFlags.Private)) {
return;
}
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Field ${ ts.getTextOfNode(node.name) } has no @type annotation.`);
jsDoc.typeAnnotation = "*";
}
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
parent.members[property.name] = property;
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
@@ -201,9 +201,9 @@ class Walker {
}
private _visitMethod(node: ts.MethodDeclaration, parent: AST.Class | AST.Interface) {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on method ${ ts.getTextOfNode(node.name) }`
);
@@ -212,25 +212,25 @@ class Walker {
jsDoc.returnType = new AST.ReturnType("", "*");
}
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
var isProtected = (node.flags & ts.NodeFlags.Protected) === ts.NodeFlags.Protected;
var isStatic = (node.flags & ts.NodeFlags.Static) === ts.NodeFlags.Static;
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
const isProtected = ts.hasModifier(node, ts.ModifierFlags.Protected);
const isStatic = ts.hasModifier(node, ts.ModifierFlags.Static);
var generics = this._getGenericsOfSignatureDeclaration(node);
const generics = this._getGenericsOfSignatureDeclaration(node);
var method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
const method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
parent.members[method.name] = method;
this._scope.leave();
}
private _visitGetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var name = ts.getTextOfNode(node.name);
const name = ts.getTextOfNode(node.name);
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
var property = <AST.Property>clazz.members[name];
let property = clazz.members[name] as AST.Property;
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
@@ -247,13 +247,13 @@ class Walker {
}
private _visitSetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var name = ts.getTextOfNode(node.name);
const name = ts.getTextOfNode(node.name);
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
var property = <AST.Property>clazz.members[name];
let property = clazz.members[name] as AST.Property;
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
@@ -274,19 +274,18 @@ class Walker {
return;
}
var declaration = node.declarationList.declarations[0];
if ((declaration.flags & ts.NodeFlags.Ambient) === ts.NodeFlags.Ambient) {
const declaration = node.declarationList.declarations[0];
if (ts.hasModifier(declaration, ts.ModifierFlags.Ambient)) {
return;
}
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
return;
}
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
parent.members[property.name] = property;
@@ -294,13 +293,13 @@ class Walker {
}
private _visitFunctionDeclaration(node: ts.FunctionDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
var generics = this._getGenericsOfSignatureDeclaration(node);
const generics = this._getGenericsOfSignatureDeclaration(node);
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on function ${ node.name.text }`
);
@@ -313,7 +312,7 @@ class Walker {
jsDoc.returnType = new AST.ReturnType("", "*");
}
var freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
const freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
parent.members[freeFunction.name] = freeFunction;
@@ -321,32 +320,32 @@ class Walker {
}
private _visitClassDeclaration(node: ts.ClassDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
var baseType: AST.UnresolvedType = null;
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
const generics = this._getGenericsOfInterfaceType(type);
const baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
let baseType: AST.UnresolvedType = null;
if (baseTypeHeritageClauseElement !== null) {
baseType = new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(baseTypeHeritageClauseElement).symbol,
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement)
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement, generics)
);
}
var interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
const interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type)
this._getGenericsOfTypeReferenceNode(type, generics)
));
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
var generics = this._getGenericsOfInterfaceType(type);
var parameters: AST.Parameter[] = [];
let parameters: AST.Parameter[] = [];
if (type.symbol.members["__constructor"] !== undefined) {
parameters = this._connectParameters((<ts.ConstructorDeclaration>type.symbol.members["__constructor"].declarations[0]).parameters, jsDoc.parameters,
parameters = this._connectParameters((type.symbol.members["__constructor"].declarations[0] as ts.ConstructorDeclaration).parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on constructor in class ${ node.name.text }`
);
}
@@ -354,11 +353,11 @@ class Walker {
this._notifyIncorrectJsDoc("There are @param annotations on this class but it has no constructors.");
}
var clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
const clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
parent.members[clazz.name] = clazz;
ts.forEachValue(type.symbol.exports, symbol => {
ts.forEachProperty(type.symbol.exports, symbol => {
if (symbol.name === "prototype") {
return;
}
@@ -368,7 +367,7 @@ class Walker {
}
});
ts.forEachValue(type.symbol.members, symbol => {
ts.forEachProperty(type.symbol.members, symbol => {
for (const declaration of symbol.declarations) {
this._walkClassMember(declaration, clazz);
}
@@ -378,28 +377,28 @@ class Walker {
}
private _visitInterfaceDeclaration(node: ts.InterfaceDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
const generics = this._getGenericsOfInterfaceType(type);
const baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type)
this._getGenericsOfTypeReferenceNode(type, generics)
));
var existingInterfaceType = parent.members[node.name.text];
const existingInterfaceType = parent.members[node.name.text];
if (existingInterfaceType !== undefined) {
return;
}
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
var generics = this._getGenericsOfInterfaceType(type);
var interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
const interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
parent.members[interfase.name] = interfase;
ts.forEachValue(type.symbol.members, symbol => {
ts.forEachProperty(type.symbol.members, symbol => {
for (const declaration of symbol.declarations) {
this._walkInterfaceMember(declaration, interfase);
}
@@ -409,33 +408,33 @@ class Walker {
}
private _visitEnumDeclaration(node: ts.EnumDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var existingEnumType = parent.members[node.name.text];
const existingEnumType = parent.members[node.name.text];
if (existingEnumType !== undefined) {
return;
}
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
var type = this._typeChecker.getTypeAtLocation(node);
const type = this._typeChecker.getTypeAtLocation(node);
var enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
const enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
parent.members[enumType.name] = enumType;
ts.forEachValue(type.symbol.exports, symbol => {
this._visitEnumMember(<ts.EnumMember>symbol.declarations[0], enumType);
ts.forEachProperty(type.symbol.exports, symbol => {
this._visitEnumMember(symbol.declarations[0] as ts.EnumMember, enumType);
});
this._scope.leave();
}
private _visitEnumMember(node: ts.EnumMember, parent: AST.Enum): void {
var jsDoc = this._parseJSDoc(node);
const jsDoc = this._parseJSDoc(node);
var value = (node.initializer === undefined) ? null : parseInt((<ts.LiteralExpression>node.initializer).text);
const value = (node.initializer === undefined) ? null : parseInt((node.initializer as ts.LiteralExpression).text);
var enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
const enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
parent.members.push(enumMember);
@@ -452,16 +451,16 @@ class Walker {
throw new Error("Default import is not supported.");
}
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
if ((<ts.NamespaceImport>node.importClause.namedBindings).name !== undefined) {
if ((node.importClause.namedBindings as ts.NamespaceImport).name !== undefined) {
// import * as foo from "baz";
parent.members[(<ts.NamespaceImport>node.importClause.namedBindings).name.text] = new AST.Reference(moduleName, "*", true);
parent.members[(node.importClause.namedBindings as ts.NamespaceImport).name.text] = new AST.Reference(moduleName, "*", true);
}
else if ((<ts.NamedImports>node.importClause.namedBindings).elements !== undefined) {
else if ((node.importClause.namedBindings as ts.NamedImports).elements !== undefined) {
// import { foo, bar } from "baz";
for (const element of (<ts.NamedImports>node.importClause.namedBindings).elements) {
var importedName = element.propertyName && element.propertyName.text || element.name.text;
for (const element of (node.importClause.namedBindings as ts.NamedImports).elements) {
const importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, true);
}
}
@@ -473,22 +472,22 @@ class Walker {
private _visitExportDeclaration(node: ts.ExportDeclaration, parent: AST.Module): void {
if (node.moduleSpecifier !== undefined) {
// export { foo } from "bar";
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
for (const element of node.exportClause.elements) {
var importedName = element.propertyName && element.propertyName.text || element.name.text;
const importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, false);
}
}
else {
// export { foo };
for (const element of node.exportClause.elements) {
(<AST.CanBePrivate><any>parent.members[element.name.text]).isPrivate = false;
(parent.members[element.name.text] as AST.CanBePrivate).isPrivate = false;
}
}
}
private _resolve(relativeModuleName: string, currentModule: AST.Module): string {
var result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
let result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
if (result[0] !== ".") {
result = `./${ result }`;
@@ -498,7 +497,7 @@ class Walker {
}
private _parseJSDoc(node: ts.Node): JSDoc {
var comments = oldGetLeadingCommentRangesOfNode(node, this._currentSourceFile);
let comments = oldGetLeadingCommentRangesOfNodeFromText(node, this._currentSourceFile.text);
if (comments === undefined) {
comments = [];
@@ -508,41 +507,41 @@ class Walker {
comments = [comments[comments.length - 1]];
}
var comment =
const comment =
(comments.length === 0) ?
"" :
this._currentSourceFile.text.substring(comments[0].pos, comments[0].end);
var commentStartIndex = comment.indexOf("/**");
var commentEndIndex = comment.lastIndexOf("*/");
const commentStartIndex = comment.indexOf("/**");
const commentEndIndex = comment.lastIndexOf("*/");
var lines =
const lines =
(commentStartIndex === -1 || commentEndIndex === -1) ?
[] :
comment.substring(commentStartIndex + 2, commentEndIndex).split("\n").map(line => {
var match = line.match(/^[ \t]*\* (.*)/);
const match = line.match(/^[ \t]*\* (.*)/);
if (match === null) {
return "";
}
return match[1];
});
var rootDescription = "";
let rootDescription = "";
var parameters: { [name: string]: AST.Parameter } = Object.create(null);
const parameters: { [name: string]: AST.Parameter } = Object.create(null);
var typeAnnotation: string = null;
let typeAnnotation: string = null;
var returnType: AST.ReturnType = null;
let returnType: AST.ReturnType = null;
var isAbstract = false;
let isAbstract = false;
var lastRead: { description: string } = null;
let lastRead: { description: string } = null;
for (const line of lines) {
var firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
var firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
var remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
const firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
const firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
let remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
if (firstWord[0] === "@") {
lastRead = null;
@@ -553,30 +552,33 @@ class Walker {
isAbstract = true;
break;
case "@param":
var type: string;
case "@param": {
let type: string;
[type, remainingLine] = this._readType(remainingLine);
var [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
const [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
var subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
const subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
if (subParameterMatch === null) {
parameters[name] = lastRead = new AST.Parameter(name, description, type);
}
else {
var parentName = subParameterMatch[1] || subParameterMatch[3];
var childName = subParameterMatch[2] || subParameterMatch[4];
var parentParameter = parameters[parentName];
const parentName = subParameterMatch[1] || subParameterMatch[3];
const childName = subParameterMatch[2] || subParameterMatch[4];
const parentParameter = parameters[parentName];
parentParameter.subParameters.push(lastRead = new AST.Parameter(childName, description, type));
}
break;
case "@return":
var [type, description] = this._readType(remainingLine);
break;
}
case "@return": {
const [type, description] = this._readType(remainingLine);
returnType = lastRead = new AST.ReturnType(description, type);
break;
}
case "@type":
[typeAnnotation] = this._readType(remainingLine);
@@ -607,9 +609,9 @@ class Walker {
return ["*", remainingLine];
}
var index = -1;
var numberOfUnterminatedBraces = 0;
for (var i = 0; i < remainingLine.length; i++) {
let index = -1;
let numberOfUnterminatedBraces = 0;
for (let i = 0; i < remainingLine.length; i++) {
if (remainingLine[i] === "{") {
numberOfUnterminatedBraces++;
}
@@ -627,7 +629,7 @@ class Walker {
throw new Error("Unterminated type specifier.");
}
var type = remainingLine.substr(1, index - 1);
const type = remainingLine.substr(1, index - 1);
remainingLine = remainingLine.substr(index + 1).replace(/^\s+/, "");
return [type, remainingLine];
@@ -641,16 +643,24 @@ class Walker {
return signatureDeclaration.typeParameters.map(typeParameter => typeParameter.name.text);
}
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.ExpressionWithTypeArguments): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.ExpressionWithTypeArguments, intrinsicGenerics: string[]): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
if (typeReferenceNode.typeArguments === undefined) {
return [];
}
var typeReference = <ts.TypeReference>this._typeChecker.getTypeAtLocation(typeReferenceNode);
const typeReference = this._typeChecker.getTypeAtLocation(typeReferenceNode) as ts.TypeReference;
return typeReference.typeArguments.map(typeArgument => {
if ((<ts.IntrinsicType>typeArgument).intrinsicName !== undefined) {
return new AST.IntrinsicTypeReference((<ts.IntrinsicType>typeArgument).intrinsicName);
if ((typeArgument as ts.IntrinsicType).intrinsicName !== undefined) {
return new AST.IntrinsicTypeReference((typeArgument as ts.IntrinsicType).intrinsicName);
}
if (typeArgument.flags & ts.TypeFlags.TypeParameter) {
if (intrinsicGenerics.indexOf(typeArgument.symbol.name) !== -1) {
return new AST.IntrinsicTypeReference(typeArgument.symbol.name);
}
throw new Error(`Unbound type parameter ${ typeArgument.symbol.name }`);
}
return new AST.UnresolvedType(typeArgument.symbol, []);
@@ -669,12 +679,12 @@ class Walker {
private _connectParameters(astParameters: ts.ParameterDeclaration[], jsDocParameters: { [name: string]: AST.Parameter }, onMissingMessageCallback: (parameterName: string) => string) {
return astParameters.map(parameter => {
var parameterName = (<ts.Identifier>parameter.name).text;
let parameterName = (parameter.name as ts.Identifier).text;
if (parameterName[0] === "_") {
parameterName = parameterName.substr(1);
}
var jsDocParameter = jsDocParameters[parameterName];
let jsDocParameter = jsDocParameters[parameterName];
if (jsDocParameter === undefined) {
this._notifyIncorrectJsDoc(onMissingMessageCallback.call(this, parameterName));
@@ -686,8 +696,8 @@ class Walker {
}
private _notifyIncorrectJsDoc(message: string): void {
var fileName = path.basename(this._currentSourceFile.fileName);
if (fileName === "lib.core.d.ts" || fileName === "lib.dom.d.ts") {
const fileName = path.basename(this._currentSourceFile.fileName);
if (fileName === "lib.es5.d.ts" || fileName === "lib.dom.d.ts") {
return;
}
@@ -696,17 +706,17 @@ class Walker {
link(rootNamespaceName: string): void {
for (const moduleName of Object.keys(this.modules)) {
var module = this.modules[moduleName];
const module = this.modules[moduleName];
for (const memberName of Object.keys(module.members)) {
var member = module.members[memberName];
const member = module.members[memberName];
if (member instanceof AST.Class) {
if (member.unresolvedBaseType instanceof AST.UnresolvedType) {
member.baseType = this._resolveTypeReference(<AST.UnresolvedType>member.unresolvedBaseType);
member.baseType = this._resolveTypeReference(member.unresolvedBaseType);
}
else {
member.baseType = <AST.TypeReference | AST.IntrinsicTypeReference>member.unresolvedBaseType;
member.baseType = member.unresolvedBaseType;
}
member.interfaces = member.unresolvedInterfaces.map(interfase => {
@@ -714,7 +724,7 @@ class Walker {
return this._resolveTypeReference(interfase);
}
return <AST.TypeReference | AST.IntrinsicTypeReference>interfase;
return interfase;
});
}
@@ -724,12 +734,12 @@ class Walker {
return this._resolveTypeReference(baseType);
}
return <AST.TypeReference | AST.IntrinsicTypeReference>baseType;
return baseType;
});
}
else if (member instanceof AST.Enum) {
var value = 0;
let value = 0;
for (const enumMember of member.members) {
if (enumMember.value === null) {
enumMember.value = value;
@@ -751,17 +761,17 @@ class Walker {
private _moduleToNamespace(module: AST.Module): void {
for (const memberName of Object.keys(module.members)) {
var member = module.members[memberName];
let member = module.members[memberName];
if (member instanceof AST.Reference) {
if ((<AST.Reference>member).isPrivate) {
if (member.isPrivate) {
continue;
}
if (member.name === "*") {
var newNamespace = this._scope.enter(new AST.Namespace(memberName));
const newNamespace = this._scope.enter(new AST.Namespace(memberName));
var existingNamespace = this.namespaces[newNamespace.fullName];
const existingNamespace = this.namespaces[newNamespace.fullName];
if (existingNamespace !== undefined) {
this._scope.leave();
this._scope.enter(existingNamespace);
@@ -770,10 +780,10 @@ class Walker {
this.namespaces[newNamespace.fullName] = newNamespace;
}
var referencedModuleName = (<AST.Reference>member).moduleName;
var referencedModule = this.modules[referencedModuleName];
let referencedModuleName = member.moduleName;
let referencedModule = this.modules[referencedModuleName];
if (referencedModule === undefined && ((referencedModuleName + "/index") in this.modules)) {
(<AST.Reference>member).moduleName = referencedModuleName = referencedModuleName + "/index";
member.moduleName = referencedModuleName = referencedModuleName + "/index";
referencedModule = this.modules[referencedModuleName];
}
this._moduleToNamespace(referencedModule);
@@ -782,56 +792,56 @@ class Walker {
}
else {
while (member instanceof AST.Reference) {
member = this.modules[(<AST.Reference>member).moduleName].members[member.name];
member = this.modules[member.moduleName].members[member.name];
}
this._scope.enter(<AST.NamespaceMember><any>member);
this._scope.enter(member);
this._scope.leave();
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
(this._scope.current as AST.Namespace).members[member.name] = member;
}
}
else if (!(<AST.CanBePrivate><any>member).isPrivate) {
this._scope.enter(<AST.NamespaceMember>member);
else if (!(member as AST.CanBePrivate).isPrivate) {
this._scope.enter(member);
this._scope.leave();
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
(this._scope.current as AST.Namespace).members[member.name] = member;
}
}
}
private _resolveTypeReference(unresolvedType: AST.UnresolvedType): AST.TypeReference {
var node: ts.Node = unresolvedType.symbol.declarations[0];
let node: ts.Node = unresolvedType.symbol.declarations[0];
while (node.kind !== ts.SyntaxKind.SourceFile) {
node = node.parent;
}
var sourceFile = <ts.SourceFile>node;
const sourceFile = node as ts.SourceFile;
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
var module = this.modules[moduleName];
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
const module = this.modules[moduleName];
var result = module.members[unresolvedType.symbol.name];
let result = module.members[unresolvedType.symbol.name];
if (result === undefined) {
throw new Error(`Type ${ unresolvedType.symbol.name } could not be resolved.`);
}
while (result instanceof AST.Reference) {
result = this.modules[(<AST.Reference>result).moduleName].members[result.name];
result = this.modules[result.moduleName].members[result.name];
}
var resultGenerics = unresolvedType.generics.map(generic => {
const resultGenerics = unresolvedType.generics.map(generic => {
if (generic instanceof AST.UnresolvedType) {
return this._resolveTypeReference(generic);
}
return <AST.IntrinsicTypeReference>generic;
return generic;
});
return new AST.TypeReference(<AST.NamespaceMember><any>result, resultGenerics);
return new AST.TypeReference(result, resultGenerics);
}
private _moduleNameFromFileName(fileName: string): string {
var result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
let result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
result = result.substr(0, result.length - ".ts".length);
@@ -844,16 +854,14 @@ class Walker {
}
export function walk(compiler: Compiler, root: string, rootNamespaceName: string) {
var sourceFiles = compiler.sourceFiles;
var rootFileName = ts.normalizeSlashes(path.resolve(root));
var rootSourceFile = sourceFiles.filter(sourceFile => sourceFile.fileName === rootFileName)[0];
const sourceFiles = compiler.sourceFiles;
var walker = new Walker(compiler);
const walker = new Walker(compiler);
// Walk
for (const sourceFile of sourceFiles) {
if (
path.basename(sourceFile.fileName) === "lib.core.d.ts" ||
path.basename(sourceFile.fileName) === "lib.es5.d.ts" ||
path.basename(sourceFile.fileName) === "lib.dom.d.ts" ||
sourceFile.fileName.substr(-"references.d.ts".length) === "references.d.ts"
) {
+108 -211
View File
@@ -26,108 +26,52 @@ var UglifyJS = require("uglify-js");
var FileTransform = require("async-build").FileTransform;
var Run = (function () {
function Run(entry, outputLibraryName, unusedVarsToIgnore) {
this._entry = path.resolve(entry).replace(/\\/g, "/");
function Run(outputLibraryName, unusedVarsToIgnore) {
this._outputLibraryName = outputLibraryName;
this._unusedVarsToIgnore = unusedVarsToIgnore;
this._root = UglifyJS.parse(
'(function (root, factory) {\n' +
' var global = this;\n' +
'\n' +
' if (typeof define === "function" && define.amd) {\n' +
' define([], function() {\n' +
' return factory(global);\n' +
' });\n' +
' }\n' +
' else if (typeof exports === "object" && typeof module === "object") {\n' +
' module.exports = factory(global);\n' +
' }\n' +
' else if (typeof exports === "object") {\n' +
' exports.libjass = factory(global);\n' +
' }\n' +
' else {\n' +
' root.libjass = factory(global);\n' +
' }\n' +
'})(this, function (global) {\n' +
' "use strict";\n' +
' return (function (modules) {\n' +
' var installedModules = Object.create(null);\n' +
' function require(moduleId) {\n' +
' if (installedModules[moduleId]) {\n' +
' return installedModules[moduleId];\n' +
' }\n' +
'\n' +
' var exports = installedModules[moduleId] = Object.create(null);\n' +
' modules[moduleId](exports, require);\n' +
' return exports;\n' +
' }\n' +
'\n' +
' return require(0);\n' +
' })([\n' +
' ]);\n' +
'});');
this._root = UglifyJS.parse(fs.readFileSync(path.resolve(__filename, "..", "umd-wrapper.js"), "utf8"));
this._root.figure_out_scope({ screw_ie8: true });
this._licenseHeader = null;
this._toInsert = null;
this._outputModulesArray = this._root.body[0].body.args[1].body[1].value.args[0].elements;
this._modules = Object.create(null);
this._rootSourceMap = UjsSourceMap({ file: this._outputLibraryName + ".js", root: "" });
this._rootSourceMap = null;
}
Run.prototype.addFile = function (file) {
var _this = this;
var moduleName = (path.extname(file.path) === ".map") ? file.path.substr(0, file.path.length - ".js.map".length) : file.path.substr(0, file.path.length - ".js".length);
if (!(moduleName in this._modules)) {
this._modules[moduleName] = { id: null, root: null };
}
var module = this._modules[moduleName];
var filenameWithoutExtension = path.relative(path.join(this._entry, ".."), moduleName).replace(/\\/g, "/");
var tsFilename = filenameWithoutExtension + ".ts";
var jsFilename = filenameWithoutExtension + ".js";
switch (path.extname(file.path)) {
case ".js":
module.root = UglifyJS.parse(file.contents.toString(), {
filename: jsFilename,
toplevel: null,
});
try {
this._toInsert = UglifyJS.parse(file.contents.toString(), {
filename: path.basename(file.path),
toplevel: null,
}).body;
}
catch (ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
throw new Error("UglifyJS parse error: " + ex.toString() + "\n");
}
if (this._licenseHeader === null) {
module.root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (_this._licenseHeader !== null) {
return;
}
if (node.start) {
(node.start.comments_before || []).some(function (comment, i) {
if (comment.value.indexOf("Copyright") !== -1) {
_this._licenseHeader = comment;
_this._licenseHeader.value = _this._licenseHeader.value.split("\n").map(function (line) { return line.replace(/^\t/, ""); }).join("\n");
node.start.comments_before.splice(i, 1);
return true;
}
return false;
});
}
}));
this._root.start.comments_before = [this._licenseHeader];
this._root.start.nlb = true;
throw ex;
}
break;
case ".map":
var sourceMapContents = JSON.parse(file.contents.toString());
sourceMapContents.sources[0] = tsFilename;
sourceMapContents.file = jsFilename;
this._rootSourceMap.addInput(sourceMapContents);
this._rootSourceMap.get().setSourceContent(tsFilename, fs.readFileSync(moduleName + ".ts", { encoding: "utf8" }));
var rawSourceMap = JSON.parse(file.contents.toString());
this._rootSourceMap = UglifyJS.SourceMap({
file: this._outputLibraryName + ".js",
root: "",
orig: rawSourceMap,
});
var generator = this._rootSourceMap.get();
rawSourceMap.sources.forEach(function (sourceRelativePath, index) {
generator.setSourceContent(sourceRelativePath, rawSourceMap.sourcesContent[index]);
});
break;
}
};
@@ -135,63 +79,72 @@ var Run = (function () {
Run.prototype.build = function (outputStream) {
var _this = this;
// Assign IDs to all modules
var moduleNames = Object.keys(this._modules);
moduleNames.sort(function (name1, name2) { return (name1 === name2) ? 0 : (name1 < name2 ? -1 : 1); });
moduleNames.unshift.apply(moduleNames, moduleNames.splice(moduleNames.indexOf(this._entry), 1));
moduleNames.forEach(function (moduleName, index) {
_this._modules[moduleName].id = index;
});
// Splice in the TS output into the UMD wrapper.
var insertionParent = this._root.body[0].body.args[1].body;
// Merge modules
moduleNames.forEach(function (moduleName) {
var module = _this._modules[moduleName];
module.root.body.forEach(function (statement) {
if (statement instanceof UglifyJS.AST_Var && statement.definitions.length === 1) {
if (
statement.definitions[0].value instanceof UglifyJS.AST_Call &&
statement.definitions[0].value.expression.name === "require"
) {
var importRelativePath = statement.definitions[0].value.args[0].value;
var importAbsolutePath = path.join(moduleName, "..", importRelativePath).replace(/\\/g, "/");
var stringArg = statement.definitions[0].value.args[0];
var importedModule = _this._modules[importAbsolutePath];
if (importedModule === undefined) {
importedModule = _this._modules[importAbsolutePath + "/index"];
}
statement.definitions[0].value.args[0] = new UglifyJS.AST_Number({ start: stringArg.start, end: stringArg.end, value: importedModule.id });
}
else if (statement.definitions[0].name.name === "__extends") {
var importAbsolutePath = path.join(_this._entry, "..", "utility", "ts-helpers").replace(/\\/g, "/");
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__extends').body[0].body;
}
else if (statement.definitions[0].name.name === "__decorate") {
var importAbsolutePath = path.join(_this._entry, "..", "utility", "ts-helpers").replace(/\\/g, "/");
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__decorate').body[0].body;
this._toInsert.reverse();
for (var i = this._toInsert.length - 1; i >= 0; i--) {
var node = this._toInsert[i];
if (node instanceof UglifyJS.AST_Var) {
for (var j = 0; j < node.definitions.length; j++) {
var definition = node.definitions[j];
if (definition.name.name === "__extends" || definition.name.name === "__decorate") {
definition.value = definition.value.right;
}
}
});
var wrapper = UglifyJS.parse('/* ' + module.id + ' ./' + path.relative(path.join(_this._entry, ".."), moduleName).replace(/\\/g, "/") + ' */ function x(exports, require) { }');
var func = wrapper.body[0];
func.body = module.root.body;
func.name = null;
_this._outputModulesArray.push(func);
});
insertionParent.splice(-1, 0, node);
this._toInsert.splice(i, 1);
}
}
this._root.figure_out_scope({ screw_ie8: true });
insertionParent.splice.apply(insertionParent, [-1, 0].concat(this._toInsert));
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
var haveUnusedVars = false;
// Fixups
for (var i = 0; i < this._toInsert.length; i++) {
var node = this._toInsert[i];
if (node instanceof UglifyJS.AST_Statement && node.body instanceof UglifyJS.AST_Call && node.body.expression.name === "define") {
var defineCall = node.body;
// Remove some things from the AST
var nodesToRemove;
defineCall.expression.name = "def";
nodesToRemove = [];
if (defineCall.args[1].elements[0].value !== "require") {
throw new Error("Expected first dep to be require");
}
defineCall.args[1].elements.shift();
defineCall.args[2].argnames.shift();
if (defineCall.args[1].elements[0].value !== "exports") {
throw new Error("Expected second dep to be exports");
}
defineCall.args[1].elements.shift();
defineCall.args[2].argnames.push(defineCall.args[2].argnames.shift());
}
}
// Remove all license headers except the one from the UMD wrapper
var firstLicenseHeader = null;
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node.start) {
(node.start.comments_before || []).some(function (comment, i) {
if (comment.value.indexOf("Copyright") !== -1) {
if (firstLicenseHeader === null) {
firstLicenseHeader = comment;
}
else if (comment !== firstLicenseHeader) {
node.start.comments_before.splice(i, 1);
}
return true;
}
return false;
});
}
}));
// Fixup anonymous functions to print a space after "function"
@@ -207,7 +160,7 @@ var Run = (function () {
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node.start && node.start.comments_before) {
node.start.comments_before.forEach(function (comment) {
if (comment === _this._licenseHeader) {
if (comment.value.indexOf("Copyright") !== -1) {
return;
}
@@ -216,7 +169,7 @@ var Run = (function () {
return;
}
var indent = " "; // 9 spaces
var indent = " "; // 5 spaces
for (var i = 0; i < comment.col; i++) {
indent += " ";
}
@@ -230,8 +183,16 @@ var Run = (function () {
this._root.figure_out_scope({ screw_ie8: true });
// Remove some things from the AST
var nodesToRemove = [];
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
var haveUnusedVars = false;
// Repeat because removing some declarations may make others unreferenced
for (;;) {
this._root.figure_out_scope({ screw_ie8: true });
// Unreferenced variable and function declarations, and unreferenced terminal function arguments
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
@@ -266,8 +227,6 @@ var Run = (function () {
});
nodesToRemove = [];
this._root.figure_out_scope({ screw_ie8: true });
}
@@ -315,39 +274,23 @@ var Run = (function () {
// Output
var output = {
source_map: _this._rootSourceMap,
source_map: this._rootSourceMap,
ascii_only: true,
beautify: true,
comments: function (node, comment) {
// If this is the first license header, keep it.
if (comment === _this._licenseHeader) {
return true;
}
// If this is a license header, remove it.
if (comment.value.indexOf("Copyright") !== -1) {
return false;
}
// If this is a TypeScript reference comment, strip it.
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
return false;
}
return true;
}
comments: true,
};
var stream = UglifyJS.OutputStream(output);
this._root.print(stream);
outputStream.push({
path: _this._outputLibraryName + ".js",
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + _this._outputLibraryName + ".js.map")])
path: this._outputLibraryName + ".js",
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + this._outputLibraryName + ".js.map")])
});
outputStream.push({
path: _this._outputLibraryName + ".js.map",
contents: new Buffer(_this._rootSourceMap.get().toString())
path: this._outputLibraryName + ".js.map",
contents: new Buffer(this._rootSourceMap.get().toString())
});
// Print unused variables
@@ -366,8 +309,8 @@ var Run = (function () {
})();
module.exports = {
build: function (entry, outputLibraryName, unusedVarsToIgnore) {
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
build: function (outputLibraryName, unusedVarsToIgnore) {
var run = new Run(outputLibraryName, unusedVarsToIgnore);
return new FileTransform(function (file) {
run.addFile(file);
@@ -376,7 +319,7 @@ module.exports = {
});
},
watch: function (entry, outputLibraryName, unusedVarsToIgnore) {
watch: function (outputLibraryName, unusedVarsToIgnore) {
var files = Object.create(null);
return new FileTransform(function (file) {
@@ -384,7 +327,7 @@ module.exports = {
files[file.path] = file;
}
else {
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
var run = new Run(outputLibraryName, unusedVarsToIgnore);
Object.keys(files).forEach(function (filename) {
run.addFile(files[filename]);
});
@@ -452,6 +395,7 @@ module.exports = {
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
ascii_only: true,
comments: function (node, comment) {
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
@@ -489,53 +433,6 @@ module.exports = {
}
};
var SourceMap = require.cache[require.resolve("uglify-js")].require("source-map");
function UjsSourceMap(options) {
var orig_maps = Object.create(null);
var generator = new SourceMap.SourceMapGenerator({
file: options.file,
sourceRoot: options.root,
});
return {
addInput: function (rawSourceMap) {
var consumer = new SourceMap.SourceMapConsumer(rawSourceMap);
orig_maps[consumer.file] = consumer;
},
add: function (source, gen_line, gen_col, orig_line, orig_col, name) {
var originalMap = orig_maps[source];
if (originalMap === undefined) {
return;
}
var info = originalMap.originalPositionFor({
line: orig_line,
column: orig_col
});
if (info.source === null) {
return;
}
source = info.source;
orig_line = info.line;
orig_col = info.column;
name = info.name || name;
generator.addMapping({
generated : { line: gen_line, column: gen_col },
original : { line: orig_line, column: orig_col },
source : source,
name : name
});
},
get: function () { return generator; },
toString: function () { return generator.toString(); },
};
}
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
// Workaround for https://github.com/mishoo/UglifyJS2/issues/789 - Nodes explicitly marked with ujs:unreferenced will not be warned for.
+65
View File
@@ -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.
*
* @license
*/
(function (root, factory) {
var global = this;
if (typeof define === "function" && define.amd) {
define([], function() {
return factory(global);
});
}
else if (typeof exports === "object" && typeof module === "object") {
module.exports = factory(global);
}
else if (typeof exports === "object") {
exports.libjass = factory(global);
}
else {
root.libjass = factory(global);
}
})(this, function (global) {
"use strict";
var registeredModules = Object.create(null);
var installedModules = Object.create(null);
function def(moduleId, deps, body) {
installedModules[moduleId] = { deps: deps, body: body, };
}
function req(moduleId) {
if (moduleId in registeredModules) {
return registeredModules[moduleId];
}
var exports = registeredModules[moduleId] = Object.create(null);
var deps = installedModules[moduleId].deps.map(req);
deps.push(exports);
installedModules[moduleId].body.apply(null, deps);
return exports;
}
return req("index");
});
+4
View File
@@ -98,3 +98,7 @@
.libjass-filters {
display: block;
}
.libjass-filters * {
color-interpolation-filters: sRGB;
}
+5 -6
View File
@@ -17,7 +17,7 @@
"main": "lib/libjass.js",
"scripts": {
"prepublish": "node ./build.js clean default",
"build": "tsc ./build/typescript/index.ts ./build/doc.ts ./build/node.d.ts ./build/typescript/typescript.d.ts ./node_modules/async-build/typings.d.ts -m commonjs -t es5 -noImplicitAny --moduleResolution classic",
"build": "tsc -p ./build/tsconfig.json",
"test": "node ./build.js test",
"test-lib": "intern-client config=tests/intern reporters=Pretty",
"test-minified": "intern-client config=tests/intern reporters=Pretty minified=true",
@@ -26,13 +26,12 @@
},
"devDependencies": {
"async": "1.x >=1.4",
"async-build": "0.2.0",
"firefox-profile": "0.3.x",
"intern": "3.x >=3.0.1",
"async-build": "0.3.1",
"intern": "3.x >=3.2.0",
"npm": "3.x",
"pngjs": "2.2.0",
"pngjs": "3.x",
"sax": "1.x",
"typescript": "1.7.5",
"typescript": "next",
"uglify-js": "2.x >=2.4.24"
},
"private": true
+67 -5
View File
@@ -42,6 +42,8 @@ export { parser };
import * as renderers from "./renderers";
export { renderers };
export { serialize, deserialize } from "./serialization";
export { ASS } from "./types/ass";
export { Attachment, AttachmentType } from "./types/attachment";
export { Dialogue } from "./types/dialogue";
@@ -50,28 +52,88 @@ export { Style } from "./types/style";
export { BorderStyle, Format, WrappingStyle } from "./types/misc";
export { version } from "./version";
/**
* Configures libjass with the given properties.
*
* @param {!*} newConfig
* @param {?boolean} newConfig["debugMode"] When true, libjass logs some debug messages.
* @param {?boolean} newConfig["verboseMode"] When true, libjass logs some more debug messages. This setting is independent of {@link libjass.debugMode}
* @param {?function(new:Set, !Array.<T>=)} newConfig["Set"] Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
* @param {?function(new:Map, !Array.<!Array.<*>>=)} newConfig["Map"] Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
* @param {?function(new:Promise)} newConfig["Promise"] Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
*/
export function configure(newConfig: {
debugMode?: boolean,
verboseMode?: boolean,
Set?: typeof set.Set | null,
Map?: typeof map.Map | null,
Promise?: typeof promise.Promise | null,
}): void {
if (typeof newConfig.debugMode === "boolean") {
settings.setDebugMode(newConfig.debugMode);
}
if (typeof newConfig.verboseMode === "boolean") {
settings.setVerboseMode(newConfig.verboseMode);
}
if (typeof newConfig.Set === "function" || newConfig.Set === null) {
set.setImplementation(newConfig.Set);
}
if (typeof newConfig.Map === "function" || newConfig.Map === null) {
map.setImplementation(newConfig.Map);
}
if (typeof newConfig.Promise === "function" || newConfig.Promise === null) {
promise.setImplementation(newConfig.Promise);
}
}
declare const exports: any;
// Getters below are to work around https://github.com/Microsoft/TypeScript/issues/6366
Object.defineProperties(exports, {
debugMode: {
get: () => settings.debugMode,
set: settings.setDebugMode,
set: value => {
console.warn("Setter `libjass.debugMode = value` has been deprecated. Use `libjass.configure({ debugMode: value })` instead.");
settings.setDebugMode(value);
},
},
verboseMode: {
get: () => settings.verboseMode,
set: settings.setVerboseMode,
set: value => {
console.warn("Setter `libjass.verboseMode = value` has been deprecated. Use `libjass.configure({ verboseMode: value })` instead.");
settings.setVerboseMode(value);
},
},
Set: {
get: () => set.Set,
set: set.setImplementation,
set: value => {
console.warn("Setter `libjass.Set = value` has been deprecated. Use `libjass.configure({ Set: value })` instead.");
set.setImplementation(value);
},
},
Map: {
get: () => map.Map,
set: map.setImplementation,
set: value => {
console.warn("Setter `libjass.Map = value` has been deprecated. Use `libjass.configure({ Map: value })` instead.");
map.setImplementation(value);
},
},
Promise: {
get: () => promise.Promise,
set: promise.setImplementation,
set: value => {
console.warn("Setter `libjass.Promise = value` has been deprecated. Use `libjass.configure({ Promise: value })` instead.");
promise.setImplementation(value);
},
},
});
+4 -4
View File
@@ -26,9 +26,9 @@ import { Map } from "../utility/map";
* Parses a line into a {@link ./types/misc.Property}.
*
* @param {string} line
* @return {!Property}
* @return {Property}
*/
export function parseLineIntoProperty(line: string): Property {
export function parseLineIntoProperty(line: string): Property | null {
const colonPos = line.indexOf(":");
if (colonPos === -1) {
return null;
@@ -45,9 +45,9 @@ export function parseLineIntoProperty(line: string): Property {
*
* @param {string} line
* @param {!Array.<string>} formatSpecifier
* @return {!TypedTemplate}
* @return {TypedTemplate}
*/
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate {
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate | null {
const property = parseLineIntoProperty(line);
if (property === null) {
return null;
+125 -122
View File
@@ -34,17 +34,17 @@ const rules = new Map<string, (parent: ParseNode) => ParseNode>();
* @return {*} The value returned depends on the rule used.
*/
export function parse(input: string, rule: string): any {
const run = new ParserRun(input, rule);
const { result } = new ParserRun(input, rule);
if (run.result === null || run.result.end !== input.length) {
if (result === null || result.end !== input.length) {
if (debugMode) {
console.error("Parse failed. %s %s %o", rule, input, run.result);
console.error("Parse failed. %s %s %o", rule, input, result);
}
throw new Error("Parse failed.");
}
return run.result.value;
return result.value;
}
/**
@@ -58,15 +58,20 @@ class ParserRun {
private _result: ParseNode;
constructor(private _input: string, rule: string) {
const ruleFunction = rules.get(rule);
if (ruleFunction === undefined) {
throw new Error(`Could not find parser rule named ${ rule }`);
}
this._parseTree = new ParseNode(null);
this._result = rules.get(rule).call(this, this._parseTree);
this._result = ruleFunction.call(this, this._parseTree);
}
/**
* @type {ParseNode}
*/
get result(): ParseNode {
get result(): ParseNode | null {
return this._result;
}
@@ -89,15 +94,10 @@ class ParserRun {
else {
const whiteSpaceOrTextNode = this.parse_newline(current) || this.parse_hardspace(current) || this.parse_text(current);
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
const 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);
const previousTextPart = current.value[current.value.length - 1] as parts.Text;
current.value[current.value.length - 1] = new parts.Text(previousTextPart.value + (whiteSpaceOrTextNode.value as parts.Text).value);
}
else {
current.value.push(whiteSpaceOrTextNode.value);
@@ -113,7 +113,7 @@ class ParserRun {
}
else if (part instanceof parts.Text && inDrawingMode) {
current.value[i] = new parts.DrawingInstructions(<parts.drawing.Instruction[]>parse(part.value, "drawingInstructions"));
current.value[i] = new parts.DrawingInstructions(parse(part.value, "drawingInstructions") as parts.drawing.Instruction[]);
}
});
@@ -124,7 +124,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_enclosedTags(parent: ParseNode): ParseNode {
parse_enclosedTags(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
current.value = [];
@@ -135,7 +135,7 @@ class ParserRun {
}
for (let next = this._peek(); this._haveMore() && next !== "}"; next = this._peek()) {
let childNode: ParseNode = null;
let childNode: ParseNode | null = null;
if (this.read(current, "\\") !== null) {
childNode =
@@ -211,8 +211,8 @@ class ParserRun {
// Merge consecutive comment parts into one part
current.value[current.value.length - 1] =
new parts.Comment(
(<parts.Comment>current.value[current.value.length - 1]).value +
(<parts.Comment>childNode.value).value
(current.value[current.value.length - 1] as parts.Comment).value +
(childNode.value as parts.Comment).value
);
}
else {
@@ -237,7 +237,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_newline(parent: ParseNode): ParseNode {
parse_newline(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "\\N") === null) {
@@ -254,7 +254,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_hardspace(parent: ParseNode): ParseNode {
parse_hardspace(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "\\h") === null) {
@@ -301,7 +301,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_a(parent: ParseNode): ParseNode {
parse_tag_a(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "a") === null) {
@@ -339,8 +339,8 @@ class ParserRun {
const valueNode = new ParseNode(current, next);
let value: number = null;
switch (valueNode.value) {
let value: number = -1;
switch ((valueNode.value)) {
case "1":
value = 1;
break;
@@ -387,7 +387,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_alpha(parent: ParseNode): ParseNode {
parse_tag_alpha(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -395,7 +395,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_an(parent: ParseNode): ParseNode {
parse_tag_an(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "an") === null) {
@@ -421,7 +421,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_b(parent: ParseNode): ParseNode {
parse_tag_b(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "b") === null) {
@@ -429,7 +429,7 @@ class ParserRun {
return null;
}
let valueNode: ParseNode = null;
let valueNode: ParseNode | null = null;
let next = this._peek();
@@ -459,7 +459,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_be(parent: ParseNode): ParseNode {
parse_tag_be(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -467,7 +467,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_blur(parent: ParseNode): ParseNode {
parse_tag_blur(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -475,7 +475,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_bord(parent: ParseNode): ParseNode {
parse_tag_bord(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -483,7 +483,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_c(parent: ParseNode): ParseNode {
parse_tag_c(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -491,7 +491,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_clip(parent: ParseNode): ParseNode {
parse_tag_clip(parent: ParseNode): ParseNode | null {
return this._parse_tag_clip_or_iclip("clip", parent);
}
@@ -499,7 +499,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fad(parent: ParseNode): ParseNode {
parse_tag_fad(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fad") === null) {
@@ -543,7 +543,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fade(parent: ParseNode): ParseNode {
parse_tag_fade(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fade") === null) {
@@ -646,7 +646,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fax(parent: ParseNode): ParseNode {
parse_tag_fax(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -654,7 +654,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fay(parent: ParseNode): ParseNode {
parse_tag_fay(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -662,7 +662,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fn(parent: ParseNode): ParseNode {
parse_tag_fn(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fn") === null) {
@@ -690,7 +690,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fr(parent: ParseNode): ParseNode {
parse_tag_fr(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -698,7 +698,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_frx(parent: ParseNode): ParseNode {
parse_tag_frx(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -706,7 +706,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fry(parent: ParseNode): ParseNode {
parse_tag_fry(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -714,7 +714,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_frz(parent: ParseNode): ParseNode {
parse_tag_frz(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -722,7 +722,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fs(parent: ParseNode): ParseNode {
parse_tag_fs(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -730,7 +730,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fsplus(parent: ParseNode): ParseNode {
parse_tag_fsplus(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fs+") === null) {
@@ -745,7 +745,7 @@ class ParserRun {
return null;
}
current.value = new parts.FontSizePlus(valueNode.value);
current.value = new parts.FontSizePlus(valueNode.value / 10);
return current;
}
@@ -754,7 +754,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fsminus(parent: ParseNode): ParseNode {
parse_tag_fsminus(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fs-") === null) {
@@ -769,7 +769,7 @@ class ParserRun {
return null;
}
current.value = new parts.FontSizeMinus(valueNode.value);
current.value = new parts.FontSizeMinus(valueNode.value / 10);
return current;
}
@@ -778,7 +778,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fscx(parent: ParseNode): ParseNode {
parse_tag_fscx(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fscx") === null) {
@@ -802,7 +802,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fscy(parent: ParseNode): ParseNode {
parse_tag_fscy(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "fscy") === null) {
@@ -826,7 +826,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_fsp(parent: ParseNode): ParseNode {
parse_tag_fsp(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -834,7 +834,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_i(parent: ParseNode): ParseNode {
parse_tag_i(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -842,7 +842,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_iclip(parent: ParseNode): ParseNode {
parse_tag_iclip(parent: ParseNode): ParseNode | null {
return this._parse_tag_clip_or_iclip("iclip", parent);
}
@@ -850,7 +850,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_k(parent: ParseNode): ParseNode {
parse_tag_k(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "k") === null) {
@@ -874,7 +874,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_K(parent: ParseNode): ParseNode {
parse_tag_K(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "K") === null) {
@@ -898,7 +898,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_kf(parent: ParseNode): ParseNode {
parse_tag_kf(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "kf") === null) {
@@ -922,7 +922,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_ko(parent: ParseNode): ParseNode {
parse_tag_ko(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "ko") === null) {
@@ -946,7 +946,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_move(parent: ParseNode): ParseNode {
parse_tag_move(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "move") === null) {
@@ -998,8 +998,8 @@ class ParserRun {
return null;
}
let t1Node: ParseNode = null;
let t2Node: ParseNode = null;
let t1Node: ParseNode | null = null;
let t2Node: ParseNode | null = null;
if (this.read(current, ",") !== null) {
t1Node = this.parse_decimal(current);
@@ -1037,7 +1037,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_org(parent: ParseNode): ParseNode {
parse_tag_org(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "org") === null) {
@@ -1081,7 +1081,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_p(parent: ParseNode): ParseNode {
parse_tag_p(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1089,7 +1089,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_pbo(parent: ParseNode): ParseNode {
parse_tag_pbo(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1097,7 +1097,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_pos(parent: ParseNode): ParseNode {
parse_tag_pos(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "pos") === null) {
@@ -1141,7 +1141,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_q(parent: ParseNode): ParseNode {
parse_tag_q(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "q") === null) {
@@ -1167,7 +1167,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_r(parent: ParseNode): ParseNode {
parse_tag_r(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "r") === null) {
@@ -1195,7 +1195,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_s(parent: ParseNode): ParseNode {
parse_tag_s(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1203,7 +1203,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_shad(parent: ParseNode): ParseNode {
parse_tag_shad(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1211,7 +1211,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_t(parent: ParseNode): ParseNode {
parse_tag_t(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, "t") === null) {
@@ -1224,9 +1224,9 @@ class ParserRun {
return null;
}
let startNode: ParseNode = null;
let endNode: ParseNode = null;
let accelNode: ParseNode = null;
let startNode: ParseNode | null = null;
let endNode: ParseNode | null = null;
let accelNode: ParseNode | null = null;
const firstNode = this.parse_decimal(current);
if (firstNode !== null) {
@@ -1268,7 +1268,7 @@ class ParserRun {
const transformTags: parts.Part[] = [];
for (let next = this._peek(); this._haveMore() && next !== ")" && next !== "}"; next = this._peek()) {
let childNode: ParseNode = null;
let childNode: ParseNode | null = null;
if (this.read(current, "\\") !== null) {
childNode =
@@ -1323,8 +1323,8 @@ class ParserRun {
// Merge consecutive comment parts into one part
transformTags[transformTags.length - 1] =
new parts.Comment(
(<parts.Comment>transformTags[transformTags.length - 1]).value +
(<parts.Comment>childNode.value).value
(transformTags[transformTags.length - 1] as parts.Comment).value +
(childNode.value as parts.Comment).value
);
}
else {
@@ -1354,7 +1354,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_u(parent: ParseNode): ParseNode {
parse_tag_u(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1362,7 +1362,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_xbord(parent: ParseNode): ParseNode {
parse_tag_xbord(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1370,7 +1370,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_xshad(parent: ParseNode): ParseNode {
parse_tag_xshad(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1378,7 +1378,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_ybord(parent: ParseNode): ParseNode {
parse_tag_ybord(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1386,7 +1386,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_yshad(parent: ParseNode): ParseNode {
parse_tag_yshad(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1394,7 +1394,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_1a(parent: ParseNode): ParseNode {
parse_tag_1a(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1402,7 +1402,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_1c(parent: ParseNode): ParseNode {
parse_tag_1c(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1410,7 +1410,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_2a(parent: ParseNode): ParseNode {
parse_tag_2a(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1418,7 +1418,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_2c(parent: ParseNode): ParseNode {
parse_tag_2c(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1426,7 +1426,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_3a(parent: ParseNode): ParseNode {
parse_tag_3a(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1434,7 +1434,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_3c(parent: ParseNode): ParseNode {
parse_tag_3c(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1442,7 +1442,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_4a(parent: ParseNode): ParseNode {
parse_tag_4a(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1450,7 +1450,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_tag_4c(parent: ParseNode): ParseNode {
parse_tag_4c(parent: ParseNode): ParseNode | null {
throw new Error("Method not implemented.");
}
@@ -1461,7 +1461,7 @@ class ParserRun {
parse_drawingInstructions(parent: ParseNode): ParseNode {
const current = new ParseNode(parent);
let currentType: string = null;
let currentType: string | null = null;
const numberParts: ParseNode[] = [];
current.value = [];
@@ -1498,11 +1498,7 @@ class ParserRun {
}
const typePart = this.parse_text(current);
if (typePart === null) {
break;
}
const newType = (<parts.Text>typePart.value).value;
const newType = (typePart.value as parts.Text).value;
switch (newType) {
case "m":
case "l":
@@ -1522,7 +1518,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_decimalInt32(parent: ParseNode): ParseNode {
parse_decimalInt32(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const isNegative = this.read(current, "-") !== null;
@@ -1554,7 +1550,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_hexInt32(parent: ParseNode): ParseNode {
parse_hexInt32(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const isNegative = this.read(current, "-") !== null;
@@ -1594,13 +1590,13 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_decimalOrHexInt32(parent: ParseNode): ParseNode {
parse_decimalOrHexInt32(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const valueNode =
(this.read(current, "&H") !== null || this.read(current, "&h") !== null) ?
this.parse_hexInt32(current) :
this.parse_decimalInt32(current);
this.parse_hexInt32(current) :
this.parse_decimalInt32(current);
if (valueNode === null) {
parent.pop();
@@ -1616,7 +1612,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_decimal(parent: ParseNode): ParseNode {
parse_decimal(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const negative = (this.read(current, "-") !== null);
@@ -1641,12 +1637,12 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_unsignedDecimal(parent: ParseNode): ParseNode {
parse_unsignedDecimal(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const characteristicNode = new ParseNode(current, "");
let mantissaNode: ParseNode = null;
let mantissaNode: ParseNode | null = null;
for (let next = this._peek(); this._haveMore() && next >= "0" && next <= "9"; next = this._peek()) {
characteristicNode.value += next;
@@ -1679,7 +1675,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_enableDisable(parent: ParseNode): ParseNode {
parse_enableDisable(parent: ParseNode): ParseNode | null {
const next = this._peek();
if (next === "0" || next === "1") {
@@ -1696,7 +1692,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_color(parent: ParseNode): ParseNode {
parse_color(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
while (this.read(current, "&") !== null || this.read(current, "H") !== null) { }
@@ -1724,7 +1720,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_alpha(parent: ParseNode): ParseNode {
parse_alpha(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
while (this.read(current, "&") !== null || this.read(current, "H") !== null) { }
@@ -1748,7 +1744,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
parse_colorWithAlpha(parent: ParseNode): ParseNode {
parse_colorWithAlpha(parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
const valueNode = this.parse_decimalOrHexInt32(current);
@@ -1774,7 +1770,7 @@ class ParserRun {
* @param {string} next
* @return {ParseNode}
*/
read(parent: ParseNode, next: string): ParseNode {
read(parent: ParseNode, next: string): ParseNode | null {
if (this._peek(next.length) !== next) {
return null;
}
@@ -1805,7 +1801,7 @@ class ParserRun {
* @param {!ParseNode} parent
* @return {ParseNode}
*/
private _parse_tag_clip_or_iclip(tagName: string, parent: ParseNode): ParseNode {
private _parse_tag_clip_or_iclip(tagName: "clip" | "iclip", parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (this.read(current, tagName) === null) {
@@ -1818,12 +1814,12 @@ class ParserRun {
return null;
}
let x1Node: ParseNode = null;
let x2Node: ParseNode = null;
let y1Node: ParseNode = null;
let y2Node: ParseNode = null;
let scaleNode: ParseNode = null;
let commandsNode: ParseNode = null;
let x1Node: ParseNode | null = null;
let x2Node: ParseNode | null = null;
let y1Node: ParseNode | null = null;
let y2Node: ParseNode | null = null;
let scaleNode: ParseNode | null = null;
let commandsNode: ParseNode | null = null;
const firstNode = this.parse_decimal(current);
@@ -1851,6 +1847,10 @@ class ParserRun {
}
x2Node = this.parse_decimal(current);
if (x2Node === null) {
parent.pop();
return null;
}
if (this.read(current, ",") === null) {
parent.pop();
@@ -1858,6 +1858,10 @@ class ParserRun {
}
y2Node = this.parse_decimal(current);
if (y2Node === null) {
parent.pop();
return null;
}
current.value = new parts.RectangularClip(x1Node.value, y1Node.value, x2Node.value, y2Node.value, tagName === "clip");
}
@@ -1868,7 +1872,7 @@ class ParserRun {
commandsNode.value += next;
}
current.value = new parts.VectorClip((scaleNode !== null) ? scaleNode.value : 1, <parts.drawing.Instruction[]>parse(commandsNode.value, "drawingInstructions"), tagName === "clip");
current.value = new parts.VectorClip((scaleNode !== null) ? scaleNode.value : 1, parse(commandsNode.value, "drawingInstructions") as parts.drawing.Instruction[], tagName === "clip");
}
if (this.read(current, ")") === null) {
@@ -1891,19 +1895,18 @@ class ParserRun {
function makeTagParserFunction(
tagName: string,
tagConstructor: { new (value: any): parts.Part },
valueParser: (current: ParseNode) => ParseNode,
valueParser: (current: ParseNode) => ParseNode | null,
required: boolean
): void {
(<any>ParserRun.prototype)[`parse_tag_${ tagName }`] = function (parent: ParseNode): ParseNode {
const self = <ParserRun>this;
(ParserRun.prototype as any)[`parse_tag_${ tagName }`] = function (this: ParserRun, parent: ParseNode): ParseNode | null {
const current = new ParseNode(parent);
if (self.read(current, tagName) === null) {
if (this.read(current, tagName) === null) {
parent.pop();
return null;
}
const valueNode = valueParser.call(self, current);
const valueNode: ParseNode | null = valueParser.call(this, current);
if (valueNode !== null) {
current.value = new tagConstructor(valueNode.value);
@@ -1953,8 +1956,8 @@ makeTagParserFunction("4a", parts.ShadowAlpha, ParserRun.prototype.parse_alpha,
makeTagParserFunction("4c", parts.ShadowColor, ParserRun.prototype.parse_color, false);
for (const key of Object.keys(ParserRun.prototype)) {
if (key.indexOf("parse_") === 0 && typeof (<any>ParserRun.prototype)[key] === "function") {
rules.set(key.substr("parse_".length), (<any>ParserRun.prototype)[key]);
if (key.indexOf("parse_") === 0 && typeof (ParserRun.prototype as any)[key] === "function") {
rules.set(key.substr("parse_".length), (ParserRun.prototype as any)[key]);
}
}
@@ -1971,7 +1974,7 @@ class ParseNode {
private _end: number;
private _value: any;
constructor(private _parent: ParseNode, value: any = null) {
constructor(private _parent: ParseNode | null, value: any = null) {
if (_parent !== null) {
_parent.children.push(this);
}
@@ -2003,7 +2006,7 @@ class ParseNode {
/**
* @type {ParseNode}
*/
get parent(): ParseNode {
get parent(): ParseNode | null {
return this._parent;
}
+7 -8
View File
@@ -24,7 +24,6 @@ import { ASS } from "../types/ass";
import { Style } from "../types/style";
import { Dialogue } from "../types/dialogue";
import { Attachment, AttachmentType } from "../types/attachment";
import { Property, TypedTemplate } from "../types/misc";
import { Map } from "../utility/map";
@@ -55,7 +54,7 @@ export class StreamParser {
private _shouldSwallowBom: boolean = true;
private _currentSection: Section = Section.ScriptInfo;
private _currentAttachment: Attachment = null;
private _currentAttachment: Attachment | null = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
@@ -119,7 +118,7 @@ export class StreamParser {
/**
* @param {string} line
*/
private _onNextLine(line: string): void {
private _onNextLine(line: string | null): void {
if (line === null) {
this.currentSection = Section.EOF;
return;
@@ -291,10 +290,10 @@ export class SrtStreamParser {
private _shouldSwallowBom: boolean = true;
private _currentDialogueNumber: string = null;
private _currentDialogueStart: string = null;
private _currentDialogueEnd: string = null;
private _currentDialogueText: string = null;
private _currentDialogueNumber: string | null = null;
private _currentDialogueStart: string | null = null;
private _currentDialogueEnd: string | null = null;
private _currentDialogueText: string | null = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
@@ -320,7 +319,7 @@ export class SrtStreamParser {
/**
* @param {string} line
*/
private _onNextLine(line: string): void {
private _onNextLine(line: string | null): void {
if (line === null) {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map([
+17 -17
View File
@@ -27,7 +27,7 @@ export interface ReadableStream {
getReader(): ReadableStreamReader;
}
interface ReadableStreamReader {
export interface ReadableStreamReader {
/**
* @return {!Promise.<{ value?: Uint8Array, done: boolean }>}
*/
@@ -65,7 +65,7 @@ export interface Stream {
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string>;
nextLine(): Promise<string | null>;
}
/**
@@ -81,8 +81,8 @@ export class StringStream implements Stream {
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the string has been completely read.
*/
nextLine(): Promise<string> {
let result: Promise<string> = null;
nextLine(): Promise<string | null> {
let result: Promise<string | null>;
if (this._readTill < this._str.length) {
const nextNewLinePos = this._str.indexOf("\n", this._readTill);
@@ -96,7 +96,7 @@ export class StringStream implements Stream {
}
}
else {
result = Promise.resolve<string>(null);
result = Promise.resolve<string | null>(null);
}
return result;
@@ -111,8 +111,8 @@ export class StringStream implements Stream {
*/
export class XhrStream implements Stream {
private _readTill: number = 0;
private _pendingDeferred: DeferredPromise<string> = null;
private _failedError: ErrorEvent = null;
private _pendingDeferred: DeferredPromise<string | null> | null = null;
private _failedError: ErrorEvent | null = null;
constructor(private _xhr: XMLHttpRequest) {
_xhr.addEventListener("progress", () => this._onXhrProgress(), false);
@@ -181,7 +181,7 @@ export class XhrStream implements Stream {
*/
private _tryResolveNextLine(): void {
if (this._failedError !== null) {
this._pendingDeferred.reject(this._failedError);
this._pendingDeferred!.reject(this._failedError);
return;
}
@@ -189,23 +189,23 @@ export class XhrStream implements Stream {
const nextNewLinePos = response.indexOf("\n", this._readTill);
if (nextNewLinePos !== -1) {
this._pendingDeferred.resolve(response.substring(this._readTill, nextNewLinePos));
this._pendingDeferred!.resolve(response.substring(this._readTill, nextNewLinePos));
this._readTill = nextNewLinePos + 1;
this._pendingDeferred = null;
}
else if (this._xhr.readyState === XMLHttpRequest.DONE) {
if (this._failedError !== null) {
this._pendingDeferred.reject(this._failedError);
this._pendingDeferred!.reject(this._failedError);
}
// No more data. This is the last line.
else if (this._readTill < response.length) {
this._pendingDeferred.resolve(response.substr(this._readTill));
this._pendingDeferred!.resolve(response.substr(this._readTill));
this._readTill = response.length;
}
else {
this._pendingDeferred.resolve(null);
this._pendingDeferred!.resolve(null);
}
this._pendingDeferred = null;
@@ -223,11 +223,11 @@ export class BrowserReadableStream implements Stream {
private _reader: ReadableStreamReader;
private _decoder: TextDecoder;
private _buffer: string = "";
private _pendingDeferred: DeferredPromise<string> = null;
private _pendingDeferred: DeferredPromise<string | null> | null = null;
constructor(stream: ReadableStream, encoding: string) {
this._reader = stream.getReader();
this._decoder = new global.TextDecoder(encoding, { ignoreBOM: true });
this._decoder = new global.TextDecoder!(encoding, { ignoreBOM: true });
}
/**
@@ -250,7 +250,7 @@ export class BrowserReadableStream implements Stream {
private _tryResolveNextLine(): void {
const nextNewLinePos = this._buffer.indexOf("\n");
if (nextNewLinePos !== -1) {
this._pendingDeferred.resolve(this._buffer.substr(0, nextNewLinePos));
this._pendingDeferred!.resolve(this._buffer.substr(0, nextNewLinePos));
this._buffer = this._buffer.substr(nextNewLinePos + 1);
this._pendingDeferred = null;
}
@@ -266,10 +266,10 @@ export class BrowserReadableStream implements Stream {
else {
// No more data.
if (this._buffer.length === 0) {
this._pendingDeferred.resolve(null);
this._pendingDeferred!.resolve(null);
}
else {
this._pendingDeferred.resolve(this._buffer);
this._pendingDeferred!.resolve(this._buffer);
this._buffer = "";
}
+7 -4
View File
@@ -31,7 +31,7 @@ enum DataType {
Uint32,
}
type StructMemberDefinition = { type: DataType; field?: string; };
type StructMemberDefinition = { type: DataType; field: string; };
const fieldDecorators = new Map<DataType, (proto: any, field: string) => void>();
@@ -103,7 +103,7 @@ export function getTtfNames(attachment: Attachment): Set<string> {
const reader = { dataView: new DataView(bytes.buffer), position: 0 };
const offsetTable = OffsetTable.read(reader);
let nameTableRecord: TableRecord = null;
let nameTableRecord: TableRecord | null = null;
for (let i = 0; i < offsetTable.numTables; i++) {
const tableRecord = TableRecord.read(reader);
if (tableRecord.c1 + tableRecord.c2 + tableRecord.c3 + tableRecord.c4 === "name") {
@@ -111,6 +111,9 @@ export function getTtfNames(attachment: Attachment): Set<string> {
break;
}
}
if (nameTableRecord === null) {
throw new Error('Could not find "name" table record.');
}
reader.position = nameTableRecord.offset;
const nameTableHeader = NameTableHeader.read(reader);
@@ -165,7 +168,7 @@ export function getTtfNames(attachment: Attachment): Set<string> {
* @return {!function(new(): T)}
*/
function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new (): T; read(reader: DataReader): T; } {
const fields: StructMemberDefinition[] = (<any>clazz).__fields;
const fields: StructMemberDefinition[] = (clazz as any).__fields;
clazz.read = (reader: DataReader) => {
const result: any = new clazz();
@@ -205,7 +208,7 @@ function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new ()
function field<T>(type: DataType): (proto: T, field: string) => void {
let existingDecorator = fieldDecorators.get(type);
if (existingDecorator === undefined) {
existingDecorator =(proto: T, field: string) => {
existingDecorator = (proto: T, field: string) => {
const ctor: { __fields?: StructMemberDefinition[] } = proto.constructor;
if (ctor.__fields === undefined) {
ctor.__fields = [];
+36 -36
View File
@@ -183,17 +183,17 @@ export class Italic {
/**
* A bold tag {\b}
*
* @param {*} value {\b1} -> true, {\b0} -> false, {\b###} -> weight of the bold (number), {\b} -> null
* @param {?boolean|?number} value {\b1} -> true, {\b0} -> false, {\b###} -> weight of the bold (number), {\b} -> null
*/
export class Bold {
constructor(private _value: Object) { }
constructor(private _value: boolean | number | null) { }
/**
* The value of this bold tag.
*
* @type {?boolean|?number}
*/
get value(): Object {
get value(): boolean | number | null {
return this._value;
}
}
@@ -384,14 +384,14 @@ export class GaussianBlur {
* @param {?string} value {\fn###} -> name (string), {\fn} -> null
*/
export class FontName {
constructor(private _value: string) { }
constructor(private _value: string | null) { }
/**
* The value of this font name tag.
*
* @type {?string}
*/
get value(): string {
get value(): string | null {
return this._value;
}
}
@@ -417,7 +417,7 @@ export class FontSize {
/**
* A font size increase tag {\fs+}
*
* @param {number} value {\fs+###} -> difference (number)
* @param {number} value {\fs+###} -> relative difference (number, percentage)
*/
export class FontSizePlus {
constructor(private _value: number) { }
@@ -435,7 +435,7 @@ export class FontSizePlus {
/**
* A font size decrease tag {\fs-}
*
* @param {number} value {\fs-###} -> difference (number)
* @param {number} value {\fs-###} -> relative difference (number, percentage)
*/
export class FontSizeMinus {
constructor(private _value: number) { }
@@ -456,14 +456,14 @@ export class FontSizeMinus {
* @param {?number} value {\fscx###} -> scale (number), {\fscx} -> null
*/
export class FontScaleX {
constructor(private _value: number) { }
constructor(private _value: number | null) { }
/**
* The value of this horizontal font scaling tag.
*
* @type {?number}
*/
get value(): number {
get value(): number | null {
return this._value;
}
}
@@ -474,14 +474,14 @@ export class FontScaleX {
* @param {?number} value {\fscy###} -> scale (number), {\fscy} -> null
*/
export class FontScaleY {
constructor(private _value: number) { }
constructor(private _value: number | null) { }
/**
* The value of this vertical font scaling tag.
*
* @type {?number}
*/
get value(): number {
get value(): number | null {
return this._value;
}
}
@@ -571,7 +571,7 @@ export class SkewX {
*
* @type {?number}
*/
get value() {
get value(): number {
return this._value;
}
}
@@ -767,7 +767,7 @@ export class Alignment {
/**
* The value of this alignment tag.
*
* @type {?number}
* @type {number}
*/
get value(): number {
return this._value;
@@ -852,14 +852,14 @@ export class WrappingStyle {
* @param {?string} value {\r###} -> style name (string), {\r} -> null
*/
export class Reset {
constructor(private _value: string) { }
constructor(private _value: string | null) { }
/**
* The value of this style reset tag.
*
* @type {?string}
*/
get value(): string {
get value(): string | null {
return this._value;
}
}
@@ -899,11 +899,11 @@ export class Position {
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} t1
* @param {number} t2
* @param {?number} t1
* @param {?number} t2
*/
export class Move {
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _t1: number, private _t2: number) { }
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _t1: number | null, private _t2: number | null) { }
/**
* The starting x value of this move tag.
@@ -944,18 +944,18 @@ export class Move {
/**
* The start time of this move tag.
*
* @type {number}
* @type {?number}
*/
get t1(): number {
get t1(): number | null {
return this._t1;
}
/**
* The end time value of this move tag.
*
* @type {number}
* @type {?number}
*/
get t2(): number {
get t2(): number | null {
return this._t2;
}
}
@@ -1100,20 +1100,20 @@ export class ComplexFade {
/**
* A transform tag {\t}
*
* @param {number} start
* @param {number} end
* @param {number} accel
* @param {?number} start
* @param {?number} end
* @param {?number} accel
* @param {!Array.<!libjass.parts.Tag>} tags
*/
export class Transform {
constructor(private _start: number, private _end: number, private _accel: number, private _tags: Part[]) { }
constructor(private _start: number | null, private _end: number | null, private _accel: number | null, private _tags: Part[]) { }
/**
* The starting time of this transform tag.
*
* @type {?number}
*/
get start(): number {
get start(): number | null {
return this._start;
}
@@ -1122,7 +1122,7 @@ export class Transform {
*
* @type {?number}
*/
get end(): number {
get end(): number | null {
return this._end;
}
@@ -1131,7 +1131,7 @@ export class Transform {
*
* @type {?number}
*/
get accel(): number {
get accel(): number | null {
return this._accel;
}
@@ -1299,18 +1299,18 @@ const addToString = function (ctor: Function, ctorName: string) {
if (!ctor.prototype.hasOwnProperty("toString")) {
const propertyNames = Object.getOwnPropertyNames(ctor.prototype).filter(property => property !== "constructor");
ctor.prototype.toString = function () {
ctor.prototype.toString = function (this: any) {
return (
ctorName + " { " +
propertyNames.map(name => `${ name }: ${ (<any>this)[name] }`).join(", ") +
propertyNames.map(name => `${ name }: ${ this[name] }`).join(", ") +
((propertyNames.length > 0) ? " " : "") +
"}"
);
}
};
}
};
import { registerClassPrototype } from "../webworker/misc";
import { registerClass } from "../serialization";
declare const exports: any;
@@ -1318,14 +1318,14 @@ for (const key of Object.keys(exports)) {
const value: any = exports[key];
if (value instanceof Function) {
addToString(value, key);
registerClassPrototype(value.prototype);
registerClass(value);
}
}
for (const key of Object.keys(drawing)) {
const value: any = (<any>drawing)[key];
const value: any = (drawing as any)[key];
if (value instanceof Function) {
addToString(value, `Drawing${ key }`);
registerClassPrototype(value.prototype);
registerClass(value);
}
}
+4 -4
View File
@@ -41,10 +41,10 @@ import { ManualClock } from "./manual";
export class AutoClock implements Clock {
private _manualClock: ManualClock = new ManualClock();
private _nextAnimationFrameRequestId: number = null;
private _nextAnimationFrameRequestId: number | null = null;
private _lastKnownExternalTime: number = null;
private _lastKnownExternalTimeObtainedAt: number = null;
private _lastKnownExternalTime: number | null = null;
private _lastKnownExternalTimeObtainedAt: number | null = null;
constructor(private _getCurrentTime: () => number, private _autoPauseAfter: number) { }
@@ -214,7 +214,7 @@ export class AutoClock implements Clock {
if (this._lastKnownExternalTime !== null && currentExternalTime === this._lastKnownExternalTime) {
if (timeStamp - this._lastKnownExternalTimeObtainedAt > this._autoPauseAfter) {
this._lastKnownExternalTimeObtainedAt = null;
this._manualClock.pause();
this._manualClock.seek(currentExternalTime);
}
else {
this._manualClock.tick((timeStamp - this._lastKnownExternalTimeObtainedAt) / 1000 * this._manualClock.rate + this._lastKnownExternalTime);
+15 -8
View File
@@ -22,7 +22,7 @@ import { Clock, ClockEvent } from "./clocks/base";
import { RendererSettings } from "./settings";
import { verboseMode } from "../settings";
import { debugMode, verboseMode } from "../settings";
import { ASS } from "../types/ass";
import { Dialogue } from "../types/dialogue";
@@ -159,14 +159,21 @@ export class NullRenderer {
}
for (const dialogue of this._ass.dialogues) {
if (dialogue.end > currentTime) {
if (dialogue.start <= currentTime) {
// This dialogue is visible right now. Draw it.
this.draw(dialogue);
try {
if (dialogue.end > currentTime) {
if (dialogue.start <= currentTime) {
// This dialogue is visible right now. Draw it.
this.draw(dialogue);
}
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
// This dialogue will be visible soon. Pre-render it.
this.preRender(dialogue);
}
}
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
// This dialogue will be visible soon. Pre-render it.
this.preRender(dialogue);
}
catch (ex) {
if (debugMode) {
console.error(`Rendering dialogue ${ dialogue.id } failed.`, ex);
}
}
}
@@ -18,15 +18,11 @@
* limitations under the License.
*/
define(["intern/dojo/node!firefox-profile"], function (FirefoxProfile) {
return {
load: function (id, require, callback) {
var firefoxProfile = new FirefoxProfile();
firefoxProfile.setPreference("browser.displayedE10SPrompt.1", 5);
firefoxProfile.setPreference("browser.tabs.remote.autostart.2", false);
firefoxProfile.encoded(callback);
}
};
});
interface Array<T> {
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
}
+77 -6
View File
@@ -18,6 +18,7 @@
* limitations under the License.
*/
import { debugMode } from "../settings";
import { Map } from "../utility/map";
/**
@@ -42,9 +43,9 @@ export class RendererSettings {
*
* Defaults to null.
*
* @type {!Map.<string, (string|!Array.<string>)>}
* @type {Map.<string, (string|!Array.<string>)>}
*/
fontMap: Map<string, string | string[]>;
fontMap: Map<string, string | string[]> | null;
/**
* Subtitles will be pre-rendered for this amount of time (seconds).
@@ -107,24 +108,26 @@ export class RendererSettings {
* src: url("/fonts/helvetica.ttf"), local("Arial");
* }
*
* More complicated @font-face syntax like format() or multi-line src are not supported.
*
* @param {!LinkStyle} linkStyle
* @return {!Map.<string, string>}
*/
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string> {
const fontMap = new Map<string, string>();
const styleSheet = <CSSStyleSheet>linkStyle.sheet;
const styleSheet = linkStyle.sheet as CSSStyleSheet;
for (let i = 0; i < styleSheet.cssRules.length; i++) {
const rule = styleSheet.cssRules[i];
if (isFontFaceRule(rule)) {
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)[1];
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)![1];
let src = rule.style.getPropertyValue("src");
if (!src) {
src = rule.cssText.split("\n")
.map(line => line.match(/src:\s*([^;]+?)\s*;/))
.filter(matches => matches !== null)
.filter((matches): matches is RegExpMatchArray => matches !== null)
.map(matches => matches[1])[0];
}
@@ -146,7 +149,15 @@ export class RendererSettings {
object = {};
}
const { fontMap = null, preRenderTime = 5, preciseOutlines = false, enableSvg = true, fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"', useAttachedFonts = false } = <RendererSettings>object;
const {
fontMap = null,
preRenderTime = 5,
preciseOutlines = false,
enableSvg = testSupportsSvg(),
fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"',
useAttachedFonts = false,
} = object as RendererSettings;
const result = new RendererSettings();
result.fontMap = fontMap;
result.preRenderTime = preRenderTime;
@@ -166,3 +177,63 @@ export class RendererSettings {
function isFontFaceRule(rule: CSSRule): rule is CSSFontFaceRule {
return rule.type === CSSRule.FONT_FACE_RULE;
}
/**
* Returns true if this environment may support SVG filter effects. May return false positives.
*
* @return {boolean}
*/
function testSupportsSvg(): boolean {
if (debugMode) {
console.log("Testing whether SVG filter effects are supported.");
}
if (typeof document === "undefined") {
if (debugMode) {
console.log("This doesn't look like a browser. Assuming it doesn't support SVG filter effects.");
}
return false;
}
const morphologyFilter = document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
// https://connect.microsoft.com/IE/feedback/details/2375800
try {
morphologyFilter.radiusX.baseVal = 1;
}
catch (ex) {
if (debugMode) {
if (ex instanceof DOMException) {
const domException = ex as DOMException;
if (domException.code === DOMException.NO_MODIFICATION_ALLOWED_ERR) {
console.log("Setting SVGFEMorphologyElement.radiusX.baseVal threw NoModificationAllowedError. This browser doesn't support SVG DOM correctly.");
}
else {
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected DOMException code ${ domException.code }. This browser doesn't support SVG DOM correctly.`);
}
}
else {
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected exception ${ ex }. This browser doesn't support SVG SVG DOM correctly.`);
}
}
return false;
}
// https://connect.microsoft.com/IE/feedback/details/2375757
morphologyFilter.setAttribute("radius", "1");
if (morphologyFilter.cloneNode().getAttribute("radius") !== "1") {
if (debugMode) {
console.log("SVGFEMorphologyElement's radius attribute was corrupted when cloned. This browser doesn't support SVG DOM correctly.");
}
return false;
}
if (debugMode) {
console.log("This browser may support SVG filter effects.");
}
return true;
}
+6 -2
View File
@@ -71,8 +71,8 @@ export class AnimationCollection {
* @param {!Array.<!libjass.renderers.Keyframe>} keyframes
*/
add(timingFunction: string, keyframes: Keyframe[]): void {
let start: number = null;
let end: number = null;
let start: number | null = null;
let end: number | null = null;
for (const keyframe of keyframes) {
if (start === null) {
@@ -82,6 +82,10 @@ export class AnimationCollection {
end = keyframe.time;
}
if (start === null || end === null) {
throw new Error("Atleast one keyframe must be provided.");
}
let ruleCssText = "";
for (const keyframe of keyframes) {
+48 -15
View File
@@ -54,43 +54,76 @@ export class DrawingStyles {
* @return {!SVGSVGElement}
*/
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGSVGElement {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("version", "1.1");
if (drawingInstructions.instructions.length === 0) {
return svg;
}
const scaleFactor = Math.pow(2, this._scale - 1);
const scaleX = this._outputScaleX / scaleFactor;
const scaleY = this._outputScaleY / scaleFactor;
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
let pathString = "";
let bboxWidth = 0;
let bboxHeight = 0;
let bboxMinX = Infinity;
let bboxMaxX = -Infinity;
let bboxMinY = Infinity;
let bboxMaxY = -Infinity;
for (const instruction of drawingInstructions.instructions) {
if (instruction instanceof parts.drawing.MoveInstruction) {
path.pathSegList.appendItem(path.createSVGPathSegMovetoAbs(instruction.x, instruction.y + this._baselineOffset));
bboxWidth = Math.max(bboxWidth, instruction.x);
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
pathString += ` M ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x);
bboxMaxX = Math.max(bboxMaxX, instruction.x);
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.LineInstruction) {
path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(instruction.x, instruction.y + this._baselineOffset));
bboxWidth = Math.max(bboxWidth, instruction.x);
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
pathString += ` L ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x);
bboxMaxX = Math.max(bboxMaxX, instruction.x);
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.CubicBezierCurveInstruction) {
path.pathSegList.appendItem(path.createSVGPathSegCurvetoCubicAbs(instruction.x3, instruction.y3 + this._baselineOffset, instruction.x1, instruction.y1 + this._baselineOffset, instruction.x2, instruction.y2 + this._baselineOffset));
bboxWidth = Math.max(bboxWidth, instruction.x1, instruction.x2, instruction.x3);
bboxHeight = Math.max(bboxHeight, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
pathString += ` C ${ instruction.x1.toFixed(3) } ${ (instruction.y1 + this._baselineOffset).toFixed(3) } ${ instruction.x2.toFixed(3) } ${ (instruction.y2 + this._baselineOffset).toFixed(3) } ${ instruction.x3.toFixed(3) } ${ (instruction.y3 + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x1, instruction.x2, instruction.x3);
bboxMaxX = Math.max(bboxMaxX, instruction.x1, instruction.x2, instruction.x3);
bboxMinY = Math.min(bboxMinY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
}
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("version", "1.1");
svg.width.baseVal.valueAsString = `${ (bboxWidth * scaleX).toFixed(3) }px`;
svg.height.baseVal.valueAsString = `${ (bboxHeight * scaleY).toFixed(3) }px`;
bboxMinX *= scaleX;
bboxMaxX *= scaleX;
bboxMinY *= scaleY;
bboxMaxY *= scaleY;
const bboxWidth = bboxMaxX - bboxMinX;
const bboxHeight = bboxMaxY - bboxMinY;
svg.width.baseVal.valueAsString = `${ bboxWidth.toFixed(3) }px`;
svg.height.baseVal.valueAsString = `${ bboxHeight.toFixed(3) }px`;
// svg.viewBox.baseVal is null in atleast FF. See https://bugzilla.mozilla.org/show_bug.cgi?id=888307 which justifies it with SVG 1.2 spec.
svg.setAttribute("viewBox", `${ bboxMinX } ${ bboxMinY } ${ bboxWidth } ${ bboxHeight }`);
svg.style.position = "relative";
svg.style.left = `${ bboxMinX.toFixed(3) }px`;
svg.style.top = `${ bboxMinY.toFixed(3) }px`;
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
svg.appendChild(g);
g.setAttribute("transform", `scale(${ scaleX.toFixed(3) } ${ scaleY.toFixed(3) })`);
g.appendChild(path);
path.setAttribute("d", pathString);
path.setAttribute("fill", fillColor.toString());
return svg;
+252 -91
View File
@@ -111,8 +111,8 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
super(ass, clock, (() => {
if (!(_libjassSubsWrapper instanceof HTMLDivElement)) {
const temp = settings;
settings = <any>_libjassSubsWrapper;
_libjassSubsWrapper = <any>temp;
settings = _libjassSubsWrapper as any;
_libjassSubsWrapper = temp as any;
console.warn("WebRenderer's constructor now takes libjassSubsWrapper as the third parameter and settings as the fourth parameter. Please update the caller.");
}
@@ -149,7 +149,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
return;
}
let ttfNames: Set<string> = null;
let ttfNames: Set<string>;
try {
ttfNames = getTtfNames(attachment);
}
@@ -215,7 +215,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
});
let fontFetchPromise: Promise<FontFace>;
let fontFetchPromise: Promise<FontFace | null>;
if (existingFontFaces.length === 0) {
const fontFace = new FontFace(fontFamily, source);
const quotedFontFace = new FontFace(`"${ fontFamily }"`, source);
@@ -223,7 +223,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
global.document.fonts.add(fontFace);
global.document.fonts.add(quotedFontFace);
fontFetchPromise = Promise_any([fontFace.load(), quotedFontFace.load()])
fontFetchPromise = Promise_any([fontFace.load(), quotedFontFace.load()]);
}
else {
fontFetchPromise = Promise_any(existingFontFaces.map(fontFace => fontFace.load()));
@@ -254,7 +254,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
// A url() URL. Extract the raw URL.
return match[2];
}).filter(url => url !== null);
}).filter((url): url is string => url !== null);
const attachedFontUrls = attachedFontsMap.get(fontFamily);
if (attachedFontUrls !== undefined) {
@@ -266,7 +266,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
let fontFetchPromise = fontFetchPromisesCache.get(url);
if (fontFetchPromise === undefined) {
fontFetchPromise =
new Promise<void>((resolve, reject) => {
new Promise<null>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
if (debugMode) {
@@ -290,7 +290,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
const allFontsFetchedPromise =
(thisFontFamilysFetchPromises.length === 0) ?
Promise.resolve<void>(null) :
Promise.resolve(null) :
Promise_first(thisFontFamilysFetchPromises).catch(reason => {
console.warn(`Fetching fonts for ${ fontFamily } at ${ urls.join(", ") } failed: %o`, reason);
return null;
@@ -354,12 +354,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
* @param {!libjass.Dialogue} dialogue
* @return {PreRenderedSub}
*/
preRender(dialogue: Dialogue): PreRenderedSub {
const alreadyPreRenderedSub = this._preRenderedSubs.get(dialogue.id);
if (alreadyPreRenderedSub) {
return alreadyPreRenderedSub;
}
preRender(dialogue: Dialogue): PreRenderedSub | null {
const currentTimeRelativeToDialogueStart = this.clock.currentTime - dialogue.start;
if (dialogue.containsTransformTag && currentTimeRelativeToDialogueStart < 0) {
@@ -367,6 +362,11 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
return null;
}
const alreadyPreRenderedSub = this._preRenderedSubs.get(dialogue.id);
if (alreadyPreRenderedSub) {
return alreadyPreRenderedSub;
}
const sub = document.createElement("div");
sub.style.marginLeft = `${ (this._scaleX * dialogue.style.marginLeft).toFixed(3) }px`;
@@ -389,15 +389,15 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
const svgDefsElement = document.createElementNS("http://www.w3.org/2000/svg", "defs");
svgElement.appendChild(svgDefsElement);
let currentSpan: HTMLSpanElement = null;
let currentSpan: HTMLSpanElement | null = null;
const currentSpanStyles = new SpanStyles(this, dialogue, this._scaleX, this._scaleY, this.settings, this._fontSizeElement, svgDefsElement, this._fontMetricsCache);
let currentAnimationCollection: AnimationCollection = null;
let currentAnimationCollection: AnimationCollection | null = null;
let previousAddNewLine = false; // If two or more \N's are encountered in sequence, then all but the first will be created using currentSpanStyles.makeNewLine() instead
const startNewSpan = (addNewLine: boolean): void => {
if (currentSpan !== null && currentSpan.hasChildNodes()) {
sub.appendChild(currentSpanStyles.setStylesOnSpan(currentSpan, currentAnimationCollection));
sub.appendChild(currentSpanStyles.setStylesOnSpan(currentSpan, currentAnimationCollection!));
}
if (currentAnimationCollection !== null) {
@@ -486,11 +486,11 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
else if (part instanceof parts.FontSizePlus) {
currentSpanStyles.fontSize += part.value;
currentSpanStyles.fontSize *= (1 + part.value);
}
else if (part instanceof parts.FontSizeMinus) {
currentSpanStyles.fontSize -= part.value;
currentSpanStyles.fontSize *= (1 - part.value);
}
else if (part instanceof parts.FontScaleX) {
@@ -571,11 +571,11 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
else if (part instanceof parts.ColorKaraoke) {
startNewSpan(false);
currentAnimationCollection.add("step-end", [
currentAnimationCollection!.add("step-end", [
new Keyframe(0, new Map([
["color", currentSpanStyles.secondaryColor.withAlpha(currentSpanStyles.secondaryAlpha).toString()],
["color", currentSpanStyles.secondaryColor!.withAlpha(currentSpanStyles.secondaryAlpha!).toString()],
])), new Keyframe(karaokeTimesAccumulator, new Map([
["color", currentSpanStyles.primaryColor.withAlpha(currentSpanStyles.primaryAlpha).toString()],
["color", currentSpanStyles.primaryColor!.withAlpha(currentSpanStyles.primaryAlpha!).toString()],
]))
]);
@@ -587,7 +587,13 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
else if (part instanceof parts.Reset) {
currentSpanStyles.reset(this.ass.styles.get(part.value));
const newStyleName = part.value;
if (newStyleName === null) {
currentSpanStyles.reset(null);
}
else {
currentSpanStyles.reset(this.ass.styles.get(newStyleName));
}
}
else if (part instanceof parts.Position) {
@@ -601,10 +607,10 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
dialogueAnimationCollection.add("linear", [new Keyframe(0, new Map([
["left", `${ (this._scaleX * part.x1).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y1).toFixed(3) }px`],
])), new Keyframe(part.t1, new Map([
])), new Keyframe(part.t1!, new Map([
["left", `${ (this._scaleX * part.x1).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y1).toFixed(3) }px`],
])), new Keyframe(part.t2, new Map([
])), new Keyframe(part.t2!, new Map([
["left", `${ (this._scaleX * part.x2).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y2).toFixed(3) }px`],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
@@ -645,124 +651,259 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
const progression =
(currentTimeRelativeToDialogueStart <= part.start) ? 0 :
(currentTimeRelativeToDialogueStart >= part.end) ? 1 :
Math.pow((currentTimeRelativeToDialogueStart - part.start) / (part.end - part.start), part.accel);
Math.pow((currentTimeRelativeToDialogueStart - part.start) / (part.end - part.start), part.accel!);
for (const tag of part.tags) {
if (tag instanceof parts.Border) {
currentSpanStyles.outlineWidth += progression * (tag.value - currentSpanStyles.outlineWidth);
currentSpanStyles.outlineHeight += progression * (tag.value - currentSpanStyles.outlineHeight);
if (tag.value !== null) {
currentSpanStyles.outlineWidth += progression * (tag.value - currentSpanStyles.outlineWidth);
currentSpanStyles.outlineHeight += progression * (tag.value - currentSpanStyles.outlineHeight);
}
else {
currentSpanStyles.outlineWidth = null;
currentSpanStyles.outlineHeight = null;
}
}
else if (tag instanceof parts.BorderX) {
currentSpanStyles.outlineWidth += progression * (tag.value - currentSpanStyles.outlineWidth);
if (tag.value !== null) {
currentSpanStyles.outlineWidth += progression * (tag.value - currentSpanStyles.outlineWidth);
}
else {
currentSpanStyles.outlineWidth = null;
}
}
else if (tag instanceof parts.BorderY) {
currentSpanStyles.outlineHeight += progression * (tag.value - currentSpanStyles.outlineHeight);
if (tag.value !== null) {
currentSpanStyles.outlineHeight += progression * (tag.value - currentSpanStyles.outlineHeight);
}
else {
currentSpanStyles.outlineHeight = null;
}
}
else if (tag instanceof parts.Shadow) {
currentSpanStyles.shadowDepthX += progression * (tag.value - currentSpanStyles.shadowDepthX);
currentSpanStyles.shadowDepthY += progression * (tag.value - currentSpanStyles.shadowDepthY);
if (tag.value !== null) {
currentSpanStyles.shadowDepthX += progression * (tag.value - currentSpanStyles.shadowDepthX);
currentSpanStyles.shadowDepthY += progression * (tag.value - currentSpanStyles.shadowDepthY);
}
else {
currentSpanStyles.shadowDepthX = null;
currentSpanStyles.shadowDepthY = null;
}
}
else if (tag instanceof parts.ShadowX) {
currentSpanStyles.shadowDepthX += progression * (tag.value - currentSpanStyles.shadowDepthX);
if (tag.value !== null) {
currentSpanStyles.shadowDepthX += progression * (tag.value - currentSpanStyles.shadowDepthX);
}
else {
currentSpanStyles.shadowDepthX = null;
}
}
else if (tag instanceof parts.ShadowY) {
currentSpanStyles.shadowDepthY += progression * (tag.value - currentSpanStyles.shadowDepthY);
if (tag.value !== null) {
currentSpanStyles.shadowDepthY += progression * (tag.value - currentSpanStyles.shadowDepthY);
}
else {
currentSpanStyles.shadowDepthY = null;
}
}
else if (tag instanceof parts.Blur) {
currentSpanStyles.blur += progression * (tag.value - currentSpanStyles.blur);
if (tag.value !== null) {
currentSpanStyles.blur += progression * (tag.value - currentSpanStyles.blur);
}
else {
currentSpanStyles.blur = null;
}
}
else if (tag instanceof parts.GaussianBlur) {
currentSpanStyles.gaussianBlur += progression * (tag.value - currentSpanStyles.gaussianBlur);
if (tag.value !== null) {
currentSpanStyles.gaussianBlur += progression * (tag.value - currentSpanStyles.gaussianBlur);
}
else {
currentSpanStyles.gaussianBlur = null;
}
}
else if (tag instanceof parts.FontSize) {
currentSpanStyles.fontSize += progression * (tag.value - currentSpanStyles.fontSize);
if (tag.value !== null) {
currentSpanStyles.fontSize += progression * (tag.value - currentSpanStyles.fontSize);
}
else {
currentSpanStyles.fontSize = null;
}
}
else if (tag instanceof parts.FontSizePlus) {
currentSpanStyles.fontSize += progression * tag.value;
currentSpanStyles.fontSize *= (1 + progression * tag.value);
}
else if (tag instanceof parts.FontSizeMinus) {
currentSpanStyles.fontSize -= progression * tag.value;
currentSpanStyles.fontSize *= (1 - progression * tag.value);
}
else if (tag instanceof parts.FontScaleX) {
currentSpanStyles.fontScaleX += progression * (tag.value - currentSpanStyles.fontScaleX);
if (tag.value !== null) {
currentSpanStyles.fontScaleX += progression * (tag.value - currentSpanStyles.fontScaleX);
}
else {
currentSpanStyles.fontScaleX = null;
}
}
else if (tag instanceof parts.FontScaleY) {
currentSpanStyles.fontScaleY += progression * (tag.value - currentSpanStyles.fontScaleY);
if (tag.value !== null) {
currentSpanStyles.fontScaleY += progression * (tag.value - currentSpanStyles.fontScaleY);
}
else {
currentSpanStyles.fontScaleY = null;
}
}
else if (tag instanceof parts.LetterSpacing) {
currentSpanStyles.letterSpacing += progression * (tag.value - currentSpanStyles.letterSpacing);
if (tag.value !== null) {
currentSpanStyles.letterSpacing += progression * (tag.value - currentSpanStyles.letterSpacing);
}
else {
currentSpanStyles.letterSpacing = null;
}
}
else if (tag instanceof parts.RotateX) {
currentSpanStyles.rotationX += progression * (tag.value - currentSpanStyles.rotationX);
if (tag.value !== null) {
currentSpanStyles.rotationX += progression * (tag.value - currentSpanStyles.rotationX);
}
else {
currentSpanStyles.rotationX = null;
}
}
else if (tag instanceof parts.RotateY) {
currentSpanStyles.rotationY += progression * (tag.value - currentSpanStyles.rotationY);
if (tag.value !== null) {
currentSpanStyles.rotationY += progression * (tag.value - currentSpanStyles.rotationY);
}
else {
currentSpanStyles.rotationY = null;
}
}
else if (tag instanceof parts.RotateZ) {
currentSpanStyles.rotationZ += progression * (tag.value - currentSpanStyles.rotationZ);
if (tag.value !== null) {
currentSpanStyles.rotationZ += progression * (tag.value - currentSpanStyles.rotationZ);
}
else {
currentSpanStyles.rotationZ = null;
}
}
else if (tag instanceof parts.SkewX) {
currentSpanStyles.skewX += progression * (tag.value - currentSpanStyles.skewX);
if (tag.value !== null) {
currentSpanStyles.skewX += progression * (tag.value - currentSpanStyles.skewX);
}
else {
currentSpanStyles.skewX = null;
}
}
else if (tag instanceof parts.SkewY) {
currentSpanStyles.skewY += progression * (tag.value - currentSpanStyles.skewY);
if (tag.value !== null) {
currentSpanStyles.skewY += progression * (tag.value - currentSpanStyles.skewY);
}
else {
currentSpanStyles.skewY = null;
}
}
else if (tag instanceof parts.PrimaryColor) {
currentSpanStyles.primaryColor = currentSpanStyles.primaryColor.interpolate(tag.value, progression);
if (tag.value !== null) {
currentSpanStyles.primaryColor = currentSpanStyles.primaryColor!.interpolate(tag.value, progression);
}
else {
currentSpanStyles.primaryColor = null;
}
}
else if (tag instanceof parts.SecondaryColor) {
currentSpanStyles.secondaryColor = currentSpanStyles.secondaryColor.interpolate(tag.value, progression);
if (tag.value !== null) {
currentSpanStyles.secondaryColor = currentSpanStyles.secondaryColor!.interpolate(tag.value, progression);
}
else {
currentSpanStyles.secondaryColor = null;
}
}
else if (tag instanceof parts.OutlineColor) {
currentSpanStyles.outlineColor = currentSpanStyles.outlineColor.interpolate(tag.value, progression);
if (tag.value !== null) {
currentSpanStyles.outlineColor = currentSpanStyles.outlineColor!.interpolate(tag.value, progression);
}
else {
currentSpanStyles.outlineColor = null;
}
}
else if (tag instanceof parts.ShadowColor) {
currentSpanStyles.shadowColor = currentSpanStyles.shadowColor.interpolate(tag.value, progression);
if (tag.value !== null) {
currentSpanStyles.shadowColor = currentSpanStyles.shadowColor!.interpolate(tag.value, progression);
}
else {
currentSpanStyles.shadowColor = null;
}
}
else if (tag instanceof parts.Alpha) {
currentSpanStyles.primaryAlpha += progression * (tag.value - currentSpanStyles.primaryAlpha);
currentSpanStyles.secondaryAlpha += progression * (tag.value - currentSpanStyles.secondaryAlpha);
currentSpanStyles.outlineAlpha += progression * (tag.value - currentSpanStyles.outlineAlpha);
currentSpanStyles.shadowAlpha += progression * (tag.value - currentSpanStyles.shadowAlpha);
if (tag.value !== null) {
currentSpanStyles.primaryAlpha += progression * (tag.value - currentSpanStyles.primaryAlpha);
currentSpanStyles.secondaryAlpha += progression * (tag.value - currentSpanStyles.secondaryAlpha);
currentSpanStyles.outlineAlpha += progression * (tag.value - currentSpanStyles.outlineAlpha);
currentSpanStyles.shadowAlpha += progression * (tag.value - currentSpanStyles.shadowAlpha);
}
else {
currentSpanStyles.primaryAlpha = null;
currentSpanStyles.secondaryAlpha = null;
currentSpanStyles.outlineAlpha = null;
currentSpanStyles.shadowAlpha = null;
}
}
else if (tag instanceof parts.PrimaryAlpha) {
currentSpanStyles.primaryAlpha += progression * (tag.value - currentSpanStyles.primaryAlpha);
if (tag.value !== null) {
currentSpanStyles.primaryAlpha += progression * (tag.value - currentSpanStyles.primaryAlpha);
}
else {
currentSpanStyles.primaryAlpha = null;
}
}
else if (tag instanceof parts.SecondaryAlpha) {
currentSpanStyles.secondaryAlpha += progression * (tag.value - currentSpanStyles.secondaryAlpha);
if (tag.value !== null) {
currentSpanStyles.secondaryAlpha += progression * (tag.value - currentSpanStyles.secondaryAlpha);
}
else {
currentSpanStyles.secondaryAlpha = null;
}
}
else if (tag instanceof parts.OutlineAlpha) {
currentSpanStyles.outlineAlpha += progression * (tag.value - currentSpanStyles.outlineAlpha);
if (tag.value !== null) {
currentSpanStyles.outlineAlpha += progression * (tag.value - currentSpanStyles.outlineAlpha);
}
else {
currentSpanStyles.outlineAlpha = null;
}
}
else if (tag instanceof parts.ShadowAlpha) {
currentSpanStyles.shadowAlpha += progression * (tag.value - currentSpanStyles.shadowAlpha);
if (tag.value !== null) {
currentSpanStyles.shadowAlpha += progression * (tag.value - currentSpanStyles.shadowAlpha);
}
else {
currentSpanStyles.shadowAlpha = null;
}
}
}
}
@@ -778,17 +919,17 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
else if (part instanceof parts.DrawingInstructions) {
currentSpan.appendChild(currentDrawingStyles.toSVG(part, currentSpanStyles.primaryColor.withAlpha(currentSpanStyles.primaryAlpha)));
currentSpan!.appendChild(currentDrawingStyles.toSVG(part, currentSpanStyles.primaryColor!.withAlpha(currentSpanStyles.primaryAlpha!)));
startNewSpan(false);
}
else if (part instanceof parts.Text) {
currentSpan.appendChild(document.createTextNode(part.value + "\u200C"));
currentSpan!.appendChild(document.createTextNode(part.value + "\u200C"));
startNewSpan(false);
}
else if (debugMode && part instanceof parts.Comment) {
currentSpan.appendChild(document.createTextNode(part.value));
currentSpan!.appendChild(document.createTextNode(part.value));
startNewSpan(false);
}
@@ -797,23 +938,35 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
}
let divTransformStyle = "";
const transformOrigin = WebRenderer._transformOrigins[dialogue.alignment];
const transformOriginString = `${ transformOrigin[0] }% ${ transformOrigin[1] }%`;
for (const part of dialogue.parts) {
if (part instanceof parts.Position || part instanceof parts.Move) {
const transformOrigin = WebRenderer._transformOrigins[dialogue.alignment];
const divTransformStyle = `translate(${ -transformOrigin[0] }%, ${ -transformOrigin[1] }%) translate(-${ sub.style.marginLeft }, -${ sub.style.marginTop })`;
const transformOriginString = `${ transformOrigin[0] }% ${ transformOrigin[1] }%`;
sub.style.webkitTransform = divTransformStyle;
sub.style.webkitTransformOrigin = transformOriginString;
sub.style.transform = divTransformStyle;
sub.style.transformOrigin = transformOriginString;
divTransformStyle = `translate(${ -transformOrigin[0] }%, ${ -transformOrigin[1] }%) translate(-${ sub.style.marginLeft }, -${ sub.style.marginTop })`;
break;
}
}
if (currentSpanStyles.rotationY !== 0) {
divTransformStyle += ` rotateY(${ currentSpanStyles.rotationY }deg)`;
}
if (currentSpanStyles.rotationX !== 0) {
divTransformStyle += ` rotateX(${ currentSpanStyles.rotationX }deg)`;
}
if (currentSpanStyles.rotationZ !== 0) {
divTransformStyle += ` rotateZ(${ -1 * currentSpanStyles.rotationZ }deg)`;
}
sub.style.webkitTransform = divTransformStyle;
sub.style.transform = divTransformStyle;
sub.style.webkitTransformOrigin = transformOriginString;
sub.style.transformOrigin = transformOriginString;
switch (wrappingStyle) {
case WrappingStyle.EndOfLineWrapping:
sub.style.whiteSpace = "pre-wrap";
@@ -876,21 +1029,23 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
console.log(dialogue.toString());
}
let preRenderedSub = this._preRenderedSubs.get(dialogue.id);
let thePreRenderedSub = this._preRenderedSubs.get(dialogue.id);
if (preRenderedSub === undefined) {
preRenderedSub = this.preRender(dialogue);
if (thePreRenderedSub === undefined) {
thePreRenderedSub = this.preRender(dialogue)!;
if (debugMode) {
console.log(dialogue.toString());
}
}
const result = <HTMLDivElement>preRenderedSub.sub.cloneNode(true);
const preRenderedSub = thePreRenderedSub;
const result = preRenderedSub.sub.cloneNode(true);
const applyAnimationDelays = (node: HTMLElement) => {
const animationNames = node.style.animationName || node.style.webkitAnimationName;
if (animationNames !== undefined && animationNames !== "") {
const animationNames = node.style.animationName || node.style.webkitAnimationName || "";
if (animationNames !== "") {
const animationDelays = animationNames.split(",").map(name => {
name = name.trim();
const delay = preRenderedSub.animationDelays.get(name);
@@ -900,11 +1055,11 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
node.style.webkitAnimationDelay = animationDelays;
node.style.animationDelay = animationDelays;
}
}
};
applyAnimationDelays(result);
const animatedDescendants = result.querySelectorAll('[style*="animation:"]');
for (let i = 0; i < animatedDescendants.length; i++) {
applyAnimationDelays(<HTMLElement>animatedDescendants[i]);
applyAnimationDelays(animatedDescendants[i] as HTMLElement);
}
const layer = dialogue.layer;
@@ -916,7 +1071,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
layerWrapper.className = `layer layer${ layer }`;
// Find the next greater layer div and insert this div before that one
let insertBeforeElement: HTMLDivElement = null;
let insertBeforeElement: HTMLDivElement | null = null;
for (let insertBeforeLayer = layer + 1; insertBeforeLayer < this._layerWrappers.length && insertBeforeElement === null; insertBeforeLayer++) {
if (this._layerWrappers[insertBeforeLayer] !== undefined) {
insertBeforeElement = this._layerWrappers[insertBeforeLayer];
@@ -929,13 +1084,14 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
this._layerAlignmentWrappers[layer] = [];
}
if (this._layerAlignmentWrappers[layer][alignment] === undefined) {
const layerAlignmentWrapper = document.createElement("div");
let layerAlignmentWrapper = this._layerAlignmentWrappers[layer][alignment];
if (layerAlignmentWrapper === undefined) {
layerAlignmentWrapper = this._layerAlignmentWrappers[layer][alignment] = document.createElement("div");
layerAlignmentWrapper.className = `an an${ alignment }`;
// Find the next greater layer,alignment div and insert this div before that one
const layerWrapper = this._layerWrappers[layer];
let insertBeforeElement: HTMLDivElement = null;
let insertBeforeElement: HTMLDivElement | null = null;
for (let insertBeforeAlignment = alignment + 1; insertBeforeAlignment < this._layerAlignmentWrappers[layer].length && insertBeforeElement === null; insertBeforeAlignment++) {
if (this._layerAlignmentWrappers[layer][insertBeforeAlignment] !== undefined) {
insertBeforeElement = this._layerAlignmentWrappers[layer][insertBeforeAlignment];
@@ -943,18 +1099,23 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
layerWrapper.insertBefore(layerAlignmentWrapper, insertBeforeElement);
this._layerAlignmentWrappers[layer][alignment] = layerAlignmentWrapper;
}
this._layerAlignmentWrappers[layer][alignment].appendChild(result);
if (alignment >= 1 && alignment <= 3) {
// New subs go above existing subs
layerAlignmentWrapper.insertBefore(result, layerAlignmentWrapper.firstElementChild);
}
else {
// New subs go below existing subs
layerAlignmentWrapper.appendChild(result);
}
// Workaround for IE
const dialogueAnimationStylesElement = result.getElementsByTagName("style")[0];
if (dialogueAnimationStylesElement !== undefined) {
const sheet = <CSSStyleSheet>dialogueAnimationStylesElement.sheet;
const sheet = dialogueAnimationStylesElement.sheet as CSSStyleSheet;
if (sheet.cssRules.length === 0) {
sheet.cssText = dialogueAnimationStylesElement.textContent;
sheet.cssText = dialogueAnimationStylesElement.textContent!;
}
}
@@ -1016,7 +1177,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
*/
private _calculateFontMetricsAfterFetch(fontFamily: string, fontFetchPromise: Promise<any>): Promise<[number, number]> {
return fontFetchPromise.then(() => {
const fontSizeElement = <HTMLDivElement>this._fontSizeElement.cloneNode(true);
const fontSizeElement = this._fontSizeElement.cloneNode(true);
this._libjassSubsWrapper.appendChild(fontSizeElement);
return Promise_finally(calculateFontMetrics(fontFamily, this.settings.fallbackFonts, fontSizeElement), () => {
this._libjassSubsWrapper.removeChild(fontSizeElement);
@@ -1037,7 +1198,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
private static _transformOrigins: number[][] = [
null,
[],
[0, 100], [50, 100], [100, 100],
[0, 50], [50, 50], [100, 50],
[0, 0], [50, 0], [100, 0]
@@ -1062,7 +1223,7 @@ export class WebRenderer extends NullRenderer implements EventSource<string> {
}
mixin(WebRenderer, [EventSource]);
interface PreRenderedSub {
export interface PreRenderedSub {
/** @type {!HTMLDivElement} */
sub: HTMLDivElement;
+321 -207
View File
@@ -51,7 +51,7 @@ export class SpanStyles {
private _defaultStyle: Style;
private _italic: boolean;
private _bold: Object;
private _bold: boolean | number;
private _underline: boolean;
private _strikeThrough: boolean;
@@ -103,7 +103,7 @@ export class SpanStyles {
*
* @param {libjass.Style} newStyle The new defaults to reset the style to. If null, the styles are reset to the default style of the Dialogue.
*/
reset(newStyle: Style): void {
reset(newStyle: Style | undefined | null): void {
if (newStyle === undefined || newStyle === null) {
newStyle = this._defaultStyle;
}
@@ -127,12 +127,12 @@ export class SpanStyles {
this.letterSpacing = newStyle.letterSpacing;
this._rotationX = null;
this._rotationY = null;
this._rotationZ = newStyle.rotationZ;
this.rotationX = 0;
this.rotationY = 0;
this.rotationZ = newStyle.rotationZ;
this._skewX = null;
this._skewY = null;
this.skewX = 0;
this.skewY = 0;
this.primaryColor = newStyle.primaryColor;
this.secondaryColor = newStyle.secondaryColor;
@@ -168,11 +168,9 @@ export class SpanStyles {
else if (this._bold !== false) {
fontStyleOrWeight += this._bold + " ";
}
const fontSize = (
this._scaleY *
fontSizeForLineHeight(this._fontName, this._fontSize * (isTextOnlySpan ? this._fontScaleX : 1), this._settings.fallbackFonts, this._fontSizeElement, this._fontMetricsCache)
).toFixed(3);
const lineHeight = (this._scaleY * this._fontSize).toFixed(3);
const lineHeight = this._scaleY * (isTextOnlySpan ? this._fontScaleX : 1) * this._fontSize;
const fontSize = fontSizeForLineHeight(this._fontName, lineHeight, this._settings.fallbackFonts, this._fontSizeElement, this._fontMetricsCache);
let fonts = this._fontName;
@@ -193,7 +191,7 @@ export class SpanStyles {
fonts += `, ${ this._settings.fallbackFonts }`;
}
span.style.font = `${ fontStyleOrWeight }${ fontSize }px/${ lineHeight }px ${ fonts }`;
span.style.font = `${ fontStyleOrWeight }${ fontSize.toFixed(3) }px/${ lineHeight.toFixed(3) }px ${ fonts }`;
let textDecoration = "";
if (this._underline) {
@@ -218,19 +216,8 @@ export class SpanStyles {
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) {
const skewX = SpanStyles._valueOrDefault(this._skewX, 0);
const skewY = SpanStyles._valueOrDefault(this._skewY, 0);
transform += `matrix(1, ${ skewY }, ${ skewX }, 1, 0, 0) `;
if (this._skewX !== 0 || this._skewY !== 0) {
transform += `matrix(1, ${ this._skewY }, ${ this._skewX }, 1, 0, 0) `;
}
if (transform !== "") {
span.style.webkitTransform = transform;
@@ -244,9 +231,8 @@ export class SpanStyles {
const outlineWidth = this._scaleX * this._outlineWidth;
const outlineHeight = this._scaleY * this._outlineHeight;
const filterWrapperSpan = document.createElement("span");
filterWrapperSpan.appendChild(span);
const shadowDepthX = this._scaleX * this._shadowDepthX;
const shadowDepthY = this._scaleY * this._shadowDepthY;
let primaryColor = this._primaryColor.withAlpha(this._primaryAlpha);
let outlineColor = this._outlineColor.withAlpha(this._outlineAlpha);
@@ -268,55 +254,74 @@ export class SpanStyles {
span.style.color = primaryColor.toString();
if (this._settings.enableSvg) {
this._setSvgOutlineOnSpan(filterWrapperSpan, outlineWidth, outlineHeight, outlineColor, this._primaryAlpha);
this._svg(
span,
outlineWidth, outlineHeight, outlineColor,
shadowDepthX, shadowDepthY, shadowColor
);
}
else {
this._setTextShadowOutlineOnSpan(span, outlineWidth, outlineHeight, outlineColor);
}
if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
const shadowCssString = `${ shadowColor.toString() } ${ (this._shadowDepthX * this._scaleX).toFixed(3) }px ${ (this._shadowDepthY * this._scaleY).toFixed(3) }px 0px`;
if (span.style.textShadow === "") {
span.style.textShadow = shadowCssString;
}
else {
span.style.textShadow += ", " + shadowCssString;
}
this._textShadow(
span,
outlineWidth, outlineHeight, outlineColor,
shadowDepthX, shadowDepthY, shadowColor
);
}
if (this._rotationX !== 0 || this._rotationY !== 0) {
// Perspective needs to be set on a "transformable element"
filterWrapperSpan.style.display = "inline-block";
span.style.display = "inline-block";
}
span.style.webkitAnimation = animationCollection.animationStyle;
span.style.animation = animationCollection.animationStyle;
return filterWrapperSpan;
return span;
}
/**
* @param {!HTMLSpanElement} filterWrapperSpan
* @param {!HTMLSpanElement} span
* @param {number} outlineWidth
* @param {number} outlineHeight
* @param {!libjass.parts.Color} outlineColor
* @param {number} primaryAlpha
* @param {number} shadowDepthX
* @param {number} shadowDepthY
* @param {!libjass.parts.Color} shadowColor
*/
private _setSvgOutlineOnSpan(filterWrapperSpan: HTMLSpanElement, outlineWidth: number, outlineHeight: number, outlineColor: Color, primaryAlpha: number): void {
const filterId = `svg-filter-${ this._id }-${ this._nextFilterId++ }`;
private _svg(
span: HTMLSpanElement,
outlineWidth: number, outlineHeight: number, outlineColor: Color,
shadowDepthX: number, shadowDepthY: number, shadowColor: Color
): void {
const filterElement = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filterElement.id = filterId;
filterElement.x.baseVal.valueAsString = "-50%";
filterElement.width.baseVal.valueAsString = "200%";
filterElement.y.baseVal.valueAsString = "-50%";
filterElement.height.baseVal.valueAsString = "200%";
if (outlineWidth > 0 || outlineHeight > 0) {
if (outlineWidth > 0 || outlineHeight > 0 || shadowDepthX > 0 || shadowDepthY > 0) {
// Start with SourceAlpha. Leave the alpha as 0 if it's 0, and set it to 1 if it's greater than 0
const source = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
filterElement.appendChild(source);
source.in1.baseVal = "SourceAlpha";
source.result.baseVal = "source";
const sourceAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
source.appendChild(sourceAlphaTransferNode);
sourceAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
sourceAlphaTransferNode.intercept.baseVal = 0;
/* The alphas of all colored pixels of the SourceAlpha should be made as close to 1 as possible. This way the summed outlines below will be uniformly dark.
* Multiply the pixels by 1 / primaryAlpha so that the primaryAlpha pixels become 1. A higher value would make the outline larger and too sharp,
* leading to jagged outer edge and transparent space around the inner edge between itself and the SourceGraphic.
*/
sourceAlphaTransferNode.slope.baseVal = (this._primaryAlpha === 0) ? 1 : (1 / this._primaryAlpha);
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
*/
// Merge the individual outlines
const mergedOutlines = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlines);
mergedOutlines.result.baseVal = "outline-alpha";
let outlineNumber = 0;
const increment = (!this._settings.preciseOutlines && this._gaussianBlur > 0) ? this._gaussianBlur : 1;
@@ -351,78 +356,61 @@ export class SpanStyles {
}
}
})((x: number, y: number): void => {
const outlineId = `outline${ outlineNumber }`;
const outlineFilter = document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
filterElement.appendChild(outlineFilter);
filterElement.insertBefore(outlineFilter, mergedOutlines);
outlineFilter.in1.baseVal = "source";
outlineFilter.operator.baseVal = SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_DILATE;
outlineFilter.radiusX.baseVal = x;
outlineFilter.radiusY.baseVal = y;
outlineFilter.result.baseVal = `outline${ outlineNumber }`;
outlineFilter.result.baseVal = outlineId;
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlines.appendChild(outlineReferenceNode);
outlineReferenceNode.in1.baseVal = outlineId;
outlineNumber++;
});
// Start with SourceAlpha. Leave the alpha as 0 if it's 0, and set it to 1 if it's greater than 0
const source = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
filterElement.insertBefore(source, filterElement.firstElementChild);
source.in1.baseVal = "SourceAlpha";
source.result.baseVal = "source";
((addOutline: (x: number, y: number) => void) => {
if ((outlineWidth % 1) > 0) {
addOutline(outlineWidth, 0);
addOutline(-outlineWidth, 0);
}
const sourceAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
source.appendChild(sourceAlphaTransferNode);
sourceAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
if ((outlineHeight % 1) > 0) {
addOutline(0, outlineHeight);
addOutline(0, -outlineHeight);
}
})((x: number, y: number): void => {
const outlineId = `outline${ outlineNumber }`;
/* The alphas of all colored pixels of the SourceAlpha should be made as close to 1 as possible. This way the summed outlines below will be uniformly dark.
* Multiply the pixels by 1 / primaryAlpha so that the primaryAlpha pixels become 1. A higher value would make the outline larger and too sharp,
* leading to jagged outer edge and transparent space around the inner edge between itself and the SourceGraphic.
*/
sourceAlphaTransferNode.slope.baseVal = (primaryAlpha === 0) ? 1 : (1 / primaryAlpha);
const outlineFilter = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
filterElement.insertBefore(outlineFilter, mergedOutlines);
outlineFilter.in1.baseVal = "source";
outlineFilter.dx.baseVal = x;
outlineFilter.dy.baseVal = y;
outlineFilter.result.baseVal = outlineId;
sourceAlphaTransferNode.intercept.baseVal = 0;
// Merge the individual outlines
const mergedOutlines = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlines);
for (let i = 0; i < outlineNumber; i++) {
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlines.appendChild(outlineReferenceNode);
outlineReferenceNode.in1.baseVal = `outline${ i }`;
}
outlineReferenceNode.in1.baseVal = outlineId;
outlineNumber++;
});
// Color it with the outline color
const coloredSource = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
filterElement.appendChild(coloredSource);
coloredSource.setAttribute("color-interpolation-filters", "sRGB");
const outlineRedTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncR");
coloredSource.appendChild(outlineRedTransferNode);
outlineRedTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineRedTransferNode.slope.baseVal = 0;
outlineRedTransferNode.intercept.baseVal = outlineColor.red / 255 * outlineColor.alpha;
const outlineGreenTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncG");
coloredSource.appendChild(outlineGreenTransferNode);
outlineGreenTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineGreenTransferNode.slope.baseVal = 0;
outlineGreenTransferNode.intercept.baseVal = outlineColor.green / 255 * outlineColor.alpha;
const outlineBlueTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
coloredSource.appendChild(outlineBlueTransferNode);
outlineBlueTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineBlueTransferNode.slope.baseVal = 0;
outlineBlueTransferNode.intercept.baseVal = outlineColor.blue / 255 * outlineColor.alpha;
const outlineAlphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
coloredSource.appendChild(outlineAlphaTransferNode);
outlineAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineAlphaTransferNode.slope.baseVal = outlineColor.alpha;
outlineAlphaTransferNode.intercept.baseVal = 0;
const coloredOutline = createComponentTransferFilter(outlineColor);
filterElement.appendChild(coloredOutline);
coloredOutline.in1.baseVal = "outline-alpha";
// Blur the merged outline
if (this._gaussianBlur > 0) {
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
// Don't use setStdDeviation - cloneNode() clears it in Chrome
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
}
@@ -433,21 +421,65 @@ export class SpanStyles {
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
}
// Cut out the source, so only the outline remains
const outlineCutoutNode = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
filterElement.appendChild(outlineCutoutNode);
outlineCutoutNode.in2.baseVal = "source";
outlineCutoutNode.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT;
// Cut out the source, so only the exterior remains
const cutoutNode = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
filterElement.appendChild(cutoutNode);
cutoutNode.in2.baseVal = "source";
cutoutNode.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT;
cutoutNode.result.baseVal = "outline-colored";
// Merge the outline with the SourceGraphic
const mergedOutlineAndSourceGraphic = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlineAndSourceGraphic);
if (shadowDepthX > 0 || shadowDepthY > 0) {
const shadowFilter = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
filterElement.appendChild(shadowFilter);
shadowFilter.in1.baseVal = "outline-alpha";
shadowFilter.dx.baseVal = shadowDepthX;
shadowFilter.dy.baseVal = shadowDepthY;
// Color it with the shadow color
const coloredShadow = createComponentTransferFilter(shadowColor);
filterElement.appendChild(coloredShadow);
let lastFilter: SVGFEComponentTransferElement | SVGFEGaussianBlurElement | SVGFEConvolveMatrixElement = coloredShadow;
// Blur the shadow
if (this._gaussianBlur > 0) {
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
// Don't use setStdDeviation - cloneNode() clears it in Chrome
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
lastFilter = gaussianBlurFilter;
}
for (let i = 0; i < this._blur; i++) {
const blurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
filterElement.appendChild(blurFilter);
blurFilter.setAttribute("kernelMatrix", "1 2 1 2 4 2 1 2 1");
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
lastFilter = blurFilter;
}
lastFilter.result.baseVal = "shadow";
}
// Merge the main text, outline and shadow
const mergedResult = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedResult);
if (shadowDepthX > 0 || shadowDepthY > 0) {
const shadowReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedResult.appendChild(shadowReferenceNode);
shadowReferenceNode.in1.baseVal = "shadow";
}
const outlineReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlineAndSourceGraphic.appendChild(outlineReferenceNode);
mergedResult.appendChild(outlineReferenceNode);
outlineReferenceNode.in1.baseVal = "outline-colored";
const sourceGraphicReferenceNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlineAndSourceGraphic.appendChild(sourceGraphicReferenceNode);
mergedResult.appendChild(sourceGraphicReferenceNode);
sourceGraphicReferenceNode.in1.baseVal = "SourceGraphic";
}
else {
@@ -455,6 +487,8 @@ export class SpanStyles {
if (this._gaussianBlur > 0) {
const gaussianBlurFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
// Don't use setStdDeviation - cloneNode() clears it in Chrome
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
}
@@ -467,10 +501,18 @@ export class SpanStyles {
}
if (filterElement.childElementCount > 0) {
this._svgDefsElement.appendChild(filterElement);
const filterId = `libjass-svg-filter-${ this._id }-${ this._nextFilterId++ }`;
filterWrapperSpan.style.webkitFilter = `url("#${ filterId }")`;
filterWrapperSpan.style.filter = `url("#${ filterId }")`;
this._svgDefsElement.appendChild(filterElement);
filterElement.id = filterId;
filterElement.x.baseVal.valueAsString = "-50%";
filterElement.width.baseVal.valueAsString = "200%";
filterElement.y.baseVal.valueAsString = "-50%";
filterElement.height.baseVal.valueAsString = "200%";
const filterProperty = `url("#${ filterId }")`;
span.style.webkitFilter = filterProperty;
span.style.filter = filterProperty;
}
}
@@ -479,10 +521,18 @@ export class SpanStyles {
* @param {number} outlineWidth
* @param {number} outlineHeight
* @param {!libjass.parts.Color} outlineColor
* @param {number} shadowDepthX
* @param {number} shadowDepthY
* @param {!libjass.parts.Color} shadowColor
*/
private _setTextShadowOutlineOnSpan(span: HTMLSpanElement, outlineWidth: number, outlineHeight: number, outlineColor: Color): void {
private _textShadow(
span: HTMLSpanElement,
outlineWidth: number, outlineHeight: number, outlineColor: Color,
shadowDepthX: number, shadowDepthY: number, shadowColor: Color
): void {
if (outlineWidth > 0 || outlineHeight > 0) {
let outlineCssString = "";
let shadowCssString = "";
((addOutline: (x: number, y: number) => void) => {
for (let x = 0; x <= outlineWidth; x++) {
@@ -503,11 +553,34 @@ export class SpanStyles {
}
}
}
if ((outlineWidth % 1) > 0) {
addOutline(outlineWidth, 0);
addOutline(-outlineWidth, 0);
}
if ((outlineHeight % 1) > 0) {
addOutline(0, outlineHeight);
addOutline(0, -outlineHeight);
}
})((x: number, y: number): void => {
outlineCssString += `, ${ outlineColor.toString() } ${ x }px ${ y }px ${ this._gaussianBlur.toFixed(3) }px`;
outlineCssString += `, ${ outlineColor.toString() } ${ x.toFixed(3) }px ${ y.toFixed(3) }px ${ this._gaussianBlur.toFixed(3) }px`;
if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
shadowCssString += `, ${ shadowColor.toString() } ${ (x + shadowDepthX).toFixed(3) }px ${ (y + shadowDepthY).toFixed(3) }px ${ this._gaussianBlur.toFixed(3) }px`;
}
});
span.style.textShadow = outlineCssString.substr(", ".length);
span.style.textShadow = (outlineCssString + shadowCssString).substr(", ".length);
}
else if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
const shadowCssString = `${ shadowColor.toString() } ${ shadowDepthX.toFixed(3) }px ${ shadowDepthY.toFixed(3) }px 0px`;
if (span.style.textShadow === "") {
span.style.textShadow = shadowCssString;
}
else {
span.style.textShadow += ", " + shadowCssString;
}
}
}
@@ -526,16 +599,16 @@ export class SpanStyles {
* @type {?boolean}
*/
set italic(value: boolean) {
this._italic = SpanStyles._valueOrDefault(value, this._defaultStyle.italic);
this._italic = valueOrDefault(value, this._defaultStyle.italic);
}
/**
* Sets the bold property. null defaults it to the default style's value.
*
* @type {(?number|?boolean)}
* @type {(?boolean|?number)}
*/
set bold(value: Object) {
this._bold = SpanStyles._valueOrDefault(value, this._defaultStyle.bold);
set bold(value: boolean | number | null) {
this._bold = valueOrDefault(value, this._defaultStyle.bold);
}
/**
@@ -544,7 +617,7 @@ export class SpanStyles {
* @type {?boolean}
*/
set underline(value: boolean) {
this._underline = SpanStyles._valueOrDefault(value, this._defaultStyle.underline);
this._underline = valueOrDefault(value, this._defaultStyle.underline);
}
/**
@@ -553,7 +626,7 @@ export class SpanStyles {
* @type {?boolean}
*/
set strikeThrough(value: boolean) {
this._strikeThrough = SpanStyles._valueOrDefault(value, this._defaultStyle.strikeThrough);
this._strikeThrough = valueOrDefault(value, this._defaultStyle.strikeThrough);
}
/**
@@ -561,7 +634,7 @@ export class SpanStyles {
*
* @type {number}
*/
get outlineWidth() {
get outlineWidth(): number | null {
return this._outlineWidth;
}
@@ -570,17 +643,17 @@ export class SpanStyles {
*
* @type {?number}
*/
set outlineWidth(value: number) {
this._outlineWidth = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
set outlineWidth(value: number | null) {
this._outlineWidth = valueOrDefault(value, this._defaultStyle.outlineThickness);
}
/**
* Gets the outline width property.
* Gets the outline height property.
*
* @type {number}
*/
get outlineHeight() {
return this._outlineWidth;
get outlineHeight(): number | null {
return this._outlineHeight;
}
/**
@@ -588,8 +661,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set outlineHeight(value: number) {
this._outlineHeight = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
set outlineHeight(value: number | null) {
this._outlineHeight = valueOrDefault(value, this._defaultStyle.outlineThickness);
}
/**
@@ -597,7 +670,7 @@ export class SpanStyles {
*
* @type {number}
*/
get shadowDepthX() {
get shadowDepthX(): number | null {
return this._shadowDepthX;
}
@@ -606,8 +679,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set shadowDepthX(value: number) {
this._shadowDepthX = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
set shadowDepthX(value: number | null) {
this._shadowDepthX = valueOrDefault(value, this._defaultStyle.shadowDepth);
}
/**
@@ -615,7 +688,7 @@ export class SpanStyles {
*
* @type {number}
*/
get shadowDepthY() {
get shadowDepthY(): number | null {
return this._shadowDepthY;
}
@@ -624,8 +697,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set shadowDepthY(value: number) {
this._shadowDepthY = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
set shadowDepthY(value: number | null) {
this._shadowDepthY = valueOrDefault(value, this._defaultStyle.shadowDepth);
}
/**
@@ -633,7 +706,7 @@ export class SpanStyles {
*
* @type {number}
*/
get blur() {
get blur(): number | null {
return this._blur;
}
@@ -642,8 +715,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set blur(value: number) {
this._blur = SpanStyles._valueOrDefault(value, 0);
set blur(value: number | null) {
this._blur = valueOrDefault(value, 0);
}
/**
@@ -651,7 +724,7 @@ export class SpanStyles {
*
* @type {number}
*/
get gaussianBlur() {
get gaussianBlur(): number | null {
return this._gaussianBlur;
}
@@ -660,8 +733,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set gaussianBlur(value: number) {
this._gaussianBlur = SpanStyles._valueOrDefault(value, 0);
set gaussianBlur(value: number | null) {
this._gaussianBlur = valueOrDefault(value, 0);
}
/**
@@ -669,8 +742,8 @@ export class SpanStyles {
*
* @type {?string}
*/
set fontName(value: string) {
this._fontName = SpanStyles._valueOrDefault(value, this._defaultStyle.fontName);
set fontName(value: string | null) {
this._fontName = valueOrDefault(value, this._defaultStyle.fontName);
}
/**
@@ -678,7 +751,7 @@ export class SpanStyles {
*
* @type {number}
*/
get fontSize() {
get fontSize(): number | null {
return this._fontSize;
}
@@ -687,8 +760,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set fontSize(value: number) {
this._fontSize = SpanStyles._valueOrDefault(value, this._defaultStyle.fontSize);
set fontSize(value: number | null) {
this._fontSize = valueOrDefault(value, this._defaultStyle.fontSize);
}
/**
@@ -696,7 +769,7 @@ export class SpanStyles {
*
* @type {number}
*/
get fontScaleX() {
get fontScaleX(): number | null {
return this._fontScaleX;
}
@@ -705,8 +778,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set fontScaleX(value: number) {
this._fontScaleX = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleX);
set fontScaleX(value: number | null) {
this._fontScaleX = valueOrDefault(value, this._defaultStyle.fontScaleX);
}
/**
@@ -714,7 +787,7 @@ export class SpanStyles {
*
* @type {number}
*/
get fontScaleY() {
get fontScaleY(): number | null {
return this._fontScaleY;
}
@@ -723,8 +796,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set fontScaleY(value: number) {
this._fontScaleY = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleY);
set fontScaleY(value: number | null) {
this._fontScaleY = valueOrDefault(value, this._defaultStyle.fontScaleY);
}
/**
@@ -732,7 +805,7 @@ export class SpanStyles {
*
* @type {number}
*/
get letterSpacing() {
get letterSpacing(): number | null {
return this._letterSpacing;
}
@@ -741,16 +814,16 @@ export class SpanStyles {
*
* @type {?number}
*/
set letterSpacing(value: number) {
this._letterSpacing = SpanStyles._valueOrDefault(value, this._defaultStyle.letterSpacing);
set letterSpacing(value: number | null) {
this._letterSpacing = valueOrDefault(value, this._defaultStyle.letterSpacing);
}
/**
* Gets the X-axis rotation property.
*
* @type {?number}
* @type {number}
*/
get rotationX() {
get rotationX(): number | null {
return this._rotationX;
}
@@ -759,16 +832,16 @@ export class SpanStyles {
*
* @type {?number}
*/
set rotationX(value: number) {
this._rotationX = value;
set rotationX(value: number | null) {
this._rotationX = valueOrDefault(value, 0);
}
/**
* Gets the Y-axis rotation property.
*
* @type {?number}
* @type {number}
*/
get rotationY() {
get rotationY(): number | null {
return this._rotationY;
}
@@ -777,16 +850,16 @@ export class SpanStyles {
*
* @type {?number}
*/
set rotationY(value: number) {
this._rotationY = value;
set rotationY(value: number | null) {
this._rotationY = valueOrDefault(value, 0);
}
/**
* Gets the Z-axis rotation property.
*
* @type {?number}
* @type {number}
*/
get rotationZ() {
get rotationZ(): number | null {
return this._rotationZ;
}
@@ -795,16 +868,16 @@ export class SpanStyles {
*
* @type {?number}
*/
set rotationZ(value: number) {
this._rotationZ = SpanStyles._valueOrDefault(value, this._defaultStyle.rotationZ);
set rotationZ(value: number | null) {
this._rotationZ = valueOrDefault(value, this._defaultStyle.rotationZ);
}
/**
* Gets the X-axis skew property.
*
* @type {?number}
* @type {number}
*/
get skewX() {
get skewX(): number | null {
return this._skewX;
}
@@ -813,16 +886,16 @@ export class SpanStyles {
*
* @type {?number}
*/
set skewX(value: number) {
this._skewX = value;
set skewX(value: number | null) {
this._skewX = valueOrDefault(value, 0);
}
/**
* Gets the Y-axis skew property.
*
* @type {?number}
* @type {number}
*/
get skewY() {
get skewY(): number | null {
return this._skewY;
}
@@ -831,8 +904,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set skewY(value: number) {
this._skewY = value;
set skewY(value: number | null) {
this._skewY = valueOrDefault(value, 0);
}
/**
@@ -840,7 +913,7 @@ export class SpanStyles {
*
* @type {!libjass.Color}
*/
get primaryColor(): Color {
get primaryColor(): Color | null {
return this._primaryColor;
}
@@ -849,8 +922,8 @@ export class SpanStyles {
*
* @type {libjass.Color}
*/
set primaryColor(value: Color) {
this._primaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.primaryColor);
set primaryColor(value: Color | null) {
this._primaryColor = valueOrDefault(value, this._defaultStyle.primaryColor);
}
/**
@@ -858,7 +931,7 @@ export class SpanStyles {
*
* @type {!libjass.Color}
*/
get secondaryColor(): Color {
get secondaryColor(): Color | null {
return this._secondaryColor;
}
@@ -867,8 +940,8 @@ export class SpanStyles {
*
* @type {libjass.Color}
*/
set secondaryColor(value: Color) {
this._secondaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.secondaryColor);
set secondaryColor(value: Color | null) {
this._secondaryColor = valueOrDefault(value, this._defaultStyle.secondaryColor);
}
/**
@@ -876,7 +949,7 @@ export class SpanStyles {
*
* @type {!libjass.Color}
*/
get outlineColor(): Color {
get outlineColor(): Color | null {
return this._outlineColor;
}
@@ -885,8 +958,8 @@ export class SpanStyles {
*
* @type {libjass.Color}
*/
set outlineColor(value: Color) {
this._outlineColor = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineColor);
set outlineColor(value: Color | null) {
this._outlineColor = valueOrDefault(value, this._defaultStyle.outlineColor);
}
/**
@@ -894,7 +967,7 @@ export class SpanStyles {
*
* @type {!libjass.Color}
*/
get shadowColor(): Color {
get shadowColor(): Color | null {
return this._shadowColor;
}
@@ -903,8 +976,8 @@ export class SpanStyles {
*
* @type {libjass.Color}
*/
set shadowColor(value: Color) {
this._shadowColor = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowColor);
set shadowColor(value: Color | null) {
this._shadowColor = valueOrDefault(value, this._defaultStyle.shadowColor);
}
/**
@@ -912,7 +985,7 @@ export class SpanStyles {
*
* @type {number}
*/
get primaryAlpha(): number {
get primaryAlpha(): number | null {
return this._primaryAlpha;
}
@@ -921,8 +994,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set primaryAlpha(value: number) {
this._primaryAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.primaryColor.alpha);
set primaryAlpha(value: number | null) {
this._primaryAlpha = valueOrDefault(value, this._defaultStyle.primaryColor.alpha);
}
/**
@@ -930,7 +1003,7 @@ export class SpanStyles {
*
* @type {number}
*/
get secondaryAlpha(): number {
get secondaryAlpha(): number | null {
return this._secondaryAlpha;
}
@@ -939,8 +1012,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set secondaryAlpha(value: number) {
this._secondaryAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.secondaryColor.alpha);
set secondaryAlpha(value: number | null) {
this._secondaryAlpha = valueOrDefault(value, this._defaultStyle.secondaryColor.alpha);
}
/**
@@ -948,7 +1021,7 @@ export class SpanStyles {
*
* @type {number}
*/
get outlineAlpha(): number {
get outlineAlpha(): number | null {
return this._outlineAlpha;
}
@@ -957,8 +1030,8 @@ export class SpanStyles {
*
* @type {?number}
*/
set outlineAlpha(value: number) {
this._outlineAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineColor.alpha);
set outlineAlpha(value: number | null) {
this._outlineAlpha = valueOrDefault(value, this._defaultStyle.outlineColor.alpha);
}
/**
@@ -966,7 +1039,7 @@ export class SpanStyles {
*
* @type {number}
*/
get shadowAlpha(): number {
get shadowAlpha(): number | null {
return this._shadowAlpha;
}
@@ -975,9 +1048,50 @@ export class SpanStyles {
*
* @type {?number}
*/
set shadowAlpha(value: number) {
this._shadowAlpha = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowColor.alpha);
set shadowAlpha(value: number | null) {
this._shadowAlpha = valueOrDefault(value, this._defaultStyle.shadowColor.alpha);
}
private static _valueOrDefault = <T>(newValue: T, defaultValue: T): T => ((newValue !== null) ? newValue : defaultValue);
}
/**
* @param {!libjass.parts.Color} color
* @return {!SVGFEComponentTransferElement}
*/
function createComponentTransferFilter(color: Color): SVGFEComponentTransferElement {
const result = document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
const redTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncR");
result.appendChild(redTransferNode);
redTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
redTransferNode.slope.baseVal = 0;
redTransferNode.intercept.baseVal = color.red / 255;
const greenTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncG");
result.appendChild(greenTransferNode);
greenTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
greenTransferNode.slope.baseVal = 0;
greenTransferNode.intercept.baseVal = color.green / 255;
const blueTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
result.appendChild(blueTransferNode);
blueTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
blueTransferNode.slope.baseVal = 0;
blueTransferNode.intercept.baseVal = color.blue / 255;
const alphaTransferNode = document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
result.appendChild(alphaTransferNode);
alphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
alphaTransferNode.slope.baseVal = color.alpha;
alphaTransferNode.intercept.baseVal = 0;
return result;
}
/**
* @param {?T} newValue
* @param {!T} defaultValue
* @return {!T}
*/
function valueOrDefault<T>(newValue: T | null, defaultValue: T): T {
return ((newValue !== null) ? newValue : defaultValue);
}
+2 -17
View File
@@ -18,23 +18,8 @@
* limitations under the License.
*/
interface Document {
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "defs"): SVGDefsElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feComponentTransfer"): SVGFEComponentTransferElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feComposite"): SVGFECompositeElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feConvolveMatrix"): SVGFEConvolveMatrixElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncA"): SVGFEFuncAElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncB"): SVGFEFuncBElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncG"): SVGFEFuncGElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feFuncR"): SVGFEFuncRElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feGaussianBlur"): SVGFEGaussianBlurElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMerge"): SVGFEMergeElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMergeNode"): SVGFEMergeNodeElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "feMorphology"): SVGFEMorphologyElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "filter"): SVGFilterElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "g"): SVGGElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "path"): SVGPathElement;
createElementNS(namespaceURI: "http://www.w3.org/2000/svg", qualifiedName: "svg"): SVGSVGElement;
interface Node {
cloneNode(deep?: boolean): this;
}
interface SVGFEComponentTransferElement {
+78
View File
@@ -0,0 +1,78 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "./utility/map";
const classes = new Map<number, Function & { fromJSON?: (obj: any) => any }>();
/**
* Registers a class as a serializable type.
*
* @param {function(new:*)} clazz
*/
export function registerClass(clazz: Function & { fromJSON?: (obj: any) => any }): void {
clazz.prototype._classTag = classes.size;
classes.set(clazz.prototype._classTag, clazz);
}
/**
* Serializes the given object.
*
* @param {*} obj
* @return {string}
*/
export function serialize(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && (value._classTag !== undefined) && !Object.prototype.hasOwnProperty.call(value, "_classTag")) {
// Copy the _classTag from this object's prototype to itself, so that it will be serialized.
value._classTag = value._classTag;
}
return value;
});
}
/**
* @param {string} str
* @return {*}
*/
export function deserialize(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && (value._classTag !== undefined)) {
const clazz = classes.get(value._classTag);
if (clazz === undefined) {
throw new Error(`Unknown class of tag ${ value._classTag } cannot be deserialized.`);
}
if (typeof clazz.fromJSON === "function") {
value = clazz.fromJSON(value);
}
else {
const hydratedValue = Object.create(clazz.prototype);
for (const key of Object.keys(value)) {
hydratedValue[key] = value[key];
}
value = hydratedValue;
}
}
return value;
});
}
+17 -3
View File
@@ -1,9 +1,23 @@
{
"compilerOptions": {
"module": "CommonJS",
"lib": ["es5", "dom"],
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"strictNullChecks": true,
"target": "es5",
"module": "amd",
"moduleResolution": "node",
"outFile": "../lib/libjass.js",
"noImplicitUseStrict": true,
"sourceMap": true,
"target": "ES5",
"experimentalDecorators": true
"inlineSources": true
}
}
+86 -23
View File
@@ -25,32 +25,35 @@ import { ScriptProperties } from "./script-properties";
import { Format } from "./misc";
import { verboseMode } from "../settings";
import { debugMode, verboseMode } from "../settings";
import * as parser from "../parser";
import { parseLineIntoTypedTemplate } from "../parser/misc";
import { ReadableStream, TextDecoder, TextDecoderConstructor } from "../parser/streams";
import { ReadableStream, TextDecoderConstructor } from "../parser/streams";
import { Map } from "../utility/map";
import { Promise } from "../utility/promise";
import { registerClass as serializable } from "../serialization";
declare const global: {
fetch?(url: string): Promise<{ body: ReadableStream; ok?: boolean; status?: number; }>;
ReadableStream?: { prototype: ReadableStream; };
ReadableStream?: Function & { prototype: ReadableStream; };
TextDecoder?: TextDecoderConstructor;
};
/**
* This class represents an ASS script. It contains the {@link libjass.ScriptProperties}, an array of {@link libjass.Style}s, and an array of {@link libjass.Dialogue}s.
*/
@serializable
export class ASS {
private _properties: ScriptProperties = new ScriptProperties();
private _styles: Map<string, Style> = new Map<string, Style>();
private _dialogues: Dialogue[] = [];
private _attachments: Attachment[] = [];
private _stylesFormatSpecifier: string[] = null;
private _dialoguesFormatSpecifier: string[] = null;
private _stylesFormatSpecifier: string[] | null = null;
private _dialoguesFormatSpecifier: string[] | null = null;
/**
* The properties of this script.
@@ -91,36 +94,36 @@ export class ASS {
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
* @type {Array.<string>}
*/
get stylesFormatSpecifier(): string[] {
get stylesFormatSpecifier(): string[] | null {
return this._stylesFormatSpecifier;
}
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
* @type {Array.<string>}
*/
get dialoguesFormatSpecifier(): string[] {
get dialoguesFormatSpecifier(): string[] | null {
return this._dialoguesFormatSpecifier;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
* @type {Array.<string>}
*/
set stylesFormatSpecifier(value: string[]) {
set stylesFormatSpecifier(value: string[] | null) {
this._stylesFormatSpecifier = value;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
* @type {Array.<string>}
*/
set dialoguesFormatSpecifier(value: string[]) {
set dialoguesFormatSpecifier(value: string[] | null) {
this._dialoguesFormatSpecifier = value;
}
@@ -139,6 +142,10 @@ export class ASS {
* @param {string} line The line from the script that contains the new style.
*/
addStyle(line: string): void {
if (this._stylesFormatSpecifier === null) {
throw new Error("stylesFormatSpecifier is not set.");
}
const styleLine = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
if (styleLine === null || styleLine.type !== "Style") {
return;
@@ -163,6 +170,10 @@ export class ASS {
* @param {string} line The line from the script that contains the new event.
*/
addEvent(line: string): void {
if (this._dialoguesFormatSpecifier === null) {
throw new Error("dialoguesFormatSpecifier is not set.");
}
const dialogueLine = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
return;
@@ -189,14 +200,37 @@ export class ASS {
this._attachments.push(attachment);
}
/**
* Custom JSON serialization for ASS objects.
*
* @return {!*}
*/
toJSON(): any {
const result = Object.create(null);
result._properties = this._properties;
result._styles = Object.create(null);
this._styles.forEach((style, name) => result._styles[name] = style);
result._dialogues = this._dialogues;
result._attachments = this._attachments;
result._stylesFormatSpecifier = this._stylesFormatSpecifier;
result._dialoguesFormatSpecifier = this._dialoguesFormatSpecifier;
result._classTag = (ASS.prototype as any)._classTag;
return result;
}
/**
* Creates an ASS object from the raw text of an ASS script.
*
* @param {string} raw The raw text of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>}
*/
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
static fromString(raw: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
return ASS.fromStream(new parser.StringStream(raw), type);
}
@@ -204,17 +238,19 @@ export class ASS {
* Creates an ASS object from the given {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The stream to parse the script from
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromStream(stream: parser.Stream, type: Format = Format.ASS): Promise<ASS> {
static fromStream(stream: parser.Stream, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
case "ass":
return new parser.StreamParser(stream).ass;
case Format.SRT:
case "srt":
return new parser.SrtStreamParser(stream).ass;
default:
throw new Error(`Illegal value of type: ${ type }`);
throw new Error(`Invalid value of type: ${ type }`);
}
}
@@ -222,10 +258,10 @@ export class ASS {
* Creates an ASS object from the given URL.
*
* @param {string} url The URL of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromUrl(url: string, type: Format = Format.ASS): Promise<ASS> {
static fromUrl(url: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
let fetchPromise: Promise<ASS>;
if (
@@ -246,7 +282,9 @@ export class ASS {
}
return fetchPromise.catch(reason => {
console.warn("fetch() failed, falling back to XHR: %o", reason);
if (debugMode) {
console.log("fetch() failed, falling back to XHR: %o", reason);
}
const xhr = new XMLHttpRequest();
const result = ASS.fromStream(new parser.XhrStream(xhr), type);
@@ -261,10 +299,35 @@ export class ASS {
*
* @param {!ReadableStream} stream
* @param {string="utf-8"} encoding
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format = Format.ASS): Promise<ASS> {
static fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
return ASS.fromStream(new parser.BrowserReadableStream(stream, encoding), type);
}
/**
* Custom deserialization for ASS objects.
*
* @param {!*} obj
* @return {!libjass.ASS}
*/
static fromJSON(obj: any): ASS {
const result: ASS = Object.create(ASS.prototype);
result._properties = obj._properties;
result._styles = new Map<string, Style>();
for (const name of Object.keys(obj._styles)) {
const style = obj._styles[name];
result._styles.set(name, style);
}
result._dialogues = obj._dialogues;
result._attachments = obj._attachments;
result._stylesFormatSpecifier = obj._stylesFormatSpecifier;
result._dialoguesFormatSpecifier = obj._dialoguesFormatSpecifier;
return result;
}
}
+3
View File
@@ -18,6 +18,8 @@
* limitations under the License.
*/
import { registerClass as serializable } from "../serialization";
/**
* The type of an attachment.
*/
@@ -32,6 +34,7 @@ export enum AttachmentType {
* @param {string} filename The filename of this attachment.
* @param {number} type The type of this attachment.
*/
@serializable
export class Attachment {
private _contents: string = "";
+41 -29
View File
@@ -23,12 +23,12 @@ import { Style } from "./style";
import { valueOrDefault } from "./misc";
import { parseLineIntoTypedTemplate } from "../parser/misc";
import { parse } from "../parser/parse";
import * as parts from "../parts";
import { registerClass as serializable } from "../serialization";
import { debugMode } from "../settings";
import { Map } from "../utility/map";
@@ -44,6 +44,7 @@ import { Map } from "../utility/map";
* @param {string} template["Text"] The text of this dialogue
* @param {!libjass.ASS} ass The ASS object to which this dialogue belongs
*/
@serializable
export class Dialogue {
private static _lastDialogueId = -1;
@@ -58,7 +59,7 @@ export class Dialogue {
private _alignment: number;
private _rawPartsString: string;
private _parts: parts.Part[] = null;
private _parts: parts.Part[] | null = null;
private _containsTransformTag: boolean = false;
@@ -74,34 +75,45 @@ export class Dialogue {
this._id = ++Dialogue._lastDialogueId;
let styleName = template.get("style");
if (styleName !== undefined && styleName !== null && styleName.constructor === String) {
if (typeof styleName === "string") {
styleName = styleName.replace(/^\*+/, "");
if (styleName.match(/^Default$/i) !== null) {
styleName = "Default";
}
}
this._style = ass.styles.get(styleName);
if (this._style === undefined) {
let style = (styleName !== undefined) ? ass.styles.get(styleName) : undefined;
if (style === undefined) {
if (debugMode) {
console.warn(`Unrecognized style ${ styleName }. Falling back to "Default"`);
}
this._style = ass.styles.get("Default");
}
if (this._style === undefined) {
throw new Error(`Unrecognized style ${ styleName }`);
style = ass.styles.get("Default");
if (style === undefined) {
throw new Error(`Unrecognized style ${ styleName }. Could not fall back to "Default" style since it doesn't exist.`);
}
}
this._style = style;
this._start = Dialogue._toTime(template.get("start"));
this._end = Dialogue._toTime(template.get("end"));
const start = template.get("start");
if (typeof start !== "string") {
throw new Error(`Dialogue start time ${ start } is not a string.`);
}
this._start = toTime(start);
const end = template.get("end");
if (typeof end !== "string") {
throw new Error(`Dialogue end time ${ end } is not a string.`);
}
this._end = toTime(end);
this._layer = Math.max(valueOrDefault(template, "layer", parseInt, value => !isNaN(value), "0"), 0);
this._rawPartsString = template.get("text");
if (this._rawPartsString === undefined || this._rawPartsString === null || this._rawPartsString.constructor !== String) {
throw new Error("Dialogue doesn't have any text.");
const text = template.get("text");
if (typeof text !== "string") {
throw new Error(`Dialogue text ${ text } is not a string.`);
}
this._rawPartsString = text;
}
/**
@@ -172,7 +184,7 @@ export class Dialogue {
this._parsePartsString();
}
return this._parts;
return this._parts!;
}
/**
@@ -199,7 +211,7 @@ export class Dialogue {
* Parses this dialogue's parts from the raw parts string.
*/
private _parsePartsString(): void {
this._parts = <parts.Part[]>parse(this._rawPartsString, "dialogueParts");
this._parts = parse(this._rawPartsString, "dialogueParts") as parts.Part[];
this._alignment = this._style.alignment;
@@ -209,7 +221,7 @@ export class Dialogue {
}
else if (part instanceof parts.Move) {
if (part.t1 === null || part.t2 === null) {
this._parts[index] =
this._parts![index] =
new parts.Move(
part.x1, part.y1, part.x2, part.y2,
0, this._end - this._start
@@ -218,7 +230,7 @@ export class Dialogue {
}
else if (part instanceof parts.Transform) {
if (part.start === null || part.end === null || part.accel === null) {
this._parts[index] =
this._parts![index] =
new parts.Transform(
(part.start === null) ? 0 : part.start,
(part.end === null) ? (this._end - this._start) : part.end,
@@ -245,14 +257,14 @@ ${ possiblyIncorrectParses.join("\n") }`
}
}
}
/**
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
*
* @param {string} str
* @return {number}
*/
private static _toTime(str: string): number {
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
}
/**
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
*
* @param {string} str
* @return {number}
*/
function toTime(str: string): number {
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
+1 -1
View File
@@ -84,7 +84,7 @@ export interface TypedTemplate {
* @param {T} defaultValue
* @return {T}
*/
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: (value: T) => boolean, defaultValue: string): T {
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: ((value: T) => boolean) | null, defaultValue: string): T {
const value = template.get(key);
if (value === undefined) {
return converter(defaultValue);
+3
View File
@@ -18,11 +18,14 @@
* limitations under the License.
*/
import { registerClass as serializable } from "../serialization";
import { WrappingStyle } from "./misc";
/**
* This class represents the properties of a {@link libjass.ASS} script.
*/
@serializable
export class ScriptProperties {
private _resolutionX: number;
private _resolutionY: number;
+12 -9
View File
@@ -24,6 +24,8 @@ import { parse } from "../parser/parse";
import { Color } from "../parts";
import { registerClass as serializable } from "../serialization";
import { Map } from "../utility/map";
/**
@@ -50,6 +52,7 @@ import { Map } from "../utility/map";
* @param {string} template["MarginR"] The right margin
* @param {string} template["MarginV"] The vertical margin
*/
@serializable
export class Style {
private _name: string;
@@ -93,11 +96,11 @@ export class Style {
template = normalizedTemplate;
}
this._name = template.get("name");
if (this._name === undefined || this._name === null || this._name.constructor !== String) {
throw new Error("Style doesn't have a name.");
const name = template.get("name");
if (typeof name !== "string") {
throw new Error(`Style name ${ name } is not a string.`);
}
this._name = this._name.replace(/^\*+/, "");
this._name = name.replace(/^\*+/, "");
this._italic = !!valueOrDefault(template, "italic", parseFloat, value => !isNaN(value), "0");
this._bold = !!valueOrDefault(template, "bold", parseFloat, value => !isNaN(value), "0");
@@ -114,13 +117,13 @@ export class Style {
this._rotationZ = valueOrDefault(template, "angle", parseFloat, value => !isNaN(value), "0");
this._primaryColor = valueOrDefault(template, "primarycolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00FFFFFF");
this._secondaryColor = valueOrDefault(template, "secondarycolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00FFFF00");
this._outlineColor = valueOrDefault(template, "outlinecolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
this._shadowColor = valueOrDefault(template, "backcolour", str => <Color>parse(str, "colorWithAlpha"), null, "&H80000000");
this._primaryColor = valueOrDefault(template, "primarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFFFF");
this._secondaryColor = valueOrDefault(template, "secondarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFF00");
this._outlineColor = valueOrDefault(template, "outlinecolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00000000");
this._shadowColor = valueOrDefault(template, "backcolour", str => parse(str, "colorWithAlpha") as Color, null, "&H80000000");
this._outlineThickness = valueOrDefault(template, "outline", parseFloat, value => value >= 0, "2");
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (<any>BorderStyle)[(<any>BorderStyle)[value]] === value, "1");
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (BorderStyle as any)[(BorderStyle as any)[value]] === value, "1");
this._shadowDepth = valueOrDefault(template, "shadow", parseFloat, value => value >= 0, "3");
+33 -31
View File
@@ -18,16 +18,12 @@
* limitations under the License.
*/
declare const global: {
Map?: typeof Map;
};
export interface Map<K, V> {
/**
* @param {K} key
* @return {V}
* @return {?V}
*/
get(key: K): V;
get(key: K): V | undefined;
/**
* @param {K} key
@@ -40,7 +36,7 @@ export interface Map<K, V> {
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value?: V): Map<K, V>;
set(key: K, value: V): this;
/**
* @param {K} key
@@ -56,7 +52,7 @@ export interface Map<K, V> {
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void;
/**
* @type {number}
@@ -64,18 +60,6 @@ export interface Map<K, V> {
size: number;
}
/**
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
*
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
*/
export var Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>;
prototype: Map<any, any>;
} = global.Map;
/**
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
*
@@ -83,7 +67,7 @@ export var Map: {
*
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
*/
class SimpleMap<K, V> {
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
@@ -106,9 +90,9 @@ class SimpleMap<K, V> {
/**
* @param {K} key
* @return {V}
* @return {?V}
*/
get(key: K): V {
get(key: K): V | undefined {
const property = this._keyToProperty(key);
if (property === null) {
@@ -137,7 +121,7 @@ class SimpleMap<K, V> {
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value: V): Map<K, V> {
set(key: K, value: V): this {
const property = this._keyToProperty(key);
if (property === null) {
@@ -188,7 +172,7 @@ class SimpleMap<K, V> {
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void {
for (const property of Object.keys(this._keys)) {
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
}
@@ -205,9 +189,9 @@ class SimpleMap<K, V> {
* Converts the given key into a property name for the internal map.
*
* @param {K} key
* @return {string}
* @return {?string}
*/
private _keyToProperty(key: K): string {
private _keyToProperty(key: K): string | null {
if (typeof key === "number") {
return `#${ key }`;
}
@@ -216,15 +200,29 @@ class SimpleMap<K, V> {
return `'${ key }`;
}
if ((<any>key).id !== undefined) {
return `!${ (<any>key).id }`;
if ((key as any).id !== undefined) {
return `!${ (key as any).id }`;
}
return null;
}
}
if (Map === undefined || typeof Map.prototype.forEach !== "function" || (() => {
/**
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
*/
export var Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>;
prototype: Map<any, any>;
} = global.Map || SimpleMap;
if (typeof Map.prototype.forEach !== "function" || (() => {
try {
return new Map([[1, "foo"], [2, "bar"]]).size !== 2;
}
@@ -235,12 +233,16 @@ if (Map === undefined || typeof Map.prototype.forEach !== "function" || (() => {
Map = SimpleMap;
}
declare var global: {
Map?: typeof Map;
};
/**
* Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
*
* @param {?function(new:Map, !Array.<!Array.<*>>=)} value
*/
export function setImplementation(value: typeof Map): void {
export function setImplementation(value: typeof Map | null): void {
if (value !== null) {
Map = value;
}
+49 -50
View File
@@ -18,15 +18,6 @@
* limitations under the License.
*/
declare const global: {
Promise?: typeof Promise;
MutationObserver?: typeof MutationObserver;
WebkitMutationObserver?: typeof MutationObserver;
process?: {
nextTick(callback: () => void): void;
}
};
export interface Thenable<T> {
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
then: ThenableThen<T>;
@@ -34,53 +25,38 @@ export interface Thenable<T> {
export interface ThenableThen<T> {
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
(resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void): void;
(this: Thenable<T>, resolve: ((resolution: T | Thenable<T>) => void) | undefined, reject: ((reason: any) => void) | undefined): void;
}
export interface Promise<T> extends Thenable<T> {
/**
* @param {?function(T):!Thenable.<U>} onFulfilled
* @param {function(T):!Thenable.<U>} onFulfilled
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled?: (value: T) => Thenable<U>, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
then<U>(onFulfilled: (value: T) => Thenable<U> | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
/**
* @param {?function(T):U} onFulfilled
* @param {function(T):U} onFulfilled
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled?: (value: T) => U, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
then<U>(onFulfilled: (value: T) => U | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
/**
* @param {function(*):(T|!Thenable.<T>)} onRejected
* @return {!Promise.<T>}
*/
catch(onRejected?: (reason: any) => T | Thenable<T>): Promise<T>
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T>;
}
/**
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
*
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
*
* @type {function(new:Promise)}
*/
export var Promise: {
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | Thenable<T>): Promise<T>;
reject<T>(reason: any): Promise<T>;
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
} = global.Promise;
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
const enqueueJob: (callback: () => void) => void = (function () {
const MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
if (global.process !== undefined && typeof global.process.nextTick === "function") {
const process = global.process;
return (callback: () => void) => {
global.process.nextTick(callback);
process.nextTick(callback);
};
}
else if (MutationObserver !== undefined) {
@@ -131,7 +107,7 @@ class SimplePromise<T> {
private _fulfillReactions: FulfilledPromiseReaction<T, any>[] = [];
private _rejectReactions: RejectedPromiseReaction<any>[] = [];
private _fulfilledValue: T = null;
private _fulfilledValue: T | null = null;
private _rejectedReason: any = null;
constructor(executor: (resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void) => void) {
@@ -153,11 +129,11 @@ class SimplePromise<T> {
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled: (value: T) => U | Thenable<U>, onRejected: (reason: any) => U | Thenable<U>): Promise<U> {
then<U>(onFulfilled: ((value: T) => U | Thenable<U>) | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U> {
const resultCapability = new DeferredPromise<U>();
if (typeof onFulfilled !== "function") {
onFulfilled = (value: T) => <U><any>value;
onFulfilled = (value: T) => value as any as U;
}
if (typeof onRejected !== "function") {
@@ -181,7 +157,7 @@ class SimplePromise<T> {
break;
case SimplePromiseState.FULFILLED:
this._enqueueFulfilledReactionJob(fulfillReaction, this._fulfilledValue);
this._enqueueFulfilledReactionJob(fulfillReaction, this._fulfilledValue!);
break;
case SimplePromiseState.REJECTED:
@@ -196,8 +172,8 @@ class SimplePromise<T> {
* @param {function(*):(T|!Thenable.<T>)} onRejected
* @return {!Promise.<T>}
*/
catch(onRejected?: (reason: any) => T | Thenable<T>): Promise<T> {
return this.then(null, onRejected);
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T> {
return this.then(undefined, onRejected);
}
/**
@@ -276,12 +252,12 @@ class SimplePromise<T> {
}
if (resolution === null || (typeof resolution !== "object" && typeof resolution !== "function")) {
this._fulfill(<T>resolution);
this._fulfill(resolution as T);
return;
}
try {
var then = (<Thenable<T>>resolution).then;
var then = (resolution as Thenable<T>).then;
}
catch (ex) {
this._reject(ex);
@@ -289,11 +265,11 @@ class SimplePromise<T> {
}
if (typeof then !== "function") {
this._fulfill(<T>resolution);
this._fulfill(resolution as T);
return;
}
enqueueJob(() => this._resolveWithThenable(<Thenable<T>>resolution, then));
enqueueJob(() => this._resolveWithThenable(resolution as Thenable<T>, then));
};
const reject = (reason: any): void => {
@@ -331,8 +307,8 @@ class SimplePromise<T> {
const reactions = this._fulfillReactions;
this._fulfilledValue = value;
this._fulfillReactions = undefined;
this._rejectReactions = undefined;
this._fulfillReactions = [];
this._rejectReactions = [];
this._state = SimplePromiseState.FULFILLED;
for (const reaction of reactions) {
@@ -347,8 +323,8 @@ class SimplePromise<T> {
const reactions = this._rejectReactions;
this._rejectedReason = reason;
this._fulfillReactions = undefined;
this._rejectReactions = undefined;
this._fulfillReactions = [];
this._rejectReactions = [];
this._state = SimplePromiseState.REJECTED;
for (const reaction of reactions) {
@@ -401,9 +377,32 @@ class SimplePromise<T> {
}
}
if (Promise === undefined) {
Promise = SimplePromise;
}
/**
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
*
* @type {function(new:Promise)}
*/
export var Promise: {
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | Thenable<T>): Promise<T>;
reject<T>(reason: any): Promise<T>;
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
} = global.Promise || SimplePromise;
declare var global: {
Promise?: typeof Promise;
MutationObserver?: typeof MutationObserver;
WebkitMutationObserver?: typeof MutationObserver;
process?: {
nextTick(callback: () => void): void;
}
};
interface FulfilledPromiseReaction<T, U> {
/** @type {!libjass.DeferredPromise.<U>} */
@@ -441,7 +440,7 @@ enum SimplePromiseState {
*
* @param {?function(new:Promise)} value
*/
export function setImplementation(value: typeof Promise): void {
export function setImplementation(value: typeof Promise | null): void {
if (value !== null) {
Promise = value;
}
+27 -25
View File
@@ -18,16 +18,12 @@
* limitations under the License.
*/
declare const global: {
Set?: typeof Set;
};
export interface Set<T> {
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T>;
add(value: T): this;
/**
*/
@@ -43,7 +39,7 @@ export interface Set<T> {
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void;
/**
* @type {number}
@@ -51,18 +47,6 @@ export interface Set<T> {
size: number;
}
/**
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
*
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
*
* @type {function(new:Set, !Array.<T>=)}
*/
export var Set: {
new <T>(iterable?: T[]): Set<T>;
prototype: Set<any>;
} = global.Set;
/**
* Set implementation for browsers that don't support it. Only supports Number and String elements.
*
@@ -70,7 +54,7 @@ export var Set: {
*
* @param {!Array.<T>=} iterable Only an array of values is supported.
*/
class SimpleSet<T> {
class SimpleSet<T> implements Set<T> {
private _elements: { [key: string]: T };
private _size: number;
@@ -94,7 +78,7 @@ class SimpleSet<T> {
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T> {
add(value: T): this {
const property = this._toProperty(value);
if (property === null) {
@@ -135,7 +119,7 @@ class SimpleSet<T> {
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void {
for (const property of Object.keys(this._elements)) {
const element = this._elements[property];
callbackfn.call(thisArg, element, element, this);
@@ -153,9 +137,9 @@ class SimpleSet<T> {
* Converts the given value into a property name for the internal map.
*
* @param {T} value
* @return {string}
* @return {?string}
*/
private _toProperty(value: T): string {
private _toProperty(value: T): string | null {
if (typeof value === "number") {
return `#${ value }`;
}
@@ -168,7 +152,21 @@ class SimpleSet<T> {
}
}
if (Set === undefined || typeof Set.prototype.forEach !== "function" || (() => {
/**
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
*
* @type {function(new:Set, !Array.<T>=)}
*/
export var Set: {
new <T>(iterable?: T[]): Set<T>;
prototype: Set<any>;
} = global.Set || SimpleSet;
if (typeof Set.prototype.forEach !== "function" || (() => {
try {
return new Set([1, 2]).size !== 2;
}
@@ -179,12 +177,16 @@ if (Set === undefined || typeof Set.prototype.forEach !== "function" || (() => {
Set = SimpleSet;
}
declare var global: {
Set?: typeof Set;
};
/**
* Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
*
* @param {?function(new:Set, !Array.<T>=)} value
*/
export function setImplementation(value: typeof Set): void {
export function setImplementation(value: typeof Set | null): void {
if (value !== null) {
Set = value;
}
-86
View File
@@ -1,86 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Class inheritance shim.
*
* @param {!Function} derived
* @param {!Function} base
*/
export function __extends(derived: any, base: any): void {
for (const property in base) {
if (base.hasOwnProperty(property)) {
derived[property] = base[property];
}
}
function __() {
this.constructor = derived;
}
__.prototype = base.prototype;
derived.prototype = new (<any>__)();
}
/**
* Decorator shim.
*
* @param {!Array.<!Function>} decorators
* @param {!*} target
* @param {string=} key
* @return {*}
*/
export function __decorate(decorators: Function[], target: any, key?: string): any {
if (arguments.length < 3) {
return decorateClass(<any>decorators.reverse(), target);
}
else {
decorateField(<any>decorators.reverse(), target, key);
}
}
/**
* Class decorator shim.
*
* @param {!Array.<function(function(new(): T)): function(new(): T)>} decorators
* @param {function(new(): T)} clazz
* @return {function(new(): T)}
*/
function decorateClass<T>(decorators: ((clazz: { new (...args: any[]): T }) => { new (...args: any[]): T })[], clazz: { new (...args: any[]): T }): { new (...args: any[]): T } {
for (const decorator of decorators) {
clazz = decorator(clazz) || clazz;
}
return clazz;
}
/**
* Class member decorator shim.
*
* @param {!Array.<function(T, string)>} decorators
* @param {!T} proto
* @param {string} name
*/
function decorateField<T>(decorators: ((proto: T, name: string) => void)[], proto: T, name: string): void {
for (const decorator of decorators) {
decorator(proto, name);
}
}
+9 -8
View File
@@ -18,13 +18,14 @@
* limitations under the License.
*/
import { serialize, deserialize } from "../serialization";
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { WorkerCommands } from "./commands";
import { getWorkerCommandHandler, serialize, deserialize, registerWorkerCommand } from "./misc";
import { getWorkerCommandHandler, registerWorkerCommand } from "./misc";
/**
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling {@link libjass.webworker.createWorker}
@@ -50,7 +51,7 @@ export interface WorkerCommandHandler {
/**
* The interface implemented by a communication channel to the other side.
*/
interface WorkerCommunication {
export interface WorkerCommunication {
/**
* @param {"message"} type
* @param {function(!MessageEvent): *} listener
@@ -135,7 +136,7 @@ export class WorkerChannelImpl implements WorkerChannel {
private _pendingRequests = new Map<number, DeferredPromise<any>>();
constructor(private _comm: WorkerCommunication) {
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
this._comm.addEventListener("message", ev => this._onMessage(ev.data as string), false);
}
/**
@@ -182,10 +183,10 @@ export class WorkerChannelImpl implements WorkerChannel {
* @param {string} rawMessage
*/
private _onMessage(rawMessage: string): void {
const message = <{ command: WorkerCommands }>deserialize(rawMessage);
const message = deserialize(rawMessage) as { command: WorkerCommands };
if (message.command === WorkerCommands.Response) {
const responseMessage = <WorkerResponseMessage><any>message;
const responseMessage = message as any as WorkerResponseMessage;
const deferred = this._pendingRequests.get(responseMessage.requestId);
if (deferred !== undefined) {
@@ -199,7 +200,7 @@ export class WorkerChannelImpl implements WorkerChannel {
}
}
else {
const requestMessage = <WorkerRequestMessage>message;
const requestMessage = message as WorkerRequestMessage;
const requestId = requestMessage.requestId;
const commandCallback = getWorkerCommandHandler(requestMessage.command);
@@ -216,4 +217,4 @@ export class WorkerChannelImpl implements WorkerChannel {
}
}
registerWorkerCommand(WorkerCommands.Ping, parameters => new Promise<void>(resolve => resolve(null)));
registerWorkerCommand(WorkerCommands.Ping, parameters => Promise.resolve(null));
+10 -8
View File
@@ -18,17 +18,11 @@
* limitations under the License.
*/
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { WorkerChannel, WorkerChannelImpl } from "./channel";
export { WorkerChannel } from "./channel";
export { WorkerCommands } from "./commands";
declare const exports: any;
/**
* Indicates whether web workers are supposed in this environment or not.
*
@@ -36,7 +30,7 @@ declare const exports: any;
*/
export const supported = typeof Worker !== "undefined";
const _scriptNode = (typeof document !== "undefined" && document.currentScript !== undefined) ? document.currentScript : null;
const _scriptNode = (typeof document !== "undefined" && document.currentScript !== undefined) ? (document.currentScript as HTMLScriptElement) : null;
/**
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
@@ -45,7 +39,15 @@ const _scriptNode = (typeof document !== "undefined" && document.currentScript !
* the path will be determined from the src attribute of the <script> element that contains the currently running copy of libjass.js
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
*/
export function createWorker(scriptPath: string = _scriptNode.src): WorkerChannel {
export function createWorker(scriptPath?: string): WorkerChannel {
if (scriptPath === undefined) {
if (_scriptNode === null) {
throw new Error("Could not auto-detect path of libjass.js, and explicit path was not passed in.");
}
scriptPath = _scriptNode.src;
}
return new WorkerChannelImpl(new Worker(scriptPath));
}
+1 -47
View File
@@ -25,8 +25,6 @@ import { WorkerCommandHandler } from "./channel";
const workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
const classPrototypes = new Map<number, any>();
/**
* Registers a handler for the given worker command.
*
@@ -43,50 +41,6 @@ export function registerWorkerCommand(command: WorkerCommands, handler: WorkerCo
* @param {number} command
* @return {?function(*, function(*, *))}
*/
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler {
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler | undefined {
return workerCommands.get(command);
}
/**
* Registers a prototype as a deserializable type.
*
* @param {!*} prototype
*/
export function registerClassPrototype(prototype: any): void {
prototype._classTag = classPrototypes.size;
classPrototypes.set(prototype._classTag, prototype);
}
/**
* @param {*} obj
* @return {string}
*/
export function serialize(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
value._classTag = value._classTag;
}
return value;
});
}
/**
* @param {string} str
* @return {*}
*/
export function deserialize(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
const hydratedValue = Object.create(classPrototypes.get(value._classTag));
for (const key of Object.keys(value)) {
if (key !== "_classTag") {
hydratedValue[key] = value[key];
}
}
value = hydratedValue;
}
return value;
});
}
-7
View File
@@ -18,13 +18,6 @@
* limitations under the License.
*/
interface Document {
/**
* @type {!HTMLScriptElement}
*/
currentScript: HTMLScriptElement;
}
interface WorkerGlobalScope {
/**
* @param {*} message
+3 -3
View File
@@ -2,8 +2,8 @@
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 256
PlayResY: 144
PlayResX: 1280
PlayResY: 720
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
@@ -11,7 +11,7 @@ ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,100,&H7F0000FF,&H00FFFFFF,&H7F000000,&H7F000000,0,0,0,0,100,100,0,0,1,2,0,2,15,15,15,1
Style: Default,Arial,500,&H7F0000FF,&H00FFFFFF,&H7F000000,&H7F000000,0,0,0,0,100,100,0,0,1,10,0,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+3 -2
View File
@@ -21,10 +21,11 @@
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("alpha", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 256, 144, "rgb(47, 163, 254)");
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 1280, 720, "rgb(47, 163, 254)");
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); });
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); })
.then(function (testPage) { return testPage.done(); });
});
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

+32
View File
@@ -0,0 +1,32 @@
[Script Info]
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 1280
PlayResY: 720
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,90,&H00FFFFFF,&H00000000,&H00000000,&H96000000,0,0,0,0,100,100,0,0,1,0,0,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an1\frz45}MM\NMM
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an2\frz45}MM\NMM
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an3\frz45}MM\NMM
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an7\frz45}MM\NMM
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an8\frz45}MM\NMM
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,{\an9\frz45}MM\NMM
Dialogue: 1,0:00:01.00,0:00:02.00,Default,,0,0,0,,{\an1\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:02.00,0:00:03.00,Default,,0,0,0,,{\an2\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:03.00,0:00:04.00,Default,,0,0,0,,{\an3\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:04.00,0:00:05.00,Default,,0,0,0,,{\an4\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:05.00,0:00:06.00,Default,,0,0,0,,{\an5\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:06.00,0:00:07.00,Default,,0,0,0,,{\an6\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:07.00,0:00:08.00,Default,,0,0,0,,{\an7\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:08.00,0:00:09.00,Default,,0,0,0,,{\an8\pos(640,360)\frz45}MM\NMM
Dialogue: 1,0:00:09.00,0:00:10.00,Default,,0,0,0,,{\an9\pos(640,360)\frz45}MM\NMM
+40
View File
@@ -0,0 +1,40 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("fr", function () {
tdd.test("frz", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fr/frz.ass", 1280, 720);
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(0.5, require.toUrl("./frz-01.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1.5, require.toUrl("./frz-02.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(2.5, require.toUrl("./frz-03.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(3.5, require.toUrl("./frz-04.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(4.5, require.toUrl("./frz-05.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(5.5, require.toUrl("./frz-06.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(6.5, require.toUrl("./frz-07.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(7.5, require.toUrl("./frz-08.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(8.5, require.toUrl("./frz-09.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(9.5, require.toUrl("./frz-10.png")); })
.then(function (testPage) { return testPage.done(); });
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

+3 -3
View File
@@ -2,8 +2,8 @@
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 256
PlayResY: 144
PlayResX: 1280
PlayResY: 720
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
@@ -11,7 +11,7 @@ ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,36,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
Style: Default,Arial,180,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,10,6,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+3 -2
View File
@@ -21,10 +21,11 @@
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("fscx and fscy", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fsc/fsc.ass", 256, 144);
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fsc/fsc.ass", 1280, 720);
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./fsc-1.png")); });
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./fsc-1.png")); })
.then(function (testPage) { return testPage.done(); });
});
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+3 -3
View File
@@ -6,12 +6,12 @@ ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: None
PlayResX: 256
PlayResY: 144
PlayResX: 1280
PlayResY: 720
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,36,&H00FFFFFF,&H000000FF,&H00000000,&H96000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
Style: Default,Arial,180,&H00FFFFFF,&H000000FF,&H00000000,&H96000000,0,0,0,0,100,100,0,0,1,10,6,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+3 -2
View File
@@ -21,7 +21,7 @@
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("kfx", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/kfx/kfx.ass", 256, 144);
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/kfx/kfx.ass", 1280, 720);
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1.5, require.toUrl("./kfx-1.png")); })
@@ -29,7 +29,8 @@ define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, requ
.then(function (testPage) { return testPage.seekAndCompareScreenshot(3.5, require.toUrl("./kfx-3.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(4.5, require.toUrl("./kfx-4.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(5.5, require.toUrl("./kfx-5.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(6.5, require.toUrl("./kfx-6.png")); });
.then(function (testPage) { return testPage.seekAndCompareScreenshot(6.5, require.toUrl("./kfx-6.png")); })
.then(function (testPage) { return testPage.done(); });
});
});
});

Some files were not shown because too many files have changed in this diff Show More