1 Commits
Author SHA1 Message Date
Arnavion c6053f59a1 Nested rotations. 2014-08-17 22:09:16 -07:00
29 changed files with 3478 additions and 3706 deletions
+3 -5
View File
@@ -1,5 +1,3 @@
language: node_js
node_js:
- "0.10"
script:
- "./node_modules/.bin/gulp doc"
language: node_js
node_js:
- "0.10"
+20
View File
@@ -9,3 +9,23 @@
```
This will install the dependencies - [Gulp](http://gulpjs.com/), [Mocha](http://visionmedia.github.io/mocha/), [TypeScript](http://www.typescriptlang.org/), [UglifyJS2](https://github.com/mishoo/UglifyJS2) and [Vinyl](https://github.com/wearefractal/vinyl). It will then run gulp to build libjass.js and minify it into libjass.min.js
1. Set the URLs of the video and the ASS file in index.xhtml
```html
<video id="video" src="<URL OF VIDEO HERE>" controls="">
<track src="<URL OF SCRIPT HERE>" kind="metadata" data-format="ass" />
</video>
```
1. Set the URLs of any fonts you want to make available in fonts.css
```css
@font-face {
font-family: "<NAME OF FONT HERE>";
src: url("<URL OF FONT HERE>");
}
```
for each font. The name of the font is what it's called in the ASS file.
1. Start your web server and navigate to index.xhtml in a browser.
-92
View File
@@ -1,92 +0,0 @@
### v0.9.0 - 2014/11/27
- BREAKING CHANGE - ASS.fromString() now returns a Promise of an ASS object, not an ASS object directly. The synchronous ASS parser used by ASS.fromString() is no more.
- BACKWARD-COMPATIBLE CHANGE - WebRenderer constructor parameters order has changed from (ass, clock, settings, libjassSubsWrapper) to (ass, clock, libjassSubsWrapper, settings). The constructor will detect the old order and reorder accordingly.
- Added ASS.fromStream and ASS.fromXhr that read a stream and an XMLHttpRequest object's response respectively and return (a Promise of) an ASS object. Both of these parse the script asynchronously.
- Added RendererSettings.enableSvg that can be used to toggle the use of SVG filter effects for outlines and blur.
- libjass.js now has an AMD wrapper so that it can be used with RequireJS, etc.
- Settings parameter is now optional for WebRenderer and DefaultRenderer.
- Added support for clock rates apart from 1 to clocks and renderers.
- Added a parameter to libjass.createWorker to specify the path to libjass.js that will run in the worker.
- Fixed Style and Dialogue constructors not setting defaults for missing properties.
- Fixed color and alpha parser to support more formats.
- Fixed SRT parser to replace all HTML tags it finds, instead of just the first one.
- Fixed font size calculation to use the vertical scale instead of horizontal.
- Fixed line-height on newlines.
- Fixed missing perspective on X and Y rotations.
### v0.8.0 - 2014/08/16
- Added web worker support. libjass.parse can now be offloaded to a web worker.
- Implemented \fs+ and \fs-
- Added ASS.addEvent() to add dialogue lines to an ASS object.
- Renamed ClockEvent.TimeUpdate to ClockEvent.Tick, and added ClockEvent.Stop
- Clock.enable() and .disable() now return a boolean to indicate whether the function had any effect.
- Added Clock.setEnabled() to force the enabled-state to the given value.
- Renamed ManualClock.timeUpdate() to ManualClock.tick()
- Moved WebRenderer.enable(), .disable() and .enabled to NullRenderer
- Fixed not being able to parse tags with default values.
- Fixed font preloader downloading the same font multiple times because it didn't filter for duplicates.
- Fixed min-width value not taking separate left and right margins into account.
- Fixed absolutely positioned subs were always left-aligned even if they had an alignment tag.
- Fixed blur and outlines getting truncated.
### v0.7.0 - 2014/05/15
- Implemented \be
- Split a new renderer, WebRenderer, off DefaultRenderer that doesn't rely on a video element.
- All renderers now require a Clock to generate time events. VideoClock is a Clock backed by a video element, while ManualClock is a clock that can be used to generate arbitrary time events.
### v0.6.0 - 2014/03/24
- All script properties and style properties are now parsed and stored in the ASS and Style objects.
- Basic SRT support, by passing in a libjass.Format argument to ASS.fromString()
- \clip and \iclip now have their drawing instructions parsed as an array of libjass.parts.drawing.Instruction's instead of just a string.
- Added DefaultRenderer.enable(), DefaultRenderer.disable() and DefaultRenderer.toggle() to change whether the renderer is displaying subtitles or not.
- DefaultRenderer.resizeVideo is now called DefaultRenderer.resize. Now it only resizes the subtitle wrapper div, not the video element.
- Replaced the 41ms setInterval-bsed timer with a requestAnimationFrame-based timer to reduce load on minimized or hidden browser tabs.
- DefaultRenderer now renders dialogues in the correct order according to the script.
- Fixed incorrect font sizes.
- Replaced jake with gulp.
### v0.5.0 - 2014/01/26
- Removed preLoadFonts renderer setting. It was redundant with the actual fontMap setting since the presence or absence of that setting is enough to signal whether the user wants to preload fonts or not.
- Multiple renderers can now be used on the same page without conflicting with each other.
- Implemented \shad, \xshad, \yshad
- Fixed ASS draw scale being used incorrectly.
- ASS.resolutionX and ASS.resolutionY are now properties of ASS.properties, a ScriptProperties object.
### v0.4.0 - 2013/12/27
- All parts moved from the libjass.tags namespace to the libjass.parts namespace.
- Replaced PEG.js parser with a hand-written one. This allows for parsing lines that are strictly invalid grammar but are parsed successfully by VSFilter or libass.
- All ASS tags are now supported by the parser.
- Removed the useHighResolutionTimer setting for DefaultRenderer. DefaultRenderer always uses the 41ms timer now.
- Implemented \move
- Implemented ASS draw
- Fixed subs overflowing the video dimensions still being visible.
- SVG filters are now used for outlines and blur.
- Delay parsing of dialogue lines till they need to be pre-rendered. As a side-effect, all fonts in the font map are preloaded now, not just the ones used in the current script.
### v0.3.0 - 2013/10/28
- Moved libjass.DefaultRenderer to libjass.renderers.DefaultRenderer
- Added libjass.renderers.NullRenderer, a renderer that doesn't render anything.
- DefaultRenderer's fontMap setting is now a Map instead of an Object. It now supports more than one URL for each font name.
- DefaultRenderer now generates the subtitle wrapper div itself.
- DefaultRenderer now takes video letterboxing into account when resizing the subtitles.
- DefaultRenderer has a new setting useHighResolutionTimer that makes it use a 41ms timer instead of video.timeUpdate's 250ms timer.
- div IDs and CSS class names are now prefixed with "libjass-" to avoid collisions with other elements on the page.
- All numeric CSS property values are now truncated to three decimal places.
- Added ```jake watch``` that rebuilds and runs tests on changes to the source.
- Added ```jake doc``` that builds API documentation.
- Added Travis CI build.
### v0.2.0 - 2013/09/11
- Added libjass.DefaultRenderer, a class that handles initializing the layer div's, preloading fonts, and drawing Dialogues based on the current video time.
- libjass.js can now be loaded in node. Only the parser can be used.
- Tests can now be run with ```jake test``` or ```npm test``` using Mocha.
### v0.1.0 - 2013/08/29
- First npm release.
+7 -13
View File
@@ -1,6 +1,6 @@
[![Build Status](https://travis-ci.org/Arnavion/libjass.png?branch=master)](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 on HTML5 video in the browser.
### What's special about libjass?
@@ -18,9 +18,7 @@ As a result, libjass is able to render subtitles with very low CPU usage. The do
* The .ts files are the source of libjass. They are TypeScript files and must be 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
* libjass.css is a CSS file that you'll need to deploy to your website.
* The rest of the files - index.xhtml, index.js, index.css and fonts.css - are a sample implementation of how to use libjass on a web page. They demonstrate the API to call, how to place &lt;div&gt; elements to render the subs, etc.
### I want to use libjass for my website. What do I need to do?
@@ -36,17 +34,15 @@ Only libjass.js and libjass.css are needed to use libjass on your website. The o
The API documentation is linked in the Links section below. Here's an overview:
* The method [ASS.fromUrl()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) takes in a URL to an ASS script and returns a promise that resolves to an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object. This ASS object represents the script properties, the line styles and dialogue lines in it. Alternatively, you can use [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) to convert a string of the script contents into an ASS object.
* The method [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) takes in the raw ASS string and returns an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object representing the script properties, the line styles and dialogue lines in it. The example index.js uses XHR to get this data using the URL specified in a track tag.
* Next, you initialize a renderer to render the subtitles. libjass ships with an easy-to-use renderer, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). It uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
* index.js initializes a default renderer that libjass ships with, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). This renderer uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
* The renderer uses [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) as a source of timer ticks. In each tick, it determines the set of dialogues to be shown at the current video time, renders each of them as a div, and appendChild's the div into the appropriate layer+alignment div.
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](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 handles resizing the subtitles when the user clicks the browser's native fullscreen-video button. index.js also contains code to change the size of the subtitles based on user input.
* 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)
* For an example of using libjass, check out [the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - index.js creates this map from the @font-face rules in fonts.css.
### Can I use libjass in node?
@@ -72,8 +68,6 @@ true
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun). For example, [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun.parse_dialogueParts) can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
Entire scripts can be parsed via [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
### Can I contribute?
@@ -111,7 +105,7 @@ You can also join the IRC channel below and ask any questions.
* Document browser compatibility. Currently libjass is tested on IE11 (Windows 7), Firefox Nightly and Google Chrome (Dev channel).
* Write API documentation. Add more explanatory comments to the code.
* Write more parser tests. Also figure out a way to test layout. See [#21](https://github.com/Arnavion/libjass/issues/21)
* Write more parser tests. Also figure out a way to test layout.
* Evaluate (document, benchmark) the benefits and drawbacks of DOM+CSS-based drawing over canvas.
+794
View File
@@ -0,0 +1,794 @@
@font-face {
font-family: "???? Pro";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
@font-face {
font-family: "123 and...";
src: url("/fonts/123-and...-Regular.ttf");
}
@font-face {
font-family: "123and";
src: url("/fonts/123-and...-Regular.ttf");
}
@font-face {
font-family: "AmbersHand-Plain";
src: url("/fonts/LEHN011.TTF");
}
@font-face {
font-family: "Amienne";
src: url("/fonts/Amienne.ttf");
}
@font-face {
font-family: "A-OTF Futo Min A101 Pro";
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "A-OTF Futo Min A101 Pro Bold";
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "Arial";
src: url("/fonts/arial.ttf");
}
@font-face {
font-family: "ArialMT";
src: url("/fonts/arial.ttf");
}
@font-face {
font-family: "ArtificeSSK";
src: url("/fonts/ArtificeSSK.ttf");
}
@font-face {
font-family: "Baar Sophia";
src: url("/fonts/BAARS.TTF");
}
@font-face {
font-family: "BaarSophia";
src: url("/fonts/BAARS.TTF");
}
@font-face {
font-family: "Babylon 27";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "Balzano";
src: url("/fonts/BALZANO.ttf");
}
@font-face {
font-family: "Bee Marker Ink";
src: url("/fonts/BeeMarkerInk.TTF");
}
@font-face {
font-family: "BeeMarkerInk";
src: url("/fonts/BeeMarkerInk.TTF");
}
@font-face {
font-family: "Berlin Sans FB Demi";
src: url("/fonts/BRLNSDB.TTF"), url("/fonts/BRLNSDB_0.TTF");
font-weight: bold;
}
@font-face {
font-family: "BerlinSansFBDemi-Bold";
src: url("/fonts/BRLNSDB.TTF"), url("/fonts/BRLNSDB_0.TTF");
font-weight: bold;
}
@font-face {
font-family: "BlueBrush";
src: url("/fonts/BlueBrush.otf");
}
@font-face {
font-family: "Brianne's hand";
src: url("/fonts/Brianne_s_hand.ttf");
}
@font-face {
font-family: "Brianne'shand";
src: url("/fonts/Brianne_s_hand.ttf");
}
@font-face {
font-family: "bylon 27";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "CAC Moose";
src: url("/fonts/CAC%20Moose.ttf");
}
@font-face {
font-family: "CACMoose";
src: url("/fonts/CAC%20Moose.ttf");
}
@font-face {
font-family: "CenturyOldStyle-Light";
src: url("/fonts/CENTURYO.TTF"), url("/fonts/CenturyOldStyle-Light.ttf");
}
@font-face {
font-family: "CenturyOldStyle-Light";
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
font-weight: bold;
}
@font-face {
font-family: "CenturyOldStyle-Light-Bold";
src: url("/fonts/CenturyOldStyle-Light%20Bold.ttf");
font-weight: bold;
}
@font-face {
font-family: "Charme";
src: url("/fonts/CHARME.TTF");
}
@font-face {
font-family: "Chinacat";
src: url("/fonts/chinrg.ttf");
}
@font-face {
font-family: "Complete in Him";
src: url("/fonts/Complete%20in%20Him.ttf");
}
@font-face {
font-family: "CompleteinHim";
src: url("/fonts/Complete%20in%20Him.ttf");
}
@font-face {
font-family: "Consolas";
src: url("/fonts/consola.ttf");
}
@font-face {
font-family: "Consolas";
src: url("/fonts/consolab.ttf");
font-weight: bold;
}
@font-face {
font-family: "Consolas-Bold";
src: url("/fonts/consolab.ttf");
font-weight: bold;
}
@font-face {
font-family: "Continuum Bold";
src: url("/fonts/contb.ttf");
}
@font-face {
font-family: "ContinuumBold";
src: url("/fonts/contb.ttf");
}
@font-face {
font-family: "Designosaur";
src: url("/fonts/Designosaur-Regular.ttf");
}
@font-face {
font-family: "Designosaur";
src: url("/fonts/Designosaur-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Designosaur-Italic";
src: url("/fonts/Designosaur-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Dutch766 BT";
src: url("/fonts/Du766Bd.ttf");
font-weight: bold;
}
@font-face {
font-family: "Dutch766BT-BoldA";
src: url("/fonts/Du766Bd.ttf");
font-weight: bold;
}
@font-face {
font-family: "EFN Impresja";
src: url("/fonts/impresja.ttf");
}
@font-face {
font-family: "EFNImpresja";
src: url("/fonts/impresja.ttf");
}
@font-face {
font-family: "Elephant";
src: url("/fonts/ELEPHNT.TTF");
}
@font-face {
font-family: "Elephant-Regular";
src: url("/fonts/ELEPHNT.TTF");
}
@font-face {
font-family: "EmilysHand-Plain";
src: url("/fonts/LEHN084.TTF");
}
@font-face {
font-family: "EngraversGothic BT";
src: url("/fonts/Engrvgot.TTF");
}
@font-face {
font-family: "EngraversGothicBT-Regular";
src: url("/fonts/Engrvgot.TTF");
}
@font-face {
font-family: "Essai";
src: url("/fonts/Essai.ttf");
}
@font-face {
font-family: "Estrangelo Edessa";
src: url("/fonts/estre.ttf");
}
@font-face {
font-family: "EstrangeloEdessa";
src: url("/fonts/estre.ttf");
}
@font-face {
font-family: "Euphemia";
src: url("/fonts/Euphemia_0.ttf");
}
@font-face {
font-family: "EuphemiaCAS";
src: url("/fonts/Euphemia_0.ttf");
}
@font-face {
font-family: "FarrahsHand-Plain";
src: url("/fonts/LEHN095.TTF");
}
@font-face {
font-family: "Fontin Sans";
src: url("/fonts/FONTIN_SANS.OTF");
font-weight: bold;
}
@font-face {
font-family: "Fontin Sans";
src: url("/fonts/FONTIN_SANS_BI.OTF");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "Fontin Sans Rg";
src: url("/fonts/FONTIN_SANS.OTF");
font-weight: bold;
}
@font-face {
font-family: "Fontin Sans Rg";
src: url("/fonts/FONTIN_SANS_BI.OTF");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "FontinSans-Bold";
src: url("/fonts/FONTIN_SANS.OTF");
font-weight: bold;
}
@font-face {
font-family: "FontinSans-BoldItalic";
src: url("/fonts/FONTIN_SANS_BI.OTF");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "Forte";
src: url("/fonts/FORTE.TTF");
}
@font-face {
font-family: "ForteMT";
src: url("/fonts/FORTE.TTF");
}
@font-face {
font-family: "Franchise";
src: url("/fonts/Franchise-Bold-hinted.ttf");
}
@font-face {
font-family: "Franchise-Bold";
src: url("/fonts/Franchise-Bold-hinted.ttf");
}
@font-face {
font-family: "FutoMinA101Pro-Bold";
src: url("/fonts/A-OTF-FutoMinA101Pro-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "Gandhi Sans";
src: url("/fonts/GandhiSans-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "Gandhi Sans";
src: url("/fonts/GandhiSans-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "GandhiSans-Bold";
src: url("/fonts/GandhiSans-Bold.otf");
font-weight: bold;
}
@font-face {
font-family: "GandhiSans-BoldItalic";
src: url("/fonts/GandhiSans-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: "HandelGotDBol";
src: url("/fonts/HANDGOTB.TTF");
}
@font-face {
font-family: "HandelGotD-Bold";
src: url("/fonts/HANDGOTB.TTF");
}
@font-face {
font-family: "HandelGothic BT";
src: url("/fonts/HANDGOTN.TTF");
}
@font-face {
font-family: "HandelGothicBT-Regular";
src: url("/fonts/HANDGOTN.TTF");
}
@font-face {
font-family: "HandVetica";
src: url("/fonts/HandVetica.ttf");
font-weight: bold;
}
@font-face {
font-family: "HandVetica-Bold";
src: url("/fonts/HandVetica.ttf");
font-weight: bold;
}
@font-face {
font-family: "HiraginoGyoDS-W4";
src: url("/fonts/HiraginoGyoDS-W4.ttf");
}
@font-face {
font-family: "HZHandwrite";
src: url("/fonts/HZHandwrite.ttf");
}
@font-face {
font-family: "Impressum Std";
src: url("/fonts/ImpressumStd-Roman.otf");
}
@font-face {
font-family: "ImpressumStd-Roman";
src: url("/fonts/ImpressumStd-Roman.otf");
}
@font-face {
font-family: "ITC Avant Garde Gothic Std";
src: url("/fonts/ITCAvantGardeStd-Md.otf");
}
@font-face {
font-family: "ITC Avant Garde Std Md";
src: url("/fonts/ITCAvantGardeStd-Md.otf");
}
@font-face {
font-family: "ITCAvantGardeStd-Md";
src: url("/fonts/ITCAvantGardeStd-Md.otf");
}
@font-face {
font-family: "IwaMinPro-Madoka-Lt";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "IwaOMinPro-Fate-Bd";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Old Pro-Fate";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Old Pro-Fate B";
src: url("/fonts/IwaOMinPro-Bd-Fate.ttf");
}
@font-face {
font-family: "Iwata Mincho Pro-Madoka";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "Iwata Mincho Pro-Madoka L";
src: url("/fonts/IwaMinPro-Lt-Madoka.ttf");
}
@font-face {
font-family: "JanieHmkBold";
src: url("/fonts/JANIHB.TTF");
}
@font-face {
font-family: "Kabel LT Std";
src: url("/fonts/KabelLTStd-Book.otf");
}
@font-face {
font-family: "Kabel LT Std Book";
src: url("/fonts/KabelLTStd-Book.otf");
}
@font-face {
font-family: "KabelLTStd-Book";
src: url("/fonts/KabelLTStd-Book.otf");
}
@font-face {
font-family: "KozGoPro-Heavy-Madoka";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "Kozuka Gothic Pro";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "KozukaGothicProH-Madoka";
src: url("/fonts/KozGoProH-Madoka.ttf");
font-weight: bold;
}
@font-face {
font-family: "LEHN011";
src: url("/fonts/LEHN011.TTF");
}
@font-face {
font-family: "LEHN084";
src: url("/fonts/LEHN084.TTF");
}
@font-face {
font-family: "LEHN095";
src: url("/fonts/LEHN095.TTF");
}
@font-face {
font-family: "LHF Scriptana";
src: url("/fonts/LHF_Scriptana.ttf");
}
@font-face {
font-family: "LHFScriptana-Regular";
src: url("/fonts/LHF_Scriptana.ttf");
}
@font-face {
font-family: "LinotypeFinnegan-Medium";
src: url("/fonts/LT.ttf"), url("/fonts/LT_3.ttf"), url("/fonts/LT_4.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "LinotypeFinnegan-MediumIt";
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
font-style: italic;
}
@font-face {
font-family: "LLRubberGrotesqueOTF";
src: url("/fonts/LLRUG.ttf");
}
@font-face {
font-family: "LTFinnegan Medium";
src: url("/fonts/LT.ttf"), url("/fonts/LT_3.ttf"), url("/fonts/LT_4.ttf"), url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "LTFinnegan Medium";
src: url("/fonts/LT_3italic.ttf"), url("/fonts/LT_italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Macron Finnetier Medium";
src: url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "MacronFinnetier-Medium";
src: url("/fonts/macron%20finnetier%200.3.ttf");
}
@font-face {
font-family: "Mentone";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Mentone Lig";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Mentone-SemiBold";
src: url("/fonts/mentone-semibol.otf");
font-weight: 600;
}
@font-face {
font-family: "Montara";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "Montara Gothic";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara Gothic";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "Montara-Gothic";
src: url("/fonts/Montara-Gothic.otf");
}
@font-face {
font-family: "Montara-Italic";
src: url("/fonts/Montara-Italic.otf");
font-style: italic;
}
@font-face {
font-family: "New";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "NewBaskerville";
src: url("/fonts/NEWBASK.TTF"), url("/fonts/NewBaskerville.ttf");
}
@font-face {
font-family: "Old Block";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "Old Block Black";
src: url("/fonts/OBGB.TTF");
}
@font-face {
font-family: "Old newspaper font";
src: url("/fonts/Old_Newspaper_Font.ttf");
}
@font-face {
font-family: "Oldnewspaperfont";
src: url("/fonts/Old_Newspaper_Font.ttf");
}
@font-face {
font-family: "Painfresco";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "Painfresco";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Painfresco-Italic";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Painfresco-Regular";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "PaintyPaint";
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
}
@font-face {
font-family: "PaintyPaint1";
src: url("/fonts/PAINP.TTF"), url("/fonts/PAINP_0.TTF");
}
@font-face {
font-family: "Panefresco800wt-Italic";
src: url("/fonts/Painfresco-Italic.ttf");
font-style: italic;
}
@font-face {
font-family: "Panefresco800wtKilllaKill-Regular";
src: url("/fonts/Painfresco-Regular.ttf");
}
@font-face {
font-family: "Pencilfied";
src: url("/fonts/pencilfied.ttf");
}
@font-face {
font-family: "PencilfiedX";
src: url("/fonts/pencilfied.ttf");
}
@font-face {
font-family: "Profile";
src: url("/fonts/Profile-Medium.otf"), url("/fonts/Profile-Regular.otf");
}
@font-face {
font-family: "Profile-Medium";
src: url("/fonts/Profile-Medium.otf");
}
@font-face {
font-family: "Profile-Regular";
src: url("/fonts/Profile-Regular.otf");
}
@font-face {
font-family: "Prototype";
src: url("/fonts/Prototype.ttf");
}
@font-face {
font-family: "Reswysokr";
src: url("/fonts/Reswysokr.otf");
}
@font-face {
font-family: "Signika";
src: url("/fonts/Signika-Regular.otf");
}
@font-face {
font-family: "Signika-Regular";
src: url("/fonts/Signika-Regular.otf");
}
@font-face {
font-family: "Spectrum MT Std";
src: url("/fonts/SpectrumMTStd.otf");
}
@font-face {
font-family: "SpectrumMTStd";
src: url("/fonts/SpectrumMTStd.otf");
}
@font-face {
font-family: "Swis721 Hv BT";
src: url("/fonts/SWZ721H.TTF");
}
@font-face {
font-family: "Swis721 Md BT";
src: url("/fonts/SWZ721M.TTF");
}
@font-face {
font-family: "Swiss721BT-Heavy";
src: url("/fonts/SWZ721H.TTF");
}
@font-face {
font-family: "Swiss721BT-Medium";
src: url("/fonts/SWZ721M.TTF");
}
@font-face {
font-family: "Unisketchlightlimited";
src: url("/fonts/Unisketch-light_limited.ttf");
}
@font-face {
font-family: "Unisketch-lightlimitedLight";
src: url("/fonts/Unisketch-light_limited.ttf");
}
@font-face {
font-family: "Untitled";
src: url("/fonts/Babylon-27-Babylon-27.ttf");
}
@font-face {
font-family: "Yryr minc R";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
@font-face {
font-family: "YryrmincR";
src: url("/fonts/Yryr%20minc%20R.ttf");
}
+9 -41
View File
@@ -38,63 +38,35 @@ gulp.task("default", ["libjass.js", "libjass.min.js"]);
function ASTModifer(namespaces) {
var visitor = function (current) {
var newComments = [];
var newText = [];
if (current instanceof TypeScript.AST.Namespace) {
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Constructor) {
newComments.push("@constructor");
newText.push("@constructor");
if (current.baseType !== null) {
newComments.push(
"@extends {" +
current.baseType.type.fullName +
(current.baseType.generics.length > 0 ? ("." + current.baseType.generics.join(", ")) : "") +
"}"
);
newText.push("@extends {" + current.baseType.fullName + "}");
}
if (current.parent !== null) {
newComments.push("@memberOf " + current.parent.fullName);
newText.push("@memberOf " + current.parent.fullName);
}
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Interface) {
if (current.baseTypes.length > 0) {
current.baseTypes.forEach(function (baseType) {
newComments.push(
"@extends {" +
baseType.type.fullName +
(baseType.generics.length > 0 ? ("." + baseType.generics.join(", ")) : "") +
"}"
);
});
}
}
else if (current instanceof TypeScript.AST.Enum) {
newComments.push("@enum");
}
if ((current.generics !== undefined) && (current.generics.length > 0)) {
newComments.push("@template " + current.generics.join(", "));
}
if (current.isPrivate) {
newComments.push("@private");
}
if (current.isProtected) {
newComments.push("@protected");
newText.push("@private");
}
if (current.isStatic) {
newComments.push("@static");
newText.push("@static");
}
if (newComments.length > 0) {
current.astNode["gulp-typescript-new-comment"] = newComments;
if (newText.length > 0) {
current.astNode["gulp-typescript-new-comment"] = newText;
}
};
@@ -153,10 +125,6 @@ gulp.task("watch", ["clean"], function (callback) {
});
});
gulp.task("demo", ["libjass.js"], function () {
return gulp.src(["./libjass.js", "./libjass.js.map", "./libjass.css"]).pipe(gulp.dest("../libjass-gh-pages/demo/"));
});
gulp.task("doc", ["libjass.js"], function () {
return gulp.src("./libjass.ts").pipe(Doc("./api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
return gulp.src("./libjass.ts").pipe(Doc("/api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
});
+426 -440
View File
@@ -37,372 +37,72 @@ Array.prototype.concatMany = function (arr) {
return result;
};
var sorter = (function () {
var visibilitySorter = function (value1, value2) {
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
return 0;
}
if (value1.isPrivate) {
return 1;
}
if (value2.isPrivate) {
return -1;
}
if (value1.isProtected) {
return 1;
}
if (value2.isProtected) {
return -1;
}
return 0;
};
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.Interface, TypeScript.AST.Constructor, TypeScript.AST.Enum];
var typeSorter = function (value1, value2) {
var type1Index = -1;
var type2Index = -1;
types.every(function (type, index) {
if (value1 instanceof type) {
type1Index = index;
}
if (value2 instanceof type) {
type2Index = index;
}
return (type1Index === -1) || (type2Index === -1);
});
return type1Index - type2Index;
};
var nameSorter = function (value1, value2) {
return value1.name.localeCompare(value2.name);
};
var sorters = [visibilitySorter, typeSorter, nameSorter];
return function (value1, value2) {
for (var i = 0; i < sorters.length; i++) {
var result = sorters[i](value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
};
})();
var indenter = function (indent) {
return function (line) {
return ((line === "") ? line : (Array(indent + 1).join("\t") + line));
};
};
var sanitize = function (string) {
return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
};
var toVariableName = function (item) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
var name = item.name;
var result = "";
for (var i = 0; i < name.length; i++) {
if (name[i] === name[i].toLowerCase()) {
// This is lower case. Write it as lower case.
result += name[i];
}
else {
// This is upper case.
if (i === 0) {
// This is the first character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i - 1] === name[i - 1].toUpperCase()) {
// The previous character was upper case.
if (i === name.length - 1) {
// This is the last character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i + 1] === name[i + 1].toLowerCase()) {
// The next character is lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
else {
// The next character is upper case. Write this one as lower case.
result += name[i].toLowerCase();
}
}
else {
// Previous character was lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
}
}
return result;
};
var toUsageName = function (item) {
if (item instanceof TypeScript.AST.Constructor || item instanceof TypeScript.AST.Interface || item instanceof TypeScript.AST.Enum) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.parent instanceof TypeScript.AST.Namespace) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.isStatic) {
return toUsageName(item.parent) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
};
var toId = function (item) {
return sanitize((item.fullName === undefined) ? item.name : item.fullName);
};
var toLink = function (item) {
return (
'<a href="#' + toId(((item instanceof TypeScript.AST.TypeReference) ? item.type : item)) + '">' +
sanitize(
((item instanceof TypeScript.AST.TypeReference) ? item.type.name : item.name) +
((item.generics !== undefined && item.generics.length > 0) ? ('.<' + item.generics.join(', ') + '>') : '')
) +
'</a>'
);
};
var writeDescription = function (text) {
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, function (substring, linkTarget) {
return '<a href="#' + linkTarget + '">' + linkTarget + '</a>';
});
var inCodeBlock = false;
result = result.split("\n").map(function (line) {
if (line.substr(0, " ".length) === " ") {
line = line.substr(" ".length);
if (!inCodeBlock) {
inCodeBlock = true;
line = '<pre class="code"><code>' + line;
}
}
else if (line.length > 0 && inCodeBlock) {
inCodeBlock = false;
line = '</code></pre>' + line;
}
return line;
}).join("\n");
if (inCodeBlock) {
result += '</code></pre>';
}
return result;
};
var writeParameters = function (parameters) {
if (parameters.length === 0) {
return [];
}
return [
'<dd class="parameters">',
' <dl>'
].concatMany(parameters.map(function (parameter) {
return [
' <dt class="parameter name">' + sanitize(parameter.name) + '</dt>',
' <dd class="parameter type">' + sanitize(parameter.type) + '</dd>',
' <dd class="parameter description">' + writeDescription(parameter.description) + '</dd>'
].concatMany(writeParameters(parameter.subParameters).map(indenter(2)));
})).concat([
' </dl>',
'</dd>'
]);
};
TypeScript.AST.Function.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="function' +
(this.isAbstract ? ' abstract' : '') +
(this.isPrivate ? ' private' : '') +
(this.isProtected ? ' protected' : '') +
(this.isStatic ? ' static' : '') +
'">',
' <dt class="name">' + toLink(this) + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize(
((this.returnType !== null) ? 'var result = ' : '') + toUsageName(this) + '(' +
this.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') + ');'
) +
'</code></pre></fieldset></dd>'
].concat(writeParameters(this.parameters).map(indenter(1))).concat(
(this.returnType === null) ? [] : [
' <dt>Returns</dt>',
' <dd class="return type">' + sanitize(this.returnType.type) + '</dd>',
' <dd class="return description">' + writeDescription(this.returnType.description) + '</dd>'
]
).concat([
'</dl>'
]);
};
TypeScript.AST.Interface.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="interface' +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">interface ' + toLink(this) + ((this.baseTypes.length > 0) ? (' extends ' + this.baseTypes.map(function (baseType) { return toLink(baseType); }).join(', ')) : '') + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="members">'
].concatMany(this.members.map(function (member) {
return member.toHtml().map(indenter(2));
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Constructor.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="constructor' +
(this.isAbstract ? ' abstract' : '') +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">class ' + toLink(this) + ((this.baseType !== null) ? (' extends ' + toLink(this.baseType)) : '') + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize(
'var ' + toVariableName(this) + ' = ' + 'new ' + toUsageName(this) + '(' +
this.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') +
');'
) + '</code></pre></fieldset></dd>'
].concat(writeParameters(this.parameters).map(indenter(1))).concat([
' <dd class="members">'
]).concatMany(this.members.map(function (member) {
return member.toHtml().map(indenter(2));
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Enum.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="enum' +
(this.isPrivate ? ' private' : '') +
'">',
' <dt class="name">enum <a href="#' + sanitize(this.fullName) + '">' + sanitize(this.name) + '</a></dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>'
].concat([
' <dd class="members">'
]).concatMany(this.members.map(function (member) {
return [
' <dl id="' + toId(member) + '" class="member">',
' <dt class="name">' + toLink(member) + ' = ' + member.value + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(member.description) + '</p>',
' </dd>',
' </dl>'
];
})).concat([
' </dd>',
'</dl>'
]);
};
TypeScript.AST.Variable.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="variable">',
' <dt class="name">' + toLink(this) + '</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' +
sanitize('var result = ' + toUsageName(this) + ';\n' +
toUsageName(this) + ' = value;') +
'</code></pre></fieldset></dd>',
' <dd class="return type">' + sanitize(this.type) + '</dd>',
'</dl>'
];
};
TypeScript.AST.Property.prototype.toHtml = function () {
return [
'<dl id="' + toId(this) + '" class="property">',
' <dt class="name">' + toLink(this) + '</dt>'
].concat((this.getter === null) ? [] : [
' <dt class="getter">Getter</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.getter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' + sanitize('var result = ' + toUsageName(this) + ';') + '</code></pre></fieldset></dd>',
' <dd class="return type">' + sanitize(this.getter.type) + '</dd>'
]).concat((this.setter === null) ? [] : [
' <dt class="setter">Setter</dt>',
' <dd class="description">',
' <p>' + writeDescription(this.setter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend /><pre><code>' + sanitize(toUsageName(this) + ' = value;') + '</code></pre></fieldset></dd>'
].concat(writeParameters([new TypeScript.AST.Parameter("value", "", this.setter.type)]).map(indenter(1)))).concat([
'</dl>'
]);
};
module.exports = function (outputFilePath) {
module.exports = function () {
var compiler = new TypeScript.Compiler();
return Transform(function (file, encoding) {
compiler.addFile(file);
}, function () {
// Compile
compiler.compile();
var filenames = [];
var allFiles = [];
return Transform(function (file, encoding) {
filenames.push(file.path);
allFiles.push.apply(allFiles, compiler.addFile(file));
}, function () {
// Walk
var namespaces = TypeScript.AST.walk(compiler);
var namespaces = TypeScript.AST.walk(compiler, allFiles);
// Make HTML
var sorter = (function () {
var visibilitySorter = function (value1, value2) {
if (value1.isPrivate === value2.isPrivate) {
return 0;
}
if (value1.isPrivate) {
return 1;
}
if (value2.isPrivate) {
return -1;
}
return 0;
};
var namespaceNames = Object.keys(namespaces).filter(function (namespaceName) {
return namespaceName.substr(0, "libjass".length) === "libjass";
}).sort(function (ns1, ns2) {
var types = [TypeScript.AST.Variable, TypeScript.AST.Property, TypeScript.AST.Function, TypeScript.AST.Interface, TypeScript.AST.Constructor];
var typeSorter = function (value1, value2) {
var type1Index = -1;
var type2Index = -1;
types.every(function (type, index) {
if (value1 instanceof type) {
type1Index = index;
}
if (value2 instanceof type) {
type2Index = index;
}
return (type1Index === -1) || (type2Index === -1);
});
return type1Index - type2Index;
};
var nameSorter = function (value1, value2) {
return value1.name.localeCompare(value2.name);
};
var sorters = [visibilitySorter, typeSorter, nameSorter];
return function (value1, value2) {
for (var i = 0; i < sorters.length; i++) {
var result = sorters[i](value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
};
})();
var namespaceNames = Object.keys(namespaces).sort(function (ns1, ns2) {
return ns1.localeCompare(ns2);
});
@@ -416,8 +116,359 @@ module.exports = function (outputFilePath) {
});
});
var sanitize = function (string) {
return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
};
var indenter = function (indent) {
return function (line) {
return ((line === "") ? line : (Array(indent + 1).join("\t") + line));
};
};
var writeOverview = function (indent) {
return [].concatMany(namespaceNames.map(function (namespaceName) {
var namespace = namespaces[namespaceName];
return [
'<span class="namespace"><a href="#' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</a></span>',
'<ul class="namespace-elements">'
].concatMany(namespace.members.map(function (value) {
return (
' <li' +
((value.isPrivate === true) ? ' class="private"' : '') +
'><a href="#' + sanitize(value.fullName) + '">' + sanitize(value.name) + '</a></li>'
);
})).concat([
'</ul>',
''
]);
})).map(indenter(indent));
};
var writeFunction = function (func, indent) {
return [
'<dl class="function' +
(func.isAbstract ? ' abstract' : '') +
(func.isPrivate ? ' private' : '') +
(func.isStatic ? ' static' : '') +
'" id="' + sanitize(func.fullName) +
'">',
' <dt class="name">' + writeFunctionName(func) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(func.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend />' + writeFunctionUsage(func) + '</fieldset></dd>'
].concat(writeParameters(func, 1)).concat(writeReturns(func, 1)).concat([
'</dl>'
]).map(indenter(indent));
};
var writeInterface = function (interface, indent) {
return [
'<dl class="interface' +
(interface.isPrivate ? ' private' : '') +
'" id="' +
sanitize(interface.fullName) +
'">',
' <dt class="name">' + writeInterfaceName(interface) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(interface.description) + '</p>',
' </dd>'
].concat([
' <dd class="members">'
]).concatMany(interface.members.map(function (member) {
if (member instanceof TypeScript.AST.Variable) {
return writeVariable(member, 2);
}
if (member instanceof TypeScript.AST.Function) {
return writeFunction(member, 2);
}
if (member instanceof TypeScript.AST.Property) {
return writeProperty(member, 2);
}
})).concat([
' </dd>',
'</dl>'
]).map(indenter(indent));
};
var writeConstructor = function (constructor, indent) {
return [
'<dl class="constructor' +
(constructor.isAbstract ? ' abstract' : '') +
(constructor.isPrivate ? ' private' : '') +
'" id="' +
sanitize(constructor.fullName) +
'">',
' <dt class="name">' + writeConstructorName(constructor) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(constructor.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend />' + writeConstructorUsage(constructor) + '</fieldset></dd>'
].concat(writeParameters(constructor, 1)).concat([
' <dd class="members">'
]).concatMany(constructor.members.map(function (member) {
if (member instanceof TypeScript.AST.Variable) {
return writeVariable(member, 2);
}
if (member instanceof TypeScript.AST.Function) {
return writeFunction(member, 2);
}
if (member instanceof TypeScript.AST.Property) {
return writeProperty(member, 2);
}
})).concat([
' </dd>',
'</dl>'
]).map(indenter(indent));
};
var writeVariable = function (variable, indent) {
return [
'<dl class="variable" id="' + sanitize(variable.fullName) + '">',
' <dt class="name">' + writePropertyName(variable) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(variable.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend />' + writeVariableUsage(variable) + '</fieldset></dd>'
].concat([
' <dd class="return type">' + sanitize(variable.type) + '</dd>',
]).concat([
'</dl>'
]).map(indenter(indent));
};
var writeCallableName = function (callable) {
return (
'<a href="#' + sanitize(callable.fullName) + '">' +
sanitize(
callable.name +
((callable.generics.length > 0) ? ('.<' + callable.generics.join(', ') + '>') : '')
) +
'</a>'
);
};
var writeFunctionName = function (func) {
return writeCallableName(func);
};
var writeInterfaceName = function (interface) {
return (
'interface ' +
writeCallableName(interface) +
((interface.baseType !== null) ? (' extends <a href="#' + sanitize(interface.baseType.fullName) + '">' + sanitize(interface.baseType.name) + '</a>') : '')
);
};
var writeConstructorName = function (constructor) {
return (
'class ' +
writeCallableName(constructor) +
((constructor.baseType !== null) ? (' extends <a href="#' + sanitize(constructor.baseType.fullName) + '">' + sanitize(constructor.baseType.name) + '</a>') : '')
);
};
var writeVariableUsage = function (variable) {
return (
'<pre><code>' +
sanitize('var result = ' + toVariableName(variable.parent) + '.' + variable.name + ';' +
'\n' +
toVariableName(variable.parent) + '.' + variable.name + ' = value;') +
'</code></pre>'
);
};
var writeFunctionUsage = function (func) {
return (
'<pre><code>' +
sanitize(
((func.returnType !== null) ? 'var result = ' : '') +
((func.parent !== null) ? ((func.isStatic ? func.parent.name : toVariableName(func.parent)) + '.') : '') +
func.name + '(' +
func.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') + ');') +
'</code></pre>'
);
};
var writeConstructorUsage = function (constructor) {
return (
'<pre><code>' +
sanitize(
'var ' + toVariableName(constructor) + ' = ' +
'new ' + constructor.name + '(' +
constructor.parameters.map(function (parameter) {
return parameter.name;
}).join(', ') + ');') +
'</code></pre>'
);
};
var writeParameters = function (callable, indent) {
return _writeParameters(callable.parameters, indent);
};
var _writeParameters = function (parameters, indent) {
if (parameters.length === 0) {
return [];
}
return [
'<dd class="parameters">',
' <dl>'
].concatMany(parameters.map(function (parameter) {
return [
' <dt class="parameter name">' + sanitize(parameter.name) + '</dt>',
' <dd class="parameter type">' + sanitize(parameter.type) + '</dd>',
' <dd class="parameter description">' + sanitize(parameter.description) + '</dd>'
].concatMany(_writeParameters(parameter.subParameters, indent + 1));
})).concat([
' </dl>',
'</dd>'
]).map(indenter(indent));
};
var writeReturns = function (func, indent) {
if (func.returnType === null) {
return [];
}
return [
'<dt>Returns</dt>',
'<dd class="return type">' + sanitize(func.returnType.type) + '</dd>',
'<dd class="return description">' + sanitize(func.returnType.description) + '</dd>'
].map(indenter(indent));
};
var writeProperty = function (property, indent) {
var result = [];
if (property.getter !== null) {
result = result.concat(writeGetter(property, indent));
}
if (property.setter !== null) {
result = result.concat(writeSetter(property, indent));
}
return result;
}
var writeGetter = function (property, indent) {
var getter = property.getter;
return [
'<dl class="getter" id="' + sanitize(property.fullName) + '">',
' <dt class="name">' + writePropertyName(property) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(getter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend />' + writeGetterUsage(property) + '</fieldset></dd>'
].concat([
' <dd class="return type">' + sanitize(getter.type) + '</dd>',
]).concat([
'</dl>'
]).map(indenter(indent));
};
var writeSetter = function (property, indent) {
var setter = property.setter;
return [
'<dl class="setter" id="' + sanitize(property.fullName) + '">',
' <dt class="name">' + writePropertyName(property) + '</dt>',
' <dd class="description">',
' <p>' + sanitize(setter.description) + '</p>',
' </dd>',
' <dd class="usage"><fieldset><legend />' + writeSetterUsage(property) + '</fieldset></dd>'
].concat(writeParameters(setter, 1)).concat([
'</dl>'
]).map(indenter(indent));
};
var writePropertyName = function (property) {
return (
'<a href="#' + sanitize(property.fullName) + '">' +
sanitize(property.name) +
'</a>'
);
};
var writeGetterUsage = function (property) {
return (
'<pre><code>' +
sanitize(
'var result = ' +
toVariableName(property.parent) + '.' + property.name +
';') +
'</code></pre>'
);
};
var writeSetterUsage = function (property) {
return (
'<pre><code>' +
sanitize(
toVariableName(property.parent) + '.' + property.name +
' = ' +
property.setter.parameters[0].name +
';') +
'</code></pre>'
);
};
var toVariableName = function (constructor) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
var name = constructor.name;
var result = "";
for (var i = 0; i < name.length; i++) {
if (name[i] === name[i].toLowerCase()) {
// This is lower case. Write it as lower case.
result += name[i];
}
else {
// This is upper case.
if (i === 0) {
// This is the first character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i - 1] === name[i - 1].toUpperCase()) {
// The previous character was upper case.
if (i === name.length - 1) {
// This is the last character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i + 1] === name[i + 1].toLowerCase()) {
// The next character is lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
else {
// The next character is upper case. Write this one as lower case.
result += name[i].toLowerCase();
}
}
else {
// Previous character was lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
}
}
return result;
};
this.push(new Vinyl({
path: outputFilePath,
path: "api.xhtml",
contents: Buffer.concat([
'<?xml version="1.0" encoding="utf-8" ?>',
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
@@ -445,20 +496,16 @@ module.exports = function (outputFilePath) {
' border-bottom: 1px solid black;',
' }',
'',
' .variable, .function, .interface, .constructor, .enum, .property {',
' .variable, .function, .interface, .constructor, .getter, .setter {',
' margin-left: 30px;',
' padding: 10px;',
' }',
'',
' .getter, .setter {',
' font-size: large;',
' }',
'',
' section > .variable:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .constructor:nth-child(2n), section > .enum:nth-child(2n) {',
' section > .variable:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .constructor:nth-child(2n) {',
' background-color: rgb(221, 250, 238);',
' }',
'',
' section > .variable:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .constructor:nth-child(2n + 1), section > .enum:nth-child(2n + 1) {',
' section > .variable:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .constructor:nth-child(2n + 1) {',
' background-color: rgb(244, 250, 221);',
' }',
'',
@@ -475,17 +522,12 @@ module.exports = function (outputFilePath) {
' content: "Usage";',
' }',
'',
' .usage fieldset {',
' min-width: initial;',
' overflow-x: auto;',
' }',
'',
' .usage pre {',
' margin: 0;',
' }',
'',
' .interface .variable, .interface .function, .interface .property,',
' .constructor .variable, .constructor .function, .constructor .property, .enum .member {',
' .interface .variable, .interface .function, .interface .getter, .interface .setter,',
' .constructor .variable, .constructor .function, .constructor .getter, .constructor .setter {',
' background-color: rgb(250, 241, 221);',
' }',
'',
@@ -509,10 +551,6 @@ module.exports = function (outputFilePath) {
' content: "private ";',
' }',
'',
' .protected > .name:before {',
' content: "protected ";',
' }',
'',
' .static > .name:before {',
' content: "static ";',
' }',
@@ -525,21 +563,9 @@ module.exports = function (outputFilePath) {
' content: "static private ";',
' }',
'',
' .abstract.protected > .name:before {',
' content: "abstract protected ";',
' }',
'',
' .protected.static > .name:before {',
' content: "static protected ";',
' }',
'',
' body:not(.show-private) .private, body:not(.show-private) .protected {',
' body:not(.show-private) .private {',
' display: none;',
' }',
'',
' .description .code {',
' margin-left: 30px;',
' }',
' ]]>',
' </style>',
' <script>',
@@ -549,43 +575,19 @@ module.exports = function (outputFilePath) {
' document.body.className = (event.target.checked ? "show-private" : "");',
' }, false);',
'',
' showPrivateIfNecessary();',
' }, false);',
'',
' function showPrivateIfNecessary() {',
' var jumpToElement = document.querySelector("[id=\\"" + location.hash.substr(1) + "\\"]");',
' if (jumpToElement !== null && jumpToElement.offsetHeight === 0) {',
' if (document.querySelector("[href=\\"" + location.hash + "\\"]").offsetHeight === 0) {',
' document.querySelector("#show-private").click()',
' jumpToElement.scrollIntoView();',
' }',
' }',
'',
' addEventListener("hashchange", showPrivateIfNecessary, false);',
' }, false);',
' ]]>',
' </script>',
' </head>',
' <body>',
' <nav class="namespaces">',
' <section class="namespaces">',
' <label><input type="checkbox" id="show-private" />Show private</label>',
' <h2>Namespaces</h2>'
].concat([].concatMany(namespaceNames.map(function (namespaceName) {
var namespace = namespaces[namespaceName];
return [
' <span class="namespace"><a href="#' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</a></span>',
' <ul class="namespace-elements">'
].concatMany(namespace.members.map(function (value) {
return (
' <li' +
((value.isPrivate === true) ? ' class="private"' : '') +
'><a href="#' + sanitize(value.fullName) + '">' + sanitize(value.name) + '</a></li>'
);
})).concat([
' </ul>',
''
]);
}))).concat([
' </nav>',
].concat(writeOverview(3)).concat([
' </section>',
' <div class="content">',
''
]).concatMany(namespaceNames.map(function (namespaceName) {
@@ -605,10 +607,6 @@ module.exports = function (outputFilePath) {
return value instanceof TypeScript.AST.Constructor;
});
var enums = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Enum;
});
var result = [
' <section>',
' <h1 id="' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</h1>',
@@ -620,7 +618,7 @@ module.exports = function (outputFilePath) {
' <section>',
' <h2>Variables</h2>'
]).concatMany(variables.map(function (value) {
return value.toHtml().map(indenter(5));
return writeVariable(value, 5);
})).concat([
' </section>',
''
@@ -632,7 +630,7 @@ module.exports = function (outputFilePath) {
' <section>',
' <h2>Free functions</h2>'
]).concatMany(functions.map(function (value) {
return value.toHtml().map(indenter(5));
return writeFunction(value, 5);
})).concat([
' </section>',
''
@@ -644,7 +642,7 @@ module.exports = function (outputFilePath) {
' <section>',
' <h2>Interfaces</h2>'
]).concatMany(interfaces.map(function (value) {
return value.toHtml().map(indenter(5));
return writeInterface(value, 5);
})).concat([
' </section>',
''
@@ -656,19 +654,7 @@ module.exports = function (outputFilePath) {
' <section>',
' <h2>Classes</h2>'
]).concatMany(constructors.map(function (value) {
return value.toHtml().map(indenter(5));
})).concat([
' </section>',
''
]);
}
if (enums.length > 0) {
result = result.concat([
' <section>',
' <h2>Enums</h2>'
]).concatMany(enums.map(function (value) {
return value.toHtml().map(indenter(5));
return writeConstructor(value, 5);
})).concat([
' </section>',
''
-4
View File
@@ -38,10 +38,6 @@ var Transform = function (transform, flush) {
callback();
}
catch (ex) {
if (!(ex instanceof Error)) {
ex = new Error(ex.message + "\nStack:\n" + ex.stack);
}
callback(ex);
}
};
+604 -886
View File
File diff suppressed because it is too large Load Diff
+23 -72
View File
@@ -50,21 +50,7 @@ module.exports = {
toplevel: null
});
root = UglifyJS.parse(
'(function (define) {\n' +
' var global = this;\n' +
' define(["exports"], function (libjass) {\n' +
'"$ORIG";\n' +
' });\n' +
'})((typeof define !== "undefined" && define.amd) ? define : function (/* ujs:unreferenced */ names, body) {\n' +
' body((typeof module !== "undefined") ? module.exports : (this.libjass = Object.create(null)));\n' +
'});'
).transform(new UglifyJS.TreeTransformer(function (node) {
if (node instanceof UglifyJS.AST_Directive && node.value === "$ORIG") {
return UglifyJS.MAP.splice(root.body);
}
}));
root = root.wrap_enclose(['(typeof module !== "undefined") ? module.exports : (this.libjass = {}):libjass', '(typeof global !== "undefined") ? global : this:global']);
root.figure_out_scope({ screw_ie8: true });
@@ -111,10 +97,6 @@ module.exports = {
if (this.parent(1).definitions.length === 1) {
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
}
if (["BorderStyle", "ClockEvent", "WorkerCommands", "WrappingStyle"].indexOf(node.name) === -1) {
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
}
}
else if (node instanceof UglifyJS.AST_SymbolDefun) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
@@ -148,36 +130,9 @@ module.exports = {
}
}));
// 5. Fixup anonymous functions to print a space after "function"
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 () { };
}
}));
// 6. Find the first license header
var licenseHeader = null;
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (licenseHeader !== null) {
return;
}
if (node.start) {
(node.start.comments_before || []).some(function (comment) {
if (comment.value.indexOf("Copyright") !== -1) {
licenseHeader = comment;
node.start.comments_before = [];
return true;
}
return false;
});
}
}));
root.start.comments_before = [licenseHeader];
root.start.nlb = true;
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: path.basename(sourceMapFile.path),
@@ -185,14 +140,15 @@ module.exports = {
}),
beautify: true,
comments: function (node, comment) {
// If this is the first license header, keep it.
if (comment === licenseHeader) {
return true;
}
// If this is a license header, remove it.
// If this is a license header
if (comment.value.indexOf("Copyright") !== -1) {
return false;
if (!firstLicenseHeaderFound) {
firstLicenseHeaderFound = true;
}
else {
// This isn't the first license header. Strip it.
return false;
}
}
// If this is a TypeScript reference comment, strip it.
@@ -200,10 +156,13 @@ module.exports = {
return false;
}
// UJS shifts multi-line comments eight spaces left, so shift each line except the first one eight spaces right.
/* Align multi-line comments correctly.
* TypeScript shifts them one space left, and UJS shifts them four spaces left,
* so shift each line except the first five spaces right.
*/
if (comment.type === "comment2") {
var lines = comment.value.split("\n");
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
comment.value = lines.join('\n');
}
@@ -341,19 +300,15 @@ module.exports = {
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
comments: function (node, comment) {
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
comments: {
test: function (comment) {
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
return true;
}
// Align the license header correctly.
var lines = comment.value.split("\n");
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^ +/g, " "); }));
comment.value = lines.join('\n');
return true;
return false;
}
return false;
},
screw_ie8: true
};
@@ -388,9 +343,5 @@ UglifyJS.AST_Symbol.prototype.unreferenced = function () {
return false;
}
if (this instanceof UglifyJS.AST_SymbolCatch) {
return false;
}
return originalSymbolUnreferenced.call(this);
};
+31
View File
@@ -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.
*/
body {
margin: 0;
}
.libjass-subs {
z-index: 2147483647;
}
.settings-form > fieldset {
display: inline-block;
}
+164
View File
@@ -0,0 +1,164 @@
/**
* 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.
*/
"use strict";
libjass.debugMode = (location.search === "?debug") || (location.search === "?verbose");
libjass.verboseMode = (location.search === "?verbose");
addEventListener("DOMContentLoaded", function () {
var debug = function () {
if (libjass.debugMode) {
console.log.apply(console, arguments);
}
}
var video = document.querySelector("#video");
var videoMetadataLoaded = false;
var ass = null;
var testVideoAndASSLoaded = function () {
if (videoMetadataLoaded === false || ass === null) {
return;
}
// Both video metadata and ASS are loaded
// Create a DefaultRenderer using the video element, the ASS object, and the font map
var renderer = new libjass.renderers.DefaultRenderer(video, ass, {
fontMap: libjass.renderers.RendererSettings.makeFontMapFromStyleElement(document.querySelector("#font-map"))
});
if (libjass.debugMode) {
// Export the renderer for debugging
window.renderer = renderer;
}
// Autoplay the video when the renderer is ready
renderer.addEventListener("ready", function () {
debug("All fonts have been preloaded. Beginning autoplay.");
video.play();
});
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
renderer.setEnabled(event.target.checked);
}, false);
// Run when the video/script resolution needs to be changed, either because the user clicked one of the resolution options,
// or because they exited full screen and their selection needs to be restored
var changeVideoSizeSelection = function (id) {
if (typeof id === "undefined") {
// Find which option is selected
[].slice.call(videoSizeSelector.querySelectorAll("input[name='video-size']")).some(function (option) {
if (option.checked) {
id = option.id;
return true;
}
return false;
});
}
if (id === "video-size-video-radio") {
// Resize to video resolution
video.style.width = video.videoWidth + "px";
video.style.height = video.videoHeight + "px";
renderer.resize(video.videoWidth, video.videoHeight);
}
else if (id === "video-size-script-radio") {
// Resize to script resolution
video.style.width = ass.properties.resolutionX + "px";
video.style.height = ass.properties.resolutionY + "px";
renderer.resize(ass.properties.resolutionX, ass.properties.resolutionY);
}
};
var videoSizeSelector = document.querySelector("#video-size-selector");
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
// When video exits full screen, restore the size according to the resolution option that's selected
renderer.addEventListener("fullScreenChange", function (newState) {
if (newState === false) {
changeVideoSizeSelection();
}
});
// Set the resolution to whatever's selected by default
changeVideoSizeSelection();
};
(function (onVideoMetadataLoaded) {
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
// Video metadata isn't available yet. Register an event handler for it.
video.addEventListener("loadedmetadata", onVideoMetadataLoaded, false);
}
else {
// Video metadata is already available.
onVideoMetadataLoaded();
}
})(function () {
debug("Video metadata loaded.");
videoMetadataLoaded = true;
// Prepare the "Video resolution" option label
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
// Test if everything is loaded
testVideoAndASSLoaded();
});
// Find the ASS or SRT track and load it
var track = document.querySelector("#video > track[data-format='ass'], #video > track[data-format='srt']");
var subsRequest = new XMLHttpRequest();
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
subsRequest.addEventListener("load", function () {
debug("Script received.");
if (libjass.debugMode) {
console.time("Parsing ASS took");
}
// Parse the response string into an ASS object
ass = libjass.ASS.fromString(subsRequest.responseText, libjass.Format[track.getAttribute("data-format").toUpperCase()]);
if (libjass.debugMode) {
console.timeEnd("Parsing ASS took");
// Export the ASS object for debugging
window.ass = ass;
}
// Prepare the "Script resolution" option label
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
// Test if everything is loaded
testVideoAndASSLoaded();
}, false);
subsRequest.send(null);
}, false);
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
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.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>&gt;2012 &gt;Streaman Animu</title>
<link rel="stylesheet" href="libjass.css" />
<link rel="stylesheet" href="index.css" />
<link rel="stylesheet" href="fonts.css" id="font-map" />
<!-- Unminified file -->
<script src="libjass.js" />
<!-- Minified file -->
<!--
<script src="libjass.min.js" />
-->
<script src="index.js" />
</head>
<body>
<h1>Foo</h1>
<h2>Bar</h2>
<video id="video" src="/video/gc-01.webm" controls="">
<track src="/video/gc-01.ass" kind="metadata" data-format="ass" />
</video>
<form class="settings-form">
<fieldset>
<legend>Video size</legend>
<div id="video-size-selector">
<input type="radio" name="video-size" id="video-size-video-radio" checked="checked" /><label for="video-size-video-radio">Video resolution <span id="video-resolution-label-width" />x<span id="video-resolution-label-height" /></label>
<input type="radio" name="video-size" id="video-size-script-radio" /><label for="video-size-script-radio">Script resolution <span id="script-resolution-label-width" />x<span id="script-resolution-label-height" /></label>
</div>
</fieldset>
<fieldset>
<legend>Subtitles</legend>
<input type="checkbox" id="enable-disable-subs" checked="checked" /><label for="enable-disable-subs">Subtitles</label>
</fieldset>
</form>
</body>
</html>
+3 -3
View File
@@ -81,11 +81,11 @@
}
/* Filter wrapper span */
.libjass-subs div[data-dialogue-id] > span {
.libjass-subs div[data-dialogue-id] {
-webkit-perspective-origin: center;
-webkit-perspective: 400px;
-webkit-perspective: 200px;
perspective-origin: center;
perspective: 400px;
perspective: 200px;
}
.libjass-font-measure {
+2 -2
View File
@@ -25,8 +25,8 @@
},
"dependencies": {
"gulp": "latest",
"mocha": "2.0.x",
"typescript": "1.3.0",
"mocha": "1.21.x",
"typescript": "1.0.1",
"uglify-js": "2.x >=2.4.14",
"vinyl": "latest"
}
+666 -684
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -130,9 +130,6 @@ module libjass.parts {
return this._value;
}
/**
* @return {string}
*/
toString(): string {
return "Text { value: " + this._value.replace(/\u00A0/g, "\\h") + " }";
}
+8 -94
View File
@@ -23,13 +23,10 @@
module libjass.renderers {
/**
* A mixin class that represents an event source.
*
* @template T
*/
export class EventSource<T> {
/**
* A map from event type to an array of all the listeners registered for that event type.
*
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<T, Function[]>;
/**
@@ -49,8 +46,6 @@ module libjass.renderers {
}
/**
* Calls all listeners registered for the given event type.
*
* @param {!T} type The type of event to dispatch
* @param {!Array.<*>} args Arguments for the listeners of the event
*/
@@ -64,20 +59,13 @@ module libjass.renderers {
}
}
/**
* The type of clock event.
*/
export enum ClockEvent {
Play,
Tick,
Pause,
Stop,
RateChange,
}
/**
* The clock interface. A clock is used by a renderer as a source of {@link libjass.renderers.ClockEvent}s.
*/
export interface Clock extends EventSource<ClockEvent> {
/**
* @type {number}
@@ -89,13 +77,6 @@ module libjass.renderers {
*/
enabled: boolean;
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
rate: number;
/**
* Enable the clock.
*
@@ -124,31 +105,25 @@ module libjass.renderers {
setEnabled(enabled: boolean): boolean;
// EventSource members
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void;
addEventListener: (type: ClockEvent, listener: Function) => void;
}
/**
* An implementation of {@link libjass.renderers.Clock} that allows user script to manually trigger play, pause and timeUpdate events.
* An implementation of libjass.renderers.Clock that allows user script to manually trigger play, pause and timeUpdate events.
*/
export class ManualClock implements Clock {
private _currentTime: number = -1;
private _enabled: boolean = true;
private _rate: number = 1;
/**
* Trigger a {@link libjass.renderers.ClockEvent.Play}
* Trigger a ClockEvent.Play
*/
play(): void {
this._dispatchEvent(ClockEvent.Play, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Tick}
* Trigger a ClockEvent.Tick
*
* @param {number} currentTime
*/
@@ -158,14 +133,14 @@ module libjass.renderers {
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Pause}
* Trigger a ClockEvent.Pause
*/
pause(): void {
this._dispatchEvent(ClockEvent.Pause, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Stop}
* Trigger a ClockEvent.Stop
*/
stop(): void {
this._dispatchEvent(ClockEvent.Stop, []);
@@ -187,15 +162,6 @@ module libjass.renderers {
return this._enabled;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._rate;
}
/**
* Enable the clock.
*
@@ -253,39 +219,13 @@ module libjass.renderers {
}
}
/**
* Sets the rate of the clock - how fast the clock ticks compared to real time.
*
* @param {number} rate The new rate of the clock.
*/
setRate(rate: number): void {
this._rate = rate;
this._dispatchEvent(ClockEvent.RateChange, []);
}
// EventSource members
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: ClockEvent, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(ManualClock, [EventSource]);
/**
* The state of the video.
*/
enum VideoState {
Playing = 0,
Paused = 1,
@@ -309,7 +249,6 @@ module libjass.renderers {
this._video.addEventListener("playing", () => this._onVideoPlaying(), false);
this._video.addEventListener("pause", () => this._onVideoPause(), false);
this._video.addEventListener("seeking", () => this._onVideoSeeking(), false);
this._video.addEventListener("ratechange", () => this._onVideoRateChange(), false);
}
// Clock members
@@ -328,15 +267,6 @@ module libjass.renderers {
return this._enabled;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._video.playbackRate;
}
/**
* Enable the clock.
*
@@ -493,10 +423,6 @@ module libjass.renderers {
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
}
private _onVideoRateChange(): void {
this._dispatchEvent(ClockEvent.RateChange, []);
}
private _startTicking(): void {
if (this._nextAnimationFrameRequestId === null) {
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
@@ -511,20 +437,8 @@ module libjass.renderers {
}
// EventSource members
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: ClockEvent, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(VideoClock, [EventSource]);
+5 -22
View File
@@ -21,19 +21,8 @@
///<reference path="libjass.ts" />
interface Document {
/**
* @type {Element}
*/
fullscreenElement: Element;
/**
* @type {Element}
*/
mozFullScreenElement: Element;
/**
* @type {Element}
*/
webkitFullscreenElement: Element;
}
@@ -43,13 +32,13 @@ module libjass.renderers {
*
* @param {!HTMLVideoElement} video
* @param {!libjass.ASS} ass
* @param {libjass.renderers.RendererSettings} settings
* @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);
constructor(private _video: HTMLVideoElement, ass: ASS, settings: RendererSettings) {
super(ass, new VideoClock(_video), settings, document.createElement("div"));
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
@@ -57,16 +46,13 @@ module libjass.renderers {
/**
* @deprecated
*
* @param {number} width
* @param {number} height
*/
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);
}
protected _ready(): void {
_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);
@@ -76,10 +62,7 @@ module libjass.renderers {
super._ready();
}
/**
* @param {!Element} fullScreenElement
*/
private _onFullScreenChange(fullScreenElement: Element): void {
private _onFullScreenChange(fullScreenElement: Element) {
if (fullScreenElement === undefined) {
fullScreenElement = document.msFullscreenElement;
}
+13 -58
View File
@@ -26,7 +26,7 @@ module libjass.renderers {
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {libjass.renderers.RendererSettings} settings
* @param {!libjass.renderers.RendererSettings} settings
*/
export class NullRenderer {
private static _lastRendererId = -1;
@@ -35,7 +35,7 @@ module libjass.renderers {
private _settings: RendererSettings;
constructor(private _ass: ASS, private _clock: Clock, settings?: RendererSettings) {
constructor(private _ass: ASS, private _clock: Clock, settings: RendererSettings) {
this._id = ++NullRenderer._lastRendererId;
this._settings = RendererSettings.from(settings);
@@ -44,7 +44,6 @@ module libjass.renderers {
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
this._clock.addEventListener(ClockEvent.RateChange, () => this._onClockRateChange());
}
/**
@@ -136,7 +135,7 @@ module libjass.renderers {
/**
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
*/
protected _onClockPlay(): void {
_onClockPlay(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockPlay");
}
@@ -145,7 +144,7 @@ module libjass.renderers {
/**
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
*/
protected _onClockTick(): void {
_onClockTick(): void {
var currentTime = this._clock.currentTime;
if (libjass.verboseMode) {
@@ -171,7 +170,7 @@ module libjass.renderers {
/**
* Runs when the clock is paused.
*/
protected _onClockPause(): void {
_onClockPause(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockPause");
}
@@ -180,20 +179,11 @@ module libjass.renderers {
/**
* Runs when the clock is disabled.
*/
protected _onClockStop(): void {
_onClockStop(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockStop");
}
}
/**
* Runs when the clock changes its rate.
*/
protected _onClockRateChange(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockRateChange");
}
}
}
/**
@@ -203,8 +193,7 @@ module libjass.renderers {
/**
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
*
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.RendererSettings.makeFontMapFromStyleElement}
* convenience method to create a font map.
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the RendererSettings.makeFontMapFromStyleElement() convenience method to create a font map.
*
* @type {!Map.<string, !Array.<string>>}
*/
@@ -229,25 +218,7 @@ module libjass.renderers {
preciseOutlines: boolean;
/**
* Outlines and blur are implemented using SVG filters by default. When false, they will be rendered using alternative means.
*
* IE 11 and below do not support SVG filters on HTML elements so this should be set to false there.
*
* Defaults to true.
*
* @type {boolean}
*/
enableSvg: 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.
*
* For example:
*
* @font-face {
* font-family: "Helvetica";
* src: url("/fonts/helvetica.ttf");
* }
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules.
*
* @param {!LinkStyle} linkStyle
* @return {!Map.<string, !Array.<string>>}
@@ -285,39 +256,23 @@ module libjass.renderers {
}
/**
* Converts an arbitrary object into a {@link libjass.renderers.RendererSettings} object.
* Converts an arbitrary object into a RendererSettings object.
*
* @param {*} object
* @param {!*} object
* @return {!libjass.renderers.RendererSettings}
*/
static from(object?: any): RendererSettings {
if (object === undefined || object === null) {
object = {};
}
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines, object.enableSvg);
static from(object: any): RendererSettings {
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines);
}
/**
* @param {Map.<string, !Array.<string>>=null} fontMap
* @param {number=5} preRenderTime
* @param {boolean=false} preciseOutlines
* @param {boolean=true} enableSvg
* @return {!libjass.renderers.RendererSettings}
*/
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false, enableSvg: boolean = true): RendererSettings {
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false): RendererSettings {
var result = new RendererSettings();
result.fontMap = fontMap;
result.preRenderTime = preRenderTime;
result.preciseOutlines = preciseOutlines;
result.enableSvg = enableSvg;
return result;
}
/**
* @param {string} str
* @return {string}
*/
private static _stripQuotes(str: string): string {
return str.match(/^["']?(.*?)["']?$/)[1];
}
+329 -380
View File
@@ -21,29 +21,10 @@
///<reference path="libjass.ts" />
interface CSSStyleDeclaration {
/**
* @type {string}
*/
webkitAnimation: string;
/**
* @type {string}
*/
webkitAnimationDelay: string;
/**
* @type {string}
*/
webkitFilter: string;
/**
* @type {string}
*/
webkitTransform: string;
/**
* @type {string}
*/
webkitTransformOrigin: string;
}
@@ -53,8 +34,8 @@ module libjass.renderers {
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {!HTMLDivElement} libjassSubsWrapper Subtitles will be rendered to this <div>
* @param {!libjass.renderers.RendererSettings} settings
* @param {!HTMLDivElement} libjassSubsWrapper Subtitles will be rendered to this <div>
*/
export class WebRenderer extends NullRenderer implements EventSource<string> {
private _subsWrapper: HTMLDivElement;
@@ -70,17 +51,8 @@ module libjass.renderers {
private _scaleX: number;
private _scaleY: number;
constructor(ass: ASS, clock: Clock, private _libjassSubsWrapper: HTMLDivElement, settings?: RendererSettings) {
super(ass, clock, (() => {
if (!(_libjassSubsWrapper instanceof HTMLDivElement)) {
var temp = settings;
settings = <any>_libjassSubsWrapper;
_libjassSubsWrapper = <any>temp;
console.warn("WebRenderer's constructor now takes libjassSubsWrapper as the third parameter and settings as the fourth parameter. Please update the caller.");
}
return settings;
})());
constructor(ass: ASS, clock: Clock, settings: RendererSettings, private _libjassSubsWrapper: HTMLDivElement) {
super(ass, clock, settings);
this._libjassSubsWrapper.classList.add("libjass-wrapper");
@@ -104,47 +76,65 @@ module libjass.renderers {
this._svgDefsElement = <SVGDefsElement>document.createElementNS("http://www.w3.org/2000/svg", "defs");
svgElement.appendChild(this._svgDefsElement);
// Preload fonts
if (this.settings.fontMap === null) {
setTimeout(() => this._ready(), 0);
}
else {
// Preload fonts
var urlsToPreload = new Set<string>();
if (this.settings.fontMap !== null) {
var urlsToPreload = new Set<string>();
this.settings.fontMap.forEach(srcs => {
srcs.forEach(src => urlsToPreload.add(src));
});
}
if (libjass.debugMode) {
console.log("Preloading " + urlsToPreload.size + " fonts...");
}
var urlsLeftToPreload = urlsToPreload.size;
var xhrPromises: Promise<void>[] = [];
urlsToPreload.forEach(url => {
xhrPromises.push(new Promise<void>((resolve, reject) => {
if (libjass.debugMode) {
console.log("Preloading fonts...");
}
urlsToPreload.forEach(url => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.addEventListener("load", () => {
if (libjass.debugMode) {
console.log("Preloaded " + url + ".");
}
resolve(null);
});
xhr.open("GET", url, true);
xhr.send();
}));
});
--urlsLeftToPreload;
if (libjass.debugMode) {
console.log(urlsLeftToPreload + " fonts left to preload.");
}
if (urlsLeftToPreload === 0) {
if (libjass.debugMode) {
console.log("All fonts have been preloaded.");
}
this._ready();
}
}, false);
xhr.send(null);
});
Promise.all(xhrPromises).then(() => {
if (libjass.debugMode) {
console.log("All fonts have been preloaded.");
console.log(urlsLeftToPreload + " fonts left to preload.");
}
this._ready();
});
if (urlsLeftToPreload === 0) {
setTimeout(() => {
if (libjass.debugMode) {
console.log("All fonts have been preloaded.");
}
this._ready();
}, 0);
}
}
}
/**
* @type {!HTMLDivElement}
*/
get libjassSubsWrapper(): HTMLDivElement {
return this._libjassSubsWrapper;
}
@@ -189,7 +179,7 @@ module libjass.renderers {
}
/**
* The magic happens here. The subtitle div is rendered and stored. Call {@link libjass.renderers.WebRenderer.draw} to get a clone of the div to display.
* The magic happens here. The subtitle div is rendered and stored. Call draw() to get a clone of the div to display.
*
* @param {!libjass.Dialogue} dialogue
*/
@@ -209,24 +199,38 @@ module libjass.renderers {
var currentSpan: HTMLSpanElement = null;
var currentSpanStyles = new SpanStyles(this, dialogue, this._scaleX, this._scaleY, this.settings, this._fontSizeElement, this._svgDefsElement);
var nextSpanParent: HTMLElement = sub;
var rotationX = 0;
var rotationY = 0;
var rotationZ = 0;
var previousAddNewLine = false; // If two or more \N's are encountered in sequence, then all but the first will be created using currentSpanStyles.makeNewLine() instead
var startNewSpan = (addNewLine: boolean): void => {
if (currentSpan !== null && currentSpan.textContent !== "") {
sub.appendChild(currentSpanStyles.setStylesOnSpan(currentSpan));
var newSpan = currentSpanStyles.setStylesOnSpan(currentSpan, rotationX, rotationY, rotationZ);
if (currentSpanStyles.rotationX !== rotationX || currentSpanStyles.rotationY !== rotationY || currentSpanStyles.rotationZ !== rotationZ) {
nextSpanParent.appendChild(newSpan);
nextSpanParent = newSpan;
rotationX = currentSpanStyles.rotationX;
rotationY = currentSpanStyles.rotationY;
rotationZ = currentSpanStyles.rotationZ;
}
else {
nextSpanParent.appendChild(newSpan.firstElementChild);
}
}
if (addNewLine) {
if (previousAddNewLine) {
sub.appendChild(currentSpanStyles.makeNewLine());
}
else {
sub.appendChild(document.createElement("br"));
}
nextSpanParent = sub;
nextSpanParent.appendChild(document.createElement("br"));
rotationX = 0;
rotationY = 0;
rotationZ = 0;
}
currentSpan = document.createElement("span");
previousAddNewLine = addNewLine;
};
startNewSpan(false);
@@ -401,51 +405,51 @@ module libjass.renderers {
var movePart = <parts.Move>part;
sub.style.position = "absolute";
animationCollection.add("linear", [new Keyframe(0, new Map([
["left", (this._scaleX * movePart.x1).toFixed(3) + "px"],
["top", (this._scaleY * movePart.y1).toFixed(3) + "px"],
])), new Keyframe(movePart.t1, new Map([
["left", (this._scaleX * movePart.x1).toFixed(3) + "px"],
["top", (this._scaleY * movePart.y1).toFixed(3) + "px"],
])), new Keyframe(movePart.t2, new Map([
["left", (this._scaleX * movePart.x2).toFixed(3) + "px"],
["top", (this._scaleY * movePart.y2).toFixed(3) + "px"],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["left", (this._scaleX * movePart.x2).toFixed(3) + "px"],
["top", (this._scaleY * movePart.y2).toFixed(3) + "px"],
]))]);
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
.set("left", (this._scaleX * movePart.x1).toFixed(3) + "px")
.set("top", (this._scaleY * movePart.y1).toFixed(3) + "px")
), new Keyframe(movePart.t1, new Map<string, string>()
.set("left", (this._scaleX * movePart.x1).toFixed(3) + "px")
.set("top", (this._scaleY * movePart.y1).toFixed(3) + "px")
), new Keyframe(movePart.t2, new Map<string, string>()
.set("left", (this._scaleX * movePart.x2).toFixed(3) + "px")
.set("top", (this._scaleY * movePart.y2).toFixed(3) + "px")
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
.set("left", (this._scaleX * movePart.x2).toFixed(3) + "px")
.set("top", (this._scaleY * movePart.y2).toFixed(3) + "px")
)]);
}
else if (part instanceof parts.Fade) {
var fadePart = <parts.Fade>part;
animationCollection.add("linear", [new Keyframe(0, new Map([
["opacity", "0"],
])), new Keyframe(fadePart.start, new Map([
["opacity", "1"],
])), new Keyframe(dialogue.end - dialogue.start - fadePart.end, new Map([
["opacity", "1"],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["opacity", "0"],
]))]);
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
.set("opacity", "0")
), new Keyframe(fadePart.start, new Map<string, string>()
.set("opacity", "1")
), new Keyframe(dialogue.end - dialogue.start - fadePart.end, new Map<string, string>()
.set("opacity", "1")
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
.set("opacity", "0")
)]);
}
else if (part instanceof parts.ComplexFade) {
var complexFadePart = <parts.ComplexFade>part;
animationCollection.add("linear", [new Keyframe(0, new Map([
["opacity", complexFadePart.a1.toFixed(3)],
])), new Keyframe(complexFadePart.t1, new Map([
["opacity", complexFadePart.a1.toFixed(3)],
])), new Keyframe(complexFadePart.t2, new Map([
["opacity", complexFadePart.a2.toFixed(3)],
])), new Keyframe(complexFadePart.t3, new Map([
["opacity", complexFadePart.a2.toFixed(3)],
])), new Keyframe(complexFadePart.t4, new Map([
["opacity", complexFadePart.a3.toFixed(3)],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["opacity", complexFadePart.a3.toFixed(3)],
]))]);
animationCollection.add("linear", [new Keyframe(0, new Map<string, string>()
.set("opacity", complexFadePart.a1.toFixed(3))
), new Keyframe(complexFadePart.t1, new Map<string, string>()
.set("opacity", complexFadePart.a1.toFixed(3))
), new Keyframe(complexFadePart.t2, new Map<string, string>()
.set("opacity", complexFadePart.a2.toFixed(3))
), new Keyframe(complexFadePart.t3, new Map<string, string>()
.set("opacity", complexFadePart.a2.toFixed(3))
), new Keyframe(complexFadePart.t4, new Map<string, string>()
.set("opacity", complexFadePart.a3.toFixed(3))
), new Keyframe(dialogue.end - dialogue.start, new Map<string, string>()
.set("opacity", complexFadePart.a3.toFixed(3))
)]);
}
else if (part instanceof parts.DrawingMode) {
@@ -537,7 +541,7 @@ module libjass.renderers {
}
/**
* Returns the subtitle div for display. The {@link libjass.renderers.WebRenderer.currentTime} is used to shift the animations appropriately, so that at the time the
* Returns the subtitle div for display. The currentTime is used to shift the animations appropriately, so that at the time the
* div is inserted into the DOM and the animations begin, they are in sync with the clock time.
*
* @param {!libjass.Dialogue} dialogue
@@ -568,7 +572,7 @@ module libjass.renderers {
var result = <HTMLDivElement>preRenderedSub.sub.cloneNode(true);
var animationDelay = preRenderedSub.animationDelays.map(delay => ((delay + dialogue.start - this.clock.currentTime) / this.clock.rate).toFixed(3) + "s").join(", ");
var animationDelay = preRenderedSub.animationDelays.map(delay => (delay + dialogue.start - this.clock.currentTime).toFixed(3) + "s").join(", ");
result.style.webkitAnimationDelay = animationDelay;
result.style.animationDelay = animationDelay;
@@ -617,7 +621,7 @@ module libjass.renderers {
this._currentSubs.set(dialogue, result);
}
protected _onClockPlay(): void {
_onClockPlay(): void {
super._onClockPlay();
this._removeAllSubs();
@@ -627,7 +631,7 @@ module libjass.renderers {
this._subsWrapper.classList.remove("paused");
}
protected _onClockTick(): void {
_onClockTick(): void {
// Remove dialogues that should be removed before adding new ones via super._onClockTick()
var currentTime = this.clock.currentTime;
@@ -642,42 +646,22 @@ module libjass.renderers {
super._onClockTick();
}
protected _onClockPause(): void {
_onClockPause(): void {
super._onClockPause();
this._subsWrapper.classList.add("paused");
}
protected _onClockStop(): void {
_onClockStop(): void {
super._onClockStop();
this._subsWrapper.style.display = "none";
}
protected _onClockRateChange(): void {
super._onClockRateChange();
// Any dialogues which have been pre-rendered will need to be pre-rendered again.
this._preRenderedSubs.clear();
if (this._animationStyleElement !== null) {
while (this._animationStyleElement.firstChild !== null) {
this._animationStyleElement.removeChild(this._animationStyleElement.firstChild);
}
}
while (this._svgDefsElement.firstChild !== null) {
this._svgDefsElement.removeChild(this._svgDefsElement.firstChild);
}
}
protected _ready(): void {
_ready(): void {
this._dispatchEvent("ready", []);
}
/**
* @param {!HTMLDivElement} sub
*/
private _removeSub(sub: HTMLDivElement): void {
sub.parentNode.removeChild(sub);
}
@@ -695,24 +679,127 @@ module libjass.renderers {
];
// EventSource members
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<string, Function[]> = new Map<string, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: string, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: string, args: Object[]) => void;
}
mixin(WebRenderer, [EventSource]);
/**
* This class represents a single keyframe. It has a list of CSS properties (names and values) associated with a point in time. Multiple keyframes make up an animation.
*
* @param {number} time
* @param {!Object.<string, string>} properties
*/
class Keyframe {
constructor(private _time: number, private _properties: Map<string, string>) { }
/**
* @type {number}
*/
get time(): number {
return this._time;
}
/**
* @type {!Object.<string, string>}
*/
get properties(): Map<string, string> {
return this._properties;
}
}
/**
* This class represents a collection of animations. Each animation contains one or more keyframes.
* The collection can then be converted to a CSS3 representation.
*
* @param {!libjass.renderers.NullRenderer} renderer The renderer that this collection is associated with
* @param {!libjass.Dialogue} dialogue The Dialogue that this collection is associated with
*/
class AnimationCollection {
private _id: string;
private _start: number;
private _end: number;
private _cssText: string = "";
private _animationStyle: string = "";
private _animationDelays: number[] = [];
private _numAnimations: number = 0;
constructor(renderer: NullRenderer, dialogue: Dialogue) {
this._id = renderer.id + "-" + dialogue.id;
this._start = dialogue.start;
this._end = dialogue.end;
}
/**
* 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.
*
* @type {string}
*/
get animationStyle(): string {
return this._animationStyle;
}
/**
* This array should be used to set the "animation-delay" CSS property of the target element.
*/
get animationDelays(): number[] {
return this._animationDelays;
}
/**
* Add an animation to this collection. The given keyframes together make one animation.
*
* @param {string} timingFunction One of the acceptable values for the "animation-timing-function" CSS property
* @param {Array.<!{time: number, properties: !Object.<string, string>}>} keyframes
*/
add(timingFunction: string, keyframes: Keyframe[]) {
var startTime: number = null;
var endTime: number = null;
var ruleCssText = "";
keyframes.forEach(keyframe => {
if (startTime === null) {
startTime = keyframe.time;
}
endTime = keyframe.time;
ruleCssText += "\t" + (100 * keyframe.time / (this._end - this._start)).toFixed(3) + "% {\n";
keyframe.properties.forEach((value, name) => {
ruleCssText += "\t\t" + name + ": " + value + ";\n";
});
ruleCssText += "\t}\n";
});
var animationName = "dialogue-" + this._id + "-" + this._numAnimations++;
this._cssText +=
"@-webkit-keyframes " + animationName + " {\n" + ruleCssText + "}\n\n" +
"@keyframes " + animationName + " {\n" + ruleCssText + "}\n\n";
if (this._animationStyle !== "") {
this._animationStyle += ",";
}
this._animationStyle += animationName + " " + (endTime - startTime).toFixed(3) + "s " + timingFunction;
this._animationDelays.push(startTime);
}
}
/**
* This class represents the style attribute of a span.
* As a Dialogue's div is rendered, individual parts are added to span's, and this class is used to maintain the style attribute of those.
@@ -808,8 +895,8 @@ module libjass.renderers {
this.letterSpacing = newStyle.letterSpacing;
this._rotationX = null;
this._rotationY = null;
this._rotationX = 0;
this._rotationY = 0;
this._rotationZ = newStyle.rotationZ;
this._skewX = null;
@@ -835,7 +922,7 @@ module libjass.renderers {
* @param {!HTMLSpanElement} span
* @return {!HTMLSpanElement} The resulting <span> with the CSS styles applied. This may be a wrapper around the input <span> if the styles were applied using SVG filters.
*/
setStylesOnSpan(span: HTMLSpanElement): HTMLSpanElement {
setStylesOnSpan(span: HTMLSpanElement, previousRotationX: number, previousRotationY: number, previousRotationZ: number): HTMLSpanElement {
var isTextOnlySpan = span.childNodes[0] instanceof Text;
var fontStyleOrWeight = "";
@@ -850,7 +937,7 @@ module libjass.renderers {
}
var fontSize: string;
if (isTextOnlySpan) {
fontSize = (this._scaleY * SpanStyles._getFontSize(this._fontName, this._fontSize * this._fontScaleY, this._fontSizeElement)).toFixed(3);
fontSize = (this._scaleY * SpanStyles._getFontSize(this._fontName, this._fontSize * this._fontScaleX, this._fontSizeElement)).toFixed(3);
}
else {
fontSize = (this._scaleY * SpanStyles._getFontSize(this._fontName, this._fontSize, this._fontSizeElement)).toFixed(3);
@@ -881,15 +968,6 @@ module libjass.renderers {
transform += "scaleY(" + this._fontScaleY + ") ";
}
}
if (this._rotationY !== null) {
transform += "rotateY(" + this._rotationY + "deg) ";
}
if (this._rotationX !== null) {
transform += "rotateX(" + this._rotationX + "deg) ";
}
if (this._rotationZ !== 0) {
transform += "rotateZ(" + (-1 * this._rotationZ) + "deg) ";
}
if (this._skewX !== null || this._skewY !== null) {
var skewX = SpanStyles._valueOrDefault(this._skewX, 0);
var skewY = SpanStyles._valueOrDefault(this._skewY, 0);
@@ -913,106 +991,73 @@ module libjass.renderers {
var outlineWidth = this._scaleX * this._outlineWidth;
var outlineHeight = this._scaleY * this._outlineHeight;
var filterId = "svg-filter-" + this._id + "-" + this._nextFilterId++;
var outlineFilter = '';
var blurFilter = '';
if (outlineWidth > 0 || outlineHeight > 0) {
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
*/
if (this._settings.enableSvg) {
var filterId = "svg-filter-" + this._id + "-" + this._nextFilterId++;
var mergeOutlinesFilter = '';
if (outlineWidth > 0 || outlineHeight > 0) {
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
*/
var outlineNumber = 0;
var mergeOutlinesFilter = '';
var increment = (!this._settings.preciseOutlines && this._gaussianBlur > 0) ? this._gaussianBlur : 1;
var outlineNumber = 0;
var increment = (!this._settings.preciseOutlines && this._gaussianBlur > 0) ? this._gaussianBlur : 1;
((addOutline: (x: number, y: number) => void) => {
if (outlineWidth <= outlineHeight) {
if (outlineWidth > 0) {
for (var x = 0; x <= outlineWidth; x += increment) {
addOutline(x, outlineHeight / outlineWidth * Math.sqrt(outlineWidth * outlineWidth - x * x));
}
if (x !== outlineWidth + increment) {
addOutline(outlineWidth, 0);
}
((addOutline: (x: number, y: number) => void) => {
if (outlineWidth <= outlineHeight) {
if (outlineWidth > 0) {
for (var x = 0; x <= outlineWidth; x += increment) {
addOutline(x, outlineHeight / outlineWidth * Math.sqrt(outlineWidth * outlineWidth - x * x));
}
else {
if (x !== outlineWidth + increment) {
addOutline(outlineWidth, 0);
}
}
else {
addOutline(0, outlineHeight);
}
}
else {
if (outlineHeight > 0) {
for (var y = 0; y <= outlineHeight; y += increment) {
addOutline(outlineWidth / outlineHeight * Math.sqrt(outlineHeight * outlineHeight - y * y), y);
}
if (y !== outlineHeight + increment) {
addOutline(0, outlineHeight);
}
}
else {
if (outlineHeight > 0) {
for (var y = 0; y <= outlineHeight; y += increment) {
addOutline(outlineWidth / outlineHeight * Math.sqrt(outlineHeight * outlineHeight - y * y), y);
}
if (y !== outlineHeight + increment) {
addOutline(0, outlineHeight);
}
}
else {
addOutline(outlineWidth, 0);
}
addOutline(outlineWidth, 0);
}
})((x: number, y: number): void => {
outlineFilter +=
'\t<feMorphology in="SourceAlpha" operator="dilate" radius="' + x.toFixed(3) + ' ' + y.toFixed(3) + '" result="outline' + outlineNumber + '" />\n';
mergeOutlinesFilter +=
'\t\t<feMergeNode in="outline' + outlineNumber + '" />\n';
outlineNumber++;
});
}
})((x: number, y: number): void => {
outlineFilter +=
'\t<feMerge result="outline">\n' +
mergeOutlinesFilter +
'\t</feMerge>\n' +
'\t<feFlood flood-color="' + outlineColor.toString() + '" />' +
'\t<feComposite operator="in" in2="outline" />';
}
'\t<feMorphology in="SourceAlpha" operator="dilate" radius="' + x.toFixed(3) + ' ' + y.toFixed(3) + '" result="outline' + outlineNumber + '" />\n';
if (this._gaussianBlur > 0) {
blurFilter +=
'\t<feGaussianBlur stdDeviation="' + this._gaussianBlur + '" />\n';
}
for (var i = 0; i < this._blur; i++) {
blurFilter +=
'\t<feConvolveMatrix kernelMatrix="1 2 1 2 4 2 1 2 1" edgeMode="none" />\n';
}
mergeOutlinesFilter +=
'\t\t<feMergeNode in="outline' + outlineNumber + '" />\n';
outlineNumber++;
});
outlineFilter +=
'\t<feMerge result="outline">\n' +
mergeOutlinesFilter +
'\t</feMerge>\n' +
'\t<feFlood flood-color="' + outlineColor.toString() + '" />' +
'\t<feComposite operator="in" in2="outline" />';
}
else {
if (outlineWidth > 0 || outlineHeight > 0) {
var outlineCssString = "";
((addOutline: (x: number, y: number) => void) => {
for (var x = 0; x <= outlineWidth; x++) {
var maxY = (outlineWidth === 0) ? outlineHeight : outlineHeight * Math.sqrt(1 - ((x * x) / (outlineWidth * outlineWidth)));
for (var y = 0; y <= maxY; y++) {
addOutline(x, y);
if (x !== 0) {
addOutline(-x, y);
}
if (y !== 0) {
addOutline(x, -y);
}
if (x !== 0 && y !== 0) {
addOutline(-x, -y);
}
}
}
})((x: number, y: number): void => {
outlineCssString += ", " + outlineColor.toString() + " " + x + "px " + y + "px " + this._gaussianBlur.toFixed(3) + "px";
});
span.style.textShadow = outlineCssString.substr(", ".length);
}
var blurFilter = '';
if (this._gaussianBlur > 0) {
blurFilter +=
'\t<feGaussianBlur stdDeviation="' + this._gaussianBlur + '" />\n';
}
for (var i = 0; i < this._blur; i++) {
blurFilter +=
'\t<feConvolveMatrix kernelMatrix="1 2 1 2 4 2 1 2 1" edgeMode="none" />\n';
}
var filterWrapperSpan = document.createElement("span");
@@ -1039,30 +1084,47 @@ module libjass.renderers {
if (this._shadowDepthX !== 0 || this._shadowDepthY !== 0) {
var shadowColor = this._shadowColor.withAlpha(this._shadowAlpha);
var shadowCssString = shadowColor.toString() + " " + (this._shadowDepthX * this._scaleX / this._fontScaleX).toFixed(3) + "px " + (this._shadowDepthY * this._scaleY / this._fontScaleY).toFixed(3) + "px 0px";
if (span.style.textShadow === "") {
span.style.textShadow = shadowCssString;
}
else {
span.style.textShadow += ", " + shadowCssString;
}
span.style.textShadow = shadowColor.toString() + " " + (this._shadowDepthX * this._scaleX / this._fontScaleX).toFixed(3) + "px " + (this._shadowDepthY * this._scaleY / this._fontScaleY).toFixed(3) + "px 0px";
}
if (this._rotationX !== 0 || this._rotationY !== 0) {
if (this._rotationZ !== 0) {
// Perspective needs to be set on a "transformable element"
filterWrapperSpan.style.display = "inline-block";
}
return filterWrapperSpan;
}
var rotationWrapperSpan = document.createElement("span");
rotationWrapperSpan.appendChild(filterWrapperSpan);
/**
* @return {!HTMLBRElement}
*/
makeNewLine(): HTMLBRElement {
var result = document.createElement("br");
result.style.lineHeight = (this._scaleY * this._fontSize).toFixed(3) + "px";
return result;
var rotationTransform = "";
if (this._rotationX !== previousRotationX || this._rotationY !== previousRotationY || this._rotationZ !== previousRotationZ) {
if (previousRotationZ !== 0) {
rotationTransform += "rotateZ(" + (previousRotationZ) + "deg) ";
}
if (previousRotationX !== 0) {
rotationTransform += "rotateX(" + (-previousRotationX) + "deg) ";
}
if (previousRotationY !== 0) {
rotationTransform += "rotateY(" + (-previousRotationY) + "deg) ";
}
if (this._rotationY !== 0) {
rotationTransform += "rotateY(" + this._rotationY + "deg) ";
}
if (this._rotationX !== 0) {
rotationTransform += "rotateX(" + this._rotationX + "deg) ";
}
if (this._rotationZ !== 0) {
rotationTransform += "rotateZ(" + (-this._rotationZ) + "deg) ";
}
}
if (rotationTransform !== "") {
rotationWrapperSpan.style.webkitTransform = rotationTransform;
rotationWrapperSpan.style.webkitTransformOrigin = "50% 50%";
rotationWrapperSpan.style.transform = rotationTransform;
rotationWrapperSpan.style.transformOrigin = "50% 50%";
rotationWrapperSpan.style.display = "inline-block";
}
return rotationWrapperSpan;
}
/**
@@ -1200,13 +1262,21 @@ module libjass.renderers {
this._letterSpacing = SpanStyles._valueOrDefault(value, this._defaultStyle.letterSpacing);
}
get rotationX() {
return this._rotationX;
}
/**
* Sets the X-axis rotation property.
*
* @type {?number}
*/
set rotationX(value: number) {
this._rotationX = value;
this._rotationX = SpanStyles._valueOrDefault(value, 0);
}
get rotationY() {
return this._rotationY;
}
/**
@@ -1215,7 +1285,11 @@ module libjass.renderers {
* @type {?number}
*/
set rotationY(value: number) {
this._rotationY = value;
this._rotationY = SpanStyles._valueOrDefault(value, 0);
}
get rotationZ() {
return this._rotationZ;
}
/**
@@ -1337,12 +1411,6 @@ module libjass.renderers {
private static _valueOrDefault = <T>(newValue: T, defaultValue: T): T => ((newValue !== null) ? newValue : defaultValue);
/**
* @param {string} fontFamily
* @param {number} lineHeight
* @param {!HTMLDivElement} fontSizeElement
* @return {number}
*/
private static _getFontSize(fontFamily: string, lineHeight: number, fontSizeElement: HTMLDivElement): number {
var existingFontSizeMap = SpanStyles._fontSizeCache.get(fontFamily);
if (existingFontSizeMap === undefined) {
@@ -1360,125 +1428,6 @@ module libjass.renderers {
}
}
/**
* This class represents a collection of animations. Each animation contains one or more keyframes.
* The collection can then be converted to a CSS3 representation.
*
* @param {!libjass.renderers.NullRenderer} renderer The renderer that this collection is associated with
* @param {!libjass.Dialogue} dialogue The Dialogue that this collection is associated with
*/
class AnimationCollection {
private _id: string;
private _start: number;
private _end: number;
private _rate: number;
private _cssText: string = "";
private _animationStyle: string = "";
private _animationDelays: number[] = [];
private _numAnimations: number = 0;
constructor(renderer: NullRenderer, dialogue: Dialogue) {
this._id = renderer.id + "-" + dialogue.id;
this._start = dialogue.start;
this._end = dialogue.end;
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.
*
* @type {string}
*/
get animationStyle(): string {
return this._animationStyle;
}
/**
* This array should be used to set the "animation-delay" CSS property of the target element.
*
* @type {!Array.<number>}
*/
get animationDelays(): number[] {
return this._animationDelays;
}
/**
* Add an animation to this collection. The given keyframes together make one animation.
*
* @param {string} timingFunction One of the acceptable values for the "animation-timing-function" CSS property
* @param {Array.<!{time: number, properties: !Object.<string, string>}>} keyframes
*/
add(timingFunction: string, keyframes: Keyframe[]): void {
var startTime: number = null;
var endTime: number = null;
var ruleCssText = "";
keyframes.forEach(keyframe => {
if (startTime === null) {
startTime = keyframe.time;
}
endTime = keyframe.time;
ruleCssText += "\t" + (100 * keyframe.time / (this._end - this._start)).toFixed(3) + "% {\n";
keyframe.properties.forEach((value, name) => {
ruleCssText += "\t\t" + name + ": " + value + ";\n";
});
ruleCssText += "\t}\n";
});
var animationName = "dialogue-" + this._id + "-" + this._numAnimations++;
this._cssText +=
"@-webkit-keyframes " + animationName + " {\n" + ruleCssText + "}\n\n" +
"@keyframes " + animationName + " {\n" + ruleCssText + "}\n\n";
if (this._animationStyle !== "") {
this._animationStyle += ",";
}
this._animationStyle += animationName + " " + ((endTime - startTime) / this._rate).toFixed(3) + "s " + timingFunction;
this._animationDelays.push(startTime);
}
}
/**
* This class represents a single keyframe. It has a list of CSS properties (names and values) associated with a point in time. Multiple keyframes make up an animation.
*
* @param {number} time
* @param {!Object.<string, string>} properties
*/
class Keyframe {
constructor(private _time: number, private _properties: Map<string, string>) { }
/**
* @type {number}
*/
get time(): number {
return this._time;
}
/**
* @type {!Object.<string, string>}
*/
get properties(): Map<string, string> {
return this._properties;
}
}
/**
* This class represents an ASS drawing - a set of drawing instructions between {\p} tags.
*
+10 -12
View File
@@ -192,7 +192,7 @@ var require = function (name) {
setup = function (body) {
currentSuite.setup = body;
};
}
test = function (description, body) {
return new Test(description, body, currentSuite);
@@ -210,7 +210,7 @@ var assert = new function () {
}
if (actual === undefined) {
throw new Error("Expected [" + expected + "] but got undefined");
throw new Error("Expected [" + expected + "] but got undefined")
}
if (expected === null && actual === null) {
@@ -222,7 +222,7 @@ var assert = new function () {
}
if (actual === null) {
throw new Error("Expected [" + expected + "] but got null");
throw new Error("Expected [" + expected + "] but got null")
}
if (expected.constructor !== actual.constructor && actual.constructor !== undefined) {
@@ -306,7 +306,7 @@ var Logger = function (outputDiv) {
console.log(message);
if (numFailed > 0) {
message = numFailed + " of " + numTotal + " tests failed";
var message = numFailed + " of " + numTotal + " tests failed";
console.warn(message);
@@ -318,7 +318,7 @@ var Logger = function (outputDiv) {
}
if (numTotal > numPassed + numFailed) {
message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped";
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped";
console.log(message);
@@ -330,20 +330,18 @@ var Logger = function (outputDiv) {
};
this.writeTest = function (test) {
var message;
if (test.passed === true) {
message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ]";
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ]";
console.log(message);
append(testDiv, message).className += " passed";
}
else if (test.passed === false) {
message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : " + ((test.exception !== null) ? test.exception.message : "<No exception>");
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : " + ((test.exception !== null) ? test.exception.message : "<No exception>");
console.warn(message);
append(testDiv, message).className += " failed";
}
else {
message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : Skipped";
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : Skipped";
console.log(message);
append(testDiv, message).className += " skipped";
}
@@ -366,13 +364,13 @@ var Logger = function (outputDiv) {
append(totalDiv, message).className = "passed";
if (numFailed > 0) {
message = numFailed + " of " + numTotal + " tests failed.";
var message = numFailed + " of " + numTotal + " tests failed.";
console.warn(message);
append(totalDiv, message).className = "failed";
}
if (numTotal > numPassed + numFailed) {
message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped.";
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped.";
console.warn(message);
append(totalDiv, message).className = "skipped";
}
+1
View File
@@ -32,6 +32,7 @@ var parserTest = function (description, input, rule, expected) {
}
catch (parseException) {
if (expected === null) {
passed = true;
return;
}
else {
+24 -66
View File
@@ -19,7 +19,6 @@
*/
var libjass = require("../libjass.js");
var assert = require("assert");
var parserTest = require("./parser-test.js");
suite("Miscellaneous", function () {
@@ -158,70 +157,29 @@ suite("Miscellaneous", function () {
new libjass.parts.Comment("p0")
]);
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "1\n00:00:10,500 --> 00:00:13,000\nElephant's Dream\n\n2\n00:00:15,000 --> 00:00:18,000 X1:52 X2:303 Y1:438 Y2:453\n<font color=\"cyan\">At the left we can see...</font>"
};
parserTest("SRT", "1\n00:00:10,500 --> 00:00:13,000\nElephant's Dream\n\n2\n00:00:15,000 --> 00:00:18,000 X1:52 X2:303 Y1:438 Y2:453\n<font color=\"cyan\">At the left we can see...</font>", "srtScript", [{
number: 1,
start: "00:00:10.500", end: "00:00:13.000",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Elephant's Dream"
}, {
number: 2,
start: "00:00:15.000", end: "00:00:18.000",
bounds: "X1:52 X2:303 Y1:438 Y2:453",
text: "<font color=\"cyan\">At the left we can see...</font>"
}
]);
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 10.500);
assert.deepEqual(ass.dialogues[0].end, 13.000);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Elephant's Dream")
]);
assert.deepEqual(ass.dialogues[1].start, 15.000);
assert.deepEqual(ass.dialogues[1].end, 18.000);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("<font color=\"cyan\">At the left we can see..."),
new libjass.parts.PrimaryColor(null)
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "1\n00:01:15,940 --> 00:01:17,280\nHave you secured the key?\nWhy can't people forgive each other\n\n2\n00:01:17,280 --> 00:01:17,670\nWhy can't people forgive each other"
};
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 1 * 60 + 15.940);
assert.deepEqual(ass.dialogues[0].end, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Have you secured the key?"),
new libjass.parts.NewLine(),
new libjass.parts.Text("Why can't people forgive each other")
]);
assert.deepEqual(ass.dialogues[1].start, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[1].end, 1 * 60 + 17.670);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("Why can't people forgive each other")
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
parserTest("SRT multiple lines", "1\n00:01:15,940 --> 00:01:17,280\nHave you secured the key?\nWhy can't people forgive each other\n\n2\n00:01:17,280 --> 00:01:17,670\nWhy can't people forgive each other", "srtScript", [{
number: 1,
start: "00:01:15.940", end: "00:01:17.280",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Have you secured the key?\nWhy can't people forgive each other"
}, {
number: 2,
start: "00:01:17.280", end: "00:01:17.670",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Why can't people forgive each other"
}
]);
});
+10 -26
View File
@@ -27,15 +27,9 @@ suite("Primitives", function () {
parserTest("Starts with H", "&3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("Less than six digits", "&H71F&", "color", new libjass.parts.Color(31, 7, 0, 1));
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(0, 31, 23, 1));
parserTest("Eight digits", "&H3F171FFF&", "color", new libjass.parts.Color(255, 31, 23, 1));
parserTest("More than eight digits", "&HAAAA3F171F00&", "color", new libjass.parts.Color(255, 255, 255, 1));
parserTest("More than eight digits", "&HAAAA3F171FFF&", "color", new libjass.parts.Color(255, 255, 255, 1));
parserTest("Eight digits, non-zero alpha", "&H3F171FFF&", "color", null);
});
suite("Alpha", function () {
@@ -43,42 +37,32 @@ suite("Primitives", function () {
parserTest("Starts with &", "&FF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&HF&", "alpha", 1 - 15 / 255);
parserTest("Starts with &H, one digit", "&HF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&H0&", "alpha", 1);
parserTest("Starts with &, one digit", "&F&", "alpha", 1 - 15 / 255);
parserTest("Starts with &, one digit", "&F&", "alpha", 0);
parserTest("Starts with &, one digit", "&0&", "alpha", 1);
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 1 - 15 / 255);
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 0);
parserTest("Starts with &H, doesn't end with &", "&H0&", "alpha", 1);
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 1 - 15 / 255);
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 0);
parserTest("Starts with &, doesn't end with &", "&0", "alpha", 1);
parserTest("Doesn't start with &, doesn't end with &", "0", "alpha", 1);
parserTest("Starts with H", "H0", "alpha", 1);
parserTest("Starts with H", "H0", "alpha", null);
});
suite("ColorWithAlpha", function () {
parserTest("Starts with &H", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
parserTest("AABBGGRR", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
parserTest("Starts with &H, non-zero alpha", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1 - 240 / 255));
parserTest("AABBGGRR", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, (1 - 240 / 255)));
parserTest("Starts with &H, zero alpha", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
parserTest("Less than six digits", "&H71F", "colorWithAlpha", new libjass.parts.Color(31, 7, 0, 1));
parserTest("Eight digits", "&H3F171F00", "colorWithAlpha", new libjass.parts.Color(0, 31, 23, 1 - 63 / 255));
parserTest("Eight digits", "&H3F171FFF", "colorWithAlpha", new libjass.parts.Color(255, 31, 23, 1 - 63 / 255));
parserTest("More than eight digits", "&HAAAA3F171F00", "colorWithAlpha", new libjass.parts.Color(255, 255, 255, 0));
parserTest("More than eight digits", "&HAAAA3F171FFF", "colorWithAlpha", new libjass.parts.Color(255, 255, 255, 0));
parserTest("AABBGGRR", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
});
});
+3 -28
View File
@@ -25,9 +25,7 @@ suite("Web worker", function () {
var workerChannel = null;
setup(function () {
if (libjass.webworker.supported) {
workerChannel = libjass.webworker.createWorker("../libjass.js");
}
workerChannel = libjass.webworker.createWorker();
});
suite("Parse", function () {
@@ -41,9 +39,9 @@ suite("Web worker", function () {
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
input: this.customProperties.input,
rule: this.customProperties.rule
}).then(function (value) {
}).then(function (promise) {
try {
assert.deepEqual(value, [
assert.deepEqual(promise.result, [
new libjass.parts.Alignment(8),
new libjass.parts.Text("Are "),
new libjass.parts.Italic(true),
@@ -57,34 +55,11 @@ suite("Web worker", function () {
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
}
else {
test("Parse: Web workers not supported in this environment.");
}
if (libjass.webworker.supported) {
test("Parse failure", function (done) {
this.customProperties = {
rule: "tag_a",
input: "a4"
};
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
input: this.customProperties.input,
rule: this.customProperties.rule
}).then(function (value) {
done(new Error("Expected parse to fail."));
}, function (reason) {
done();
});
});
}
else {
test("Parse failure: Web workers not supported in this environment.");
}
});
});
+151 -208
View File
@@ -22,15 +22,13 @@
module libjass {
/**
* 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.
* This class represents an ASS script. It contains the script properties, an array of Styles, and an array of Dialogues.
*/
export class ASS {
private _properties: ScriptProperties = new ScriptProperties();
private _styles: Map<string, Style> = new Map<string, Style>();
private _dialogues: Dialogue[] = [];
private _stylesFormatSpecifier: string[] = null;
private _dialoguesFormatSpecifier: string[] = null;
private _dialoguesFormatSpecifier: string[];
/**
* The properties of this script.
@@ -59,42 +57,6 @@ module libjass {
return this._dialogues;
}
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
*/
get stylesFormatSpecifier(): string[] {
return this._stylesFormatSpecifier;
}
/**
* The format specifier for the styles section.
*
* @type {!Array.<string>}
*/
get dialoguesFormatSpecifier(): string[] {
return this._dialoguesFormatSpecifier;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
*/
set stylesFormatSpecifier(value: string[]) {
this._stylesFormatSpecifier = value;
}
/**
* The format specifier for the events section.
*
* @type {!Array.<string>}
*/
set dialoguesFormatSpecifier(value: string[]) {
this._dialoguesFormatSpecifier = value;
}
constructor() {
// Deprecated constructor argument
if (arguments.length === 1) {
@@ -102,117 +64,145 @@ module libjass {
}
}
/**
* Add a style to this ASS script.
*
* @param {string} line The line from the script that contains the new style.
*/
addStyle(line: string): void {
var styleLine = parser.parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
if (styleLine === null || styleLine.type !== "Style") {
return;
}
var styleTemplate = styleLine.template;
if (libjass.verboseMode) {
var 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);
this._styles.set(style.name, style);
}
/**
* Add an event to this ASS script.
*
* @param {string} line The line from the script that contains the new event.
*/
addEvent(line: string): void {
var dialogueLine = parser.parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
return;
var typedTemplateArray = <TypedTemplateArray>[];
typedTemplateArray.formatSpecifier = this._dialoguesFormatSpecifier;
var typedTemplate = <TypedTemplate>parser.parse(line, "assScriptProperty", new parser.ParseNode(null, { contents: typedTemplateArray }));
this._addEvent(typedTemplate);
}
private _addEvent(line: TypedTemplate) {
if (line.type === "Dialogue") {
var dialogueTemplate = line.template;
if (libjass.verboseMode) {
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
}
// Create the dialogue and add it to the dialogues array
this.dialogues.push(new Dialogue(dialogueTemplate, this));
}
var dialogueTemplate = dialogueLine.template;
if (libjass.verboseMode) {
var repr = "";
dialogueTemplate.forEach((value, key) => repr += key + " = " + value + ", ");
console.log("Read dialogue: " + repr);
}
// Create the dialogue and add it to the dialogues array
this.dialogues.push(new Dialogue(dialogueTemplate, this));
}
/**
* Creates an ASS object from the raw text of an ASS script.
*
* @param {string} raw The raw text of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>}
* @param {number=0} type The type of the script. One of the libjass.Format constants.
* @return {!libjass.ASS}
*/
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
}
static fromString(raw: string, type: Format = Format.ASS): ASS {
raw = raw.replace(/\r$/gm, "");
return ASS.fromStream(new parser.StringStream(raw), type);
}
/**
* Creates an ASS object from the given {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The stream to parse the script from
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromStream(stream: parser.Stream, type: Format = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
return new parser.StreamParser(stream).ass;
return ASS._fromASSString(raw);
case Format.SRT:
return new parser.SrtStreamParser(stream).ass;
return ASS._fromSRTString(raw);
default:
throw new Error("Illegal value of type: " + type);
}
}
/**
* Creates an ASS object from the given URL.
*
* @param {string} url The URL of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromUrl(url: string, type: Format = Format.ASS): Promise<ASS> {
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
private static _fromASSString(rawASS: string): ASS {
var script = <Map<string, any>>parser.parse(rawASS, "assScript");
var result = new ASS();
// Get the script info template
var infoTemplate: Map<string, string> = script.get("Script Info");
if (libjass.verboseMode) {
console.log("Read script info: " + JSON.stringify(infoTemplate), infoTemplate);
}
var xhr = new XMLHttpRequest();
var result = ASS.fromStream(new parser.XhrStream(xhr), type);
xhr.open("GET", url, true);
xhr.send();
// Parse the script properties
result.properties.resolutionX = parseInt(infoTemplate.get("PlayResX"));
result.properties.resolutionY = parseInt(infoTemplate.get("PlayResY"));
result.properties.wrappingStyle = parseInt(infoTemplate.get("WrapStyle"));
result.properties.scaleBorderAndShadow = (infoTemplate.get("ScaledBorderAndShadow") === "yes");
// Get styles from the styles section
(<TypedTemplateArray>script.get("V4+ Styles")).forEach(line => {
if (line.type === "Style") {
var styleTemplate = line.template;
if (libjass.verboseMode) {
console.log("Read style: " + JSON.stringify(styleTemplate), styleTemplate);
}
// Create the style and add it to the styles map
var newStyle = new Style(styleTemplate);
result.styles.set(newStyle.name, newStyle);
}
});
// Get dialogues from the events section
var events = <TypedTemplateArray>script.get("Events");
result._dialoguesFormatSpecifier = events.formatSpecifier;
events.forEach(line => result._addEvent(line));
return result;
}
private static _fromSRTString(rawSRT: string): ASS {
var script: any[] = parser.parse(rawSRT, "srtScript");
var result = new ASS();
// Parse the script properties
result.properties.resolutionX = 1280;
result.properties.resolutionY = 720;
result.properties.wrappingStyle = 1;
result.properties.scaleBorderAndShadow = true;
var newStyle = new Style(new Map<string, string>()
.set("Name", "Default")
.set("Italic", "0").set("Bold", "0").set("Underline", "0").set("StrikeOut", "0")
.set("Fontname", "").set("Fontsize", "50")
.set("ScaleX", "100").set("ScaleY", "100")
.set("Spacing", "0")
.set("Angle", "0")
.set("PrimaryColour", "&H0000FFFF").set("SecondaryColour", "&H00000000").set("OutlineColour", "&H00000000").set("BackColour", "&H00000000")
.set("Outline", "1").set("BorderStyle", "1")
.set("Shadow", "1")
.set("Alignment", "2")
.set("MarginL", "80").set("MarginR", "80").set("MarginV", "35")
);
result.styles.set(newStyle.name, newStyle);
script.sort((line1, line2) => line1["number"] - line2["number"]).forEach(line => {
result.dialogues.push(new Dialogue(new Map<string, string>()
.set("Style", "Default")
.set("Start", line.start).set("End", line.end)
.set("Layer", "0")
.set("Text",
(<string>line.text)
.replace(/<b>/g, "{\\b1}").replace(/<\/b>/g, "{\\b0}")
.replace(/<i>/g, "{\\i1}").replace(/<\/i>/g, "{\\i0}")
.replace(/<u>/g, "{\\u1}").replace(/<\/u>/g, "{\\u0}")
.replace(
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}"
).replace(/<\/font>/g, "{\\c}")
), result));
});
return result;
}
}
/**
* The format of the string passed to {@link libjass.ASS.fromString}
*/
export enum Format {
ASS,
SRT
}
/**
* The wrapping style defined in the {@link libjass.ScriptProperties}
*/
export enum WrappingStyle {
SmartWrappingWithWiderTopLine = 0,
SmartWrappingWithWiderBottomLine = 3,
@@ -221,7 +211,7 @@ module libjass {
}
/**
* This class represents the properties of a {@link libjass.ASS} script.
* This class represents the properties of an ASS script.
*/
export class ScriptProperties {
private _resolutionX: number;
@@ -266,7 +256,7 @@ module libjass {
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
* The wrap style.
*
* @type {number}
*/
@@ -275,7 +265,7 @@ module libjass {
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
* The wrap style.
*
* @type {number}
*/
@@ -302,16 +292,13 @@ module libjass {
}
}
/**
* The border style defined in the {@link libjass.Style} properties.
*/
export enum BorderStyle {
Outline = 1,
OpaqueBox = 3,
}
/**
* This class represents a single global style declaration in a {@link libjass.ASS} script. The styles can be obtained via the {@link libjass.ASS.styles} property.
* This class represents a single global style declaration in an ASS script. The styles can be obtained via the ASS.styles property.
*
* @param {!Map.<string, string>} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Name"] The name of the style
@@ -370,9 +357,6 @@ module libjass {
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.");
}
this._italic = template.get("Italic") === "-1";
this._bold = template.get("Bold") === "-1";
@@ -380,30 +364,30 @@ module libjass {
this._strikeThrough = template.get("StrikeOut") === "-1";
this._fontName = template.get("Fontname");
this._fontSize = _valueOrDefault(template, "Fontsize", parseFloat, value => !isNaN(value), "50");
this._fontSize = parseFloat(template.get("Fontsize"));
this._fontScaleX = _valueOrDefault(template, "ScaleX", parseFloat, value => !isNaN(value), "100") / 100;
this._fontScaleY = _valueOrDefault(template, "ScaleY", parseFloat, value => !isNaN(value), "100") / 100;
this._fontScaleX = parseFloat(template.get("ScaleX")) / 100;
this._fontScaleY = parseFloat(template.get("ScaleY")) / 100;
this._letterSpacing = _valueOrDefault(template, "Spacing", parseFloat, value => !isNaN(value), "0");
this._letterSpacing = parseFloat(template.get("Spacing"));
this._rotationZ = _valueOrDefault(template, "Angle", parseFloat, value => !isNaN(value), "0");
this._rotationZ = parseFloat(template.get("Angle"));
this._primaryColor = _valueOrDefault(template, "PrimaryColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H0000FFFF");
this._secondaryColor = _valueOrDefault(template, "SecondaryColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._outlineColor = _valueOrDefault(template, "OutlineColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._shadowColor = _valueOrDefault(template, "BackColour", str => <parts.Color>parser.parse(str, "colorWithAlpha"), null, "&H00000000");
this._primaryColor = <parts.Color>parser.parse(template.get("PrimaryColour"), "colorWithAlpha");
this._secondaryColor = <parts.Color>parser.parse(template.get("SecondaryColour"), "colorWithAlpha");
this._outlineColor = <parts.Color>parser.parse(template.get("OutlineColour"), "colorWithAlpha");
this._shadowColor = <parts.Color>parser.parse(template.get("BackColour"), "colorWithAlpha");
this._outlineThickness = _valueOrDefault(template, "Outline", parseFloat, value => !isNaN(value), "1");
this._borderStyle = _valueOrDefault(template, "BorderStyle", parseInt, value => !isNaN(value), "1");
this._outlineThickness = parseFloat(template.get("Outline"));
this._borderStyle = parseInt(template.get("BorderStyle"));
this._shadowDepth = _valueOrDefault(template, "Shadow", parseFloat, value => !isNaN(value), "1");
this._shadowDepth = parseFloat(template.get("Shadow"));
this._alignment = _valueOrDefault(template, "Alignment", parseInt, value => !isNaN(value), "2");
this._alignment = parseInt(template.get("Alignment"));
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._marginLeft = parseFloat(template.get("MarginL"));
this._marginRight = parseFloat(template.get("MarginR"));
this._marginVertical = parseFloat(template.get("MarginV"));
}
/**
@@ -603,10 +587,10 @@ module libjass {
get marginVertical(): number {
return this._marginVertical;
}
}
};
/**
* This class represents a dialogue in a {@link libjass.ASS} script.
* This class represents a dialogue in an ASS script.
*
* @param {!Map.<string, string>} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Style"] The name of the default style of this dialogue
@@ -637,21 +621,14 @@ module libjass {
constructor(template: Map<string, string>, ass: ASS) {
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);
}
this._style = ass.styles.get(template.get("Style"));
this._start = Dialogue._toTime(template.get("Start"));
this._end = Dialogue._toTime(template.get("End"));
this._layer = Math.max(_valueOrDefault(template, "Layer", parseInt, value => !isNaN(value), "0"), 0);
this._layer = Math.max(parseInt(template.get("Layer")), 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.");
}
}
/**
@@ -681,11 +658,6 @@ module libjass {
return this._end;
}
/**
* The default style of this dialogue.
*
* @type {!libjass.Style}
*/
get style(): Style {
return this._style;
}
@@ -713,9 +685,9 @@ module libjass {
}
/**
* The {@link libjass.parts} of this dialogue.
* The parts of this dialogue.
*
* @type {!Array.<!libjass.parts.Part>}
* @type {!Array.<!libjass.parts.Tag>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
@@ -726,7 +698,7 @@ module libjass {
}
/**
* @return {string} A simple representation of this dialogue's properties and parts.
* @return {string} A simple representation of this dialogue's properties and tags.
*/
toString(): string {
return "#" + this._id + " [" + this._start.toFixed(3) + "-" + this._end.toFixed(3) + "] " + ((this._parts !== null) ? this._parts.join(", ") : this._rawPartsString);
@@ -796,61 +768,32 @@ module libjass {
}
}
/**
* A property.
*/
export interface Property {
/**
* @type {string}
*/
name: string;
/**
* @type {string}
*/
value: string;
}
/**
* A template object with a particular type.
*/
export interface TypedTemplate {
/**
* @type {string}
*/
type: string;
/**
* @type {!Map.<string, string>}
*/
template: Map<string, string>;
}
/**
* @param {!Map.<string, string>} template
* @param {string} key
* @param {function(string):T} converter
* @param {?function(T):boolean} validator
* @param {T} defaultValue
* @return {T}
* An array of TypedTemplate objects with the format specifier that was used to parse them.
*/
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);
if (value === undefined) {
return converter(defaultValue);
}
try {
var numValue = converter(value);
if (validator !== null && !validator(numValue)) {
throw new Error("Validation failed.");
}
return numValue;
}
catch (ex) {
throw new Error("Property " + key + " has invalid value " + value + " - " + ex.stack);
}
export interface TypedTemplateArray extends Array<TypedTemplate> {
formatSpecifier: string[];
}
/**
* Debug mode. When true, libjass logs some debug messages.
*
* @type {boolean}
*/
export var debugMode: boolean = false;
/**
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of debugMode.
*
* @type {boolean}
*/
export var verboseMode: boolean = false;
}
+14 -436
View File
@@ -22,61 +22,9 @@
"use strict";
interface Promise<T> {
/**
* @param {function(T):U} fulfilledHandler
* @param {?function(*):U} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler?: (reason: any) => U): Promise<U>;
/**
* @param {function(T):!Promise.<U>} fulfilledHandler
* @param {?function(*):!Promise.<U>} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => Promise<U>, rejectedHandler?: (reason: any) => Promise<U>): Promise<U>;
/**
* @param {function(T):U} fulfilledHandler
* @param {?function(*):!Promise.<U>} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler?: (reason: any) => Promise<U>): Promise<U>;
/**
* @param {function(T):!Promise.<U>} fulfilledHandler
* @param {?function(*):U} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => Promise<U>, rejectedHandler?: (reason: any) => U): Promise<U>;
}
interface Global {
/**
* @type {function(new:Set.<T>, !Array.<T>=)}
*/
Set: {
new <T>(iterable?: T[]): Set<T>; prototype: Set<any>
};
/**
* @type {function(new:Map.<K, V>, !Array.<!Array.<*>>=)}
*/
Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>; prototype: Map<any, any>
};
/**
* @type {function(new:Promise.<T>, function(T), function(*)), resolve: function(T):!Promise.<T>, all: function(!Array.<!Promise.<T>>):!Promise.<!Array.<T>>}}
*/
Promise: {
new <T>(resolver: (fulfill: (value: T) => void, reject: (reason: any) => void) => void): Promise<T>;
resolve<T>(value: T): Promise<T>;
all<T>(promises: Promise<T>[]): Promise<T[]>;
};
Set: { new <T>(): Set<T>; prototype: Set<any> }
Map: { new <K, V>(): Map<K, V>; prototype: Map<any, any> }
}
declare var global: Global; // Defined as a parameter of the anonymous function wrapper
@@ -87,18 +35,14 @@ module libjass {
*
* Elements are stored as properties of an object, with names derived from their type.
*
* @param {!Array.<T>=} iterable Only an array of values is supported.
* @template T
*/
class SimpleSet<T> implements Set<T> {
private _elements: { [key: string]: T };
private _size: number;
constructor(iterable?: T[]) {
constructor() {
this.clear();
if (Array.isArray(iterable)) {
iterable.forEach(value => this.add(value));
}
}
/**
@@ -144,7 +88,6 @@ module libjass {
/**
* @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 {
Object.keys(this._elements).map((property: string) => {
@@ -153,12 +96,6 @@ module libjass {
});
}
/**
* Not implemented.
*
* @param {T} value
* @return {boolean}
*/
delete(value: T): boolean {
throw new Error("This Set implementation doesn't support delete().");
}
@@ -170,12 +107,6 @@ module libjass {
return this._size;
}
/**
* Converts the given value into a property name for the internal map.
*
* @param {T} value
* @return {string}
*/
private _toProperty(value: T): string {
if (typeof value === "number") {
return "#" + value;
@@ -190,12 +121,12 @@ module libjass {
}
/**
* Set to browser's implementation of Set if it has one, else set to {@link libjass.SimpleSet}
* Set to browser's implementation of Set if it has one, else set to libjass.SimpleSet
*
* @type {function(new:Set, !Array.<T>=)}
* @type {function(new:Set)}
*/
export var Set = global.Set;
if (Set === undefined || typeof Set.prototype.forEach !== "function" || new Set([1, 2]).size !== 2) {
if (Set === undefined || typeof Set.prototype.forEach !== "function") {
Set = <any>SimpleSet;
}
@@ -204,19 +135,15 @@ module libjass {
*
* Keys and values are stored as properties of an object, with property names derived from the key type.
*
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
* @template K, V
*/
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
constructor(iterable?: [K, V][]) {
constructor() {
this.clear();
if (Array.isArray(iterable)) {
iterable.forEach(element => this.set(element[0], element[1]));
}
}
/**
@@ -301,7 +228,6 @@ module libjass {
/**
* @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 {
var keysArray = Object.keys(this._keys);
@@ -318,12 +244,6 @@ module libjass {
return this._size;
}
/**
* Converts the given key into a property name for the internal map.
*
* @param {K} key
* @return {string}
*/
private _keyToProperty(key: K): string {
if (typeof key === "number") {
return "#" + key;
@@ -342,362 +262,20 @@ module libjass {
}
/**
* Set to browser's implementation of Map if it has one, else set to {@link libjass.SimpleMap}
* Set to browser's implementation of Map if it has one, else set to libjass.SimpleMap
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
* @type {function(new:Map)}
*/
export var Map = global.Map;
if (Map === undefined || typeof Map.prototype.forEach !== "function" || new Map([[1, "foo"], [2, "bar"]]).size !== 2) {
if (Map === undefined || typeof Map.prototype.forEach !== "function") {
Map = <any>SimpleMap;
}
/**
* Promise implementation for browsers that don't support it.
*
* @param {function(function(T), function(*))} resolver
*/
class SimplePromise<T> implements Promise<T> {
private _state: SimplePromiseState = SimplePromiseState.PENDING;
private _thens: { propagateFulfilling: (value: T) => void; propagateRejection: (reason: any) => void }[] = [];
private _pendingPropagateTimeout: number = null;
private _alreadyFulfilledValue: T = null;
private _alreadyRejectedReason: any = null;
constructor(private _resolver: (resolve: (value: T) => void, reject: (reason: any) => void) => void) {
try {
_resolver(value => this._resolve(value), reason => this._reject(reason));
}
catch (ex) {
this._reject(ex);
}
}
/**
* @param {function(T):(U|Promise.<U>)} fulfilledHandler
* @param {?function(*):(U|Promise.<U>)} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler: (value: T) => U, rejectedHandler: (reason: any) => U): Promise<U> {
fulfilledHandler = (typeof fulfilledHandler === "function") ? fulfilledHandler : null;
rejectedHandler = (typeof rejectedHandler === "function") ? rejectedHandler : null;
if (fulfilledHandler === null && rejectedHandler === null) {
return <any>this;
}
if (fulfilledHandler === null) {
fulfilledHandler = value => <U><any>value;
}
if (rejectedHandler === null) {
rejectedHandler = (reason): U => { throw reason; };
}
var result = new SimplePromise<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);
}
}
});
});
if ((this._state === SimplePromiseState.FULFILLED || this._state === SimplePromiseState.REJECTED) && this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
return result;
}
/**
* @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} value
* @return {!Promise.<T>}
*/
static resolve<T>(value: T): Promise<T> {
return new SimplePromise<T>(resolve => resolve(value));
}
/**
* @param {!Array.<!Promise.<T>>} promises
* @return {!Promise.<!Array.<T>>}
*/
static all<T>(promises: Promise<T>[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
var result: T[] = [];
var numUnresolved = promises.length;
if (numUnresolved === 0) {
resolve(result);
return;
}
promises.forEach((promise, index) => promise.then(value => {
result[index] = value;
numUnresolved--;
if (numUnresolved === 0) {
resolve(result);
}
}), reject);
});
}
/**
* @param {T} value
*/
private _resolve(value: T): void {
var alreadyCalled = false;
try {
if (<any>value === this) {
throw new TypeError("2.3.1");
}
var thenMethod = SimplePromise._getThenMethod<T>(value);
if (thenMethod === null) {
this._fulfill(value);
return;
}
thenMethod.call(
<Promise<T>><any>value,
(value: T) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._resolve(value);
},
(reason: any) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._reject(reason);
});
}
catch (ex) {
if (alreadyCalled) {
return;
}
this._reject(ex);
}
}
/**
* @param {T} value
*/
private _fulfill(value: T): void {
if (this._state !== SimplePromiseState.PENDING) {
return;
}
this._state = SimplePromiseState.FULFILLED;
this._alreadyFulfilledValue = value;
if (this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
}
/**
* @param {*} reason
*/
private _reject(reason: any): void {
if (this._state !== SimplePromiseState.PENDING) {
return;
}
this._state = SimplePromiseState.REJECTED;
this._alreadyRejectedReason = reason;
if (this._pendingPropagateTimeout === null) {
this._pendingPropagateTimeout = setTimeout(() => this._propagateResolution(), 0);
}
}
/**
* @param {!*} obj
* @return {?function(function(T):T, function(*):T):!Promise.<T>}
*/
private static _getThenMethod<T>(obj: any): (fulfilledHandler: (value: T) => T, rejectedHandler: (reason: any) => T) => Promise<T> {
if (typeof obj !== "object" && typeof obj !== "function") {
return null;
}
if (obj === null || obj === undefined) {
return null;
}
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 {
this._pendingPropagateTimeout = null;
if (this._state === SimplePromiseState.FULFILLED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateFulfilling(this._alreadyFulfilledValue);
}
}
else if (this._state === SimplePromiseState.REJECTED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateRejection(this._alreadyRejectedReason);
}
}
}
}
/**
* The state of the {@link libjass.SimplePromise}
*/
enum SimplePromiseState {
PENDING = 0,
FULFILLED = 1,
REJECTED = 2,
}
/**
* Set to browser's implementation of Promise if it has one, else set to {@link libjass.SimplePromise}
*
* @type {function(new:Promise)}
*/
export var Promise = global.Promise;
if (Promise === undefined) {
Promise = <any>SimplePromise;
}
/**
* A deferred promise.
*/
export class DeferredPromise<T> {
private _promise: Promise<T>;
private _resolve: (value: T) => void;
private _reject: (reason: any) => void;
constructor() {
this._promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
/**
* @type {!Promise.<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);
}
}
/**
* Adds properties of the given mixins' prototypes to the given class's prototype.
*
* @param {!*} clazz
* @param {!Array.<*>} mixins
*/
export function mixin(clazz: any, mixins: any[]): void {
export function mixin(derived: any, mixins: any[]) {
mixins.forEach((mixin: any) => {
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
clazz.prototype[name] = mixin.prototype[name];
derived.prototype[name] = mixin.prototype[name];
});
});
}
/**
* Debug mode. When true, libjass logs some debug messages.
*
* @type {boolean}
*/
export var 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;
}
+100 -131
View File
@@ -21,23 +21,11 @@
///<reference path="libjass.ts" />
interface Document {
/**
* @type {!HTMLScriptElement}
*/
currentScript: HTMLScriptElement;
}
interface WorkerGlobalScope {
/**
* @param {*} message
*/
postMessage(message: any): void;
/**
* @param {string} type
* @param {function(*)} listener
* @param {boolean} useCapture
*/
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
}
declare var WorkerGlobalScope: {
@@ -53,7 +41,7 @@ module libjass.webworker {
});
/**
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling {@link libjass.webworker.createWorker}
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling libjass.webworker.setup()
*/
export interface WorkerChannel {
/**
@@ -61,20 +49,42 @@ module libjass.webworker {
*
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>} A promise that will get resolved when the other side computes the result
* @return {!libjass.webworker.WorkerPromise} A promise that will get resolved when the other side computes the result
*/
request(command: WorkerCommands, parameters: any): Promise<any>;
request(command: WorkerCommands, parameters: any): WorkerPromise;
}
/**
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
* Create a new web worker.
*
* @param {string=} scriptPath The path to libjass.js to be loaded in the web worker. If the browser supports document.currentScript, the parameter is optional and, if not provided,
* 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 {
return new WorkerChannelImpl(new Worker(scriptPath));
export function createWorker(): WorkerChannel {
return new WorkerChannelImpl(new Worker(_scriptNode.src));
}
/**
* A promise returned by libjass.webworker.WorkerChannel.request()
*/
export interface WorkerPromise {
resolved: boolean;
result: any;
/**
* Registers a callback to run when this promise is resolved. If the promise is already resolved, the callback is called immediately.
*
* @param {function(*) } callback A function of the form (promise: *): void
*/
then(callback: WorkerPromiseCallback): void;
cancel(): void;
}
/**
* The signature of a callback called by WorkerPromise when it is resolved.
*/
export interface WorkerPromiseCallback {
(promise: WorkerPromise): void;
}
/**
@@ -85,14 +95,14 @@ module libjass.webworker {
}
/**
* The signature of a handler registered to handle a particular command in {@link libjass.webworker.WorkerCommands}
* The signature of a handler registered to handle a particular WorkerCommand.
*/
export interface WorkerCommandHandler {
(parameters: any, response: WorkerResultCallback): void;
}
/**
* The signature of a callback called by a {@link libjass.webworker.WorkerCommandHandler} to report its result back to the caller.
* The signature of a callback called by a WorkerCommandHandler to report its result back to the caller.
*/
export interface WorkerResultCallback {
(error: any, result: any): void;
@@ -101,18 +111,13 @@ module libjass.webworker {
/**
* Registers a handler for the given worker command.
*
* @param {number} command The command that this handler will handle. One of the {@link libjass.webworker.WorkerCommands} constants.
* @param {number} command The command that this handler will handle. Valid values are the values of libjass.webworker.WorkerCommands
* @param {function(*, function(*, *))} handler The handler. A function of the form (parameters: *, response: function(error: *, result: *): void): void
*/
export function _registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler): void {
export function _registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler) {
workerCommands.set(command, handler);
}
/**
* Registers a prototype as a deserializable type.
*
* @param {!*} prototype
*/
export function _registerClassPrototype(prototype: any): void {
prototype._classTag = classPrototypes.size;
classPrototypes.set(prototype._classTag, prototype);
@@ -127,79 +132,21 @@ module libjass.webworker {
var classPrototypes = new Map<number, any>();
/**
* The interface implemented by a communication channel to the other side.
*/
interface WorkerCommunication {
/**
* @param {"message"} type
* @param {function(!MessageEvent): *} listener
* @param {?boolean} useCapture
*/
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
/**
* @param {string} type
* @param {!EventListener} listener
* @param {?boolean} useCapture
*/
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
/**
* @param {*} message
*/
postMessage(message: any): void;
}
/**
* The interface implemented by a request sent to the other side of the communication channel.
*/
interface WorkerRequestMessage {
/**
* An internal identifier for this request. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* The command type of this request.
*
* @type {number}
*/
command: WorkerCommands;
/**
* Any parameters serialized with this request.
*
* @type {*}
*/
parameters: any;
}
/**
* The interface implemented by a response received from the other side of the communication channel.
*/
interface WorkerResponseMessage {
/**
* An internal identifier for this response. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* Set if the computation of this response resulted in an error.
*
* @type {*}
*/
error: any;
/**
* The result of computing this response.
*
* @type {*}
*/
result: any;
}
@@ -209,68 +156,41 @@ module libjass.webworker {
* @param {!*} comm The other side of the channel. When created by the host, this is the web worker. When created by the web worker, this is its global object.
*/
class WorkerChannelImpl implements WorkerChannel {
private static _lastRequestId: number = -1;
private _pendingRequests = new Map<number, DeferredPromise<any>>();
private _pendingRequests = new Map<number, WorkerPromiseImpl>();
constructor(private _comm: WorkerCommunication) {
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
}
/**
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>}
*/
request(command: WorkerCommands, parameters: any): Promise<any> {
var deferred = new DeferredPromise<any>();
var requestId = ++WorkerChannelImpl._lastRequestId;
this._pendingRequests.set(requestId, deferred);
request(command: WorkerCommands, parameters: any): WorkerPromise {
var promise = new WorkerPromiseImpl(this);
var requestId = promise.id;
this._pendingRequests.set(requestId, promise);
var requestMessage: WorkerRequestMessage = { requestId: requestId, command: command, parameters: parameters };
this._comm.postMessage(WorkerChannelImpl._toJSON(requestMessage));
return deferred.promise;
return promise;
}
/**
* @param {number} requestId
*/
cancelRequest(requestId: number): void {
var deferred = this._pendingRequests.get(requestId);
if (deferred === undefined) {
return;
}
this._pendingRequests.delete(requestId);
deferred.reject(new Error("Cancelled."));
}
/**
* @param {!WorkerResponseMessage} message
*/
private _respond(message: WorkerResponseMessage): void {
this._comm.postMessage(WorkerChannelImpl._toJSON({ command: WorkerCommands.Response, requestId: message.requestId, error: message.error, result: message.result }));
}
/**
* @param {string} rawMessage
*/
private _onMessage(rawMessage: string): void {
var message = <{ command: WorkerCommands }>WorkerChannelImpl._fromJSON(rawMessage);
if (message.command === WorkerCommands.Response) {
var responseMessage = <WorkerResponseMessage><any>message;
var deferred = this._pendingRequests.get(responseMessage.requestId);
if (deferred !== undefined) {
var promise = this._pendingRequests.get(responseMessage.requestId);
if (promise !== undefined) {
this._pendingRequests.delete(responseMessage.requestId);
if (responseMessage.error === null) {
deferred.resolve(responseMessage.result);
}
else {
deferred.reject(responseMessage.error);
}
promise.resolve(responseMessage.error, responseMessage.result);
}
return;
@@ -287,10 +207,6 @@ module libjass.webworker {
commandCallback(requestMessage.parameters, (error: any, result: any) => this._respond({ requestId: requestMessage.requestId, error: error, result: result }));
}
/**
* @param {*} obj
* @return {string}
*/
private static _toJSON(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
@@ -301,10 +217,6 @@ module libjass.webworker {
});
}
/**
* @param {string} str
* @return {*}
*/
private static _fromJSON(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
@@ -326,4 +238,61 @@ module libjass.webworker {
if (inWorker) {
new WorkerChannelImpl(<WorkerGlobalScope><any>global);
}
class WorkerPromiseImpl implements WorkerPromise {
private static _lastPromiseId: number = -1;
private _id: number = ++WorkerPromiseImpl._lastPromiseId;
private _resolved: boolean = false;
private _result: any = null;
private _error: any = null;
private _callback: WorkerPromiseCallback = null;
constructor(private _channel: WorkerChannelImpl) { }
get resolved(): boolean {
return this._resolved;
}
get result(): any {
if (!this._resolved) {
throw new Error("Unresolved promise.");
}
if (this._error !== null) {
throw this._error;
}
return this._result;
}
then(callback: WorkerPromiseCallback): void {
this._callback = callback;
if (this._resolved) {
setTimeout(() => this._callback(this), 0);
}
}
cancel(): void {
if (this._resolved) {
return;
}
this._channel.cancelRequest(this._id);
}
get id(): number {
return this._id;
}
resolve(error: any, result: any): void {
this._resolved = true;
this._error = error;
this._result = result;
if (this._callback !== null) {
this._callback(this);
}
}
}
}