Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9c3ee5edf | |||
| 41d8516449 | |||
| 8b7ef6201e | |||
| 5fb5a4d73d | |||
| 86971d1902 | |||
| 9071332e1e | |||
| ae62c0c89f | |||
| 027a8d69f5 | |||
| 4c4ef34e8b | |||
| 4d519fd6c9 | |||
| 4ffa5499a4 | |||
| 6e7e7a005e | |||
| 20a0d8ee64 | |||
| 56fbd7a9b2 | |||
| 293617aa22 | |||
| 7ae40e7164 | |||
| 361bc707d3 | |||
| 04dc60f128 | |||
| 70537168a0 | |||
| 6e33697c5c | |||
| 8680e884a1 | |||
| ec693190b5 | |||
| 4dfdaf6010 | |||
| 9540b2c00a | |||
| 9445d04efa | |||
| 7a7ee02905 | |||
| a6605064c1 | |||
| 4438caae92 | |||
| 212f376b77 | |||
| 35310a5980 | |||
| 4ccb76f53c | |||
| 1ac4fb24aa | |||
| 1aef4ef1c9 | |||
| c421cb10b6 | |||
| 739ae86ffe | |||
| 3dc4a78812 | |||
| 97c0691994 | |||
| 79c9b73e0d | |||
| 1e20e498c4 | |||
| 5e6f3c8ba4 | |||
| c15bd7391d | |||
| 66f8ffc4c2 | |||
| 93c2428626 | |||
| c04d0b6631 | |||
| 05fc742588 | |||
| d590984eb6 | |||
| a67ec2948f | |||
| c432f33149 | |||
| 1bc6e63d18 | |||
| d5831e65a1 | |||
| 15d13e2fa2 | |||
| 5e100e88fb | |||
| 18b5d88f07 | |||
| 201fc044ee | |||
| bb28a85d97 | |||
| 414e48c022 | |||
| 7a5957cb27 | |||
| 2ae4ba0a09 | |||
| 40ac799134 | |||
| 990ca04065 | |||
| efa2fa7ade | |||
| 1de86c435a | |||
| df3ddb214e | |||
| fa3467c649 | |||
| bba93dad9f | |||
| d2db781f82 | |||
| 11d1b36fc3 | |||
| 509aa3b52d | |||
| d916a8dece | |||
| ff9fa3954d | |||
| 9b0e15cd78 | |||
| 795bef96ac | |||
| 45f081ba54 | |||
| 308715a495 | |||
| abbbd6bf04 | |||
| 569fce4072 | |||
| 5b88230c32 | |||
| 097854f30f | |||
| 6fc6bcc4b2 | |||
| 0045ebe3fb | |||
| 1cefb68b47 | |||
| 78bab74e11 | |||
| a43f2da4c9 | |||
| 7ff81409c7 | |||
| 0661ada8e6 | |||
| ec1c180173 | |||
| d5969cc2f5 | |||
| b23f6e4e9f | |||
| da1e7ed52b | |||
| e6d3195df0 | |||
| 81117ce07b | |||
| 6dbee54f6a | |||
| 5f9890d157 | |||
| db1201d84e | |||
| f3ab2bc57f | |||
| a4c617f1df | |||
| e1b993a0ae | |||
| 42db525b30 | |||
| 76691cc52b | |||
| 82f3706eff | |||
| a0ac2ba83b | |||
| b89b5f76bb | |||
| 2bfdcb582c | |||
| 604129a10d | |||
| 563141f1c4 | |||
| a0d49f4ee5 | |||
| 23445a82c4 | |||
| 0c51760fc5 | |||
| 2ce0d7985a | |||
| f8eb111f89 | |||
| 9e9ba88ede | |||
| 8ed209bba4 | |||
| 0260014524 | |||
| c6cafd10d4 | |||
| e46f0322ed | |||
| 0ecb96c42a | |||
| 191a34d060 | |||
| 484997e3fc | |||
| 7482220201 | |||
| 6606413eef | |||
| 6dce371011 | |||
| 6b2800ef70 | |||
| 29c187f076 | |||
| 7d7217007a | |||
| e783afc43c | |||
| 4183228262 | |||
| b92b63f94f | |||
| 4ddab3a2d9 | |||
| 3b381ac925 | |||
| 1d803dd1b8 | |||
| 074f3ec723 | |||
| caf6701c7b | |||
| 99d72711be | |||
| 7ed52215cb | |||
| 50e9140ee3 | |||
| 6f81c4dc17 | |||
| 7a1dc0582c | |||
| d9f2431414 | |||
| cec2f13361 | |||
| 6939e10c9b | |||
| 2ee04485d6 | |||
| d98f3ea94f | |||
| 83b6ae9f64 | |||
| 92397aa351 | |||
| 5e64329a8c | |||
| c0e0dec1fb | |||
| 90c781c4e2 | |||
| 74636c4098 | |||
| bc989a5761 | |||
| 586ff43bec | |||
| e856423ee1 | |||
| 8e903ba50e | |||
| 68d898764e | |||
| 9bde3b9982 | |||
| c4c935175e | |||
| c7986b0eab | |||
| a1dfc2f79d | |||
| bda06a8385 | |||
| 0dd0df60f5 | |||
| d4956466e4 | |||
| 0b4c66f88a | |||
| f21b90b061 | |||
| 1df6c44007 | |||
| 53f96a2367 | |||
| f6b4b705e0 | |||
| fc3c62af0a | |||
| fe2f7bde9e | |||
| 070ef1aee3 | |||
| d05e8015b4 | |||
| cb52d62158 | |||
| 232dddf6e5 | |||
| 53d9721f18 | |||
| 981132c08a | |||
| ff1739fe67 | |||
| 3df0b5bab7 | |||
| f9a8791280 | |||
| 3140391c16 | |||
| 6fa2b2c581 | |||
| c099015f6a | |||
| 9e039ee13d | |||
| 8a53a56157 | |||
| ff04dbab24 | |||
| a9ec919118 | |||
| 676f167180 | |||
| 3a277f9295 | |||
| c469780107 | |||
| 539fa58daf | |||
| 3df3eb7c11 | |||
| eebbad7fd1 | |||
| 0cd9dbe150 | |||
| 5b783850e8 | |||
| 0c2fecd1cc | |||
| d1b17204e9 | |||
| 0292acc20c | |||
| f1e820819c | |||
| c28c722263 | |||
| 64ac166485 | |||
| 8cabd87c60 | |||
| 6986531992 | |||
| 42282c2807 | |||
| f2765085c4 | |||
| 8e28a95e4e | |||
| 782d34c3eb | |||
| b2d9b757e2 | |||
| 41cb005ec6 | |||
| d84573038d | |||
| e047bed5b9 |
@@ -1,12 +1,14 @@
|
||||
dist/
|
||||
/build/doc.js
|
||||
/build/typescript/typescript.d.ts
|
||||
/build/typescript/*.js
|
||||
|
||||
gulplib/doc.js
|
||||
gulplib/helpers.js
|
||||
gulplib/typescript/*.js
|
||||
/dist/
|
||||
|
||||
lib/
|
||||
!lib/libjass.css
|
||||
/lib/
|
||||
!/lib/libjass.css
|
||||
|
||||
node_modules/
|
||||
/node_modules/
|
||||
|
||||
npm-debug.log
|
||||
/npm-debug.log
|
||||
|
||||
/src/version.ts
|
||||
|
||||
@@ -2,7 +2,9 @@ language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- iojs
|
||||
- "4"
|
||||
- "5"
|
||||
- "6"
|
||||
before_script:
|
||||
- "./node_modules/.bin/gulp doc"
|
||||
- "node ./build.js doc"
|
||||
sudo: false
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
### v0.11.0 - 2016/01/24
|
||||
- BREAKING CHANGE - WebRenderer.resize(width, height) used to have a broken implementation of letterboxing to move the subs div right or down. Now it's WebRenderer.resize(width, height, left, top) and expects the caller to calculate letterboxing itself and supply left and top accordingly. DefaultRenderer does it using the video resolution and users of WebRenderer can do the same.
|
||||
- BREAKING CHANGE - DefaultRenderer.resize() now ignores its parameters and always resizes to its video element's dimensions. It had already stopped resizing the video element when it was renamed from resizeVideo in v0.6.0, so it doesn't make sense to let it take a completely different width and height.
|
||||
- BREAKING CHANGE - Removed fullscreen support in DefaultRenderer. It started out as a hack using max z-index and works on even fewer browsers now. It probably didn't work for you anyway so it should be no big loss.
|
||||
- Implemented experimental support for \t
|
||||
- Added RendererSettings.fallbackFonts to set the fallback fonts for all styles. Defaults to 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"'.
|
||||
- Better compatibility with loose ASS scripts - assume unnamed first section is Script Info, fall back to Default style for missing styles, recognize arbitrary-case property names, normalize asterisks in style names, etc.
|
||||
- Various font size improvements - faster calculation, fix for incorrect size when line-height is overridden by site CSS, fix for incorrect scaled sizes for letterboxed subs, fix for incorrect metrics for web fonts, etc. The last one requires that all web fonts be specified in RendererSettings.fontMap to be rendered accurately.
|
||||
- WebRenderer now supports using local() URLs in addition to url() in CSS font-face rules.
|
||||
- Added RendererSettings.useAttachedFonts. If true, TTF fonts attached to the script will be used in addition to fonts specified in RendererSettings.fontMap. This setting is false by default, and should only be enabled on trusted fonts since it uses a very naive base64 and TTF parser to extract the font names from the attachment. It also requires ES6 typed arrays - ArrayBuffer, DataView, Uint8Array, etc. in the environment.
|
||||
- Various pre-render, SVG filter and DOM perf improvements.
|
||||
- Fixed \fscx and \fscy to not scale shadows.
|
||||
- Fixed \fscx and \fscy to have optional values.
|
||||
- Fixed \fs+ and \fs- to have required values.
|
||||
- Fixed \r<target_style> to use the target style's alpha values instead of 1.
|
||||
- Fixed \fad subs to not flash after the fade-out ends with low-resolution clocks.
|
||||
- Fixed outlines to not be darker than they should be.
|
||||
- Fixed styles to not ignore the ScaleX and ScaleY properties in the script.
|
||||
- Fixed lack of sufficient space between normal and italic text.
|
||||
- Fixed SVG filters to interpolate in sRGB space instead of RGB.
|
||||
- Fixed ASS parser to complain if a script doesn't have a Script Info section at all.
|
||||
- The promise returned from ASS.from*() is now properly rejected due to errors from loading the script, instead of just remaining unresolved forever.
|
||||
- Fixed SRT parser to swallow UTF-8 BOM just like the ASS parser.
|
||||
- Fixed all clocks to suppress redundant ticks if the current timestamp hasn't change from the last tick.
|
||||
- Fixed {AutoClock, VideoClock}.{setEnabled, toggle} methods to actually enable / disable the high-resolution timer.
|
||||
|
||||
|
||||
### v0.10.0 - 2015/05/05
|
||||
- Implemented libjass.renderers.AutoClock, a clock that automatically ticks and generates clock events according to the state of an external driver.
|
||||
- Implemented \k
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[](https://travis-ci.org/Arnavion/libjass)
|
||||
|
||||
libjass is a JavaScript library written in TypeScript to render ASS subs on HTML5 video in the browser. [Check out the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
|
||||
libjass is a JavaScript library written in TypeScript to render ASS subs in the browser. [Check out the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
|
||||
|
||||
|
||||
### What's special about libjass?
|
||||
@@ -14,7 +14,13 @@ As a result, libjass is able to render subtitles with very low CPU usage. The do
|
||||
|
||||
### I want to use libjass for my website. What do I need to do?
|
||||
|
||||
You can install the latest release of libjass using npm with `npm install libjass` or using bower with `bower install https://github.com/Arnavion/libjass/releases/download/<release name>/libjass.zip`. Inside the package, you will find libjass.js and libjass.css, which you need to load on your website with your video.
|
||||
You can install the latest release of libjass
|
||||
|
||||
* using npm with `npm install libjass` and load with `var libjass = require("libjass");`
|
||||
* using bower with `bower install https://github.com/Arnavion/libjass/releases/download/<release name>/libjass.zip`
|
||||
* using jspm with `jspm install github:Arnavion/libjass` and load with `import libjass from "Arnavion/libjass";`
|
||||
|
||||
Inside the package, you will find libjass.js and libjass.css, which you need to load on your website with your video.
|
||||
|
||||
Alternatively, you can build libjass from source by cloning this repository and running `npm install`. This will install the dependencies and run the build. libjass.js and libjass.css will be found in the lib/ directory.
|
||||
|
||||
@@ -25,7 +31,7 @@ Only libjass.js and libjass.css are needed to use libjass on your website. The o
|
||||
|
||||
* The src/ directory contains the source of libjass. They are TypeScript files and get compiled into JavaScript for the browser using the TypeScript compiler.
|
||||
|
||||
* gulpfile.js is the build script. The build command will use this script to build libjass.js. The gulplib/ directory contains other files used for the build.
|
||||
* build.js is the build script. The build command will use this script to build libjass.js. The build/ directory contains other files used for the build.
|
||||
|
||||
* The tests/ directory contains unit and functional tests.
|
||||
|
||||
@@ -42,7 +48,7 @@ The API documentation is linked in the Links section below. Here's an overview:
|
||||
|
||||
* The renderer uses [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) as a source of timer ticks. In each tick, it determines the set of dialogues to be shown at the current video time, renders each of them as a div, and appendChild's the div into the appropriate layer+alignment div.
|
||||
|
||||
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize) DefaultRenderer also handles resizing the subtitles automatically when the user clicks the browser's native fullscreen-video button.
|
||||
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize)
|
||||
|
||||
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - this map can be conveniently created from a CSS file containing @font-face rules using [RendererSettings.makeFontMapFromStyleElement()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.makeFontMapFromStyleElement)
|
||||
|
||||
@@ -61,6 +67,8 @@ The API documentation is linked in the Links section below. Here's an overview:
|
||||
|
||||
* WebRenderer and DefaultRenderer use [CSS3 animations](http://caniuse.com/#feat=css-animation) for effects like \mov and \fad.
|
||||
|
||||
* Using fonts attached to the script requires [ES6 typed arrays](http://caniuse.com/#feat=typedarrays) (ArrayBuffer, DataView, Uint8Array, etc).
|
||||
|
||||
|
||||
### Can I use libjass in node?
|
||||
|
||||
@@ -119,16 +127,17 @@ You can also join the IRC channel in the links section below and ask any questio
|
||||
### Supported features
|
||||
|
||||
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
|
||||
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \be, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \k, \q, \r, \pos, \move, \fad, \fade, \p
|
||||
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \be, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \k, \q, \r, \pos, \move, \fad, \fade, \t (experimental), \p
|
||||
* Custom fonts, using CSS web fonts.
|
||||
|
||||
|
||||
### Known issues
|
||||
|
||||
* Unsupported tags: \fe, \2c, \2a, \K, \kf, \ko, \org, \t, \clip, \iclip
|
||||
* Unsupported tags: \fe, \2c, \2a, \K, \kf, \ko, \org, \clip, \iclip
|
||||
* \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
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var stream = require("stream");
|
||||
|
||||
var async = require("async");
|
||||
|
||||
var task = require("async-build");
|
||||
|
||||
(function () {
|
||||
var _TypeScript = null;
|
||||
var _UglifyJS = null;
|
||||
var _npm = null;
|
||||
|
||||
Object.defineProperties(global, {
|
||||
TypeScript: { get: function () { return _TypeScript || (_TypeScript = require("./build/typescript/index.js")); } },
|
||||
UglifyJS: { get: function () { return _UglifyJS || (_UglifyJS = require("./build/uglify.js")); } },
|
||||
npm: { get: function () { return _npm || (_npm = require("npm")); } },
|
||||
});
|
||||
})();
|
||||
|
||||
task("build-tools", function (callback) {
|
||||
async.every(["./build/typescript/typescript.d.ts", "./build/typescript/index.js", "./build/doc.js"], fs.exists.bind(fs), function (allExist) {
|
||||
if (allExist) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var typescriptPath = path.join(require.resolve("typescript"), "..", "..");
|
||||
|
||||
async.waterfall([
|
||||
async.parallel.bind(async, [
|
||||
fs.readFile.bind(fs, path.join(typescriptPath, "lib", "typescript.d.ts"), "utf8"),
|
||||
fs.readFile.bind(fs, "./build/typescript/extras.d.ts", "utf8")
|
||||
]),
|
||||
function (results, callback) {
|
||||
var newDts = results[0] + "\n\n" + results[1];
|
||||
fs.writeFile("./build/typescript/typescript.d.ts", newDts, "utf8", callback);
|
||||
},
|
||||
function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["build"], callback);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
});
|
||||
});
|
||||
|
||||
task("default", ["libjass.js", "libjass.min.js"]);
|
||||
|
||||
task("version.ts", function (callback) {
|
||||
fs.exists("./src/version.ts", function (exists) {
|
||||
if (exists) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
fs.readFile.bind(fs, "./package.json"),
|
||||
function (buffer, callback) {
|
||||
try {
|
||||
var packageJson = JSON.parse(buffer);
|
||||
var versionString = packageJson.version;
|
||||
var versionParts = versionString.split(".").map(function (num) { return parseInt(num); });
|
||||
var versionFileContents =
|
||||
"/**\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();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, task.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.build("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.build("libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(task.dest("./lib")));
|
||||
});
|
||||
});
|
||||
|
||||
task("libjass.min.js", ["libjass.js"], function (callback) {
|
||||
fs.exists("./lib/libjass.min.js", function (exists) {
|
||||
if (exists) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, task.src(["./lib/libjass.js", "./lib/libjass.js.map"], { relativeTo: "./lib" })
|
||||
.pipe(UglifyJS.minify())
|
||||
.pipe(task.dest("./lib")));
|
||||
});
|
||||
});
|
||||
|
||||
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", "version.ts"], function (callback) {
|
||||
npm.load(function () {
|
||||
var testRunning = false;
|
||||
var rerunTest = false;
|
||||
|
||||
var startTest = function () {
|
||||
npm.commands["run-script"](["test-lib"], function () {
|
||||
testRunning = false;
|
||||
|
||||
if (rerunTest) {
|
||||
startTest();
|
||||
rerunTest = false;
|
||||
}
|
||||
});
|
||||
|
||||
testRunning = true;
|
||||
};
|
||||
|
||||
var runTest = function () {
|
||||
if (!testRunning) {
|
||||
startTest();
|
||||
}
|
||||
else {
|
||||
rerunTest = true;
|
||||
}
|
||||
};
|
||||
|
||||
task.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.watch("libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(task.dest("./lib"))
|
||||
.pipe(new task.FileTransform(function (file) {
|
||||
if (file.path === "libjass.js") {
|
||||
runTest();
|
||||
}
|
||||
}));
|
||||
|
||||
task.watch("./tests/unit/", runTest);
|
||||
});
|
||||
});
|
||||
|
||||
task("test-lib", ["libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-lib"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
task("test-minified", ["libjass.min.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-minified"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
// Start Selenium server with
|
||||
// java.exe -jar .\selenium-server-standalone-2.48.2.jar -Dwebdriver.ie.driver=IEDriverServer.exe -Dwebdriver.chrome.driver=chromedriver.exe
|
||||
task("test-browser", ["libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-browser"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
task("test", ["test-lib", "test-minified"]);
|
||||
|
||||
task("demo", ["libjass.js"], function () {
|
||||
return task.src(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.css"], { relativeTo: "./lib" }).pipe(task.dest("../libjass-gh-pages/demo/"));
|
||||
});
|
||||
|
||||
task("doc", ["make-doc", "test-doc"]);
|
||||
|
||||
task("make-doc", ["build-tools", "version.ts"], function () {
|
||||
var Doc = require("./build/doc.js");
|
||||
|
||||
return task.src("./src/tsconfig.json")
|
||||
.pipe(Doc.build("./api.xhtml", "./src/index.ts", "libjass"))
|
||||
.pipe(task.dest("../libjass-gh-pages/"));
|
||||
});
|
||||
|
||||
task("test-doc", ["make-doc", "libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-doc"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
task("dist", ["libjass.js", "libjass.min.js", "test", "test-browser", "demo", "doc"], function (callback) {
|
||||
var inputFiles = [
|
||||
"./README.md", "./CHANGELOG.md", "./LICENSE",
|
||||
"./lib/libjass.js", "./lib/libjass.js.map",
|
||||
"./lib/libjass.min.js", "./lib/libjass.min.js.map",
|
||||
"./lib/libjass.css"
|
||||
];
|
||||
|
||||
var files = Object.create(null);
|
||||
inputFiles.forEach(function (filename) {
|
||||
files["./dist/" + path.basename(filename)] = filename;
|
||||
});
|
||||
|
||||
async.series([
|
||||
// Clean dist/
|
||||
task.clean(Object.keys(files).concat(["./dist/package.json"])),
|
||||
|
||||
// Create dist/ if necessary
|
||||
function (callback) {
|
||||
fs.mkdir("./dist", function (err) {
|
||||
if (err && err.code !== "EEXIST") {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
// Copy all files except package.json
|
||||
async.forEachOf.bind(async, files, function (inputFilename, outputFilename, callback) {
|
||||
async.waterfall([fs.readFile.bind(fs, inputFilename), fs.writeFile.bind(fs, outputFilename)], callback);
|
||||
}),
|
||||
|
||||
// Copy package.json
|
||||
async.waterfall.bind(async, [
|
||||
fs.readFile.bind(fs, "./package.json"),
|
||||
function (data, callback) {
|
||||
try {
|
||||
var packageJson = JSON.parse(data);
|
||||
packageJson.devDependencies = undefined;
|
||||
packageJson.private = undefined;
|
||||
packageJson.scripts = undefined;
|
||||
packageJson.main = "libjass.js";
|
||||
}
|
||||
catch (ex) {
|
||||
callback(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, new Buffer(JSON.stringify(packageJson, null, "\t")));
|
||||
},
|
||||
fs.writeFile.bind(fs, "./dist/package.json")
|
||||
])
|
||||
], callback);
|
||||
});
|
||||
|
||||
task.runArgv(function (err) {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -18,28 +18,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Transform } from "stream";
|
||||
import Vinyl = require("vinyl");
|
||||
|
||||
import { makeTransform } from "./helpers";
|
||||
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 (let a of arr) {
|
||||
for (const a of arr) {
|
||||
result = result.concat(a);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -64,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) {
|
||||
@@ -86,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;
|
||||
@@ -112,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];
|
||||
@@ -170,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;
|
||||
@@ -189,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(', ') }>`);
|
||||
}
|
||||
|
||||
@@ -205,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);
|
||||
@@ -221,6 +215,9 @@ function writeDescription(text: string): string {
|
||||
inCodeBlock = false;
|
||||
line = `</code></pre>${ line }`;
|
||||
}
|
||||
else if (line.length === 0 && !inCodeBlock) {
|
||||
line = "</p><p>";
|
||||
}
|
||||
|
||||
return line;
|
||||
}).join("\n");
|
||||
@@ -229,6 +226,12 @@ function writeDescription(text: string): string {
|
||||
result += '</code></pre>';
|
||||
}
|
||||
|
||||
result = `<p>${ result }</p>`.replace(/<p>\s*<\/p>/g, "").replace(/<p>\s+/g, "<p>").replace(/\s+<\/p>/g, "</p>");
|
||||
|
||||
if (result.substr("<p>".length).indexOf("<p>") === -1) {
|
||||
result = result.substring("<p>".length, result.length - "</p>".length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -261,7 +264,7 @@ function functionToHtml(func: AST.Function): string[] {
|
||||
func.isStatic ? ' static' : ''}">`,
|
||||
` <dt class="name">${ toLink(func) }</dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(func.description) }</p>`,
|
||||
` ${ writeDescription(func.description) }`,
|
||||
' </dd>',
|
||||
` <dd class="usage"><fieldset><legend /><pre><code>${
|
||||
sanitize(
|
||||
@@ -281,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);
|
||||
@@ -292,7 +295,7 @@ function interfaceToHtml(interfase: AST.Interface): string[] {
|
||||
interfase.baseTypes.map(baseType => baseType instanceof AST.TypeReference ? toLink(baseType) : baseType.name).join(', ')
|
||||
}` : '' }</dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(interfase.description) }</p>`,
|
||||
` ${ writeDescription(interfase.description) }`,
|
||||
' </dd>',
|
||||
' <dd class="members">'
|
||||
].concat(flatten(members.map(member => {
|
||||
@@ -303,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>',
|
||||
@@ -313,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);
|
||||
@@ -323,10 +326,10 @@ 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">',
|
||||
` <p>${ writeDescription(clazz.description) }</p>`,
|
||||
` ${ writeDescription(clazz.description) }`,
|
||||
' </dd>',
|
||||
` <dd class="usage"><fieldset><legend /><pre><code>${
|
||||
sanitize(
|
||||
@@ -343,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>',
|
||||
@@ -357,7 +360,7 @@ function enumToHtml(enumType: AST.Enum): string[] {
|
||||
`<dl id="${ toId(enumType) }" class="enum${ enumType.isPrivate ? ' private' : '' }">`,
|
||||
` <dt class="name">enum <a href="#${ sanitize(enumType.fullName) }">${ sanitize(enumType.name) }</a></dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(enumType.description) }</p>`,
|
||||
` ${ writeDescription(enumType.description) }`,
|
||||
' </dd>'
|
||||
].concat([
|
||||
' <dd class="members">'
|
||||
@@ -365,7 +368,7 @@ function enumToHtml(enumType: AST.Enum): string[] {
|
||||
` <dl id="${ toId(member) }" class="member">`,
|
||||
` <dt class="name">${ toLink(member) } = ${ member.value }</dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(member.description) }</p>`,
|
||||
` ${ writeDescription(member.description) }`,
|
||||
' </dd>',
|
||||
' </dl>'
|
||||
]))).concat([
|
||||
@@ -382,14 +385,14 @@ function propertyToHtml(property: AST.Property): string[] {
|
||||
].concat((property.getter === null) ? [] : [
|
||||
` <dt class="getter${ property.getter.isPrivate ? ' private' : '' }">Getter</dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(property.getter.description) }</p>`,
|
||||
` ${ writeDescription(property.getter.description) }`,
|
||||
' </dd>',
|
||||
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`var result = ${ toUsageName(property) };`) }</code></pre></fieldset></dd>`,
|
||||
` <dd class="return type">${ sanitize(property.getter.type) }</dd>`
|
||||
]).concat((property.setter === null) ? [] : [
|
||||
` <dt class="setter${ property.setter.isPrivate ? ' private' : '' }">Setter</dt>`,
|
||||
' <dd class="description">',
|
||||
` <p>${ writeDescription(property.setter.description) }</p>`,
|
||||
` ${ writeDescription(property.setter.description) }`,
|
||||
' </dd>',
|
||||
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`${ toUsageName(property) } = value;`) }</code></pre></fieldset></dd>`
|
||||
].concat(writeParameters([new AST.Parameter("value", "", property.setter.type)]).map(indenter(1)))).concat([
|
||||
@@ -398,31 +401,27 @@ function propertyToHtml(property: AST.Property): string[] {
|
||||
]);
|
||||
}
|
||||
|
||||
export function gulp(outputFilePath: string, root: string, rootNamespaceName: string): any {
|
||||
var compiler = new Compiler();
|
||||
|
||||
return makeTransform(function (file: Vinyl): void {
|
||||
var self: Transform<Vinyl> = this;
|
||||
export function build(outputFilePath: string, root: string, rootNamespaceName: string): FileTransform {
|
||||
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(new Vinyl({
|
||||
this.push({
|
||||
path: outputFilePath,
|
||||
contents: Buffer.concat([new Buffer(
|
||||
`<?xml version="1.0" encoding="utf-8" ?>
|
||||
@@ -582,10 +581,10 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
<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[] = [];
|
||||
for (let memberName of Object.keys(namespace.members)) {
|
||||
const namespaceMembers: AST.NamespaceMember[] = [];
|
||||
for (const memberName of Object.keys(namespace.members)) {
|
||||
namespaceMembers.push(namespace.members[memberName]);
|
||||
}
|
||||
|
||||
@@ -606,13 +605,13 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
)]));
|
||||
})).concat(moduleNames.map(moduleName => {
|
||||
var module = modules[moduleName];
|
||||
const module = modules[moduleName];
|
||||
|
||||
var moduleMembers: AST.ModuleMemberWithoutReference[] = [];
|
||||
for (let memberName of Object.keys(module.members)) {
|
||||
var member = module.members[memberName];
|
||||
if ((<AST.HasParent><any>member).parent === module) {
|
||||
moduleMembers.push(<AST.ModuleMemberWithoutReference>member);
|
||||
const moduleMembers: AST.ModuleMemberWithoutReference[] = [];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
const member = module.members[memberName];
|
||||
if ((member as AST.HasParent).parent === module) {
|
||||
moduleMembers.push(member as AST.ModuleMemberWithoutReference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,22 +641,22 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
<div class="content">
|
||||
`
|
||||
)]).concat(flatten(namespaceNames.map(namespaceName => {
|
||||
var namespace = namespaces[namespaceName];
|
||||
const namespace = namespaces[namespaceName];
|
||||
|
||||
var namespaceMembers: AST.NamespaceMember[] = [];
|
||||
for (let memberName of Object.keys(namespace.members)) {
|
||||
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>
|
||||
`
|
||||
@@ -670,7 +669,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let property of properties) {
|
||||
for (const property of properties) {
|
||||
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -687,7 +686,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let func of functions) {
|
||||
for (const func of functions) {
|
||||
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -704,7 +703,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let interfase of interfaces) {
|
||||
for (const interfase of interfaces) {
|
||||
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -721,7 +720,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let clazz of classes) {
|
||||
for (const clazz of classes) {
|
||||
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -738,7 +737,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let enumType of enums) {
|
||||
for (const enumType of enums) {
|
||||
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -755,13 +754,13 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
|
||||
return result;
|
||||
}))).concat(flatten(moduleNames.map(moduleName => {
|
||||
var module = modules[moduleName];
|
||||
const module = modules[moduleName];
|
||||
|
||||
var moduleMembers: AST.ModuleMember[] = [];
|
||||
for (let memberName of Object.keys(module.members)) {
|
||||
var member = module.members[memberName];
|
||||
if ((<AST.HasParent><any>member).parent === module) {
|
||||
moduleMembers.push(<AST.ModuleMember>member);
|
||||
const moduleMembers: AST.ModuleMember[] = [];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
const member = module.members[memberName];
|
||||
if ((member as AST.HasParent).parent === module) {
|
||||
moduleMembers.push(member);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,13 +770,13 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
|
||||
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>
|
||||
`
|
||||
@@ -790,7 +789,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let property of properties) {
|
||||
for (const property of properties) {
|
||||
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -807,7 +806,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let func of functions) {
|
||||
for (const func of functions) {
|
||||
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -824,7 +823,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let interfase of interfaces) {
|
||||
for (const interfase of interfaces) {
|
||||
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -841,7 +840,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let clazz of classes) {
|
||||
for (const clazz of classes) {
|
||||
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -858,7 +857,7 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
`
|
||||
));
|
||||
|
||||
for (let enumType of enums) {
|
||||
for (const enumType of enums) {
|
||||
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
|
||||
}
|
||||
|
||||
@@ -880,6 +879,6 @@ export function gulp(outputFilePath: string, root: string, rootNamespaceName: st
|
||||
</html>
|
||||
`
|
||||
)]))
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Type definitions for Node.js v0.12.0
|
||||
// Project: http://nodejs.org/
|
||||
// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/borisyankov/DefinitelyTyped>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
declare module "path" {
|
||||
export function basename(p: string, ext?: string): string;
|
||||
export function extname(p: string): string;
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* 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 path = require("path");
|
||||
import ts = require("typescript");
|
||||
|
||||
import { File, FileTransform } from "async-build";
|
||||
|
||||
import * as AST from "./ast";
|
||||
import { walk } from "./walker";
|
||||
|
||||
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;
|
||||
|
||||
compile(projectConfigFile: File) {
|
||||
this._projectRoot = path.dirname(projectConfigFile.path);
|
||||
|
||||
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);
|
||||
|
||||
const syntacticDiagnostics = this._program.getSyntacticDiagnostics();
|
||||
if (syntacticDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(syntacticDiagnostics);
|
||||
throw new Error("There were one or more syntactic diagnostics.");
|
||||
}
|
||||
|
||||
const optionsDiagnostics = this._program.getOptionsDiagnostics();
|
||||
if (optionsDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(optionsDiagnostics);
|
||||
throw new Error("There were one or more options diagnostics.");
|
||||
}
|
||||
|
||||
const globalDiagnostics = this._program.getGlobalDiagnostics();
|
||||
if (globalDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(globalDiagnostics);
|
||||
throw new Error("There were one or more global diagnostics.");
|
||||
}
|
||||
|
||||
const semanticDiagnostics = this._program.getSemanticDiagnostics();
|
||||
if (semanticDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(semanticDiagnostics);
|
||||
throw new Error("There were one or more semantic diagnostics.");
|
||||
}
|
||||
};
|
||||
|
||||
writeFiles(outputStream: FileTransform) {
|
||||
this._host.setOutputStream(outputStream);
|
||||
|
||||
const emitDiagnostics = this._program.emit().diagnostics;
|
||||
if (emitDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(emitDiagnostics);
|
||||
throw new Error("There were one or more emit diagnostics.");
|
||||
}
|
||||
};
|
||||
|
||||
get projectRoot(): string {
|
||||
return this._projectRoot;
|
||||
}
|
||||
|
||||
get typeChecker(): ts.TypeChecker {
|
||||
return this._program.getTypeChecker();
|
||||
}
|
||||
|
||||
get sourceFiles(): ts.SourceFile[] {
|
||||
return this._program.getSourceFiles();
|
||||
}
|
||||
|
||||
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
for (const diagnostic of diagnostics) {
|
||||
let message = "";
|
||||
|
||||
if (diagnostic.file) {
|
||||
const location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
||||
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
|
||||
}
|
||||
|
||||
message +=
|
||||
ts.DiagnosticCategory[diagnostic.category].toLowerCase() +
|
||||
` TS${ diagnostic.code }: ` +
|
||||
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function build(root: string, rootNamespaceName: string): FileTransform {
|
||||
const compiler = new Compiler();
|
||||
|
||||
return new FileTransform(function (projectConfigFile): void {
|
||||
console.log("Compiling " + projectConfigFile.path + "...");
|
||||
|
||||
compiler.compile(projectConfigFile);
|
||||
|
||||
const walkResult = walk(compiler, root, rootNamespaceName);
|
||||
addJSDocComments(walkResult.modules);
|
||||
|
||||
compiler.writeFiles(this);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
});
|
||||
}
|
||||
|
||||
function addJSDocComments(modules: { [name: string]: AST.Module }): void {
|
||||
function visitor(current: AST.Module | AST.ModuleMember | AST.InterfaceMember) {
|
||||
if (current instanceof AST.Module) {
|
||||
for (const memberName of Object.keys(current.members)) {
|
||||
visitor(current.members[memberName]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newComments: string[] = [];
|
||||
|
||||
if (current instanceof AST.Class) {
|
||||
newComments.push("@constructor");
|
||||
|
||||
if (current.baseType !== null) {
|
||||
const baseType = current.baseType;
|
||||
newComments.push(
|
||||
"@extends {" +
|
||||
baseType.fullName + (
|
||||
(baseType instanceof AST.TypeReference && baseType.generics.length) > 0 ?
|
||||
(".<" + (baseType as AST.TypeReference).generics.map(generic => generic.fullName).join(", ") + ">") :
|
||||
""
|
||||
) +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
if (current.interfaces.length > 0) {
|
||||
current.interfaces.forEach(interfase => {
|
||||
newComments.push(
|
||||
"@implements {" +
|
||||
interfase.fullName + (
|
||||
(interfase instanceof AST.TypeReference && interfase.generics.length > 0) ?
|
||||
(".<" + interfase.generics.map(generic => generic.fullName).join(", ") + ">") :
|
||||
""
|
||||
) +
|
||||
"}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(current.members).forEach(memberName => visitor(current.members[memberName]));
|
||||
}
|
||||
else if (current instanceof AST.Enum) {
|
||||
newComments.push("@enum");
|
||||
}
|
||||
else if (current instanceof AST.Reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.parent instanceof AST.Namespace) {
|
||||
newComments.push("@memberOf " + current.parent.fullName);
|
||||
}
|
||||
|
||||
if (AST.hasStringGenerics(current) && current.generics.length > 0) {
|
||||
newComments.push("@template " + current.generics.join(", "));
|
||||
}
|
||||
|
||||
if ((current as AST.CanBePrivate).isPrivate) {
|
||||
newComments.push("@private");
|
||||
}
|
||||
|
||||
if ((current as AST.CanBeProtected).isProtected) {
|
||||
newComments.push("@protected");
|
||||
}
|
||||
|
||||
if ((current as AST.CanBeStatic).isStatic) {
|
||||
newComments.push("@static");
|
||||
}
|
||||
|
||||
if (newComments.length > 0) {
|
||||
if (current instanceof AST.Property) {
|
||||
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) {
|
||||
(node as any)["typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
else {
|
||||
(current.astNode as any)["typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const moduleName of Object.keys(modules)) {
|
||||
visitor(modules[moduleName]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class FakeSourceFile {
|
||||
public text: string;
|
||||
public lineMap: number[];
|
||||
|
||||
constructor(originalSourceFile: ts.SourceFile) {
|
||||
this.text = originalSourceFile.text;
|
||||
this.lineMap = ts.getLineStarts(originalSourceFile).slice();
|
||||
}
|
||||
|
||||
addComment(originalComment: ts.CommentRange, newComments: string[]): ts.CommentRange & { sourceFile: FakeSourceFile } {
|
||||
var pos = this.text.length;
|
||||
|
||||
this.text += "/**\n";
|
||||
this.lineMap.push(this.text.length);
|
||||
|
||||
var originalCommentLines = this.text.substring(originalComment.pos, originalComment.end).split("\n");
|
||||
originalCommentLines.shift();
|
||||
|
||||
originalCommentLines = originalCommentLines.map(line => line.replace(/^\s+/, " "));
|
||||
|
||||
if (originalCommentLines.length > 1) {
|
||||
originalCommentLines.splice(originalCommentLines.length - 1, 0, " *");
|
||||
}
|
||||
|
||||
for (const newComment of newComments) {
|
||||
originalCommentLines.splice(originalCommentLines.length - 1, 0, " * " + newComment);
|
||||
}
|
||||
|
||||
for (const newCommentLine of originalCommentLines) {
|
||||
this.text += newCommentLine + "\n";
|
||||
this.lineMap.push(this.text.length);
|
||||
}
|
||||
|
||||
var end = this.text.length;
|
||||
|
||||
return { pos, end, hasTrailingNewLine: originalComment.hasTrailingNewLine, kind: ts.SyntaxKind.MultiLineCommentTrivia, sourceFile: this };
|
||||
}
|
||||
}
|
||||
|
||||
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
|
||||
*/
|
||||
|
||||
export const oldGetLeadingCommentRangesOfNodeFromText: typeof ts.getLeadingCommentRangesOfNodeFromText = ts.getLeadingCommentRangesOfNodeFromText.bind(ts);
|
||||
|
||||
/*
|
||||
ts.getLeadingCommentRangesOfNodeFromText = (node: ts.Node, text: string) => {
|
||||
const originalComments = oldGetLeadingCommentRangesOfNodeFromText(node, text);
|
||||
|
||||
if (originalComments !== undefined && (<any>node)["typescript-new-comment"] !== undefined) {
|
||||
const sourceFileOfNode = ts.getSourceFileOfNode(node);
|
||||
let fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
|
||||
if (fakeSourceFile === undefined) {
|
||||
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
|
||||
}
|
||||
|
||||
originalComments[originalComments.length - 1] = fakeSourceFile.addComment(originalComments[originalComments.length - 1], (<any>node)["typescript-new-comment"]);
|
||||
}
|
||||
|
||||
return originalComments;
|
||||
};
|
||||
|
||||
var oldWriteCommentRange: typeof ts.writeCommentRange = ts.writeCommentRange.bind(ts);
|
||||
ts.writeCommentRange = (text: string, lineMap: number[], writer: ts.EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => {
|
||||
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
|
||||
const currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
|
||||
text = currentSourceFile.text;
|
||||
lineMap = currentSourceFile.lineMap;
|
||||
}
|
||||
|
||||
return oldWriteCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
|
||||
};
|
||||
*/
|
||||
@@ -0,0 +1,23 @@
|
||||
declare namespace ts {
|
||||
interface EmitTextWriter { }
|
||||
|
||||
interface IntrinsicType extends Type {
|
||||
intrinsicName: string;
|
||||
}
|
||||
|
||||
interface SourceFile {
|
||||
lineMap: number[];
|
||||
}
|
||||
|
||||
function forEachProperty<T, U>(map: Map<T>, callback: (value: T, key: string) => U): U;
|
||||
function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments;
|
||||
function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration): ExpressionWithTypeArguments[];
|
||||
function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): ExpressionWithTypeArguments[];
|
||||
function getLeadingCommentRangesOfNodeFromText(node: Node, text: string): CommentRange[];
|
||||
function getLineStarts(sourceFile: SourceFile): number[];
|
||||
function getSourceFileOfNode(node: Node): SourceFile;
|
||||
function getTextOfNode(node: Node, includeTrivia?: boolean): string;
|
||||
function normalizeSlashes(path: string): string;
|
||||
function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
|
||||
function hasModifier(node: Node, flags: ModifierFlags): boolean;
|
||||
}
|
||||
@@ -18,4 +18,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { gulp, watch } from "./compiler";
|
||||
export { build } from "./compiler";
|
||||
@@ -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,16 +68,16 @@ 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 (let statement of sourceFile.statements) {
|
||||
for (const statement of sourceFile.statements) {
|
||||
this._walk(statement, module);
|
||||
}
|
||||
|
||||
@@ -87,40 +87,41 @@ 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:
|
||||
case ts.SyntaxKind.ForOfStatement:
|
||||
case ts.SyntaxKind.IfStatement:
|
||||
case ts.SyntaxKind.TypeAliasDeclaration:
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -129,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:
|
||||
@@ -151,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.");
|
||||
}
|
||||
}
|
||||
@@ -160,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:
|
||||
@@ -175,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);
|
||||
@@ -200,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) }`
|
||||
);
|
||||
|
||||
@@ -211,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));
|
||||
|
||||
@@ -246,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));
|
||||
|
||||
@@ -273,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;
|
||||
|
||||
@@ -293,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 }`
|
||||
);
|
||||
|
||||
@@ -312,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;
|
||||
|
||||
@@ -320,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 }`
|
||||
);
|
||||
}
|
||||
@@ -353,22 +353,22 @@ 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;
|
||||
}
|
||||
|
||||
for (let declaration of symbol.declarations) {
|
||||
for (const declaration of symbol.declarations) {
|
||||
this._walkClassMember(declaration, clazz);
|
||||
}
|
||||
});
|
||||
|
||||
ts.forEachValue(type.symbol.members, symbol => {
|
||||
for (let declaration of symbol.declarations) {
|
||||
ts.forEachProperty(type.symbol.members, symbol => {
|
||||
for (const declaration of symbol.declarations) {
|
||||
this._walkClassMember(declaration, clazz);
|
||||
}
|
||||
});
|
||||
@@ -377,29 +377,29 @@ 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 => {
|
||||
for (let declaration of symbol.declarations) {
|
||||
ts.forEachProperty(type.symbol.members, symbol => {
|
||||
for (const declaration of symbol.declarations) {
|
||||
this._walkInterfaceMember(declaration, interfase);
|
||||
}
|
||||
});
|
||||
@@ -408,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);
|
||||
|
||||
@@ -451,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 (let 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);
|
||||
}
|
||||
}
|
||||
@@ -472,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);
|
||||
for (let element of node.exportClause.elements) {
|
||||
var importedName = element.propertyName && element.propertyName.text || element.name.text;
|
||||
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
|
||||
for (const element of node.exportClause.elements) {
|
||||
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 (let element of node.exportClause.elements) {
|
||||
(<AST.CanBePrivate><any>parent.members[element.name.text]).isPrivate = false;
|
||||
for (const element of node.exportClause.elements) {
|
||||
(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 }`;
|
||||
@@ -497,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 = [];
|
||||
@@ -507,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];
|
||||
}).filter(line => line.length > 0);
|
||||
});
|
||||
|
||||
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 (let line of lines) {
|
||||
var firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
|
||||
var firstWord = firstWordMatch[1];
|
||||
var remainingLine = line.substring(firstWordMatch[0].length);
|
||||
for (const line of lines) {
|
||||
const firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
|
||||
const firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
|
||||
let remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
|
||||
|
||||
if (firstWord[0] === "@") {
|
||||
lastRead = null;
|
||||
@@ -552,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);
|
||||
@@ -606,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++;
|
||||
}
|
||||
@@ -626,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];
|
||||
@@ -640,16 +643,24 @@ class Walker {
|
||||
return signatureDeclaration.typeParameters.map(typeParameter => typeParameter.name.text);
|
||||
}
|
||||
|
||||
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.TypeReferenceNode | ts.HeritageClauseElement): (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, []);
|
||||
@@ -668,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));
|
||||
@@ -685,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;
|
||||
}
|
||||
|
||||
@@ -694,18 +705,18 @@ class Walker {
|
||||
}
|
||||
|
||||
link(rootNamespaceName: string): void {
|
||||
for (let moduleName of Object.keys(this.modules)) {
|
||||
var module = this.modules[moduleName];
|
||||
for (const moduleName of Object.keys(this.modules)) {
|
||||
const module = this.modules[moduleName];
|
||||
|
||||
for (let memberName of Object.keys(module.members)) {
|
||||
var member = module.members[memberName];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
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 => {
|
||||
@@ -713,7 +724,7 @@ class Walker {
|
||||
return this._resolveTypeReference(interfase);
|
||||
}
|
||||
|
||||
return <AST.TypeReference | AST.IntrinsicTypeReference>interfase;
|
||||
return interfase;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -723,13 +734,13 @@ class Walker {
|
||||
return this._resolveTypeReference(baseType);
|
||||
}
|
||||
|
||||
return <AST.TypeReference | AST.IntrinsicTypeReference>baseType;
|
||||
return baseType;
|
||||
});
|
||||
}
|
||||
|
||||
else if (member instanceof AST.Enum) {
|
||||
var value = 0;
|
||||
for (let enumMember of member.members) {
|
||||
let value = 0;
|
||||
for (const enumMember of member.members) {
|
||||
if (enumMember.value === null) {
|
||||
enumMember.value = value;
|
||||
}
|
||||
@@ -749,18 +760,18 @@ class Walker {
|
||||
}
|
||||
|
||||
private _moduleToNamespace(module: AST.Module): void {
|
||||
for (let memberName of Object.keys(module.members)) {
|
||||
var member = module.members[memberName];
|
||||
for (const memberName of Object.keys(module.members)) {
|
||||
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);
|
||||
@@ -769,62 +780,68 @@ class Walker {
|
||||
this.namespaces[newNamespace.fullName] = newNamespace;
|
||||
}
|
||||
|
||||
this._moduleToNamespace(this.modules[(<AST.Reference>member).moduleName]);
|
||||
let referencedModuleName = member.moduleName;
|
||||
let referencedModule = this.modules[referencedModuleName];
|
||||
if (referencedModule === undefined && ((referencedModuleName + "/index") in this.modules)) {
|
||||
member.moduleName = referencedModuleName = referencedModuleName + "/index";
|
||||
referencedModule = this.modules[referencedModuleName];
|
||||
}
|
||||
this._moduleToNamespace(referencedModule);
|
||||
|
||||
this._scope.leave();
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -837,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 (let sourceFile of sourceFiles) {
|
||||
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"
|
||||
) {
|
||||
@@ -0,0 +1,445 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var UglifyJS = require("uglify-js");
|
||||
|
||||
var FileTransform = require("async-build").FileTransform;
|
||||
|
||||
var Run = (function () {
|
||||
function Run(outputLibraryName, unusedVarsToIgnore) {
|
||||
this._outputLibraryName = outputLibraryName;
|
||||
this._unusedVarsToIgnore = unusedVarsToIgnore;
|
||||
|
||||
this._root = UglifyJS.parse(fs.readFileSync(path.resolve(__filename, "..", "umd-wrapper.js"), "utf8"));
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
this._toInsert = null;
|
||||
|
||||
this._rootSourceMap = null;
|
||||
}
|
||||
|
||||
Run.prototype.addFile = function (file) {
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
try {
|
||||
this._toInsert = UglifyJS.parse(file.contents.toString(), {
|
||||
filename: path.basename(file.path),
|
||||
toplevel: null,
|
||||
}).body;
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex instanceof UglifyJS.JS_Parse_Error) {
|
||||
throw new Error("UglifyJS parse error: " + ex.toString() + "\n");
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
break;
|
||||
|
||||
case ".map":
|
||||
var rawSourceMap = JSON.parse(file.contents.toString());
|
||||
|
||||
this._rootSourceMap = UglifyJS.SourceMap({
|
||||
file: this._outputLibraryName + ".js",
|
||||
root: "",
|
||||
orig: rawSourceMap,
|
||||
});
|
||||
|
||||
var generator = this._rootSourceMap.get();
|
||||
|
||||
rawSourceMap.sources.forEach(function (sourceRelativePath, index) {
|
||||
generator.setSourceContent(sourceRelativePath, rawSourceMap.sourcesContent[index]);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Run.prototype.build = function (outputStream) {
|
||||
var _this = this;
|
||||
|
||||
|
||||
// Splice in the TS output into the UMD wrapper.
|
||||
var insertionParent = this._root.body[0].body.args[1].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;
|
||||
}
|
||||
}
|
||||
|
||||
insertionParent.splice(-1, 0, node);
|
||||
this._toInsert.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
insertionParent.splice.apply(insertionParent, [-1, 0].concat(this._toInsert));
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
defineCall.expression.name = "def";
|
||||
|
||||
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"
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Lambda && !node.name) {
|
||||
node.name = Object.create(UglifyJS.AST_Node.prototype);
|
||||
node.name.print = function () { };
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Fix alignment of multi-line block comments
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node.start && node.start.comments_before) {
|
||||
node.start.comments_before.forEach(function (comment) {
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = comment.value.split("\n");
|
||||
if (lines.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
var indent = " "; // 5 spaces
|
||||
for (var i = 0; i < comment.col; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+$/, indent);
|
||||
comment.value = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^\s+/, indent); })).join("\n");
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
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()) {
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) {
|
||||
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().argnames });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolVar) {
|
||||
if (_this._unusedVarsToIgnore.indexOf(node.name) !== -1) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
|
||||
if (this.parent(1).definitions.length === 1) {
|
||||
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
|
||||
}
|
||||
}
|
||||
else {
|
||||
haveUnusedVars = true;
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolDefun) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (nodesToRemove.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach(function (tuple) {
|
||||
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
}
|
||||
|
||||
|
||||
// Move var statements at the end of blocks (generated by TS for rest parameters) to the start of the block.
|
||||
// This is needed to prevent unreachable-code warnings from UJS
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_Block &&
|
||||
node.body[node.body.length - 1] instanceof UglifyJS.AST_Var
|
||||
) {
|
||||
node.body.unshift(node.body.pop());
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Split multiple vars per declaration into one var per declaration
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_Var &&
|
||||
node.definitions.length > 1 &&
|
||||
this.parent() instanceof UglifyJS.AST_Block
|
||||
) {
|
||||
var parent = this.parent().body;
|
||||
parent.splice.apply(parent, [parent.indexOf(node), 1].concat(node.definitions.map(function (definition) {
|
||||
return new UglifyJS.AST_Var({ start: node.start, end: node.end, definitions: [definition] });
|
||||
})));
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Rename all function arguments that begin with _ to not have the _.
|
||||
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
|
||||
// so that it matches the JSDoc (and looks nicer).
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_SymbolFunarg &&
|
||||
node.thedef.name[0] === "_" &&
|
||||
node.thedef.name[1] !== "_" &&
|
||||
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
|
||||
) {
|
||||
node.thedef.name = node.thedef.name.slice(1);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var output = {
|
||||
source_map: this._rootSourceMap,
|
||||
ascii_only: true,
|
||||
beautify: 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")])
|
||||
});
|
||||
|
||||
outputStream.push({
|
||||
path: this._outputLibraryName + ".js.map",
|
||||
contents: new Buffer(this._rootSourceMap.get().toString())
|
||||
});
|
||||
|
||||
// Print unused variables
|
||||
if (haveUnusedVars) {
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolVar && node.unreferenced()) {
|
||||
if (_this._unusedVarsToIgnore.indexOf(node.name) === -1) {
|
||||
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return Run;
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
build: function (outputLibraryName, unusedVarsToIgnore) {
|
||||
var run = new Run(outputLibraryName, unusedVarsToIgnore);
|
||||
|
||||
return new FileTransform(function (file) {
|
||||
run.addFile(file);
|
||||
}, function () {
|
||||
run.build(this);
|
||||
});
|
||||
},
|
||||
|
||||
watch: function (outputLibraryName, unusedVarsToIgnore) {
|
||||
var files = Object.create(null);
|
||||
|
||||
return new FileTransform(function (file) {
|
||||
if (file.path !== "END") {
|
||||
files[file.path] = file;
|
||||
}
|
||||
else {
|
||||
var run = new Run(outputLibraryName, unusedVarsToIgnore);
|
||||
Object.keys(files).forEach(function (filename) {
|
||||
run.addFile(files[filename]);
|
||||
});
|
||||
run.build(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
minify: function () {
|
||||
var codeFile = null;
|
||||
var sourceMapFile = null;
|
||||
|
||||
return new FileTransform(function (file) {
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
codeFile = file;
|
||||
break;
|
||||
case ".map":
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = null;
|
||||
root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: root
|
||||
});
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Warnings
|
||||
root.scope_warnings({
|
||||
func_arguments: false
|
||||
});
|
||||
|
||||
|
||||
// Compress
|
||||
var compressor = UglifyJS.Compressor({
|
||||
warnings: true,
|
||||
screw_ie8: true
|
||||
});
|
||||
root = root.transform(compressor);
|
||||
|
||||
|
||||
// Mangle
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.compute_char_frequency();
|
||||
root.mangle_names({ screw_ie8: true });
|
||||
root = UglifyJS.mangle_properties(root, {
|
||||
regex: /^_/
|
||||
});
|
||||
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
ascii_only: true,
|
||||
comments: function (node, comment) {
|
||||
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
screw_ie8: true
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.path)]);
|
||||
this.push(codeFile);
|
||||
|
||||
var inputSourceMapObject = JSON.parse(sourceMapFile.contents.toString());
|
||||
var outputSourceMapObject = output.source_map.get();
|
||||
outputSourceMapObject._sources.toArray().forEach(function (filename, i) {
|
||||
outputSourceMapObject.setSourceContent(filename, inputSourceMapObject.sourcesContent[i]);
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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.
|
||||
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
|
||||
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalSymbolUnreferenced.call(this);
|
||||
};
|
||||
@@ -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");
|
||||
});
|
||||
@@ -1,225 +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.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var gulp = require("gulp");
|
||||
|
||||
(function () {
|
||||
var _TypeScript = null;
|
||||
var _UglifyJS = null;
|
||||
var _npm = null;
|
||||
|
||||
Object.defineProperties(global, {
|
||||
TypeScript: { get: function () { return _TypeScript || (_TypeScript = require("./gulplib/typescript/index.js")); } },
|
||||
UglifyJS: { get: function () { return _UglifyJS || (_UglifyJS = require("./gulplib/uglify.js")); } },
|
||||
npm: { get: function () { return _npm || (_npm = require("npm")); } },
|
||||
});
|
||||
})();
|
||||
|
||||
gulp.task("default", ["libjass.js", "libjass.min.js"]);
|
||||
|
||||
gulp.task("libjass.js", ["build-gulplib"], function () {
|
||||
if (fs.existsSync("./lib/libjass.js")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return gulp.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.gulp("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.gulp("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(gulp.dest("./lib"));
|
||||
});
|
||||
|
||||
gulp.task("libjass.min.js", ["libjass.js"], function () {
|
||||
if (fs.existsSync("./lib/libjass.min.js")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return gulp.src(["./lib/libjass.js", "./lib/libjass.js.map"])
|
||||
.pipe(UglifyJS.minify())
|
||||
.pipe(gulp.dest("./lib"));
|
||||
});
|
||||
|
||||
gulp.task("clean", function () {
|
||||
["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"].forEach(function (file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex.code !== "ENOENT") {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("watch", ["clean", "build-gulplib"], function (callback) {
|
||||
var helpers = require("./gulplib/helpers.js");
|
||||
|
||||
npm.load(function () {
|
||||
var testRunning = false;
|
||||
var rerunTest = false;
|
||||
|
||||
var startTest = function () {
|
||||
npm.commands["run-script"](["test-lib"], function () {
|
||||
testRunning = false;
|
||||
|
||||
if (rerunTest) {
|
||||
startTest();
|
||||
rerunTest = false;
|
||||
}
|
||||
});
|
||||
|
||||
testRunning = true;
|
||||
};
|
||||
|
||||
var runTest = function () {
|
||||
if (!testRunning) {
|
||||
startTest();
|
||||
}
|
||||
else {
|
||||
rerunTest = true;
|
||||
}
|
||||
};
|
||||
|
||||
gulp.src("./src/tsconfig.json")
|
||||
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
|
||||
.pipe(UglifyJS.watch("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
|
||||
.pipe(gulp.dest("./lib"))
|
||||
.pipe(helpers.makeTransform(function (file) {
|
||||
if (path.basename(file.path) === "libjass.js") {
|
||||
runTest();
|
||||
}
|
||||
}));
|
||||
|
||||
gulp.watch(["./tests/unit/*.js"], { debounceDelay: 1 }, function () {
|
||||
runTest();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("test-lib", ["libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-lib"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("test-minified", ["libjass.min.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-minified"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
// Start Selenium server with
|
||||
// java.exe -jar .\selenium-server-standalone-2.45.0.jar "-Dwebdriver.ie.driver=$PWD\IEDriverServer.exe" "-Dwebdriver.chrome.driver=$PWD\chromedriver.exe"
|
||||
gulp.task("test-browser", ["libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-browser"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("test", ["test-lib", "test-minified"]);
|
||||
|
||||
gulp.task("demo", ["libjass.js"], function () {
|
||||
return gulp.src(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.css"]).pipe(gulp.dest("../libjass-gh-pages/demo/"));
|
||||
});
|
||||
|
||||
gulp.task("doc", ["make-doc", "test-doc"]);
|
||||
|
||||
gulp.task("make-doc", ["libjass.js"], function () {
|
||||
var Doc = require("./gulplib/doc.js");
|
||||
|
||||
return gulp.src("./src/tsconfig.json")
|
||||
.pipe(Doc.gulp("./api.xhtml", "./src/index.ts", "libjass"))
|
||||
.pipe(gulp.dest("../libjass-gh-pages/"));
|
||||
});
|
||||
|
||||
gulp.task("test-doc", ["make-doc", "libjass.js"], function (callback) {
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["test-doc"], callback);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("dist", ["clean", "default", "test", "test-browser", "demo", "doc"], function () {
|
||||
var inputFiles = [
|
||||
"./README.md", "./CHANGELOG.md", "./LICENSE",
|
||||
"./lib/libjass.js", "./lib/libjass.js.map",
|
||||
"./lib/libjass.min.js", "./lib/libjass.min.js.map",
|
||||
"./lib/libjass.css"
|
||||
];
|
||||
|
||||
var files = Object.create(null);
|
||||
inputFiles.forEach(function (filename) {
|
||||
files["./dist/" + path.basename(filename)] = filename;
|
||||
});
|
||||
|
||||
// Clean dist/
|
||||
Object.keys(files).concat("./dist/package.json").forEach(function (file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex.code !== "ENOENT") {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create dist/ if necessary
|
||||
try {
|
||||
fs.mkdirSync("./dist");
|
||||
}
|
||||
catch (ex) {
|
||||
if (ex.code !== "EEXIST") {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all files except package.json
|
||||
Object.keys(files).forEach(function (outputFilename) {
|
||||
var inputFilename = files[outputFilename];
|
||||
var contents = fs.readFileSync(inputFilename);
|
||||
fs.writeFileSync(outputFilename, contents);
|
||||
});
|
||||
|
||||
// Copy package.json
|
||||
var packageJson = fs.readFileSync("./package.json");
|
||||
packageJson = JSON.parse(packageJson);
|
||||
|
||||
packageJson.devDependencies = undefined;
|
||||
packageJson.private = undefined;
|
||||
packageJson.scripts = undefined;
|
||||
packageJson.main = "libjass.js";
|
||||
|
||||
packageJson = JSON.stringify(packageJson, null, "\t");
|
||||
fs.writeFileSync("./dist/package.json", packageJson, { encoding: "utf8" });
|
||||
});
|
||||
|
||||
gulp.task("build-gulplib", function (callback) {
|
||||
if (fs.existsSync("./gulplib/typescript/index.js") && fs.existsSync("./gulplib/doc.js")) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
npm.load(function () {
|
||||
npm.commands["run-script"](["build-gulplib"], callback);
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Transform } from "stream";
|
||||
|
||||
export function makeTransform<T>(transform: (chunk: T, encoding?: string, callback?: (error?: Error) => void) => void, flush?: (callback?: (error?: Error) => void) => void): Transform<T> {
|
||||
return new GulpTransformer<T>(transform, flush);
|
||||
}
|
||||
|
||||
class GulpTransformer<T> extends Transform<T> {
|
||||
constructor(transform?: (chunk: T, encoding?: string, callback?: (error?: Error) => void) => void, flush?: (callback?: (error?: Error) => void) => void) {
|
||||
super({ objectMode: true });
|
||||
|
||||
transform = transform || ((chunk, encoding, callback) => callback());
|
||||
|
||||
if (transform.length < 3) {
|
||||
this._transform = <any>((chunk: T, encoding: string, callback: (error?: Error) => void) => {
|
||||
try {
|
||||
transform.call(this, chunk, encoding);
|
||||
callback();
|
||||
}
|
||||
catch (ex) {
|
||||
callback(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._transform = transform.bind(this);
|
||||
}
|
||||
|
||||
flush = flush || (callback => callback());
|
||||
|
||||
if (flush.length < 1) {
|
||||
this._flush = ((callback: (error?: Error) => void) => {
|
||||
try {
|
||||
flush.call(this);
|
||||
callback();
|
||||
}
|
||||
catch (ex) {
|
||||
callback(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._flush = flush.bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
/**
|
||||
* libjass
|
||||
*
|
||||
* https://github.com/Arnavion/libjass
|
||||
*
|
||||
* Copyright 2013 Arnav Singh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Transform } from "stream";
|
||||
import * as ts from "typescript";
|
||||
import Vinyl = require("vinyl");
|
||||
|
||||
import * as AST from "./ast";
|
||||
import { makeTransform } from "../helpers";
|
||||
import { walk } from "./walker";
|
||||
|
||||
export interface GulpCompilerHost extends ts.CompilerHost {
|
||||
setOutputStream(outputStream: Transform<Vinyl>): void;
|
||||
|
||||
setOutputPathsRelativeTo(path: string): void;
|
||||
}
|
||||
|
||||
export class Compiler {
|
||||
private _projectRoot: string = null;
|
||||
private _program: ts.Program = null;
|
||||
|
||||
constructor(private _host: GulpCompilerHost = CompilerHost()) { }
|
||||
|
||||
compile(projectConfigFile: Vinyl) {
|
||||
this._projectRoot = path.dirname(projectConfigFile.path);
|
||||
|
||||
var projectConfig = parseConfigFile(JSON.parse(projectConfigFile.contents.toString()), this._projectRoot);
|
||||
|
||||
this._host.setOutputPathsRelativeTo(this._projectRoot);
|
||||
|
||||
this._program = ts.createProgram(projectConfig.fileNames, projectConfig.options, this._host);
|
||||
|
||||
var 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();
|
||||
if (globalDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(globalDiagnostics);
|
||||
throw new Error("There were one or more global diagnostics.");
|
||||
}
|
||||
|
||||
var semanticDiagnostics = this._program.getSemanticDiagnostics();
|
||||
if (semanticDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(semanticDiagnostics);
|
||||
throw new Error("There were one or more semantic diagnostics.");
|
||||
}
|
||||
};
|
||||
|
||||
writeFiles(outputStream: Transform<Vinyl>) {
|
||||
this._host.setOutputStream(outputStream);
|
||||
|
||||
var emitDiagnostics = this._program.emit().diagnostics;
|
||||
if (emitDiagnostics.length > 0) {
|
||||
this._reportDiagnostics(emitDiagnostics);
|
||||
throw new Error("There were one or more emit diagnostics.");
|
||||
}
|
||||
};
|
||||
|
||||
get projectRoot(): string {
|
||||
return this._projectRoot;
|
||||
}
|
||||
|
||||
get typeChecker(): ts.TypeChecker {
|
||||
return this._program.getTypeChecker();
|
||||
}
|
||||
|
||||
get sourceFiles(): ts.SourceFile[] {
|
||||
return this._program.getSourceFiles();
|
||||
}
|
||||
|
||||
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
for (let diagnostic of diagnostics) {
|
||||
var message = "";
|
||||
|
||||
if (diagnostic.file) {
|
||||
var location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
||||
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
|
||||
}
|
||||
|
||||
message +=
|
||||
ts.DiagnosticCategory[diagnostic.category].toLowerCase() +
|
||||
` TS${ diagnostic.code }: ` +
|
||||
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const typeScriptModulePath = path.resolve("./node_modules/typescript/bin");
|
||||
|
||||
function CompilerHost(): GulpCompilerHost {
|
||||
var _outputStream: Transform<Vinyl> = null;
|
||||
var _outputPathsRelativeTo: string = null;
|
||||
|
||||
return {
|
||||
setOutputStream(outputStream: Transform<Vinyl>): void {
|
||||
_outputStream = outputStream;
|
||||
},
|
||||
|
||||
setOutputPathsRelativeTo(path: string): void {
|
||||
_outputPathsRelativeTo = path;
|
||||
},
|
||||
|
||||
// ts.CompilerHost members
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
|
||||
try {
|
||||
var text = fs.readFileSync(fileName, { encoding: "utf8" });
|
||||
|
||||
if (path.basename(fileName) === "lib.dom.d.ts") {
|
||||
var startOfES6Extensions = text.indexOf("/// IE11 ECMAScript Extensions");
|
||||
var endOfES6Extensions = text.indexOf("/// ECMAScript Internationalization API", startOfES6Extensions);
|
||||
text = text.substring(0, startOfES6Extensions) + text.substring(endOfES6Extensions);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
if (onError) {
|
||||
onError(ex.message);
|
||||
}
|
||||
}
|
||||
|
||||
return (text !== undefined) ? ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5) : undefined;
|
||||
},
|
||||
|
||||
getDefaultLibFileName: () => path.join(typeScriptModulePath, "lib.dom.d.ts"),
|
||||
|
||||
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError: (message?: string) => void) {
|
||||
_outputStream.push(new Vinyl({
|
||||
base: _outputPathsRelativeTo,
|
||||
path: fileName,
|
||||
contents: new Buffer(data)
|
||||
}));
|
||||
},
|
||||
|
||||
getCurrentDirectory: () => path.resolve("."),
|
||||
|
||||
getCanonicalFileName: (fileName: string) => ts.normalizeSlashes(path.resolve(fileName)),
|
||||
|
||||
useCaseSensitiveFileNames: () => true,
|
||||
|
||||
getNewLine: () => "\n",
|
||||
};
|
||||
}
|
||||
|
||||
function WatchCompilerHost(onChangeCallback: () => void): GulpCompilerHost {
|
||||
var compilerHost = CompilerHost();
|
||||
|
||||
var _onChangeCallback = onChangeCallback;
|
||||
|
||||
var _sourceFiles = Object.create(null);
|
||||
|
||||
var _filesChangedSinceLast: string[] = [];
|
||||
|
||||
var _super_getSourceFile = compilerHost.getSourceFile;
|
||||
compilerHost.getSourceFile = (fileName, languageVersion, onError) => {
|
||||
if (fileName in _sourceFiles) {
|
||||
return _sourceFiles[fileName];
|
||||
}
|
||||
|
||||
var result = _super_getSourceFile(fileName, languageVersion, onError);
|
||||
if (result !== undefined) {
|
||||
_sourceFiles[fileName] = result;
|
||||
}
|
||||
|
||||
watchFile(fileName);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function watchFile(fileName: string): void {
|
||||
function watchFileCallback(currentFile: fs.Stats, previousFile: fs.Stats) {
|
||||
if (currentFile.mtime >= previousFile.mtime) {
|
||||
fileChangedCallback(fileName);
|
||||
}
|
||||
else {
|
||||
fs.unwatchFile(fileName, watchFileCallback);
|
||||
|
||||
fileChangedCallback(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
fs.watchFile(fileName, { interval: 500 }, watchFileCallback);
|
||||
}
|
||||
|
||||
function fileChangedCallback(fileName: string): void {
|
||||
delete _sourceFiles[fileName];
|
||||
|
||||
if (_filesChangedSinceLast.length === 0) {
|
||||
setTimeout(() => {
|
||||
_filesChangedSinceLast = [];
|
||||
|
||||
_onChangeCallback();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
_filesChangedSinceLast.push(fileName);
|
||||
}
|
||||
|
||||
return compilerHost;
|
||||
}
|
||||
|
||||
export function gulp(root: string, rootNamespaceName: string): Transform<Vinyl> {
|
||||
var compiler = new Compiler();
|
||||
|
||||
return makeTransform(function (projectConfigFile: Vinyl): void {
|
||||
var self: Transform<Vinyl> = this;
|
||||
|
||||
console.log("Compiling " + projectConfigFile.path + "...");
|
||||
|
||||
compiler.compile(projectConfigFile);
|
||||
|
||||
var walkResult = walk(compiler, root, rootNamespaceName);
|
||||
addJSDocComments(walkResult.modules);
|
||||
|
||||
compiler.writeFiles(self);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
});
|
||||
}
|
||||
|
||||
export function watch(root: string, rootNamespaceName: string): Transform<Vinyl> {
|
||||
return makeTransform(function (projectConfigFile: Vinyl): void {
|
||||
var self: Transform<Vinyl> = this;
|
||||
|
||||
function compile() {
|
||||
console.log("Compiling " + projectConfigFile.path + "...");
|
||||
|
||||
compiler.compile(projectConfigFile);
|
||||
|
||||
compiler.writeFiles(self);
|
||||
|
||||
console.log("Compile succeeded.");
|
||||
|
||||
self.push(new Vinyl({
|
||||
base: this._outputPathsRelativeTo,
|
||||
path: "END",
|
||||
contents: new Buffer("")
|
||||
}));
|
||||
};
|
||||
|
||||
var compilerHost = 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) {
|
||||
for (let memberName of Object.keys(current.members)) {
|
||||
visitor(current.members[memberName]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var newComments: string[] = [];
|
||||
|
||||
if (current instanceof AST.Class) {
|
||||
newComments.push("@constructor");
|
||||
|
||||
if (current.baseType !== null) {
|
||||
var 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(", ") + ">") :
|
||||
""
|
||||
) +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
if (current.interfaces.length > 0) {
|
||||
current.interfaces.forEach(interfase => {
|
||||
newComments.push(
|
||||
"@implements {" +
|
||||
interfase.fullName + (
|
||||
(interfase instanceof AST.TypeReference && interfase.generics.length > 0) ?
|
||||
(".<" + interfase.generics.map(generic => generic.fullName).join(", ") + ">") :
|
||||
""
|
||||
) +
|
||||
"}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(current.members).forEach(memberName => visitor(current.members[memberName]));
|
||||
}
|
||||
else if (current instanceof AST.Enum) {
|
||||
newComments.push("@enum");
|
||||
}
|
||||
else if (current instanceof AST.Reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((<AST.HasParent><any>current).parent instanceof AST.Namespace) {
|
||||
newComments.push("@memberOf " + (<AST.HasParent><any>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.CanBePrivate><any>current).isPrivate) {
|
||||
newComments.push("@private");
|
||||
}
|
||||
|
||||
if ((<AST.CanBeProtected>current).isProtected) {
|
||||
newComments.push("@protected");
|
||||
}
|
||||
|
||||
if ((<AST.CanBeStatic>current).isStatic) {
|
||||
newComments.push("@static");
|
||||
}
|
||||
|
||||
if (newComments.length > 0) {
|
||||
if (current instanceof AST.Property) {
|
||||
var nodes: ts.Node[] = [];
|
||||
if (current.getter !== null) { nodes.push(current.getter.astNode); }
|
||||
if (current.setter !== null && nodes[0] !== current.setter.astNode) { nodes.push(current.setter.astNode); }
|
||||
for (let node of nodes) {
|
||||
(<any>node)["gulp-typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
else {
|
||||
(<any>(<AST.Class | AST.Interface | AST.Function | AST.Enum>current).astNode)["gulp-typescript-new-comment"] = newComments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let moduleName of Object.keys(modules)) {
|
||||
visitor(modules[moduleName]);
|
||||
}
|
||||
}
|
||||
|
||||
function parseConfigFile(json: { compilerOptions: ts.CompilerOptions }, basePath: string): { options: ts.CompilerOptions, fileNames: string[] } {
|
||||
var options = json.compilerOptions;
|
||||
options.module = (<any>ts).ModuleKind[options.module];
|
||||
options.target = (<any>ts).ScriptTarget[options.target];
|
||||
|
||||
var fileNames: string[] = [];
|
||||
|
||||
function walk(directory: string) {
|
||||
fs.readdirSync(directory).forEach(entry => {
|
||||
var entryPath = path.join(directory, entry);
|
||||
var stat = fs.lstatSync(entryPath);
|
||||
if (stat.isFile()) {
|
||||
if (path.extname(entry) === ".ts") {
|
||||
fileNames.push(entryPath);
|
||||
}
|
||||
}
|
||||
else if (stat.isDirectory()) {
|
||||
walk(entryPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
walk(basePath);
|
||||
|
||||
return { options, fileNames };
|
||||
}
|
||||
|
||||
class FakeSourceFile {
|
||||
public text: string;
|
||||
public lineMap: number[];
|
||||
|
||||
constructor(originalSourceFile: ts.SourceFile) {
|
||||
this.text = originalSourceFile.text;
|
||||
this.lineMap = ts.getLineStarts(originalSourceFile).slice();
|
||||
}
|
||||
|
||||
addComment(originalComment: ts.CommentRange, newComments: string[]): ts.CommentRange {
|
||||
var pos = this.text.length;
|
||||
|
||||
this.text += "/**\n";
|
||||
this.lineMap.push(this.text.length);
|
||||
|
||||
var originalCommentLines = this.text.substring(originalComment.pos, originalComment.end).split("\n");
|
||||
originalCommentLines.shift();
|
||||
|
||||
originalCommentLines = originalCommentLines.map(line => line.replace(/^\s+/, " "));
|
||||
|
||||
if (originalCommentLines.length > 1) {
|
||||
originalCommentLines.splice(originalCommentLines.length - 1, 0, " *");
|
||||
}
|
||||
|
||||
for (let newComment of newComments) {
|
||||
originalCommentLines.splice(originalCommentLines.length - 1, 0, " * " + newComment);
|
||||
}
|
||||
|
||||
for (let newCommentLine of originalCommentLines) {
|
||||
this.text += newCommentLine + "\n";
|
||||
this.lineMap.push(this.text.length);
|
||||
}
|
||||
|
||||
var end = this.text.length;
|
||||
|
||||
return { pos, end, hasTrailingNewLine: originalComment.hasTrailingNewLine, sourceFile: this };
|
||||
}
|
||||
}
|
||||
|
||||
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
|
||||
|
||||
export var oldGetLeadingCommentRangesOfNode: (node: ts.Node, sourceFileOfNode: ts.SourceFile) => ts.CommentRange[] = ts.getLeadingCommentRangesOfNode.bind(ts);
|
||||
ts.getLeadingCommentRangesOfNode = (node: ts.Node, sourceFileOfNode: ts.SourceFile) => {
|
||||
sourceFileOfNode = sourceFileOfNode || ts.getSourceFileOfNode(node);
|
||||
|
||||
var originalComments = oldGetLeadingCommentRangesOfNode(node, sourceFileOfNode);
|
||||
|
||||
if (originalComments !== undefined && (<any>node)["gulp-typescript-new-comment"] !== undefined) {
|
||||
var fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
|
||||
if (fakeSourceFile === undefined) {
|
||||
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
|
||||
}
|
||||
|
||||
originalComments[originalComments.length - 1] = fakeSourceFile.addComment(originalComments[originalComments.length - 1], (<any>node)["gulp-typescript-new-comment"]);
|
||||
}
|
||||
|
||||
return originalComments;
|
||||
};
|
||||
|
||||
var oldWriteCommentRange = ts.writeCommentRange.bind(ts);
|
||||
ts.writeCommentRange = (currentSourceFile: ts.SourceFile, writer: ts.EmitTextWriter, comment: ts.CommentRange, newLine: string) => {
|
||||
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
|
||||
currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
|
||||
}
|
||||
|
||||
return oldWriteCommentRange(currentSourceFile, writer, comment, newLine);
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
// Type definitions for Node.js v0.12.0
|
||||
// Project: http://nodejs.org/
|
||||
// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/borisyankov/DefinitelyTyped>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface Buffer { }
|
||||
declare var Buffer: {
|
||||
new (str: string): Buffer;
|
||||
prototype: Buffer;
|
||||
concat(list: Buffer[]): Buffer;
|
||||
};
|
||||
|
||||
declare module "fs" {
|
||||
export function lstatSync(path: string): Stats;
|
||||
export function readdirSync(path: string): string[];
|
||||
export function readFileSync(filename: string, options: { encoding: string }): string;
|
||||
export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void;
|
||||
export function watchFile(filename: string, options: { interval?: number }, listener: (curr: Stats, prev: Stats) => void): void;
|
||||
|
||||
interface Stats {
|
||||
isDirectory(): boolean;
|
||||
isFile(): boolean;
|
||||
mtime: Date;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "path" {
|
||||
export function basename(p: string, ext?: string): string;
|
||||
export function dirname(p: string): string;
|
||||
export function extname(p: string): string;
|
||||
export function join(...paths: string[]): string;
|
||||
export function relative(from: string, to: string): string;
|
||||
export function resolve(...pathSegments: string[]): string;
|
||||
}
|
||||
|
||||
declare module "stream" {
|
||||
export class Transform<T> {
|
||||
constructor(opts?: { objectMode?: boolean; });
|
||||
|
||||
_transform(chunk: T, encoding: string, callback: (error?: Error) => void): void;
|
||||
_flush(callback: (error?: Error) => void): void;
|
||||
push(chunk: T, encoding?: string): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Type definitions for vinyl 0.4.3
|
||||
// Project: https://github.com/wearefractal/vinyl
|
||||
// Definitions by: vvakame <https://github.com/vvakame/>, jedmao <https://github.com/jedmao>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
declare module "vinyl" {
|
||||
class File {
|
||||
constructor(options?: { path: string; contents: Buffer; base?: string; });
|
||||
|
||||
path: string;
|
||||
contents: Buffer;
|
||||
}
|
||||
|
||||
export = File;
|
||||
}
|
||||
@@ -1,577 +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.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var SourceMap = require("uglify-js/node_modules/source-map");
|
||||
var UglifyJS = require("uglify-js");
|
||||
var Vinyl = require("vinyl");
|
||||
|
||||
var makeTransform = require("./helpers.js").makeTransform;
|
||||
|
||||
var Run = (function () {
|
||||
function Run(entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
this._entry = path.resolve(entry).replace(/\\/g, "/");
|
||||
this._outputLibraryName = outputLibraryName;
|
||||
this._unusedVarsToIgnore = unusedVarsToIgnore;
|
||||
|
||||
this._root = UglifyJS.parse(
|
||||
'(function (root, factory) {\n' +
|
||||
' var global = this;\n' +
|
||||
' if (typeof exports === "object" && typeof module === "object") {\n' +
|
||||
' module.exports = factory(global);\n' +
|
||||
' }\n' +
|
||||
' else if (typeof define === "function" && define.amd) {\n' +
|
||||
' define(function() {\n' +
|
||||
' return factory(global);\n' +
|
||||
' });\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].exports;\n' +
|
||||
' }\n' +
|
||||
'\n' +
|
||||
' var module = installedModules[moduleId] = {\n' +
|
||||
' exports: Object.create(null),\n' +
|
||||
' id: moduleId,\n' +
|
||||
' loaded: false,\n' +
|
||||
' };\n' +
|
||||
'\n' +
|
||||
' modules[moduleId](module.exports, require);\n' +
|
||||
' module.loaded = true;\n' +
|
||||
' return module.exports;\n' +
|
||||
' }\n' +
|
||||
'\n' +
|
||||
' return require(0);\n' +
|
||||
' })([\n' +
|
||||
' ]);\n' +
|
||||
'});', {
|
||||
filename: this._outputLibraryName + ".js"
|
||||
});
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
this._licenseHeader = null;
|
||||
|
||||
this._outputModulesArray = this._root.body[0].body.args[1].body[1].value.args[0].elements;
|
||||
|
||||
this._modules = Object.create(null);
|
||||
|
||||
this._rootSourceMap = new UjsSourceMap(this._outputLibraryName + ".js", "");
|
||||
}
|
||||
|
||||
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 filenameForSourceMap = path.relative(path.join(this._entry, ".."), moduleName + ".ts").replace(/\\/g, "/");
|
||||
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
module.root = UglifyJS.parse(file.contents.toString(), {
|
||||
filename: filenameForSourceMap,
|
||||
toplevel: null,
|
||||
});
|
||||
|
||||
if (this._licenseHeader === null) {
|
||||
module.root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (_this._licenseHeader !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.start) {
|
||||
(node.start.comments_before || []).some(function (comment, i) {
|
||||
if (comment.value.indexOf("Copyright") !== -1) {
|
||||
_this._licenseHeader = comment;
|
||||
_this._licenseHeader.value = _this._licenseHeader.value.split("\n").map(function (line) { return line.replace(/^\t/, ""); }).join("\n");
|
||||
node.start.comments_before.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}));
|
||||
this._root.start.comments_before = [this._licenseHeader];
|
||||
this._root.start.nlb = true;
|
||||
}
|
||||
break;
|
||||
case ".map":
|
||||
this._rootSourceMap.addInput(filenameForSourceMap, fs.readFileSync(moduleName + ".ts", { encoding: "utf8" }), file.contents);
|
||||
module.codeBuffer = undefined;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
||||
// 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];
|
||||
statement.definitions[0].value.args[0] = new UglifyJS.AST_Number({ start: stringArg.start, end: stringArg.end, value: _this._modules[importAbsolutePath].id });
|
||||
}
|
||||
else if (statement.definitions[0].name.name === "__extends") {
|
||||
var importAbsolutePath = path.join(_this._entry, "..", "utility", "extends").replace(/\\/g, "/");
|
||||
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__extends').body[0].body;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
|
||||
var haveUnusedVars = false;
|
||||
|
||||
// Remove some things from the AST
|
||||
var nodesToRemove;
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
|
||||
// Fixup anonymous functions to print a space after "function"
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_Lambda && !node.name) {
|
||||
node.name = Object.create(UglifyJS.AST_Node.prototype);
|
||||
node.name.print = function () { };
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Fix alignment of multi-line block comments
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = comment.value.split("\n");
|
||||
if (lines.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
var indent = " "; // 9 spaces
|
||||
for (var i = 0; i < comment.col; i++) {
|
||||
indent += " ";
|
||||
}
|
||||
lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+$/, indent);
|
||||
comment.value = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^\s+/, indent); })).join("\n");
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Repeat because removing some declarations may make others unreferenced
|
||||
for (;;) {
|
||||
// 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()) {
|
||||
if (node instanceof UglifyJS.AST_SymbolFunarg) {
|
||||
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
|
||||
nodesToRemove.push({ node: node, parent: this.parent().argnames });
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolVar) {
|
||||
if (_this._unusedVarsToIgnore.indexOf(node.name) !== -1) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
|
||||
if (this.parent(1).definitions.length === 1) {
|
||||
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
|
||||
}
|
||||
}
|
||||
else {
|
||||
haveUnusedVars = true;
|
||||
}
|
||||
}
|
||||
else if (node instanceof UglifyJS.AST_SymbolDefun) {
|
||||
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (nodesToRemove.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
nodesToRemove.forEach(function (tuple) {
|
||||
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
|
||||
});
|
||||
|
||||
nodesToRemove = [];
|
||||
|
||||
this._root.figure_out_scope({ screw_ie8: true });
|
||||
}
|
||||
|
||||
|
||||
// Rename all function arguments that begin with _ to not have the _.
|
||||
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
|
||||
// so that it matches the JSDoc (and looks nicer).
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_SymbolFunarg &&
|
||||
node.thedef.name[0] === "_" &&
|
||||
node.thedef.name[1] !== "_" &&
|
||||
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
|
||||
) {
|
||||
node.thedef.name = node.thedef.name.slice(1);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var output = {
|
||||
source_map: _this._rootSourceMap.asUjsOutputSourceMap(),
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
this._root.print(stream);
|
||||
|
||||
outputStream.push(new Vinyl({
|
||||
path: _this._outputLibraryName + ".js",
|
||||
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + _this._outputLibraryName + ".js.map")])
|
||||
}));
|
||||
|
||||
outputStream.push(new Vinyl({
|
||||
path: _this._outputLibraryName + ".js.map",
|
||||
contents: _this._rootSourceMap.toBuffer()
|
||||
}));
|
||||
|
||||
// Print unused variables
|
||||
if (haveUnusedVars) {
|
||||
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (node instanceof UglifyJS.AST_SymbolVar && node.unreferenced()) {
|
||||
if (_this._unusedVarsToIgnore.indexOf(node.name) === -1) {
|
||||
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return Run;
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
gulp: function (entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
|
||||
|
||||
return makeTransform(function (file) {
|
||||
run.addFile(file);
|
||||
}, function () {
|
||||
run.build(this);
|
||||
});
|
||||
},
|
||||
|
||||
watch: function (entry, outputLibraryName, unusedVarsToIgnore) {
|
||||
var files = Object.create(null);
|
||||
|
||||
return makeTransform(function (file) {
|
||||
if (file.path !== "END") {
|
||||
files[file.path] = file;
|
||||
}
|
||||
else {
|
||||
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
|
||||
Object.keys(files).forEach(function (filename) {
|
||||
run.addFile(files[filename]);
|
||||
});
|
||||
run.build(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
minify: function () {
|
||||
var codeFile = null;
|
||||
var sourceMapFile = null;
|
||||
|
||||
return makeTransform(function (file) {
|
||||
switch (path.extname(file.path)) {
|
||||
case ".js":
|
||||
codeFile = file;
|
||||
break;
|
||||
case ".map":
|
||||
sourceMapFile = file;
|
||||
break;
|
||||
}
|
||||
|
||||
if (codeFile !== null && sourceMapFile !== null) {
|
||||
UglifyJS.base54.reset();
|
||||
|
||||
|
||||
// Parse
|
||||
var root = null;
|
||||
root = UglifyJS.parse(codeFile.contents.toString(), {
|
||||
filename: path.basename(codeFile.path),
|
||||
toplevel: root
|
||||
});
|
||||
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
|
||||
|
||||
// Warnings
|
||||
root.scope_warnings({
|
||||
func_arguments: false
|
||||
});
|
||||
|
||||
|
||||
// Compress
|
||||
var compressor = UglifyJS.Compressor({
|
||||
warnings: true,
|
||||
screw_ie8: true
|
||||
});
|
||||
root = root.transform(compressor);
|
||||
|
||||
|
||||
// Mangle
|
||||
root.figure_out_scope({ screw_ie8: true });
|
||||
root.compute_char_frequency();
|
||||
root.mangle_names({ screw_ie8: true });
|
||||
|
||||
|
||||
// Mangle private members
|
||||
var occurrences = Object.create(null);
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property[0] === "_" &&
|
||||
node.property[1] !== "_" && // Doesn't start with two leading underscores
|
||||
node.property !== "_classTag" // webworker serializer uses this property by name, so it shouldn't be changed.
|
||||
) {
|
||||
var occurrence = occurrences[node.property];
|
||||
if (occurrence === undefined) {
|
||||
occurrences[node.property] = 1;
|
||||
}
|
||||
else {
|
||||
occurrences[node.property]++;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
var identifiers = Object.keys(occurrences);
|
||||
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
|
||||
|
||||
var generatedIdentifiers = occurrences;
|
||||
|
||||
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
|
||||
var toIdentifier = function (index) {
|
||||
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
|
||||
while (index > 0) {
|
||||
index--;
|
||||
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
|
||||
index = (index / validIdentifierCharacters.length) | 0;
|
||||
}
|
||||
|
||||
return "_" + result;
|
||||
};
|
||||
|
||||
identifiers.forEach(function (identifier, index) {
|
||||
generatedIdentifiers[identifier] = toIdentifier(index);
|
||||
});
|
||||
|
||||
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
|
||||
if (
|
||||
node instanceof UglifyJS.AST_PropAccess &&
|
||||
typeof node.property === "string" &&
|
||||
node.property in generatedIdentifiers
|
||||
) {
|
||||
node.property = generatedIdentifiers[node.property];
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// Output
|
||||
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
|
||||
|
||||
var output = {
|
||||
source_map: UglifyJS.SourceMap({
|
||||
file: path.basename(sourceMapFile.path),
|
||||
orig: sourceMapFile.contents.toString()
|
||||
}),
|
||||
comments: function (node, comment) {
|
||||
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
|
||||
firstLicenseHeaderFound = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
screw_ie8: true
|
||||
};
|
||||
|
||||
var stream = UglifyJS.OutputStream(output);
|
||||
root.print(stream);
|
||||
|
||||
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
|
||||
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
|
||||
|
||||
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
|
||||
this.push(codeFile);
|
||||
|
||||
var inputSourceMapObject = JSON.parse(sourceMapFile.contents.toString());
|
||||
var outputSourceMapObject = output.source_map.get();
|
||||
outputSourceMapObject._sources.toArray().forEach(function (filename, i) {
|
||||
outputSourceMapObject.setSourceContent(filename, inputSourceMapObject.sourcesContent[i]);
|
||||
});
|
||||
|
||||
sourceMapFile.contents = new Buffer(output.source_map.toString());
|
||||
this.push(sourceMapFile);
|
||||
|
||||
codeFile = null;
|
||||
sourceMapFile = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var UjsSourceMap = (function () {
|
||||
function UjsSourceMap(outputFilename, sourceRoot) {
|
||||
this._outputFilename = outputFilename;
|
||||
this._inputs = Object.create(null);
|
||||
|
||||
this._generator = new SourceMap.SourceMapGenerator({
|
||||
file: outputFilename,
|
||||
sourceRoot: sourceRoot,
|
||||
});
|
||||
}
|
||||
|
||||
UjsSourceMap.prototype.addInput = function (codeFilename, code, sourceMapContents) {
|
||||
this._inputs[codeFilename] = new SourceMap.SourceMapConsumer(sourceMapContents.toString());
|
||||
this._generator.setSourceContent(codeFilename, code);
|
||||
};
|
||||
|
||||
UjsSourceMap.prototype.asUjsOutputSourceMap = function () {
|
||||
var _this = this;
|
||||
|
||||
return {
|
||||
add: function (source, gen_line, gen_col, orig_line, orig_col, name) {
|
||||
if (source === _this._outputFilename || source === "?") {
|
||||
return;
|
||||
}
|
||||
var info = _this._inputs[source].originalPositionFor({
|
||||
line: orig_line,
|
||||
column: orig_col
|
||||
});
|
||||
|
||||
if (info.source === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_this._generator.addMapping({
|
||||
generated: { line: gen_line, column: gen_col },
|
||||
original: { line: info.line, column: info.column },
|
||||
source: source,
|
||||
name: info.name || name
|
||||
});
|
||||
},
|
||||
get: function () { return _this._generator; },
|
||||
toString: function () { return _this._generator.toString(); },
|
||||
};
|
||||
};
|
||||
|
||||
UjsSourceMap.prototype.toBuffer = function () {
|
||||
return new Buffer(this._generator.toString());
|
||||
};
|
||||
|
||||
return UjsSourceMap;
|
||||
})();
|
||||
|
||||
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
|
||||
|
||||
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
|
||||
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.name === "module" || this.name === "exports") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalSymbolUnreferenced.call(this);
|
||||
};
|
||||
@@ -23,10 +23,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.libjass-wrapper.libjass-full-screen {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.libjass-subs {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
@@ -34,6 +30,8 @@
|
||||
|
||||
.libjass-subs, .libjass-subs * {
|
||||
pointer-events: none;
|
||||
-webkit-animation-fill-mode: both !important;
|
||||
animation-fill-mode: both !important;
|
||||
}
|
||||
|
||||
.libjass-subs.paused * {
|
||||
@@ -94,8 +92,13 @@
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.libjass-filters {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.libjass-filters * {
|
||||
color-interpolation-filters: sRGB;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libjass",
|
||||
"version": "0.10.0",
|
||||
"version": "0.12.0",
|
||||
"description": "A library to render ASS subtitles on HTML5 video in the browser.",
|
||||
"keywords": ["browser", "html5", "subtitles"],
|
||||
"homepage": "https://github.com/Arnavion/libjass",
|
||||
@@ -16,23 +16,23 @@
|
||||
},
|
||||
"main": "lib/libjass.js",
|
||||
"scripts": {
|
||||
"prepublish": "gulp clean default",
|
||||
"build-gulplib": "tsc ./gulplib/typescript/index.ts ./gulplib/doc.ts ./gulplib/typings.d.ts ./node_modules/typescript/bin/typescript.d.ts ./node_modules/typescript/bin/typescript_internal.d.ts -m commonjs -t es5 -noImplicitAny",
|
||||
"test": "gulp test",
|
||||
"test-lib": "intern-client config=tests/intern reporters=pretty",
|
||||
"test-minified": "intern-client config=tests/intern reporters=pretty minified=true",
|
||||
"prepublish": "node ./build.js clean default",
|
||||
"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",
|
||||
"test-browser": "intern-runner config=tests/intern",
|
||||
"test-doc": "intern-client config=tests/intern-doc reporters=pretty"
|
||||
"test-doc": "intern-client config=tests/intern-doc reporters=Pretty"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "3.x",
|
||||
"intern": "2.x",
|
||||
"npm": "2.x",
|
||||
"pngjs": "0.4.0",
|
||||
"sax": "0.6.x",
|
||||
"typescript": "1.5.0-alpha",
|
||||
"uglify-js": "2.x >=2.4.16",
|
||||
"vinyl": "latest"
|
||||
"async": "1.x >=1.4",
|
||||
"async-build": "0.3.1",
|
||||
"intern": "3.x >=3.2.0",
|
||||
"npm": "3.x",
|
||||
"pngjs": "3.x",
|
||||
"sax": "1.x",
|
||||
"typescript": "next",
|
||||
"uglify-js": "2.x >=2.4.24"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -30,47 +30,110 @@ export { Map } from "./utility/map";
|
||||
import * as promise from "./utility/promise";
|
||||
export { Promise, DeferredPromise } from "./utility/promise";
|
||||
|
||||
import * as webworker from "./webworker/index";
|
||||
import * as webworker from "./webworker";
|
||||
export { webworker };
|
||||
|
||||
import * as parts from "./parts/index";
|
||||
import * as parts from "./parts";
|
||||
export { parts };
|
||||
|
||||
import * as parser from "./parser/index";
|
||||
import * as parser from "./parser";
|
||||
export { parser };
|
||||
|
||||
import * as renderers from "./renderers/index";
|
||||
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";
|
||||
export { ScriptProperties } from "./types/script-properties";
|
||||
export { Style } from "./types/style";
|
||||
|
||||
export { BorderStyle, Format, WrappingStyle } from "./types/misc";
|
||||
|
||||
declare var exports: any;
|
||||
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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,16 +26,16 @@ 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 {
|
||||
var colonPos = line.indexOf(":");
|
||||
export function parseLineIntoProperty(line: string): Property | null {
|
||||
const colonPos = line.indexOf(":");
|
||||
if (colonPos === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = line.substr(0, colonPos);
|
||||
var value = line.substr(colonPos + 1).replace(/^\s+/, "");
|
||||
const name = line.substr(0, colonPos);
|
||||
const value = line.substr(colonPos + 1).replace(/^\s+/, "");
|
||||
|
||||
return { name, value };
|
||||
}
|
||||
@@ -45,21 +45,21 @@ 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 {
|
||||
var property = parseLineIntoProperty(line);
|
||||
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate | null {
|
||||
const property = parseLineIntoProperty(line);
|
||||
if (property === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = property.value.split(",");
|
||||
const value = property.value.split(",");
|
||||
|
||||
if (value.length > formatSpecifier.length) {
|
||||
value[formatSpecifier.length - 1] = value.slice(formatSpecifier.length - 1).join(",");
|
||||
}
|
||||
|
||||
var template = new Map<string, string>();
|
||||
const template = new Map<string, string>();
|
||||
formatSpecifier.forEach((formatKey, index) => {
|
||||
template.set(formatKey, value[index]);
|
||||
});
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { debugMode } from "../settings";
|
||||
|
||||
import { ASS } from "../types/ass";
|
||||
import { Style } from "../types/style";
|
||||
import { Dialogue } from "../types/dialogue";
|
||||
import { Property, TypedTemplate } from "../types/misc";
|
||||
import { Attachment, AttachmentType } from "../types/attachment";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
@@ -30,6 +32,16 @@ import { Promise, DeferredPromise } from "../utility/promise";
|
||||
import { parseLineIntoProperty } from "./misc";
|
||||
import { Stream } from "./streams";
|
||||
|
||||
enum Section {
|
||||
ScriptInfo,
|
||||
Styles,
|
||||
Events,
|
||||
Fonts,
|
||||
Graphics,
|
||||
Other,
|
||||
EOF,
|
||||
}
|
||||
|
||||
/**
|
||||
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream}.
|
||||
*
|
||||
@@ -41,10 +53,14 @@ export class StreamParser {
|
||||
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
|
||||
|
||||
private _shouldSwallowBom: boolean = true;
|
||||
private _currentSectionName: string = null;
|
||||
private _currentSection: Section = Section.ScriptInfo;
|
||||
private _currentAttachment: Attachment | null = null;
|
||||
|
||||
constructor(private _stream: Stream) {
|
||||
this._stream.nextLine().then(line => this._onNextLine(line));
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._minimalDeferred.reject(reason);
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,13 +78,49 @@ export class StreamParser {
|
||||
return this._deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
private get currentSection(): Section {
|
||||
return this._currentSection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
private set currentSection(value: Section) {
|
||||
if (this._currentAttachment !== null) {
|
||||
this._ass.addAttachment(this._currentAttachment);
|
||||
this._currentAttachment = null;
|
||||
}
|
||||
|
||||
if (this._currentSection === Section.ScriptInfo && value !== Section.ScriptInfo) {
|
||||
// Exiting script info section
|
||||
this._minimalDeferred.resolve(this._ass);
|
||||
}
|
||||
|
||||
if (value === Section.EOF) {
|
||||
const scriptProperties = this._ass.properties;
|
||||
if (scriptProperties.resolutionX === undefined || scriptProperties.resolutionY === undefined) {
|
||||
// Malformed script.
|
||||
this._minimalDeferred.reject("Malformed ASS script.");
|
||||
this._deferred.reject("Malformed ASS script.");
|
||||
}
|
||||
else {
|
||||
this._minimalDeferred.resolve(this._ass);
|
||||
this._deferred.resolve(this._ass);
|
||||
}
|
||||
}
|
||||
|
||||
this._currentSection = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
*/
|
||||
private _onNextLine(line: string): void {
|
||||
private _onNextLine(line: string | null): void {
|
||||
if (line === null) {
|
||||
this._minimalDeferred.resolve(this._ass);
|
||||
this._deferred.resolve(this._ass);
|
||||
this.currentSection = Section.EOF;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,25 +134,41 @@ export class StreamParser {
|
||||
|
||||
this._shouldSwallowBom = false;
|
||||
|
||||
if (line === "" || line[0] === ";") {
|
||||
// Ignore empty lines and comments
|
||||
if (line === "") {
|
||||
// Ignore empty lines.
|
||||
}
|
||||
|
||||
else if (line[0] === "[" && line[line.length - 1] === "]") {
|
||||
// Start of new section
|
||||
else if (line[0] === ";" && this._currentAttachment === null) {
|
||||
// Lines starting with ; are comments, unless reading an attachment.
|
||||
}
|
||||
|
||||
if (this._currentSectionName === "Script Info") {
|
||||
// Exiting script info section
|
||||
this._minimalDeferred.resolve(this._ass);
|
||||
else if (line === "[Script Info]") {
|
||||
this.currentSection = Section.ScriptInfo;
|
||||
}
|
||||
else if (line === "[V4+ Styles]" || line === "[V4 Styles]") {
|
||||
this.currentSection = Section.Styles;
|
||||
}
|
||||
else if (line === "[Events]") {
|
||||
this.currentSection = Section.Events;
|
||||
}
|
||||
else if (line === "[Fonts]") {
|
||||
this.currentSection = Section.Fonts;
|
||||
}
|
||||
else if (line === "[Graphics]") {
|
||||
this.currentSection = Section.Graphics;
|
||||
}
|
||||
else {
|
||||
if (this._currentAttachment === null && line[0] === "[" && line[line.length - 1] === "]") {
|
||||
/* This looks like the start of a new section. The section name is unrecognized if it is.
|
||||
* Since there's no current attachment being parsed it's definitely the start of a new section.
|
||||
* If an attachment is being parsed, this might be part of the attachment.
|
||||
*/
|
||||
this.currentSection = Section.Other;
|
||||
}
|
||||
|
||||
this._currentSectionName = line.substring(1, line.length - 1);
|
||||
}
|
||||
|
||||
else {
|
||||
switch (this._currentSectionName) {
|
||||
case "Script Info":
|
||||
var property = parseLineIntoProperty(line);
|
||||
switch (this.currentSection) {
|
||||
case Section.ScriptInfo:
|
||||
const property = parseLineIntoProperty(line);
|
||||
if (property !== null) {
|
||||
switch (property.name) {
|
||||
case "PlayResX":
|
||||
@@ -119,9 +187,9 @@ export class StreamParser {
|
||||
}
|
||||
break;
|
||||
|
||||
case "V4+ Styles":
|
||||
case Section.Styles:
|
||||
if (this._ass.stylesFormatSpecifier === null) {
|
||||
var property = parseLineIntoProperty(line);
|
||||
const property = parseLineIntoProperty(line);
|
||||
if (property !== null && property.name === "Format") {
|
||||
this._ass.stylesFormatSpecifier = property.value.split(",").map(str => str.trim());
|
||||
}
|
||||
@@ -130,13 +198,20 @@ export class StreamParser {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._ass.addStyle(line);
|
||||
try {
|
||||
this._ass.addStyle(line);
|
||||
}
|
||||
catch (ex) {
|
||||
if (debugMode) {
|
||||
console.error(`Could not parse style from line ${ line } - ${ ex.stack || ex }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "Events":
|
||||
case Section.Events:
|
||||
if (this._ass.dialoguesFormatSpecifier === null) {
|
||||
var property = parseLineIntoProperty(line);
|
||||
const property = parseLineIntoProperty(line);
|
||||
if (property !== null && property.name === "Format") {
|
||||
this._ass.dialoguesFormatSpecifier = property.value.split(",").map(str => str.trim());
|
||||
}
|
||||
@@ -145,17 +220,62 @@ export class StreamParser {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._ass.addEvent(line);
|
||||
try {
|
||||
this._ass.addEvent(line);
|
||||
}
|
||||
catch (ex) {
|
||||
if (debugMode) {
|
||||
console.error(`Could not parse event from line ${ line } - ${ ex.stack || ex }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore other sections
|
||||
case Section.Fonts:
|
||||
case Section.Graphics:
|
||||
const startOfNewAttachmentRegex = (this.currentSection === Section.Fonts) ? /^fontname:(.+)/ : /^filename:(.+)/;
|
||||
const startOfNewAttachment = startOfNewAttachmentRegex.exec(line);
|
||||
|
||||
if (startOfNewAttachment !== null) {
|
||||
// Start of new attachment
|
||||
|
||||
if (this._currentAttachment !== null) {
|
||||
this._ass.addAttachment(this._currentAttachment);
|
||||
this._currentAttachment = null;
|
||||
}
|
||||
|
||||
this._currentAttachment = new Attachment(startOfNewAttachment[1].trim(), (this.currentSection === Section.Fonts) ? AttachmentType.Font : AttachmentType.Graphic);
|
||||
}
|
||||
else if (this._currentAttachment !== null) {
|
||||
try {
|
||||
this._currentAttachment.contents += uuencodedToBase64(line);
|
||||
}
|
||||
catch (ex) {
|
||||
if (debugMode) {
|
||||
console.error(`Encountered error while reading font ${ this._currentAttachment.filename }: %o`, ex);
|
||||
}
|
||||
|
||||
this._currentAttachment = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Ignore.
|
||||
}
|
||||
break;
|
||||
|
||||
case Section.Other:
|
||||
// Ignore other sections.
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unhandled state ${ this.currentSection }`);
|
||||
}
|
||||
}
|
||||
|
||||
this._stream.nextLine().then(line => this._onNextLine(line));
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._minimalDeferred.reject(reason);
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,20 +288,24 @@ export class SrtStreamParser {
|
||||
private _ass: ASS = new ASS();
|
||||
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
|
||||
|
||||
private _currentDialogueNumber: string = null;
|
||||
private _currentDialogueStart: string = null;
|
||||
private _currentDialogueEnd: string = null;
|
||||
private _currentDialogueText: string = null;
|
||||
private _shouldSwallowBom: boolean = true;
|
||||
|
||||
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));
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
|
||||
this._ass.properties.resolutionX = 1280;
|
||||
this._ass.properties.resolutionY = 720;
|
||||
this._ass.properties.wrappingStyle = 1;
|
||||
this._ass.properties.scaleBorderAndShadow = true;
|
||||
|
||||
var newStyle = new Style(new Map([["Name", "Default"]]));
|
||||
const newStyle = new Style(new Map([["Name", "Default"], ["FontSize", "36"]]));
|
||||
this._ass.styles.set(newStyle.name, newStyle);
|
||||
}
|
||||
|
||||
@@ -195,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([
|
||||
@@ -214,6 +338,12 @@ export class SrtStreamParser {
|
||||
line = line.substr(0, line.length - 1);
|
||||
}
|
||||
|
||||
if (line.charCodeAt(0) === 0xfeff && this._shouldSwallowBom) {
|
||||
line = line.substr(1);
|
||||
}
|
||||
|
||||
this._shouldSwallowBom = false;
|
||||
|
||||
if (line === "") {
|
||||
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
|
||||
this._ass.dialogues.push(new Dialogue(new Map([
|
||||
@@ -233,7 +363,7 @@ export class SrtStreamParser {
|
||||
}
|
||||
}
|
||||
else if (this._currentDialogueStart === null && this._currentDialogueEnd === null) {
|
||||
var match = /^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/.exec(line);
|
||||
const match = /^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/.exec(line);
|
||||
if (match !== null) {
|
||||
this._currentDialogueStart = match[1].replace(",", ".");
|
||||
this._currentDialogueEnd = match[2].replace(",", ".");
|
||||
@@ -261,6 +391,43 @@ export class SrtStreamParser {
|
||||
}
|
||||
}
|
||||
|
||||
this._stream.nextLine().then(line => this._onNextLine(line));
|
||||
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
|
||||
this._deferred.reject(reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a uuencoded string to a base64 string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function uuencodedToBase64(str: string): string {
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const charCode = str.charCodeAt(i) - 33;
|
||||
|
||||
if (charCode < 0 || charCode > 63) {
|
||||
throw new Error(`Out-of-range character code ${ charCode } at index ${ i } in string ${ str }`);
|
||||
}
|
||||
if (charCode < 26) {
|
||||
result += String.fromCharCode("A".charCodeAt(0) + charCode);
|
||||
}
|
||||
else if (charCode < 52) {
|
||||
result += String.fromCharCode("a".charCodeAt(0) + charCode - 26);
|
||||
}
|
||||
else if (charCode < 62) {
|
||||
result += String.fromCharCode("0".charCodeAt(0) + charCode - 52);
|
||||
}
|
||||
else if (charCode === 62) {
|
||||
result += "+";
|
||||
}
|
||||
else {
|
||||
result += "/";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface ReadableStream {
|
||||
getReader(): ReadableStreamReader;
|
||||
}
|
||||
|
||||
interface ReadableStreamReader {
|
||||
export interface ReadableStreamReader {
|
||||
/**
|
||||
* @return {!Promise.<{ value?: Uint8Array, done: boolean }>}
|
||||
*/
|
||||
@@ -51,7 +51,7 @@ export interface TextDecoderConstructor {
|
||||
prototype: TextDecoder;
|
||||
}
|
||||
|
||||
declare var global: {
|
||||
declare const global: {
|
||||
/**
|
||||
* @type {!TextDecoderConstructor}
|
||||
*/
|
||||
@@ -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,11 +81,11 @@ 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> {
|
||||
var result: Promise<string> = null;
|
||||
nextLine(): Promise<string | null> {
|
||||
let result: Promise<string | null>;
|
||||
|
||||
if (this._readTill < this._str.length) {
|
||||
var nextNewLinePos = this._str.indexOf("\n", this._readTill);
|
||||
const nextNewLinePos = this._str.indexOf("\n", this._readTill);
|
||||
if (nextNewLinePos !== -1) {
|
||||
result = Promise.resolve(this._str.substring(this._readTill, nextNewLinePos));
|
||||
this._readTill = nextNewLinePos + 1;
|
||||
@@ -96,7 +96,7 @@ export class StringStream implements Stream {
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = Promise.resolve<string>(null);
|
||||
result = Promise.resolve<string | null>(null);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -111,11 +111,13 @@ export class StringStream implements Stream {
|
||||
*/
|
||||
export class XhrStream implements Stream {
|
||||
private _readTill: number = 0;
|
||||
private _pendingDeferred: DeferredPromise<string> = null;
|
||||
private _pendingDeferred: DeferredPromise<string | null> | null = null;
|
||||
private _failedError: ErrorEvent | null = null;
|
||||
|
||||
constructor(private _xhr: XMLHttpRequest) {
|
||||
_xhr.addEventListener("progress", event => this._onXhrProgress(event), false);
|
||||
_xhr.addEventListener("loadend", event => this._onXhrLoadEnd(event), false);
|
||||
_xhr.addEventListener("progress", () => this._onXhrProgress(), false);
|
||||
_xhr.addEventListener("load", () => this._onXhrLoad(), false);
|
||||
_xhr.addEventListener("error", event => this._onXhrError(event), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +128,7 @@ export class XhrStream implements Stream {
|
||||
throw new Error("XhrStream only supports one pending unfulfilled read at a time.");
|
||||
}
|
||||
|
||||
var deferred = this._pendingDeferred = new DeferredPromise<string>();
|
||||
const deferred = this._pendingDeferred = new DeferredPromise<string>();
|
||||
|
||||
this._tryResolveNextLine();
|
||||
|
||||
@@ -134,9 +136,27 @@ export class XhrStream implements Stream {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ProgressEvent} event
|
||||
*/
|
||||
private _onXhrProgress(event: ProgressEvent): void {
|
||||
private _onXhrProgress(): void {
|
||||
if (this._pendingDeferred === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._xhr.readyState === XMLHttpRequest.DONE) {
|
||||
/* Suppress resolving next line here. Let the "load" or "error" event handlers do it.
|
||||
*
|
||||
* This is required because a failed XHR fires the progress event with readyState === DONE before it fires the error event.
|
||||
* This would confuse _tryResolveNextLine() into thinking the request succeeded with no data if it was called here.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
this._tryResolveNextLine();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
private _onXhrLoad(): void {
|
||||
if (this._pendingDeferred === null) {
|
||||
return;
|
||||
}
|
||||
@@ -145,9 +165,11 @@ export class XhrStream implements Stream {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ProgressEvent} event
|
||||
* @param {!ErrorEvent} event
|
||||
*/
|
||||
private _onXhrLoadEnd(event: ProgressEvent): void {
|
||||
private _onXhrError(event: ErrorEvent): void {
|
||||
this._failedError = event;
|
||||
|
||||
if (this._pendingDeferred === null) {
|
||||
return;
|
||||
}
|
||||
@@ -158,23 +180,32 @@ export class XhrStream implements Stream {
|
||||
/**
|
||||
*/
|
||||
private _tryResolveNextLine(): void {
|
||||
var response = this._xhr.responseText;
|
||||
if (this._failedError !== null) {
|
||||
this._pendingDeferred!.reject(this._failedError);
|
||||
return;
|
||||
}
|
||||
|
||||
var nextNewLinePos = response.indexOf("\n", this._readTill);
|
||||
const response = this._xhr.responseText;
|
||||
|
||||
const nextNewLinePos = response.indexOf("\n", this._readTill);
|
||||
if (nextNewLinePos !== -1) {
|
||||
this._pendingDeferred.resolve(response.substring(this._readTill, nextNewLinePos));
|
||||
this._pendingDeferred!.resolve(response.substring(this._readTill, nextNewLinePos));
|
||||
this._readTill = nextNewLinePos + 1;
|
||||
this._pendingDeferred = null;
|
||||
}
|
||||
|
||||
else if (this._xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (this._failedError !== null) {
|
||||
this._pendingDeferred!.reject(this._failedError);
|
||||
}
|
||||
|
||||
// No more data. This is the last line.
|
||||
if (this._readTill < response.length) {
|
||||
this._pendingDeferred.resolve(response.substr(this._readTill));
|
||||
else if (this._readTill < response.length) {
|
||||
this._pendingDeferred!.resolve(response.substr(this._readTill));
|
||||
this._readTill = response.length;
|
||||
}
|
||||
else {
|
||||
this._pendingDeferred.resolve(null);
|
||||
this._pendingDeferred!.resolve(null);
|
||||
}
|
||||
|
||||
this._pendingDeferred = null;
|
||||
@@ -192,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 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +238,7 @@ export class BrowserReadableStream implements Stream {
|
||||
throw new Error("BrowserReadableStream only supports one pending unfulfilled read at a time.");
|
||||
}
|
||||
|
||||
var deferred = this._pendingDeferred = new DeferredPromise<string>();
|
||||
const deferred = this._pendingDeferred = new DeferredPromise<string>();
|
||||
|
||||
this._tryResolveNextLine();
|
||||
|
||||
@@ -217,16 +248,16 @@ export class BrowserReadableStream implements Stream {
|
||||
/**
|
||||
*/
|
||||
private _tryResolveNextLine(): void {
|
||||
var nextNewLinePos = this._buffer.indexOf("\n");
|
||||
const nextNewLinePos = this._buffer.indexOf("\n");
|
||||
if (nextNewLinePos !== -1) {
|
||||
this._pendingDeferred.resolve(this._buffer.substr(0, nextNewLinePos));
|
||||
this._pendingDeferred!.resolve(this._buffer.substr(0, nextNewLinePos));
|
||||
this._buffer = this._buffer.substr(nextNewLinePos + 1);
|
||||
this._pendingDeferred = null;
|
||||
}
|
||||
|
||||
else {
|
||||
this._reader.read().then(next => {
|
||||
var { value, done } = next;
|
||||
const { value, done } = next;
|
||||
|
||||
if (!done) {
|
||||
this._buffer += this._decoder.decode(value, { stream: true });
|
||||
@@ -235,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 = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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 { Attachment } from "../types/attachment";
|
||||
|
||||
import { Map } from "../utility/map";
|
||||
import { Set } from "../utility/set";
|
||||
|
||||
type DataReader = { dataView: DataView; position: number; };
|
||||
|
||||
enum DataType {
|
||||
Char,
|
||||
Uint16,
|
||||
Uint32,
|
||||
}
|
||||
|
||||
type StructMemberDefinition = { type: DataType; field: string; };
|
||||
|
||||
const fieldDecorators = new Map<DataType, (proto: any, field: string) => void>();
|
||||
|
||||
@struct
|
||||
class OffsetTable {
|
||||
/** @type {number} */ @field(DataType.Uint16) majorVersion: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) minorVersion: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) numTables: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) searchRange: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) entrySelector: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) rangeShift: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): OffsetTable} */
|
||||
static read: (reader: DataReader) => OffsetTable;
|
||||
}
|
||||
|
||||
@struct
|
||||
class TableRecord {
|
||||
/** @type {string} */ @field(DataType.Char) c1: string;
|
||||
/** @type {string} */ @field(DataType.Char) c2: string;
|
||||
/** @type {string} */ @field(DataType.Char) c3: string;
|
||||
/** @type {string} */ @field(DataType.Char) c4: string;
|
||||
/** @type {number} */ @field(DataType.Uint32) checksum: number;
|
||||
/** @type {number} */ @field(DataType.Uint32) offset: number;
|
||||
/** @type {number} */ @field(DataType.Uint32) length: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): TableRecord} */
|
||||
static read: (reader: DataReader) => TableRecord;
|
||||
}
|
||||
|
||||
@struct
|
||||
class NameTableHeader {
|
||||
/** @type {number} */ @field(DataType.Uint16) formatSelector: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) count: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) stringOffset: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameTableHeader} */
|
||||
static read: (reader: DataReader) => NameTableHeader;
|
||||
}
|
||||
|
||||
@struct
|
||||
class NameRecord {
|
||||
/** @type {number} */ @field(DataType.Uint16) platformId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) encodingId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) languageId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) nameId: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) length: number;
|
||||
/** @type {number} */ @field(DataType.Uint16) offset: number;
|
||||
|
||||
/** @type {function(!{ dataView: DataView, position: number }): NameRecord} */
|
||||
static read: (reader: DataReader) => NameRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the font names from the given font attachment.
|
||||
*
|
||||
* @param {!libjass.Attachment} attachment
|
||||
* @return {!libjass.Set.<string>}
|
||||
*/
|
||||
export function getTtfNames(attachment: Attachment): Set<string> {
|
||||
const decoded = atob(attachment.contents);
|
||||
|
||||
const bytes = new Uint8Array(new ArrayBuffer(decoded.length));
|
||||
|
||||
for (let i = 0; i < decoded.length; i++) {
|
||||
bytes[i] = decoded.charCodeAt(i);
|
||||
}
|
||||
|
||||
const reader = { dataView: new DataView(bytes.buffer), position: 0 };
|
||||
|
||||
const offsetTable = OffsetTable.read(reader);
|
||||
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") {
|
||||
nameTableRecord = tableRecord;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nameTableRecord === null) {
|
||||
throw new Error('Could not find "name" table record.');
|
||||
}
|
||||
|
||||
reader.position = nameTableRecord.offset;
|
||||
const nameTableHeader = NameTableHeader.read(reader);
|
||||
|
||||
const result = new Set<string>();
|
||||
for (let i = 0; i < nameTableHeader.count; i++) {
|
||||
const nameRecord = NameRecord.read(reader);
|
||||
|
||||
switch (nameRecord.nameId) {
|
||||
case 1:
|
||||
case 4:
|
||||
case 6:
|
||||
const recordOffset = nameTableRecord.offset + nameTableHeader.stringOffset + nameRecord.offset;
|
||||
const nameBytes = bytes.subarray(recordOffset, recordOffset + nameRecord.length);
|
||||
|
||||
switch (nameRecord.platformId) {
|
||||
case 1: {
|
||||
let name = "";
|
||||
|
||||
for (let j = 0; j < nameBytes.length; j++) {
|
||||
name += String.fromCharCode(nameBytes[j]);
|
||||
}
|
||||
|
||||
result.add(name);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
let name = "";
|
||||
|
||||
for (let j = 0; j < nameBytes.length; j += 2) {
|
||||
name += String.fromCharCode((nameBytes[j] << 8) + nameBytes[j + 1]);
|
||||
}
|
||||
|
||||
result.add(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!function(new(): T)} clazz
|
||||
* @return {!function(new(): T)}
|
||||
*/
|
||||
function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new (): T; read(reader: DataReader): T; } {
|
||||
const fields: StructMemberDefinition[] = (clazz as any).__fields;
|
||||
|
||||
clazz.read = (reader: DataReader) => {
|
||||
const result: any = new clazz();
|
||||
|
||||
for (const field of fields) {
|
||||
let value: any;
|
||||
switch (field.type) {
|
||||
case DataType.Char:
|
||||
value = String.fromCharCode(reader.dataView.getInt8(reader.position));
|
||||
reader.position += 1;
|
||||
break;
|
||||
|
||||
case DataType.Uint16:
|
||||
value = reader.dataView.getUint16(reader.position);
|
||||
reader.position += 2;
|
||||
break;
|
||||
|
||||
case DataType.Uint32:
|
||||
value = reader.dataView.getUint32(reader.position);
|
||||
reader.position += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
result[field.field] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} type
|
||||
* @return {function(T, string)}
|
||||
*/
|
||||
function field<T>(type: DataType): (proto: T, field: string) => void {
|
||||
let existingDecorator = fieldDecorators.get(type);
|
||||
if (existingDecorator === undefined) {
|
||||
existingDecorator = (proto: T, field: string) => {
|
||||
const ctor: { __fields?: StructMemberDefinition[] } = proto.constructor;
|
||||
if (ctor.__fields === undefined) {
|
||||
ctor.__fields = [];
|
||||
}
|
||||
|
||||
ctor.__fields.push({ type, field });
|
||||
};
|
||||
|
||||
fieldDecorators.set(type, existingDecorator);
|
||||
}
|
||||
|
||||
return existingDecorator;
|
||||
}
|
||||
@@ -90,6 +90,22 @@ export class Color {
|
||||
toString(): string {
|
||||
return `rgba(${ this._red }, ${ this._green }, ${ this._blue }, ${ this._alpha.toFixed(3) })`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Color by interpolating the current color to the final color by the given progression.
|
||||
*
|
||||
* @param {!libjass.parts.Color} final
|
||||
* @param {number} progression
|
||||
* @return {!libjass.parts.Color}
|
||||
*/
|
||||
interpolate(final: Color, progression: number): Color {
|
||||
return new Color(
|
||||
this._red + progression * (final.red - this._red),
|
||||
this._green + progression * (final.green - this._green),
|
||||
this._blue + progression * (final.blue - this._blue),
|
||||
this._alpha + progression * (final.alpha - this._alpha)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,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;
|
||||
}
|
||||
}
|
||||
@@ -368,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;
|
||||
}
|
||||
}
|
||||
@@ -401,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) { }
|
||||
@@ -409,7 +425,7 @@ export class FontSizePlus {
|
||||
/**
|
||||
* The value of this font size increase tag.
|
||||
*
|
||||
* @type {?number}
|
||||
* @type {number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
@@ -419,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) { }
|
||||
@@ -427,7 +443,7 @@ export class FontSizeMinus {
|
||||
/**
|
||||
* The value of this font size decrease tag.
|
||||
*
|
||||
* @type {?number}
|
||||
* @type {number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
@@ -440,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;
|
||||
}
|
||||
}
|
||||
@@ -458,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;
|
||||
}
|
||||
}
|
||||
@@ -555,7 +571,7 @@ export class SkewX {
|
||||
*
|
||||
* @type {?number}
|
||||
*/
|
||||
get value() {
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
@@ -751,7 +767,7 @@ export class Alignment {
|
||||
/**
|
||||
* The value of this alignment tag.
|
||||
*
|
||||
* @type {?number}
|
||||
* @type {number}
|
||||
*/
|
||||
get value(): number {
|
||||
return this._value;
|
||||
@@ -836,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;
|
||||
}
|
||||
}
|
||||
@@ -883,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.
|
||||
@@ -928,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;
|
||||
}
|
||||
}
|
||||
@@ -1084,38 +1100,38 @@ 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}
|
||||
* @type {?number}
|
||||
*/
|
||||
get start(): number {
|
||||
get start(): number | null {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ending time of this transform tag.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get end(): number {
|
||||
get end(): number | null {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
/**
|
||||
* The acceleration of this transform tag.
|
||||
*
|
||||
* @type {number}
|
||||
* @type {?number}
|
||||
*/
|
||||
get accel(): number {
|
||||
get accel(): number | null {
|
||||
return this._accel;
|
||||
}
|
||||
|
||||
@@ -1279,37 +1295,37 @@ export class DrawingInstructions {
|
||||
}
|
||||
}
|
||||
|
||||
var addToString = function (ctor: Function, ctorName: string) {
|
||||
const addToString = function (ctor: Function, ctorName: string) {
|
||||
if (!ctor.prototype.hasOwnProperty("toString")) {
|
||||
var propertyNames = Object.getOwnPropertyNames(ctor.prototype).filter(property => property !== "constructor");
|
||||
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 var exports: any;
|
||||
declare const exports: any;
|
||||
|
||||
for (let key of Object.keys(exports)) {
|
||||
var value: any = exports[key];
|
||||
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 (let key of Object.keys(drawing)) {
|
||||
var value: any = (<any>drawing)[key];
|
||||
for (const key of Object.keys(drawing)) {
|
||||
const value: any = (drawing as any)[key];
|
||||
if (value instanceof Function) {
|
||||
addToString(value, `Drawing${ key }`);
|
||||
registerClassPrototype(value.prototype);
|
||||
registerClass(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ import { Clock, ClockEvent } from "./base";
|
||||
import { ManualClock } from "./manual";
|
||||
|
||||
/**
|
||||
* An implementation of libjass.renderers.Clock that automatically ticks and generates {@link libjass.renderers.ClockEvent}s according to the state of an external driver.
|
||||
* An implementation of {@link libjass.renderers.Clock} that automatically ticks and generates {@link libjass.renderers.ClockEvent}s according to the state of an external driver.
|
||||
*
|
||||
* For example, if you're using libjass to render subtitles on a canvas with your own video controls, these video controls will function as the driver to this AutoClock.
|
||||
* It would call {@link libjass.renderers.AutoClock.play}, {@link libjass.renderers.AutoClock.play}, etc. when the user pressed the corresponding video controls.
|
||||
* It would call {@link libjass.renderers.AutoClock.play}, {@link libjass.renderers.AutoClock.pause}, etc. when the user pressed the corresponding video controls.
|
||||
*
|
||||
* The difference from ManualClock is that AutoClock does not require the driver to call something like {@link libjass.renderers.ManualClock.tick}. Instead it keeps its
|
||||
* own time with a high-resolution requestAnimationFrame-based timer.
|
||||
@@ -35,18 +35,18 @@ import { ManualClock } from "./manual";
|
||||
* If using libjass with a <video> element, consider using {@link libjass.renderers.VideoClock} that uses the video element as a driver.
|
||||
*
|
||||
* @param {function():number} getCurrentTime A callback that will be invoked to get the current time of the external driver.
|
||||
* @param {number} currentTimeUpdateMaxDelay If two calls to getCurrentTime are more than currentTimeUpdateMaxDelay milliseconds apart, then the external driver will be
|
||||
* @param {number} autoPauseAfter If two calls to getCurrentTime are more than autoPauseAfter milliseconds apart but return the same time, then the external driver will be
|
||||
* considered to have paused.
|
||||
*/
|
||||
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 _currentTimeUpdateMaxDelay: number) { }
|
||||
constructor(private _getCurrentTime: () => number, private _autoPauseAfter: number) { }
|
||||
|
||||
/**
|
||||
* Tells the clock to start generating ticks.
|
||||
@@ -164,7 +164,12 @@ export class AutoClock implements Clock {
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
this._manualClock.toggle();
|
||||
if (this._manualClock.enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +179,12 @@ export class AutoClock implements Clock {
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
return this._manualClock.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,14 +207,14 @@ export class AutoClock implements Clock {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = this._manualClock.currentTime;
|
||||
var currentExternalTime = this._getCurrentTime();
|
||||
const currentTime = this._manualClock.currentTime;
|
||||
const currentExternalTime = this._getCurrentTime();
|
||||
|
||||
if (!this._manualClock.paused) {
|
||||
if (this._lastKnownExternalTime !== null && currentExternalTime === this._lastKnownExternalTime) {
|
||||
if (timeStamp - this._lastKnownExternalTimeObtainedAt > this._currentTimeUpdateMaxDelay) {
|
||||
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);
|
||||
|
||||
@@ -38,7 +38,7 @@ export class EventSource<T> {
|
||||
* @param {!Function} listener The listener
|
||||
*/
|
||||
addEventListener(type: T, listener: Function): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
let listeners = this._eventListeners.get(type);
|
||||
|
||||
if (listeners === undefined) {
|
||||
this._eventListeners.set(type, listeners = []);
|
||||
@@ -54,9 +54,9 @@ export class EventSource<T> {
|
||||
* @param {!Array.<*>} args Arguments for the listeners of the event
|
||||
*/
|
||||
_dispatchEvent(type: T, args: Object[]): void {
|
||||
var listeners = this._eventListeners.get(type);
|
||||
const listeners = this._eventListeners.get(type);
|
||||
if (listeners !== undefined) {
|
||||
for (let listener of listeners) {
|
||||
for (const listener of listeners) {
|
||||
listener.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,12 @@ export class ManualClock implements Clock, EventSource<ClockEvent> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentTime !== currentTime) {
|
||||
this.play();
|
||||
if (this._currentTime === currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.play();
|
||||
|
||||
this._currentTime = currentTime;
|
||||
this._dispatchEvent(ClockEvent.Tick, []);
|
||||
}
|
||||
@@ -188,12 +190,12 @@ export class ManualClock implements Clock, EventSource<ClockEvent> {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
this.pause();
|
||||
|
||||
this.stop();
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,12 @@ export class VideoClock implements Clock {
|
||||
* Toggle the clock.
|
||||
*/
|
||||
toggle(): void {
|
||||
this._autoClock.toggle();
|
||||
if (this._autoClock.enabled) {
|
||||
this.disable();
|
||||
}
|
||||
else {
|
||||
this.enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +104,12 @@ export class VideoClock implements Clock {
|
||||
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
|
||||
*/
|
||||
setEnabled(enabled: boolean): boolean {
|
||||
return this._autoClock.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
return this.enable();
|
||||
}
|
||||
else {
|
||||
return this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,6 @@ import { WebRenderer } from "./web/renderer";
|
||||
|
||||
import { ASS } from "../types/ass";
|
||||
|
||||
///<reference path="./default-references.d.ts" />
|
||||
|
||||
/**
|
||||
* A default renderer implementation.
|
||||
*
|
||||
@@ -34,8 +32,6 @@ import { ASS } from "../types/ass";
|
||||
* @param {libjass.renderers.RendererSettings} settings
|
||||
*/
|
||||
export class DefaultRenderer extends WebRenderer {
|
||||
private _videoIsFullScreen: boolean = false;
|
||||
|
||||
constructor(private _video: HTMLVideoElement, ass: ASS, settings?: RendererSettings) {
|
||||
super(ass, new VideoClock(_video), document.createElement("div"), settings);
|
||||
|
||||
@@ -44,49 +40,38 @@ export class DefaultRenderer extends WebRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Resize the subtitles to the dimensions of the video element.
|
||||
*
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* This method accounts for letterboxing if the video element's size is not the same ratio as the video resolution.
|
||||
*/
|
||||
resizeVideo(width: number, height: number): void {
|
||||
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize(width, height)` instead.");
|
||||
this.resize(width, height);
|
||||
}
|
||||
resize(): void {
|
||||
// Handle letterboxing around the video. If the width or height are greater than the video can be, then consider that dead space.
|
||||
|
||||
protected _ready(): void {
|
||||
document.addEventListener("mozfullscreenchange", event => this._onFullScreenChange(document.mozFullScreenElement), false);
|
||||
document.addEventListener("webkitfullscreenchange", event => this._onFullScreenChange(document.webkitFullscreenElement), false);
|
||||
document.addEventListener("fullscreenchange", event => this._onFullScreenChange(document.fullscreenElement), false);
|
||||
const videoWidth = this._video.videoWidth;
|
||||
const videoHeight = this._video.videoHeight;
|
||||
const videoOffsetWidth = this._video.offsetWidth;
|
||||
const videoOffsetHeight = this._video.offsetHeight;
|
||||
|
||||
this.resize(this._video.offsetWidth, this._video.offsetHeight);
|
||||
const ratio = Math.min(videoOffsetWidth / videoWidth, videoOffsetHeight / videoHeight);
|
||||
const subsWrapperWidth = videoWidth * ratio;
|
||||
const subsWrapperHeight = videoHeight * ratio;
|
||||
const subsWrapperLeft = (videoOffsetWidth - subsWrapperWidth) / 2;
|
||||
const subsWrapperTop = (videoOffsetHeight - subsWrapperHeight) / 2;
|
||||
|
||||
super._ready();
|
||||
super.resize(subsWrapperWidth, subsWrapperHeight, subsWrapperLeft, subsWrapperTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Element} fullScreenElement
|
||||
* @deprecated
|
||||
*/
|
||||
private _onFullScreenChange(fullScreenElement: Element): void {
|
||||
if (fullScreenElement === undefined) {
|
||||
fullScreenElement = document.msFullscreenElement;
|
||||
}
|
||||
resizeVideo(): void {
|
||||
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize()` instead.");
|
||||
this.resize();
|
||||
}
|
||||
|
||||
if (fullScreenElement === this._video) {
|
||||
this.libjassSubsWrapper.classList.add("libjass-full-screen");
|
||||
protected _ready(): void {
|
||||
this.resize();
|
||||
|
||||
this.resize(screen.width, screen.height);
|
||||
|
||||
this._videoIsFullScreen = true;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
else if (fullScreenElement === null && this._videoIsFullScreen) {
|
||||
this.libjassSubsWrapper.classList.remove("libjass-full-screen");
|
||||
|
||||
this._videoIsFullScreen = false;
|
||||
|
||||
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
|
||||
}
|
||||
super._ready();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -152,21 +152,28 @@ export class NullRenderer {
|
||||
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
|
||||
*/
|
||||
protected _onClockTick(): void {
|
||||
var currentTime = this._clock.currentTime;
|
||||
const currentTime = this._clock.currentTime;
|
||||
|
||||
if (verboseMode) {
|
||||
console.log(`NullRenderer._onClockTick: currentTime = ${ currentTime }`);
|
||||
}
|
||||
|
||||
for (let dialogue of this._ass.dialogues) {
|
||||
if (dialogue.end > currentTime) {
|
||||
if (dialogue.start <= currentTime) {
|
||||
// This dialogue is visible right now. Draw it.
|
||||
this.draw(dialogue);
|
||||
for (const dialogue of this._ass.dialogues) {
|
||||
try {
|
||||
if (dialogue.end > currentTime) {
|
||||
if (dialogue.start <= currentTime) {
|
||||
// This dialogue is visible right now. Draw it.
|
||||
this.draw(dialogue);
|
||||
}
|
||||
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
|
||||
// This dialogue will be visible soon. Pre-render it.
|
||||
this.preRender(dialogue);
|
||||
}
|
||||
}
|
||||
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,19 +18,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface Document {
|
||||
interface Array<T> {
|
||||
/**
|
||||
* @type {Element}
|
||||
* 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.
|
||||
*/
|
||||
fullscreenElement: Element;
|
||||
|
||||
/**
|
||||
* @type {Element}
|
||||
*/
|
||||
mozFullScreenElement: Element;
|
||||
|
||||
/**
|
||||
* @type {Element}
|
||||
*/
|
||||
webkitFullscreenElement: Element;
|
||||
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { debugMode } from "../settings";
|
||||
import { Map } from "../utility/map";
|
||||
|
||||
/**
|
||||
@@ -27,12 +28,24 @@ export class RendererSettings {
|
||||
/**
|
||||
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
|
||||
*
|
||||
* The key of each entry of the map is the font name used in the ASS script. There are three choices for the value:
|
||||
*
|
||||
* - A single string that you would use for the src attribute of a @font-face rule. Eg: `'url("/fonts.foo.ttf"), url("/fonts/foo-fallback.ttf"), local("Arial.ttf")'`
|
||||
*
|
||||
* - An array of the individual sources that you would use for the src attribute of a @font-face rule. Eg: `['url("/fonts.foo.ttf")', 'url("/fonts/foo-fallback.ttf")', 'local("Arial")']`
|
||||
*
|
||||
* - An array of URLs. Eg: `["/fonts.foo.ttf", "/fonts/foo-fallback.ttf"]`
|
||||
*
|
||||
* Only the first and second forms allow you to use local fonts. The third form only allows you to use remote fonts.
|
||||
*
|
||||
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.RendererSettings.makeFontMapFromStyleElement}
|
||||
* convenience method to create a font map.
|
||||
*
|
||||
* @type {!Map.<string, !Array.<string>>}
|
||||
* Defaults to null.
|
||||
*
|
||||
* @type {Map.<string, (string|!Array.<string>)>}
|
||||
*/
|
||||
fontMap: Map<string, string[]>;
|
||||
fontMap: Map<string, string | string[]> | null;
|
||||
|
||||
/**
|
||||
* Subtitles will be pre-rendered for this amount of time (seconds).
|
||||
@@ -63,6 +76,28 @@ export class RendererSettings {
|
||||
*/
|
||||
enableSvg: boolean;
|
||||
|
||||
/**
|
||||
* Comma-separated list of fonts to be used when font specified in ASS Styles not loaded.
|
||||
*
|
||||
* The value should be a valid CSS font-family property (i.e. comma-separated and individual names in quotes if necessary). Use empty string to disable fallback.
|
||||
*
|
||||
* Defaults to 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"'.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
fallbackFonts: string;
|
||||
|
||||
/**
|
||||
* If true, attached TTF fonts in the ASS script will be used. The font is loaded as a data: URI. Requires ES6 typed arrays (ArrayBuffer, DataView, Uint8Array, etc).
|
||||
*
|
||||
* The font is naively parsed to extract the strings that will be used as the font family. Do not use this option with untrusted fonts or scripts.
|
||||
*
|
||||
* Defaults to false.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
useAttachedFonts: boolean;
|
||||
|
||||
/**
|
||||
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules. There should be one @font-face rule for each font name, mapping to a font file URL.
|
||||
*
|
||||
@@ -70,35 +105,33 @@ export class RendererSettings {
|
||||
*
|
||||
* @font-face {
|
||||
* font-family: "Helvetica";
|
||||
* src: url("/fonts/helvetica.ttf");
|
||||
* 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, !Array.<string>>}
|
||||
* @return {!Map.<string, string>}
|
||||
*/
|
||||
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string[]> {
|
||||
var fontMap = new Map<string, string[]>();
|
||||
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string> {
|
||||
const fontMap = new Map<string, string>();
|
||||
|
||||
var styleSheet = <CSSStyleSheet>linkStyle.sheet;
|
||||
var rules: CSSFontFaceRule[] = Array.prototype.filter.call(styleSheet.cssRules, (rule: CSSRule) => rule.type === CSSRule.FONT_FACE_RULE);
|
||||
for (let rule of rules) {
|
||||
var src = rule.style.getPropertyValue("src");
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map(line => line.match(/src: ([^;]+);/))
|
||||
.filter(matches => matches !== null)
|
||||
.map(matches => matches[1])[0];
|
||||
}
|
||||
const styleSheet = linkStyle.sheet as CSSStyleSheet;
|
||||
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||
const rule = styleSheet.cssRules[i];
|
||||
|
||||
var urls = src.split(/,\s*/).map(url => RendererSettings._stripQuotes(url.match(/^url\((.+)\)$/)[1]));
|
||||
if (urls.length > 0) {
|
||||
var name = RendererSettings._stripQuotes(rule.style.getPropertyValue("font-family"));
|
||||
var existingList = fontMap.get(name);
|
||||
if (existingList === undefined) {
|
||||
existingList = [];
|
||||
fontMap.set(name, existingList);
|
||||
if (isFontFaceRule(rule)) {
|
||||
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)![1];
|
||||
|
||||
let src = rule.style.getPropertyValue("src");
|
||||
if (!src) {
|
||||
src = rule.cssText.split("\n")
|
||||
.map(line => line.match(/src:\s*([^;]+?)\s*;/))
|
||||
.filter((matches): matches is RegExpMatchArray => matches !== null)
|
||||
.map(matches => matches[1])[0];
|
||||
}
|
||||
existingList.unshift.apply(existingList, urls);
|
||||
|
||||
fontMap.set(name, src);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,21 +149,91 @@ export class RendererSettings {
|
||||
object = {};
|
||||
}
|
||||
|
||||
var { fontMap = null, preRenderTime = 5, preciseOutlines = false, enableSvg = true } = <RendererSettings>object;
|
||||
var result = new RendererSettings();
|
||||
const {
|
||||
fontMap = null,
|
||||
preRenderTime = 5,
|
||||
preciseOutlines = false,
|
||||
enableSvg = testSupportsSvg(),
|
||||
fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"',
|
||||
useAttachedFonts = false,
|
||||
} = object as RendererSettings;
|
||||
|
||||
const result = new RendererSettings();
|
||||
result.fontMap = fontMap;
|
||||
result.preRenderTime = preRenderTime;
|
||||
result.preciseOutlines = preciseOutlines;
|
||||
result.enableSvg = enableSvg;
|
||||
result.fallbackFonts = fallbackFonts;
|
||||
result.useAttachedFonts = useAttachedFonts;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
private static _stripQuotes(str: string): string {
|
||||
return str.match(/^["']?(.*?)["']?$/)[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!CSSRule} rule
|
||||
* @return {boolean}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { Map } from "../../utility/map";
|
||||
* The collection can then be converted to a CSS3 representation.
|
||||
*
|
||||
* @param {!libjass.renderers.NullRenderer} renderer The renderer that this collection is associated with
|
||||
* @param {!HTMLStyleElement} style A <style> element to insert the animation rules into
|
||||
*/
|
||||
export class AnimationCollection {
|
||||
private static _nextId: number = 0;
|
||||
@@ -36,25 +37,15 @@ export class AnimationCollection {
|
||||
private _id: string;
|
||||
private _rate: number;
|
||||
|
||||
private _cssText: string = "";
|
||||
private _animationStyle: string = "";
|
||||
private _animationDelays: Map<string, number> = new Map<string, number>();
|
||||
private _numAnimations: number = 0;
|
||||
|
||||
constructor(renderer: NullRenderer) {
|
||||
constructor(renderer: NullRenderer, private _style: HTMLStyleElement) {
|
||||
this._id = `${ renderer.id }-${ AnimationCollection._nextId++ }`;
|
||||
this._rate = renderer.clock.rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* This string contains the animation definitions and should be inserted into a <style> element.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
get cssText(): string {
|
||||
return this._cssText;
|
||||
}
|
||||
|
||||
/**
|
||||
* This string should be set as the "animation" CSS property of the target element.
|
||||
*
|
||||
@@ -80,10 +71,10 @@ export class AnimationCollection {
|
||||
* @param {!Array.<!libjass.renderers.Keyframe>} keyframes
|
||||
*/
|
||||
add(timingFunction: string, keyframes: Keyframe[]): void {
|
||||
var start: number = null;
|
||||
var end: number = null;
|
||||
let start: number | null = null;
|
||||
let end: number | null = null;
|
||||
|
||||
for (let keyframe of keyframes) {
|
||||
for (const keyframe of keyframes) {
|
||||
if (start === null) {
|
||||
start = keyframe.time;
|
||||
}
|
||||
@@ -91,9 +82,13 @@ export class AnimationCollection {
|
||||
end = keyframe.time;
|
||||
}
|
||||
|
||||
var ruleCssText = "";
|
||||
if (start === null || end === null) {
|
||||
throw new Error("Atleast one keyframe must be provided.");
|
||||
}
|
||||
|
||||
for (let keyframe of keyframes) {
|
||||
let ruleCssText = "";
|
||||
|
||||
for (const keyframe of keyframes) {
|
||||
ruleCssText +=
|
||||
` ${ (100 * ((end - start === 0) ? 1 : ((keyframe.time - start) / (end - start)))).toFixed(3) }% {
|
||||
`;
|
||||
@@ -109,18 +104,17 @@ export class AnimationCollection {
|
||||
`;
|
||||
}
|
||||
|
||||
var animationName = `animation-${ this._id }-${ this._numAnimations++ }`;
|
||||
const animationName = `animation-${ this._id }-${ this._numAnimations++ }`;
|
||||
|
||||
this._cssText +=
|
||||
this._style.appendChild(document.createTextNode(
|
||||
`@-webkit-keyframes ${ animationName } {
|
||||
${ ruleCssText }
|
||||
}
|
||||
}`));
|
||||
|
||||
@keyframes ${ animationName } {
|
||||
this._style.appendChild(document.createTextNode(
|
||||
`@keyframes ${ animationName } {
|
||||
${ ruleCssText }
|
||||
}
|
||||
|
||||
`;
|
||||
}`));
|
||||
|
||||
if (this._animationStyle !== "") {
|
||||
this._animationStyle += ",";
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { domParser } from "./dom-parser";
|
||||
|
||||
import * as parts from "../../parts/index";
|
||||
import * as parts from "../../parts";
|
||||
|
||||
/**
|
||||
* This class represents an ASS drawing - a set of drawing instructions between {\p} tags.
|
||||
@@ -55,40 +53,79 @@ export class DrawingStyles {
|
||||
* @param {!libjass.parts.Color} fillColor
|
||||
* @return {!SVGSVGElement}
|
||||
*/
|
||||
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGElement {
|
||||
var scaleFactor = Math.pow(2, this._scale - 1);
|
||||
var scaleX = this._outputScaleX / scaleFactor;
|
||||
var scaleY = this._outputScaleY / scaleFactor;
|
||||
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGSVGElement {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("version", "1.1");
|
||||
|
||||
var path = "";
|
||||
var bboxWidth = 0;
|
||||
var bboxHeight = 0;
|
||||
if (drawingInstructions.instructions.length === 0) {
|
||||
return svg;
|
||||
}
|
||||
|
||||
for (let instruction of drawingInstructions.instructions) {
|
||||
const scaleFactor = Math.pow(2, this._scale - 1);
|
||||
const scaleX = this._outputScaleX / scaleFactor;
|
||||
const scaleY = this._outputScaleY / scaleFactor;
|
||||
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
let pathString = "";
|
||||
|
||||
let bboxMinX = Infinity;
|
||||
let bboxMaxX = -Infinity;
|
||||
let bboxMinY = Infinity;
|
||||
let bboxMaxY = -Infinity;
|
||||
|
||||
for (const instruction of drawingInstructions.instructions) {
|
||||
if (instruction instanceof parts.drawing.MoveInstruction) {
|
||||
path += ` M ${ 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 += ` L ${ 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 += ` C ${ instruction.x1 } ${ instruction.y1 + this._baselineOffset }, ${ instruction.x2 } ${ instruction.y2 + this._baselineOffset }, ${ instruction.x3 } ${ instruction.y3 + 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);
|
||||
}
|
||||
}
|
||||
|
||||
var result =
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${ (bboxWidth * scaleX).toFixed(3) }px" height="${ (bboxHeight * scaleY).toFixed(3) }px">
|
||||
<g transform="scale(${ scaleX.toFixed(3) } ${ scaleY.toFixed(3) })">
|
||||
<path d="${ path }" fill="${ fillColor.toString() }" />
|
||||
</g>
|
||||
</svg>`;
|
||||
bboxMinX *= scaleX;
|
||||
bboxMaxX *= scaleX;
|
||||
bboxMinY *= scaleY;
|
||||
bboxMaxY *= scaleY;
|
||||
|
||||
return <SVGSVGElement>domParser.parseFromString(result, "image/svg+xml").childNodes[0];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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";
|
||||
import { Promise } from "../../utility/promise";
|
||||
|
||||
/**
|
||||
* @param {string} fontFamily
|
||||
* @param {number} fontSize
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
*/
|
||||
function prepareFontSizeElement(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): void {
|
||||
let fonts = `"${ fontFamily }"`;
|
||||
if (fallbackFonts !== "") {
|
||||
fonts += `, ${ fallbackFonts }`;
|
||||
}
|
||||
|
||||
fontSizeElement.style.fontFamily = fonts;
|
||||
fontSizeElement.style.fontSize = `${ fontSize }px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} fontFamily
|
||||
* @param {number} fontSize
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
* @return {!Promise.<number>}
|
||||
*/
|
||||
function lineHeightForFontSize(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<number> {
|
||||
prepareFontSizeElement(fontFamily, fontSize, fallbackFonts, fontSizeElement);
|
||||
|
||||
return new Promise(resolve => setTimeout(() => resolve(fontSizeElement.offsetHeight), 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} fontFamily
|
||||
* @param {number} fontSize
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
* @return {number}
|
||||
*/
|
||||
function lineHeightForFontSizeSync(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): number {
|
||||
prepareFontSizeElement(fontFamily, fontSize, fallbackFonts, fontSizeElement);
|
||||
|
||||
return fontSizeElement.offsetHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} lowerLineHeight
|
||||
* @param {number} upperLineHeight
|
||||
* @return {[number, number]}
|
||||
*/
|
||||
function fontMetricsFromLineHeights(lowerLineHeight: number, upperLineHeight: number): [number, number] {
|
||||
return [lowerLineHeight, (360 - 180) / (upperLineHeight - lowerLineHeight)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates font metrics for the given font family.
|
||||
*
|
||||
* @param {string} fontFamily
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
* @return {!Promise.<number>}
|
||||
*/
|
||||
export function calculateFontMetrics(fontFamily: string, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<[number, number]> {
|
||||
return lineHeightForFontSize(fontFamily, 180, fallbackFonts, fontSizeElement).then(lowerLineHeight =>
|
||||
lineHeightForFontSize(fontFamily, 360, fallbackFonts, fontSizeElement).then(upperLineHeight =>
|
||||
fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} lineHeight
|
||||
* @param {number} lowerLineHeight
|
||||
* @param {number} factor
|
||||
* @return {number}
|
||||
*/
|
||||
function fontSizeFromMetrics(lineHeight: number, lowerLineHeight: number, factor: number): number {
|
||||
return 180 + (lineHeight - lowerLineHeight) * factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses linear interpolation to calculate the CSS font size that would give the specified line height for the specified font family.
|
||||
*
|
||||
* WARNING: If fontMetricsCache doesn't already contain a cached value for this font family, and it is not a font already installed on the user's device, then this function
|
||||
* may return wrong values. To avoid this, make sure to preload the font using the {@link libjass.renderers.RendererSettings.fontMap} property when constructing the renderer.
|
||||
*
|
||||
* @param {string} fontFamily
|
||||
* @param {number} lineHeight
|
||||
* @param {string} fallbackFonts
|
||||
* @param {!HTMLDivElement} fontSizeElement
|
||||
* @param {!Map.<string, [number, number]>} fontMetricsCache
|
||||
* @return {number}
|
||||
*/
|
||||
export function fontSizeForLineHeight(fontFamily: string, lineHeight: number, fallbackFonts: string, fontSizeElement: HTMLDivElement, fontMetricsCache: Map<string, [number, number]>): number {
|
||||
let existingMetrics = fontMetricsCache.get(fontFamily);
|
||||
if (existingMetrics === undefined) {
|
||||
const lowerLineHeight = lineHeightForFontSizeSync(fontFamily, 180, fallbackFonts, fontSizeElement);
|
||||
const upperLineHeight = lineHeightForFontSizeSync(fontFamily, 360, fallbackFonts, fontSizeElement);
|
||||
fontMetricsCache.set(fontFamily, existingMetrics = fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight));
|
||||
}
|
||||
|
||||
const [lowerLineHeight, factor] = existingMetrics;
|
||||
return fontSizeFromMetrics(lineHeight, lowerLineHeight, factor);
|
||||
}
|
||||
@@ -18,35 +18,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface CSSStyleDeclaration {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitAnimation: string;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitAnimationDelay: string;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitAnimationName: string;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitFilter: string;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitTransform: string;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
webkitTransformOrigin: string;
|
||||
interface Node {
|
||||
cloneNode(deep?: boolean): this;
|
||||
}
|
||||
|
||||
interface SVGFEComponentTransferElement {
|
||||
appendChild(newChild: SVGFEFuncAElement): SVGFEFuncAElement;
|
||||
appendChild(newChild: SVGFEFuncBElement): SVGFEFuncBElement;
|
||||
appendChild(newChild: SVGFEFuncGElement): SVGFEFuncGElement;
|
||||
appendChild(newChild: SVGFEFuncRElement): SVGFEFuncRElement;
|
||||
}
|
||||
|
||||
interface SVGFEMergeElement {
|
||||
appendChild(newChild: SVGFEMergeNodeElement): SVGFEMergeNodeElement;
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -23,14 +23,14 @@
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export var debugMode: boolean = false;
|
||||
export let debugMode: boolean = false;
|
||||
|
||||
/**
|
||||
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of {@link libjass.debugMode}
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export var verboseMode: boolean = false;
|
||||
export let verboseMode: boolean = false;
|
||||
|
||||
/**
|
||||
* Sets the debug mode.
|
||||
|
||||
@@ -1,8 +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"
|
||||
"inlineSources": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,37 +18,42 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Attachment } from "./attachment";
|
||||
import { Dialogue } from "./dialogue";
|
||||
import { Style } from "./style";
|
||||
import { ScriptProperties } from "./script-properties";
|
||||
|
||||
import { Format } from "./misc";
|
||||
|
||||
import { verboseMode } from "../settings";
|
||||
import { debugMode, verboseMode } from "../settings";
|
||||
|
||||
import * as parser from "../parser/index";
|
||||
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";
|
||||
|
||||
declare var global: {
|
||||
fetch?(url: string): Promise<{ body: ReadableStream }>;
|
||||
ReadableStream?: { prototype: ReadableStream; };
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
declare const global: {
|
||||
fetch?(url: string): Promise<{ body: ReadableStream; ok?: boolean; status?: number; }>;
|
||||
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.
|
||||
@@ -77,39 +82,48 @@ export class ASS {
|
||||
return this._dialogues;
|
||||
}
|
||||
|
||||
/**
|
||||
* The attachments of this script.
|
||||
*
|
||||
* @type {!Array.<!libjass.Attachment>}
|
||||
*/
|
||||
get attachments(): Attachment[] {
|
||||
return this._attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -118,6 +132,8 @@ export class ASS {
|
||||
if (arguments.length === 1) {
|
||||
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
|
||||
}
|
||||
|
||||
this._styles.set("Default", new Style(new Map([["Name", "Default"]])));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,21 +142,25 @@ export class ASS {
|
||||
* @param {string} line The line from the script that contains the new style.
|
||||
*/
|
||||
addStyle(line: string): void {
|
||||
var styleLine = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
|
||||
if (this._stylesFormatSpecifier === null) {
|
||||
throw new Error("stylesFormatSpecifier is not set.");
|
||||
}
|
||||
|
||||
const styleLine = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
|
||||
if (styleLine === null || styleLine.type !== "Style") {
|
||||
return;
|
||||
}
|
||||
|
||||
var styleTemplate = styleLine.template;
|
||||
const styleTemplate = styleLine.template;
|
||||
|
||||
if (verboseMode) {
|
||||
var repr = "";
|
||||
let repr = "";
|
||||
styleTemplate.forEach((value, key) => repr += `${ key } = ${ value }, `);
|
||||
console.log(`Read style: ${ repr }`);
|
||||
}
|
||||
|
||||
// Create the dialogue and add it to the dialogues array
|
||||
var style = new Style(styleTemplate);
|
||||
const style = new Style(styleTemplate);
|
||||
this._styles.set(style.name, style);
|
||||
}
|
||||
|
||||
@@ -150,15 +170,19 @@ export class ASS {
|
||||
* @param {string} line The line from the script that contains the new event.
|
||||
*/
|
||||
addEvent(line: string): void {
|
||||
var dialogueLine = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
|
||||
if (this._dialoguesFormatSpecifier === null) {
|
||||
throw new Error("dialoguesFormatSpecifier is not set.");
|
||||
}
|
||||
|
||||
const dialogueLine = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
|
||||
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
|
||||
return;
|
||||
}
|
||||
|
||||
var dialogueTemplate = dialogueLine.template;
|
||||
const dialogueTemplate = dialogueLine.template;
|
||||
|
||||
if (verboseMode) {
|
||||
var repr = "";
|
||||
let repr = "";
|
||||
dialogueTemplate.forEach((value, key) => repr += `${ key } = ${ value }, `);
|
||||
console.log(`Read dialogue: ${ repr }`);
|
||||
}
|
||||
@@ -167,14 +191,46 @@ export class ASS {
|
||||
this.dialogues.push(new Dialogue(dialogueTemplate, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment to this ASS script.
|
||||
*
|
||||
* @param {!libjass.Attachment} attachment
|
||||
*/
|
||||
addAttachment(attachment: Attachment): void {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -182,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 }`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,23 +258,40 @@ 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 (
|
||||
typeof global.fetch === "function" &&
|
||||
typeof global.ReadableStream === "function" && typeof global.ReadableStream.prototype.getReader === "function" &&
|
||||
typeof global.TextDecoder === "function"
|
||||
) {
|
||||
return global.fetch(url).then(response => ASS.fromReadableStream(response.body, "utf-8", type));
|
||||
fetchPromise = global.fetch(url).then(response => {
|
||||
if (response.ok === false || (response.ok === undefined && (response.status < 200 || response.status > 299))) {
|
||||
throw new Error(`HTTP request for ${ url } failed with status code ${ response.status }`);
|
||||
}
|
||||
|
||||
return ASS.fromReadableStream(response.body, "utf-8", type);
|
||||
});
|
||||
}
|
||||
else {
|
||||
fetchPromise = Promise.reject<ASS>(new Error("Not supported."));
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
var result = ASS.fromStream(new parser.XhrStream(xhr), type);
|
||||
xhr.open("GET", url, true);
|
||||
xhr.send();
|
||||
return result;
|
||||
return fetchPromise.catch(reason => {
|
||||
if (debugMode) {
|
||||
console.log("fetch() failed, falling back to XHR: %o", reason);
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
const result = ASS.fromStream(new parser.XhrStream(xhr), type);
|
||||
xhr.open("GET", url, true);
|
||||
xhr.send();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { registerClass as serializable } from "../serialization";
|
||||
|
||||
/**
|
||||
* The type of an attachment.
|
||||
*/
|
||||
export enum AttachmentType {
|
||||
Font,
|
||||
Graphic,
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents an attachment in a {@link libjass.ASS} script.
|
||||
*
|
||||
* @param {string} filename The filename of this attachment.
|
||||
* @param {number} type The type of this attachment.
|
||||
*/
|
||||
@serializable
|
||||
export class Attachment {
|
||||
private _contents: string = "";
|
||||
|
||||
constructor(private _filename: string, private _type: AttachmentType) { }
|
||||
|
||||
/**
|
||||
* The filename of this attachment.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get filename(): string {
|
||||
return this._filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of this attachment.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get type(): AttachmentType {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The contents of this attachment in base64 encoding.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get contents(): string {
|
||||
return this._contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* The contents of this attachment in base64 encoding.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
set contents(value: string) {
|
||||
this._contents = value;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import { valueOrDefault } from "./misc";
|
||||
|
||||
import { parse } from "../parser/parse";
|
||||
|
||||
import * as parts from "../parts/index";
|
||||
import * as parts from "../parts";
|
||||
|
||||
import { registerClass as serializable } from "../serialization";
|
||||
|
||||
import { debugMode } from "../settings";
|
||||
|
||||
@@ -40,8 +42,9 @@ import { Map } from "../utility/map";
|
||||
* @param {string} template["End"] The end time
|
||||
* @param {string} template["Layer"] The layer number
|
||||
* @param {string} template["Text"] The text of this dialogue
|
||||
* @param {ASS} ass The ASS object to which this dialogue belongs
|
||||
* @param {!libjass.ASS} ass The ASS object to which this dialogue belongs
|
||||
*/
|
||||
@serializable
|
||||
export class Dialogue {
|
||||
private static _lastDialogueId = -1;
|
||||
|
||||
@@ -56,28 +59,61 @@ export class Dialogue {
|
||||
private _alignment: number;
|
||||
|
||||
private _rawPartsString: string;
|
||||
private _parts: parts.Part[] = null;
|
||||
private _parts: parts.Part[] | null = null;
|
||||
|
||||
private _sub: HTMLDivElement = null;
|
||||
private _containsTransformTag: boolean = false;
|
||||
|
||||
constructor(template: Map<string, string>, ass: ASS) {
|
||||
{
|
||||
const normalizedTemplate = new Map<string, string>();
|
||||
template.forEach((value, key) => {
|
||||
normalizedTemplate.set(key.toLowerCase(), value);
|
||||
});
|
||||
template = normalizedTemplate;
|
||||
}
|
||||
|
||||
this._id = ++Dialogue._lastDialogueId;
|
||||
|
||||
var style = template.get("Style");
|
||||
this._style = ass.styles.get(style);
|
||||
if (this._style === undefined) {
|
||||
throw new Error(`Unrecognized style ${ style }`);
|
||||
let styleName = template.get("style");
|
||||
if (typeof styleName === "string") {
|
||||
styleName = styleName.replace(/^\*+/, "");
|
||||
if (styleName.match(/^Default$/i) !== null) {
|
||||
styleName = "Default";
|
||||
}
|
||||
}
|
||||
|
||||
this._start = Dialogue._toTime(template.get("Start"));
|
||||
this._end = Dialogue._toTime(template.get("End"));
|
||||
let style = (styleName !== undefined) ? ass.styles.get(styleName) : undefined;
|
||||
if (style === undefined) {
|
||||
if (debugMode) {
|
||||
console.warn(`Unrecognized style ${ styleName }. Falling back to "Default"`);
|
||||
}
|
||||
|
||||
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.");
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
const text = template.get("text");
|
||||
if (typeof text !== "string") {
|
||||
throw new Error(`Dialogue text ${ text } is not a string.`);
|
||||
}
|
||||
this._rawPartsString = text;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,7 +184,20 @@ export class Dialogue {
|
||||
this._parsePartsString();
|
||||
}
|
||||
|
||||
return this._parts;
|
||||
return this._parts!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience getter for whether this dialogue contains a {\t} tag.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get containsTransformTag(): boolean {
|
||||
if (this._parts === null) {
|
||||
this._parsePartsString();
|
||||
}
|
||||
|
||||
return this._containsTransformTag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,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;
|
||||
|
||||
@@ -172,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
|
||||
@@ -181,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,
|
||||
@@ -189,11 +238,13 @@ export class Dialogue {
|
||||
part.tags
|
||||
);
|
||||
}
|
||||
|
||||
this._containsTransformTag = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (debugMode) {
|
||||
var possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && part.value.indexOf("\\") !== -1);
|
||||
const possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && part.value.indexOf("\\") !== -1);
|
||||
if (possiblyIncorrectParses.length > 0) {
|
||||
console.warn(
|
||||
`Possible incorrect parse:
|
||||
@@ -206,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);
|
||||
}
|
||||
|
||||
@@ -84,20 +84,20 @@ 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 {
|
||||
var value = template.get(key);
|
||||
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);
|
||||
}
|
||||
|
||||
try {
|
||||
var numValue = converter(value);
|
||||
const result = converter(value);
|
||||
|
||||
if (validator !== null && !validator(numValue)) {
|
||||
if (validator !== null && !validator(result)) {
|
||||
throw new Error("Validation failed.");
|
||||
}
|
||||
|
||||
return numValue;
|
||||
return result;
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error(`Property ${ key } has invalid value ${ value } - ${ ex.stack }`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,7 +22,9 @@ import { valueOrDefault, BorderStyle } from "./misc";
|
||||
|
||||
import { parse } from "../parser/parse";
|
||||
|
||||
import { Color } from "../parts/index";
|
||||
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;
|
||||
|
||||
@@ -85,41 +88,50 @@ export class Style {
|
||||
private _marginVertical: number;
|
||||
|
||||
constructor(template: Map<string, string>) {
|
||||
this._name = template.get("Name");
|
||||
if (this._name === undefined || this._name === null || this._name.constructor !== String) {
|
||||
throw new Error("Style doesn't have a name.");
|
||||
{
|
||||
const normalizedTemplate = new Map<string, string>();
|
||||
template.forEach((value, key) => {
|
||||
normalizedTemplate.set(key.toLowerCase(), value);
|
||||
});
|
||||
template = normalizedTemplate;
|
||||
}
|
||||
|
||||
this._italic = template.get("Italic") === "-1";
|
||||
this._bold = template.get("Bold") === "-1";
|
||||
this._underline = template.get("Underline") === "-1";
|
||||
this._strikeThrough = template.get("StrikeOut") === "-1";
|
||||
const name = template.get("name");
|
||||
if (typeof name !== "string") {
|
||||
throw new Error(`Style name ${ name } is not a string.`);
|
||||
}
|
||||
this._name = name.replace(/^\*+/, "");
|
||||
|
||||
this._fontName = template.get("Fontname");
|
||||
this._fontSize = valueOrDefault(template, "Fontsize", parseFloat, value => !isNaN(value), "50");
|
||||
this._italic = !!valueOrDefault(template, "italic", parseFloat, value => !isNaN(value), "0");
|
||||
this._bold = !!valueOrDefault(template, "bold", parseFloat, value => !isNaN(value), "0");
|
||||
this._underline = !!valueOrDefault(template, "underline", parseFloat, value => !isNaN(value), "0");
|
||||
this._strikeThrough = !!valueOrDefault(template, "strikeout", parseFloat, value => !isNaN(value), "0");
|
||||
|
||||
this._fontScaleX = valueOrDefault(template, "ScaleX", parseFloat, value => !isNaN(value), "100") / 100;
|
||||
this._fontScaleY = valueOrDefault(template, "ScaleY", parseFloat, value => !isNaN(value), "100") / 100;
|
||||
this._fontName = valueOrDefault(template, "fontname", str => str, value => value.constructor === String, "sans-serif");
|
||||
this._fontSize = valueOrDefault(template, "fontsize", parseFloat, value => !isNaN(value), "18");
|
||||
|
||||
this._letterSpacing = valueOrDefault(template, "Spacing", parseFloat, value => !isNaN(value), "0");
|
||||
this._fontScaleX = valueOrDefault(template, "scalex", parseFloat, value => value >= 0, "100") / 100;
|
||||
this._fontScaleY = valueOrDefault(template, "scaley", parseFloat, value => value >= 0, "100") / 100;
|
||||
|
||||
this._rotationZ = valueOrDefault(template, "Angle", parseFloat, value => !isNaN(value), "0");
|
||||
this._letterSpacing = valueOrDefault(template, "spacing", parseFloat, value => value >= 0, "0");
|
||||
|
||||
this._primaryColor = valueOrDefault(template, "PrimaryColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H0000FFFF");
|
||||
this._secondaryColor = valueOrDefault(template, "SecondaryColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
|
||||
this._outlineColor = valueOrDefault(template, "OutlineColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
|
||||
this._shadowColor = valueOrDefault(template, "BackColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
|
||||
this._rotationZ = valueOrDefault(template, "angle", parseFloat, value => !isNaN(value), "0");
|
||||
|
||||
this._outlineThickness = valueOrDefault(template, "Outline", parseFloat, value => !isNaN(value), "1");
|
||||
this._borderStyle = valueOrDefault(template, "BorderStyle", parseInt, value => !isNaN(value), "1");
|
||||
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._shadowDepth = valueOrDefault(template, "Shadow", parseFloat, value => !isNaN(value), "1");
|
||||
this._outlineThickness = valueOrDefault(template, "outline", parseFloat, value => value >= 0, "2");
|
||||
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (BorderStyle as any)[(BorderStyle as any)[value]] === value, "1");
|
||||
|
||||
this._alignment = valueOrDefault(template, "Alignment", parseInt, value => !isNaN(value), "2");
|
||||
this._shadowDepth = valueOrDefault(template, "shadow", parseFloat, value => value >= 0, "3");
|
||||
|
||||
this._marginLeft = valueOrDefault(template, "MarginL", parseFloat, value => !isNaN(value), "80");
|
||||
this._marginRight = valueOrDefault(template, "MarginR", parseFloat, value => !isNaN(value), "80");
|
||||
this._marginVertical = valueOrDefault(template, "MarginV", parseFloat, value => !isNaN(value), "35");
|
||||
this._alignment = valueOrDefault(template, "alignment", parseInt, value => value >= 1 && value <= 9, "2");
|
||||
|
||||
this._marginLeft = valueOrDefault(template, "marginl", parseFloat, value => !isNaN(value), "20");
|
||||
this._marginRight = valueOrDefault(template, "marginr", parseFloat, value => !isNaN(value), "20");
|
||||
this._marginVertical = valueOrDefault(template, "marginv", parseFloat, value => !isNaN(value), "20");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,48 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface Map<K, V> {
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {?V}
|
||||
*/
|
||||
get(key: K): V | undefined;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value: V): this;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
|
||||
*
|
||||
@@ -25,7 +67,7 @@
|
||||
*
|
||||
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
|
||||
*/
|
||||
class SimpleMap<K, V> {
|
||||
class SimpleMap<K, V> implements Map<K, V> {
|
||||
private _keys: { [key: string]: K };
|
||||
private _values: { [key: string]: V };
|
||||
private _size: number;
|
||||
@@ -41,17 +83,17 @@ class SimpleMap<K, V> {
|
||||
throw new Error("Non-array iterables are not supported by the SimpleMap constructor.");
|
||||
}
|
||||
|
||||
for (let element of iterable) {
|
||||
for (const element of iterable) {
|
||||
this.set(element[0], element[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {V}
|
||||
* @return {?V}
|
||||
*/
|
||||
get(key: K): V {
|
||||
var property = this._keyToProperty(key);
|
||||
get(key: K): V | undefined {
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return undefined;
|
||||
@@ -65,7 +107,7 @@ class SimpleMap<K, V> {
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean {
|
||||
var property = this._keyToProperty(key);
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
@@ -79,8 +121,8 @@ class SimpleMap<K, V> {
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value: V): Map<K, V> {
|
||||
var property = this._keyToProperty(key);
|
||||
set(key: K, value: V): this {
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
|
||||
@@ -101,13 +143,13 @@ class SimpleMap<K, V> {
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean {
|
||||
var property = this._keyToProperty(key);
|
||||
const property = this._keyToProperty(key);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = property in this._keys;
|
||||
const result = property in this._keys;
|
||||
|
||||
if (result) {
|
||||
delete this._keys[property];
|
||||
@@ -130,8 +172,8 @@ 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 {
|
||||
for (let property of Object.keys(this._keys)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -147,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 }`;
|
||||
}
|
||||
@@ -158,63 +200,19 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
declare var global: {
|
||||
Map?: typeof SimpleMap;
|
||||
};
|
||||
|
||||
export interface Map<K, V> {
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {V}
|
||||
*/
|
||||
get(key: K): V;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(key: K): boolean;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @param {V} value
|
||||
* @return {libjass.Map.<K, V>} This map
|
||||
*/
|
||||
set(key: K, value?: V): Map<K, V>;
|
||||
|
||||
/**
|
||||
* @param {K} key
|
||||
* @return {boolean} true if the key was present before being deleted, false otherwise
|
||||
*/
|
||||
delete(key: K): boolean;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
|
||||
*
|
||||
* 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.<*>>=)}
|
||||
@@ -222,9 +220,9 @@ export interface Map<K, V> {
|
||||
export var Map: {
|
||||
new <K, V>(iterable?: [K, V][]): Map<K, V>;
|
||||
prototype: Map<any, any>;
|
||||
} = global.Map;
|
||||
} = global.Map || SimpleMap;
|
||||
|
||||
if (Map === undefined || typeof Map.prototype.forEach !== "function" || (() => {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
* @param {!Array.<*>} mixins
|
||||
*/
|
||||
export function mixin(clazz: any, mixins: any[]): void {
|
||||
for (let mixin of mixins) {
|
||||
for (let name of Object.getOwnPropertyNames(mixin.prototype)) {
|
||||
for (const mixin of mixins) {
|
||||
for (const name of Object.getOwnPropertyNames(mixin.prototype)) {
|
||||
clazz.prototype[name] = mixin.prototype[name];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,131 +18,169 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface Thenable<T> {
|
||||
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
|
||||
then: ThenableThen<T>;
|
||||
}
|
||||
|
||||
export interface ThenableThen<T> {
|
||||
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
|
||||
(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(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!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(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!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>;
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
process.nextTick(callback);
|
||||
};
|
||||
}
|
||||
else if (MutationObserver !== undefined) {
|
||||
const pending: (() => void)[] = [];
|
||||
let currentlyPending = false;
|
||||
|
||||
const div = document.createElement("div");
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const processing = pending.splice(0, pending.length);
|
||||
|
||||
for (const callback of processing) {
|
||||
callback();
|
||||
}
|
||||
|
||||
currentlyPending = false;
|
||||
|
||||
if (pending.length > 0) {
|
||||
div.classList.toggle("foo");
|
||||
currentlyPending = true;
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(div, { attributes: true });
|
||||
|
||||
return (callback: () => void) => {
|
||||
pending.push(callback);
|
||||
|
||||
if (!currentlyPending) {
|
||||
div.classList.toggle("foo");
|
||||
currentlyPending = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (callback: () => void) => setTimeout(callback, 0);
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Promise implementation for browsers that don't support it.
|
||||
*
|
||||
* @param {function(function(T), function(*))} resolver
|
||||
* @param {function(function(T|!Thenable.<T>), function(*))} executor
|
||||
*/
|
||||
class SimplePromise<T> {
|
||||
private _state: SimplePromiseState = SimplePromiseState.PENDING;
|
||||
|
||||
private _thens: { propagateFulfilling: (value: T) => void; propagateRejection: (reason: any) => void }[] = [];
|
||||
private _propagateIsPending: boolean = false;
|
||||
private _fulfillReactions: FulfilledPromiseReaction<T, any>[] = [];
|
||||
private _rejectReactions: RejectedPromiseReaction<any>[] = [];
|
||||
|
||||
private _alreadyFulfilledValue: T = null;
|
||||
private _alreadyRejectedReason: any = null;
|
||||
private _fulfilledValue: T | null = null;
|
||||
private _rejectedReason: any = null;
|
||||
|
||||
constructor(resolver: (resolve: (value: T | Promise<T>) => void, reject: (reason: any) => void) => void) {
|
||||
constructor(executor: (resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void) => void) {
|
||||
if (typeof executor !== "function") {
|
||||
throw new TypeError(`typeof executor !== "function"`);
|
||||
}
|
||||
|
||||
const { resolve, reject } = this._createResolvingFunctions();
|
||||
try {
|
||||
resolver(value => this._resolve(value), reason => this._reject(reason));
|
||||
executor(resolve, reject);
|
||||
}
|
||||
catch (ex) {
|
||||
this._reject(ex);
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?function(T):(U|Promise.<U>)} fulfilledHandler
|
||||
* @param {?function(*):(U|Promise.<U>)} rejectedHandler
|
||||
* @param {?function(T):(U|!Thenable.<U>)} onFulfilled
|
||||
* @param {?function(*):(U|!Thenable.<U>)} onRejected
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(fulfilledHandler?: (value: T) => U | Promise<U>, rejectedHandler?: (reason: any) => U | Promise<U>): Promise<U> {
|
||||
fulfilledHandler = (typeof fulfilledHandler === "function") ? fulfilledHandler : null;
|
||||
rejectedHandler = (typeof rejectedHandler === "function") ? rejectedHandler : null;
|
||||
then<U>(onFulfilled: ((value: T) => U | Thenable<U>) | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U> {
|
||||
const resultCapability = new DeferredPromise<U>();
|
||||
|
||||
if (fulfilledHandler === null && rejectedHandler === null) {
|
||||
return <any>this;
|
||||
if (typeof onFulfilled !== "function") {
|
||||
onFulfilled = (value: T) => value as any as U;
|
||||
}
|
||||
|
||||
if (fulfilledHandler === null) {
|
||||
fulfilledHandler = value => <U><any>value;
|
||||
if (typeof onRejected !== "function") {
|
||||
onRejected = (reason: any): U => { throw reason; };
|
||||
}
|
||||
|
||||
if (rejectedHandler === null) {
|
||||
rejectedHandler = (reason): U => { throw reason; };
|
||||
const fulfillReaction: FulfilledPromiseReaction<T, U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onFulfilled,
|
||||
};
|
||||
|
||||
const rejectReaction: RejectedPromiseReaction<U> = {
|
||||
capabilities: resultCapability,
|
||||
handler: onRejected,
|
||||
};
|
||||
|
||||
switch (this._state) {
|
||||
case SimplePromiseState.PENDING:
|
||||
this._fulfillReactions.push(fulfillReaction);
|
||||
this._rejectReactions.push(rejectReaction);
|
||||
break;
|
||||
|
||||
case SimplePromiseState.FULFILLED:
|
||||
this._enqueueFulfilledReactionJob(fulfillReaction, this._fulfilledValue!);
|
||||
break;
|
||||
|
||||
case SimplePromiseState.REJECTED:
|
||||
this._enqueueRejectedReactionJob(rejectReaction, this._rejectedReason);
|
||||
break;
|
||||
}
|
||||
|
||||
var result = new Promise<U>((resolve, reject) => {
|
||||
this._thens.push({
|
||||
propagateFulfilling: value => {
|
||||
try {
|
||||
resolve(fulfilledHandler(value));
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}, propagateRejection: reason => {
|
||||
try {
|
||||
resolve(rejectedHandler(reason));
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._propagateResolution();
|
||||
|
||||
return result;
|
||||
return resultCapability.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(*):(T|Promise.<T>)} rejectedHandler
|
||||
* @param {function(*):(T|!Thenable.<T>)} onRejected
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
catch(rejectedHandler?: (reason: any) => T | Promise<T>): Promise<T> {
|
||||
return this.then(null, rejectedHandler);
|
||||
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T> {
|
||||
return this.then(undefined, onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isFulfilled(): boolean {
|
||||
return this._state === SimplePromiseState.FULFILLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRejected(): boolean {
|
||||
return this._state === SimplePromiseState.REJECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isPending(): boolean {
|
||||
return this._state === SimplePromiseState.PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {T}
|
||||
*/
|
||||
value(): T {
|
||||
if (this._state !== SimplePromiseState.FULFILLED) {
|
||||
throw new Error("This promise is not in FULFILLED state.");
|
||||
}
|
||||
|
||||
return this._alreadyFulfilledValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {*}
|
||||
*/
|
||||
reason(): any {
|
||||
if (this._state !== SimplePromiseState.REJECTED) {
|
||||
throw new Error("This promise is not in FULFILLED state.");
|
||||
}
|
||||
|
||||
return this._alreadyRejectedReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T|!Promise.<T>} value
|
||||
* @param {T|!Thenable.<T>} value
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
static resolve<T>(value: T | Promise<T>): Promise<T> {
|
||||
static resolve<T>(value: T | Thenable<T>): Promise<T> {
|
||||
if (value instanceof SimplePromise) {
|
||||
return value;
|
||||
}
|
||||
@@ -151,14 +189,22 @@ class SimplePromise<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<T|!Promise.<T>>} values
|
||||
* @param {*} reason
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
static reject<T>(reason: any): Promise<T> {
|
||||
return new Promise<T>((/* ujs:unreferenced */ resolve, reject) => reject(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<T|!Thenable.<T>>} values
|
||||
* @return {!Promise.<!Array.<T>>}
|
||||
*/
|
||||
static all<T>(values: (T | Promise<T>)[]): Promise<T[]> {
|
||||
static all<T>(values: (T | Thenable<T>)[]): Promise<T[]> {
|
||||
return new Promise<T[]>((resolve, reject) => {
|
||||
var result: T[] = [];
|
||||
const result: T[] = [];
|
||||
|
||||
var numUnresolved = values.length;
|
||||
let numUnresolved = values.length;
|
||||
if (numUnresolved === 0) {
|
||||
resolve(result);
|
||||
return;
|
||||
@@ -171,52 +217,86 @@ class SimplePromise<T> {
|
||||
if (numUnresolved === 0) {
|
||||
resolve(result);
|
||||
}
|
||||
}), reject);
|
||||
}, reject));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T|!Promise.<T>} value
|
||||
* @param {!Array.<T|!Thenable.<T>>} values
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
private _resolve(value: T | Promise<T>): void {
|
||||
var alreadyCalled = false;
|
||||
static race<T>(values: (T | Thenable<T>)[]): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
for (const value of values) {
|
||||
Promise.resolve(value).then(resolve, reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {{ resolve(T|!Thenable.<T>), reject(*) }}
|
||||
*/
|
||||
private _createResolvingFunctions(): { resolve(resolution: T | Thenable<T>): void; reject(reason: any): void; } {
|
||||
let alreadyResolved = false;
|
||||
|
||||
const resolve = (resolution: T | Thenable<T>): void => {
|
||||
if (alreadyResolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
alreadyResolved = true;
|
||||
|
||||
if (resolution === this) {
|
||||
this._reject(new TypeError(`resolution === this`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolution === null || (typeof resolution !== "object" && typeof resolution !== "function")) {
|
||||
this._fulfill(resolution as T);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var then = (resolution as Thenable<T>).then;
|
||||
}
|
||||
catch (ex) {
|
||||
this._reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof then !== "function") {
|
||||
this._fulfill(resolution as T);
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueJob(() => this._resolveWithThenable(resolution as Thenable<T>, then));
|
||||
};
|
||||
|
||||
const reject = (reason: any): void => {
|
||||
if (alreadyResolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
alreadyResolved = true;
|
||||
|
||||
this._reject(reason);
|
||||
};
|
||||
|
||||
return { resolve, reject };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Thenable.<T>} thenable
|
||||
* @param {{function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))}} then
|
||||
*/
|
||||
private _resolveWithThenable(thenable: Thenable<T>, then: ThenableThen<T>): void {
|
||||
const { resolve, reject } = this._createResolvingFunctions();
|
||||
|
||||
try {
|
||||
if (value === this) {
|
||||
throw new TypeError("2.3.1");
|
||||
}
|
||||
|
||||
var thenMethod = SimplePromise._getThenMethod<T>(value);
|
||||
if (thenMethod === null) {
|
||||
this._fulfill(<T>value);
|
||||
return;
|
||||
}
|
||||
|
||||
thenMethod.call(
|
||||
<Promise<T>>value,
|
||||
(value: T) => {
|
||||
if (alreadyCalled) {
|
||||
return;
|
||||
}
|
||||
alreadyCalled = true;
|
||||
|
||||
this._resolve(value);
|
||||
},
|
||||
(reason: any) => {
|
||||
if (alreadyCalled) {
|
||||
return;
|
||||
}
|
||||
alreadyCalled = true;
|
||||
|
||||
this._reject(reason);
|
||||
});
|
||||
then.call(thenable, resolve, reject);
|
||||
}
|
||||
catch (ex) {
|
||||
if (alreadyCalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._reject(ex);
|
||||
reject(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,127 +304,126 @@ class SimplePromise<T> {
|
||||
* @param {T} value
|
||||
*/
|
||||
private _fulfill(value: T): void {
|
||||
if (this._state !== SimplePromiseState.PENDING) {
|
||||
return;
|
||||
}
|
||||
const reactions = this._fulfillReactions;
|
||||
|
||||
this._fulfilledValue = value;
|
||||
this._fulfillReactions = [];
|
||||
this._rejectReactions = [];
|
||||
this._state = SimplePromiseState.FULFILLED;
|
||||
this._alreadyFulfilledValue = value;
|
||||
|
||||
this._propagateResolution();
|
||||
for (const reaction of reactions) {
|
||||
this._enqueueFulfilledReactionJob(reaction, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} reason
|
||||
*/
|
||||
private _reject(reason: any): void {
|
||||
if (this._state !== SimplePromiseState.PENDING) {
|
||||
return;
|
||||
}
|
||||
const reactions = this._rejectReactions;
|
||||
|
||||
this._rejectedReason = reason;
|
||||
this._fulfillReactions = [];
|
||||
this._rejectReactions = [];
|
||||
this._state = SimplePromiseState.REJECTED;
|
||||
this._alreadyRejectedReason = reason;
|
||||
|
||||
this._propagateResolution();
|
||||
for (const reaction of reactions) {
|
||||
this._enqueueRejectedReactionJob(reaction, reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!*} obj
|
||||
* @return {?function(function(T):(T|!Promise.<T>), function(*):(T|!Promise.<T>)):!Promise.<T>}
|
||||
* @param {!FulfilledPromiseReaction.<T, *>} reaction
|
||||
* @param {T} value
|
||||
*/
|
||||
private static _getThenMethod<T>(obj: any): (fulfilledHandler: (value: T) => T | Promise<T>, rejectedHandler: (reason: any) => T | Promise<T>) => Promise<T> {
|
||||
if (typeof obj !== "object" && typeof obj !== "function") {
|
||||
return null;
|
||||
}
|
||||
private _enqueueFulfilledReactionJob(reaction: FulfilledPromiseReaction<T, any>, value: T): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
return null;
|
||||
}
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
var then: any = obj.then;
|
||||
if (typeof then !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return then;
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagates the result of the current promise to all its children.
|
||||
*/
|
||||
private _propagateResolution(): void {
|
||||
if (this._state === SimplePromiseState.PENDING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._propagateIsPending) {
|
||||
return;
|
||||
}
|
||||
this._propagateIsPending = true;
|
||||
|
||||
SimplePromise._nextTick(() => {
|
||||
this._propagateIsPending = false;
|
||||
|
||||
if (this._state === SimplePromiseState.FULFILLED) {
|
||||
while (this._thens.length > 0) {
|
||||
var nextThen = this._thens.shift();
|
||||
nextThen.propagateFulfilling(this._alreadyFulfilledValue);
|
||||
}
|
||||
try {
|
||||
handlerResult = handler(value);
|
||||
}
|
||||
else if (this._state === SimplePromiseState.REJECTED) {
|
||||
while (this._thens.length > 0) {
|
||||
var nextThen = this._thens.shift();
|
||||
nextThen.propagateRejection(this._alreadyRejectedReason);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
|
||||
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
|
||||
private static _nextTick: (callback: () => void) => void = (function () {
|
||||
var MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
|
||||
if (global.process !== undefined && typeof global.process.nextTick === "function") {
|
||||
return (callback: () => void) => {
|
||||
global.process.nextTick(callback);
|
||||
};
|
||||
}
|
||||
else if (MutationObserver !== undefined) {
|
||||
var pending: (() => void)[] = [];
|
||||
var currentlyPending = false;
|
||||
/**
|
||||
* @param {!RejectedPromiseReaction.<*>} reaction
|
||||
* @param {*} reason
|
||||
*/
|
||||
private _enqueueRejectedReactionJob(reaction: RejectedPromiseReaction<any>, reason: any): void {
|
||||
enqueueJob(() => {
|
||||
const { capabilities: { resolve, reject }, handler } = reaction;
|
||||
|
||||
var div = document.createElement("div");
|
||||
let handlerResult: any | Thenable<any>;
|
||||
|
||||
var observer = new MutationObserver(() => {
|
||||
var processing = pending;
|
||||
pending = [];
|
||||
try {
|
||||
handlerResult = handler(reason);
|
||||
}
|
||||
catch (ex) {
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let callback of processing) {
|
||||
callback();
|
||||
}
|
||||
resolve(handlerResult);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
currentlyPending = false;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
if (pending.length > 0) {
|
||||
div.classList.toggle("foo");
|
||||
currentlyPending = true;
|
||||
}
|
||||
});
|
||||
declare var global: {
|
||||
Promise?: typeof Promise;
|
||||
MutationObserver?: typeof MutationObserver;
|
||||
WebkitMutationObserver?: typeof MutationObserver;
|
||||
process?: {
|
||||
nextTick(callback: () => void): void;
|
||||
}
|
||||
};
|
||||
|
||||
observer.observe(div, { attributes: true });
|
||||
interface FulfilledPromiseReaction<T, U> {
|
||||
/** @type {!libjass.DeferredPromise.<U>} */
|
||||
capabilities: DeferredPromise<U>;
|
||||
|
||||
return (callback: () => void) => {
|
||||
pending.push(callback);
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {U|!Thenable.<U>}
|
||||
*/
|
||||
handler(value: T): U | Thenable<U>;
|
||||
}
|
||||
|
||||
if (!currentlyPending) {
|
||||
div.classList.toggle("foo");
|
||||
currentlyPending = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (callback: () => void) => setTimeout(callback, 0);
|
||||
}
|
||||
})();
|
||||
interface RejectedPromiseReaction<U> {
|
||||
/** @type {!libjass.DeferredPromise.<U>} */
|
||||
capabilities: DeferredPromise<U>;
|
||||
|
||||
/**
|
||||
* @param {*} reason
|
||||
* @return {U|!Thenable.<U>}
|
||||
*/
|
||||
handler(reason: any): U | Thenable<U>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,54 +435,12 @@ enum SimplePromiseState {
|
||||
REJECTED = 2,
|
||||
}
|
||||
|
||||
declare var global: {
|
||||
Promise?: typeof SimplePromise;
|
||||
MutationObserver?: typeof MutationObserver;
|
||||
WebkitMutationObserver?: typeof MutationObserver;
|
||||
process?: {
|
||||
nextTick(callback: () => void): void;
|
||||
}
|
||||
};
|
||||
|
||||
export interface Promise<T> {
|
||||
/**
|
||||
* @param {?function(T):(U|Promise.<U>)} fulfilledHandler
|
||||
* @param {?function(*):(U|Promise.<U>)} rejectedHandler
|
||||
* @return {!Promise.<U>}
|
||||
*/
|
||||
then<U>(fulfilledHandler?: (value: T) => U | Promise<U>, rejectedHandler?: (reason: any) => U | Promise<U>): Promise<U>;
|
||||
|
||||
/**
|
||||
* @param {function(*):(T|Promise.<T>)} rejectedHandler
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
catch(rejectedHandler?: (reason: any) => T | Promise<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 | Promise<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
|
||||
prototype: Promise<any>;
|
||||
resolve<T>(value: T | Promise<T>): Promise<T>;
|
||||
all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
|
||||
} = global.Promise;
|
||||
|
||||
if (Promise === undefined) {
|
||||
Promise = SimplePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
|
||||
*
|
||||
* @param {?function(new:Promise)} value
|
||||
*/
|
||||
export function setImplementation(value: typeof Promise): void {
|
||||
export function setImplementation(value: typeof Promise | null): void {
|
||||
if (value !== null) {
|
||||
Promise = value;
|
||||
}
|
||||
@@ -417,13 +454,23 @@ export function setImplementation(value: typeof Promise): void {
|
||||
*/
|
||||
export class DeferredPromise<T> {
|
||||
private _promise: Promise<T>;
|
||||
private _resolve: (value: T) => void;
|
||||
private _reject: (reason: any) => void;
|
||||
|
||||
/**
|
||||
* @type {function(T|!Thenable.<T>)}
|
||||
*/
|
||||
resolve: (value: T | Thenable<T>) => void;
|
||||
|
||||
/**
|
||||
* @type {function(*)} reason
|
||||
*/
|
||||
reject: (reason: any) => void;
|
||||
|
||||
constructor() {
|
||||
this._promise = new Promise<T>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
Object.defineProperties(this, {
|
||||
resolve: { value: resolve, enumerable: true },
|
||||
reject: { value: reject, enumerable: true },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,18 +480,56 @@ export class DeferredPromise<T> {
|
||||
get promise(): Promise<T> {
|
||||
return this._promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
*/
|
||||
resolve(value: T): void {
|
||||
this._resolve(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} reason
|
||||
*/
|
||||
reject(reason: any): void {
|
||||
this._reject(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the first (in iteration order) promise that fulfills, and rejects if all the promises reject.
|
||||
*
|
||||
* @param {!Array.<!Promise.<T>>} promises
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
export function first<T>(promises: Promise<T>[]): Promise<T> {
|
||||
return first_rec(promises, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<!Promise.<T>>} promises
|
||||
* @param {!Array.<*>} previousRejections
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
function first_rec<T>(promises: Promise<T>[], previousRejections: any[]): Promise<T> {
|
||||
if (promises.length === 0) {
|
||||
return Promise.reject(previousRejections);
|
||||
}
|
||||
|
||||
const [head, ...tail] = promises;
|
||||
return head.catch(reason => first_rec(tail, previousRejections.concat(reason)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to the first (in time order) promise that fulfills, and rejects if all the promises reject.
|
||||
*
|
||||
* @param {!Array.<!Promise.<T>>} promises
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
export function any<T>(promises: Promise<T>[]): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) =>
|
||||
Promise.all<any>(promises.map(promise => promise.then(resolve, reason => reason))).then(reject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that runs the given callback when the promise has resolved regardless of whether it fulfilled or rejected.
|
||||
*
|
||||
* @param {!Promise.<T>} promise
|
||||
* @param {function()} body
|
||||
* @return {!Promise.<T>}
|
||||
*/
|
||||
export function lastly<T>(promise: Promise<T>, body: () => void): Promise<T> {
|
||||
return promise.then<any>(value => {
|
||||
body();
|
||||
return value;
|
||||
}, reason => {
|
||||
body();
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface Set<T> {
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): this;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean;
|
||||
|
||||
/**
|
||||
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set implementation for browsers that don't support it. Only supports Number and String elements.
|
||||
*
|
||||
@@ -25,7 +54,7 @@
|
||||
*
|
||||
* @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;
|
||||
|
||||
@@ -40,7 +69,7 @@ class SimpleSet<T> {
|
||||
throw new Error("Non-array iterables are not supported by the SimpleSet constructor.");
|
||||
}
|
||||
|
||||
for (let value of iterable) {
|
||||
for (const value of iterable) {
|
||||
this.add(value);
|
||||
}
|
||||
}
|
||||
@@ -49,8 +78,8 @@ class SimpleSet<T> {
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): Set<T> {
|
||||
var property = this._toProperty(value);
|
||||
add(value: T): this {
|
||||
const property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
throw new Error("This Set implementation only supports Number and String values.");
|
||||
@@ -77,7 +106,7 @@ class SimpleSet<T> {
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean {
|
||||
var property = this._toProperty(value);
|
||||
const property = this._toProperty(value);
|
||||
|
||||
if (property === null) {
|
||||
return false;
|
||||
@@ -90,9 +119,9 @@ 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 {
|
||||
for (let property of Object.keys(this._elements)) {
|
||||
var element = this._elements[property];
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -108,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 }`;
|
||||
}
|
||||
@@ -123,42 +152,11 @@ class SimpleSet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
declare var global: {
|
||||
Set?: typeof SimpleSet;
|
||||
};
|
||||
|
||||
export interface Set<T> {
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {libjass.Set.<T>} This set
|
||||
*/
|
||||
add(value: T): Set<T>;
|
||||
|
||||
/**
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
has(value: T): boolean;
|
||||
|
||||
/**
|
||||
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
|
||||
* @param {*} thisArg
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
|
||||
*
|
||||
* 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>=)}
|
||||
@@ -166,9 +164,9 @@ export interface Set<T> {
|
||||
export var Set: {
|
||||
new <T>(iterable?: T[]): Set<T>;
|
||||
prototype: Set<any>;
|
||||
} = global.Set;
|
||||
} = global.Set || SimpleSet;
|
||||
|
||||
if (Set === undefined || typeof Set.prototype.forEach !== "function" || (() => {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,11 +145,11 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
* @return {!Promise.<*>}
|
||||
*/
|
||||
request(command: WorkerCommands, parameters: any): Promise<any> {
|
||||
var deferred = new DeferredPromise<any>();
|
||||
var requestId = ++WorkerChannelImpl._lastRequestId;
|
||||
const deferred = new DeferredPromise<any>();
|
||||
const requestId = ++WorkerChannelImpl._lastRequestId;
|
||||
this._pendingRequests.set(requestId, deferred);
|
||||
|
||||
var requestMessage: WorkerRequestMessage = { requestId, command, parameters };
|
||||
const requestMessage: WorkerRequestMessage = { requestId, command, parameters };
|
||||
this._comm.postMessage(serialize(requestMessage));
|
||||
|
||||
return deferred.promise;
|
||||
@@ -158,7 +159,7 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
* @param {number} requestId
|
||||
*/
|
||||
cancelRequest(requestId: number): void {
|
||||
var deferred = this._pendingRequests.get(requestId);
|
||||
const deferred = this._pendingRequests.get(requestId);
|
||||
if (deferred === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -171,7 +172,7 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
* @param {!WorkerResponseMessage} message
|
||||
*/
|
||||
private _respond(message: WorkerResponseMessage): void {
|
||||
var { requestId, error, result } = message;
|
||||
let { requestId, error, result } = message;
|
||||
if (error instanceof Error) {
|
||||
error = { message: error.message, stack: error.stack };
|
||||
}
|
||||
@@ -182,12 +183,12 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
* @param {string} rawMessage
|
||||
*/
|
||||
private _onMessage(rawMessage: string): void {
|
||||
var message = <{ command: WorkerCommands }>deserialize(rawMessage);
|
||||
const message = deserialize(rawMessage) as { command: WorkerCommands };
|
||||
|
||||
if (message.command === WorkerCommands.Response) {
|
||||
var responseMessage = <WorkerResponseMessage><any>message;
|
||||
const responseMessage = message as any as WorkerResponseMessage;
|
||||
|
||||
var deferred = this._pendingRequests.get(responseMessage.requestId);
|
||||
const deferred = this._pendingRequests.get(responseMessage.requestId);
|
||||
if (deferred !== undefined) {
|
||||
this._pendingRequests.delete(responseMessage.requestId);
|
||||
if (responseMessage.error === null) {
|
||||
@@ -199,10 +200,10 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
}
|
||||
}
|
||||
else {
|
||||
var requestMessage = <WorkerRequestMessage>message;
|
||||
var requestId = requestMessage.requestId;
|
||||
const requestMessage = message as WorkerRequestMessage;
|
||||
const requestId = requestMessage.requestId;
|
||||
|
||||
var commandCallback = getWorkerCommandHandler(requestMessage.command);
|
||||
const commandCallback = getWorkerCommandHandler(requestMessage.command);
|
||||
if (commandCallback === undefined) {
|
||||
this._respond({ requestId, error: new Error(`No handler registered for command ${ requestMessage.command }`), result: null });
|
||||
return;
|
||||
@@ -216,4 +217,4 @@ export class WorkerChannelImpl implements WorkerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
registerWorkerCommand(WorkerCommands.Ping, parameters => new Promise(resolve => resolve()));
|
||||
registerWorkerCommand(WorkerCommands.Ping, parameters => Promise.resolve(null));
|
||||
|
||||
@@ -18,23 +18,19 @@
|
||||
* 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 var exports: any;
|
||||
|
||||
/**
|
||||
* Indicates whether web workers are supposed in this environment or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
export var supported = typeof Worker !== "undefined";
|
||||
export const supported = typeof Worker !== "undefined";
|
||||
|
||||
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.
|
||||
@@ -43,16 +39,19 @@ export var supported = typeof Worker !== "undefined";
|
||||
* 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));
|
||||
}
|
||||
|
||||
var _scriptNode: HTMLScriptElement = null;
|
||||
if (typeof document !== "undefined" && document.currentScript !== undefined) {
|
||||
_scriptNode = document.currentScript;
|
||||
}
|
||||
|
||||
declare var global: any;
|
||||
declare const global: any;
|
||||
|
||||
if (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope) {
|
||||
// This is a web worker. Set up a channel to talk back to the main thread.
|
||||
|
||||
@@ -23,9 +23,7 @@ import { Map } from "../utility/map";
|
||||
import { WorkerCommands } from "./commands";
|
||||
import { WorkerCommandHandler } from "./channel";
|
||||
|
||||
var workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
|
||||
|
||||
var classPrototypes = new Map<number, any>();
|
||||
const workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var hydratedValue = Object.create(classPrototypes.get(value._classTag));
|
||||
for (let key of Object.keys(value)) {
|
||||
if (key !== "_classTag") {
|
||||
hydratedValue[key] = value[key];
|
||||
}
|
||||
}
|
||||
value = hydratedValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,13 +18,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface Document {
|
||||
/**
|
||||
* @type {!HTMLScriptElement}
|
||||
*/
|
||||
currentScript: HTMLScriptElement;
|
||||
}
|
||||
|
||||
interface WorkerGlobalScope {
|
||||
/**
|
||||
* @param {*} message
|
||||
|
||||
@@ -65,7 +65,7 @@ define(["intern!tdd", "intern/chai!assert", "intern/dojo/node!fs", "intern/dojo/
|
||||
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
|
||||
});
|
||||
|
||||
tdd.test("@{link} tags in the source", function () {
|
||||
tdd.test("{@link} tags in the source", function () {
|
||||
var regex = /\{@link ([^}]+)\}/g;
|
||||
var brokenLinks = [];
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[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,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
|
||||
Dialogue: 0,0:00:00.00,0:10:00.00,Default,,0,0,0,,X
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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("alpha", function () {
|
||||
tdd.test("Basic", function () {
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 1280, 720, "rgb(47, 163, 254)");
|
||||
return testPage
|
||||
.prepare()
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); })
|
||||
.then(function (testPage) { return testPage.done(); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 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", "intern/chai!assert", "require", "intern/dojo/node!leadfoot/helpers/pollUntil"], function (tdd, assert, require, pollUntil) {
|
||||
tdd.suite("Auto clock", function () {
|
||||
tdd.test("Operations", function () {
|
||||
this.remote.session.setExecuteAsyncTimeout(10000);
|
||||
|
||||
return this.remote
|
||||
.get(require.toUrl("tests/support/browser-test-page.html"))
|
||||
.then(pollUntil('return (document.readyState === "complete") ? true : null;', 100))
|
||||
.execute(function () {
|
||||
window.driverStartTime = 0;
|
||||
window.driverStartTimeAt = new Date();
|
||||
window.driver = function () {
|
||||
return (new Date() - driverStartTimeAt) / 1000 + driverStartTime;
|
||||
};
|
||||
|
||||
window.clock = new libjass.renderers.AutoClock(driver);
|
||||
|
||||
window.events = [];
|
||||
|
||||
clock.addEventListener(libjass.renderers.ClockEvent.Play, function () {
|
||||
events.push("play");
|
||||
});
|
||||
|
||||
clock.addEventListener(libjass.renderers.ClockEvent.Tick, function () {
|
||||
events.push("tick");
|
||||
});
|
||||
|
||||
clock.addEventListener(libjass.renderers.ClockEvent.Pause, function () {
|
||||
events.push("pause");
|
||||
});
|
||||
|
||||
clock.addEventListener(libjass.renderers.ClockEvent.Stop, function () {
|
||||
events.push("stop");
|
||||
});
|
||||
|
||||
return { enabled: clock.enabled, paused: clock.paused, rate: clock.rate, events: events.slice() };
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, true);
|
||||
assert.strictEqual(clock.rate, 1);
|
||||
assert.deepEqual(clock.events, []);
|
||||
})
|
||||
.executeAsync(function (callback) {
|
||||
clock.play();
|
||||
|
||||
setTimeout(function () {
|
||||
callback({ enabled: clock.enabled, paused: clock.paused, events: events.slice() });
|
||||
}, 1000);
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, false);
|
||||
|
||||
assert(clock.events.length >= 2);
|
||||
assert.strictEqual(clock.events[0], "play");
|
||||
for (var i = 1; i < clock.events.length; i++) {
|
||||
assert.strictEqual(clock.events[i], "tick");
|
||||
}
|
||||
})
|
||||
.execute(function () {
|
||||
events = [];
|
||||
|
||||
clock.disable();
|
||||
|
||||
return { enabled: clock.enabled, paused: clock.paused, events: events.slice() };
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, false);
|
||||
assert.strictEqual(clock.paused, true);
|
||||
assert.deepEqual(clock.events, ["pause", "stop"]);
|
||||
})
|
||||
.execute(function () {
|
||||
events = [];
|
||||
|
||||
clock.enable();
|
||||
|
||||
return { enabled: clock.enabled, paused: clock.paused, events: events.slice() };
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, true);
|
||||
assert.deepEqual(clock.events, []);
|
||||
})
|
||||
.executeAsync(function (callback) {
|
||||
setTimeout(function () {
|
||||
callback({ enabled: clock.enabled, paused: clock.paused, events: events.slice() });
|
||||
}, 1000);
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, false);
|
||||
|
||||
assert(clock.events.length >= 2);
|
||||
assert.strictEqual(clock.events[0], "play");
|
||||
for (var i = 1; i < clock.events.length; i++) {
|
||||
assert.strictEqual(clock.events[i], "tick");
|
||||
}
|
||||
})
|
||||
.execute(function () {
|
||||
events = [];
|
||||
|
||||
driverStartTime = 30000;
|
||||
clock.seeking();
|
||||
|
||||
var result = { enabled: clock.enabled, paused: clock.paused, currentTime: clock.currentTime, events: events.slice() };
|
||||
events = [];
|
||||
return result;
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, true);
|
||||
assert(clock.currentTime >= 30);
|
||||
assert.deepEqual(clock.events, ["pause", "stop", "play", "tick", "pause"]);
|
||||
})
|
||||
.executeAsync(function (callback) {
|
||||
setTimeout(function () {
|
||||
callback({ enabled: clock.enabled, paused: clock.paused, currentTime: clock.currentTime, events: events.slice() });
|
||||
}, 1000);
|
||||
})
|
||||
.then(function (clock) {
|
||||
assert.strictEqual(clock.enabled, true);
|
||||
assert.strictEqual(clock.paused, false);
|
||||
assert(clock.currentTime >= 31);
|
||||
|
||||
assert(clock.events.length >= 2);
|
||||
assert.strictEqual(clock.events[0], "play");
|
||||
for (var i = 1; i < clock.events.length; i++) {
|
||||
assert.strictEqual(clock.events[i], "tick");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -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
|
||||
@@ -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(); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,18 @@
|
||||
[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,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
|
||||
Dialogue: 0,0:00:00.00,0:10:00.00,Default,,0,0,0,,xxx{\fscx50}xxx{\fscx\fscy50}xxx{\fscx50\fscy50}xxx
|
||||
@@ -18,8 +18,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @type {!DomParser} */
|
||||
export var domParser: DOMParser = null;
|
||||
if (typeof DOMParser !== "undefined") {
|
||||
domParser = new DOMParser();
|
||||
}
|
||||
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
|
||||
tdd.suite("fscx and fscy", function () {
|
||||
tdd.test("Basic", function () {
|
||||
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/fsc/fsc.ass", 1280, 720);
|
||||
return testPage
|
||||
.prepare()
|
||||
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./fsc-1.png")); })
|
||||
.then(function (testPage) { return testPage.done(); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |