1 Commits
Author SHA1 Message Date
Arnavion c6053f59a1 Nested rotations. 2014-08-17 22:09:16 -07:00
112 changed files with 11919 additions and 14597 deletions
+5 -10
View File
@@ -1,12 +1,7 @@
dist/
gulplib/doc.js
gulplib/helpers.js
gulplib/typescript/*.js
lib/
!lib/libjass.css
node_modules/
npm-debug.log
libjass.js
libjass.min.js
*.map
*.log
+3 -8
View File
@@ -1,8 +1,3 @@
language: node_js
node_js:
- "0.10"
- "0.12"
- iojs
before_script:
- "./node_modules/.bin/gulp doc"
sudo: false
language: node_js
node_js:
- "0.10"
+31
View File
@@ -0,0 +1,31 @@
1. Install node.js from http://nodejs.org/ or via your package manager
1. Change to the directory where you cloned this repository.
1. Run the following command
```shell
npm install
```
This will install the dependencies - [Gulp](http://gulpjs.com/), [Mocha](http://visionmedia.github.io/mocha/), [TypeScript](http://www.typescriptlang.org/), [UglifyJS2](https://github.com/mishoo/UglifyJS2) and [Vinyl](https://github.com/wearefractal/vinyl). It will then run gulp to build libjass.js and minify it into libjass.min.js
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.
-106
View File
@@ -1,106 +0,0 @@
### v0.10.0 - 2015/05/05
- Implemented libjass.renderers.AutoClock, a clock that automatically ticks and generates clock events according to the state of an external driver.
- Implemented \k
- libjass.{Set, Map, Promise} can now be set to null to force the use of the polyfills, even if it defaulted to a runtime-provided implementation.
- Added ASS.fromReadableStream(), a function that can be used to parse ASS from a readable stream such as the response of window.fetch().
- ASS.fromUrl() now tries to use window.fetch() if available instead of XMLHttpRequest.
- Fixed constant pausing and playing on Firefox triggered by how slowly it updates video.currentTime (wasn't noticeable but still undesirable).
- Fixed a dialogue's animation state not getting updated while seeking if the start and end times of the seek were within its start and end times.
- Fixed wrapping mode 1 (end-of-line wrapping) to actually wrap.
- Fixed parser to parse the time components of karaoke tags as centiseconds instead of seconds.
- Fixed parser to swallow leading BOM, if any.
- Fixed errors reported by webworker API were empty objects without message and stack properties.
### 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.
+47 -77
View File
@@ -1,95 +1,57 @@
[![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?
* libjass requires no tweaks to the ASS file from the original video.
* It's easy to deploy. There is no server-side support required. A static hosting is all that's needed.
* libjass uses the browser's native CSS engine by converting the components of each line in the ASS script into a series of styled &lt;div&gt; and &lt;span&gt; elements. This allows all the layout and rendering to be handled by the browser instead of requiring complex and costly drawing and animation code. For example, libjass uses CSS3 animations to simulate tags such as \fad. While a canvas-drawing library would have to re-draw such a subtitle on the canvas for every frame of the video, libjass only renders it once and lets the browser render the fade effect.
As a result, libjass is able to render subtitles with very low CPU usage. The downside to libjass's aproach is that it is hard (and potentially impossible) to map all effects possible in ASS (using \t, ASS draw) etc. into DOM elements. As of now, the subset of tags supported by libjass has no such problems.
### What are all these files?
* 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.
* 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?
You can install the latest release of libjass using npm with `npm install libjass` or using bower with `bower install https://github.com/Arnavion/libjass/releases/download/<release name>/libjass.zip`. Inside the package, you will find libjass.js and libjass.css, which you need to load on your website with your video.
Alternatively, you can build libjass from source by cloning this repository and running `npm install`. This will install the dependencies and run the build. libjass.js and libjass.css will be found in the lib/ directory.
1. You need to build libjass.js using the instructions in BUILD.md
1. You need to load libjass.js and libjass.css on the page with your video.
1. You need to call the libjass API.
Only libjass.js and libjass.css are needed to use libjass on your website. The other files are only used during the build process and you don't need to deploy them to your website.
### What are all these files?
* The src/ directory contains the source of libjass. They are TypeScript files and get compiled into JavaScript for the browser using the TypeScript compiler.
* gulpfile.js is the build script. The build command will use this script to build libjass.js. The gulplib/ directory contains other files used for the build.
* The tests/ directory contains unit and functional tests.
* The lib/ directory contains libjass.js and libjass.css. You will need to deploy these to your website.
### How do I use libjass?
### Where's the API documentation? What API do I need to call to use libjass?
The API documentation is linked in the Links section below. Here's an overview:
* The [ASS.fromUrl()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) function 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) It has comments explaining basic usage and pointers to some advanced usage.
### What browser and JavaScript features does libjass need?
* libjass uses some ES5 features like getters and setters (via Object.defineProperty), and assumptions like the behavior of parseInt with leading zeros. It cannot be used with an ES3 environment.
* libjass will use ES6 Set, Map and Promise if they're available on the global object. If they're not present, it will use its own minimal internal implementations. If you have implementations of these that you would like libjass to use but don't want to register them on the global object, you can provide them to libjass specifically by setting the [libjass.Set](http://arnavion.github.io/libjass/api.xhtml#libjass.Set), [libjass.Map](http://arnavion.github.io/libjass/api.xhtml#libjass.Map) and [libjass.Promise](http://arnavion.github.io/libjass/api.xhtml#libjass.Promise) properties.
* [AutoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.AutoClock) and [VideoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.VideoClock) use [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to generate clock ticks.
* [WebRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer) and [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer) use [SVG filter effects for HTML](http://caniuse.com/#feat=svg-html) to render outlines and blur. This feature is not available on all browsers, so you can tell them to fall back to more widely available CSS methods by setting the [RendererSettings.enableSvg](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.enableSvg) property to false.
* WebRenderer and DefaultRenderer use [CSS3 animations](http://caniuse.com/#feat=css-animation) for effects like \mov and \fad.
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - index.js creates this map from the @font-face rules in fonts.css.
### Can I use libjass in node?
libjass's parser works in node. Entire scripts can be parsed via [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
libjass's parser works in node.
```javascript
> var libjass = require("libjass")
> var libjass = require("./libjass.js")
undefined
> var ass; libjass.ASS.fromString(fs.readFileSync("mysubs.ass", "utf8")).then(function (result) { ass = result; })
{}
> ass.properties.resolutionX
1280
> ass.dialogues.length
9
> ass.dialogues[0].toString()
'#0 [646.460-652.130] {\\fad(200,0)}Sapien rhoncus, suscipit posuere in nunc pellentesque'
> var parts = ass.dialogues[0].parts
undefined
> parts.length
2
> parts[0] instanceof libjass.parts.Fade
true
> parts[0].toString()
'Fade { start: 0.2, end: 0 }'
```
[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. For example, the [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun.parse_dialogueParts) rule 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.
```javascript
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
undefined
> parts.join(" ")
@@ -104,31 +66,14 @@ true
8
```
The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun).
See the tests, particularly the ones in tests/unit/miscellaneous.js, for examples.
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun). For example, [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun.parse_dialogueParts) can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
### Can I contribute?
Yes! Feature requests, suggestions, bug reports and pull requests are welcome! I'm especially looking for details and edge-cases of the ASS syntax that libjass doesn't support.
You can also join the IRC channel in the links section below and ask any questions.
### Supported features
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \be, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \k, \q, \r, \pos, \move, \fad, \fade, \p
* Custom fonts, using CSS web fonts.
### Known issues
* Unsupported tags: \fe, \2c, \2a, \K, \kf, \ko, \org, \t, \clip, \iclip
* \an4, \an5, \an6 aren't positioned correctly.
* Smart line wrapping is not supported. Such lines are rendered as [wrapping style 1 (end-of-line wrapping).](http://docs.aegisub.org/3.0/ASS_Tags/#wrapstyle)
* Lines with multiple rotations aren't rotated the same as VSFilter or libass. See [#14](https://github.com/Arnavion/libjass/issues/14)
You can also join the IRC channel below and ask any questions.
### Links
@@ -139,6 +84,31 @@ You can also join the IRC channel in the links section below and ask any questio
* [Aegisub's documentation on ASS](http://docs.aegisub.org/3.0/ASS_Tags/)
### Supported features
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \r, \pos, \move, \fad, \fade, \p
* Custom fonts, using CSS web fonts.
### Known issues
* Unsupported tags: \be, \fe, \2c, \2a, \k, \K, \kf, \ko, \q, \org, \t, \clip, \iclip
* \an4, \an5, \an6 aren't positioned correctly.
* Lines scaled using {\fscx} and {\fscy} have their text clip into itself in some situations, and aren't always positioned correctly.
* All lines are rendered as wrapping style 1 (end-of-line wrapping).
* Lines with multiple rotations aren't rotated the same as VSFilter or libass.
* Subtitles aren't styled in IE11 because [it doesn't support SVG filter effects on HTML](http://caniuse.com/svg-html).
### Planned improvements
* 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.
* Evaluate (document, benchmark) the benefits and drawbacks of DOM+CSS-based drawing over canvas.
### License
```
+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");
}
+76 -171
View File
@@ -22,44 +22,80 @@ var fs = require("fs");
var path = require("path");
var gulp = require("gulp");
var Vinyl = require("vinyl");
(function () {
var _TypeScript = null;
var _UglifyJS = null;
var _npm = null;
var helpers = require("./gulplib/helpers.js");
var Transform = helpers.Transform;
var SingletonChildProcess = helpers.SingletonChildProcess;
Object.defineProperties(global, {
TypeScript: { get: function () { return _TypeScript || (_TypeScript = require("./gulplib/typescript/index.js")); } },
UglifyJS: { get: function () { return _UglifyJS || (_UglifyJS = require("./gulplib/uglify.js")); } },
npm: { get: function () { return _npm || (_npm = require("npm")); } },
});
})();
Object.defineProperties(global, {
Doc: { get: function () { return require("./gulplib/doc.js"); } },
TypeScript: { get: function () { return require("./gulplib/typescript.js"); } },
UglifyJS: { get: function () { return require("./gulplib/uglify.js"); } },
});
gulp.task("default", ["libjass.js", "libjass.min.js"]);
gulp.task("libjass.js", ["build-gulplib"], function () {
if (fs.existsSync("./lib/libjass.js")) {
return;
function ASTModifer(namespaces) {
var visitor = function (current) {
var newText = [];
if (current instanceof TypeScript.AST.Namespace) {
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Constructor) {
newText.push("@constructor");
if (current.baseType !== null) {
newText.push("@extends {" + current.baseType.fullName + "}");
}
if (current.parent !== null) {
newText.push("@memberOf " + current.parent.fullName);
}
current.members.forEach(visitor);
}
if (current.isPrivate) {
newText.push("@private");
}
if (current.isStatic) {
newText.push("@static");
}
if (newText.length > 0) {
current.astNode["gulp-typescript-new-comment"] = newText;
}
};
Object.keys(namespaces).forEach(function (namespaceName) { visitor(namespaces[namespaceName]); });
}
gulp.task("libjass.js", function (callback) {
if (fs.existsSync("./libjass.js")) {
return callback(null);
}
return gulp.src("./src/tsconfig.json")
.pipe(TypeScript.gulp("./src/index.ts", "libjass"))
.pipe(UglifyJS.gulp("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(gulp.dest("./lib"));
return gulp.src("./libjass.ts", { read: false })
.pipe(TypeScript.gulp("/libjass.js", "/libjass.js.map", ASTModifer))
.pipe(UglifyJS.fixup())
.pipe(gulp.dest("."));
});
gulp.task("libjass.min.js", ["libjass.js"], function () {
if (fs.existsSync("./lib/libjass.min.js")) {
return;
gulp.task("libjass.min.js", ["libjass.js"], function (callback) {
if (fs.existsSync("./libjass.min.js")) {
return callback(null);
}
return gulp.src(["./lib/libjass.js", "./lib/libjass.js.map"])
.pipe(UglifyJS.minify())
.pipe(gulp.dest("./lib"));
var input = Object.create(null);
return gulp.src(["./libjass.js", "./libjass.js.map"]).pipe(UglifyJS.minify()).pipe(gulp.dest("."));
});
gulp.task("clean", function () {
["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"].forEach(function (file) {
["libjass.js", "libjass.js.map", "libjass.min.js", "libjass.min.js.map"].forEach(function (file) {
try {
fs.unlinkSync(file);
}
@@ -71,155 +107,24 @@ gulp.task("clean", function () {
});
});
gulp.task("watch", ["clean", "build-gulplib"], function (callback) {
var helpers = require("./gulplib/helpers.js");
gulp.task("watch", ["clean"], function (callback) {
var gulpTestProcess = SingletonChildProcess(path.resolve("./gulplib/test-runner.js"));
npm.load(function () {
var testRunning = false;
var rerunTest = false;
var startTest = function () {
npm.commands["run-script"](["test-lib"], function () {
testRunning = false;
if (rerunTest) {
startTest();
rerunTest = false;
}
});
testRunning = true;
};
var runTest = function () {
if (!testRunning) {
startTest();
}
else {
rerunTest = true;
}
};
gulp.src("./src/tsconfig.json")
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
.pipe(UglifyJS.watch("./src/index", "libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(gulp.dest("./lib"))
.pipe(helpers.makeTransform(function (file) {
if (path.basename(file.path) === "libjass.js") {
runTest();
}
}));
gulp.watch(["./tests/unit/*.js"], { debounceDelay: 1 }, function () {
runTest();
});
});
});
gulp.task("test-lib", ["libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-lib"], callback);
});
});
gulp.task("test-minified", ["libjass.min.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-minified"], callback);
});
});
// Start Selenium server with
// java.exe -jar .\selenium-server-standalone-2.45.0.jar "-Dwebdriver.ie.driver=$PWD\IEDriverServer.exe" "-Dwebdriver.chrome.driver=$PWD\chromedriver.exe"
gulp.task("test-browser", ["libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-browser"], callback);
});
});
gulp.task("test", ["test-lib", "test-minified"]);
gulp.task("demo", ["libjass.js"], function () {
return gulp.src(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.css"]).pipe(gulp.dest("../libjass-gh-pages/demo/"));
});
gulp.task("doc", ["make-doc", "test-doc"]);
gulp.task("make-doc", ["libjass.js"], function () {
var Doc = require("./gulplib/doc.js");
return gulp.src("./src/tsconfig.json")
.pipe(Doc.gulp("./api.xhtml", "./src/index.ts", "libjass"))
.pipe(gulp.dest("../libjass-gh-pages/"));
});
gulp.task("test-doc", ["make-doc", "libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-doc"], callback);
});
});
gulp.task("dist", ["clean", "default", "test", "test-browser", "demo", "doc"], function () {
var inputFiles = [
"./README.md", "./CHANGELOG.md", "./LICENSE",
"./lib/libjass.js", "./lib/libjass.js.map",
"./lib/libjass.min.js", "./lib/libjass.min.js.map",
"./lib/libjass.css"
];
var files = Object.create(null);
inputFiles.forEach(function (filename) {
files["./dist/" + path.basename(filename)] = filename;
});
// Clean dist/
Object.keys(files).concat("./dist/package.json").forEach(function (file) {
try {
fs.unlinkSync(file);
gulp.src("./libjass.ts", { read: false })
.pipe(TypeScript.watch("/libjass.js", "/libjass.js.map"))
.pipe(UglifyJS.fixup())
.pipe(gulp.dest("."))
.pipe(Transform(function (file) {
if (path.basename(file.path) === "libjass.js") {
gulpTestProcess();
}
catch (ex) {
if (ex.code !== "ENOENT") {
throw ex;
}
}
});
}));
// Create dist/ if necessary
try {
fs.mkdirSync("./dist");
}
catch (ex) {
if (ex.code !== "EEXIST") {
throw ex;
}
}
// Copy all files except package.json
Object.keys(files).forEach(function (outputFilename) {
var inputFilename = files[outputFilename];
var contents = fs.readFileSync(inputFilename);
fs.writeFileSync(outputFilename, contents);
});
// Copy package.json
var packageJson = fs.readFileSync("./package.json");
packageJson = JSON.parse(packageJson);
packageJson.devDependencies = undefined;
packageJson.private = undefined;
packageJson.scripts = undefined;
packageJson.main = "libjass.js";
packageJson = JSON.stringify(packageJson, null, "\t");
fs.writeFileSync("./dist/package.json", packageJson, { encoding: "utf8" });
});
gulp.task("build-gulplib", function (callback) {
if (fs.existsSync("./gulplib/typescript/index.js") && fs.existsSync("./gulplib/doc.js")) {
callback();
return;
}
npm.load(function () {
npm.commands["run-script"](["build-gulplib"], callback);
gulp.watch(["./tests/test-*.js"], { debounceDelay: 1 }, function () {
gulpTestProcess();
});
});
gulp.task("doc", ["libjass.js"], function () {
return gulp.src("./libjass.ts").pipe(Doc("/api.xhtml")).pipe(gulp.dest("../libjass-gh-pages/"));
});
+678
View File
@@ -0,0 +1,678 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require("fs");
var path = require("path");
var vm = require("vm");
var Vinyl = require("vinyl");
var Transform = require("./helpers.js").Transform;
var TypeScript = require("./typescript.js");
Array.prototype.concatMany = function (arr) {
var result = this;
arr.forEach(function (a) {
result = result.concat(a);
});
return result;
};
module.exports = function () {
var compiler = new TypeScript.Compiler();
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, 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 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);
});
namespaceNames.forEach(function (namespaceName) {
namespaces[namespaceName].members.sort(sorter);
namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Interface || value instanceof TypeScript.AST.Constructor;
}).forEach(function (interfaceOrConstructor) {
interfaceOrConstructor.members.sort(sorter);
});
});
var sanitize = function (string) {
return string.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: "api.xhtml",
contents: Buffer.concat([
'<?xml version="1.0" encoding="utf-8" ?>',
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">',
' <head>',
' <title>libjass API Documentation</title>',
' <style type="text/css">',
' <![CDATA[',
' html, body, .namespaces, .content {',
' height: 100%;',
' margin: 0;',
' }',
'',
' .namespaces, .content {',
' overflow-y: scroll;',
' }',
'',
' .namespaces {',
' float: left;',
' background-color: white;',
' padding: 0 20px;',
' margin-right: 20px;',
' }',
'',
' .content > section:not(:last-child) {',
' border-bottom: 1px solid black;',
' }',
'',
' .variable, .function, .interface, .constructor, .getter, .setter {',
' margin-left: 30px;',
' padding: 10px;',
' }',
'',
' 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) {',
' background-color: rgb(244, 250, 221);',
' }',
'',
' .name {',
' font-size: x-large;',
' }',
'',
' .usage {',
' font-size: large;',
' font-style: italic;',
' }',
'',
' .usage legend:before {',
' content: "Usage";',
' }',
'',
' .usage pre {',
' margin: 0;',
' }',
'',
' .interface .variable, .interface .function, .interface .getter, .interface .setter,',
' .constructor .variable, .constructor .function, .constructor .getter, .constructor .setter {',
' background-color: rgb(250, 241, 221);',
' }',
'',
' .parameter.name {',
' font-size: large;',
' }',
'',
' .type {',
' font-style: italic;',
' }',
'',
' .type:before {',
' content: "Type: ";',
' }',
'',
' .abstract > .name:before {',
' content: "abstract ";',
' }',
'',
' .private > .name:before {',
' content: "private ";',
' }',
'',
' .static > .name:before {',
' content: "static ";',
' }',
'',
' .abstract.private > .name:before {',
' content: "abstract private ";',
' }',
'',
' .private.static > .name:before {',
' content: "static private ";',
' }',
'',
' body:not(.show-private) .private {',
' display: none;',
' }',
' ]]>',
' </style>',
' <script>',
' <![CDATA[',
' addEventListener("DOMContentLoaded", function () {',
' document.querySelector("#show-private").addEventListener("change", function (event) {',
' document.body.className = (event.target.checked ? "show-private" : "");',
' }, false);',
'',
' if (document.querySelector("[href=\\"" + location.hash + "\\"]").offsetHeight === 0) {',
' document.querySelector("#show-private").click()',
' }',
' }, false);',
' ]]>',
' </script>',
' </head>',
' <body>',
' <section class="namespaces">',
' <label><input type="checkbox" id="show-private" />Show private</label>',
' <h2>Namespaces</h2>'
].concat(writeOverview(3)).concat([
' </section>',
' <div class="content">',
''
]).concatMany(namespaceNames.map(function (namespaceName) {
var variables = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Variable;
});
var functions = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Function;
});
var interfaces = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Interface;
});
var constructors = namespaces[namespaceName].members.filter(function (value) {
return value instanceof TypeScript.AST.Constructor;
});
var result = [
' <section>',
' <h1 id="' + sanitize(namespaceName) + '">' + sanitize(namespaceName) + '</h1>',
''
];
if (variables.length > 0) {
result = result.concat([
' <section>',
' <h2>Variables</h2>'
]).concatMany(variables.map(function (value) {
return writeVariable(value, 5);
})).concat([
' </section>',
''
]);
}
if (functions.length > 0) {
result = result.concat([
' <section>',
' <h2>Free functions</h2>'
]).concatMany(functions.map(function (value) {
return writeFunction(value, 5);
})).concat([
' </section>',
''
]);
}
if (interfaces.length > 0) {
result = result.concat([
' <section>',
' <h2>Interfaces</h2>'
]).concatMany(interfaces.map(function (value) {
return writeInterface(value, 5);
})).concat([
' </section>',
''
]);
}
if (constructors.length > 0) {
result = result.concat([
' <section>',
' <h2>Classes</h2>'
]).concatMany(constructors.map(function (value) {
return writeConstructor(value, 5);
})).concat([
' </section>',
''
]);
}
result = result.concat([
' </section>'
]);
return result;
})).concat([
' </div>',
' </body>',
'</html>'
]).map(function (line) {
return new Buffer(line + "\n");
}))
}));
});
};
-885
View File
@@ -1,885 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from "fs";
import * as path from "path";
import { Transform } from "stream";
import Vinyl = require("vinyl");
import { makeTransform } from "./helpers";
import * as AST from "./typescript/ast";
import { Compiler } from "./typescript/compiler";
import { walk } from "./typescript/walker";
function flatten<T>(arr: T[][]): T[] {
var result: T[] = [];
for (let a of arr) {
result = result.concat(a);
}
return result;
}
var sorter = (() => {
function visibilitySorter(value1: { isPrivate?: boolean; isProtected?: boolean; }, value2: { isPrivate?: boolean; isProtected?: boolean; }) {
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 = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
function typeSorter(value1: AST.ModuleMember | AST.NamespaceMember, value2: AST.ModuleMember | AST.NamespaceMember) {
var type1Index = -1;
var type2Index = -1;
types.every((type, index) => {
if (value1 instanceof type) {
type1Index = index;
}
if (value2 instanceof type) {
type2Index = index;
}
return (type1Index === -1) || (type2Index === -1);
});
return type1Index - type2Index;
}
function nameSorter(value1: { name: string }, value2: { name: string }) {
return value1.name.localeCompare(value2.name);
}
var sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
return (value1: AST.ModuleMember, value2: AST.ModuleMember) => {
for (var i = 0; i < sorters.length; i++) {
var result = sorters[i](value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
};
})();
function indenter(indent: number) {
return (line: string) => ((line === "") ? line : (Array(indent + 1).join("\t") + line));
}
function sanitize(str: string) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function toVariableName(item: { name: string }) {
// 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;
}
function toUsageName(item: AST.Class | AST.Interface | AST.Function | AST.Property | AST.Enum): string {
if (item.parent instanceof AST.Module) {
return item.name;
}
if (item instanceof AST.Class || item instanceof AST.Interface || item instanceof AST.Enum) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.parent instanceof AST.Namespace) {
if ((<AST.Class | AST.Interface | AST.Function | AST.Enum>item).isPrivate) {
return item.name;
}
return item.fullName;
}
if ((<AST.Function>item).isStatic) {
return toUsageName(<AST.Class | AST.Interface>item.parent) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
}
function toId(item: { fullName?: string; name: string; }): string {
return sanitize((item.fullName === undefined) ? item.name : item.fullName);
}
function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): string {
var result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
var itemWithGenerics = <AST.HasGenerics>item;
if (itemWithGenerics.generics !== undefined && itemWithGenerics.generics.length > 0) {
var generics = <(string | AST.TypeReference | AST.IntrinsicTypeReference)[]>itemWithGenerics.generics;
result += sanitize(`.<${ generics.map(generic =>
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : <string>generic
).join(', ') }>`);
}
result += '</a>';
return result;
}
function writeDescription(text: string): string {
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
var inCodeBlock = false;
result = result.split("\n").map(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;
}
function writeParameters(parameters: AST.Parameter[]): string[] {
if (parameters.length === 0) {
return [];
}
return [
'<dd class="parameters">',
' <dl>'
].concat(flatten(parameters.map(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>`
].concat(writeParameters(parameter.subParameters).map(indenter(2)));
}))).concat([
' </dl>',
'</dd>'
]);
}
function functionToHtml(func: AST.Function): string[] {
return [
`<dl id="${ toId(func) }" class="function${
func.isAbstract ? ' abstract' : '' }${
func.isPrivate ? ' private' : ''}${
func.isProtected ? ' protected' : ''}${
func.isStatic ? ' static' : ''}">`,
` <dt class="name">${ toLink(func) }</dt>`,
' <dd class="description">',
` <p>${ writeDescription(func.description) }</p>`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${
sanitize(
`${ (func.returnType !== null) ? 'var result = ' : '' }${ toUsageName(func) }(${
func.parameters.map(parameter => parameter.name).join(', ') });`
) }</code></pre></fieldset></dd>`
].concat(writeParameters(func.parameters).map(indenter(1))).concat(
(func.returnType === null) ? [] : [
' <dt>Returns</dt>',
` <dd class="return type">${ sanitize(func.returnType.type) }</dd>`,
` <dd class="return description">${ writeDescription(func.returnType.description) }</dd>`
]
).concat([
'</dl>',
''
]);
}
function interfaceToHtml(interfase: AST.Interface): string[] {
var members: AST.InterfaceMember[] = [];
Object.keys(interfase.members).forEach(memberName => members.push(interfase.members[memberName]));
members.sort(sorter);
return [
`<dl id="${ toId(interfase) }" class="interface${ interfase.isPrivate ? ' private' : '' }">`,
` <dt class="name">interface ${ toLink(interfase) }${ (interfase.baseTypes.length > 0) ? ` extends ${
interfase.baseTypes.map(baseType => baseType instanceof AST.TypeReference ? toLink(baseType) : baseType.name).join(', ')
}` : '' }</dt>`,
' <dd class="description">',
` <p>${ writeDescription(interfase.description) }</p>`,
' </dd>',
' <dd class="members">'
].concat(flatten(members.map(member => {
if (member instanceof AST.Property) {
return propertyToHtml(member).map(indenter(2));
}
else if (member instanceof AST.Function) {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
}
}))).concat([
' </dd>',
'</dl>',
''
]);
}
function classToHtml(clazz: AST.Class): string[] {
var members: AST.InterfaceMember[] = [];
Object.keys(clazz.members).forEach(memberName => members.push(clazz.members[memberName]));
members.sort(sorter);
return [
`<dl id="${ toId(clazz) }" class="clazz${
clazz.isAbstract ? ' abstract' : ''}${
clazz.isPrivate ? ' private' : ''}">`,
` <dt class="name">class ${ toLink(clazz) }${
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(<AST.TypeReference>clazz.baseType) : clazz.baseType.name }` : '' }${
(clazz.interfaces.length > 0) ? ` implements ${ clazz.interfaces.map(interfase => interfase instanceof AST.TypeReference ? toLink(interfase) : interfase.name).join(', ') }` : ''}</dt>`,
' <dd class="description">',
` <p>${ writeDescription(clazz.description) }</p>`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${
sanitize(
`var ${ toVariableName(clazz) } = new ${ toUsageName(clazz) }(${
clazz.parameters.map(parameter => parameter.name).join(', ') });`
) }</code></pre></fieldset></dd>`
].concat(writeParameters(clazz.parameters).map(indenter(1))).concat([
' <dd class="members">'
]).concat(flatten(members.map(member => {
if (member instanceof AST.Property) {
return propertyToHtml(member).map(indenter(2));
}
else if (member instanceof AST.Function) {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (<any>member.constructor).name }`);
}
}))).concat([
' </dd>',
'</dl>',
''
]);
}
function enumToHtml(enumType: AST.Enum): string[] {
return [
`<dl id="${ toId(enumType) }" class="enum${ enumType.isPrivate ? ' private' : '' }">`,
` <dt class="name">enum <a href="#${ sanitize(enumType.fullName) }">${ sanitize(enumType.name) }</a></dt>`,
' <dd class="description">',
` <p>${ writeDescription(enumType.description) }</p>`,
' </dd>'
].concat([
' <dd class="members">'
]).concat(flatten(enumType.members.map(member => [
` <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>',
''
]);
}
function propertyToHtml(property: AST.Property): string[] {
return [
`<dl id="${ toId(property) }" class="property">`,
` <dt class="name">${ toLink(property) }</dt>`
].concat((property.getter === null) ? [] : [
` <dt class="getter${ property.getter.isPrivate ? ' private' : '' }">Getter</dt>`,
' <dd class="description">',
` <p>${ writeDescription(property.getter.description) }</p>`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`var result = ${ toUsageName(property) };`) }</code></pre></fieldset></dd>`,
` <dd class="return type">${ sanitize(property.getter.type) }</dd>`
]).concat((property.setter === null) ? [] : [
` <dt class="setter${ property.setter.isPrivate ? ' private' : '' }">Setter</dt>`,
' <dd class="description">',
` <p>${ writeDescription(property.setter.description) }</p>`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`${ toUsageName(property) } = value;`) }</code></pre></fieldset></dd>`
].concat(writeParameters([new AST.Parameter("value", "", property.setter.type)]).map(indenter(1)))).concat([
'</dl>',
''
]);
}
export function gulp(outputFilePath: string, root: string, rootNamespaceName: string): any {
var compiler = new Compiler();
return makeTransform(function (file: Vinyl): void {
var self: Transform<Vinyl> = this;
// Compile
compiler.compile(file);
// Walk
var walkResult = walk(compiler, root, rootNamespaceName);
var namespaces = walkResult.namespaces;
var modules = walkResult.modules;
// Make HTML
var namespaceNames = Object.keys(namespaces)
.filter(namespaceName => namespaceName.substr(0, rootNamespaceName.length) === rootNamespaceName)
.sort((ns1, ns2) => ns1.localeCompare(ns2));
var moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2));
moduleNames = moduleNames.filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
self.push(new Vinyl({
path: outputFilePath,
contents: Buffer.concat([new Buffer(
`<?xml version="1.0" encoding="utf-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>${ rootNamespaceName } API Documentation</title>
<style type="text/css">
<![CDATA[
html, body, .navigation, .content {
height: 100%;
margin: 0;
}
.navigation, .content {
overflow-y: scroll;
}
.navigation {
float: left;
background-color: white;
padding: 0 20px;
margin-right: 20px;
}
.navigation .namespace, .navigation .module {
margin-top: 1em;
}
.navigation .elements {
margin: 0;
}
.content > section:not(:last-child) {
border-bottom: 1px solid black;
}
.clazz, .enum, .function, .interface, .property {
margin-left: 30px;
padding: 10px;
}
.getter, .setter {
font-size: large;
}
section > .clazz:nth-child(2n), section > .enum:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .property:nth-child(2n) {
background-color: rgb(221, 250, 238);
}
section > .clazz:nth-child(2n + 1), section > .enum:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .property:nth-child(2n + 1) {
background-color: rgb(244, 250, 221);
}
.name {
font-size: x-large;
}
.usage {
font-size: large;
font-style: italic;
}
.usage legend:before {
content: "Usage";
}
.usage fieldset {
min-width: initial;
overflow-x: auto;
}
.usage pre {
margin: 0;
}
.clazz .function, .clazz .property, .interface .function, .interface .property, .enum .member {
background-color: rgb(250, 241, 221);
}
.parameter.name {
font-size: large;
}
.type {
font-style: italic;
}
.type:before {
content: "Type: ";
}
.abstract > .name:before {
content: "abstract ";
}
.clazz .private > .name:before {
content: "private ";
}
.clazz .protected > .name:before {
content: "protected ";
}
.static > .name:before {
content: "static ";
}
.abstract.private > .name:before {
content: "abstract private ";
}
.private.static > .name:before {
content: "static private ";
}
.abstract.protected > .name:before {
content: "abstract protected ";
}
.protected.static > .name:before {
content: "static protected ";
}
body:not(.show-private) .clazz .private, body:not(.show-private) .clazz .protected, body:not(.show-private) .module {
display: none;
}
.description .code {
margin-left: 30px;
}
]]>
</style>
<script>
<![CDATA[
addEventListener("DOMContentLoaded", function () {
document.querySelector("#show-private").addEventListener("change", function (event) {
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) {
document.querySelector("#show-private").click()
jumpToElement.scrollIntoView();
}
}
addEventListener("hashchange", showPrivateIfNecessary, false);
]]>
</script>
</head>
<body>
<nav class="navigation">
<label><input type="checkbox" id="show-private" />Show private</label>
`
)].concat(namespaceNames.map(namespaceName => {
var namespace = namespaces[namespaceName];
var namespaceMembers: AST.NamespaceMember[] = [];
for (let memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
return Buffer.concat([new Buffer(
` <fieldset class="namespace">
<legend><a href="#${ sanitize(namespaceName) }">${ sanitize(namespaceName) }</a></legend>
<ul class="elements">
`
)].concat(namespaceMembers.map(member => new Buffer(
` <li><a href="#${ sanitize(member.fullName) }">${ sanitize(member.name) }</a></li>
`
))).concat([new Buffer(
` </ul>
</fieldset>
`
)]));
})).concat(moduleNames.map(moduleName => {
var module = modules[moduleName];
var moduleMembers: AST.ModuleMemberWithoutReference[] = [];
for (let memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if ((<AST.HasParent><any>member).parent === module) {
moduleMembers.push(<AST.ModuleMemberWithoutReference>member);
}
}
if (moduleMembers.length === 0) {
return new Buffer("");
}
moduleMembers.sort(sorter);
return Buffer.concat([new Buffer(
` <fieldset class="module">
<legend><a href="#${ sanitize(moduleName) }">${ sanitize(moduleName) }</a></legend>
<ul class="elements">
`
)].concat(moduleMembers.map(member => new Buffer(
` <li><a href="#${ sanitize(member.fullName) }">${ sanitize(member.name) }</a></li>
`
))).concat([new Buffer(
` </ul>
</fieldset>
`
)]));
})).concat([new Buffer(
` </nav>
<div class="content">
`
)]).concat(flatten(namespaceNames.map(namespaceName => {
var namespace = namespaces[namespaceName];
var namespaceMembers: AST.NamespaceMember[] = [];
for (let memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
var properties = <AST.Property[]>namespaceMembers.filter(member => member instanceof AST.Property);
var functions = <AST.Function[]>namespaceMembers.filter(member => member instanceof AST.Function);
var interfaces = <AST.Interface[]>namespaceMembers.filter(member => member instanceof AST.Interface);
var classes = <AST.Class[]>namespaceMembers.filter(member => member instanceof AST.Class);
var enums = <AST.Enum[]>namespaceMembers.filter(member => member instanceof AST.Enum);
var result = [new Buffer(
` <section class="namespace">
<h1 id="${ sanitize(namespaceName) }">Namespace ${ sanitize(namespaceName) }</h1>
`
)];
if (properties.length > 0) {
result.push(new Buffer(
` <section>
<h2>Properties</h2>
`
));
for (let property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
` <section>
<h2>Free functions</h2>
`
));
for (let func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
` <section>
<h2>Interfaces</h2>
`
));
for (let interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
` <section>
<h2>Classes</h2>
`
));
for (let clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
` <section>
<h2>Enums</h2>
`
));
for (let enumType of enums) {
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
result.push(new Buffer(
` </section>
`
));
return result;
}))).concat(flatten(moduleNames.map(moduleName => {
var module = modules[moduleName];
var moduleMembers: AST.ModuleMember[] = [];
for (let memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if ((<AST.HasParent><any>member).parent === module) {
moduleMembers.push(<AST.ModuleMember>member);
}
}
if (moduleMembers.length === 0) {
return [];
}
moduleMembers.sort(sorter);
var properties = <AST.Property[]>moduleMembers.filter(member => member instanceof AST.Property);
var functions = <AST.Function[]>moduleMembers.filter(member => member instanceof AST.Function);
var interfaces = <AST.Interface[]>moduleMembers.filter(member => member instanceof AST.Interface);
var classes = <AST.Class[]>moduleMembers.filter(member => member instanceof AST.Class);
var enums = <AST.Enum[]>moduleMembers.filter(member => member instanceof AST.Enum);
var result = [new Buffer(
` <section class="module">
<h1 id="${ sanitize(moduleName) }">Module ${ sanitize(moduleName) }</h1>
`
)];
if (properties.length > 0) {
result.push(new Buffer(
` <section>
<h2>Properties</h2>
`
));
for (let property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
` <section>
<h2>Free functions</h2>
`
));
for (let func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
` <section>
<h2>Interfaces</h2>
`
));
for (let interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
` <section>
<h2>Classes</h2>
`
));
for (let clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
` <section>
<h2>Enums</h2>
`
));
for (let enumType of enums) {
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
result.push(new Buffer(
` </section>
`
));
return result;
}))).concat([new Buffer(
` </div>
</body>
</html>
`
)]))
}));
});
}
+88
View File
@@ -0,0 +1,88 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var childProcess = require("child_process");
var stream = require("stream");
var util = require("util");
var Transform = function (transform, flush) {
if (!(this instanceof Transform)) {
return new Transform(transform, flush);
}
stream.Transform.call(this, { objectMode: true });
transform = transform || function (chunk, encoding, callback) { callback(); };
if (transform.length < 3) {
this._transform = function (chunk, encoding, callback) {
try {
transform.call(this, chunk, encoding);
callback();
}
catch (ex) {
callback(ex);
}
};
}
else {
this._transform = transform.bind(this);
}
flush = flush || function (callback) { callback(); };
if (flush.length < 1) {
this._flush = function (callback) { try { flush.call(this); callback(); } catch (ex) { callback(ex); } };
}
else {
this._flush = flush.bind(this);
}
};
util.inherits(Transform, stream.Transform);
exports.Transform = Transform;
exports.SingletonChildProcess = function (filename) {
var subProcess = null;
var rerun = false;
var spawnSubProcess = function () {
subProcess = childProcess.fork(filename);
subProcess.addListener("exit", function (code, signal) {
subProcess = null;
if (rerun) {
spawnSubProcess();
}
rerun = false;
});
};
return function () {
if (subProcess === null) {
spawnSubProcess();
}
else {
rerun = true;
}
};
};
-65
View File
@@ -1,65 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Transform } from "stream";
export function makeTransform<T>(transform: (chunk: T, encoding?: string, callback?: (error?: Error) => void) => void, flush?: (callback?: (error?: Error) => void) => void): Transform<T> {
return new GulpTransformer<T>(transform, flush);
}
class GulpTransformer<T> extends Transform<T> {
constructor(transform?: (chunk: T, encoding?: string, callback?: (error?: Error) => void) => void, flush?: (callback?: (error?: Error) => void) => void) {
super({ objectMode: true });
transform = transform || ((chunk, encoding, callback) => callback());
if (transform.length < 3) {
this._transform = <any>((chunk: T, encoding: string, callback: (error?: Error) => void) => {
try {
transform.call(this, chunk, encoding);
callback();
}
catch (ex) {
callback(ex);
}
});
}
else {
this._transform = transform.bind(this);
}
flush = flush || (callback => callback());
if (flush.length < 1) {
this._flush = ((callback: (error?: Error) => void) => {
try {
flush.call(this);
callback();
}
catch (ex) {
callback(ex);
}
});
}
else {
this._flush = flush.bind(this);
}
}
}
@@ -18,24 +18,24 @@
* limitations under the License.
*/
/**
* Class inheritance shim.
*
* @param {!Function} derived
* @param {!Function} base
*/
export function __extends(derived: any, base: any): void {
for (var property in base) {
if (base.hasOwnProperty(property)) {
derived[property] = base[property];
}
var fs = require("fs");
var path = require("path");
var Mocha = require("mocha");
console.log("Running tests...");
var mocha = new Mocha({
ui: "tdd",
reporter: "spec"
});
fs.readdirSync("./tests/")
.filter(function (filename) { return filename.match(/^test-.*\.js$/) !== null; })
.forEach(function (filename) { mocha.addFile(path.resolve("./tests", filename)); });
mocha.run(function (failures) {
if (failures > 0) {
throw new Error(failures + " test(s) failed.");
}
function __() {
this.constructor = derived;
}
__.prototype = base.prototype;
derived.prototype = new (<any>__)();
}
});
File diff suppressed because it is too large Load Diff
-194
View File
@@ -1,194 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as ts from "typescript";
export class HasParent {
public parent: HasParent = null;
constructor(public name: string) { }
get fullName(): string {
if (this.parent === null) {
return this.name;
}
var parent = this.parent;
if (parent instanceof Namespace) {
return parent.getMemberFullName(this);
}
return `${ this.parent.fullName }.${ this.name }`;
}
}
export class Module extends HasParent {
public parent: Module = null;
public members: { [name: string]: ModuleMember } = Object.create(null);
constructor(public fileName: string) {
super(fileName);
}
}
export type ModuleMember = Class | Interface | Function | Property | Enum | Reference;
export type ModuleMemberWithoutReference = Class | Interface | Function | Property | Enum;
export class Reference {
constructor(public moduleName: string, public name: string, public isPrivate: boolean) { }
}
export class Namespace extends HasParent {
public parent: Namespace = null;
public members: { [name: string]: NamespaceMember } = Object.create(null);
constructor(name: string) {
super(name);
}
getMemberFullName(member: HasParent) {
return `${ this.fullName }.${ member.name }`;
}
}
export type NamespaceMember = Class | Interface | Function | Property | Enum;
export class Class extends HasParent {
public parent: Module | Namespace = null;
public unresolvedBaseType: UnresolvedType | TypeReference | IntrinsicTypeReference;
public baseType: TypeReference | IntrinsicTypeReference = null;
public unresolvedInterfaces: (UnresolvedType | TypeReference | IntrinsicTypeReference)[];
public interfaces: (TypeReference | IntrinsicTypeReference)[] = [];
public members: { [name: string]: InterfaceMember } = Object.create(null);
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], public parameters: Parameter[], baseType: UnresolvedType | TypeReference | IntrinsicTypeReference, interfaces: (UnresolvedType | TypeReference | IntrinsicTypeReference)[], public isAbstract: boolean, public isPrivate: boolean) {
super(name);
this.unresolvedBaseType = baseType;
this.unresolvedInterfaces = interfaces;
}
}
export type InterfaceMember = Property | Function;
export class Interface extends HasParent {
public parent: Module | Namespace = null;
public unresolvedBaseTypes: (UnresolvedType | TypeReference | IntrinsicTypeReference)[];
public baseTypes: (TypeReference | IntrinsicTypeReference)[] = [];
public members: { [name: string]: InterfaceMember } = Object.create(null);
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], baseTypes: (UnresolvedType | TypeReference | IntrinsicTypeReference)[], public isPrivate: boolean) {
super(name);
this.unresolvedBaseTypes = baseTypes;
}
}
export class Function extends HasParent {
public parent: Module | Namespace | Class | Interface = null;
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], public parameters: Parameter[], public returnType: ReturnType, public isAbstract: boolean, public isPrivate: boolean, public isProtected: boolean, public isStatic: boolean) {
super(name);
}
}
export class Property extends HasParent {
public parent: Module | Namespace | Class | Interface = null;
public getter: Getter = null;
public setter: Setter = null;
constructor(name: string) {
super(name);
}
}
export class Getter {
constructor(public astNode: ts.Node, public description: string, public type: string, public isPrivate: boolean) { }
}
export class Setter {
constructor(public astNode: ts.Node, public description: string, public type: string, public isPrivate: boolean) { }
}
export class Parameter {
public subParameters: Parameter[] = [];
constructor(public name: string, public description: string, public type: string) { }
}
export class ReturnType {
constructor(public description: string, public type: string) { }
}
export class Enum extends HasParent {
public parent: Module | Namespace = null;
public members: EnumMember[] = [];
constructor(name: string, public astNode: ts.Node, public description: string, public isPrivate: boolean) {
super(name);
}
}
export class EnumMember extends HasParent {
public parent: Enum = null;
constructor(name: string, public description: string, public value: number) {
super(name);
}
}
export class TypeReference {
constructor(public type: NamespaceMember, public generics: (TypeReference | IntrinsicTypeReference)[]) { }
get name(): string {
return this.type.name;
}
get fullName(): string {
return this.type.fullName;
}
}
export class IntrinsicTypeReference {
public fullName: string;
constructor(public name: string) {
this.fullName = name;
}
}
export class UnresolvedType {
constructor(public symbol: ts.Symbol, public generics: (UnresolvedType | IntrinsicTypeReference)[]) { }
}
export type HasStringGenerics = Class | Interface | Function;
export type HasGenerics = HasStringGenerics | TypeReference;
export type CanBePrivate = Class | Interface | Function | Getter | Setter | Enum | Reference;
export type CanBeProtected = Function;
export type CanBeStatic = Function;
-465
View File
@@ -1,465 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from "fs";
import * as path from "path";
import { Transform } from "stream";
import * as ts from "typescript";
import Vinyl = require("vinyl");
import * as AST from "./ast";
import { makeTransform } from "../helpers";
import { walk } from "./walker";
export interface GulpCompilerHost extends ts.CompilerHost {
setOutputStream(outputStream: Transform<Vinyl>): void;
setOutputPathsRelativeTo(path: string): void;
}
export class Compiler {
private _projectRoot: string = null;
private _program: ts.Program = null;
constructor(private _host: GulpCompilerHost = CompilerHost()) { }
compile(projectConfigFile: Vinyl) {
this._projectRoot = path.dirname(projectConfigFile.path);
var projectConfig = parseConfigFile(JSON.parse(projectConfigFile.contents.toString()), this._projectRoot);
this._host.setOutputPathsRelativeTo(this._projectRoot);
this._program = ts.createProgram(projectConfig.fileNames, projectConfig.options, this._host);
var syntacticDiagnostics = this._program.getSyntacticDiagnostics();
if (syntacticDiagnostics.length > 0) {
this._reportDiagnostics(syntacticDiagnostics);
throw new Error("There were one or more syntactic diagnostics.");
}
var globalDiagnostics = this._program.getGlobalDiagnostics();
if (globalDiagnostics.length > 0) {
this._reportDiagnostics(globalDiagnostics);
throw new Error("There were one or more global diagnostics.");
}
var semanticDiagnostics = this._program.getSemanticDiagnostics();
if (semanticDiagnostics.length > 0) {
this._reportDiagnostics(semanticDiagnostics);
throw new Error("There were one or more semantic diagnostics.");
}
};
writeFiles(outputStream: Transform<Vinyl>) {
this._host.setOutputStream(outputStream);
var emitDiagnostics = this._program.emit().diagnostics;
if (emitDiagnostics.length > 0) {
this._reportDiagnostics(emitDiagnostics);
throw new Error("There were one or more emit diagnostics.");
}
};
get projectRoot(): string {
return this._projectRoot;
}
get typeChecker(): ts.TypeChecker {
return this._program.getTypeChecker();
}
get sourceFiles(): ts.SourceFile[] {
return this._program.getSourceFiles();
}
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
for (let diagnostic of diagnostics) {
var message = "";
if (diagnostic.file) {
var location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
}
message +=
ts.DiagnosticCategory[diagnostic.category].toLowerCase() +
` TS${ diagnostic.code }: ` +
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
console.error(message);
}
};
}
const typeScriptModulePath = path.resolve("./node_modules/typescript/bin");
function CompilerHost(): GulpCompilerHost {
var _outputStream: Transform<Vinyl> = null;
var _outputPathsRelativeTo: string = null;
return {
setOutputStream(outputStream: Transform<Vinyl>): void {
_outputStream = outputStream;
},
setOutputPathsRelativeTo(path: string): void {
_outputPathsRelativeTo = path;
},
// ts.CompilerHost members
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError: (message: string) => void): ts.SourceFile {
try {
var text = fs.readFileSync(fileName, { encoding: "utf8" });
if (path.basename(fileName) === "lib.dom.d.ts") {
var startOfES6Extensions = text.indexOf("/// IE11 ECMAScript Extensions");
var endOfES6Extensions = text.indexOf("/// ECMAScript Internationalization API", startOfES6Extensions);
text = text.substring(0, startOfES6Extensions) + text.substring(endOfES6Extensions);
}
}
catch (ex) {
if (onError) {
onError(ex.message);
}
}
return (text !== undefined) ? ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5) : undefined;
},
getDefaultLibFileName: () => path.join(typeScriptModulePath, "lib.dom.d.ts"),
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError: (message?: string) => void) {
_outputStream.push(new Vinyl({
base: _outputPathsRelativeTo,
path: fileName,
contents: new Buffer(data)
}));
},
getCurrentDirectory: () => path.resolve("."),
getCanonicalFileName: (fileName: string) => ts.normalizeSlashes(path.resolve(fileName)),
useCaseSensitiveFileNames: () => true,
getNewLine: () => "\n",
};
}
function WatchCompilerHost(onChangeCallback: () => void): GulpCompilerHost {
var compilerHost = CompilerHost();
var _onChangeCallback = onChangeCallback;
var _sourceFiles = Object.create(null);
var _filesChangedSinceLast: string[] = [];
var _super_getSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (fileName, languageVersion, onError) => {
if (fileName in _sourceFiles) {
return _sourceFiles[fileName];
}
var result = _super_getSourceFile(fileName, languageVersion, onError);
if (result !== undefined) {
_sourceFiles[fileName] = result;
}
watchFile(fileName);
return result;
};
function watchFile(fileName: string): void {
function watchFileCallback(currentFile: fs.Stats, previousFile: fs.Stats) {
if (currentFile.mtime >= previousFile.mtime) {
fileChangedCallback(fileName);
}
else {
fs.unwatchFile(fileName, watchFileCallback);
fileChangedCallback(fileName);
}
}
fs.watchFile(fileName, { interval: 500 }, watchFileCallback);
}
function fileChangedCallback(fileName: string): void {
delete _sourceFiles[fileName];
if (_filesChangedSinceLast.length === 0) {
setTimeout(() => {
_filesChangedSinceLast = [];
_onChangeCallback();
}, 100);
}
_filesChangedSinceLast.push(fileName);
}
return compilerHost;
}
export function gulp(root: string, rootNamespaceName: string): Transform<Vinyl> {
var compiler = new Compiler();
return makeTransform(function (projectConfigFile: Vinyl): void {
var self: Transform<Vinyl> = this;
console.log("Compiling " + projectConfigFile.path + "...");
compiler.compile(projectConfigFile);
var walkResult = walk(compiler, root, rootNamespaceName);
addJSDocComments(walkResult.modules);
compiler.writeFiles(self);
console.log("Compile succeeded.");
});
}
export function watch(root: string, rootNamespaceName: string): Transform<Vinyl> {
return makeTransform(function (projectConfigFile: Vinyl): void {
var self: Transform<Vinyl> = this;
function compile() {
console.log("Compiling " + projectConfigFile.path + "...");
compiler.compile(projectConfigFile);
compiler.writeFiles(self);
console.log("Compile succeeded.");
self.push(new Vinyl({
base: this._outputPathsRelativeTo,
path: "END",
contents: new Buffer("")
}));
};
var compilerHost = WatchCompilerHost(() => {
try {
compile();
}
catch (ex) {
console.error("Compile failed." + ex.stack);
}
});
var compiler = new Compiler(compilerHost);
compile();
console.log("Listening for changes...");
}, callback => { });
}
function addJSDocComments(modules: { [name: string]: AST.Module }): void {
function visitor(current: AST.Module | AST.ModuleMember | AST.InterfaceMember) {
if (current instanceof AST.Module) {
for (let memberName of Object.keys(current.members)) {
visitor(current.members[memberName]);
}
return;
}
var newComments: string[] = [];
if (current instanceof AST.Class) {
newComments.push("@constructor");
if (current.baseType !== null) {
var baseType = current.baseType;
newComments.push(
"@extends {" +
baseType.fullName + (
(baseType instanceof AST.TypeReference && baseType.generics.length) > 0 ?
(".<" + (<AST.TypeReference>baseType).generics.map(generic => generic.fullName).join(", ") + ">") :
""
) +
"}"
);
}
if (current.interfaces.length > 0) {
current.interfaces.forEach(interfase => {
newComments.push(
"@implements {" +
interfase.fullName + (
(interfase instanceof AST.TypeReference && interfase.generics.length > 0) ?
(".<" + interfase.generics.map(generic => generic.fullName).join(", ") + ">") :
""
) +
"}"
);
});
}
Object.keys(current.members).forEach(memberName => visitor(current.members[memberName]));
}
else if (current instanceof AST.Enum) {
newComments.push("@enum");
}
else if (current instanceof AST.Reference) {
return;
}
if ((<AST.HasParent><any>current).parent instanceof AST.Namespace) {
newComments.push("@memberOf " + (<AST.HasParent><any>current).parent.fullName);
}
if ((<AST.HasStringGenerics>current).generics !== undefined && (<AST.HasStringGenerics>current).generics.length > 0) {
newComments.push("@template " + (<AST.HasStringGenerics>current).generics.join(", "));
}
if ((<AST.CanBePrivate><any>current).isPrivate) {
newComments.push("@private");
}
if ((<AST.CanBeProtected>current).isProtected) {
newComments.push("@protected");
}
if ((<AST.CanBeStatic>current).isStatic) {
newComments.push("@static");
}
if (newComments.length > 0) {
if (current instanceof AST.Property) {
var nodes: ts.Node[] = [];
if (current.getter !== null) { nodes.push(current.getter.astNode); }
if (current.setter !== null && nodes[0] !== current.setter.astNode) { nodes.push(current.setter.astNode); }
for (let node of nodes) {
(<any>node)["gulp-typescript-new-comment"] = newComments;
}
}
else {
(<any>(<AST.Class | AST.Interface | AST.Function | AST.Enum>current).astNode)["gulp-typescript-new-comment"] = newComments;
}
}
}
for (let moduleName of Object.keys(modules)) {
visitor(modules[moduleName]);
}
}
function parseConfigFile(json: { compilerOptions: ts.CompilerOptions }, basePath: string): { options: ts.CompilerOptions, fileNames: string[] } {
var options = json.compilerOptions;
options.module = (<any>ts).ModuleKind[options.module];
options.target = (<any>ts).ScriptTarget[options.target];
var fileNames: string[] = [];
function walk(directory: string) {
fs.readdirSync(directory).forEach(entry => {
var entryPath = path.join(directory, entry);
var stat = fs.lstatSync(entryPath);
if (stat.isFile()) {
if (path.extname(entry) === ".ts") {
fileNames.push(entryPath);
}
}
else if (stat.isDirectory()) {
walk(entryPath);
}
});
}
walk(basePath);
return { options, fileNames };
}
class FakeSourceFile {
public text: string;
public lineMap: number[];
constructor(originalSourceFile: ts.SourceFile) {
this.text = originalSourceFile.text;
this.lineMap = ts.getLineStarts(originalSourceFile).slice();
}
addComment(originalComment: ts.CommentRange, newComments: string[]): ts.CommentRange {
var pos = this.text.length;
this.text += "/**\n";
this.lineMap.push(this.text.length);
var originalCommentLines = this.text.substring(originalComment.pos, originalComment.end).split("\n");
originalCommentLines.shift();
originalCommentLines = originalCommentLines.map(line => line.replace(/^\s+/, " "));
if (originalCommentLines.length > 1) {
originalCommentLines.splice(originalCommentLines.length - 1, 0, " *");
}
for (let newComment of newComments) {
originalCommentLines.splice(originalCommentLines.length - 1, 0, " * " + newComment);
}
for (let newCommentLine of originalCommentLines) {
this.text += newCommentLine + "\n";
this.lineMap.push(this.text.length);
}
var end = this.text.length;
return { pos, end, hasTrailingNewLine: originalComment.hasTrailingNewLine, sourceFile: this };
}
}
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
export var oldGetLeadingCommentRangesOfNode: (node: ts.Node, sourceFileOfNode: ts.SourceFile) => ts.CommentRange[] = ts.getLeadingCommentRangesOfNode.bind(ts);
ts.getLeadingCommentRangesOfNode = (node: ts.Node, sourceFileOfNode: ts.SourceFile) => {
sourceFileOfNode = sourceFileOfNode || ts.getSourceFileOfNode(node);
var originalComments = oldGetLeadingCommentRangesOfNode(node, sourceFileOfNode);
if (originalComments !== undefined && (<any>node)["gulp-typescript-new-comment"] !== undefined) {
var fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
if (fakeSourceFile === undefined) {
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
}
originalComments[originalComments.length - 1] = fakeSourceFile.addComment(originalComments[originalComments.length - 1], (<any>node)["gulp-typescript-new-comment"]);
}
return originalComments;
};
var oldWriteCommentRange = ts.writeCommentRange.bind(ts);
ts.writeCommentRange = (currentSourceFile: ts.SourceFile, writer: ts.EmitTextWriter, comment: ts.CommentRange, newLine: string) => {
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
}
return oldWriteCommentRange(currentSourceFile, writer, comment, newLine);
};
-864
View File
@@ -1,864 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from "path";
import * as ts from "typescript";
import { Compiler, oldGetLeadingCommentRangesOfNode } from "./compiler";
import * as AST from "./ast";
interface JSDoc {
description: string;
isAbstract: boolean;
parameters: { [name: string]: AST.Parameter };
returnType: AST.ReturnType;
typeAnnotation: string;
}
class WalkerScope {
private _scopes: AST.HasParent[] = [];
get current(): AST.HasParent {
return (this._scopes.length > 0) ? this._scopes[this._scopes.length - 1] : null;
}
enter<T extends AST.HasParent>(scope: T): T {
scope.parent = this.current;
this._scopes.push(scope);
return scope;
}
leave(): void {
this._scopes.pop();
}
}
class Walker {
private _typeChecker: ts.TypeChecker;
private _globalNS: AST.Namespace = new AST.Namespace("Global");
private _scope: WalkerScope = new WalkerScope();
private _currentSourceFile: ts.SourceFile;
public modules: { [name: string]: AST.Module } = Object.create(null);
public namespaces: { [name: string]: AST.Namespace } = Object.create(null);
constructor(private _compiler: Compiler) {
this._typeChecker = _compiler.typeChecker;
this._globalNS.getMemberFullName = member => member.name;
}
walk(sourceFile: ts.SourceFile): void {
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
if (!(moduleName in this.modules)) {
this.modules[moduleName] = new AST.Module(moduleName);
}
var module = this._scope.enter(this.modules[moduleName]);
this._currentSourceFile = sourceFile;
for (let statement of sourceFile.statements) {
this._walk(statement, module);
}
this._scope.leave();
}
private _walk(node: ts.Node, parent: AST.Module): void {
switch (node.kind) {
case ts.SyntaxKind.VariableStatement:
this._visitVariableStatement(<ts.VariableStatement>node, parent);
break;
case ts.SyntaxKind.FunctionDeclaration:
this._visitFunctionDeclaration(<ts.FunctionDeclaration>node, parent);
break;
case ts.SyntaxKind.ClassDeclaration:
this._visitClassDeclaration(<ts.ClassDeclaration>node, parent);
break;
case ts.SyntaxKind.InterfaceDeclaration:
this._visitInterfaceDeclaration(<ts.InterfaceDeclaration>node, parent);
break;
case ts.SyntaxKind.EnumDeclaration:
this._visitEnumDeclaration(<ts.EnumDeclaration>node, parent);
break;
case ts.SyntaxKind.ImportDeclaration:
this._visitImportDeclaration(<ts.ImportDeclaration>node, parent);
break;
case ts.SyntaxKind.ExportDeclaration:
this._visitExportDeclaration(<ts.ExportDeclaration>node, parent);
break;
case ts.SyntaxKind.ExpressionStatement:
case ts.SyntaxKind.ForOfStatement:
case ts.SyntaxKind.IfStatement:
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _walkClassMember(node: ts.Node, clazz: AST.Class): void {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(<ts.PropertyDeclaration>node, clazz);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(<ts.MethodDeclaration>node, clazz);
break;
case ts.SyntaxKind.GetAccessor:
this._visitGetAccessor(<ts.AccessorDeclaration>node, clazz);
break;
case ts.SyntaxKind.SetAccessor:
this._visitSetAccessor(<ts.AccessorDeclaration>node, clazz);
break;
case ts.SyntaxKind.TypeParameter:
case ts.SyntaxKind.Parameter:
case ts.SyntaxKind.Constructor:
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _walkInterfaceMember(node: ts.Node, interfase: AST.Interface): void {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(<ts.PropertyDeclaration>node, interfase);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(<ts.MethodDeclaration>node, interfase);
break;
case ts.SyntaxKind.TypeParameter:
case ts.SyntaxKind.CallSignature:
case ts.SyntaxKind.ConstructSignature:
case ts.SyntaxKind.IndexSignature:
break;
default:
console.error(node.kind, (<any>ts).SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _visitProperty(node: ts.PropertyDeclaration, parent: AST.Class | AST.Interface) {
if ((node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private) {
return;
}
var jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Field ${ ts.getTextOfNode(node.name) } has no @type annotation.`);
jsDoc.typeAnnotation = "*";
}
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
parent.members[property.name] = property;
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
this._scope.leave();
}
private _visitMethod(node: ts.MethodDeclaration, parent: AST.Class | AST.Interface) {
var jsDoc = this._parseJSDoc(node);
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on method ${ ts.getTextOfNode(node.name) }`
);
if (jsDoc.returnType === null && (node.type === undefined || node.type.kind !== ts.SyntaxKind.VoidKeyword)) {
this._notifyIncorrectJsDoc(`Missing @return annotation for method ${ ts.getTextOfNode(node.name) }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
var isProtected = (node.flags & ts.NodeFlags.Protected) === ts.NodeFlags.Protected;
var isStatic = (node.flags & ts.NodeFlags.Static) === ts.NodeFlags.Static;
var generics = this._getGenericsOfSignatureDeclaration(node);
var method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
parent.members[method.name] = method;
this._scope.leave();
}
private _visitGetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
var jsDoc = this._parseJSDoc(node);
var name = ts.getTextOfNode(node.name);
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
var property = <AST.Property>clazz.members[name];
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
clazz.members[property.name] = property;
this._scope.leave();
}
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Getter ${ name } has no @type annotation.`);
}
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, isPrivate);
}
private _visitSetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
var jsDoc = this._parseJSDoc(node);
var name = ts.getTextOfNode(node.name);
var isPrivate = (node.flags & ts.NodeFlags.Private) === ts.NodeFlags.Private;
var property = <AST.Property>clazz.members[name];
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
clazz.members[property.name] = property;
this._scope.leave();
}
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Setter ${ name } has no @type annotation.`);
}
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, isPrivate);
}
private _visitVariableStatement(node: ts.VariableStatement, parent: AST.Module): void {
if (node.declarationList.declarations.length > 1) {
return;
}
var declaration = node.declarationList.declarations[0];
if ((declaration.flags & ts.NodeFlags.Ambient) === ts.NodeFlags.Ambient) {
return;
}
var jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
return;
}
var property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
parent.members[property.name] = property;
this._scope.leave();
}
private _visitFunctionDeclaration(node: ts.FunctionDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
var generics = this._getGenericsOfSignatureDeclaration(node);
var parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on function ${ node.name.text }`
);
if (node.type === undefined) {
this._notifyIncorrectJsDoc(`Missing return type annotation for function ${ node.name.text }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
else if (jsDoc.returnType === null && node.type.kind !== ts.SyntaxKind.VoidKeyword) {
this._notifyIncorrectJsDoc(`Missing @return annotation for function ${ node.name.text }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
var freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
parent.members[freeFunction.name] = freeFunction;
this._scope.leave();
}
private _visitClassDeclaration(node: ts.ClassDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
var baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
var baseType: AST.UnresolvedType = null;
if (baseTypeHeritageClauseElement !== null) {
baseType = new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(baseTypeHeritageClauseElement).symbol,
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement)
);
}
var interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type)
));
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
var generics = this._getGenericsOfInterfaceType(type);
var parameters: AST.Parameter[] = [];
if (type.symbol.members["__constructor"] !== undefined) {
parameters = this._connectParameters((<ts.ConstructorDeclaration>type.symbol.members["__constructor"].declarations[0]).parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on constructor in class ${ node.name.text }`
);
}
else if (Object.keys(jsDoc.parameters).length > 0) {
this._notifyIncorrectJsDoc("There are @param annotations on this class but it has no constructors.");
}
var clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
parent.members[clazz.name] = clazz;
ts.forEachValue(type.symbol.exports, symbol => {
if (symbol.name === "prototype") {
return;
}
for (let declaration of symbol.declarations) {
this._walkClassMember(declaration, clazz);
}
});
ts.forEachValue(type.symbol.members, symbol => {
for (let declaration of symbol.declarations) {
this._walkClassMember(declaration, clazz);
}
});
this._scope.leave();
}
private _visitInterfaceDeclaration(node: ts.InterfaceDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
var baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type)
));
var existingInterfaceType = parent.members[node.name.text];
if (existingInterfaceType !== undefined) {
return;
}
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
var type = <ts.InterfaceType>this._typeChecker.getTypeAtLocation(node);
var generics = this._getGenericsOfInterfaceType(type);
var interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
parent.members[interfase.name] = interfase;
ts.forEachValue(type.symbol.members, symbol => {
for (let declaration of symbol.declarations) {
this._walkInterfaceMember(declaration, interfase);
}
});
this._scope.leave();
}
private _visitEnumDeclaration(node: ts.EnumDeclaration, parent: AST.Module): void {
var jsDoc = this._parseJSDoc(node);
var existingEnumType = parent.members[node.name.text];
if (existingEnumType !== undefined) {
return;
}
var isPrivate = (node.flags & ts.NodeFlags.Export) !== ts.NodeFlags.Export;
var type = this._typeChecker.getTypeAtLocation(node);
var enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
parent.members[enumType.name] = enumType;
ts.forEachValue(type.symbol.exports, symbol => {
this._visitEnumMember(<ts.EnumMember>symbol.declarations[0], enumType);
});
this._scope.leave();
}
private _visitEnumMember(node: ts.EnumMember, parent: AST.Enum): void {
var jsDoc = this._parseJSDoc(node);
var value = (node.initializer === undefined) ? null : parseInt((<ts.LiteralExpression>node.initializer).text);
var enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
parent.members.push(enumMember);
this._scope.leave();
}
private _visitImportDeclaration(node: ts.ImportDeclaration, parent: AST.Module): void {
if (node.importClause === undefined) {
// import "foo";
return;
}
if (node.importClause.namedBindings === undefined) {
throw new Error("Default import is not supported.");
}
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
if ((<ts.NamespaceImport>node.importClause.namedBindings).name !== undefined) {
// import * as foo from "baz";
parent.members[(<ts.NamespaceImport>node.importClause.namedBindings).name.text] = new AST.Reference(moduleName, "*", true);
}
else if ((<ts.NamedImports>node.importClause.namedBindings).elements !== undefined) {
// import { foo, bar } from "baz";
for (let element of (<ts.NamedImports>node.importClause.namedBindings).elements) {
var importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, true);
}
}
else {
throw new Error("Unrecognized import declaration syntax.");
}
}
private _visitExportDeclaration(node: ts.ExportDeclaration, parent: AST.Module): void {
if (node.moduleSpecifier !== undefined) {
// export { foo } from "bar";
var moduleName = this._resolve((<ts.LiteralExpression>node.moduleSpecifier).text, parent);
for (let element of node.exportClause.elements) {
var importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, false);
}
}
else {
// export { foo };
for (let element of node.exportClause.elements) {
(<AST.CanBePrivate><any>parent.members[element.name.text]).isPrivate = false;
}
}
}
private _resolve(relativeModuleName: string, currentModule: AST.Module): string {
var result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
if (result[0] !== ".") {
result = `./${ result }`;
}
return result;
}
private _parseJSDoc(node: ts.Node): JSDoc {
var comments = oldGetLeadingCommentRangesOfNode(node, this._currentSourceFile);
if (comments === undefined) {
comments = [];
}
if (comments.length > 1) {
comments = [comments[comments.length - 1]];
}
var comment =
(comments.length === 0) ?
"" :
this._currentSourceFile.text.substring(comments[0].pos, comments[0].end);
var commentStartIndex = comment.indexOf("/**");
var commentEndIndex = comment.lastIndexOf("*/");
var lines =
(commentStartIndex === -1 || commentEndIndex === -1) ?
[] :
comment.substring(commentStartIndex + 2, commentEndIndex).split("\n").map(line => {
var match = line.match(/^[ \t]*\* (.*)/);
if (match === null) {
return "";
}
return match[1];
}).filter(line => line.length > 0);
var rootDescription = "";
var parameters: { [name: string]: AST.Parameter } = Object.create(null);
var typeAnnotation: string = null;
var returnType: AST.ReturnType = null;
var isAbstract = false;
var lastRead: { description: string } = null;
for (let line of lines) {
var firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
var firstWord = firstWordMatch[1];
var remainingLine = line.substring(firstWordMatch[0].length);
if (firstWord[0] === "@") {
lastRead = null;
}
switch (firstWord) {
case "@abstract":
isAbstract = true;
break;
case "@param":
var type: string;
[type, remainingLine] = this._readType(remainingLine);
var [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
var subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
if (subParameterMatch === null) {
parameters[name] = lastRead = new AST.Parameter(name, description, type);
}
else {
var parentName = subParameterMatch[1] || subParameterMatch[3];
var childName = subParameterMatch[2] || subParameterMatch[4];
var parentParameter = parameters[parentName];
parentParameter.subParameters.push(lastRead = new AST.Parameter(childName, description, type));
}
break;
case "@return":
var [type, description] = this._readType(remainingLine);
returnType = lastRead = new AST.ReturnType(description, type);
break;
case "@type":
[typeAnnotation] = this._readType(remainingLine);
break;
default:
if (lastRead !== null) {
lastRead.description += "\n" + line;
}
else {
rootDescription += ((rootDescription.length > 0) ? "\n" : "") + line;
}
break;
}
}
return {
description: rootDescription,
isAbstract: isAbstract,
parameters: parameters,
returnType: returnType,
typeAnnotation: typeAnnotation,
};
}
private _readType(remainingLine: string): [string, string] {
if (remainingLine[0] !== "{") {
return ["*", remainingLine];
}
var index = -1;
var numberOfUnterminatedBraces = 0;
for (var i = 0; i < remainingLine.length; i++) {
if (remainingLine[i] === "{") {
numberOfUnterminatedBraces++;
}
else if (remainingLine[i] === "}") {
numberOfUnterminatedBraces--;
if (numberOfUnterminatedBraces === 0) {
index = i;
break;
}
}
}
if (index === -1) {
throw new Error("Unterminated type specifier.");
}
var type = remainingLine.substr(1, index - 1);
remainingLine = remainingLine.substr(index + 1).replace(/^\s+/, "");
return [type, remainingLine];
}
private _getGenericsOfSignatureDeclaration(signatureDeclaration: ts.SignatureDeclaration): string[] {
if (signatureDeclaration.typeParameters === undefined) {
return [];
}
return signatureDeclaration.typeParameters.map(typeParameter => typeParameter.name.text);
}
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.TypeReferenceNode | ts.HeritageClauseElement): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
if (typeReferenceNode.typeArguments === undefined) {
return [];
}
var typeReference = <ts.TypeReference>this._typeChecker.getTypeAtLocation(typeReferenceNode);
return typeReference.typeArguments.map(typeArgument => {
if ((<ts.IntrinsicType>typeArgument).intrinsicName !== undefined) {
return new AST.IntrinsicTypeReference((<ts.IntrinsicType>typeArgument).intrinsicName);
}
return new AST.UnresolvedType(typeArgument.symbol, []);
});
}
private _getGenericsOfInterfaceType(interfaceType: ts.InterfaceType): string[] {
if (interfaceType.typeParameters === undefined) {
return [];
}
return interfaceType.typeParameters.map(typeParameter => {
return typeParameter.symbol.name;
});
}
private _connectParameters(astParameters: ts.ParameterDeclaration[], jsDocParameters: { [name: string]: AST.Parameter }, onMissingMessageCallback: (parameterName: string) => string) {
return astParameters.map(parameter => {
var parameterName = (<ts.Identifier>parameter.name).text;
if (parameterName[0] === "_") {
parameterName = parameterName.substr(1);
}
var jsDocParameter = jsDocParameters[parameterName];
if (jsDocParameter === undefined) {
this._notifyIncorrectJsDoc(onMissingMessageCallback.call(this, parameterName));
jsDocParameter = new AST.Parameter(parameterName, "*", "");
}
return jsDocParameter;
});
}
private _notifyIncorrectJsDoc(message: string): void {
var fileName = path.basename(this._currentSourceFile.fileName);
if (fileName === "lib.core.d.ts" || fileName === "lib.dom.d.ts") {
return;
}
throw new Error(`${ fileName }: ${ this._scope.current.fullName }: ${ message }`);
}
link(rootNamespaceName: string): void {
for (let moduleName of Object.keys(this.modules)) {
var module = this.modules[moduleName];
for (let memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if (member instanceof AST.Class) {
if (member.unresolvedBaseType instanceof AST.UnresolvedType) {
member.baseType = this._resolveTypeReference(<AST.UnresolvedType>member.unresolvedBaseType);
}
else {
member.baseType = <AST.TypeReference | AST.IntrinsicTypeReference>member.unresolvedBaseType;
}
member.interfaces = member.unresolvedInterfaces.map(interfase => {
if (interfase instanceof AST.UnresolvedType) {
return this._resolveTypeReference(interfase);
}
return <AST.TypeReference | AST.IntrinsicTypeReference>interfase;
});
}
else if (member instanceof AST.Interface) {
member.baseTypes = member.unresolvedBaseTypes.map(baseType => {
if (baseType instanceof AST.UnresolvedType) {
return this._resolveTypeReference(baseType);
}
return <AST.TypeReference | AST.IntrinsicTypeReference>baseType;
});
}
else if (member instanceof AST.Enum) {
var value = 0;
for (let enumMember of member.members) {
if (enumMember.value === null) {
enumMember.value = value;
}
else {
value = enumMember.value;
}
value++;
}
}
}
}
this.namespaces[rootNamespaceName] = this._scope.enter(new AST.Namespace(rootNamespaceName));
this._moduleToNamespace(this.modules["./index"]);
this._scope.leave();
}
private _moduleToNamespace(module: AST.Module): void {
for (let memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if (member instanceof AST.Reference) {
if ((<AST.Reference>member).isPrivate) {
continue;
}
if (member.name === "*") {
var newNamespace = this._scope.enter(new AST.Namespace(memberName));
var existingNamespace = this.namespaces[newNamespace.fullName];
if (existingNamespace !== undefined) {
this._scope.leave();
this._scope.enter(existingNamespace);
}
else {
this.namespaces[newNamespace.fullName] = newNamespace;
}
this._moduleToNamespace(this.modules[(<AST.Reference>member).moduleName]);
this._scope.leave();
}
else {
while (member instanceof AST.Reference) {
member = this.modules[(<AST.Reference>member).moduleName].members[member.name];
}
this._scope.enter(<AST.NamespaceMember><any>member);
this._scope.leave();
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
}
}
else if (!(<AST.CanBePrivate><any>member).isPrivate) {
this._scope.enter(<AST.NamespaceMember>member);
this._scope.leave();
(<AST.Namespace>this._scope.current).members[member.name] = <AST.NamespaceMember>member;
}
}
}
private _resolveTypeReference(unresolvedType: AST.UnresolvedType): AST.TypeReference {
var node: ts.Node = unresolvedType.symbol.declarations[0];
while (node.kind !== ts.SyntaxKind.SourceFile) {
node = node.parent;
}
var sourceFile = <ts.SourceFile>node;
var moduleName = this._moduleNameFromFileName(sourceFile.fileName);
var module = this.modules[moduleName];
var result = module.members[unresolvedType.symbol.name];
if (result === undefined) {
throw new Error(`Type ${ unresolvedType.symbol.name } could not be resolved.`);
}
while (result instanceof AST.Reference) {
result = this.modules[(<AST.Reference>result).moduleName].members[result.name];
}
var resultGenerics = unresolvedType.generics.map(generic => {
if (generic instanceof AST.UnresolvedType) {
return this._resolveTypeReference(generic);
}
return <AST.IntrinsicTypeReference>generic;
});
return new AST.TypeReference(<AST.NamespaceMember><any>result, resultGenerics);
}
private _moduleNameFromFileName(fileName: string): string {
var result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
result = result.substr(0, result.length - ".ts".length);
if (result[0] !== ".") {
result = `./${ result }`;
}
return result;
}
}
export function walk(compiler: Compiler, root: string, rootNamespaceName: string) {
var sourceFiles = compiler.sourceFiles;
var rootFileName = ts.normalizeSlashes(path.resolve(root));
var rootSourceFile = sourceFiles.filter(sourceFile => sourceFile.fileName === rootFileName)[0];
var walker = new Walker(compiler);
// Walk
for (let sourceFile of sourceFiles) {
if (
path.basename(sourceFile.fileName) === "lib.core.d.ts" ||
path.basename(sourceFile.fileName) === "lib.dom.d.ts" ||
sourceFile.fileName.substr(-"references.d.ts".length) === "references.d.ts"
) {
continue;
}
walker.walk(sourceFile);
}
// Link base types and set enum member values if unspecified.
walker.link(rootNamespaceName);
// Return types
return { namespaces: walker.namespaces, modules: walker.modules };
}
-61
View File
@@ -1,61 +0,0 @@
// Type definitions for Node.js v0.12.0
// Project: http://nodejs.org/
// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/borisyankov/DefinitelyTyped>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
interface Buffer { }
declare var Buffer: {
new (str: string): Buffer;
prototype: Buffer;
concat(list: Buffer[]): Buffer;
};
declare module "fs" {
export function lstatSync(path: string): Stats;
export function readdirSync(path: string): string[];
export function readFileSync(filename: string, options: { encoding: string }): string;
export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void;
export function watchFile(filename: string, options: { interval?: number }, listener: (curr: Stats, prev: Stats) => void): void;
interface Stats {
isDirectory(): boolean;
isFile(): boolean;
mtime: Date;
}
}
declare module "path" {
export function basename(p: string, ext?: string): string;
export function dirname(p: string): string;
export function extname(p: string): string;
export function join(...paths: string[]): string;
export function relative(from: string, to: string): string;
export function resolve(...pathSegments: string[]): string;
}
declare module "stream" {
export class Transform<T> {
constructor(opts?: { objectMode?: boolean; });
_transform(chunk: T, encoding: string, callback: (error?: Error) => void): void;
_flush(callback: (error?: Error) => void): void;
push(chunk: T, encoding?: string): boolean;
}
}
// Type definitions for vinyl 0.4.3
// Project: https://github.com/wearefractal/vinyl
// Definitions by: vvakame <https://github.com/vvakame/>, jedmao <https://github.com/jedmao>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
declare module "vinyl" {
class File {
constructor(options?: { path: string; contents: Buffer; base?: string; });
path: string;
contents: Buffer;
}
export = File;
}
+167 -397
View File
@@ -21,343 +21,170 @@
var fs = require("fs");
var path = require("path");
var SourceMap = require("uglify-js/node_modules/source-map");
var UglifyJS = require("uglify-js");
var Vinyl = require("vinyl");
var makeTransform = require("./helpers.js").makeTransform;
var Run = (function () {
function Run(entry, outputLibraryName, unusedVarsToIgnore) {
this._entry = path.resolve(entry).replace(/\\/g, "/");
this._outputLibraryName = outputLibraryName;
this._unusedVarsToIgnore = unusedVarsToIgnore;
this._root = UglifyJS.parse(
'(function (root, factory) {\n' +
' var global = this;\n' +
' if (typeof exports === "object" && typeof module === "object") {\n' +
' module.exports = factory(global);\n' +
' }\n' +
' else if (typeof define === "function" && define.amd) {\n' +
' define(function() {\n' +
' return factory(global);\n' +
' });\n' +
' }\n' +
' else if (typeof exports === "object") {\n' +
' exports["libjass"] = factory(global);\n' +
' }\n' +
' else {\n' +
' root["libjass"] = factory(global);\n' +
' }\n' +
'})(this, function (global) {\n' +
' "use strict";\n' +
' return (function (modules) {\n' +
' var installedModules = Object.create(null);\n' +
' function require(moduleId) {\n' +
' if (installedModules[moduleId]) {\n' +
' return installedModules[moduleId].exports;\n' +
' }\n' +
'\n' +
' var module = installedModules[moduleId] = {\n' +
' exports: Object.create(null),\n' +
' id: moduleId,\n' +
' loaded: false,\n' +
' };\n' +
'\n' +
' modules[moduleId](module.exports, require);\n' +
' module.loaded = true;\n' +
' return module.exports;\n' +
' }\n' +
'\n' +
' return require(0);\n' +
' })([\n' +
' ]);\n' +
'});', {
filename: this._outputLibraryName + ".js"
});
this._root.figure_out_scope({ screw_ie8: true });
this._licenseHeader = null;
this._outputModulesArray = this._root.body[0].body.args[1].body[1].value.args[0].elements;
this._modules = Object.create(null);
this._rootSourceMap = new UjsSourceMap(this._outputLibraryName + ".js", "");
}
Run.prototype.addFile = function (file) {
var _this = this;
var moduleName = (path.extname(file.path) === ".map") ? file.path.substr(0, file.path.length - ".js.map".length) : file.path.substr(0, file.path.length - ".js".length);
if (!(moduleName in this._modules)) {
this._modules[moduleName] = { id: null, root: null };
}
var module = this._modules[moduleName];
var filenameForSourceMap = path.relative(path.join(this._entry, ".."), moduleName + ".ts").replace(/\\/g, "/");
switch (path.extname(file.path)) {
case ".js":
module.root = UglifyJS.parse(file.contents.toString(), {
filename: filenameForSourceMap,
toplevel: null,
});
if (this._licenseHeader === null) {
module.root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (_this._licenseHeader !== null) {
return;
}
if (node.start) {
(node.start.comments_before || []).some(function (comment, i) {
if (comment.value.indexOf("Copyright") !== -1) {
_this._licenseHeader = comment;
_this._licenseHeader.value = _this._licenseHeader.value.split("\n").map(function (line) { return line.replace(/^\t/, ""); }).join("\n");
node.start.comments_before.splice(i, 1);
return true;
}
return false;
});
}
}));
this._root.start.comments_before = [this._licenseHeader];
this._root.start.nlb = true;
}
break;
case ".map":
this._rootSourceMap.addInput(filenameForSourceMap, fs.readFileSync(moduleName + ".ts", { encoding: "utf8" }), file.contents);
module.codeBuffer = undefined;
break;
}
};
Run.prototype.build = function (outputStream) {
var _this = this;
// Assign IDs to all modules
var moduleNames = Object.keys(this._modules);
moduleNames.sort(function (name1, name2) { return (name1 === name2) ? 0 : (name1 < name2 ? -1 : 1); });
moduleNames.unshift.apply(moduleNames, moduleNames.splice(moduleNames.indexOf(this._entry), 1));
moduleNames.forEach(function (moduleName, index) {
_this._modules[moduleName].id = index;
});
// Merge modules
moduleNames.forEach(function (moduleName) {
var module = _this._modules[moduleName];
module.root.body.forEach(function (statement) {
if (statement instanceof UglifyJS.AST_Var && statement.definitions.length === 1) {
if (
statement.definitions[0].value instanceof UglifyJS.AST_Call &&
statement.definitions[0].value.expression.name === "require"
) {
var importRelativePath = statement.definitions[0].value.args[0].value;
var importAbsolutePath = path.join(moduleName, "..", importRelativePath).replace(/\\/g, "/");
var stringArg = statement.definitions[0].value.args[0];
statement.definitions[0].value.args[0] = new UglifyJS.AST_Number({ start: stringArg.start, end: stringArg.end, value: _this._modules[importAbsolutePath].id });
}
else if (statement.definitions[0].name.name === "__extends") {
var importAbsolutePath = path.join(_this._entry, "..", "utility", "extends").replace(/\\/g, "/");
statement.definitions[0].value = UglifyJS.parse('require(' + _this._modules[importAbsolutePath].id + ').__extends').body[0].body;
}
}
});
var wrapper = UglifyJS.parse('/* ' + module.id + ' ./' + path.relative(path.join(_this._entry, ".."), moduleName).replace(/\\/g, "/") + ' */ function x(exports, require) { }');
var func = wrapper.body[0];
func.body = module.root.body;
func.name = null;
_this._outputModulesArray.push(func);
});
this._root.figure_out_scope({ screw_ie8: true });
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
var haveUnusedVars = false;
// Remove some things from the AST
var nodesToRemove;
nodesToRemove = [];
// Fixup anonymous functions to print a space after "function"
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Lambda && !node.name) {
node.name = Object.create(UglifyJS.AST_Node.prototype);
node.name.print = function () { };
}
}));
// Fix alignment of multi-line block comments
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node.start && node.start.comments_before) {
node.start.comments_before.forEach(function (comment) {
if (comment === _this._licenseHeader) {
return;
}
var lines = comment.value.split("\n");
if (lines.length < 2) {
return;
}
var indent = " "; // 9 spaces
for (var i = 0; i < comment.col; i++) {
indent += " ";
}
lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+$/, indent);
comment.value = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^\s+/, indent); })).join("\n");
});
}
}));
this._root.figure_out_scope({ screw_ie8: true });
// Repeat because removing some declarations may make others unreferenced
for (;;) {
// Unreferenced variable and function declarations, and unreferenced terminal function arguments
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
if (node instanceof UglifyJS.AST_SymbolFunarg) {
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
nodesToRemove.push({ node: node, parent: this.parent().argnames });
}
}
else if (node instanceof UglifyJS.AST_SymbolVar) {
if (_this._unusedVarsToIgnore.indexOf(node.name) !== -1) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
if (this.parent(1).definitions.length === 1) {
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
}
}
else {
haveUnusedVars = true;
}
}
else if (node instanceof UglifyJS.AST_SymbolDefun) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
}
}
}));
if (nodesToRemove.length === 0) {
break;
}
nodesToRemove.forEach(function (tuple) {
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
});
nodesToRemove = [];
this._root.figure_out_scope({ screw_ie8: true });
}
// Rename all function arguments that begin with _ to not have the _.
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
// so that it matches the JSDoc (and looks nicer).
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_SymbolFunarg &&
node.thedef.name[0] === "_" &&
node.thedef.name[1] !== "_" &&
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
) {
node.thedef.name = node.thedef.name.slice(1);
}
}));
// Output
var output = {
source_map: _this._rootSourceMap.asUjsOutputSourceMap(),
beautify: true,
comments: function (node, comment) {
// If this is the first license header, keep it.
if (comment === _this._licenseHeader) {
return true;
}
// If this is a license header, remove it.
if (comment.value.indexOf("Copyright") !== -1) {
return false;
}
// If this is a TypeScript reference comment, strip it.
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
return false;
}
return true;
}
};
var stream = UglifyJS.OutputStream(output);
this._root.print(stream);
outputStream.push(new Vinyl({
path: _this._outputLibraryName + ".js",
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + _this._outputLibraryName + ".js.map")])
}));
outputStream.push(new Vinyl({
path: _this._outputLibraryName + ".js.map",
contents: _this._rootSourceMap.toBuffer()
}));
// Print unused variables
if (haveUnusedVars) {
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolVar && node.unreferenced()) {
if (_this._unusedVarsToIgnore.indexOf(node.name) === -1) {
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
}
}
}));
}
};
return Run;
})();
var Transform = require("./helpers.js").Transform;
module.exports = {
gulp: function (entry, outputLibraryName, unusedVarsToIgnore) {
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
fixup: function () {
var codeFile = null;
var sourceMapFile = null;
return makeTransform(function (file) {
run.addFile(file);
}, function () {
run.build(this);
});
},
watch: function (entry, outputLibraryName, unusedVarsToIgnore) {
var files = Object.create(null);
return makeTransform(function (file) {
if (file.path !== "END") {
files[file.path] = file;
return Transform(function (file) {
switch (path.extname(file.path)) {
case ".js":
codeFile = file;
break;
case ".map":
sourceMapFile = file;
break;
}
else {
var run = new Run(entry, outputLibraryName, unusedVarsToIgnore);
Object.keys(files).forEach(function (filename) {
run.addFile(files[filename]);
if (codeFile !== null && sourceMapFile !== null) {
UglifyJS.base54.reset();
// Parse
var root = UglifyJS.parse(codeFile.contents.toString(), {
filename: path.basename(codeFile.path),
toplevel: null
});
run.build(this);
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 });
// Remove some things from the AST
var nodesToRemove;
nodesToRemove = [];
// 1. All the top-level "var libjass;"
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
nodesToRemove.push({ node: node, parent: this.parent().body });
}
}));
// 2. All but the first top-level "var __extends = ..."
var firstVarExtendsFound = false;
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "__extends") {
if (firstVarExtendsFound === false) {
firstVarExtendsFound = true;
// Remove the check for this.__extends
node.definitions[0].value = node.definitions[0].value.right;
}
else {
nodesToRemove.push({ node: node, parent: this.parent().body });
}
}
}));
// Repeat because removing some declarations may make others unreferenced
for (;;) {
// 3. Unreferenced variable and function declarations, and unreferenced terminal function arguments
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
if (node instanceof UglifyJS.AST_SymbolFunarg) {
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
nodesToRemove.push({ node: node, parent: this.parent().argnames });
}
}
else if (node instanceof UglifyJS.AST_SymbolVar) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
if (this.parent(1).definitions.length === 1) {
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
}
}
else if (node instanceof UglifyJS.AST_SymbolDefun) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
}
}
}));
if (nodesToRemove.length === 0) {
break;
}
nodesToRemove.forEach(function (tuple) {
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
});
nodesToRemove = [];
root.figure_out_scope({ screw_ie8: true });
}
// 4. Rename all function arguments that begin with _ to not have the _.
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
// so that it matches the JSDoc (and looks nicer).
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_SymbolFunarg &&
node.thedef.name[0] === "_" &&
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
) {
node.thedef.name = node.thedef.name.slice(1);
}
}));
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
beautify: true,
comments: function (node, comment) {
// If this is a license header
if (comment.value.indexOf("Copyright") !== -1) {
if (!firstLicenseHeaderFound) {
firstLicenseHeaderFound = true;
}
else {
// This isn't the first license header. Strip it.
return false;
}
}
// If this is a TypeScript reference comment, strip it.
if (comment.value.substr(0, "/<reference path=".length) === "/<reference path=") {
return false;
}
/* Align multi-line comments correctly.
* TypeScript shifts them one space left, and UJS shifts them four spaces left,
* so shift each line except the first five spaces right.
*/
if (comment.type === "comment2") {
var lines = comment.value.split("\n");
lines = [lines[0]].concat(lines.slice(1).map(function (line) { return ' ' + line; }));
comment.value = lines.join('\n');
}
return true;
}
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
this.push(codeFile);
output.source_map.get()._sources.toArray().forEach(function (filename) {
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
});
sourceMapFile.contents = new Buffer(output.source_map.toString());
this.push(sourceMapFile);
codeFile = null;
sourceMapFile = null;
}
});
},
@@ -366,7 +193,7 @@ module.exports = {
var codeFile = null;
var sourceMapFile = null;
return makeTransform(function (file) {
return Transform(function (file) {
switch (path.extname(file.path)) {
case ".js":
codeFile = file;
@@ -473,14 +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;
}
return true;
return false;
}
return false;
},
screw_ie8: true
};
@@ -494,10 +322,8 @@ module.exports = {
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
this.push(codeFile);
var inputSourceMapObject = JSON.parse(sourceMapFile.contents.toString());
var outputSourceMapObject = output.source_map.get();
outputSourceMapObject._sources.toArray().forEach(function (filename, i) {
outputSourceMapObject.setSourceContent(filename, inputSourceMapObject.sourcesContent[i]);
output.source_map.get()._sources.toArray().forEach(function (filename) {
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
});
sourceMapFile.contents = new Buffer(output.source_map.toString());
@@ -510,58 +336,6 @@ module.exports = {
}
};
var UjsSourceMap = (function () {
function UjsSourceMap(outputFilename, sourceRoot) {
this._outputFilename = outputFilename;
this._inputs = Object.create(null);
this._generator = new SourceMap.SourceMapGenerator({
file: outputFilename,
sourceRoot: sourceRoot,
});
}
UjsSourceMap.prototype.addInput = function (codeFilename, code, sourceMapContents) {
this._inputs[codeFilename] = new SourceMap.SourceMapConsumer(sourceMapContents.toString());
this._generator.setSourceContent(codeFilename, code);
};
UjsSourceMap.prototype.asUjsOutputSourceMap = function () {
var _this = this;
return {
add: function (source, gen_line, gen_col, orig_line, orig_col, name) {
if (source === _this._outputFilename || source === "?") {
return;
}
var info = _this._inputs[source].originalPositionFor({
line: orig_line,
column: orig_col
});
if (info.source === null) {
return;
}
_this._generator.addMapping({
generated: { line: gen_line, column: gen_col },
original: { line: info.line, column: info.column },
source: source,
name: info.name || name
});
},
get: function () { return _this._generator; },
toString: function () { return _this._generator.toString(); },
};
};
UjsSourceMap.prototype.toBuffer = function () {
return new Buffer(this._generator.toString());
};
return UjsSourceMap;
})();
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
@@ -569,9 +343,5 @@ UglifyJS.AST_Symbol.prototype.unreferenced = function () {
return false;
}
if (this.name === "module" || this.name === "exports") {
return false;
}
return originalSymbolUnreferenced.call(this);
};
@@ -20,6 +20,12 @@
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.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 {
+9 -1
View File
@@ -18,4 +18,12 @@
* limitations under the License.
*/
export { gulp, watch } from "./compiler";
///<reference path="utility.ts" />
///<reference path="web-worker.ts" />
///<reference path="parts.ts" />
///<reference path="parser.ts" />
///<reference path="renderers-clocks.ts" />
///<reference path="renderers-null.ts" />
///<reference path="renderers-web.ts" />
///<reference path="renderers-default.ts" />
///<reference path="types.ts" />
+17 -22
View File
@@ -1,12 +1,17 @@
{
"name": "libjass",
"version": "0.10.0",
"version": "0.9.0",
"description": "A library to render ASS subtitles on HTML5 video in the browser.",
"keywords": ["browser", "html5", "subtitles"],
"homepage": "https://github.com/Arnavion/libjass",
"bugs": "https://github.com/Arnavion/libjass/issues",
"license": "Apache-2.0",
"contributors": [{
"bugs": {
"url": "https://github.com/Arnavion/libjass/issues"
},
"licenses": [{
"type": "Apache-2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}],
"maintainers": [{
"name": "Arnav Singh",
"email": "arnavion@gmail.com"
}],
@@ -14,25 +19,15 @@
"type": "git",
"url": "https://github.com/Arnavion/libjass"
},
"main": "lib/libjass.js",
"scripts": {
"prepublish": "gulp clean default",
"build-gulplib": "tsc ./gulplib/typescript/index.ts ./gulplib/doc.ts ./gulplib/typings.d.ts ./node_modules/typescript/bin/typescript.d.ts ./node_modules/typescript/bin/typescript_internal.d.ts -m commonjs -t es5 -noImplicitAny",
"test": "gulp test",
"test-lib": "intern-client config=tests/intern reporters=pretty",
"test-minified": "intern-client config=tests/intern reporters=pretty minified=true",
"test-browser": "intern-runner config=tests/intern",
"test-doc": "intern-client config=tests/intern-doc reporters=pretty"
"postinstall": "gulp clean default",
"test": "node ./gulplib/test-runner.js"
},
"devDependencies": {
"gulp": "3.x",
"intern": "2.x",
"npm": "2.x",
"pngjs": "0.4.0",
"sax": "0.6.x",
"typescript": "1.5.0-alpha",
"uglify-js": "2.x >=2.4.16",
"dependencies": {
"gulp": "latest",
"mocha": "1.21.x",
"typescript": "1.0.1",
"uglify-js": "2.x >=2.4.14",
"vinyl": "latest"
},
"private": true
}
}
+2542
View File
File diff suppressed because it is too large Load Diff
+1438
View File
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="libjass.ts" />
module libjass.renderers {
/**
* A mixin class that represents an event source.
*
* @template T
*/
export class EventSource<T> {
_eventListeners: Map<T, Function[]>;
/**
* Add a listener for the given event.
*
* @param {!T} type The type of event to attach the listener for
* @param {!Function} listener The listener
*/
addEventListener(type: T, listener: Function): void {
var listeners = this._eventListeners.get(type);
if (listeners === undefined) {
this._eventListeners.set(type, listeners = []);
}
listeners.push(listener);
}
/**
* @param {!T} type The type of event to dispatch
* @param {!Array.<*>} args Arguments for the listeners of the event
*/
_dispatchEvent(type: T, args: Object[]): void {
var listeners = this._eventListeners.get(type);
if (listeners !== undefined) {
listeners.forEach((listener: Function) => {
listener.apply(this, args);
});
}
}
}
export enum ClockEvent {
Play,
Tick,
Pause,
Stop,
}
export interface Clock extends EventSource<ClockEvent> {
/**
* @type {number}
*/
currentTime: number;
/**
* @type {boolean}
*/
enabled: boolean;
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean;
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean;
/**
* Toggle the clock.
*/
toggle(): void;
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean;
// EventSource members
addEventListener: (type: ClockEvent, listener: Function) => void;
}
/**
* An implementation of libjass.renderers.Clock that allows user script to manually trigger play, pause and timeUpdate events.
*/
export class ManualClock implements Clock {
private _currentTime: number = -1;
private _enabled: boolean = true;
/**
* Trigger a ClockEvent.Play
*/
play(): void {
this._dispatchEvent(ClockEvent.Play, []);
}
/**
* Trigger a ClockEvent.Tick
*
* @param {number} currentTime
*/
tick(currentTime: number): void {
this._currentTime = currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
/**
* Trigger a ClockEvent.Pause
*/
pause(): void {
this._dispatchEvent(ClockEvent.Pause, []);
}
/**
* Trigger a ClockEvent.Stop
*/
stop(): void {
this._dispatchEvent(ClockEvent.Stop, []);
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._enabled;
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (this._enabled) {
return false;
}
this._enabled = true;
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._enabled) {
return false;
}
this._enabled = false;
return true;
}
/**
* Toggle the clock.
*/
toggle(): void {
if (this._enabled) {
this.disable();
}
else {
this.enable();
}
}
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
if (enabled) {
return this.enable();
}
else {
return this.disable();
}
}
// EventSource members
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
addEventListener: (type: ClockEvent, listener: Function) => void;
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(ManualClock, [EventSource]);
enum VideoState {
Playing = 0,
Paused = 1,
}
/**
* An implementation of libjass.renderers.Clock that generates play, pause and timeUpdate events according to the state of a <video> element.
*
* @param {!HTMLVideoElement} video
*/
export class VideoClock implements Clock {
private _currentTime: number = -1;
private _enabled: boolean = true;
private _videoState: VideoState;
private _nextAnimationFrameRequestId: number = null;
constructor(private _video: HTMLVideoElement) {
this._video.addEventListener("playing", () => this._onVideoPlaying(), false);
this._video.addEventListener("pause", () => this._onVideoPause(), false);
this._video.addEventListener("seeking", () => this._onVideoSeeking(), false);
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._enabled;
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (this._enabled) {
return false;
}
if (this._videoState !== VideoState.Paused) {
if (libjass.debugMode) {
console.warn("VideoClock.enable: Abnormal state detected. VideoClock._videoState should have been VideoState.Paused");
}
}
this._enabled = true;
this._startTicking();
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._enabled) {
return false;
}
if (this._videoState === VideoState.Playing) {
this._videoState = VideoState.Paused;
this._dispatchEvent(ClockEvent.Pause, []);
}
this._enabled = false;
this._dispatchEvent(ClockEvent.Stop, []);
this._stopTicking();
return true;
}
/**
* Toggle the clock.
*/
toggle(): void {
if (this._enabled) {
this.disable();
}
else {
this.enable();
}
}
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
if (enabled) {
return this.enable();
}
else {
return this.disable();
}
}
private _onVideoPlaying(): void {
if (!this._enabled) {
return;
}
if (this._videoState === VideoState.Playing) {
return;
}
this._startTicking();
}
private _onVideoPause(): void {
if (!this._enabled) {
return;
}
if (this._videoState === VideoState.Paused) {
return;
}
this._videoState = VideoState.Paused;
this._dispatchEvent(ClockEvent.Pause, []);
if (this._nextAnimationFrameRequestId === null) {
if (libjass.debugMode) {
console.warn("VideoClock._onVideoPause: Abnormal state detected. VideoClock._nextAnimationFrameRequestId should not have been null");
}
return;
}
this._stopTicking();
}
private _onVideoSeeking(): void {
if (!this._enabled) {
return;
}
if (this._videoState === VideoState.Playing) {
this._videoState = VideoState.Paused;
this._dispatchEvent(ClockEvent.Pause, []);
}
if (this._currentTime === this._video.currentTime) {
return;
}
this._currentTime = this._video.currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
private _onTimerTick(): void {
if (!this._enabled) {
return;
}
if (this._videoState === VideoState.Playing) {
if (this._currentTime !== this._video.currentTime) {
this._currentTime = this._video.currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
else {
this._videoState = VideoState.Paused;
this._dispatchEvent(ClockEvent.Pause, []);
}
}
else {
if (this._currentTime !== this._video.currentTime) {
this._currentTime = this._video.currentTime;
this._videoState = VideoState.Playing;
this._dispatchEvent(ClockEvent.Play, []);
this._dispatchEvent(ClockEvent.Tick, []);
}
}
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
}
private _startTicking(): void {
if (this._nextAnimationFrameRequestId === null) {
this._nextAnimationFrameRequestId = requestAnimationFrame(() => this._onTimerTick());
}
}
private _stopTicking(): void {
if (this._nextAnimationFrameRequestId !== null) {
cancelAnimationFrame(this._nextAnimationFrameRequestId);
this._nextAnimationFrameRequestId = null;
}
}
// EventSource members
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
addEventListener: (type: ClockEvent, listener: Function) => void;
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(VideoClock, [EventSource]);
}
+88
View File
@@ -0,0 +1,88 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="libjass.ts" />
interface Document {
fullscreenElement: Element;
mozFullScreenElement: Element;
webkitFullscreenElement: Element;
}
module libjass.renderers {
/**
* A default renderer implementation.
*
* @param {!HTMLVideoElement} video
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.RendererSettings} settings
*/
export class DefaultRenderer extends WebRenderer {
private _videoIsFullScreen: boolean = false;
constructor(private _video: HTMLVideoElement, ass: ASS, settings: RendererSettings) {
super(ass, new VideoClock(_video), settings, document.createElement("div"));
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
}
/**
* @deprecated
*/
resizeVideo(width: number, height: number): void {
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize(width, height)` instead.");
this.resize(width, height);
}
_ready(): void {
document.addEventListener("mozfullscreenchange", event => this._onFullScreenChange(document.mozFullScreenElement), false);
document.addEventListener("webkitfullscreenchange", event => this._onFullScreenChange(document.webkitFullscreenElement), false);
document.addEventListener("fullscreenchange", event => this._onFullScreenChange(document.fullscreenElement), false);
this.resize(this._video.offsetWidth, this._video.offsetHeight);
super._ready();
}
private _onFullScreenChange(fullScreenElement: Element) {
if (fullScreenElement === undefined) {
fullScreenElement = document.msFullscreenElement;
}
if (fullScreenElement === this._video) {
this.libjassSubsWrapper.classList.add("libjass-full-screen");
this.resize(screen.width, screen.height);
this._videoIsFullScreen = true;
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
}
else if (fullScreenElement === null && this._videoIsFullScreen) {
this.libjassSubsWrapper.classList.remove("libjass-full-screen");
this._videoIsFullScreen = false;
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
}
}
}
}
+280
View File
@@ -0,0 +1,280 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="libjass.ts" />
module libjass.renderers {
/**
* A renderer implementation that doesn't output anything.
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {!libjass.renderers.RendererSettings} settings
*/
export class NullRenderer {
private static _lastRendererId = -1;
private _id: number;
private _settings: RendererSettings;
constructor(private _ass: ASS, private _clock: Clock, settings: RendererSettings) {
this._id = ++NullRenderer._lastRendererId;
this._settings = RendererSettings.from(settings);
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
}
/**
* The unique ID of this renderer. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* @type {!libjass.ASS}
*/
get ass(): ASS {
return this._ass;
}
/**
* @type {!libjass.renderers.Clock}
*/
get clock(): Clock {
return this._clock;
}
/**
* @type {!libjass.renderers.RendererSettings}
*/
get settings(): RendererSettings {
return this._settings;
}
/**
* Pre-render a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
preRender(dialogue: Dialogue): void { }
/**
* Draw a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
draw(dialogue: Dialogue): void { }
/**
* Enable the renderer.
*
* @return {boolean} True if the renderer is now enabled, false if it was already enabled.
*/
enable(): boolean {
return this._clock.enable();
}
/**
* Disable the renderer.
*
* @return {boolean} True if the renderer is now disabled, false if it was already disabled.
*/
disable(): boolean {
return this._clock.disable();
}
/**
* Toggle the renderer.
*/
toggle(): void {
this._clock.toggle();
}
/**
* Enable or disable the renderer.
*
* @param {boolean} enabled If true, the renderer is enabled, otherwise it's disabled.
* @return {boolean} True if the renderer is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
return this._clock.setEnabled(enabled);
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._clock.enabled;
}
/**
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
*/
_onClockPlay(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockPlay");
}
}
/**
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
*/
_onClockTick(): void {
var currentTime = this._clock.currentTime;
if (libjass.verboseMode) {
console.log("NullRenderer._onClockTick: currentTime = " + currentTime);
}
for (var i = 0; i < this._ass.dialogues.length; i++) {
var dialogue = this._ass.dialogues[i];
if (dialogue.end > currentTime) {
if (dialogue.start <= currentTime) {
// This dialogue is visible right now. Draw it.
this.draw(dialogue);
}
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
// This dialogue will be visible soon. Pre-render it.
this.preRender(dialogue);
}
}
}
}
/**
* Runs when the clock is paused.
*/
_onClockPause(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockPause");
}
}
/**
* Runs when the clock is disabled.
*/
_onClockStop(): void {
if (libjass.verboseMode) {
console.log("NullRenderer._onClockStop");
}
}
}
/**
* Settings for the renderer.
*/
export class RendererSettings {
/**
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
*
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the RendererSettings.makeFontMapFromStyleElement() convenience method to create a font map.
*
* @type {!Map.<string, !Array.<string>>}
*/
fontMap: Map<string, string[]>;
/**
* Subtitles will be pre-rendered for this amount of time (seconds).
*
* Defaults to 5.
*
* @type {number}
*/
preRenderTime: number;
/**
* Subtitle outlines will be rendered in full detail. When false, the value of blur is used to draw less outlines for better performance and (hopefully) similar output.
*
* Defaults to false.
*
* @type {boolean}
*/
preciseOutlines: boolean;
/**
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules.
*
* @param {!LinkStyle} linkStyle
* @return {!Map.<string, !Array.<string>>}
*/
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string[]> {
var map = new Map<string, string[]>();
var styleSheet = <CSSStyleSheet>linkStyle.sheet;
var rules: CSSFontFaceRule[] = Array.prototype.filter.call(styleSheet.cssRules, (rule: CSSRule) => rule.type === CSSRule.FONT_FACE_RULE);
rules.forEach((rule: CSSFontFaceRule) => {
var src = rule.style.getPropertyValue("src");
var urls: string[] = [];
if (!src) {
src = rule.cssText.split("\n")
.map((line: string) => line.match(/src: ([^;]+);/))
.filter((matches: string[]) => matches !== null)
.map((matches: string[]) => matches[1])[0];
}
urls = src.split(/,\s*/).map((url: string) => url.match(/^url\((.+)\)$/)[1]);
if (urls.length > 0) {
var name = RendererSettings._stripQuotes(rule.style.getPropertyValue("font-family"));
var existingList = map.get(name);
if (existingList === undefined) {
existingList = [];
map.set(name, existingList);
}
existingList.unshift.apply(existingList, urls.map(RendererSettings._stripQuotes));
}
});
return map;
}
/**
* Converts an arbitrary object into a RendererSettings object.
*
* @param {!*} object
* @return {!libjass.renderers.RendererSettings}
*/
static from(object: any): RendererSettings {
return RendererSettings._from(object.fontMap, object.preRenderTime, object.preciseOutlines);
}
private static _from(fontMap: Map<string, string[]> = null, preRenderTime: number = 5, preciseOutlines: boolean = false): RendererSettings {
var result = new RendererSettings();
result.fontMap = fontMap;
result.preRenderTime = preRenderTime;
result.preciseOutlines = preciseOutlines;
return result;
}
private static _stripQuotes(str: string): string {
return str.match(/^["']?(.*?)["']?$/)[1];
}
}
}
+1509
View File
File diff suppressed because it is too large Load Diff
-76
View File
@@ -1,76 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as settings from "./settings";
export { debugMode, verboseMode } from "./settings";
import * as set from "./utility/set";
export { Set } from "./utility/set";
import * as map from "./utility/map";
export { Map } from "./utility/map";
import * as promise from "./utility/promise";
export { Promise, DeferredPromise } from "./utility/promise";
import * as webworker from "./webworker/index";
export { webworker };
import * as parts from "./parts/index";
export { parts };
import * as parser from "./parser/index";
export { parser };
import * as renderers from "./renderers/index";
export { renderers };
export { ASS } from "./types/ass";
export { Dialogue } from "./types/dialogue";
export { ScriptProperties } from "./types/script-properties";
export { Style } from "./types/style";
export { BorderStyle, Format, WrappingStyle } from "./types/misc";
declare var exports: any;
Object.defineProperties(exports, {
debugMode: {
get: () => settings.debugMode,
set: settings.setDebugMode,
},
verboseMode: {
get: () => settings.verboseMode,
set: settings.setVerboseMode,
},
Set: {
get: () => set.Set,
set: set.setImplementation,
},
Map: {
get: () => map.Map,
set: map.setImplementation,
},
Promise: {
get: () => promise.Promise,
set: promise.setImplementation,
},
});
-23
View File
@@ -1,23 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { parse } from "./parse";
export { BrowserReadableStream, Stream, StringStream, XhrStream } from "./streams";
export { StreamParser, SrtStreamParser } from "./stream-parsers";
-68
View File
@@ -1,68 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Property, TypedTemplate } from "../types/misc";
import { Map } from "../utility/map";
/**
* Parses a line into a {@link ./types/misc.Property}.
*
* @param {string} line
* @return {!Property}
*/
export function parseLineIntoProperty(line: string): Property {
var colonPos = line.indexOf(":");
if (colonPos === -1) {
return null;
}
var name = line.substr(0, colonPos);
var value = line.substr(colonPos + 1).replace(/^\s+/, "");
return { name, value };
}
/**
* Parses a line into a {@link ./types/misc.TypedTemplate} according to the given format specifier.
*
* @param {string} line
* @param {!Array.<string>} formatSpecifier
* @return {!TypedTemplate}
*/
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate {
var property = parseLineIntoProperty(line);
if (property === null) {
return null;
}
var value = property.value.split(",");
if (value.length > formatSpecifier.length) {
value[formatSpecifier.length - 1] = value.slice(formatSpecifier.length - 1).join(",");
}
var template = new Map<string, string>();
formatSpecifier.forEach((formatKey, index) => {
template.set(formatKey, value[index]);
});
return { type: property.name, template };
}
-2213
View File
File diff suppressed because it is too large Load Diff
-266
View File
@@ -1,266 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ASS } from "../types/ass";
import { Style } from "../types/style";
import { Dialogue } from "../types/dialogue";
import { Property, TypedTemplate } from "../types/misc";
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { parseLineIntoProperty } from "./misc";
import { Stream } from "./streams";
/**
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The {@link libjass.parser.Stream} to parse
*/
export class StreamParser {
private _ass: ASS = new ASS();
private _minimalDeferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _shouldSwallowBom: boolean = true;
private _currentSectionName: string = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line));
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the script properties of the ASS script have been parsed from the stream. Styles and events have not necessarily been
* parsed at the point this promise becomes resolved.
*/
get minimalASS(): Promise<ASS> {
return this._minimalDeferred.promise;
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the entire stream has been parsed.
*/
get ass(): Promise<ASS> {
return this._deferred.promise;
}
/**
* @param {string} line
*/
private _onNextLine(line: string): void {
if (line === null) {
this._minimalDeferred.resolve(this._ass);
this._deferred.resolve(this._ass);
return;
}
if (line[line.length - 1] === "\r") {
line = line.substr(0, line.length - 1);
}
if (line.charCodeAt(0) === 0xfeff && this._shouldSwallowBom) {
line = line.substr(1);
}
this._shouldSwallowBom = false;
if (line === "" || line[0] === ";") {
// Ignore empty lines and comments
}
else if (line[0] === "[" && line[line.length - 1] === "]") {
// Start of new section
if (this._currentSectionName === "Script Info") {
// Exiting script info section
this._minimalDeferred.resolve(this._ass);
}
this._currentSectionName = line.substring(1, line.length - 1);
}
else {
switch (this._currentSectionName) {
case "Script Info":
var property = parseLineIntoProperty(line);
if (property !== null) {
switch (property.name) {
case "PlayResX":
this._ass.properties.resolutionX = parseInt(property.value);
break;
case "PlayResY":
this._ass.properties.resolutionY = parseInt(property.value);
break;
case "WrapStyle":
this._ass.properties.wrappingStyle = parseInt(property.value);
break;
case "ScaledBorderAndShadow":
this._ass.properties.scaleBorderAndShadow = (property.value === "yes");
break;
}
}
break;
case "V4+ Styles":
if (this._ass.stylesFormatSpecifier === null) {
var property = parseLineIntoProperty(line);
if (property !== null && property.name === "Format") {
this._ass.stylesFormatSpecifier = property.value.split(",").map(str => str.trim());
}
else {
// Ignore any non-format lines
}
}
else {
this._ass.addStyle(line);
}
break;
case "Events":
if (this._ass.dialoguesFormatSpecifier === null) {
var property = parseLineIntoProperty(line);
if (property !== null && property.name === "Format") {
this._ass.dialoguesFormatSpecifier = property.value.split(",").map(str => str.trim());
}
else {
// Ignore any non-format lines
}
}
else {
this._ass.addEvent(line);
}
break;
default:
// Ignore other sections
break;
}
}
this._stream.nextLine().then(line => this._onNextLine(line));
}
}
/**
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream} of an SRT script.
*
* @param {!libjass.parser.Stream} stream The {@link libjass.parser.Stream} to parse
*/
export class SrtStreamParser {
private _ass: ASS = new ASS();
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _currentDialogueNumber: string = null;
private _currentDialogueStart: string = null;
private _currentDialogueEnd: string = null;
private _currentDialogueText: string = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line));
this._ass.properties.resolutionX = 1280;
this._ass.properties.resolutionY = 720;
this._ass.properties.wrappingStyle = 1;
this._ass.properties.scaleBorderAndShadow = true;
var newStyle = new Style(new Map([["Name", "Default"]]));
this._ass.styles.set(newStyle.name, newStyle);
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the entire stream has been parsed.
*/
get ass(): Promise<ASS> {
return this._deferred.promise;
}
/**
* @param {string} line
*/
private _onNextLine(line: string): void {
if (line === null) {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map([
["Style", "Default"],
["Start", this._currentDialogueStart],
["End", this._currentDialogueEnd],
["Text", this._currentDialogueText],
]), this._ass));
}
this._deferred.resolve(this._ass);
return;
}
if (line[line.length - 1] === "\r") {
line = line.substr(0, line.length - 1);
}
if (line === "") {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map([
["Style", "Default"],
["Start", this._currentDialogueStart],
["End", this._currentDialogueEnd],
["Text", this._currentDialogueText],
]), this._ass));
}
this._currentDialogueNumber = this._currentDialogueStart = this._currentDialogueEnd = this._currentDialogueText = null;
}
else {
if (this._currentDialogueNumber === null) {
if (/^\d+$/.test(line)) {
this._currentDialogueNumber = line;
}
}
else if (this._currentDialogueStart === null && this._currentDialogueEnd === null) {
var match = /^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/.exec(line);
if (match !== null) {
this._currentDialogueStart = match[1].replace(",", ".");
this._currentDialogueEnd = match[2].replace(",", ".");
}
}
else {
line = line
.replace(/<b>/g, "{\\b1}").replace(/\{b\}/g, "{\\b1}")
.replace(/<\/b>/g, "{\\b0}").replace(/\{\/b\}/g, "{\\b0}")
.replace(/<i>/g, "{\\i1}").replace(/\{i\}/g, "{\\i1}")
.replace(/<\/i>/g, "{\\i0}").replace(/\{\/i\}/g, "{\\i0}")
.replace(/<u>/g, "{\\u1}").replace(/\{u\}/g, "{\\u1}")
.replace(/<\/u>/g, "{\\u0}").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}");
if (this._currentDialogueText !== null) {
this._currentDialogueText += "\\N" + line;
}
else {
this._currentDialogueText = line;
}
}
}
this._stream.nextLine().then(line => this._onNextLine(line));
}
}
-250
View File
@@ -1,250 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Promise, DeferredPromise } from "../utility/promise";
export interface ReadableStream {
/**
* @return {!ReadableStreamReader}
*/
getReader(): ReadableStreamReader;
}
interface ReadableStreamReader {
/**
* @return {!Promise.<{ value?: Uint8Array, done: boolean }>}
*/
read(): Promise<{ value: Uint8Array; done: boolean; }>;
}
export interface TextDecoder {
/**
* @param {!ArrayBuffer|!ArrayBufferView} input
* @param {{ stream: boolean }} options
* @return {string}
*/
decode(input: ArrayBuffer | ArrayBufferView, options: { stream: boolean }): string;
}
export interface TextDecoderConstructor {
new (encoding: string, options: { ignoreBOM: boolean }): TextDecoder;
/**
* @type {!TextDecoder}
*/
prototype: TextDecoder;
}
declare var global: {
/**
* @type {!TextDecoderConstructor}
*/
TextDecoder?: TextDecoderConstructor;
};
/**
* An interface for a stream.
*/
export interface Stream {
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string>;
}
/**
* A {@link libjass.parser.Stream} that reads from a string in memory.
*
* @param {string} str The string
*/
export class StringStream implements Stream {
private _readTill: number = 0;
constructor(private _str: string) { }
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the string has been completely read.
*/
nextLine(): Promise<string> {
var result: Promise<string> = null;
if (this._readTill < this._str.length) {
var nextNewLinePos = this._str.indexOf("\n", this._readTill);
if (nextNewLinePos !== -1) {
result = Promise.resolve(this._str.substring(this._readTill, nextNewLinePos));
this._readTill = nextNewLinePos + 1;
}
else {
result = Promise.resolve(this._str.substr(this._readTill));
this._readTill = this._str.length;
}
}
else {
result = Promise.resolve<string>(null);
}
return result;
}
}
/**
* A {@link libjass.parser.Stream} that reads from an XMLHttpRequest object.
*
* @param {!XMLHttpRequest} xhr The XMLHttpRequest object. Make sure to not call .open() on this object before passing it in here,
* since event handlers cannot be registered after open() has been called.
*/
export class XhrStream implements Stream {
private _readTill: number = 0;
private _pendingDeferred: DeferredPromise<string> = null;
constructor(private _xhr: XMLHttpRequest) {
_xhr.addEventListener("progress", event => this._onXhrProgress(event), false);
_xhr.addEventListener("loadend", event => this._onXhrLoadEnd(event), false);
}
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string> {
if (this._pendingDeferred !== null) {
throw new Error("XhrStream only supports one pending unfulfilled read at a time.");
}
var deferred = this._pendingDeferred = new DeferredPromise<string>();
this._tryResolveNextLine();
return deferred.promise;
}
/**
* @param {!ProgressEvent} event
*/
private _onXhrProgress(event: ProgressEvent): void {
if (this._pendingDeferred === null) {
return;
}
this._tryResolveNextLine();
}
/**
* @param {!ProgressEvent} event
*/
private _onXhrLoadEnd(event: ProgressEvent): void {
if (this._pendingDeferred === null) {
return;
}
this._tryResolveNextLine();
}
/**
*/
private _tryResolveNextLine(): void {
var response = this._xhr.responseText;
var nextNewLinePos = response.indexOf("\n", this._readTill);
if (nextNewLinePos !== -1) {
this._pendingDeferred.resolve(response.substring(this._readTill, nextNewLinePos));
this._readTill = nextNewLinePos + 1;
this._pendingDeferred = null;
}
else if (this._xhr.readyState === XMLHttpRequest.DONE) {
// No more data. This is the last line.
if (this._readTill < response.length) {
this._pendingDeferred.resolve(response.substr(this._readTill));
this._readTill = response.length;
}
else {
this._pendingDeferred.resolve(null);
}
this._pendingDeferred = null;
}
}
}
/**
* A {@link libjass.parser.Stream} that reads from a ReadableStream object.
*
* @param {!ReadableStream} stream
* @param {string} encoding
*/
export class BrowserReadableStream implements Stream {
private _reader: ReadableStreamReader;
private _decoder: TextDecoder;
private _buffer: string = "";
private _pendingDeferred: DeferredPromise<string> = null;
constructor(stream: ReadableStream, encoding: string) {
this._reader = stream.getReader();
this._decoder = new global.TextDecoder(encoding, { ignoreBOM: true });
}
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string> {
if (this._pendingDeferred !== null) {
throw new Error("BrowserReadableStream only supports one pending unfulfilled read at a time.");
}
var deferred = this._pendingDeferred = new DeferredPromise<string>();
this._tryResolveNextLine();
return deferred.promise;
}
/**
*/
private _tryResolveNextLine(): void {
var nextNewLinePos = this._buffer.indexOf("\n");
if (nextNewLinePos !== -1) {
this._pendingDeferred.resolve(this._buffer.substr(0, nextNewLinePos));
this._buffer = this._buffer.substr(nextNewLinePos + 1);
this._pendingDeferred = null;
}
else {
this._reader.read().then(next => {
var { value, done } = next;
if (!done) {
this._buffer += this._decoder.decode(value, { stream: true });
this._tryResolveNextLine();
}
else {
// No more data.
if (this._buffer.length === 0) {
this._pendingDeferred.resolve(null);
}
else {
this._pendingDeferred.resolve(this._buffer);
this._buffer = "";
}
this._pendingDeferred = null;
}
});
}
}
}
-148
View File
@@ -1,148 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The base interface of the drawing instructions.
*/
export interface Instruction { }
/**
* An instruction to move to a particular position.
*
* @param {number} x
* @param {number} y
*/
export class MoveInstruction implements Instruction {
constructor(private _x: number, private _y: number) { }
/**
* The X position of this move instruction.
*
* @type {number}
*/
get x(): number {
return this._x;
}
/**
* The Y position of this move instruction.
*
* @type {number}
*/
get y(): number {
return this._y;
}
}
/**
* An instruction to draw a line to a particular position.
*
* @param {number} x
* @param {number} y
*/
export class LineInstruction implements Instruction {
constructor(private _x: number, private _y: number) { }
/**
* The X position of this line instruction.
*
* @type {number}
*/
get x(): number {
return this._x;
}
/**
* The Y position of this line instruction.
*
* @type {number}
*/
get y(): number {
return this._y;
}
}
/**
* An instruction to draw a cubic bezier curve to a particular position, with two given control points.
*
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
*/
export class CubicBezierCurveInstruction implements Instruction {
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _x3: number, private _y3: number) { }
/**
* The X position of the first control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get x1(): number {
return this._x1;
}
/**
* The Y position of the first control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get y1(): number {
return this._y1;
}
/**
* The X position of the second control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get x2(): number {
return this._x2;
}
/**
* The Y position of the second control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get y2(): number {
return this._y2;
}
/**
* The ending X position of this cubic bezier curve instruction.
*
* @type {number}
*/
get x3(): number {
return this._x3;
}
/**
* The ending Y position of this cubic bezier curve instruction.
*
* @type {number}
*/
get y3(): number {
return this._y3;
}
}
-1315
View File
File diff suppressed because it is too large Load Diff
-242
View File
@@ -1,242 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { debugMode } from "../../settings";
import { Clock, ClockEvent } from "./base";
import { ManualClock } from "./manual";
/**
* An implementation of libjass.renderers.Clock that automatically ticks and generates {@link libjass.renderers.ClockEvent}s according to the state of an external driver.
*
* For example, if you're using libjass to render subtitles on a canvas with your own video controls, these video controls will function as the driver to this AutoClock.
* It would call {@link libjass.renderers.AutoClock.play}, {@link libjass.renderers.AutoClock.play}, etc. when the user pressed the corresponding video controls.
*
* The difference from ManualClock is that AutoClock does not require the driver to call something like {@link libjass.renderers.ManualClock.tick}. Instead it keeps its
* own time with a high-resolution requestAnimationFrame-based timer.
*
* If using libjass with a <video> element, consider using {@link libjass.renderers.VideoClock} that uses the video element as a driver.
*
* @param {function():number} getCurrentTime A callback that will be invoked to get the current time of the external driver.
* @param {number} currentTimeUpdateMaxDelay If two calls to getCurrentTime are more than currentTimeUpdateMaxDelay milliseconds apart, then the external driver will be
* considered to have paused.
*/
export class AutoClock implements Clock {
private _manualClock: ManualClock = new ManualClock();
private _nextAnimationFrameRequestId: number = null;
private _lastKnownExternalTime: number = null;
private _lastKnownExternalTimeObtainedAt: number = null;
constructor(private _getCurrentTime: () => number, private _currentTimeUpdateMaxDelay: number) { }
/**
* Tells the clock to start generating ticks.
*/
play(): void {
if (!this._manualClock.enabled) {
return;
}
this._startTicking();
this._manualClock.play();
}
/**
* Tells the clock to pause.
*/
pause(): void {
if (!this._manualClock.enabled) {
return;
}
if (this._nextAnimationFrameRequestId === null) {
if (debugMode) {
console.warn("AutoClock.pause: Abnormal state detected. AutoClock._nextAnimationFrameRequestId should not have been null.");
}
return;
}
this._stopTicking();
this._manualClock.pause();
}
/**
* Tells the clock that the external driver is seeking.
*/
seeking(): void {
this._manualClock.seek(this._getCurrentTime());
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._manualClock.currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._manualClock.enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._manualClock.paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._manualClock.rate;
}
/**
* 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._manualClock.setRate(rate);
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (!this._manualClock.enable()) {
return false;
}
this._startTicking();
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._manualClock.disable()) {
return false;
}
this._stopTicking();
return true;
}
/**
* Toggle the clock.
*/
toggle(): void {
this._manualClock.toggle();
}
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
return this._manualClock.setEnabled(enabled);
}
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void {
this._manualClock.addEventListener(type, listener);
};
/**
* @param {number} timeStamp
*/
private _onTimerTick(timeStamp: number): void {
if (!this._manualClock.enabled) {
if (debugMode) {
console.warn("AutoClock._onTimerTick: Called when disabled.");
}
return;
}
var currentTime = this._manualClock.currentTime;
var currentExternalTime = this._getCurrentTime();
if (!this._manualClock.paused) {
if (this._lastKnownExternalTime !== null && currentExternalTime === this._lastKnownExternalTime) {
if (timeStamp - this._lastKnownExternalTimeObtainedAt > this._currentTimeUpdateMaxDelay) {
this._lastKnownExternalTimeObtainedAt = null;
this._manualClock.pause();
}
else {
this._manualClock.tick((timeStamp - this._lastKnownExternalTimeObtainedAt) / 1000 * this._manualClock.rate + this._lastKnownExternalTime);
}
}
else {
this._lastKnownExternalTime = currentExternalTime;
this._lastKnownExternalTimeObtainedAt = timeStamp;
this._manualClock.tick(currentExternalTime);
}
}
else {
if (currentTime !== currentExternalTime) {
this._lastKnownExternalTime = currentExternalTime;
this._lastKnownExternalTimeObtainedAt = timeStamp;
this._manualClock.tick(currentExternalTime);
}
}
this._nextAnimationFrameRequestId = requestAnimationFrame(timeStamp => this._onTimerTick(timeStamp));
}
private _startTicking(): void {
if (this._nextAnimationFrameRequestId === null) {
this._nextAnimationFrameRequestId = requestAnimationFrame(timeStamp => this._onTimerTick(timeStamp));
}
}
private _stopTicking(): void {
if (this._nextAnimationFrameRequestId !== null) {
cancelAnimationFrame(this._nextAnimationFrameRequestId);
this._nextAnimationFrameRequestId = null;
}
}
}
-137
View File
@@ -1,137 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../../utility/map";
/**
* A mixin class that represents an event source.
*/
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[]>;
/**
* Add a listener for the given event.
*
* @param {!T} type The type of event to attach the listener for
* @param {!Function} listener The listener
*/
addEventListener(type: T, listener: Function): void {
var listeners = this._eventListeners.get(type);
if (listeners === undefined) {
this._eventListeners.set(type, listeners = []);
}
listeners.push(listener);
}
/**
* 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
*/
_dispatchEvent(type: T, args: Object[]): void {
var listeners = this._eventListeners.get(type);
if (listeners !== undefined) {
for (let listener of listeners) {
listener.apply(this, args);
}
}
}
}
/**
* 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 {
/**
* @type {number}
*/
currentTime: number;
/**
* @type {boolean}
*/
enabled: boolean;
/**
* @type {boolean}
*/
paused: boolean;
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
rate: number;
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean;
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean;
/**
* Toggle the clock.
*/
toggle(): void;
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean;
// EventSource members
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void;
}
-244
View File
@@ -1,244 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mixin } from "../../utility/mixin";
import { Map } from "../../utility/map";
import { Clock, ClockEvent, EventSource } from "./base";
/**
* An implementation of {@link libjass.renderers.Clock} that allows user script to manually trigger {@link libjass.renderers.ClockEvent}s.
*/
export class ManualClock implements Clock, EventSource<ClockEvent> {
private _currentTime: number = -1;
private _rate: number = 1;
private _enabled: boolean = true;
private _paused: boolean = true;
/**
* Trigger a {@link libjass.renderers.ClockEvent.Play}
*/
play(): void {
if (!this._enabled) {
return;
}
if (!this._paused) {
return;
}
this._paused = false;
this._dispatchEvent(ClockEvent.Play, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Tick} with the given time.
*
* @param {number} currentTime
*/
tick(currentTime: number): void {
if (!this._enabled) {
return;
}
if (this._currentTime !== currentTime) {
this.play();
}
this._currentTime = currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
/**
* Seek to the given time. Unlike {@link libjass.renderers.ManualClock.tick} this is used to represent a discontinuous jump, such as the user seeking
* via the video element's position bar.
*
* @param {number} time
*/
seek(time: number): void {
if (!this._enabled) {
return;
}
this.pause();
if (this._currentTime === time) {
return;
}
this.stop();
this.tick(time);
this.pause();
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Pause}
*/
pause(): void {
if (!this._enabled) {
return;
}
if (this._paused) {
return;
}
this._paused = true;
this._dispatchEvent(ClockEvent.Pause, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Stop}
*/
stop(): void {
this._dispatchEvent(ClockEvent.Stop, []);
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._rate;
}
/**
* 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 {
if (this._rate === rate) {
return;
}
this._rate = rate;
this._dispatchEvent(ClockEvent.RateChange, []);
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (this._enabled) {
return false;
}
this._enabled = true;
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._enabled) {
return false;
}
this._enabled = false;
this.pause();
this.stop();
return true;
}
/**
* Toggle the clock.
*/
toggle(): void {
if (this._enabled) {
this.disable();
}
else {
this.enable();
}
}
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
if (enabled) {
return this.enable();
}
else {
return this.disable();
}
}
// EventSource members
/**
* @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]);
-112
View File
@@ -1,112 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AutoClock } from "./auto";
import { Clock, ClockEvent } from "./base";
/**
* An implementation of libjass.renderers.Clock that generates {@link libjass.renderers.ClockEvent}s according to the state of a <video> element.
*
* @param {!HTMLVideoElement} video
*/
export class VideoClock implements Clock {
private _autoClock: AutoClock;
constructor(video: HTMLVideoElement) {
this._autoClock = new AutoClock(() => video.currentTime, 100);
video.addEventListener("playing", () => this._autoClock.play(), false);
video.addEventListener("pause", () => this._autoClock.pause(), false);
video.addEventListener("seeking", () => this._autoClock.seeking(), false);
video.addEventListener("ratechange", () => this._autoClock.setRate(video.playbackRate), false);
}
/**
* @type {number}
*/
get currentTime(): number {
return this._autoClock.currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._autoClock.enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._autoClock.paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._autoClock.rate;
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
return this._autoClock.enable();
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
return this._autoClock.disable();
}
/**
* Toggle the clock.
*/
toggle(): void {
this._autoClock.toggle();
}
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
return this._autoClock.setEnabled(enabled);
}
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void {
this._autoClock.addEventListener(type, listener);
}
}
-36
View File
@@ -1,36 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface Document {
/**
* @type {Element}
*/
fullscreenElement: Element;
/**
* @type {Element}
*/
mozFullScreenElement: Element;
/**
* @type {Element}
*/
webkitFullscreenElement: Element;
}
-92
View File
@@ -1,92 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { VideoClock } from "./clocks/video";
import { RendererSettings } from "./settings";
import { WebRenderer } from "./web/renderer";
import { ASS } from "../types/ass";
///<reference path="./default-references.d.ts" />
/**
* A default renderer implementation.
*
* @param {!HTMLVideoElement} video
* @param {!libjass.ASS} ass
* @param {libjass.renderers.RendererSettings} settings
*/
export class DefaultRenderer extends WebRenderer {
private _videoIsFullScreen: boolean = false;
constructor(private _video: HTMLVideoElement, ass: ASS, settings?: RendererSettings) {
super(ass, new VideoClock(_video), document.createElement("div"), settings);
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
}
/**
* @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 {
document.addEventListener("mozfullscreenchange", event => this._onFullScreenChange(document.mozFullScreenElement), false);
document.addEventListener("webkitfullscreenchange", event => this._onFullScreenChange(document.webkitFullscreenElement), false);
document.addEventListener("fullscreenchange", event => this._onFullScreenChange(document.fullscreenElement), false);
this.resize(this._video.offsetWidth, this._video.offsetHeight);
super._ready();
}
/**
* @param {!Element} fullScreenElement
*/
private _onFullScreenChange(fullScreenElement: Element): void {
if (fullScreenElement === undefined) {
fullScreenElement = document.msFullscreenElement;
}
if (fullScreenElement === this._video) {
this.libjassSubsWrapper.classList.add("libjass-full-screen");
this.resize(screen.width, screen.height);
this._videoIsFullScreen = true;
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
}
else if (fullScreenElement === null && this._videoIsFullScreen) {
this.libjassSubsWrapper.classList.remove("libjass-full-screen");
this._videoIsFullScreen = false;
this._dispatchEvent("fullScreenChange", [this._videoIsFullScreen]);
}
}
}
-29
View File
@@ -1,29 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Clock, ClockEvent, EventSource } from "./clocks/base";
export { AutoClock } from "./clocks/auto";
export { ManualClock } from "./clocks/manual";
export { VideoClock } from "./clocks/video";
export { DefaultRenderer } from "./default";
export { NullRenderer } from "./null";
export { WebRenderer } from "./web/renderer";
export { RendererSettings } from "./settings";
-201
View File
@@ -1,201 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Clock, ClockEvent } from "./clocks/base";
import { RendererSettings } from "./settings";
import { verboseMode } from "../settings";
import { ASS } from "../types/ass";
import { Dialogue } from "../types/dialogue";
/**
* A renderer implementation that doesn't output anything.
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {libjass.renderers.RendererSettings} settings
*/
export class NullRenderer {
private static _lastRendererId = -1;
private _id: number;
private _settings: RendererSettings;
constructor(private _ass: ASS, private _clock: Clock, settings?: RendererSettings) {
this._id = ++NullRenderer._lastRendererId;
this._settings = RendererSettings.from(settings);
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
this._clock.addEventListener(ClockEvent.RateChange, () => this._onClockRateChange());
}
/**
* The unique ID of this renderer. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* @type {!libjass.ASS}
*/
get ass(): ASS {
return this._ass;
}
/**
* @type {!libjass.renderers.Clock}
*/
get clock(): Clock {
return this._clock;
}
/**
* @type {!libjass.renderers.RendererSettings}
*/
get settings(): RendererSettings {
return this._settings;
}
/**
* Pre-render a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
preRender(dialogue: Dialogue): void { }
/**
* Draw a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
draw(dialogue: Dialogue): void { }
/**
* Enable the renderer.
*
* @return {boolean} True if the renderer is now enabled, false if it was already enabled.
*/
enable(): boolean {
return this._clock.enable();
}
/**
* Disable the renderer.
*
* @return {boolean} True if the renderer is now disabled, false if it was already disabled.
*/
disable(): boolean {
return this._clock.disable();
}
/**
* Toggle the renderer.
*/
toggle(): void {
this._clock.toggle();
}
/**
* Enable or disable the renderer.
*
* @param {boolean} enabled If true, the renderer is enabled, otherwise it's disabled.
* @return {boolean} True if the renderer is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
return this._clock.setEnabled(enabled);
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._clock.enabled;
}
/**
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
*/
protected _onClockPlay(): void {
if (verboseMode) {
console.log("NullRenderer._onClockPlay");
}
}
/**
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
*/
protected _onClockTick(): void {
var currentTime = this._clock.currentTime;
if (verboseMode) {
console.log(`NullRenderer._onClockTick: currentTime = ${ currentTime }`);
}
for (let dialogue of this._ass.dialogues) {
if (dialogue.end > currentTime) {
if (dialogue.start <= currentTime) {
// This dialogue is visible right now. Draw it.
this.draw(dialogue);
}
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
// This dialogue will be visible soon. Pre-render it.
this.preRender(dialogue);
}
}
}
}
/**
* Runs when the clock is paused.
*/
protected _onClockPause(): void {
if (verboseMode) {
console.log("NullRenderer._onClockPause");
}
}
/**
* Runs when the clock is disabled.
*/
protected _onClockStop(): void {
if (verboseMode) {
console.log("NullRenderer._onClockStop");
}
}
/**
* Runs when the clock changes its rate.
*/
protected _onClockRateChange(): void {
if (verboseMode) {
console.log("NullRenderer._onClockRateChange");
}
}
}
-136
View File
@@ -1,136 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
/**
* Settings for the renderer.
*/
export class RendererSettings {
/**
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
*
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.RendererSettings.makeFontMapFromStyleElement}
* convenience method to create a font map.
*
* @type {!Map.<string, !Array.<string>>}
*/
fontMap: Map<string, string[]>;
/**
* Subtitles will be pre-rendered for this amount of time (seconds).
*
* Defaults to 5.
*
* @type {number}
*/
preRenderTime: number;
/**
* Subtitle outlines will be rendered in full detail. When false, the value of blur is used to draw less outlines for better performance and (hopefully) similar output.
*
* Defaults to false.
*
* @type {boolean}
*/
preciseOutlines: boolean;
/**
* 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. See http://caniuse.com/svg-html for details.
*
* 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");
* }
*
* @param {!LinkStyle} linkStyle
* @return {!Map.<string, !Array.<string>>}
*/
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string[]> {
var fontMap = new Map<string, string[]>();
var styleSheet = <CSSStyleSheet>linkStyle.sheet;
var rules: CSSFontFaceRule[] = Array.prototype.filter.call(styleSheet.cssRules, (rule: CSSRule) => rule.type === CSSRule.FONT_FACE_RULE);
for (let rule of rules) {
var src = rule.style.getPropertyValue("src");
if (!src) {
src = rule.cssText.split("\n")
.map(line => line.match(/src: ([^;]+);/))
.filter(matches => matches !== null)
.map(matches => matches[1])[0];
}
var urls = src.split(/,\s*/).map(url => RendererSettings._stripQuotes(url.match(/^url\((.+)\)$/)[1]));
if (urls.length > 0) {
var name = RendererSettings._stripQuotes(rule.style.getPropertyValue("font-family"));
var existingList = fontMap.get(name);
if (existingList === undefined) {
existingList = [];
fontMap.set(name, existingList);
}
existingList.unshift.apply(existingList, urls);
}
}
return fontMap;
}
/**
* Converts an arbitrary object into a {@link libjass.renderers.RendererSettings} object.
*
* @param {*} object
* @return {!libjass.renderers.RendererSettings}
*/
static from(object?: any): RendererSettings {
if (object === undefined || object === null) {
object = {};
}
var { fontMap = null, preRenderTime = 5, preciseOutlines = false, enableSvg = true } = <RendererSettings>object;
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];
}
}
-132
View File
@@ -1,132 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Keyframe } from "./keyframe";
import { NullRenderer } from "../null";
import { Map } from "../../utility/map";
/**
* 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
*/
export class AnimationCollection {
private static _nextId: number = 0;
private _id: string;
private _rate: number;
private _cssText: string = "";
private _animationStyle: string = "";
private _animationDelays: Map<string, number> = new Map<string, number>();
private _numAnimations: number = 0;
constructor(renderer: NullRenderer) {
this._id = `${ renderer.id }-${ AnimationCollection._nextId++ }`;
this._rate = renderer.clock.rate;
}
/**
* This string contains the animation definitions and should be inserted into a <style> element.
*
* @type {string}
*/
get cssText(): string {
return this._cssText;
}
/**
* This string should be set as the "animation" CSS property of the target element.
*
* @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(): Map<string, 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.<!libjass.renderers.Keyframe>} keyframes
*/
add(timingFunction: string, keyframes: Keyframe[]): void {
var start: number = null;
var end: number = null;
for (let keyframe of keyframes) {
if (start === null) {
start = keyframe.time;
}
end = keyframe.time;
}
var ruleCssText = "";
for (let keyframe of keyframes) {
ruleCssText +=
` ${ (100 * ((end - start === 0) ? 1 : ((keyframe.time - start) / (end - start)))).toFixed(3) }% {
`;
keyframe.properties.forEach((value, name) => {
ruleCssText +=
` ${ name }: ${ value };
`;
});
ruleCssText +=
` }
`;
}
var animationName = `animation-${ this._id }-${ this._numAnimations++ }`;
this._cssText +=
`@-webkit-keyframes ${ animationName } {
${ ruleCssText }
}
@keyframes ${ animationName } {
${ ruleCssText }
}
`;
if (this._animationStyle !== "") {
this._animationStyle += ",";
}
this._animationStyle += `${ animationName } ${ ((end - start) / this._rate).toFixed(3) }s ${ timingFunction }`;
this._animationDelays.set(animationName, start);
}
}
-25
View File
@@ -1,25 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {!DomParser} */
export var domParser: DOMParser = null;
if (typeof DOMParser !== "undefined") {
domParser = new DOMParser();
}
-94
View File
@@ -1,94 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { domParser } from "./dom-parser";
import * as parts from "../../parts/index";
/**
* This class represents an ASS drawing - a set of drawing instructions between {\p} tags.
*
* @param {number} outputScaleX
* @param {number} outputScaleY
*/
export class DrawingStyles {
private _scale: number = 1;
private _baselineOffset: number = 0;
constructor(private _outputScaleX: number, private _outputScaleY: number) { }
/**
* @type {number}
*/
set scale(value: number) {
this._scale = value;
}
/**
* @type {number}
*/
set baselineOffset(value: number) {
this._baselineOffset = value;
}
/**
* Converts this drawing to an <svg> element.
*
* @param {!libjass.parts.DrawingInstructions} drawingInstructions
* @param {!libjass.parts.Color} fillColor
* @return {!SVGSVGElement}
*/
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGElement {
var scaleFactor = Math.pow(2, this._scale - 1);
var scaleX = this._outputScaleX / scaleFactor;
var scaleY = this._outputScaleY / scaleFactor;
var path = "";
var bboxWidth = 0;
var bboxHeight = 0;
for (let instruction of drawingInstructions.instructions) {
if (instruction instanceof parts.drawing.MoveInstruction) {
path += ` M ${ instruction.x } ${ instruction.y + this._baselineOffset }`;
bboxWidth = Math.max(bboxWidth, instruction.x);
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.LineInstruction) {
path += ` L ${ instruction.x } ${ instruction.y + this._baselineOffset }`;
bboxWidth = Math.max(bboxWidth, instruction.x);
bboxHeight = Math.max(bboxHeight, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.CubicBezierCurveInstruction) {
path += ` C ${ instruction.x1 } ${ instruction.y1 + this._baselineOffset }, ${ instruction.x2 } ${ instruction.y2 + this._baselineOffset }, ${ instruction.x3 } ${ instruction.y3 + this._baselineOffset }`;
bboxWidth = Math.max(bboxWidth, instruction.x1, instruction.x2, instruction.x3);
bboxHeight = Math.max(bboxHeight, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
}
}
var result =
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${ (bboxWidth * scaleX).toFixed(3) }px" height="${ (bboxHeight * scaleY).toFixed(3) }px">
<g transform="scale(${ scaleX.toFixed(3) } ${ scaleY.toFixed(3) })">
<path d="${ path }" fill="${ fillColor.toString() }" />
</g>
</svg>`;
return <SVGSVGElement>domParser.parseFromString(result, "image/svg+xml").childNodes[0];
}
}
-45
View File
@@ -1,45 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../../utility/map";
/**
* 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 {!Map.<string, string>} properties
*/
export class Keyframe {
constructor(private _time: number, private _properties: Map<string, string>) { }
/**
* @type {number}
*/
get time(): number {
return this._time;
}
/**
* @type {!Map.<string, string>}
*/
get properties(): Map<string, string> {
return this._properties;
}
}
-52
View File
@@ -1,52 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface CSSStyleDeclaration {
/**
* @type {string}
*/
webkitAnimation: string;
/**
* @type {string}
*/
webkitAnimationDelay: string;
/**
* @type {string}
*/
webkitAnimationName: string;
/**
* @type {string}
*/
webkitFilter: string;
/**
* @type {string}
*/
webkitTransform: string;
/**
* @type {string}
*/
webkitTransformOrigin: string;
}
-744
View File
@@ -1,744 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AnimationCollection } from "./animation-collection";
import { DrawingStyles } from "./drawing-styles";
import { Keyframe } from "./keyframe";
import { SpanStyles } from "./span-styles";
import { Clock, EventSource } from "../clocks/base";
import { NullRenderer } from "../null";
import { RendererSettings } from "../settings";
import * as parts from "../../parts/index";
import { debugMode } from "../../settings";
import { ASS } from "../../types/ass";
import { Dialogue } from "../../types/dialogue";
import { WrappingStyle } from "../../types/misc";
import { mixin } from "../../utility/mixin";
import { Map } from "../../utility/map";
import { Set } from "../../utility/set";
import { Promise } from "../../utility/promise";
///<reference path="./references.d.ts" />
/**
* A renderer implementation that draws subtitles to the given <div>
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {!HTMLDivElement} libjassSubsWrapper Subtitles will be rendered to this <div>
* @param {!libjass.renderers.RendererSettings} settings
*/
export class WebRenderer extends NullRenderer implements EventSource<string> {
private _subsWrapper: HTMLDivElement;
private _layerWrappers: HTMLDivElement[] = [];
private _layerAlignmentWrappers: HTMLDivElement[][] = [];
private _fontSizeElement: HTMLDivElement;
private _animationStyleElement: HTMLStyleElement;
private _svgDefsElement: SVGDefsElement;
private _currentSubs: Map<Dialogue, HTMLDivElement> = new Map<Dialogue, HTMLDivElement>();
private _preRenderedSubs: Map<number, PreRenderedSub> = new Map<number, PreRenderedSub>();
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;
})());
this._libjassSubsWrapper.classList.add("libjass-wrapper");
this._subsWrapper = document.createElement("div");
this._libjassSubsWrapper.appendChild(this._subsWrapper);
this._subsWrapper.className = "libjass-subs";
this._fontSizeElement = document.createElement("div");
this._libjassSubsWrapper.appendChild(this._fontSizeElement);
this._fontSizeElement.className = "libjass-font-measure";
this._fontSizeElement.appendChild(document.createTextNode("M"));
this._animationStyleElement = document.createElement("style");
this._animationStyleElement.id = `libjass-animation-styles-${ this.id }`;
this._animationStyleElement.type = "text/css";
document.querySelector("head").appendChild(this._animationStyleElement);
var svgElement = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
this._libjassSubsWrapper.appendChild(svgElement);
svgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgElement.setAttribute("version", "1.1");
svgElement.setAttribute("class", "libjass-filters");
svgElement.setAttribute("width", "0");
svgElement.setAttribute("height", "0");
this._svgDefsElement = <SVGDefsElement>document.createElementNS("http://www.w3.org/2000/svg", "defs");
svgElement.appendChild(this._svgDefsElement);
// Preload fonts
var urlsToPreload = new Set<string>();
if (this.settings.fontMap !== null) {
this.settings.fontMap.forEach(srcs => {
for (let src of srcs) {
urlsToPreload.add(src);
}
});
}
if (debugMode) {
console.log(`Preloading ${ urlsToPreload.size } fonts...`);
}
var xhrPromises: Promise<void>[] = [];
urlsToPreload.forEach(url => {
xhrPromises.push(new Promise<void>((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", () => {
if (debugMode) {
console.log(`Preloaded ${ url }.`);
}
resolve(null);
});
xhr.open("GET", url, true);
xhr.send();
}));
});
Promise.all(xhrPromises).then(() => {
if (debugMode) {
console.log("All fonts have been preloaded.");
}
this._ready();
});
}
/**
* @type {!HTMLDivElement}
*/
get libjassSubsWrapper(): HTMLDivElement {
return this._libjassSubsWrapper;
}
/**
* Resize the subtitles to the given new dimensions.
*
* @param {number} width
* @param {number} height
*/
resize(width: number, height: number): void {
this._removeAllSubs();
var ratio = Math.min(width / this.ass.properties.resolutionX, height / this.ass.properties.resolutionY);
var subsWrapperWidth = this.ass.properties.resolutionX * ratio;
var subsWrapperHeight = this.ass.properties.resolutionY * ratio;
this._subsWrapper.style.width = `${ subsWrapperWidth.toFixed(3) }px`;
this._subsWrapper.style.height = `${ subsWrapperHeight.toFixed(3) }px`;
this._subsWrapper.style.left = `${ ((width - subsWrapperWidth) / 2).toFixed(3) }px`;
this._subsWrapper.style.top = `${ ((height - subsWrapperHeight) / 2).toFixed(3) }px`;
this._scaleX = subsWrapperWidth / this.ass.properties.resolutionX;
this._scaleY = subsWrapperHeight / this.ass.properties.resolutionY;
// 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);
}
// this.currentTime will be -1 if resize() is called before the clock begins playing for the first time. In this situation, there is no need to force a redraw.
if (this.clock.currentTime !== -1) {
this._onClockTick();
}
}
/**
* 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.
*
* @param {!libjass.Dialogue} dialogue
*/
preRender(dialogue: Dialogue): void {
if (this._preRenderedSubs.has(dialogue.id)) {
return;
}
var sub = document.createElement("div");
sub.style.marginLeft = `${ (this._scaleX * dialogue.style.marginLeft).toFixed(3) }px`;
sub.style.marginRight = `${ (this._scaleX * dialogue.style.marginRight).toFixed(3) }px`;
sub.style.marginTop = sub.style.marginBottom = `${ (this._scaleY * dialogue.style.marginVertical).toFixed(3) }px`;
sub.style.minWidth = `${ (this._subsWrapper.offsetWidth - this._scaleX * (dialogue.style.marginLeft + dialogue.style.marginRight)).toFixed(3) }px`;
var dialogueAnimationCollection = new AnimationCollection(this);
var currentSpan: HTMLSpanElement = null;
var currentSpanStyles = new SpanStyles(this, dialogue, this._scaleX, this._scaleY, this.settings, this._fontSizeElement, this._svgDefsElement);
var currentAnimationCollection: AnimationCollection = null;
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, currentAnimationCollection, this._animationStyleElement));
}
if (currentAnimationCollection !== null) {
currentAnimationCollection.animationDelays.forEach((delay, name) => dialogueAnimationCollection.animationDelays.set(name, delay));
}
if (addNewLine) {
if (previousAddNewLine) {
sub.appendChild(currentSpanStyles.makeNewLine());
}
else {
sub.appendChild(document.createElement("br"));
}
}
currentSpan = document.createElement("span");
currentAnimationCollection = new AnimationCollection(this);
previousAddNewLine = addNewLine;
};
startNewSpan(false);
var currentDrawingStyles: DrawingStyles = new DrawingStyles(this._scaleX, this._scaleY);
var wrappingStyle = this.ass.properties.wrappingStyle;
var karaokeTimesAccumulator = 0;
for (let part of dialogue.parts) {
if (part instanceof parts.Italic) {
currentSpanStyles.italic = part.value;
}
else if (part instanceof parts.Bold) {
currentSpanStyles.bold = part.value;
}
else if (part instanceof parts.Underline) {
currentSpanStyles.underline = part.value;
}
else if (part instanceof parts.StrikeThrough) {
currentSpanStyles.strikeThrough = part.value;
}
else if (part instanceof parts.Border) {
currentSpanStyles.outlineWidth = part.value;
currentSpanStyles.outlineHeight = part.value;
}
else if (part instanceof parts.BorderX) {
currentSpanStyles.outlineWidth = part.value;
}
else if (part instanceof parts.BorderY) {
currentSpanStyles.outlineHeight = part.value;
}
else if (part instanceof parts.Shadow) {
currentSpanStyles.shadowDepthX = part.value;
currentSpanStyles.shadowDepthY = part.value;
}
else if (part instanceof parts.ShadowX) {
currentSpanStyles.shadowDepthX = part.value;
}
else if (part instanceof parts.ShadowY) {
currentSpanStyles.shadowDepthY = part.value;
}
else if (part instanceof parts.Blur) {
currentSpanStyles.blur = part.value;
}
else if (part instanceof parts.GaussianBlur) {
currentSpanStyles.gaussianBlur = part.value;
}
else if (part instanceof parts.FontName) {
currentSpanStyles.fontName = part.value;
}
else if (part instanceof parts.FontSize) {
currentSpanStyles.fontSize = part.value;
}
else if (part instanceof parts.FontSizePlus) {
currentSpanStyles.fontSize += part.value;
}
else if (part instanceof parts.FontSizeMinus) {
currentSpanStyles.fontSize -= part.value;
}
else if (part instanceof parts.FontScaleX) {
currentSpanStyles.fontScaleX = part.value;
}
else if (part instanceof parts.FontScaleY) {
currentSpanStyles.fontScaleY = part.value;
}
else if (part instanceof parts.LetterSpacing) {
currentSpanStyles.letterSpacing = part.value;
}
else if (part instanceof parts.RotateX) {
currentSpanStyles.rotationX = part.value;
}
else if (part instanceof parts.RotateY) {
currentSpanStyles.rotationY = part.value;
}
else if (part instanceof parts.RotateZ) {
currentSpanStyles.rotationZ = part.value;
}
else if (part instanceof parts.SkewX) {
currentSpanStyles.skewX = part.value;
}
else if (part instanceof parts.SkewY) {
currentSpanStyles.skewY = part.value;
}
else if (part instanceof parts.PrimaryColor) {
currentSpanStyles.primaryColor = part.value;
}
else if (part instanceof parts.SecondaryColor) {
currentSpanStyles.secondaryColor = part.value;
}
else if (part instanceof parts.OutlineColor) {
currentSpanStyles.outlineColor = part.value;
}
else if (part instanceof parts.ShadowColor) {
currentSpanStyles.shadowColor = part.value;
}
else if (part instanceof parts.Alpha) {
currentSpanStyles.primaryAlpha = part.value;
currentSpanStyles.secondaryAlpha = part.value;
currentSpanStyles.outlineAlpha = part.value;
currentSpanStyles.shadowAlpha = part.value;
}
else if (part instanceof parts.PrimaryAlpha) {
currentSpanStyles.primaryAlpha = part.value;
}
else if (part instanceof parts.SecondaryAlpha) {
currentSpanStyles.secondaryAlpha = part.value;
}
else if (part instanceof parts.OutlineAlpha) {
currentSpanStyles.outlineAlpha = part.value;
}
else if (part instanceof parts.ShadowAlpha) {
currentSpanStyles.shadowAlpha = part.value;
}
else if (part instanceof parts.Alignment) {
// Already handled in Dialogue
}
else if (part instanceof parts.ColorKaraoke) {
startNewSpan(false);
currentAnimationCollection.add("step-end", [
new Keyframe(0, new Map([
["color", currentSpanStyles.secondaryColor.withAlpha(currentSpanStyles.secondaryAlpha).toString()],
])), new Keyframe(karaokeTimesAccumulator, new Map([
["color", currentSpanStyles.primaryColor.withAlpha(currentSpanStyles.primaryAlpha).toString()],
]))
]);
karaokeTimesAccumulator += part.duration;
}
else if (part instanceof parts.WrappingStyle) {
wrappingStyle = part.value;
}
else if (part instanceof parts.Reset) {
currentSpanStyles.reset(this.ass.styles.get(part.value));
}
else if (part instanceof parts.Position) {
sub.style.position = "absolute";
sub.style.left = `${ (this._scaleX * part.x).toFixed(3) }px`;
sub.style.top = `${ (this._scaleY * part.y).toFixed(3) }px`;
}
else if (part instanceof parts.Move) {
sub.style.position = "absolute";
dialogueAnimationCollection.add("linear", [new Keyframe(0, new Map([
["left", `${ (this._scaleX * part.x1).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y1).toFixed(3) }px`],
])), new Keyframe(part.t1, new Map([
["left", `${ (this._scaleX * part.x1).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y1).toFixed(3) }px`],
])), new Keyframe(part.t2, new Map([
["left", `${ (this._scaleX * part.x2).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y2).toFixed(3) }px`],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["left", `${ (this._scaleX * part.x2).toFixed(3) }px`],
["top", `${ (this._scaleY * part.y2).toFixed(3) }px`],
]))]);
}
else if (part instanceof parts.Fade) {
dialogueAnimationCollection.add("linear", [new Keyframe(0, new Map([
["opacity", "0"],
])), new Keyframe(part.start, new Map([
["opacity", "1"],
])), new Keyframe(dialogue.end - dialogue.start - part.end, new Map([
["opacity", "1"],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["opacity", "0"],
]))]);
}
else if (part instanceof parts.ComplexFade) {
dialogueAnimationCollection.add("linear", [new Keyframe(0, new Map([
["opacity", part.a1.toFixed(3)],
])), new Keyframe(part.t1, new Map([
["opacity", part.a1.toFixed(3)],
])), new Keyframe(part.t2, new Map([
["opacity", part.a2.toFixed(3)],
])), new Keyframe(part.t3, new Map([
["opacity", part.a2.toFixed(3)],
])), new Keyframe(part.t4, new Map([
["opacity", part.a3.toFixed(3)],
])), new Keyframe(dialogue.end - dialogue.start, new Map([
["opacity", part.a3.toFixed(3)],
]))]);
}
else if (part instanceof parts.DrawingMode) {
if (part.scale !== 0) {
currentDrawingStyles.scale = part.scale;
}
}
else if (part instanceof parts.DrawingBaselineOffset) {
currentDrawingStyles.baselineOffset = part.value;
}
else if (part instanceof parts.DrawingInstructions) {
currentSpan.appendChild(currentDrawingStyles.toSVG(part, currentSpanStyles.primaryColor.withAlpha(currentSpanStyles.primaryAlpha)));
startNewSpan(false);
}
else if (part instanceof parts.Text) {
currentSpan.appendChild(document.createTextNode(part.value));
startNewSpan(false);
}
else if (debugMode && part instanceof parts.Comment) {
currentSpan.appendChild(document.createTextNode(part.value));
startNewSpan(false);
}
else if (part instanceof parts.NewLine) {
startNewSpan(true);
}
}
for (let part of dialogue.parts) {
if (part instanceof parts.Position || part instanceof parts.Move) {
var transformOrigin = WebRenderer._transformOrigins[dialogue.alignment];
var divTransformStyle = `translate(${ -transformOrigin[0] }%, ${ -transformOrigin[1] }%) translate(-${ sub.style.marginLeft }, -${ sub.style.marginTop })`;
var transformOriginString = `${ transformOrigin[0] }% ${ transformOrigin[1] }%`;
sub.style.webkitTransform = divTransformStyle;
sub.style.webkitTransformOrigin = transformOriginString;
sub.style.transform = divTransformStyle;
sub.style.transformOrigin = transformOriginString;
break;
}
}
switch (wrappingStyle) {
case WrappingStyle.EndOfLineWrapping:
sub.style.whiteSpace = "pre-wrap";
break;
case WrappingStyle.NoLineWrapping:
sub.style.whiteSpace = "pre";
break;
case WrappingStyle.SmartWrappingWithWiderTopLine:
case WrappingStyle.SmartWrappingWithWiderBottomLine:
/* Not supported. Treat the same as EndOfLineWrapping */
sub.style.whiteSpace = "pre-wrap";
break;
}
if (sub.style.position !== "") {
// Explicitly set text alignment on absolutely-positioned subs because they'll go in a .an0 <div> and so won't get alignment CSS text-align.
switch (dialogue.alignment) {
case 1: case 4: case 7: sub.style.textAlign = "left"; break;
case 2: case 5: case 8: sub.style.textAlign = "center"; break;
case 3: case 6: case 9: sub.style.textAlign = "right"; break;
}
}
this._animationStyleElement.appendChild(document.createTextNode(dialogueAnimationCollection.cssText));
sub.style.webkitAnimation = dialogueAnimationCollection.animationStyle;
sub.style.animation = dialogueAnimationCollection.animationStyle;
sub.setAttribute("data-dialogue-id", `${ this.id }-${ dialogue.id }`);
this._preRenderedSubs.set(dialogue.id, { sub, animationDelays: dialogueAnimationCollection.animationDelays });
}
/**
* Returns the subtitle div for display. The {@link libjass.renderers.Clock.currentTime} of the {@link libjass.renderers.NullRenderer.clock} 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
*/
draw(dialogue: Dialogue): void {
if (this._currentSubs.has(dialogue)) {
return;
}
if (debugMode) {
console.log(dialogue.toString());
}
var preRenderedSub = this._preRenderedSubs.get(dialogue.id);
if (preRenderedSub === undefined) {
if (debugMode) {
console.warn("This dialogue was not pre-rendered. Call preRender() before calling draw() so that draw() is faster.");
}
this.preRender(dialogue);
preRenderedSub = this._preRenderedSubs.get(dialogue.id);
if (debugMode) {
console.log(dialogue.toString());
}
}
var result = <HTMLDivElement>preRenderedSub.sub.cloneNode(true);
var applyAnimationDelays = (node: HTMLElement) => {
var animationNames = node.style.animationName || node.style.webkitAnimationName;
if (animationNames !== undefined && animationNames !== "") {
var animationDelays = animationNames.split(",").map(name => {
name = name.trim();
var delay = preRenderedSub.animationDelays.get(name);
return `${ ((delay + dialogue.start - this.clock.currentTime) / this.clock.rate).toFixed(3) }s`;
}).join(", ");
node.style.webkitAnimationDelay = animationDelays;
node.style.animationDelay = animationDelays;
}
}
applyAnimationDelays(result);
var animatedDescendants = result.querySelectorAll('[style*="animation:"]');
for (var i = 0; i < animatedDescendants.length; i++) {
applyAnimationDelays(<HTMLElement>animatedDescendants[i]);
}
var layer = dialogue.layer;
var alignment = (result.style.position === "absolute") ? 0 : dialogue.alignment; // Alignment 0 is for absolutely-positioned subs
// Create the layer wrapper div and the alignment div inside it if not already created
if (this._layerWrappers[layer] === undefined) {
var layerWrapper = document.createElement("div");
layerWrapper.className = `layer layer${ layer }`;
// Find the next greater layer div and insert this div before that one
var insertBeforeElement: HTMLDivElement = null;
for (var insertBeforeLayer = layer + 1; insertBeforeLayer < this._layerWrappers.length && insertBeforeElement === null; insertBeforeLayer++) {
if (this._layerWrappers[insertBeforeLayer] !== undefined) {
insertBeforeElement = this._layerWrappers[insertBeforeLayer];
}
}
this._subsWrapper.insertBefore(layerWrapper, insertBeforeElement);
this._layerWrappers[layer] = layerWrapper;
this._layerAlignmentWrappers[layer] = [];
}
if (this._layerAlignmentWrappers[layer][alignment] === undefined) {
var layerAlignmentWrapper = document.createElement("div");
layerAlignmentWrapper.className = `an an${ alignment }`;
// Find the next greater layer,alignment div and insert this div before that one
var layerWrapper = this._layerWrappers[layer];
var insertBeforeElement: HTMLDivElement = null;
for (var insertBeforeAlignment = alignment + 1; insertBeforeAlignment < this._layerAlignmentWrappers[layer].length && insertBeforeElement === null; insertBeforeAlignment++) {
if (this._layerAlignmentWrappers[layer][insertBeforeAlignment] !== undefined) {
insertBeforeElement = this._layerAlignmentWrappers[layer][insertBeforeAlignment];
}
}
layerWrapper.insertBefore(layerAlignmentWrapper, insertBeforeElement);
this._layerAlignmentWrappers[layer][alignment] = layerAlignmentWrapper;
}
this._layerAlignmentWrappers[layer][alignment].appendChild(result);
this._currentSubs.set(dialogue, result);
}
protected _onClockPlay(): void {
super._onClockPlay();
this._removeAllSubs();
this._subsWrapper.style.display = "";
this._subsWrapper.classList.remove("paused");
}
protected _onClockTick(): void {
// Remove dialogues that should be removed before adding new ones via super._onClockTick()
var currentTime = this.clock.currentTime;
this._currentSubs.forEach((sub: HTMLDivElement, dialogue: Dialogue) => {
if (dialogue.start > currentTime || dialogue.end < currentTime) {
this._currentSubs.delete(dialogue);
this._removeSub(sub);
}
});
super._onClockTick();
}
protected _onClockPause(): void {
super._onClockPause();
this._subsWrapper.classList.add("paused");
}
protected _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 {
this._dispatchEvent("ready", []);
}
/**
* @param {!HTMLDivElement} sub
*/
private _removeSub(sub: HTMLDivElement): void {
sub.parentNode.removeChild(sub);
}
private _removeAllSubs(): void {
this._currentSubs.forEach((sub: HTMLDivElement) => this._removeSub(sub));
this._currentSubs.clear();
}
private static _transformOrigins: number[][] = [
null,
[0, 100], [50, 100], [100, 100],
[0, 50], [50, 50], [100, 50],
[0, 0], [50, 0], [100, 0]
];
// 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]);
interface PreRenderedSub {
/** @type {!HTMLDivElement} */
sub: HTMLDivElement;
/** @type {!Map.<string, number>} */
animationDelays: Map<string, number>;
}
-713
View File
@@ -1,713 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AnimationCollection } from "./animation-collection";
import { domParser } from "./dom-parser";
import { WebRenderer } from "./renderer";
import { RendererSettings } from "../settings";
import { Color } from "../../parts/index";
import { Style } from "../../types/style";
import { Dialogue } from "../../types/dialogue";
import { Map } from "../../utility/map";
///<reference path="./web-references.d.ts" />
/**
* 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.
*
* @param {!libjass.renderers.NullRenderer} renderer The renderer that this set of styles is associated with
* @param {!libjass.Dialogue} dialogue The Dialogue that this set of styles is associated with
* @param {number} scaleX The horizontal scaling of the subtitles
* @param {number} scaleY The vertical scaling of the subtitles
* @param {!libjass.renderers.RendererSettings} settings The renderer settings
* @param {!HTMLDivElement} fontSizeElement A <div> element to measure font sizes with
* @param {!SVGDefsElement} svgDefsElement An SVG <defs> element to append filter definitions to
*/
export class SpanStyles {
private static _fontSizeCache: Map<string, Map<number, number>> = new Map<string, Map<number, number>>();
private _id: string;
private _defaultStyle: Style;
private _italic: boolean;
private _bold: Object;
private _underline: boolean;
private _strikeThrough: boolean;
private _outlineWidth: number;
private _outlineHeight: number;
private _shadowDepthX: number;
private _shadowDepthY: number;
private _fontName: string;
private _fontSize: number;
private _fontScaleX: number;
private _fontScaleY: number;
private _letterSpacing: number;
private _rotationX: number;
private _rotationY: number;
private _rotationZ: number;
private _skewX: number;
private _skewY: number;
private _primaryColor: Color;
private _secondaryColor: Color;
private _outlineColor: Color;
private _shadowColor: Color;
private _primaryAlpha: number;
private _secondaryAlpha: number;
private _outlineAlpha: number;
private _shadowAlpha: number;
private _blur: number;
private _gaussianBlur: number;
private _nextFilterId = 0;
constructor(renderer: WebRenderer, dialogue: Dialogue, private _scaleX: number, private _scaleY: number, private _settings: RendererSettings, private _fontSizeElement: HTMLDivElement, private _svgDefsElement: SVGDefsElement) {
this._id = `${ renderer.id }-${ dialogue.id }`;
this._defaultStyle = dialogue.style;
this.reset(null);
}
/**
* Resets the styles to the defaults provided by the argument.
*
* @param {libjass.Style} newStyle The new defaults to reset the style to. If null, the styles are reset to the default style of the Dialogue.
*/
reset(newStyle: Style): void {
if (newStyle === undefined || newStyle === null) {
newStyle = this._defaultStyle;
}
this.italic = newStyle.italic;
this.bold = newStyle.bold;
this.underline = newStyle.underline;
this.strikeThrough = newStyle.strikeThrough;
this.outlineWidth = newStyle.outlineThickness;
this.outlineHeight = newStyle.outlineThickness;
this.shadowDepthX = newStyle.shadowDepth;
this.shadowDepthY = newStyle.shadowDepth;
this.fontName = newStyle.fontName;
this.fontSize = newStyle.fontSize;
this.fontScaleX = newStyle.fontScaleX;
this.fontScaleY = newStyle.fontScaleY;
this.letterSpacing = newStyle.letterSpacing;
this._rotationX = null;
this._rotationY = null;
this._rotationZ = newStyle.rotationZ;
this._skewX = null;
this._skewY = null;
this.primaryColor = newStyle.primaryColor;
this.secondaryColor = newStyle.secondaryColor;
this.outlineColor = newStyle.outlineColor;
this.shadowColor = newStyle.shadowColor;
this.primaryAlpha = null;
this.secondaryAlpha = null;
this.outlineAlpha = null;
this.shadowAlpha = null;
this.blur = null;
this.gaussianBlur = null;
}
/**
* Sets the style attribute on the given span element.
*
* @param {!HTMLSpanElement} span
* @param {!AnimationCollection} animationCollection
* @param {!HTMLStyleElement} animationStyleElement
* @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, animationCollection: AnimationCollection, animationStyleElement: HTMLStyleElement): HTMLSpanElement {
var isTextOnlySpan = span.childNodes[0] instanceof Text;
var fontStyleOrWeight = "";
if (this._italic) {
fontStyleOrWeight += "italic ";
}
if (this._bold === true) {
fontStyleOrWeight += "bold ";
}
else if (this._bold !== false) {
fontStyleOrWeight += this._bold + " ";
}
var fontSize: string;
if (isTextOnlySpan) {
fontSize = (this._scaleY * SpanStyles._getFontSize(this._fontName, this._fontSize * this._fontScaleY, this._fontSizeElement)).toFixed(3);
}
else {
fontSize = (this._scaleY * SpanStyles._getFontSize(this._fontName, this._fontSize, this._fontSizeElement)).toFixed(3);
}
var lineHeight = (this._scaleY * this._fontSize).toFixed(3);
span.style.font = `${ fontStyleOrWeight }${ fontSize }px/${ lineHeight }px "${ this._fontName }"`;
var textDecoration = "";
if (this._underline) {
textDecoration = "underline";
}
if (this._strikeThrough) {
textDecoration += " line-through";
}
span.style.textDecoration = textDecoration.trim();
var transform = "";
if (isTextOnlySpan) {
if (this._fontScaleY !== this._fontScaleX) {
transform += `scaleY(${ (this._fontScaleY / this._fontScaleX).toFixed(3) }) `;
}
}
else {
if (this._fontScaleX !== 1) {
transform += `scaleX(${ this._fontScaleX }) `;
}
if (this._fontScaleY !== 1) {
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);
transform += `matrix(1, ${ skewY }, ${ skewX }, 1, 0, 0) `;
}
if (transform !== "") {
span.style.webkitTransform = transform;
span.style.webkitTransformOrigin = "50% 50%";
span.style.transform = transform;
span.style.transformOrigin = "50% 50%";
span.style.display = "inline-block";
}
span.style.letterSpacing = `${ (this._scaleX * this._letterSpacing).toFixed(3) }px`;
var primaryColor = this._primaryColor.withAlpha(this._primaryAlpha);
span.style.color = primaryColor.toString();
var outlineColor = this._outlineColor.withAlpha(this._outlineAlpha);
var outlineWidth = this._scaleX * this._outlineWidth;
var outlineHeight = this._scaleY * this._outlineHeight;
var outlineFilter = '';
var blurFilter = '';
if (this._settings.enableSvg) {
var filterId = `svg-filter-${ this._id }-${ this._nextFilterId++ }`;
if (outlineWidth > 0 || outlineHeight > 0) {
/* Construct an elliptical border by merging together many rectangles. The border is creating using dilate morphology filters, but these only support
* generating rectangles. http://lists.w3.org/Archives/Public/public-fx/2012OctDec/0003.html
*/
var mergeOutlinesFilter = '';
var 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);
}
}
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 {
addOutline(outlineWidth, 0);
}
}
})((x: number, y: number): void => {
outlineFilter +=
` <feMorphology in="SourceAlpha" operator="dilate" radius="${ x.toFixed(3) } ${ y.toFixed(3) }" result="outline${ outlineNumber }" />
`;
mergeOutlinesFilter +=
` <feMergeNode in="outline${ outlineNumber }" />
`;
outlineNumber++;
});
outlineFilter +=
` <feMerge result="outline">
${ mergeOutlinesFilter }
</feMerge>
<feFlood flood-color="${ outlineColor.toString() }" />
<feComposite operator="in" in2="outline" />
`;
}
if (this._gaussianBlur > 0) {
blurFilter +=
` <feGaussianBlur stdDeviation="${ this._gaussianBlur }" />
`;
}
for (var i = 0; i < this._blur; i++) {
blurFilter +=
` <feConvolveMatrix kernelMatrix="1 2 1 2 4 2 1 2 1" edgeMode="none" />
`;
}
}
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 filterWrapperSpan = document.createElement("span");
filterWrapperSpan.appendChild(span);
if (outlineFilter !== '' || blurFilter !== '') {
var filterString =
`<filter xmlns="http://www.w3.org/2000/svg" id="${ filterId }" x="-50%" width="200%" y="-50%" height="200%">
${ outlineFilter }
${ blurFilter }
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
`;
var filterElement = domParser.parseFromString(filterString, "image/svg+xml").childNodes[0];
this._svgDefsElement.appendChild(filterElement);
filterWrapperSpan.style.webkitFilter = `url("#${ filterId }")`;
filterWrapperSpan.style.filter = `url("#${ filterId }")`;
}
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;
}
}
if (this._rotationX !== 0 || this._rotationY !== 0) {
// Perspective needs to be set on a "transformable element"
filterWrapperSpan.style.display = "inline-block";
}
animationStyleElement.appendChild(document.createTextNode(animationCollection.cssText));
span.style.webkitAnimation = animationCollection.animationStyle;
span.style.animation = animationCollection.animationStyle;
return filterWrapperSpan;
}
/**
* @return {!HTMLBRElement}
*/
makeNewLine(): HTMLBRElement {
var result = document.createElement("br");
result.style.lineHeight = `${ (this._scaleY * this._fontSize).toFixed(3) }px`;
return result;
}
/**
* Sets the italic property. null defaults it to the default style's value.
*
* @type {?boolean}
*/
set italic(value: boolean) {
this._italic = SpanStyles._valueOrDefault(value, this._defaultStyle.italic);
}
/**
* Sets the bold property. null defaults it to the default style's value.
*
* @type {(?number|?boolean)}
*/
set bold(value: Object) {
this._bold = SpanStyles._valueOrDefault(value, this._defaultStyle.bold);
}
/**
* Sets the underline property. null defaults it to the default style's value.
*
* @type {?boolean}
*/
set underline(value: boolean) {
this._underline = SpanStyles._valueOrDefault(value, this._defaultStyle.underline);
}
/**
* Sets the strike-through property. null defaults it to the default style's value.
*
* @type {?boolean}
*/
set strikeThrough(value: boolean) {
this._strikeThrough = SpanStyles._valueOrDefault(value, this._defaultStyle.strikeThrough);
}
/**
* Sets the outline width property. null defaults it to the style's original outline width value.
*
* @type {?number}
*/
set outlineWidth(value: number) {
this._outlineWidth = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
}
/**
* Sets the outline height property. null defaults it to the style's original outline height value.
*
* @type {?number}
*/
set outlineHeight(value: number) {
this._outlineHeight = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineThickness);
}
/**
* Sets the outline width property. null defaults it to the style's original shadow depth X value.
*
* @type {?number}
*/
set shadowDepthX(value: number) {
this._shadowDepthX = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
}
/**
* Sets the shadow height property. null defaults it to the style's original shadow depth Y value.
*
* @type {?number}
*/
set shadowDepthY(value: number) {
this._shadowDepthY = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowDepth);
}
/**
* Sets the blur property. null defaults it to 0.
*
* @type {?number}
*/
set blur(value: number) {
this._blur = SpanStyles._valueOrDefault(value, 0);
}
/**
* Sets the Gaussian blur property. null defaults it to 0.
*
* @type {?number}
*/
set gaussianBlur(value: number) {
this._gaussianBlur = SpanStyles._valueOrDefault(value, 0);
}
/**
* Sets the font name property. null defaults it to the default style's value.
*
* @type {?string}
*/
set fontName(value: string) {
this._fontName = SpanStyles._valueOrDefault(value, this._defaultStyle.fontName);
}
/**
* Sets the font size property. null defaults it to the default style's value.
*
* @type {?number}
*/
set fontSize(value: number) {
this._fontSize = SpanStyles._valueOrDefault(value, this._defaultStyle.fontSize);
}
/**
* Sets the horizontal font scaling property. null defaults it to the default style's value.
*
* @type {?number}
*/
set fontScaleX(value: number) {
this._fontScaleX = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleX);
}
/**
* Sets the vertical font scaling property. null defaults it to the default style's value.
*
* @type {?number}
*/
set fontScaleY(value: number) {
this._fontScaleY = SpanStyles._valueOrDefault(value, this._defaultStyle.fontScaleY);
}
/**
* Sets the letter spacing property. null defaults it to the default style's value.
*
* @type {?number}
*/
set letterSpacing(value: number) {
this._letterSpacing = SpanStyles._valueOrDefault(value, this._defaultStyle.letterSpacing);
}
/**
* Sets the X-axis rotation property.
*
* @type {?number}
*/
set rotationX(value: number) {
this._rotationX = value;
}
/**
* Sets the Y-axis rotation property.
*
* @type {?number}
*/
set rotationY(value: number) {
this._rotationY = value;
}
/**
* Sets the Z-axis rotation property.
*
* @type {?number}
*/
set rotationZ(value: number) {
this._rotationZ = SpanStyles._valueOrDefault(value, this._defaultStyle.rotationZ);
}
/**
* Sets the X-axis skew property.
*
* @type {?number}
*/
set skewX(value: number) {
this._skewX = value;
}
/**
* Sets the Y-axis skew property.
*
* @type {?number}
*/
set skewY(value: number) {
this._skewY = value;
}
/**
* Gets the primary color property.
*
* @type {!libjass.Color}
*/
get primaryColor(): Color {
return this._primaryColor;
}
/**
* Sets the primary color property. null defaults it to the default style's value.
*
* @type {libjass.Color}
*/
set primaryColor(value: Color) {
this._primaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.primaryColor);
}
/**
* Gets the secondary color property.
*
* @type {!libjass.Color}
*/
get secondaryColor(): Color {
return this._secondaryColor;
}
/**
* Sets the secondary color property. null defaults it to the default style's value.
*
* @type {libjass.Color}
*/
set secondaryColor(value: Color) {
this._secondaryColor = SpanStyles._valueOrDefault(value, this._defaultStyle.secondaryColor);
}
/**
* Sets the outline color property. null defaults it to the default style's value.
*
* @type {libjass.Color}
*/
set outlineColor(value: Color) {
this._outlineColor = SpanStyles._valueOrDefault(value, this._defaultStyle.outlineColor);
}
/**
* Sets the shadow color property. null defaults it to the default style's value.
*
* @type {libjass.Color}
*/
set shadowColor(value: Color) {
this._shadowColor = SpanStyles._valueOrDefault(value, this._defaultStyle.shadowColor);
}
/**
* Gets the primary alpha property.
*
* @type {?number}
*/
get primaryAlpha(): number {
return this._primaryAlpha;
}
/**
* Sets the primary alpha property.
*
* @type {?number}
*/
set primaryAlpha(value: number) {
this._primaryAlpha = value;
}
/**
* Gets the secondary alpha property.
*
* @type {?number}
*/
get secondaryAlpha(): number {
return this._secondaryAlpha;
}
/**
* Sets the secondary alpha property.
*
* @type {?number}
*/
set secondaryAlpha(value: number) {
this._secondaryAlpha = value;
}
/**
* Sets the outline alpha property.
*
* @type {?number}
*/
set outlineAlpha(value: number) {
this._outlineAlpha = value;
}
/**
* Sets the shadow alpha property.
*
* @type {?number}
*/
set shadowAlpha(value: number) {
this._shadowAlpha = value;
}
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) {
SpanStyles._fontSizeCache.set(fontFamily, existingFontSizeMap = new Map<number, number>());
}
var existingFontSize = existingFontSizeMap.get(lineHeight);
if (existingFontSize === undefined) {
fontSizeElement.style.fontFamily = fontFamily;
fontSizeElement.style.fontSize = `${ lineHeight }px`;
existingFontSizeMap.set(lineHeight, existingFontSize = lineHeight * lineHeight / fontSizeElement.offsetHeight);
}
return existingFontSize;
}
}
-51
View File
@@ -1,51 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 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;
/**
* Sets the debug mode.
*
* @param {boolean} value
*/
export function setDebugMode(value: boolean): void {
debugMode = value;
}
/**
* Sets the verbose debug mode.
*
* @param {boolean} value
*/
export function setVerboseMode(value: boolean): void {
verboseMode = value;
}
-8
View File
@@ -1,8 +0,0 @@
{
"compilerOptions": {
"module": "CommonJS",
"noImplicitAny": true,
"sourceMap": true,
"target": "ES5"
}
}
-233
View File
@@ -1,233 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Dialogue } from "./dialogue";
import { Style } from "./style";
import { ScriptProperties } from "./script-properties";
import { Format } from "./misc";
import { verboseMode } from "../settings";
import * as parser from "../parser/index";
import { parseLineIntoTypedTemplate } from "../parser/misc";
import { ReadableStream, TextDecoder, TextDecoderConstructor } from "../parser/streams";
import { Map } from "../utility/map";
import { Promise } from "../utility/promise";
declare var global: {
fetch?(url: string): Promise<{ body: ReadableStream }>;
ReadableStream?: { prototype: ReadableStream; };
TextDecoder?: TextDecoderConstructor;
};
/**
* This class represents an ASS script. It contains the {@link libjass.ScriptProperties}, an array of {@link libjass.Style}s, and an array of {@link libjass.Dialogue}s.
*/
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;
/**
* The properties of this script.
*
* @type {!libjass.ScriptProperties}
*/
get properties(): ScriptProperties {
return this._properties;
}
/**
* The styles in this script.
*
* @type {!Map.<string, !libjass.Style>}
*/
get styles(): Map<string, Style> {
return this._styles;
}
/**
* The dialogues in this script.
*
* @type {!Array.<!libjass.Dialogue>}
*/
get dialogues(): Dialogue[] {
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) {
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
}
}
/**
* 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 = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
if (styleLine === null || styleLine.type !== "Style") {
return;
}
var styleTemplate = styleLine.template;
if (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 = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
return;
}
var dialogueTemplate = dialogueLine.template;
if (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>}
*/
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
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;
case Format.SRT:
return new parser.SrtStreamParser(stream).ass;
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 (
typeof global.fetch === "function" &&
typeof global.ReadableStream === "function" && typeof global.ReadableStream.prototype.getReader === "function" &&
typeof global.TextDecoder === "function"
) {
return global.fetch(url).then(response => ASS.fromReadableStream(response.body, "utf-8", type));
}
var xhr = new XMLHttpRequest();
var result = ASS.fromStream(new parser.XhrStream(xhr), type);
xhr.open("GET", url, true);
xhr.send();
return result;
}
/**
* Creates an ASS object from the given ReadableStream.
*
* @param {!ReadableStream} stream
* @param {string="utf-8"} encoding
* @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 fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format = Format.ASS): Promise<ASS> {
return ASS.fromStream(new parser.BrowserReadableStream(stream, encoding), type);
}
}
-219
View File
@@ -1,219 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ASS } from "./ass";
import { Style } from "./style";
import { valueOrDefault } from "./misc";
import { parse } from "../parser/parse";
import * as parts from "../parts/index";
import { debugMode } from "../settings";
import { Map } from "../utility/map";
/**
* This class represents a dialogue in a {@link libjass.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
* @param {string} template["Start"] The start time
* @param {string} template["End"] The end time
* @param {string} template["Layer"] The layer number
* @param {string} template["Text"] The text of this dialogue
* @param {ASS} ass The ASS object to which this dialogue belongs
*/
export class Dialogue {
private static _lastDialogueId = -1;
private _id: number;
private _style: Style;
private _start: number;
private _end: number;
private _layer: number;
private _alignment: number;
private _rawPartsString: string;
private _parts: parts.Part[] = null;
private _sub: HTMLDivElement = null;
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._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._rawPartsString = template.get("Text");
if (this._rawPartsString === undefined || this._rawPartsString === null || this._rawPartsString.constructor !== String) {
throw new Error("Dialogue doesn't have any text.");
}
}
/**
* The unique ID of this dialogue. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* The start time of this dialogue.
*
* @type {number}
*/
get start(): number {
return this._start;
}
/**
* The end time of this dialogue.
*
* @type {number}
*/
get end(): number {
return this._end;
}
/**
* The default style of this dialogue.
*
* @type {!libjass.Style}
*/
get style(): Style {
return this._style;
}
/**
* The alignment number of this dialogue.
*
* @type {number}
*/
get alignment(): number {
if (this._parts === null) {
this._parsePartsString();
}
return this._alignment;
}
/**
* The layer number of this dialogue.
*
* @type {number}
*/
get layer(): number {
return this._layer;
}
/**
* The {@link libjass.parts} of this dialogue.
*
* @type {!Array.<!libjass.parts.Part>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
this._parsePartsString();
}
return this._parts;
}
/**
* @return {string} A simple representation of this dialogue's properties and parts.
*/
toString(): string {
return `#${ this._id } [${ this._start.toFixed(3) }-${ this._end.toFixed(3) }] ${ (this._parts !== null) ? this._parts.join(", ") : this._rawPartsString }`;
}
/**
* Parses this dialogue's parts from the raw parts string.
*/
private _parsePartsString(): void {
this._parts = <parts.Part[]>parse(this._rawPartsString, "dialogueParts");
this._alignment = this._style.alignment;
this._parts.forEach((part, index) => {
if (part instanceof parts.Alignment) {
this._alignment = part.value;
}
else if (part instanceof parts.Move) {
if (part.t1 === null || part.t2 === null) {
this._parts[index] =
new parts.Move(
part.x1, part.y1, part.x2, part.y2,
0, this._end - this._start
);
}
}
else if (part instanceof parts.Transform) {
if (part.start === null || part.end === null || part.accel === null) {
this._parts[index] =
new parts.Transform(
(part.start === null) ? 0 : part.start,
(part.end === null) ? (this._end - this._start) : part.end,
(part.accel === null) ? 1 : part.accel,
part.tags
);
}
}
});
if (debugMode) {
var possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && part.value.indexOf("\\") !== -1);
if (possiblyIncorrectParses.length > 0) {
console.warn(
`Possible incorrect parse:
${ this._rawPartsString }
was parsed as
${ this.toString() }
The possibly incorrect parses are:
${ possiblyIncorrectParses.join("\n") }`
);
}
}
}
/**
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
*
* @param {string} str
* @return {number}
*/
private static _toTime(str: string): number {
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
}
-105
View File
@@ -1,105 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
/**
* 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,
EndOfLineWrapping = 1,
NoLineWrapping = 2,
}
/**
* The border style defined in the {@link libjass.Style} properties.
*/
export enum BorderStyle {
Outline = 1,
OpaqueBox = 3,
}
/**
* 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}
*/
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: (value: T) => boolean, defaultValue: string): T {
var value = template.get(key);
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 }`);
}
}
-103
View File
@@ -1,103 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WrappingStyle } from "./misc";
/**
* This class represents the properties of a {@link libjass.ASS} script.
*/
export class ScriptProperties {
private _resolutionX: number;
private _resolutionY: number;
private _wrappingStyle: WrappingStyle;
private _scaleBorderAndShadow: boolean;
/**
* The horizontal script resolution.
*
* @type {number}
*/
get resolutionX(): number {
return this._resolutionX;
}
/**
* The horizontal script resolution.
*
* @type {number}
*/
set resolutionX(value: number) {
this._resolutionX = value;
}
/**
* The vertical script resolution.
*
* @type {number}
*/
get resolutionY(): number {
return this._resolutionY;
}
/**
* The vertical script resolution.
*
* @type {number}
*/
set resolutionY(value: number) {
this._resolutionY = value;
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
*
* @type {number}
*/
get wrappingStyle(): WrappingStyle {
return this._wrappingStyle;
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
*
* @type {number}
*/
set wrappingStyle(value: WrappingStyle) {
this._wrappingStyle = value;
}
/**
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
*
* @type {boolean}
*/
get scaleBorderAndShadow(): boolean {
return this._scaleBorderAndShadow;
}
/**
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
*
* @type {boolean}
*/
set scaleBorderAndShadow(value: boolean) {
this._scaleBorderAndShadow = value;
}
}
-322
View File
@@ -1,322 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { valueOrDefault, BorderStyle } from "./misc";
import { parse } from "../parser/parse";
import { Color } from "../parts/index";
import { Map } from "../utility/map";
/**
* 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.
*
* @param {!Map.<string, string>} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Name"] The name of the style
* @param {string} template["Italic"] -1 if the style is italicized
* @param {string} template["Bold"] -1 if the style is bold
* @param {string} template["Underline"] -1 if the style is underlined
* @param {string} template["StrikeOut"] -1 if the style is struck-through
* @param {string} template["Fontname"] The name of the font
* @param {string} template["Fontsize"] The size of the font
* @param {string} template["ScaleX"] The horizontal scaling of the font
* @param {string} template["ScaleY"] The vertical scaling of the font
* @param {string} template["Spacing"] The letter spacing of the font
* @param {string} template["PrimaryColour"] The primary color
* @param {string} template["OutlineColour"] The outline color
* @param {string} template["BackColour"] The shadow color
* @param {string} template["Outline"] The outline thickness
* @param {string} template["Shadow"] The shadow depth
* @param {string} template["Alignment"] The alignment number
* @param {string} template["MarginL"] The left margin
* @param {string} template["MarginR"] The right margin
* @param {string} template["MarginV"] The vertical margin
*/
export class Style {
private _name: string;
private _italic: boolean;
private _bold: boolean;
private _underline: boolean;
private _strikeThrough: boolean;
private _fontName: string;
private _fontSize: number;
private _fontScaleX: number;
private _fontScaleY: number;
private _letterSpacing: number;
private _rotationZ: number;
private _primaryColor: Color;
private _secondaryColor: Color;
private _outlineColor: Color;
private _shadowColor: Color;
private _outlineThickness: number;
private _borderStyle: BorderStyle;
private _shadowDepth: number;
private _alignment: number;
private _marginLeft: number;
private _marginRight: number;
private _marginVertical: number;
constructor(template: Map<string, string>) {
this._name = template.get("Name");
if (this._name === undefined || this._name === null || this._name.constructor !== String) {
throw new Error("Style doesn't have a name.");
}
this._italic = template.get("Italic") === "-1";
this._bold = template.get("Bold") === "-1";
this._underline = template.get("Underline") === "-1";
this._strikeThrough = template.get("StrikeOut") === "-1";
this._fontName = template.get("Fontname");
this._fontSize = valueOrDefault(template, "Fontsize", parseFloat, value => !isNaN(value), "50");
this._fontScaleX = valueOrDefault(template, "ScaleX", parseFloat, value => !isNaN(value), "100") / 100;
this._fontScaleY = valueOrDefault(template, "ScaleY", parseFloat, value => !isNaN(value), "100") / 100;
this._letterSpacing = valueOrDefault(template, "Spacing", parseFloat, value => !isNaN(value), "0");
this._rotationZ = valueOrDefault(template, "Angle", parseFloat, value => !isNaN(value), "0");
this._primaryColor = valueOrDefault(template, "PrimaryColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H0000FFFF");
this._secondaryColor = valueOrDefault(template, "SecondaryColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
this._outlineColor = valueOrDefault(template, "OutlineColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
this._shadowColor = valueOrDefault(template, "BackColour", str => <Color>parse(str, "colorWithAlpha"), null, "&H00000000");
this._outlineThickness = valueOrDefault(template, "Outline", parseFloat, value => !isNaN(value), "1");
this._borderStyle = valueOrDefault(template, "BorderStyle", parseInt, value => !isNaN(value), "1");
this._shadowDepth = valueOrDefault(template, "Shadow", parseFloat, value => !isNaN(value), "1");
this._alignment = valueOrDefault(template, "Alignment", parseInt, value => !isNaN(value), "2");
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");
}
/**
* The name of this style.
*
* @type {string}
*/
get name(): string {
return this._name;
}
/**
* Whether this style is italicized or not.
*
* @type {string}
*/
get italic(): boolean {
return this._italic;
}
/**
* Whether this style is bold or not.
*
* @type {boolean}
*/
get bold(): boolean {
return this._bold;
}
/**
* Whether this style is underlined or not.
*
* @type {boolean}
*/
get underline(): boolean {
return this._underline;
}
/**
* Whether this style is struck-through or not.
*
* @type {boolean}
*/
get strikeThrough(): boolean {
return this._strikeThrough;
}
/**
* The name of this style's font.
*
* @type {string}
*/
get fontName(): string {
return this._fontName;
}
/**
* The size of this style's font.
*
* @type {number}
*/
get fontSize(): number {
return this._fontSize;
}
/**
* The horizontal scaling of this style's font.
*
* @type {number}
*/
get fontScaleX(): number {
return this._fontScaleX;
}
/**
* The vertical scaling of this style's font.
*
* @type {number}
*/
get fontScaleY(): number {
return this._fontScaleY;
}
/**
* The letter spacing scaling of this style's font.
*
* @type {number}
*/
get letterSpacing(): number {
return this._letterSpacing;
}
/**
* The default Z-rotation of this style.
*
* @type {number}
*/
get rotationZ(): number {
return this._rotationZ;
}
/**
* The color of this style's font.
*
* @type {!libjass.parts.Color}
*/
get primaryColor(): Color {
return this._primaryColor;
}
/**
* The alternate color of this style's font, used in karaoke.
*
* @type {!libjass.parts.Color}
*/
get secondaryColor(): Color {
return this._secondaryColor;
}
/**
* The color of this style's outline.
*
* @type {!libjass.parts.Color}
*/
get outlineColor(): Color {
return this._outlineColor;
}
/**
* The color of this style's shadow.
*
* @type {!libjass.parts.Color}
*/
get shadowColor(): Color {
return this._shadowColor;
}
/**
* The thickness of this style's outline.
*
* @type {number}
*/
get outlineThickness(): number {
return this._outlineThickness;
}
/**
* The border style of this style.
*
* @type {number}
*/
get borderStyle(): BorderStyle {
return this._borderStyle;
}
/**
* The depth of this style's shadow.
*
* @type {number}
*/
get shadowDepth(): number {
return this._shadowDepth;
}
/**
* The alignment of dialogues of this style.
*
* @type {number}
*/
get alignment(): number {
return this._alignment;
}
/**
* The left margin of dialogues of this style.
*
* @type {number}
*/
get marginLeft(): number {
return this._marginLeft;
}
/**
* The right margin of dialogues of this style.
*
* @type {number}
*/
get marginRight(): number {
return this._marginRight;
}
/**
* The vertical margin of dialogues of this style.
*
* @type {number}
*/
get marginVertical(): number {
return this._marginVertical;
}
}
-250
View File
@@ -1,250 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
*
* 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.
*/
class SimpleMap<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
constructor(iterable?: [K, V][]) {
this.clear();
if (iterable === undefined) {
return;
}
if (!Array.isArray(iterable)) {
throw new Error("Non-array iterables are not supported by the SimpleMap constructor.");
}
for (let element of iterable) {
this.set(element[0], element[1]);
}
}
/**
* @param {K} key
* @return {V}
*/
get(key: K): V {
var property = this._keyToProperty(key);
if (property === null) {
return undefined;
}
return this._values[property];
}
/**
* @param {K} key
* @return {boolean}
*/
has(key: K): boolean {
var property = this._keyToProperty(key);
if (property === null) {
return false;
}
return property in this._keys;
}
/**
* @param {K} key
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value: V): Map<K, V> {
var property = this._keyToProperty(key);
if (property === null) {
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
}
if (!(property in this._keys)) {
this._size++;
}
this._keys[property] = key;
this._values[property] = value;
return this;
}
/**
* @param {K} key
* @return {boolean} true if the key was present before being deleted, false otherwise
*/
delete(key: K): boolean {
var property = this._keyToProperty(key);
if (property === null) {
return false;
}
var result = property in this._keys;
if (result) {
delete this._keys[property];
delete this._values[property];
this._size--;
}
return result;
}
/**
*/
clear(): void {
this._keys = Object.create(null);
this._values = Object.create(null);
this._size = 0;
}
/**
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
for (let property of Object.keys(this._keys)) {
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
}
}
/**
* @type {number}
*/
get size(): number {
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 }`;
}
if (typeof key === "string") {
return `'${ key }`;
}
if ((<any>key).id !== undefined) {
return `!${ (<any>key).id }`;
}
return null;
}
}
declare var global: {
Map?: typeof SimpleMap;
};
export interface Map<K, V> {
/**
* @param {K} key
* @return {V}
*/
get(key: K): V;
/**
* @param {K} key
* @return {boolean}
*/
has(key: K): boolean;
/**
* @param {K} key
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value?: V): Map<K, V>;
/**
* @param {K} key
* @return {boolean} true if the key was present before being deleted, false otherwise
*/
delete(key: K): boolean;
/**
*/
clear(): void;
/**
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
/**
* @type {number}
*/
size: number;
}
/**
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
*
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
*/
export var Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>;
prototype: Map<any, any>;
} = global.Map;
if (Map === undefined || typeof Map.prototype.forEach !== "function" || (() => {
try {
return new Map([[1, "foo"], [2, "bar"]]).size !== 2;
}
catch (ex) {
return true;
}
})()) {
Map = SimpleMap;
}
/**
* Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
*
* @param {?function(new:Map, !Array.<!Array.<*>>=)} value
*/
export function setImplementation(value: typeof Map): void {
if (value !== null) {
Map = value;
}
else {
Map = SimpleMap;
}
}
-33
View File
@@ -1,33 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 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 {
for (let mixin of mixins) {
for (let name of Object.getOwnPropertyNames(mixin.prototype)) {
clazz.prototype[name] = mixin.prototype[name];
}
}
}
-450
View File
@@ -1,450 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Promise implementation for browsers that don't support it.
*
* @param {function(function(T), function(*))} resolver
*/
class SimplePromise<T> {
private _state: SimplePromiseState = SimplePromiseState.PENDING;
private _thens: { propagateFulfilling: (value: T) => void; propagateRejection: (reason: any) => void }[] = [];
private _propagateIsPending: boolean = false;
private _alreadyFulfilledValue: T = null;
private _alreadyRejectedReason: any = null;
constructor(resolver: (resolve: (value: T | Promise<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 | Promise<U>, rejectedHandler?: (reason: any) => U | Promise<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 Promise<U>((resolve, reject) => {
this._thens.push({
propagateFulfilling: value => {
try {
resolve(fulfilledHandler(value));
}
catch (ex) {
reject(ex);
}
}, propagateRejection: reason => {
try {
resolve(rejectedHandler(reason));
}
catch (ex) {
reject(ex);
}
}
});
});
this._propagateResolution();
return result;
}
/**
* @param {function(*):(T|Promise.<T>)} rejectedHandler
* @return {!Promise.<T>}
*/
catch(rejectedHandler?: (reason: any) => T | Promise<T>): Promise<T> {
return this.then(null, rejectedHandler);
}
/**
* @return {boolean}
*/
isFulfilled(): boolean {
return this._state === SimplePromiseState.FULFILLED;
}
/**
* @return {boolean}
*/
isRejected(): boolean {
return this._state === SimplePromiseState.REJECTED;
}
/**
* @return {boolean}
*/
isPending(): boolean {
return this._state === SimplePromiseState.PENDING;
}
/**
* @return {T}
*/
value(): T {
if (this._state !== SimplePromiseState.FULFILLED) {
throw new Error("This promise is not in FULFILLED state.");
}
return this._alreadyFulfilledValue;
}
/**
* @return {*}
*/
reason(): any {
if (this._state !== SimplePromiseState.REJECTED) {
throw new Error("This promise is not in FULFILLED state.");
}
return this._alreadyRejectedReason;
}
/**
* @param {T|!Promise.<T>} value
* @return {!Promise.<T>}
*/
static resolve<T>(value: T | Promise<T>): Promise<T> {
if (value instanceof SimplePromise) {
return value;
}
return new Promise<T>(resolve => resolve(value));
}
/**
* @param {!Array.<T|!Promise.<T>>} values
* @return {!Promise.<!Array.<T>>}
*/
static all<T>(values: (T | Promise<T>)[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
var result: T[] = [];
var numUnresolved = values.length;
if (numUnresolved === 0) {
resolve(result);
return;
}
values.forEach((value, index) => Promise.resolve(value).then(value => {
result[index] = value;
numUnresolved--;
if (numUnresolved === 0) {
resolve(result);
}
}), reject);
});
}
/**
* @param {T|!Promise.<T>} value
*/
private _resolve(value: T | Promise<T>): void {
var alreadyCalled = false;
try {
if (value === this) {
throw new TypeError("2.3.1");
}
var thenMethod = SimplePromise._getThenMethod<T>(value);
if (thenMethod === null) {
this._fulfill(<T>value);
return;
}
thenMethod.call(
<Promise<T>>value,
(value: T) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._resolve(value);
},
(reason: any) => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
this._reject(reason);
});
}
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;
this._propagateResolution();
}
/**
* @param {*} reason
*/
private _reject(reason: any): void {
if (this._state !== SimplePromiseState.PENDING) {
return;
}
this._state = SimplePromiseState.REJECTED;
this._alreadyRejectedReason = reason;
this._propagateResolution();
}
/**
* @param {!*} obj
* @return {?function(function(T):(T|!Promise.<T>), function(*):(T|!Promise.<T>)):!Promise.<T>}
*/
private static _getThenMethod<T>(obj: any): (fulfilledHandler: (value: T) => T | Promise<T>, rejectedHandler: (reason: any) => T | Promise<T>) => Promise<T> {
if (typeof obj !== "object" && typeof obj !== "function") {
return null;
}
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 {
if (this._state === SimplePromiseState.PENDING) {
return;
}
if (this._propagateIsPending) {
return;
}
this._propagateIsPending = true;
SimplePromise._nextTick(() => {
this._propagateIsPending = false;
if (this._state === SimplePromiseState.FULFILLED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateFulfilling(this._alreadyFulfilledValue);
}
}
else if (this._state === SimplePromiseState.REJECTED) {
while (this._thens.length > 0) {
var nextThen = this._thens.shift();
nextThen.propagateRejection(this._alreadyRejectedReason);
}
}
});
}
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
private static _nextTick: (callback: () => void) => void = (function () {
var MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
if (global.process !== undefined && typeof global.process.nextTick === "function") {
return (callback: () => void) => {
global.process.nextTick(callback);
};
}
else if (MutationObserver !== undefined) {
var pending: (() => void)[] = [];
var currentlyPending = false;
var div = document.createElement("div");
var observer = new MutationObserver(() => {
var processing = pending;
pending = [];
for (let callback of processing) {
callback();
}
currentlyPending = false;
if (pending.length > 0) {
div.classList.toggle("foo");
currentlyPending = true;
}
});
observer.observe(div, { attributes: true });
return (callback: () => void) => {
pending.push(callback);
if (!currentlyPending) {
div.classList.toggle("foo");
currentlyPending = true;
}
};
}
else {
return (callback: () => void) => setTimeout(callback, 0);
}
})();
}
/**
* The state of the {@link ./utility/promise.SimplePromise}
*/
enum SimplePromiseState {
PENDING = 0,
FULFILLED = 1,
REJECTED = 2,
}
declare var global: {
Promise?: typeof SimplePromise;
MutationObserver?: typeof MutationObserver;
WebkitMutationObserver?: typeof MutationObserver;
process?: {
nextTick(callback: () => void): void;
}
};
export interface Promise<T> {
/**
* @param {?function(T):(U|Promise.<U>)} fulfilledHandler
* @param {?function(*):(U|Promise.<U>)} rejectedHandler
* @return {!Promise.<U>}
*/
then<U>(fulfilledHandler?: (value: T) => U | Promise<U>, rejectedHandler?: (reason: any) => U | Promise<U>): Promise<U>;
/**
* @param {function(*):(T|Promise.<T>)} rejectedHandler
* @return {!Promise.<T>}
*/
catch(rejectedHandler?: (reason: any) => T | Promise<T>): Promise<T>
}
/**
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
*
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
*
* @type {function(new:Promise)}
*/
export var Promise: {
new <T>(init: (resolve: (value?: T | Promise<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | Promise<T>): Promise<T>;
all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
} = global.Promise;
if (Promise === undefined) {
Promise = SimplePromise;
}
/**
* Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
*
* @param {?function(new:Promise)} value
*/
export function setImplementation(value: typeof Promise): void {
if (value !== null) {
Promise = value;
}
else {
Promise = 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);
}
}
-194
View File
@@ -1,194 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Set implementation for browsers that don't support it. Only supports Number and String elements.
*
* Elements are stored as properties of an object, with names derived from their type.
*
* @param {!Array.<T>=} iterable Only an array of values is supported.
*/
class SimpleSet<T> {
private _elements: { [key: string]: T };
private _size: number;
constructor(iterable?: T[]) {
this.clear();
if (iterable === undefined) {
return;
}
if (!Array.isArray(iterable)) {
throw new Error("Non-array iterables are not supported by the SimpleSet constructor.");
}
for (let value of iterable) {
this.add(value);
}
}
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T> {
var property = this._toProperty(value);
if (property === null) {
throw new Error("This Set implementation only supports Number and String values.");
}
if (!(property in this._elements)) {
this._size++;
}
this._elements[property] = value;
return this;
}
/**
*/
clear(): void {
this._elements = Object.create(null);
this._size = 0;
}
/**
* @param {T} value
* @return {boolean}
*/
has(value: T): boolean {
var property = this._toProperty(value);
if (property === null) {
return false;
}
return property in this._elements;
}
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
for (let property of Object.keys(this._elements)) {
var element = this._elements[property];
callbackfn.call(thisArg, element, element, this);
}
}
/**
* @type {number}
*/
get size(): number {
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 }`;
}
if (typeof value === "string") {
return `'${ value }`;
}
return null;
}
}
declare var global: {
Set?: typeof SimpleSet;
};
export interface Set<T> {
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T>;
/**
*/
clear(): void;
/**
* @param {T} value
* @return {boolean}
*/
has(value: T): boolean;
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
/**
* @type {number}
*/
size: number;
}
/**
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
*
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
*
* @type {function(new:Set, !Array.<T>=)}
*/
export var Set: {
new <T>(iterable?: T[]): Set<T>;
prototype: Set<any>;
} = global.Set;
if (Set === undefined || typeof Set.prototype.forEach !== "function" || (() => {
try {
return new Set([1, 2]).size !== 2;
}
catch (ex) {
return true;
}
})()) {
Set = SimpleSet;
}
/**
* Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
*
* @param {?function(new:Set, !Array.<T>=)} value
*/
export function setImplementation(value: typeof Set): void {
if (value !== null) {
Set = value;
}
else {
Set = SimpleSet;
}
}
-219
View File
@@ -1,219 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { WorkerCommands } from "./commands";
import { getWorkerCommandHandler, serialize, deserialize, registerWorkerCommand } from "./misc";
/**
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling {@link libjass.webworker.createWorker}
*/
export interface WorkerChannel {
/**
* Sends a request to the other side to execute the given command with the given parameters.
*
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>} A promise that will get resolved when the other side computes the result
*/
request(command: WorkerCommands, parameters: any): Promise<any>;
}
/**
* The signature of a handler registered to handle a particular command in {@link libjass.webworker.WorkerCommands}
*/
export interface WorkerCommandHandler {
(parameters: any): Promise<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;
}
/**
* Internal implementation of libjass.webworker.WorkerChannel
*
* @param {!*} comm The object used to talk to the other side of the channel. When created by the main thread, this is the Worker object.
* When created by the web worker, this is its global object.
*/
export class WorkerChannelImpl implements WorkerChannel {
private static _lastRequestId: number = -1;
private _pendingRequests = new Map<number, DeferredPromise<any>>();
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);
var requestMessage: WorkerRequestMessage = { requestId, command, parameters };
this._comm.postMessage(serialize(requestMessage));
return deferred.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 {
var { requestId, error, result } = message;
if (error instanceof Error) {
error = { message: error.message, stack: error.stack };
}
this._comm.postMessage(serialize({ command: WorkerCommands.Response, requestId, error, result }));
}
/**
* @param {string} rawMessage
*/
private _onMessage(rawMessage: string): void {
var message = <{ command: WorkerCommands }>deserialize(rawMessage);
if (message.command === WorkerCommands.Response) {
var responseMessage = <WorkerResponseMessage><any>message;
var deferred = this._pendingRequests.get(responseMessage.requestId);
if (deferred !== undefined) {
this._pendingRequests.delete(responseMessage.requestId);
if (responseMessage.error === null) {
deferred.resolve(responseMessage.result);
}
else {
deferred.reject(responseMessage.error);
}
}
}
else {
var requestMessage = <WorkerRequestMessage>message;
var requestId = requestMessage.requestId;
var commandCallback = getWorkerCommandHandler(requestMessage.command);
if (commandCallback === undefined) {
this._respond({ requestId, error: new Error(`No handler registered for command ${ requestMessage.command }`), result: null });
return;
}
commandCallback(requestMessage.parameters).then<WorkerResponseMessage>(
result => ({ requestId, error: null, result }),
error => ({ requestId, error, result: null })
).then(responseMessage => this._respond(responseMessage));
}
}
}
registerWorkerCommand(WorkerCommands.Ping, parameters => new Promise(resolve => resolve()));
-28
View File
@@ -1,28 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The commands that can be sent to or from a web worker.
*/
export enum WorkerCommands {
Response = 0,
Parse = 1,
Ping = 2,
}
-60
View File
@@ -1,60 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { WorkerChannel, WorkerChannelImpl } from "./channel";
export { WorkerChannel } from "./channel";
export { WorkerCommands } from "./commands";
declare var exports: any;
/**
* Indicates whether web workers are supposed in this environment or not.
*
* @type {boolean}
*/
export var supported = typeof Worker !== "undefined";
/**
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
*
* @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));
}
var _scriptNode: HTMLScriptElement = null;
if (typeof document !== "undefined" && document.currentScript !== undefined) {
_scriptNode = document.currentScript;
}
declare var global: any;
if (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope) {
// This is a web worker. Set up a channel to talk back to the main thread.
new WorkerChannelImpl(global);
}
-92
View File
@@ -1,92 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
import { WorkerCommands } from "./commands";
import { WorkerCommandHandler } from "./channel";
var workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
var classPrototypes = new Map<number, any>();
/**
* 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 {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 {
workerCommands.set(command, handler);
}
/**
* Gets the handler for the given worker command.
*
* @param {number} command
* @return {?function(*, function(*, *))}
*/
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler {
return workerCommands.get(command);
}
/**
* Registers a prototype as a deserializable type.
*
* @param {!*} prototype
*/
export function registerClassPrototype(prototype: any): void {
prototype._classTag = classPrototypes.size;
classPrototypes.set(prototype._classTag, prototype);
}
/**
* @param {*} obj
* @return {string}
*/
export function serialize(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
value._classTag = value._classTag;
}
return value;
});
}
/**
* @param {string} str
* @return {*}
*/
export function deserialize(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
var hydratedValue = Object.create(classPrototypes.get(value._classTag));
for (let key of Object.keys(value)) {
if (key !== "_classTag") {
hydratedValue[key] = value[key];
}
}
value = hydratedValue;
}
return value;
});
}
-44
View File
@@ -1,44 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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: {
prototype: WorkerGlobalScope;
new (): WorkerGlobalScope;
};
-84
View File
@@ -1,84 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "intern/chai!assert", "intern/dojo/node!fs", "intern/dojo/node!path", "intern/dojo/node!sax"], function (tdd, assert, fs, path, sax) {
tdd.suite("Documentation tests", function () {
var ids = [];
var hrefs = [];
tdd.before(function () {
var parser = sax.parser(true);
parser.onopentag = function (node) {
if (node.name === "a" && node.attributes.href[0] === "#") {
hrefs.push(node.attributes.href.substr(1));
}
if (node.attributes.id !== undefined) {
ids.push(node.attributes.id);
}
};
parser.onerror = function (error) { throw error; };
parser.write(fs.readFileSync(require.toUrl("../libjass-gh-pages/api.xhtml"), { encoding: "utf8" })).close();
});
tdd.test("api.xhtml", function () {
var brokenLinks = hrefs.filter(function (href) {
return ids.indexOf(href) === -1;
});
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
tdd.test("README.md", function () {
var regex = /\[[^\]]+\]\(http:\/\/arnavion\.github\.io\/libjass\/api\.xhtml#([^)]+)\)/g;
var brokenLinks = [];
var readme = fs.readFileSync(require.toUrl("./README.md"), { encoding: "utf8" });
var match;
while ((match = regex.exec(readme)) !== null) {
if (ids.indexOf(match[1]) === -1) {
brokenLinks.push(match[1]);
}
}
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
tdd.test("@{link} tags in the source", function () {
var regex = /\{@link ([^}]+)\}/g;
var brokenLinks = [];
var libjassJs = fs.readFileSync(require.toUrl("./lib/libjass.js"), { encoding: "utf8" });
var match;
while ((match = regex.exec(libjassJs)) !== null) {
if (ids.indexOf(match[1]) === -1) {
brokenLinks.push(match[1]);
}
}
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

-18
View File
@@ -1,18 +0,0 @@
[Script Info]
; Script generated by Aegisub 3.2.2
; http://www.aegisub.org/
Title: Default Aegisub file
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: None
PlayResX: 256
PlayResY: 144
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,36,&H00FFFFFF,&H000000FF,&H00000000,&H96000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:07.00,Default,,0,0,0,,{\k100}{\k200}a{\k100}bc{\k200}d
-35
View File
@@ -1,35 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("WebDriver", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/kfx/kfx.ass", 256, 144);
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1.5, require.toUrl("./kfx-1.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(2.5, require.toUrl("./kfx-2.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(3.5, require.toUrl("./kfx-3.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(4.5, require.toUrl("./kfx-4.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(5.5, require.toUrl("./kfx-5.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(6.5, require.toUrl("./kfx-6.png")); });
});
});
});
+406
View File
@@ -0,0 +1,406 @@
/**
* 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.
*/
Array.prototype.sum = function () {
return this.reduce(function (previous, current) {
return previous + current;
}, 0);
};
var rootSuites = [];
var suite = null;
var setup = null;
var test = null;
var require = function (name) {
if (name === "../libjass.js") {
return libjass;
}
else if (name === "assert") {
return assert;
}
else if (name === "./parser-test.js") {
return parserTest;
}
else {
throw new Error("Cannot require unmocked module: " + name);
}
};
(function () {
var Suite = function (name, parent) {
this.name = name;
this.setup = function () { };
this.children = [];
this.parent = parent;
if (this.parent !== null) {
this.parent.children.push(this);
this.number = this.parent.number + "." + this.parent.children.length;
}
else {
rootSuites.push(this);
this.number = String(rootSuites.length);
}
};
Object.defineProperties(Suite.prototype, {
total: { get: function () {
return this.children.reduce(function (previous, current) {
if (current instanceof Suite) {
return previous + current.total;
}
else {
return previous + 1;
}
}, 0);
}, enumerable: true },
passed: { get: function () {
return this.children.reduce(function (previous, current) {
if (current instanceof Suite) {
return previous + current.passed;
}
else {
return previous + ((current.passed === true) ? 1 : 0);
}
}, 0);
}, enumerable: true },
failed: { get: function () {
return this.children.reduce(function (previous, current) {
if (current instanceof Suite) {
return previous + current.failed;
}
else {
return previous + ((current.passed === false) ? 1 : 0);
}
}, 0);
}, enumerable: true },
});
Suite.prototype.run = function (logger, done) {
var _this = this;
logger.beginSuite(this);
this.setup();
var remainingChildren = this.children.slice();
var runChild = function () {
if (remainingChildren.length > 0) {
var nextChild = remainingChildren.shift();
nextChild.run(logger, runChild);
}
else {
logger.endSuite(_this);
done();
}
};
runChild();
};
var Test = function (description, body, parent) {
this.description = description;
this.body = body;
parent.children.push(this);
this.number = parent.number + "." + parent.children.length;
this.passed = null;
this.exception = null;
};
Test.prototype.run = function (logger, done) {
if (this.body) {
this.passed = false;
if (this.body.length === 0) {
try {
this.body();
this.passed = true;
}
catch (testException) {
this.exception = testException;
}
logger.writeTest(this);
done();
}
else {
try {
var _this = this;
this.body(function (testException) {
if (testException !== undefined && testException !== null) {
_this.exception = testException;
}
else {
_this.passed = true;
}
logger.writeTest(_this);
done();
});
}
catch (testException) {
this.exception = testException;
logger.writeTest(this);
done();
}
}
}
};
var currentSuite = null;
suite = function (name, body) {
var newSuite = new Suite(name, currentSuite);
currentSuite = newSuite;
body();
currentSuite = newSuite.parent;
return newSuite;
};
setup = function (body) {
currentSuite.setup = body;
}
test = function (description, body) {
return new Test(description, body, currentSuite);
};
})();
var assert = new function () {
this.deepEqual = function (actual, expected) {
if (expected === undefined && actual === undefined) {
return;
}
if (expected === undefined) {
throw new Error("Expected undefined but got [" + actual + "]");
}
if (actual === undefined) {
throw new Error("Expected [" + expected + "] but got undefined")
}
if (expected === null && actual === null) {
return;
}
if (expected === null) {
throw new Error("Expected null but got [" + actual + "]");
}
if (actual === null) {
throw new Error("Expected [" + expected + "] but got null")
}
if (expected.constructor !== actual.constructor && actual.constructor !== undefined) {
throw new Error("Expected value of type [" + expected.constructor.name + "] but got value of type [" + actual.constructor.name + "].");
}
switch(typeof expected) {
case "boolean":
case "number":
case "string":
if (expected !== actual) {
throw new Error("Expected [" + expected + "] but got [" + actual + "]");
}
break;
case "object":
if (Array.isArray(expected)) {
if (!Array.isArray(actual)) {
throw new Error("Expected an array but got [" + actual + "]");
}
if (expected.length !== actual.length) {
throw new Error("Expected an array of length " + expected.length + " but got array of length " + actual.length);
}
var i;
for (i = 0; i < expected.length; i++) {
assert.deepEqual(actual[i], expected[i]);
}
}
else {
var expectedProperties = Object.keys(expected);
var actualProperties = Object.keys(actual);
expectedProperties.sort();
actualProperties.sort();
assert.deepEqual(actualProperties, expectedProperties);
expectedProperties.forEach(function (property) {
assert.deepEqual(actual[property], expected[property]);
});
}
break;
default:
throw new Error("Unrecognized type: " + typeof expected);
}
};
};
var Logger = function (outputDiv) {
var testDiv = document.createElement("div");
testDiv.className = "test";
var suiteElement = document.createElement("fieldset");
suiteElement.className = "suite";
suiteElement.appendChild(document.createElement("legend"));
var totalDiv = document.createElement("div");
var currentSuiteElement = outputDiv;
this.beginSuite = function (suite) {
var newSectionElement = suiteElement.cloneNode(true);
currentSuiteElement.appendChild(newSectionElement);
currentSuiteElement = newSectionElement;
var message = "Suite " + suite.number + " - \"" + suite.name + "\"";
console.group(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(message));
};
this.endSuite = function (suite) {
var numTotal = suite.total;
var numPassed = suite.passed;
var numFailed = suite.failed;
var message = numPassed + " of " + numTotal + " tests passed";
console.log(message);
if (numFailed > 0) {
var message = numFailed + " of " + numTotal + " tests failed";
console.warn(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
currentSuiteElement.className += " failed";
}
else {
currentSuiteElement.className += " passed";
}
if (numTotal > numPassed + numFailed) {
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped";
console.log(message);
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode(" - " + message));
}
currentSuiteElement = currentSuiteElement.parentElement;
console.groupEnd();
};
this.writeTest = function (test) {
if (test.passed === true) {
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) {
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 {
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] " + " : Skipped";
console.log(message);
append(testDiv, message).className += " skipped";
}
};
this.writeTotal = function (suites) {
currentSuiteElement = suiteElement.cloneNode(true);
outputDiv.appendChild(currentSuiteElement);
currentSuiteElement.className = "total";
console.group("Total");
currentSuiteElement.querySelector("legend").appendChild(document.createTextNode("Total"));
var numTotal = suites.map(function (section) { return section.total; }).sum();
var numPassed = suites.map(function (section) { return section.passed; }).sum();
var numFailed = suites.map(function (section) { return section.failed; }).sum();
var message = numPassed + " of " + numTotal + " tests passed.";
console.log(message);
append(totalDiv, message).className = "passed";
if (numFailed > 0) {
var message = numFailed + " of " + numTotal + " tests failed.";
console.warn(message);
append(totalDiv, message).className = "failed";
}
if (numTotal > numPassed + numFailed) {
var message = (numTotal - (numPassed + numFailed)) + " of " + numTotal + " tests skipped.";
console.warn(message);
append(totalDiv, message).className = "skipped";
}
console.groupEnd();
};
var append = function (messageDivType, message) {
var messageDiv = messageDivType.cloneNode();
currentSuiteElement.appendChild(messageDiv);
messageDiv.appendChild(document.createTextNode(message));
return messageDiv;
};
};
addEventListener("DOMContentLoaded", function () {
var logger = new Logger(document.querySelector("#output"));
var remainingSuites = rootSuites.slice();
var runSuite = function () {
if (remainingSuites.length > 0) {
var nextSuite = remainingSuites.shift();
nextSuite.run(logger, runSuite);
}
else {
logger.writeTotal(rootSuites);
}
};
runSuite();
}, false);
+47
View File
@@ -0,0 +1,47 @@
<?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>Parser tests</title>
<style type="text/css">
<![CDATA[
.test.passed, .suite.passed >legend, .total > .passed {
color: green;
}
.test.failed, .suite.failed >legend, .total > .failed {
color: red;
}
]]>
</style>
<script src="../libjass.js" />
<script src="index.js" />
<script src="parser-test.js" />
<script src="test-primitives.js" />
<script src="test-tags.js" />
<script src="test-miscellaneous.js" />
<script src="test-webworker.js" />
</head>
<body>
<div id="output" />
</body>
</html>
-26
View File
@@ -1,26 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern"], {
suites: [
"tests/doc/doc"
],
excludeInstrumentation: /^(?:tests|node_modules)[/\\]/,
});
-75
View File
@@ -1,75 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern"], function (intern) {
var result = {
suites: [
"tests/unit/minified",
"tests/unit/miscellaneous",
"tests/unit/polyfills",
"tests/unit/primitives",
"tests/unit/tags",
"tests/unit/webworker"
],
functionalSuites: [
"tests/functional/kfx/kfx"
],
excludeInstrumentation: /^(?:tests|node_modules)[/\\]/,
tunnel: "NullTunnel",
environments: [
{ browserName: "chrome" },
{ browserName: "firefox", firefox_profile:
"UEsDBBQAAAAIAE6yo0ZkJDVLdgMAAGENAAAHAAAAdXNlci5qc6VXTc/TMAy+I/Ef0HsCiUUDNCHB" +
"ia8DEhKICXGM0tRdw9IkSpz13b/HSVo0DbpQuC2ZndiPHz9OYwDPnYfu8V3j7UgrFkQH+bcyBzYI" +
"PQoPDIxoNLR3Tx91Qgd48vrhgwvXViAZOesx+fQgNPZlzcjopGTtAAM4Wn9kPaJjrlehP2/S306E" +
"sNFgDtiT5/Pd7vpiO1CI91z23g7Ag/TKIffRcFQDkMuL7eRxnSWKJjBKzXw277QNMAe2YN7a0Wgr" +
"WrrNiENCqbfj9x7MHkXOesHfUVwOQ7qOD7YVuoYknNBaHRh4b720Jlh9CR76eO0S1MFYwzwMMDTg" +
"93kZKvkECEGRGVoP5BoioddRrFx6EfqKs1bmyKwDw+EewRuhU3HWc6JTPuDXaBauG6FpvTrRWkgJ" +
"Dnk0lH5AaLkEj2GG43dKlHu40Jr/CBRk8qZsFzwS4EeFbCLhXOFWhYT6Auip7NGxnmjnyJyPoGWi" +
"YPQJjLu7K/sElMmIs+gIGmDGourO38hkIf0AMnqF58xSflIwEpB8UPfQZvZxaySsb8joEo8/FEbV" +
"ru6UBvrFAnolkVuvDspwZ7WS5wpJDIxEejbhIRob8VWjhTne3Yy1nM3S5j42g8o0/ZI332QWXAW9" +
"6pwp60rkc21TXcl0W+0CSpVqQxLxhzawXUeGsBHOBUaEpLo1Z95CJ6LGwq1F9GVwU+v/FUXAIPjE" +
"kRHEcfao8LAmzHNvIGgYAP2ZFUXLDs9rxvPh68LP21Dl+FyHD98+vWEvWNGIWSSXjK/79pqb1Yw8" +
"/ABZvaaQP12wCgIN4rSAQKWYIqJ9XwRrL62DJHXPtouCWoLiRiAtOZzA/JLT+rz87MDUI2q0lUet" +
"Av4HAuuVsTr2hJd9oX595A+stDVr4+BuD2Hh3GJDLbPWUi28aqcZU1Xha/l9sfCKKhNsM0tPQIGx" +
"9iCYjBespndW5YF1PbVDelZcTG1FG+BrAGYuL8TxQ5xEiYHZMtJzk3w078pLqZx983VJaWykNQZk" +
"9t848BsypYBvt0ynaMPe8zRgyPLldrdbI2fLwM4PDZ5nSRuzU5rR60X/rzUz9KA1kz3I4/syit6W" +
"fy79Kt8G//5NoO3hkDan5+2HFfoQ0ixHrkzergtjuWsOtibbVAmnxRnaD8+2+y950LFn5LK7JYoe" +
"BjuxNk8X9vwyqp9QSwECPwAUAAAACABOsqNGZCQ1S3YDAABhDQAABwAkAAAAAAAAACAAAAAAAAAA" +
"dXNlci5qcwoAIAAAAAAAAQAYABHwicAphtABMWWQhyaG0AExZZCHJobQAVBLBQYAAAAAAQABAFkA" +
"AACbAwAAAAA="
},
{ browserName: "internet explorer", version: "11" }
],
};
if (intern.args.minified === "true") {
result.loader = {
map: {
tests: {
"lib/libjass": "lib/libjass.min",
"lib/libjass.js": "lib/libjass.min.js",
}
}
};
}
return result;
});
+72
View File
@@ -0,0 +1,72 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var libjass = require("../libjass.js");
var assert = require("assert");
libjass.debugMode = true;
var parserTest = function (description, input, rule, expected) {
var result = test(description, function () {
var actual = null;
try {
actual = libjass.parser.parse(input, rule);
}
catch (parseException) {
if (expected === null) {
passed = true;
return;
}
else {
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.stack);
}
}
if (expected === null) {
throw new Error("Expected parse to fail.");
}
else if (actual === null) {
throw new Error("Parse failed without throwing an exception.");
}
assert.deepEqual(actual, expected);
});
result.customProperties = Object.create(null);
result.customProperties.input = input;
result.customProperties.rule = rule;
return result;
};
parserTest.skip = function (description, input, rule, expected) {
var result = test(description); // Could use test.skip but Mocha's implementation has a bug - it doesn't return the Test object
result.customProperties = Object.create(null);
result.customProperties.input = input;
result.customProperties.rule = rule;
return result;
};
if (typeof module !== "undefined") {
module.exports = parserTest;
}
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>&gt;2012 &gt;Streaman Animu</title>
<link rel="stylesheet" href="../../lib/libjass.css" />
<link rel="stylesheet" href="browser-test-page.css" />
<script src="../../lib/libjass.js"></script>
</head>
<body>
<div class="libjass-wrapper" />
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern/chai!assert", "lib/libjass"], function (assert, libjass) {
return function (input, rule, expected) {
return function () {
var actual = null;
try {
actual = libjass.parser.parse(input, rule);
}
catch (parseException) {
if (expected !== null) {
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.stack);
}
assert.strictEqual(parseException.message, "Parse failed.");
return;
}
if (expected === null) {
throw new Error("Expected parse to fail.");
}
if (actual === null) {
throw new Error("Parse failed without throwing an exception.");
}
assert.deepEqual(actual, expected);
};
}
});
-115
View File
@@ -1,115 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define([
"intern/dojo/node!fs",
"intern/dojo/node!path",
"intern/dojo/node!pngjs",
"intern/chai!assert",
"intern/dojo/node!intern/node_modules/leadfoot/helpers/pollUntil",
"lib/libjass"
], function (fs, path, pngjs, assert, pollUntil, libjass) {
function TestPage(remote, pageUrl, assUrl, targetWidth, targetHeight) {
this._remote = remote;
this._pageUrl = pageUrl;
this._assUrl = assUrl;
this._targetWidth = targetWidth;
this._targetHeight = targetHeight;
}
TestPage.prototype.prepare = function () {
var _this = this;
this._remote.session.setExecuteAsyncTimeout(10000);
return this._remote
.setWindowSize(1280, 720)
.get(this._pageUrl)
.then(pollUntil('return (document.readyState === "complete") ? true : null;'), 100)
.executeAsync(function (assUrl, enableSvg, callback) {
window.clock = new libjass.renderers.ManualClock();
var libjassSubsWrapper = document.querySelector(".libjass-wrapper");
libjass.ASS.fromUrl(assUrl).then(function (ass) {
libjassSubsWrapper.style.width = ass.properties.resolutionX + "px";
libjassSubsWrapper.style.height = ass.properties.resolutionY + "px";
var renderer = new libjass.renderers.WebRenderer(ass, clock, libjassSubsWrapper, { enableSvg: enableSvg });
renderer.addEventListener("ready", function () {
renderer.resize(ass.properties.resolutionX, ass.properties.resolutionY);
clock.pause();
callback();
});
});
}, [this._assUrl, this._remote.session.capabilities.browserName !== "internet explorer"])
.then(function () { return _this; });
};
TestPage.prototype.seekAndCompareScreenshot = function (time, filename) {
var _this = this;
filename = path.join(path.dirname(filename), this._remote.session.capabilities.browserName, path.basename(filename));
return this._remote
.execute(function (time) { clock.seek(time); }, [time])
.takeScreenshot()
.then(function (buffer) {
return new libjass.Promise(function (resolve, reject) {
new pngjs.PNG().parse(buffer, function (error, input) {
if (error !== null) {
reject(error);
return;
}
var output = new pngjs.PNG({ width: _this._targetWidth, height: _this._targetHeight });
input.bitblt(output, 0, 0, _this._targetWidth, _this._targetHeight, 0, 0);
resolve(output);
});
});
})
.then(function (screenshot) {
return new libjass.Promise(function (resolve, reject) {
var baselineBuffer = fs.readFileSync(filename);
new pngjs.PNG().parse(baselineBuffer, function (error, baseline) {
if (error !== null) {
reject(error);
return;
}
try {
assert.equal(0, screenshot.data.compare(baseline.data), filename + " does not match!");
resolve();
}
catch (ex) {
reject(ex);
}
});
}).catch(function (err) {
return new libjass.Promise(function (resolve, reject) {
var newFilename = filename.substr(0, filename.length - path.extname(filename).length) + "-new" + path.extname(filename);
screenshot.pack().pipe(fs.createWriteStream(newFilename)).once("error", reject).on("finish", function () { reject(err); });
});
});
})
.then(function () { return _this; });
};
return TestPage;
});
+185
View File
@@ -0,0 +1,185 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var libjass = require("../libjass.js");
var parserTest = require("./parser-test.js");
suite("Miscellaneous", function () {
parserTest("herkz", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
new libjass.parts.Position(311, 4),
new libjass.parts.GaussianBlur(0.8),
new libjass.parts.FontSize(40),
new libjass.parts.Border(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
new libjass.parts.Text("Chi"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
new libjass.parts.Text("tose "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Text("Furu")
]);
parserTest("herkz-fixed", "{\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6)}Chi{\\c&H422CB1&}tose {\\c&H3F171F&}Furu", "dialogueParts", [
new libjass.parts.Position(311, 4),
new libjass.parts.GaussianBlur(0.8),
new libjass.parts.FontSize(40),
new libjass.parts.Border(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Transform(3.82, 3.82, null, [new libjass.parts.GaussianBlur(6)]),
new libjass.parts.Text("Chi"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(177, 44, 66, 1)),
new libjass.parts.Text("tose "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)),
new libjass.parts.Text("Furu")
]);
parserTest("knk-02", "{\\\\k0}{\\1c&H35C2BD&}s{\\1c&H53A2C0&}e{\\1c&H757FC4&}i{\\1c&H866EC6&}j{\\1c&H995AC8&}a{\\1c&HBC36CC&}k{\\1c&HD420C8&}u{\\1c&HB942A7&} {\\1c&HA25F8A&}w{\\1c&H799356&}a{\\1c&H63B03A&} {\\1c&H54C723&}k{\\1c&H68C126&}i{\\1c&H7BBC29&}r{\\1c&H8CB82C&}i{\\1c&HA2B230&}s{\\1c&HBEAB34&}a{\\1c&HD8A339&}k{\\1c&HB68D3B&}u{\\1c&HA4813C&} {\\1c&H88703E&}y{\\1c&H665A41&}o{\\1c&H444444&}u", "dialogueParts", [
new libjass.parts.Comment("\\"),
new libjass.parts.ColorKaraoke(0),
new libjass.parts.PrimaryColor(new libjass.parts.Color(189, 194, 53, 1)),
new libjass.parts.Text("s"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(192, 162, 83, 1)),
new libjass.parts.Text("e"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(196, 127, 117, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(198, 110, 134, 1)),
new libjass.parts.Text("j"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 90, 153, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(204, 54, 188, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(200, 32, 212, 1)),
new libjass.parts.Text("u"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(167, 66, 185, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(138, 95, 162, 1)),
new libjass.parts.Text("w"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(86, 147, 121, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(58, 176, 99, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(35, 199, 84, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(38, 193, 104, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(41, 188, 123, 1)),
new libjass.parts.Text("r"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(44, 184, 140, 1)),
new libjass.parts.Text("i"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(48, 178, 162, 1)),
new libjass.parts.Text("s"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(52, 171, 190, 1)),
new libjass.parts.Text("a"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(57, 163, 216, 1)),
new libjass.parts.Text("k"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(59, 141, 182, 1)),
new libjass.parts.Text("u"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(60, 129, 164, 1)),
new libjass.parts.Text(" "),
new libjass.parts.PrimaryColor(new libjass.parts.Color(62, 112, 136, 1)),
new libjass.parts.Text("y"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(65, 90, 102, 1)),
new libjass.parts.Text("o"),
new libjass.parts.PrimaryColor(new libjass.parts.Color(68, 68, 68, 1)),
new libjass.parts.Text("u")
]);
parserTest("klk-01", "{\\an5\\bord0\\blur1\\p1\\c&H97B9D6&\\pos(143.39,358.77)\\frz334.3\\fscx39\\fscy47\\iclip(m 129 338 l 121 381 110 372 110 331)}m 0 0 l 100 0 100 100 0 100", "dialogueParts", [
new libjass.parts.Alignment(5),
new libjass.parts.Border(0),
new libjass.parts.GaussianBlur(1),
new libjass.parts.DrawingMode(1),
new libjass.parts.PrimaryColor(new libjass.parts.Color(214, 185, 151, 1)),
new libjass.parts.Position(143.39, 358.77),
new libjass.parts.RotateZ(334.3),
new libjass.parts.FontScaleX(0.39),
new libjass.parts.FontScaleY(0.47),
new libjass.parts.VectorClip(1, [
new libjass.parts.drawing.MoveInstruction(129, 338),
new libjass.parts.drawing.LineInstruction(121, 381),
new libjass.parts.drawing.LineInstruction(110, 372),
new libjass.parts.drawing.LineInstruction(110, 331)
], false),
new libjass.parts.DrawingInstructions([
new libjass.parts.drawing.MoveInstruction(0, 0),
new libjass.parts.drawing.LineInstruction(100, 0),
new libjass.parts.drawing.LineInstruction(100, 100),
new libjass.parts.drawing.LineInstruction(0, 100)
])
]);
parserTest("chihaya-16", "{\\an7\\pos(0,0)\\bord0\\blur0.5\\p1\\c&HFAFAFA&}m 88 644 l 87 681 l 255 681 l 256 643 m 311 643 l 310 681 l 482 681 l 483 641 m 562 641 l 561 679 l 730 676 l 731 640 m 800 638 l 798 677 l 968 676 l 970 639 m 1031 638 l 1028 675 l 1200 674 l 1200 640 {p0}", "dialogueParts", [
new libjass.parts.Alignment(7),
new libjass.parts.Position(0, 0),
new libjass.parts.Border(0),
new libjass.parts.GaussianBlur(0.5),
new libjass.parts.DrawingMode(1),
new libjass.parts.PrimaryColor(new libjass.parts.Color(250, 250, 250)),
new libjass.parts.DrawingInstructions([
new libjass.parts.drawing.MoveInstruction(88, 644),
new libjass.parts.drawing.LineInstruction(87, 681),
new libjass.parts.drawing.LineInstruction(255, 681),
new libjass.parts.drawing.LineInstruction(256, 643),
new libjass.parts.drawing.MoveInstruction(311, 643),
new libjass.parts.drawing.LineInstruction(310, 681),
new libjass.parts.drawing.LineInstruction(482, 681),
new libjass.parts.drawing.LineInstruction(483, 641),
new libjass.parts.drawing.MoveInstruction(562, 641),
new libjass.parts.drawing.LineInstruction(561, 679),
new libjass.parts.drawing.LineInstruction(730, 676),
new libjass.parts.drawing.LineInstruction(731, 640),
new libjass.parts.drawing.MoveInstruction(800, 638),
new libjass.parts.drawing.LineInstruction(798, 677),
new libjass.parts.drawing.LineInstruction(968, 676),
new libjass.parts.drawing.LineInstruction(970, 639),
new libjass.parts.drawing.MoveInstruction(1031, 638),
new libjass.parts.drawing.LineInstruction(1028, 675),
new libjass.parts.drawing.LineInstruction(1200, 674),
new libjass.parts.drawing.LineInstruction(1200, 640)
]),
new libjass.parts.Comment("p0")
]);
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>"
}
]);
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"
}
]);
});

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