323 Commits
Author SHA1 Message Date
Arnavion c6053f59a1 Nested rotations. 2014-08-17 22:09:16 -07:00
Arnavion 73755e0c8a libjass.parser.parse_assScriptProperty() now properly handles a parent that's not a script section. 2014-08-17 03:59:41 -07:00
Arnavion f951475103 Replaced custom map-like interfaces with actual Map. 2014-08-17 03:59:40 -07:00
Arnavion 77474771e6 Fixed bug in SRT-to-ASS conversion not converting all HTML tags. 2014-08-17 03:51:29 -07:00
Arnavion 55f9cb42fe Store animation delays separately instead of parsing them from the sub's style properties. 2014-08-17 03:51:27 -07:00
Arnavion a8addddbd6 Begin v0.9.0 2014-08-16 21:58:29 -07:00
Arnavion ccd4a53cfb Updated mocha to v1.21.x 2014-08-16 21:52:38 -07:00
Arnavion 3928bf57f0 Added ASS.addEvent() to dynamically add dialogues to an ASS object.
Fixes #16
2014-08-16 21:33:26 -07:00
Arnavion dd6d008b74 Parse format-specifier-enabled properties inside parse_assScriptProperty itself instead of relying on caller. 2014-08-16 21:28:56 -07:00
Arnavion 45aabac358 Allow passing in a custom parse tree root to libjass.parser.parse() 2014-08-16 21:28:56 -07:00
Arnavion 55c9dc10ca Log how long it to parse the script in debug mode. 2014-08-16 18:47:17 -07:00
Arnavion 32c232308f Update ManualClock with new API. 2014-08-08 23:02:26 -07:00
Arnavion 2eaa6e451d Cleanup and typos. 2014-08-08 22:51:12 -07:00
Arnavion 03ed6091c2 Handle unknown types gracefully in type linker. 2014-08-08 22:32:32 -07:00
Arnavion 43c3e26596 Use a checkbox to toggle subs instead of a button. 2014-08-08 22:22:44 -07:00
Arnavion 9e584bb75b Better clock state machine and API. 2014-08-08 22:22:13 -07:00
Arnavion e62f150f32 Added explanatory comments to example. 2014-08-08 20:23:53 -07:00
Arnavion 24ccc5200c Set resolution option labels even if video metadata is already loaded, and other tiny fixes. 2014-08-08 20:14:56 -07:00
Arnavion c627e72b03 Use XHR's load event instead of readystatechange + readyState === DONE 2014-08-08 20:02:46 -07:00
Arnavion 6eeb634a36 Minor improvements. 2014-07-31 22:56:38 -07:00
Arnavion 2349b82f0f Fastpath for ParseNode._peek() 2014-07-31 22:56:10 -07:00
Arnavion 443714878b Simply logic for parsing whitespace or text nodes while parsing dialogue parts. 2014-07-31 22:55:11 -07:00
Arnavion 7a6f956b49 Remember the format specifier for script sections that have it. Persist the format specifier for ASS dialogues.
Required for #16
2014-07-31 22:53:45 -07:00
Arnavion 73e464e357 Parse newlines as part of parsing a script header/comment/property instead of requiring script section to swallow it. 2014-07-31 22:52:31 -07:00
Arnavion 71d58e686c Don't create objects before they're needed. 2014-07-31 22:51:38 -07:00
Arnavion adaba54be0 Revert "Work around build break because of source-map 0.1.35"
This reverts commit 54b4245482.

Fixed in source-map 0.1.37.
2014-07-13 02:16:39 -07:00
Arnavion 5be310a6ba Added RendererSettings.preciseOutlines
When false, outlines are rendered in increments of the gaussian blur (if any). Vastly boosts performance for large-outline-large-blur lines and doesn't look appreciably different.
2014-07-13 02:14:09 -07:00
Arnavion ee45e0379e Added default value of RendererSettings.preRenderTime to JSDoc. 2014-07-13 02:13:04 -07:00
Arnavion 848898c8d0 Don't rename "_classTag" property in minifier. webworker serializer refers to it by name. 2014-07-11 00:46:10 -07:00
Arnavion deda90698b UJS.SourceMap.root defaults to null. 2014-07-09 20:29:59 -07:00
Arnavion 774b218aff Use a set to hold font URLs to avoid duplicates. 2014-07-09 20:28:11 -07:00
Arnavion 6bb4873d67 Implemented SimpleSet.size 2014-07-09 20:27:26 -07:00
Arnavion 54b4245482 Work around build break because of source-map 0.1.35
Depending on https://github.com/mozilla/source-map/issues/123 it's either a bug in source-map or in TypeScript.
2014-07-09 00:21:52 -07:00
Arnavion 39d75c189d Implemented UglifyJS directive to suppress unreferenced symbol warnings. 2014-07-08 22:27:41 -07:00
Arnavion ae56620f91 Updated mocha to v1.20.x 2014-07-04 23:41:32 -07:00
Arnavion ee07ca1d7c Removed workaround for uglify-js < 2.4.14 2014-07-04 23:41:19 -07:00
Arnavion b6ca859f4b Fixed typo. 2014-07-04 23:20:07 -07:00
Arnavion c4f87d54bf Added support for \fs+ and \fs- 2014-07-04 23:19:41 -07:00
Arnavion b662698e0c Increase range of filter effects from 150% to 200% 2014-07-03 01:21:49 -07:00
Arnavion 0198fb235c Fixed some things detected by Safer Typescript compiler. 2014-07-03 00:57:06 -07:00
Arnavion fc5dce86ea Increase range of filter effects from 120% to 150% 2014-06-20 21:31:27 -07:00
Arnavion d918b1256d 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. 2014-06-02 22:25:57 -07:00
Arnavion c65305fbe8 Fixed typo. 2014-06-02 21:49:00 -07:00
Arnavion 111c4199ff Fixed min-width calculation to correctly sum horizontal margins. 2014-06-02 21:23:25 -07:00
Arnavion 8bc205fc01 Added support for interfaces to documentation generator. 2014-05-31 02:17:49 -07:00
Arnavion e69221af7d Web worker improvements.
- Don't restrict usage to only one worker channel per library instance.
- WorkerChannel.request() now returns a promise instead of needing a callback passed in.
2014-05-31 01:42:22 -07:00
Arnavion 6ba048e55a Added web worker test for libjass.parser.parse() 2014-05-29 00:35:12 -07:00
Arnavion 8dda6d67e6 Web worker support for calling libjass.parser.parse() 2014-05-29 00:35:10 -07:00
Arnavion 7c6793be8c Implemented a mechanism for running tasks in a web worker. 2014-05-29 00:35:09 -07:00
Arnavion 123339519d Added browser support for async tests and mocha's setup() function. 2014-05-29 00:35:08 -07:00
Arnavion 12374765ed Added support for Map.size to SimpleMap 2014-05-29 00:35:06 -07:00
Arnavion 255447b147 Updated documentation generator to use the new TypeScript API. 2014-05-29 00:35:05 -07:00
Arnavion 32507ad644 Fixed not being able to parse tags without values. 2014-05-28 22:00:04 -07:00
Arnavion d06777d2b3 Removed private member from interface. 2014-05-27 20:46:13 -07:00
Arnavion b727f3c6e2 Added support for adding and removing files while watching.
Removed astWatcher support for watch since it's not something I care to have run in incremental compiles.
2014-05-22 23:21:53 -07:00
Arnavion 140393fcca Compiler was resolving files using the old file contents instead of the updated file contents, which would suppress reference errors until the next compile. 2014-05-22 12:35:38 -07:00
Arnavion 2d308b7130 Support top-level classes and free functions in AST walker.
While libjass will never have these, they can occur because of what's actually a compile error. Thus while the AST walker will succeed, the subsequent call to compiler.compile() will fail.
2014-05-21 00:49:10 -07:00
Arnavion 6a282416a8 Split test runner into its own file that doesn't need gulp to run.
This allows the tests child process to start nearly instantly since it doesn't have to spend a few seconds loading gulp.
2014-05-21 00:49:08 -07:00
Arnavion 40ebcfc6b7 Tell gulp.src not to read file contents since we read the files ourselves. 2014-05-21 00:21:58 -07:00
Arnavion d23e57d215 Added better watch implementation of TypeScript gulp task that only re-compiled changed files. 2014-05-21 00:21:57 -07:00
Arnavion 77ca69e1d3 Added information about using the parser in node. 2014-05-15 15:51:30 -07:00
Arnavion c2ff6dc369 Begin v0.8.0 2014-05-15 15:16:30 -07:00
Arnavion af9ea7c886 Use SPDX identifier for license. 2014-05-15 14:49:06 -07:00
Arnavion e1eb4f8e6f Updated TypeScript to v1.0.1 2014-05-13 20:12:35 -07:00
Arnavion 7303fd0f53 line-height gets inherited by children already. 2014-05-02 08:48:25 -07:00
Arnavion 96bf8d7f6c Use CSS to set alignment-specific text-align values. 2014-05-02 08:47:47 -07:00
Arnavion 74de507782 Fixed typo in JSDoc. 2014-05-02 00:48:46 -07:00
Arnavion 0155cd7392 Added clocks and a WebRenderer that's less magical than DefaultRenderer.
- Clocks are now used to trigger play, pause and timeUpdate events for renderers. There is a VideoClock that feeds off a <video> (what NullRenderer used to do) and a ManualClock that allows user script to send events (such as a player not backed by a <video>)
- NullRenderer now takes a Clock and uses its events to determine what dialogues to pre-render and draw.
- WebRenderer is a new renderer that's halfway between NullRenderer and DefaultRenderer. It takes in a <div> to render to, the libjass subs wrapper, but it is not tied to an actual <video> element. It is most of the old DefaultRenderer code except the bits that were specific to a wrapped <video>.
- DefaultRenderer now inherits from WebRenderer. It specializes WebRenderer as a renderer that wraps a <video>, just like it used to before. That is, it sets the clock to a VideoClock backed by a <video>, and makes the libjass subs wrapper wrap the <video>.
2014-05-02 00:48:28 -07:00
Arnavion 974c8f70ff Implemented support for \be
Fixes #3
2014-04-26 15:30:31 -07:00
Arnavion e4cd237779 Added missing space in SVG filter text. 2014-04-26 14:22:04 -07:00
Arnavion 4dec3b36f4 Fixed typo - rotationZ defaults to 0, not null 2014-04-26 14:22:03 -07:00
Arnavion e275cfbf2e Use pre-computed array for transform origins. 2014-04-26 14:22:02 -07:00
Arnavion 4d25127617 Changed Drawing to an accumulator of DrawingStyles.
This fixes the following:
- Declaring \pbo before \p1 used to have no effect, but now it does.
- Multiple DrawingInstructions parts used to cause an error instead of being treated as separate drawings.
2014-04-12 12:35:36 -07:00
Arnavion 8f6deb2f8e Added getter for current primary alpha to SpanStyles. 2014-04-12 12:31:11 -07:00
Arnavion 3958d9f504 Added missing return type to SpanStyles.primaryColor getter. 2014-04-12 12:30:26 -07:00
Arnavion 120a8e8cbd Separated the code that renders comments as text in debug mode into its own branch. 2014-04-12 12:00:03 -07:00
Arnavion 99dfafeafd Auto-generate @constructor, @extends, @memberOf, @private and @static from TypeScript AST. 2014-04-06 21:23:24 -07:00
Arnavion 379265414a Get baseType from AST walker instead of JSDoc. 2014-04-04 00:24:10 -07:00
Arnavion e09a34cca6 Added support for variables to documentation generator. 2014-04-04 00:24:09 -07:00
Arnavion b49bd14e55 Rewrote documentation generator to use the TypeScript syntax tree instead of UglifyJS. 2014-04-04 00:24:05 -07:00
Arnavion febd4f18ca Updated TypeScript to v1.0.0 2014-04-04 00:24:04 -07:00
Arnavion a633963b9f Added syntax highlighting. 2014-03-30 03:34:23 -07:00
Arnavion b0db48f28e Some gulp changes.
- gulp can take the names of multiple tasks to run. Use that in npm install instead of spawning two gulp processes.
- Made watch faster and more robust.
2014-03-29 15:28:39 -07:00
Arnavion c893984115 Fixed copy-paste typo. 2014-03-29 15:28:38 -07:00
Arnavion a4ef8b7ef8 Fixed typo in source-map URL for minified JS. 2014-03-28 23:17:47 -07:00
Arnavion a26a238b09 Begin v0.7.0 2014-03-24 00:43:35 -07:00
Arnavion 43c72308fb Updated mocha to v1.18.x and restricted uglify-js version to one that has the needed bugfixes. 2014-03-24 00:34:44 -07:00
Arnavion b5579ab340 Query the list of sources from output.source_map instead of guessing. 2014-03-22 18:44:34 -07:00
Arnavion dbf4b26768 Removed workaround for https://github.com/mishoo/UglifyJS2/issues/436 - Fixed in v2.4.13 2014-03-19 23:21:50 -07:00
Arnavion d6abba96d1 Use <pre><code> for usages. 2014-03-16 13:47:08 -07:00
Arnavion 165e18ae38 Replaced jake with gulp. 2014-03-11 23:14:38 -07:00
Arnavion 90f08a4116 Updated jake to v0.7.9 2014-03-07 23:45:26 -08:00
Arnavion 0dc8a08b3f Set jake version to 0.7.7 until https://github.com/mde/jake/pull/246 is fixed. 2014-03-07 22:30:20 -08:00
Arnavion 6ffd67523d Updated suppressed warnings. 2014-03-07 22:30:08 -08:00
Arnavion bd857350a0 Combine horizontal scaling into font-size for text parts so that they don't clip into their neighbours.
Fixes #15
2014-03-06 00:41:13 -08:00
Arnavion d42261ae3e Don't let .libjass-font-measure interfere with the rest of the DOM. 2014-03-06 00:39:31 -08:00
Arnavion 18b764c7ed Implemented basic SRT support.
- Supported: Multiple lines, <b>, <i>, <u>, <font color="#[0-9a-fA-F]{6}">
- Not supported: Explicit positioning.
- Internally converts SRT to ASS because I'm lazy.

Fixes #2
2014-03-04 21:29:07 -08:00
Arnavion df5318f14c Support undefined values and objects with undefined constructors in assert.deepEqual 2014-03-04 21:24:31 -08:00
Arnavion 6783efbe91 Fixed typo in \fade handling. 2014-03-04 20:33:42 -08:00
Arnavion 72df7fce8a Replaced pre-defined fade animation with explicit animation.
`animation-fill-mode: both` in Firefox caused the fade-out animation of a \fad to be ignored if the fade-in animation was also applied. With this change the fade-in and fade-out make only one animation.
2014-03-04 20:30:21 -08:00
Arnavion 0ad4eac649 Show private by default if location.hash is for a private thing. 2014-03-01 17:50:30 -08:00
Arnavion 225b696077 Updated README 2014-03-01 17:23:19 -08:00
Arnavion 33ed1e44cd Added missing JSDoc. 2014-03-01 17:13:23 -08:00
Arnavion 44f02e50b4 Fixed ASS drawings always being black. 2014-03-01 17:00:43 -08:00
Arnavion c06dd13717 Fixed parser failure when drawing instructions have terminal spaces or are only spaces. 2014-03-01 16:44:32 -08:00
Arnavion 26edc2e0d9 Minor typo. 2014-03-01 16:24:50 -08:00
Arnavion 5992664bed Replaced 41ms setInterval-based timer with requestAnimationFrame-based timer. 2014-03-01 16:23:59 -08:00
Arnavion 7d2070900b Expose reference to renderer in debug mode. 2014-03-01 16:22:39 -08:00
Arnavion 1fcb8d6e37 Fixed font sizes.
The font size in ASS is actually meant to be the line height. ASS renderers scale the font size so that it fits in the line height. Since there is no way in the browser to calculate a font size such that it results in a given line height, libjass now uses an invisible div's offsetHeight to calculate the line height for a given font size, and works backwards from there to approximate the font size required for a target line height.
2014-03-01 16:16:33 -08:00
Arnavion 7d81f7d695 Workaround for https://github.com/mishoo/UglifyJS2/issues/436 2014-03-01 05:52:36 -08:00
Arnavion f4c2c0ee28 Don't call renderer.onVideoTimeUpdate() when this.currentTime is not available. 2014-03-01 05:52:02 -08:00
Arnavion 3b513c0e27 Separate layer wrappers. 2014-02-25 19:23:39 -08:00
Arnavion ed49bd544f Updated TypeScript to v0.9.7 2014-02-25 19:23:38 -08:00
Arnavion 3667ab415b Changed vararg functions to use arrays because the Closure Compiler annotation doesn't match what the TypeScript compiler generates for these functions.
GCC wants the vararg parameter as an actual parameter to the function even though it will be unused, while TS doesn't generate it.
2014-02-23 19:01:00 -08:00
Arnavion 7aaf3a3f24 Fixed JSDoc issues found by Closure Compiler. 2014-02-23 19:00:25 -08:00
Arnavion d3608c0f72 More JSDoc fixes. 2014-02-23 18:39:57 -08:00
Arnavion 07341f89c8 Fixed return-type JSDoc annotation. 2014-02-23 18:34:58 -08:00
Arnavion d2b0da2954 Added support for toggling subtitles by disabling and enabling the renderer.
Fixes #5
2014-02-23 01:29:27 -08:00
Arnavion f312514e40 Simplified video-event normalization. 2014-02-23 01:29:26 -08:00
Arnavion 1354964bb6 Also escape ampersands. 2014-02-22 22:52:13 -08:00
Arnavion a14389c7e1 Fixed HTML indentation and order of members. 2014-02-22 22:38:48 -08:00
Arnavion 31d03baf9f Added JSDoc for libjass.Set and libjass.Map 2014-02-22 21:54:16 -08:00
Arnavion 833c429e88 Prettier "Usage" box. 2014-02-22 21:53:58 -08:00
Arnavion 6ba8ce45c7 Don't guess private-ness without explicit @private JSDoc annotation. 2014-02-22 21:53:16 -08:00
Arnavion 269e3b917b Minor fixes. 2014-02-22 21:16:07 -08:00
Arnavion b7919d8b7c Embed the source in the source maps so that source debugging is available without needing to host the source. 2014-02-19 13:58:10 -08:00
Arnavion 3fa1be6c33 Use screw_ie8 config option.
This doesn't actually change anything, but we don't support IE6-8 anyway so there's no harm in using it.
2014-02-19 11:12:40 -08:00
Arnavion 754e28231d Fixed some outdated info. 2014-02-17 12:04:47 -08:00
Arnavion 5823d77075 Truncate Color.alpha in rgba() notation to three decimal places. 2014-02-12 09:54:45 -08:00
Arnavion f40766049a Added support for opacity to borders. 2014-02-12 09:51:28 -08:00
Arnavion 94cdace6d9 Don't create unnecessary empty span's. 2014-02-10 18:51:52 -08:00
Arnavion b5de1556db Replaced feOffset+feComponentTransfer-based outlines with feMorphology+feFlood-based outlines.
The resulting filters are smaller and faster and the outlines have the right colors.
2014-02-10 18:40:08 -08:00
Arnavion 5bf0a13277 Don't render shadow when both shadow depth X and Y are 0. 2014-02-10 17:53:34 -08:00
Arnavion d8636b0302 Fixed outdated JSDoc. 2014-02-09 13:24:37 -08:00
Arnavion 4e2cb294ed Prevent browsers being over-generous in inserting newlines.
This behavior will cause all lines to be as wide as the subtitle wrapper. While this does mean that signs positioned not exactly in the center can be so wide they clip at the edges of the video, this is the same behavior as VSFilter.
2014-02-09 13:17:54 -08:00
Arnavion 1e52a0943b Re-introduced parts.NewLine
This is needed since all span's can be inline-block now.
2014-02-09 13:16:28 -08:00
Arnavion 86cdaf2edf Fixed error when parsing cubic bezier curve drawing instructions. 2014-02-08 19:38:15 -08:00
Arnavion 508c49cc44 Trace parse failures in greater detail in debug mode. 2014-02-08 19:37:20 -08:00
Arnavion e4e3401573 Renamed DefaultRenderer.resizeVideo to DefaultRenderer.resize 2014-02-08 19:15:11 -08:00
Arnavion 8e5f242743 Added support for parsing wrapping style and border/shadow scaling script properties.
Fixes #10
2014-02-08 17:41:33 -08:00
Arnavion 0cbe15698e Set Dialogue._alignment in Dialogue._parsePartsString() for consistency. 2014-02-08 17:09:04 -08:00
Arnavion a90c8a0e0a Updated example and readme to resize video manually because of 98e95936b6 2014-02-08 17:07:46 -08:00
Arnavion 5e81bff15c Added support for parsing secondary color, default Z-rotation and border style of styles.
Fixes #9
2014-02-08 17:06:25 -08:00
Arnavion 79268096e0 Rotation and skew fixes.
- Rotation and skew now act on individual spans instead of the whole div.
- Rotation now has a perspective set.
- Rotations are now applied in the fixed order of Y-X-Z in accordance with VSFilter.
- Skew is now calculated correctly - skew angle is now set to "atan(value) deg" instead of "(45 * value) deg"
2014-02-08 15:59:21 -08:00
Arnavion 28e2588df3 Updated SimpleMap.set error message for unsupported keys. 2014-02-06 23:41:38 -08:00
Arnavion 8261bac95a Changed DefaultRenderer._currentSubs to have Dialogue keys instead of their IDs.
This fixes the use of the incorrect assumption that dialogue IDs are also their indices in ass.dialogues
2014-02-06 23:38:48 -08:00
Arnavion 79a9cd06cc Added support for Dialogue objects in SimpleMap. 2014-02-06 23:37:19 -08:00
Arnavion ec84d62c60 Removed leftovers from tsline experimentation. 2014-02-06 23:14:39 -08:00
Arnavion e5fc5ca571 Removed broken binary-search-on-end-times algorithm for deciding which dialogues to pre-render / draw. 2014-02-06 22:55:03 -08:00
Arnavion dfb9a372dd Use .toFixed(3) on margins. 2014-02-06 22:33:09 -08:00
Arnavion 98e95936b6 Don't resize the video in DefaultRenderer.resizeVideo() and don't rely on video.style.width and video.style.height to be present. 2014-02-06 21:56:49 -08:00
Arnavion dc8e7d4214 Made .libjass-filters SVG element have display: block 2014-02-06 21:56:11 -08:00
Arnavion 23f5c21bb6 Log dialogue parts even when it wasn't already pre-rendered. 2014-02-06 15:56:47 -08:00
Arnavion a0d357329a Only log timeUpdate, etc. in verbose mode. 2014-02-06 15:56:33 -08:00
Arnavion ffb6da34a3 Converted unnecessary and redundant inheritance to a mixin. 2014-02-06 15:52:11 -08:00
Arnavion 05dcd65dd5 Changed parts.VectorClip.instructions from parts.DrawingInstruction to parts.drawing.Instruction[]
vectorClipPart.instructions.instructions looked too silly, and DrawingInstruction was only meant to be a pseudo-part for ASS draw anyway.
2014-02-06 13:55:26 -08:00
Arnavion 7567a6a8b6 Fixed typo. 2014-02-06 13:29:16 -08:00
Arnavion 09efaea7ac Implemented parsing drawing instructions for VectorClip. 2014-02-06 13:28:41 -08:00
Arnavion 985de99d81 Renamed parts.DrawingInstructions.value to parts.DrawingInstructions.instructions 2014-02-06 13:27:43 -08:00
Arnavion 6492af0675 Fixed some typos and method order. 2014-02-06 13:26:09 -08:00
Arnavion b0c327ed26 Made all headings consistently level 3 2014-02-05 20:49:53 -08:00
Arnavion d5292e374e Rename function arguments that start with an underscore to not have the underscore. 2014-02-05 20:15:36 -08:00
Arnavion dac9cb0688 Fixed style errors from tslint. 2014-02-01 00:42:05 -08:00
Arnavion e40c3d50fd Replaced DefaultRenderer._{add,remove}Class with Element.classList
Fixes #11
2014-01-29 01:17:41 -08:00
Arnavion f2edbfc4cf Fixed example to use ass.properties to get script resolution. 2014-01-29 01:17:05 -08:00
Arnavion 4505edf8ba Begin v0.6.0 2014-01-26 11:42:41 -08:00
Arnavion 7f9c72dab6 v0.5.0 2014-01-26 11:42:07 -08:00
Arnavion 00c7a6bbc4 Updated mocha to 1.17.x 2014-01-26 11:40:24 -08:00
Arnavion fb10729218 Revert "Run watch sub-process and mocha with inherited stdio handles for colored output."
This reverts commit 11bd54014e. This isn't needed nor correct.
2014-01-26 10:47:28 -08:00
Arnavion 0b3fecb4ed libjass.css needs to be deployed too. 2014-01-25 11:26:53 -08:00
Arnavion 11bd54014e Run watch sub-process and mocha with inherited stdio handles for colored output. 2014-01-25 11:11:59 -08:00
Arnavion f25eec680b Render both getters and setters in documentation. 2014-01-23 18:25:19 -08:00
Arnavion bd4ea55c49 Split script properties into their own ScriptProperties class.
This is the beginning of the editable ASS object work to allow JS clients to edit the ASS properties, styles, dialogues, etc. after parsing the ASS object.
2014-01-22 23:03:13 -08:00
Arnavion 0b13f7d73b Fixed parser parsing comments as properties. 2014-01-22 22:42:11 -08:00
Arnavion 5ec09a2046 TS dev compiler doesn't like indexers on objects which don't have them defined. 2014-01-22 22:00:48 -08:00
Arnavion 893739e0a9 Renamed drawing instruction classes and added JSDoc for part properties. 2014-01-15 16:45:20 -08:00
Arnavion f82cd599ea Fixed typos in libjass.Style's JSDoc 2014-01-15 16:08:05 -08:00
Arnavion 5878fdc0f0 Implemented shadow support - \shad, \xshad, \yshad
Fixes #4
2014-01-15 15:57:58 -08:00
Arnavion 9a70f46c8e Renamed SpanStyles.outlineWidthX to outlineWidth, SpanStyles.outlineWidthY to outlineHeight, and Style.outlineWidth to outlineThickness
Fixes #8
2014-01-15 15:47:50 -08:00
Arnavion d026a88214 Allow multiple renderers to be used on the same page without conflict.
Renderers now have IDs, and dialogues, animation collections and SVG filters now have their IDs based on the renderer ID.

Fixes #1
2014-01-15 15:09:57 -08:00
Arnavion 5e25f4eedb More font definitions. 2014-01-15 14:57:15 -08:00
Arnavion ce071a8abd Fixed some implicit-any errors from development TS compiler. 2014-01-13 21:00:42 -08:00
Arnavion d8818f9a0c More font definitions. 2014-01-13 20:53:37 -08:00
Arnavion 8187072a4e Added license header to jakelib/*
Fixes #6
2014-01-10 19:14:50 -08:00
Arnavion c93caa5010 Drawing scale of {\p} was interpreted incorrectly.
Fixes #7
2014-01-10 19:12:01 -08:00
Arnavion 2efb871d3c Fixed whitespace. 2014-01-10 19:10:15 -08:00
Arnavion b5f9532717 Fixed various lint warnings from jslint and jshint.
- `Array(n)` to `new Array(n)`
- `typeof foo.bar === "undefined"` to `foo.bar === undefined`
- `if (foo) { return bar; } else if (foo2) { return bar2; }` to `if (foo) { return bar; } if (foo2) { return bar2; }`
- The two export var statements for libjass.Set and libjass.Map resulted in empty statements "libjass.Set;". Fixed by assigning null to them.
- Some instances of `==` and `!=` to `===` and `!==`
2014-01-09 23:36:36 -08:00
Arnavion 5b5c4e76b0 Moved DOM-related TS interface extensions from utility.ts to renderer.ts 2014-01-09 23:34:38 -08:00
Arnavion 2cc37f9dc0 Fixed loading module in node. 2014-01-09 23:34:37 -08:00
Arnavion d247af70f6 Replaced non-ASCII character in source with Unicode character code. 2014-01-06 02:25:19 -08:00
Arnavion b4f9125c42 Removed the use of eval to get the global this-object. 2013-12-28 22:02:33 -08:00
Arnavion a793ea2d3a Use UJS to align multi-line comments properly and strip TypeScript reference comments. 2013-12-28 21:52:03 -08:00
Arnavion cfacd9dd62 Use UJS to wrap code in a closure to prevent __extends from leaking out.
- Now all the "var libjass;" statements are stripped, as opposed to all but the first.
- All but the first "var __extends = ..." are now stripped. Consequently, it doesn't need to check for this.__extends either.
- Minification step now generates a warning about being unable to figure out a source mapping but it appears to be harmless.
2013-12-28 21:36:14 -08:00
Arnavion 79c70367dd Don't treat property names with two leading underscores as private names to be minified.
This was causing TypeScript's __extend to try to default to an arbitrary global property.
2013-12-28 20:20:23 -08:00
Arnavion 12aaf1b70f Added "@static" to the JSDoc of static functions. 2013-12-28 19:55:53 -08:00
Arnavion fa08da4607 Removed explicit "public" keyword for consistency. 2013-12-28 19:43:40 -08:00
Arnavion d23b9f94e6 One more typo in the JSDoc. 2013-12-28 19:43:07 -08:00
Arnavion 9cb08a3f67 Moved ASS.scaleTo functionality to DefaultRenderer.
Now everything related to rendering is contained within the renderer classes, and not in the ASS classes.
2013-12-28 19:38:30 -08:00
Arnavion 61e4632a51 Fixed typo in JSDoc and comment. 2013-12-28 19:37:50 -08:00
Arnavion 39986b546e Updated README. 2013-12-28 19:35:21 -08:00
Arnavion e2b0a1f0a5 Added and updated more JSDoc's. 2013-12-28 00:00:49 -08:00
Arnavion c5aa45ef66 Moved transform origin calculation from Dialogue to DefaultRenderer. 2013-12-27 23:58:09 -08:00
Arnavion c4b52a9df6 API cleanup.
- Renamed class Animation to Keyframe, AnimationPropertiesMap to KeyframePropertiesMap
- The constructors of classes AnimationCollection and SpanStyles now take in the Dialogue object itself instead of its properties.
2013-12-27 23:57:01 -08:00
Arnavion 7400ffc20f Span transforms should always be applied from the center, irrespective of the Dialogue's transform origin. 2013-12-27 23:56:41 -08:00
Arnavion 410b899820 A nicer way to create a proper RendererSettings from an arbitrary object. 2013-12-27 23:46:41 -08:00
Arnavion 9a278ce847 Removed RendererSettings.preLoadFonts
If RendererSettings.fontMap is set to non-null, then all fonts in the map are pre-loaded.
2013-12-27 23:42:21 -08:00
Arnavion 74bff2b045 Don't default the parser to parse Dialogue parts strings. 2013-12-27 23:33:07 -08:00
Arnavion 03a3bf0bf9 Added a checkbox to show/hide private things. 2013-12-27 23:32:12 -08:00
Arnavion 1987ea4ae8 Added support for @static on functions. 2013-12-27 23:31:42 -08:00
Arnavion 12b9764407 Removed unused branch. 2013-12-27 23:30:13 -08:00
Arnavion 43c02eac4e Treat all comments beginning with /** as JSDoc comments. 2013-12-27 23:29:41 -08:00
Arnavion b9c7b1a592 v0.4.0 2013-12-27 15:09:40 -08:00
Arnavion 8e34b1a3be Updated Mocha to 1.16.2 2013-12-27 14:59:14 -08:00
Arnavion ab17f15db6 Added doc support for properties defined using Object.defineProperty() 2013-12-27 14:59:13 -08:00
Arnavion f38bc04f00 Apply video size scaling to ASS drawing. 2013-12-20 13:03:59 -08:00
Arnavion a28efac134 Implemented ASS draw - m, l and b. 2013-12-20 12:40:52 -08:00
Arnavion 27192b1923 Set xmlns and version attributes on DefaultRenderer's SVG element. 2013-12-20 12:40:22 -08:00
Arnavion cece34bb97 Dialogues array was being sorted by start time instead of end time. 2013-12-20 12:39:33 -08:00
Arnavion 39d9f4a6cc Apply SVG filters on a wrapping span, so that CSS transforms don't affect the SVG transforms.
This fixes the outline and blur being scaled by the font-scaling parameters.
2013-12-20 11:59:32 -08:00
Arnavion 1990f9292f Only parse Dialogue parts when the Dialogue has to be pre-rendered to save on startup time.
As a side effect, the option to preload fonts now causes all the fonts in the font map to be preloaded, as opposed to the original behavior where only the fonts actually used in the ASS script would get preloaded.
2013-12-19 21:20:11 -08:00
Arnavion ac9e195259 Began implementing ASS draw. 2013-12-19 12:42:51 -08:00
Arnavion 4e30e24d7e Allow filters to compose normally. 2013-12-19 11:02:54 -08:00
Arnavion cbd267806b Use SVG filters for outlines and blur. 2013-12-19 00:35:42 -08:00
Arnavion c184d740fc Removed unnecessary CSS for .libjass-subs 2013-12-19 00:09:09 -08:00
Arnavion aab7e7686a More font defintions. 2013-12-19 00:09:08 -08:00
Arnavion 738097aa3f Removed DPI-related stuff.
This isn't the reason why the font sizes don't match libass/vsfilter.
2013-12-14 23:13:05 -08:00
Arnavion ee4dc2debe Use a CSS class for subs wrapper instead of id 2013-12-14 02:20:06 -08:00
Arnavion b24350b479 Split libjass.css off index.css
libjass.css is required with libjass.js, while the contents of index.css are specific to the example index.xhtml and index.js
2013-12-14 02:16:45 -08:00
Arnavion fb750feba3 Fixed dereferencing null when resetting to default style with {\r}. 2013-12-14 00:31:24 -08:00
Arnavion 98aee11520 CSS transforms don't work on inline elements. 2013-12-13 23:17:03 -08:00
Arnavion c818482698 Use toFixed(3) for text-shadow values. 2013-12-13 23:15:58 -08:00
Arnavion 05fc563fb7 More font definitions. 2013-12-13 23:14:55 -08:00
Arnavion 0f999d9c3d Trim overflowing subs. 2013-12-13 23:13:57 -08:00
Arnavion 9995e9dbef Fixed ordering of arguments to parts.Move() 2013-12-13 23:03:27 -08:00
Arnavion 3e3df2f2bb Updated TypeScript to 0.9.5 and Mocha to 1.15.1
- TypeScript.BatchCompiler is too inflexible. Use custom TypeScriptCompiler wrapper instead.
- Some more tweaks to satisfy the compiler.
2013-12-06 23:56:22 -08:00
Arnavion 85880708b7 Bold italic text was not being made bold. 2013-11-30 03:59:16 -08:00
Arnavion 1c2145b518 Alignment wrapper changes.
- Only create the required layer-alignment wrappers instead of creating all 9 wrappers per layer.
- Absolutely-positioned subs go in their own alignment layer because they must always be positioned from the top-left corner.
2013-11-30 03:48:00 -08:00
Arnavion 151ab46b4a Renderer improvements.
- Use binary search to look up dialogues by end time instead of linear scan.
- Store the subtitle div for currently visible dialogues so that removing them doesn't need expensive querySelector.
- Merged separate currentDialogues and newDialogues arrays into a single map of dialogue ID to subtitle div.
- Used Maps for EventListenersMap and PreRenderedSubsMap
- Miscellaneous cleanup
2013-11-29 23:26:20 -08:00
Arnavion 07c2d4616e SimpleMap improvements.
- Changed SimpleMap to hold two maps for keys and values respectively, instead of needing to convert properties back to keys.
- Implemented SimpleMap.delete() and SimpleMap.clear()
2013-11-29 23:26:18 -08:00
Arnavion 11cb6c99c3 Implemented SimpleSet.clear() 2013-11-29 23:26:17 -08:00
Arnavion 74ca49a76a Removed unused CSSStyleDeclaration.webkitPerspective field. 2013-11-29 23:24:55 -08:00
Arnavion 00a1289a94 Removed RendererSettings.useHighResolutionTimer because there's no good reason to not use it all the time. 2013-11-29 22:21:53 -08:00
Arnavion c972912a6b The span's should not be inline-block. It breaks multi-line spans.
The line height of a span can be enforced by forcing the parent div's line height to be the smallest possible value (0).
2013-11-28 03:00:46 -08:00
Arnavion e0842477e2 Set full-screen CSS class on wrapper div instead of document body. 2013-11-28 01:01:14 -08:00
Arnavion e20cb424f9 Fixed \fscx and \fscy resulting in huge text. 2013-11-28 00:43:11 -08:00
Arnavion e9ce7cf0f4 Made closing parenthesis optional in \t if immediately followed by a } or end-of-line. 2013-11-28 00:32:04 -08:00
Arnavion 41c787c5d2 Renamed parts.Tag to parts.Part and parts.TagBase to parts.PartBase 2013-11-28 00:25:40 -08:00
Arnavion 91837dccf7 Use short "font" CSS property for setting font name, font weight/style, font size and line height to reduce verbosity in DevTools. 2013-11-28 00:18:24 -08:00
Arnavion d6d2c9be6c Some more toFixed(3) for truncating long decimals in CSS properties. 2013-11-28 00:17:43 -08:00
Arnavion d0aae3a060 Support alpha values in \alpha, etc. that don't start with & 2013-11-28 00:16:44 -08:00
Arnavion 50a3abd90e Fixed line-height not being respected in unzoomed (script resolution) video. 2013-11-28 00:14:19 -08:00
Arnavion ee833e8638 Added parts.DrawingInstructions pseudo-part to represent drawing instructions. 2013-11-27 23:27:50 -08:00
Arnavion 96e006404f \move was missing specification for the terminal animation state. 2013-11-27 23:11:33 -08:00
Arnavion 0404e5bbe7 Replaced parts.HardSpace and parts.NewLine with parts.Text 2013-11-27 23:00:34 -08:00
Arnavion 8b0138a66f Implemented \fade
- More fiddling with AnimationCollection.
2013-11-23 18:19:37 -08:00
Arnavion 840c2353b9 Clear animation styles when clearing pre-rendered subs. 2013-11-23 18:18:59 -08:00
Arnavion 98efc87f04 Miscellaneous tiny things.
- Moved Webkit-specific keyframes declaration before generic declaration.
- Use fully-qualified type names in JSDoc
- Renamed *Karaoke.value to *Karaoke.duration
- Whitespace
2013-11-23 18:13:27 -08:00
Arnavion fed6ec8331 Renamed libjass.tags to libjass.parts 2013-11-19 20:36:26 -08:00
Arnavion 2568372923 Implemented \move
- Fixed \fad being applied using ease-in function instead of linear.
- Individual effects in a dialogue are now applied using separate animations instead of one gigantic one.
2013-11-19 11:19:44 -08:00
Arnavion d4cfe0169e Some JSDoc fixes. 2013-11-19 11:19:09 -08:00
Arnavion fe5eb70809 Return self instead of an identical clone from Color.withAlpha when the new alpha is null. 2013-11-19 11:18:19 -08:00
Arnavion 20191d30c9 Parameters of \move are in milliseconds. 2013-11-19 11:17:23 -08:00
Arnavion cb3cd43047 Implemented all ASS tags in the parser.
- Simple tag parser functions are now generated via a factory, leading to lesser boilerplate.
- Renamed libjass.tags.Blur to libjass.tags.GaussianBlur. libjass.tags.Blur now refers to the {\be} tag.
- Renamed libjass.tags.Pos to libjass.tags.Position
- Loosened the requirements of color and alpha tags based on a real-world script.
2013-11-19 00:26:42 -08:00
Arnavion ae04f8c35c More detailed logging of possibly incorrect parses. 2013-11-18 23:24:21 -08:00
Arnavion 63acb94037 Made parsing code less verbose. 2013-11-16 14:09:58 -08:00
Arnavion b90d92e8aa Removed unused function arguments. 2013-11-16 13:52:38 -08:00
Arnavion e79445f4e1 Parse \t 2013-11-10 19:50:01 -08:00
Arnavion ca90aff3e5 Treat empty comment and text parses as an error. 2013-11-10 19:47:59 -08:00
Arnavion 40e47f1b26 Removed last vestiges of PEG.js 2013-11-10 19:03:52 -08:00
Arnavion eda209430b Replaced PEG.js' generated parser with a much smaller, hand-written one. 2013-11-10 17:27:24 -08:00
Arnavion 98d75ba557 Print both the message and the stack of a parser exception. 2013-11-10 06:33:38 -08:00
Arnavion ba7bc66b30 Update mocha to ~1.14 2013-11-03 15:08:22 -08:00
Arnavion 42825b5c7e v0.3.0 2013-10-28 23:38:26 -07:00
Arnavion 1efb48cf41 Updated docs. 2013-10-28 23:35:18 -07:00
Arnavion 39066d868f Updated doc generator.
- Merged private and public API docs into one.
- Prettier output with hyperlinks.
2013-10-27 17:28:56 -07:00
Arnavion aeca930448 More JSDoc fixes. 2013-10-27 17:03:34 -07:00
Arnavion bb9ab8bd35 Normalize video events across browsers. 2013-10-21 02:02:56 -07:00
Arnavion f5fa07727a Removed LazySequence implementation. 2013-10-21 02:02:55 -07:00
Arnavion 49ce0c582e Invoke the timeupdate handler directly when seeking instead of via a browser event. 2013-10-21 00:19:26 -07:00
Arnavion f46b2ad419 Added a RendererSetting "useHighResolutionTimer" that selects a 41ms timer for the renderer, instead of video.timeupdate's 250ms timer. 2013-10-20 23:45:11 -07:00
Arnavion 36b421d4cb Prefix the video wrapper's CSS class with "libjass-" to avoid collisions. 2013-10-20 23:45:11 -07:00
Arnavion c6c6562592 Don't polyfill Iterator, Set, Map and StopIteration in the browser bcause that might interfere with other scripts on the page. 2013-10-20 18:12:46 -07:00
Arnavion d0b617b642 Fixed some more JSDoc issues discovered using Closure Compiler. 2013-10-20 18:12:45 -07:00
Arnavion 3c3b806a2b Changed libjass.removeElement into a private function DefaultRenderer._removeElement 2013-10-20 13:54:00 -07:00
Arnavion 10ffd48223 DefaultRenderer now creates the wrapper around the video element and the subtitle wrapper. 2013-10-20 13:48:07 -07:00
Arnavion 70da450a9c Don't use regex hacks to add and remove CSS classes. 2013-10-20 13:47:12 -07:00
Arnavion bf0555e6c2 Don't fire DefaultRenderer's ready event in the same tick as constructor. 2013-10-19 22:53:10 -07:00
Arnavion 4172b43558 Fixed some semi-bold fonts being labeled as bold instead. 2013-10-19 22:50:43 -07:00
Arnavion e1954a4609 More font definitions. 2013-10-13 17:45:57 -07:00
Arnavion 62fbbe9303 Support multiple fallback URLs when pre-loading fonts. 2013-10-13 17:45:15 -07:00
Arnavion 55e23dac3b IE throws an exception on dispatchEvent. Swallow it. 2013-10-13 17:43:35 -07:00
Arnavion c7146b9846 Made the subtitle wrapper absolutely positioned at (0, 0) in full-screen, because other elements in the DOM tree affect its position otherwise. 2013-10-12 16:27:50 -07:00
Arnavion fcab391f81 Firefox complains about setting numbers with exponents as CSS properties. Run all CSS length assignments through toFixed(3) 2013-10-12 16:27:49 -07:00
Arnavion 75614aa0f1 Added polyfill for Map and implemented DefaultRenderer's font map using it. 2013-10-11 05:29:00 -07:00
Arnavion bb9b69749e Optimization - JS engines don't optimize functions with try-catch blocks.
For a reduced test case (http://jsperf.com/try-catch-performance-overhead/14), there is a 40% speedup on Chrome 32 dev, 5% on IE11/Win7 and 800% on FF Nightly.
2013-10-06 21:22:48 -07:00
Arnavion d804e93ba2 Changed ass.styles to a map instead of an array, since lookup is the main operation for styles. 2013-10-06 20:30:37 -07:00
Arnavion b486bb6846 More font definitions. 2013-10-06 19:58:13 -07:00
Arnavion d01233f6ba Use "transform: translate(...)" instead of "position: relative" for rendering {\pos}.
This fixes a bug where the top property was ignored because the absolute wrapper has undefined height. It also obviates the need for the absolute wrapper.
2013-10-06 19:55:53 -07:00
Arnavion b953b6a05e Fixed incorrect scaling when taking letterboxing into account. 2013-10-06 19:55:11 -07:00
Arnavion 887b64d213 Also build sourcemap in watch task. 2013-10-05 00:33:59 -07:00
Arnavion 8bd8c2d498 Fixed build break. 2013-09-29 15:57:02 -07:00
Arnavion 822b663f1c Take letterboxing into account when resizing video. 2013-09-29 15:31:47 -07:00
Arnavion 9d2113f02e Created a NullRenderer that doesn't render anything. DefaultRenderer now inherits from NullRenderer and *does* render things. 2013-09-29 15:30:26 -07:00
Arnavion 9b9820c8b1 Add animation styles element dynamically. 2013-09-28 14:23:11 -07:00
Arnavion 1b0b2f0a45 Moved DefaultRenderer into libjass.renderers namespace. 2013-09-28 14:22:25 -07:00
Arnavion 2285cd25fd Added some JSDoc's. 2013-09-28 14:16:48 -07:00
Arnavion 2d5699728a Added license header to libjass.ts 2013-09-28 13:57:51 -07:00
Arnavion bfb837b422 Moved iterators into libjass.iterators namespace. 2013-09-28 13:54:29 -07:00
Arnavion 73629ce126 Moved Dialogue drawing code into DefaultRenderer and merged remaining dialogue.ts with original parser.ts into types.ts 2013-09-28 13:51:43 -07:00
Arnavion 23471f17f8 Added a jake task to generate API documentation.
It parses the JSDoc comments using UglifyJS and generates an XHTML file.
2013-09-28 00:20:03 -07:00
Arnavion 32e62092cf Added more JSDoc's. 2013-09-28 00:20:02 -07:00
Arnavion 56d9a5e072 Fixed another error that dev version of tsc complains about. 2013-09-26 12:12:34 -07:00
Arnavion 6311c8ddb2 Create TS compiler before waiting for changes so that first compilation is faster. 2013-09-21 12:07:04 -07:00
Arnavion 9361f1db81 Fixed some errors that dev version of tsc complains about. 2013-09-21 12:06:49 -07:00
Arnavion f79626f9e1 Added Travis build image to readme. 2013-09-20 23:05:32 -07:00
Arnavion caf3f99860 Added Travis CI file. 2013-09-20 22:57:53 -07:00
Arnavion 3c1d6e4e21 Skip the three failing {\b} tests for now. 2013-09-20 22:57:32 -07:00
Arnavion 65ff77c628 Test failures now cause the jake task to fail with a non-zero error code (except in watch mode). 2013-09-20 22:57:12 -07:00
Arnavion da46ad1964 Added watch task that compiles and runs tests. 2013-09-16 03:13:26 -07:00
Arnavion 5d40ba02ff Split tasks into separate files. 2013-09-16 02:49:13 -07:00
Arnavion 8004fc4f68 Minor JSDoc fixes. 2013-09-16 02:03:32 -07:00
Arnavion b00eef4335 Use jake.FileList to get test files. 2013-09-14 14:23:32 -07:00
Arnavion 5ef99ac871 Properly reset TypeScript compiler before each run. 2013-09-14 14:22:50 -07:00
38 changed files with 11358 additions and 3244 deletions
+3
View File
@@ -0,0 +1,3 @@
language: node_js
node_js:
- "0.10"
+16 -25
View File
@@ -4,37 +4,28 @@
1. Run the following command
npm install
```shell
npm install
```
This will install the dependencies - [Jake](https://github.com/mde/jake), [PEG.js](http://pegjs.majda.cz/), [TypeScript](http://www.typescriptlang.org/) and [UglifyJS2](https://github.com/mishoo/UglifyJS2). It will then run Jake to build libjass.js and use UglifyJS2 to minify it into libjass.min.js
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
<video id="video" src=" <URL OF VIDEO HERE> " controls="">
<track src=" <URL OF SCRIPT HERE> " kind="metadata" data-format="ass" />
</video>
```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
@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.
```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.
***
### Alternative ways to minify
* UglifyJS2 via command line
uglifyjs libjass.js --source-map libjass.min.js.map --in-source-map libjass.js.map --output libjass.min.js --mangle --compress
* Microsoft AJAX Minifer:
"C:\Program Files (x86)\Microsoft\Microsoft Ajax Minifier\ajaxmin.exe" libjass.js ass.pegjs.js -enc:in utf-8 -enc:out utf-8 -out libjass.min.js -comments:none -debug:false,console,libjass.debugMode,libjass.verboseMode -esc:true -inline:false -map:V3 libjass.min.js.map -strict:true
These commands will not preserve the license notice header in the minified file. Remember to prepend the license notice to libjass.min.js from libjass.js or any one of the TS files.
-455
View File
@@ -1,455 +0,0 @@
var fs = require("fs");
var path = require("path");
var UglifyJS = require("uglify-js");
namespace("_default", function () {
var tscJsResolvedPath = path.resolve("./node_modules/typescript/bin/tsc.js");
task("tscRequire", [], function () {
console.log("[" + this.fullName + "]");
var vm = require("vm");
var data = fs.readFileSync(tscJsResolvedPath, { encoding: "utf8" });
data =
data.substr(0, data.lastIndexOf("})(TypeScript || (TypeScript = {}));") + "})(TypeScript || (TypeScript = {}));".length) +
"module.exports = TypeScript;";
var TypeScript = {};
vm.runInNewContext(data, {
module: Object.defineProperty(Object.create(null), "exports", {
get: function () { return TypeScript; },
set: function (value) { TypeScript = value; }
}),
require: require,
process: process,
__filename: tscJsResolvedPath,
__dirname: path.dirname(tscJsResolvedPath)
});
return TypeScript;
});
task("tscCreate", ["_default:tscRequire"], function () {
console.log("[" + this.fullName + "]");
var TypeScript = jake.Task["_default:tscRequire"].value;
var compiler = new TypeScript.BatchCompiler({
arguments: [],
directoryExists: function (path) {
return fs.existsSync(path) && fs.statSync(path).isDirectory();
},
dirName: function (file) {
var result = path.dirname(file);
if (result === file) {
result = null;
}
return result;
},
fileExists: fs.existsSync.bind(fs),
getExecutingFilePath: function () {
return tscJsResolvedPath;
},
quit: function (code) {
if (code !== 0) {
throw new Error("TypeScript compiler exited with code " + code);
}
},
readFile: function (file, codepage) {
return {
contents: fs.readFileSync(file, { encoding: "utf8" }),
byteOrderMark: 0
};
},
resolvePath: path.resolve.bind(path),
stdout: {
Write: process.stdout.write.bind(process.stderr),
WriteLine: console.log.bind(console)
},
stderr: {
Write: process.stderr.write.bind(process.stderr),
WriteLine: console.error.bind(console)
},
writeFile: function (file, contents, writeByteOrderMark) {
file = path.relative(".", file);
output[file] = contents;
}
});
var output;
compiler.compilationSettings.codeGenTarget = TypeScript.LanguageVersion.EcmaScript5;
compiler.compilationSettings.mapSourceFiles = true;
compiler.compilationSettings.noImplicitAny = true;
return function (inputFilenames, outputFilename) {
output = Object.create(null);
compiler.inputFiles = inputFilenames;
compiler.compilationSettings.outFileOption = outputFilename;
try {
compiler.batchCompile();
}
catch (ex) {
if (ex instanceof Error) {
throw ex;
}
else {
throw new Error("Internal compiler error: " + ex.stack + "\n");
}
}
return output;
};
});
task("tscCompile", ["_default:tscCreate"], function () {
console.log("[" + this.fullName + "]");
var compile = jake.Task["_default:tscCreate"].value;
var output = compile(["libjass.ts"], "libjass.js");
return { code: output["libjass.js"], sourceMap: JSON.parse(output["libjass.js.map"]) };
});
task("pegjs", [], function () {
console.log("[" + this.fullName + "]");
var PEG = require("pegjs");
var data = fs.readFileSync("ass.pegjs", { encoding: "utf8" });
var parser = PEG.buildParser(data);
return "libjass.parser = " + parser.toSource();
});
task("combine", ["_default:tscCompile", "_default:pegjs"], function () {
console.log("[" + this.fullName + "]");
var compiled = jake.Task["_default:tscCompile"].value;
var parserSource = jake.Task["_default:pegjs"].value;
UglifyJS.base54.reset();
// Parse
var root = null;
root = UglifyJS.parse(compiled.code, {
filename: "libjass.js",
toplevel: root
});
root = UglifyJS.parse(parserSource, {
filename: "ass.pegjs.js",
toplevel: root
});
root.figure_out_scope();
// Remove some things from the AST
var nodesToRemove;
nodesToRemove = [];
// 1. All but the first top-level "var libjass;"
var firstVarLibjassFound = false;
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Var && node.definitions[0].name.name === "libjass") {
if (firstVarLibjassFound === false) {
firstVarLibjassFound = true;
}
else {
nodesToRemove.push({ node: node, parent: root.body });
}
}
}));
// Repeat because removing some declarations may make others unreferenced
for (;;) {
// 2. 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 (node) {
node.parent.splice(node.parent.indexOf(node.node), 1);
});
nodesToRemove = [];
root.figure_out_scope();
}
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: "libjass.js.map",
orig: compiled.sourceMap,
root: ""
}),
beautify: true,
comments: {
test: function (comment) {
if (comment.indexOf("Copyright") !== -1) {
if (!firstLicenseHeaderFound) {
firstLicenseHeaderFound = true;
return true;
}
else {
return false;
}
}
return true;
}
}
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
return {
code: stream.toString() + "\n//# sourceMappingURL=libjass.js.map",
sourceMap: JSON.parse(output.source_map.toString())
};
});
task("writeCode", ["_default:combine"], function () {
console.log("[" + this.fullName + "]");
var combined = jake.Task["_default:combine"].value;
fs.writeFileSync("libjass.js", combined.code);
});
task("writeSourceMap", ["_default:combine"], function () {
console.log("[" + this.fullName + "]");
var combined = jake.Task["_default:combine"].value;
fs.writeFileSync("libjass.js.map", JSON.stringify(combined.sourceMap));
});
task("minify", ["_default:combine"], function () {
console.log("[" + this.fullName + "]");
var combined = jake.Task["_default:combine"].value;
UglifyJS.base54.reset();
// Parse
var root = null;
root = UglifyJS.parse(combined.code, {
filename: "libjass.js",
toplevel: root
});
root.figure_out_scope();
// Suppress some warnings
var originalWarn = UglifyJS.AST_Node.warn;
UglifyJS.AST_Node.warn = function (text, properties) {
if (
(text === "{type} {name} is declared but not referenced [{file}:{line},{col}]" && properties.type === "Symbol" && properties.name === "offset" && properties.col === 39) ||
(text === "Eval is used [{file}:{line},{col}]" && properties.file === "libjass.js" && properties.line === 23)
) {
return;
}
originalWarn.call(UglifyJS.AST_Node, text, properties);
};
// Warnings
root.scope_warnings({
func_arguments: false
});
// Compress
var compressor = UglifyJS.Compressor({
warnings: true
});
root = root.transform(compressor);
// Mangle
root.figure_out_scope();
root.compute_char_frequency();
root.mangle_names();
// Mangle private members
var occurrences = Object.create(null);
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_PropAccess &&
typeof node.property === "string" &&
node.property.indexOf("_") === 0 &&
node.property !== "__iterator__"
) {
var occurrence = occurrences[node.property];
if (occurrence === undefined) {
occurrences[node.property] = 1;
}
else {
occurrences[node.property]++;
}
}
}));
var identifiers = Object.keys(occurrences);
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
var generatedIdentifiers = occurrences;
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
var toIdentifier = function (index) {
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
index = (index / validIdentifierCharacters.length) | 0;
while (index > 0) {
index--;
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
index = (index / validIdentifierCharacters.length) | 0;
}
return "_" + result;
};
identifiers.forEach(function (identifier, index) {
generatedIdentifiers[identifier] = toIdentifier(index);
});
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_PropAccess &&
typeof node.property === "string" &&
node.property in generatedIdentifiers
) {
node.property = generatedIdentifiers[node.property];
}
}));
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: "libjass.min.js.map",
orig: combined.sourceMap,
root: ""
}),
comments: {
test: function (comment) {
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
return true;
}
return false;
}
}
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
return {
code: stream.toString() + "\n//# sourceMappingURL=libjass.min.js.map",
sourceMap: JSON.parse(output.source_map.toString())
};
});
task("writeMinifiedCode", ["_default:minify"], function () {
console.log("[" + this.fullName + "]");
var minified = jake.Task["_default:minify"].value;
fs.writeFileSync("libjass.min.js", minified.code);
});
task("writeMinifiedSourceMap", ["_default:minify"], function () {
console.log("[" + this.fullName + "]");
var minified = jake.Task["_default:minify"].value;
fs.writeFileSync("libjass.min.js.map", JSON.stringify(minified.sourceMap));
});
});
desc("Build libjass.js, libjass.min.js and their sourcemaps");
task("default", ["_default:writeCode", "_default:writeSourceMap", "_default:writeMinifiedCode", "_default:writeMinifiedSourceMap"], function () {
console.log("[" + this.fullName + "]");
});
desc("Clean");
task("clean", [], function () {
console.log("[" + this.fullName + "]");
["libjass.js", "libjass.js.map", "libjass.min.js", "libjass.min.js.map"].forEach(function (file) {
try {
fs.unlinkSync(file);
}
catch (ex) {
if (ex.code !== "ENOENT") {
throw ex;
}
}
});
});
desc("Test");
task("test", [], function () {
var Mocha = require("mocha");
var mocha = new Mocha({
ui: "tdd"
});
fs.readdirSync("./tests/").filter(function (filename) {
if (filename.indexOf("test-") === 0) {
var extensionIndex = filename.lastIndexOf(".js");
if (extensionIndex !== -1 && extensionIndex === filename.length - ".js".length) {
return true;
}
}
return false;
}).forEach(function (filename) {
mocha.addFile("./tests/" + filename);
});
mocha.run();
// .\node_modules\.bin\mocha -u tdd tests/test*.js
});
+47 -23
View File
@@ -1,3 +1,5 @@
[![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.
@@ -16,68 +18,90 @@ As a result, libjass is able to render subtitles with very low CPU usage. The do
* The .ts files are the source of libjass. They are TypeScript files and must be compiled into JavaScript for the browser using the TypeScript compiler.
* The ass.pegjs file is the source of a parser for the ASS format. It is compiled to JavaScript along with the .ts files.
* 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?
1. You need to build libjass.js using the instructions in BUILD.md
1. You need to load libjass.js on the page with your video.
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 is 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.
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.
### Where's the API documentation? What API do I need to call to use libjass?
Formal documentation is coming soon. In the meantime, here's an overview:
The API documentation is linked in the Links section below. Here's an overview:
* The constructor ASS() takes in the raw ASS string and returns an object representing the script information, 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.
* 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.
* ASS.dialogues is an array of Dialogue objects, each corresponding to a dialogue in the ASS file. These objects have a draw() method that returns a &lt;div&gt; containing the rendered subtitle line.
* 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.
* index.js initializes a default renderer that libjass ships with, the 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 (#subs) containing div's corresponding to the 9 alignment directions, 9 for each layer in the ASS script. index.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 listens for the video element's "timeupdate" event. In the event handler, it determines the set of dialogues to be shown, calls draw() on each of them, and appendChild's the result into the appropriate layer+alignment div. It only does this if the dialogue has not already been drawn by a previous timeupdate.
* 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.
* The default renderer handles resizing the video and subs when the user clicks the browser's native fullscreen-video button. index.js also contains code to change the size of the video 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 - index.js creates this map from the @font-face rules in fonts.css.
* Lastly, the renderer contains an implementation of preloading all the fonts used in the ASS file. It uses a map of font names to URLs provided by index.js - this map is contained in fonts.css in the form of @font-family rules.
### Can I use libjass in node?
libjass's parser works in node.
```javascript
> var libjass = require("./libjass.js")
undefined
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
undefined
> parts.join(" ")
'Alignment { value: 8 } Text { value: Are } Italic { value: true } Text { value: you } Italic { value: false } Text { value: the one who stole the clock?! }'
> parts.length
6
> parts[0].toString()
'Alignment { value: 8 }'
> parts[0] instanceof libjass.parts.Alignment
true
> parts[0].value
8
```
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun). For example, [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.ParserRun.parse_dialogueParts) can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
### Can I contribute?
Yes! Feature requests, suggestions, bug reports and pull requests are welcome! I'm especially looking for details and edge-cases of the ASS syntax that libjass doesn't support.
You can also hop by the IRC channel below and ask any questions.
You can also join the IRC channel below and ask any questions.
## Links
### Links
* [GitHub](https://github.com/Arnavion/libjass/)
* IRC channel - #libjass on irc.rizon.net
* [API documentation](http://arnavion.github.io/libjass/api.xhtml)
* [Aegisub's documentation on ASS](http://docs.aegisub.org/3.0/ASS_Tags/)
## Supported features
### Supported features
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, Outline, Alignment, MarginL, MarginR, MarginV
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fax, \fay, \c, \1c, \3c, \alpha, \1a, \3a, \an, \r, \pos, \fad
* 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 bugs
### Known issues
* Unsupported tags: \be, \fe, \2c, \2a, \k, \K, \kf, \ko, \q, \org, \t, \clip, \iclip
* \an4, \an5, \an6 aren't positioned correctly.
* Unsupported tags: Everything else, notably \t.
* Font sizes aren't pixel perfect.
* \blur uses an approximation instead of Gaussian blur.
* ASS draw is unsupported.
* 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
### 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.
@@ -85,7 +109,7 @@ You can also hop by the IRC channel below and ask any questions.
* Evaluate (document, benchmark) the benefits and drawbacks of DOM+CSS-based drawing over canvas.
# License
### License
```
libjass
-438
View File
@@ -1,438 +0,0 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
var scriptTemplate = Object.create(null);
var currentSectionContents;
var currentFormatSpecifier;
}
script
= sections:(script_section eol*)* {
return scriptTemplate;
}
script_section
= name:script_sectionHeader (
script_comment /
script_property /
eol
)* {
scriptTemplate[name] = currentSectionContents;
}
script_sectionHeader
= "[" name:[^\]\n]+ "]" eol? {
currentSectionContents = null;
currentFormatSpecifier = null;
return name.join("");
}
script_comment
= ";" [^\n]+ eol?
script_property
= key:[^:\n]+ ":" " "* value:[^\n]+ eol? {
key = key.join("");
value = value.join("");
if (key === "Format") {
currentFormatSpecifier = value.split(",").map(function (formatPart) { return formatPart.trim(); });
}
else if (currentFormatSpecifier !== null) {
if (currentSectionContents === null) {
currentSectionContents = [];
}
var template = Object.create(null);
value = value.split(",");
if (value.length > currentFormatSpecifier.length) {
value[currentFormatSpecifier.length - 1] = value.slice(currentFormatSpecifier.length - 1).join(",");
}
currentFormatSpecifier.forEach(function (formatKey, index) {
template[formatKey] = value[index];
});
currentSectionContents.push({ type: key, template: template });
}
else {
if (currentSectionContents === null) {
currentSectionContents = Object.create(null);
}
currentSectionContents[key] = value;
}
return "";
}
eol
= "\n"
dialogueParts
= parts:(enclosedTags / comment / newline / hardspace / text)* {
// Flatten parts
parts = parts.reduce(function (previous, current) {
return previous.concat(current);
}, []);
// Merge consecutive text and comment parts into one part
parts = parts.reduce(function (previous, current) {
if (current instanceof libjass.tags.Text && previous[previous.length - 1] instanceof libjass.tags.Text) {
previous[previous.length - 1] = new libjass.tags.Text(previous[previous.length - 1].value + current.value);
}
else if (current instanceof libjass.tags.Comment && previous[previous.length - 1] instanceof libjass.tags.Comment) {
previous[previous.length - 1] = new libjass.tags.Comment(previous[previous.length - 1].value + current.value);
}
else {
previous.push(current);
}
return previous;
}, []);
return parts;
}
enclosedTags
= "{" tagsWithSlashes:(
"\\" tag_alpha /
"\\" tag_xbord /
"\\" tag_ybord /
"\\" tag_bord /
"\\" tag_blur /
"\\" tag_fscx /
"\\" tag_fscy /
"\\" tag_fsp /
"\\" tag_frx /
"\\" tag_fry /
"\\" tag_frz /
"\\" tag_fax /
"\\" tag_fay /
"\\" tag_pos /
"\\" tag_fad /
"\\" tag_fn /
"\\" tag_fs /
"\\" tag_1c /
"\\" tag_3c /
"\\" tag_1a /
"\\" tag_3a /
"\\" tag_an /
"\\" tag_i /
"\\" tag_b /
"\\" tag_u /
"\\" tag_s /
"\\" tag_c /
"\\" tag_r
)+
"}" {
return tagsWithSlashes.map(function (tagWithSlash) { return tagWithSlash[1]; });
}
comment
= "{" value:[^}]* "}" {
return new libjass.tags.Comment(
value.join("")
);
}
newline
= "\\N" {
return new libjass.tags.NewLine();
}
hardspace
= "\\h" {
return new libjass.tags.HardSpace();
}
text
= value:. {
return new libjass.tags.Text(
value
);
}
tag_i
= "i" value:enableDisable? {
return new libjass.tags.Italic(
(value !== "") ? value : null
);
}
tag_b
= "b" value:(([1-9] "0" "0") / "1" / "0")? {
if (Array.isArray(value)) {
value = value.join("");
}
switch (value) {
case "1":
return new libjass.tags.Bold(true);
case "0":
return new libjass.tags.Bold(false);
case "":
return new libjass.tags.Bold(null);
default:
return new libjass.tags.Bold(parseInt(value));
}
}
tag_u
= "u" value:enableDisable? {
return new libjass.tags.Underline(
(value !== "") ? value : null
);
}
tag_s
= "s" value:enableDisable? {
return new libjass.tags.StrikeThrough(
(value !== "") ? value : null
);
}
tag_bord
= "bord" value:decimal? {
return new libjass.tags.Border(
(value !== "") ? value : null
);
}
tag_xbord
= "bord" value:decimal? {
return new libjass.tags.BorderX(
(value !== "") ? value : null
);
}
tag_ybord
= "bord" value:decimal? {
return new libjass.tags.BorderY(
(value !== "") ? value : null
);
}
tag_blur
= "blur" value:decimal? {
return new libjass.tags.Blur(
(value !== "") ? value : null
);
}
tag_fn
= "fn" value:[^\\}]* {
return new libjass.tags.FontName(
(value.length > 0) ? value.join("") : null
);
}
tag_fs
= "fs" value:decimal? {
return new libjass.tags.FontSize(
(value !== "") ? value : null
);
}
tag_fscx
= "fscx" value:decimal? {
return new libjass.tags.FontScaleX(
(value !== "") ? (value / 100) : null
);
}
tag_fscy
= "fscy" value:decimal? {
return new libjass.tags.FontScaleY(
(value !== "") ? (value / 100) : null
);
}
tag_fsp
= "fsp" value:decimal? {
return new libjass.tags.LetterSpacing(
(value !== "") ? value : null
);
}
tag_frx
= "frx" value:decimal? {
return new libjass.tags.RotateX(
(value !== "") ? value : null
);
}
tag_fry
= "fry" value:decimal? {
return new libjass.tags.RotateY(
(value !== "") ? value : null
);
}
tag_frz
= "frz" value:decimal? {
return new libjass.tags.RotateZ(
(value !== "") ? value : null
);
}
tag_fax
= "fax" value:decimal? {
return new libjass.tags.SkewX(
(value !== "") ? value : null
);
}
tag_fay
= "fay" value:decimal? {
return new libjass.tags.SkewY(
(value !== "") ? value : null
);
}
tag_1c
= "1c" value:color? {
return new libjass.tags.PrimaryColor(
(value !== "") ? value : null
);
}
tag_c
= "c" value:color? {
return new libjass.tags.PrimaryColor(
(value !== "") ? value : null
);
}
tag_3c
= "3c" value:color? {
return new libjass.tags.OutlineColor(
(value !== "") ? value : null
);
}
tag_alpha
= "alpha" value:alpha? {
return new libjass.tags.Alpha(
(value !== "") ? value : null
);
}
tag_1a
= "1a" value:alpha? {
return new libjass.tags.PrimaryAlpha(
(value !== "") ? value : null
);
}
tag_3a
= "3a" value:alpha? {
return new libjass.tags.OutlineAlpha(
(value !== "") ? value : null
);
}
tag_an
= "an" value:[1-9] {
return new libjass.tags.Alignment(
parseInt(value)
);
}
tag_r
= "r" value:[^\\}]* {
return new libjass.tags.Reset(
(value.length > 0) ? value.join("") : null
);
}
tag_pos
= "pos(" x:decimal "," y:decimal ")" {
return new libjass.tags.Pos(
x,
y
);
}
tag_fad
= "fad(" start:decimal "," end:decimal ")" {
return new libjass.tags.Fade(
parseFloat(start) / 1000,
parseFloat(end) / 1000
);
}
decimal
= sign:"-"? unsignedDecimal:unsignedDecimal {
return (sign === "") ? unsignedDecimal : -unsignedDecimal;
}
unsignedDecimal
= characteristic:[0-9]+ mantissa:("." [0-9]+)? {
return parseFloat(
characteristic.join("") +
(mantissa[0] || "") + (mantissa[1] && mantissa[1].join("") || "")
);
}
enableDisable
= value:("0" / "1") {
switch (value) {
case "0":
return false;
case "1":
return true;
}
}
hex
= [0-9a-fA-F]
color
= "&H" blue:(hex hex) green:(hex hex) red:(hex hex) "&" {
return new libjass.tags.Color(
parseInt(red.join(""), 16),
parseInt(green.join(""), 16),
parseInt(blue.join(""), 16)
);
}
alpha
= "&H" value:(hex hex) "&" {
return 1 - parseInt(value.join(""), 16) / 255;
}
colorWithAlpha
= "&H" alpha:(hex hex) blue:(hex hex) green:(hex hex) red:(hex hex) {
return new libjass.tags.Color(
parseInt(red.join(""), 16),
parseInt(green.join(""), 16),
parseInt(blue.join(""), 16),
1 - parseInt(alpha.join(""), 16) / 255
);
}
-662
View File
@@ -1,662 +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.
*/
///<reference path="libjass.ts" />
module libjass {
export class Dialogue {
private static _lastDialogueId = -1;
private static _animationStyleElement: HTMLStyleElement = null;
private _id: number;
private _style: Style;
private _start: number;
private _end: number;
private _layer: number;
private _alignment: number;
private _transformOrigin: string;
private _parts: tags.Tag[];
private _sub: HTMLDivElement = null;
constructor(template: Object, private _ass: ASS) {
this._id = ++Dialogue._lastDialogueId;
this._style = this._ass.styles.filter(aStyle => aStyle.name === template["Style"])[0];
this._start = Dialogue._toTime(template["Start"]);
this._end = Dialogue._toTime(template["End"]);
this._layer = Math.max(parseInt(template["Layer"]), 0);
this._alignment = this._style.alignment;
this._setTransformOrigin();
this._parts = <tags.Tag[]>parser.parse(template["Text"], "dialogueParts");
if (libjass.debugMode) {
if (this._parts.some(part => part instanceof tags.Comment && (<tags.Comment>part).value.indexOf("\\") !== -1)) {
console.warn("Possible incorrect parse: " + this.toString());
}
}
}
get id(): number {
return this._id;
}
get start(): number {
return this._start;
}
get end(): number {
return this._end;
}
get alignment(): number {
return this._alignment;
}
get layer(): number {
return this._layer;
}
get parts(): tags.Tag[] {
return this._parts;
}
/**
* The magic happens here. The subtitle div is rendered and stored. Call draw() to get a clone of the div to display.
*/
preRender(): void {
if (this._sub === null) {
this._preRender();
}
}
/**
* Discards the pre-rendered subtitle div created from an earlier call to preRender().
*/
unPreRender(): void {
this._sub = null;
}
/**
* Returns the subtitle div for display. The currentTime is used to shift the animations appropriately, so that at the time the
* div is inserted into the DOM and the animations begin, they are in sync with the video time.
*
* @param {number} currentTime
* @return {!HTMLDivElement}
*/
draw(currentTime: number): HTMLDivElement {
if (this._sub === null) {
if (libjass.debugMode) {
console.warn("This dialogue was not pre-rendered. Call preRender() before calling draw() so that draw() is faster.");
}
this._preRender();
}
var sub = <HTMLDivElement>this._sub.cloneNode(true);
var animationEndCallback: () => void = () => removeElement(sub);
sub.style.webkitAnimationDelay = (this._start - currentTime) + "s";
sub.addEventListener("webkitAnimationEnd", animationEndCallback, false);
sub.style.animationDelay = (this._start - currentTime) + "s";
sub.addEventListener("animationend", animationEndCallback, false);
return sub;
}
/**
* @return {string}
*/
toString(): string {
return "#" + this._id + " [" + this._start.toFixed(3) + "-" + this._end.toFixed(3) + "] " + this._parts.join(", ");
}
/**
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
*
* @param {string} string
* @return {number}
*/
private static _toTime(str: string): number {
return str.split(":").reduce((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
private static _valueOrDefault = <T>(newValue: T, defaultValue: T): T => ((newValue !== null) ? newValue : defaultValue);
private _preRender(): void {
var sub = document.createElement("div");
// Create an animation if there is a part that requires it
var keyframes = new KeyframeCollection(this._id, this._start, this._end);
this._parts.forEach(part => {
if (part instanceof tags.Alignment) {
this._alignment = (<tags.Alignment>part).value;
this._setTransformOrigin();
}
else if (part instanceof tags.Fade) {
var fadePart = <tags.Fade>part;
if (fadePart.start !== 0) {
keyframes.add(this._start, "opacity", "0");
keyframes.add(this._start + fadePart.start, "opacity", "1");
}
if (fadePart.end !== 0) {
keyframes.add(this._end - fadePart.end, "opacity", "1");
keyframes.add(this._end, "opacity", "0");
}
}
});
if (Dialogue._animationStyleElement === null) {
Dialogue._animationStyleElement = <HTMLStyleElement>document.querySelector("#animation-styles");
}
Dialogue._animationStyleElement.appendChild(document.createTextNode(keyframes.toString()));
var scaleX = this._ass.scaleX;
var scaleY = this._ass.scaleY;
var dpi = this._ass.dpi;
sub.style.webkitAnimationName = "dialogue-" + this._id;
sub.style.webkitAnimationDuration = (this._end - this._start) + "s";
sub.style.animationName = "dialogue-" + this._id;
sub.style.animationDuration = (this._end - this._start) + "s";
sub.style.marginLeft = (scaleX * this._style.marginLeft) + "px";
sub.style.marginRight = (scaleX * this._style.marginRight) + "px";
sub.style.marginTop = sub.style.marginBottom = (scaleY * this._style.marginVertical) + "px";
var divTransformStyle = "";
var currentSpan: HTMLSpanElement = null;
var currentSpanStyles = new SpanStyles(this._style, this._transformOrigin, scaleX, scaleY, dpi);
var startNewSpan = (): void => {
if (currentSpan !== null) {
currentSpanStyles.setStylesOnSpan(currentSpan);
sub.appendChild(currentSpan);
}
currentSpan = document.createElement("span");
};
startNewSpan();
this._parts.forEach(part => {
if (part instanceof tags.Italic) {
currentSpanStyles.italic = (<tags.Italic>part).value;
}
else if (part instanceof tags.Bold) {
currentSpanStyles.bold = (<tags.Bold>part).value;
}
else if (part instanceof tags.Underline) {
currentSpanStyles.underline = (<tags.Underline>part).value;
}
else if (part instanceof tags.StrikeThrough) {
currentSpanStyles.strikeThrough = (<tags.StrikeThrough>part).value;
}
else if (part instanceof tags.Border) {
currentSpanStyles.outlineWidthX = (<tags.Border>part).value;
currentSpanStyles.outlineWidthY = (<tags.Border>part).value;
}
else if (part instanceof tags.BorderX) {
currentSpanStyles.outlineWidthX = (<tags.BorderX>part).value;
}
else if (part instanceof tags.BorderY) {
currentSpanStyles.outlineWidthY = (<tags.BorderY>part).value;
}
else if (part instanceof tags.Blur) {
currentSpanStyles.blur = (<tags.Blur>part).value;
}
else if (part instanceof tags.FontName) {
currentSpanStyles.fontName = (<tags.FontName>part).value;
}
else if (part instanceof tags.FontSize) {
currentSpanStyles.fontSize = (<tags.FontSize>part).value;
}
else if (part instanceof tags.FontScaleX) {
currentSpanStyles.fontScaleX = (<tags.FontScaleX>part).value;
}
else if (part instanceof tags.FontScaleY) {
currentSpanStyles.fontScaleY = (<tags.FontScaleY>part).value;
}
else if (part instanceof tags.LetterSpacing) {
currentSpanStyles.letterSpacing = (<tags.LetterSpacing>part).value;
}
else if (part instanceof tags.RotateX) {
divTransformStyle += " rotateX(" + (<tags.RotateX>part).value + "deg)";
}
else if (part instanceof tags.RotateY) {
divTransformStyle += " rotateY(" + (<tags.RotateY>part).value + "deg)";
}
else if (part instanceof tags.RotateZ) {
divTransformStyle += " rotateZ(" + (-1 * (<tags.RotateZ>part).value) + "deg)";
}
else if (part instanceof tags.SkewX) {
divTransformStyle += " skewX(" + (45 * (<tags.SkewX>part).value) + "deg)";
}
else if (part instanceof tags.SkewY) {
divTransformStyle += " skewY(" + (45 * (<tags.SkewY>part).value) + "deg)";
}
else if (part instanceof tags.PrimaryColor) {
currentSpanStyles.primaryColor = (<tags.PrimaryColor>part).value;
}
else if (part instanceof tags.OutlineColor) {
currentSpanStyles.outlineColor = (<tags.OutlineColor>part).value;
}
else if (part instanceof tags.Alpha) {
currentSpanStyles.primaryAlpha = (<tags.Alpha>part).value;
currentSpanStyles.outlineAlpha = (<tags.Alpha>part).value;
}
else if (part instanceof tags.PrimaryAlpha) {
currentSpanStyles.primaryAlpha = (<tags.PrimaryAlpha>part).value;
}
else if (part instanceof tags.OutlineAlpha) {
currentSpanStyles.outlineAlpha = (<tags.OutlineAlpha>part).value;
}
else if (part instanceof tags.Alignment) {
// Already handled at the beginning of draw()
}
else if (part instanceof tags.Reset) {
var newStyleName = (<tags.Reset>part).value;
var newStyle: Style = null;
if (newStyleName !== null) {
newStyle = this._ass.styles.filter(style => style.name === newStyleName)[0];
}
currentSpanStyles.reset(newStyle);
}
else if (part instanceof tags.Pos) {
// Will be handled at the end of draw()
}
else if (part instanceof tags.Fade) {
// Already handled at the beginning of draw()
}
else if (part instanceof tags.NewLine) {
sub.appendChild(document.createElement("br"));
}
else if (part instanceof tags.HardSpace) {
currentSpan.appendChild(document.createTextNode("\u00A0"));
startNewSpan();
}
else if (part instanceof tags.Text || (libjass.debugMode && part instanceof tags.Comment)) {
currentSpan.appendChild(document.createTextNode((<tags.Text>part).value));
startNewSpan();
}
});
if (divTransformStyle) {
sub.style.webkitTransform = divTransformStyle;
sub.style.webkitTransformOrigin = this._transformOrigin;
sub.style.transform = divTransformStyle;
sub.style.transformOrigin = this._transformOrigin;
}
this._parts.some(part => {
if (part instanceof tags.Pos) {
var posPart = <tags.Pos>part;
var absoluteWrapper = document.createElement("div");
absoluteWrapper.style.position = "absolute";
absoluteWrapper.style.left = (scaleX * posPart.x) + "px";
absoluteWrapper.style.top = (scaleY * posPart.y) + "px";
sub.style.position = "relative";
var relativeTop: number;
var relativeLeft: number;
switch (this._alignment) {
case 1: relativeLeft = 0; relativeTop = -100; break;
case 2: relativeLeft = -50; relativeTop = -100; break;
case 3: relativeLeft = -100; relativeTop = -100; break;
case 4: relativeLeft = 0; relativeTop = -50; break;
case 5: relativeLeft = -50; relativeTop = -50; break;
case 6: relativeLeft = -100; relativeTop = -50; break;
case 7: relativeLeft = 0; relativeTop = 0; break;
case 8: relativeLeft = -50; relativeTop = 0; break;
case 9: relativeLeft = -100; relativeTop = 0; break;
}
sub.style.left = relativeLeft + "%";
sub.style.top = relativeTop + "%";
absoluteWrapper.appendChild(sub);
sub = absoluteWrapper;
return true;
}
return false;
});
sub.setAttribute("data-dialogue-id", String(this._id));
this._sub = sub;
}
private _setTransformOrigin(): void {
var transformOriginX: number;
var transformOriginY: number;
switch (this._alignment) {
case 1: transformOriginX = 0; transformOriginY = 100; break;
case 2: transformOriginX = 50; transformOriginY = 100; break;
case 3: transformOriginX = 100; transformOriginY = 100; break;
case 4: transformOriginX = 0; transformOriginY = 50; break;
case 5: transformOriginX = 50; transformOriginY = 50; break;
case 6: transformOriginX = 100; transformOriginY = 50; break;
case 7: transformOriginX = 0; transformOriginY = 0; break;
case 8: transformOriginX = 50; transformOriginY = 0; break;
case 9: transformOriginX = 100; transformOriginY = 0; break;
}
this._transformOrigin = transformOriginX + "% " + transformOriginY + "%";
}
}
class KeyframeCollection {
/** @type {!Object.<string, !Object.<string, string>>} */
private _keyframes: Object = Object.create(null);
constructor(private _id: number, private _start: number, private _end: number) { }
/**
* @param {number} time
* @param {string} property
* @param {string} value
*/
add(time: number, property: string, value: string) {
var step = (100 * (time - this._start) / (this._end - this._start)) + "%";
this._keyframes[step] = this._keyframes[step] || {};
this._keyframes[step][property] = value;
}
/**
* Creates a CSS3 animations representation of this keyframe collection.
*
* @return {string}
*/
toString(): string {
var result = "";
var steps = Object.keys(this._keyframes);
if (steps.length > 0) {
var cssText = "";
steps.forEach(step => {
cssText += "\t" + step + " {\n";
var properties: Object = this._keyframes[step];
Object.keys(properties).forEach(property => {
cssText += "\t\t" + property + ": " + properties[property] + ";\n";
});
cssText += "\t}\n";
});
result =
"@-webkit-keyframes dialogue-" + this._id + " {\n" + cssText + "}\n\n" +
"@keyframes dialogue-" + this._id + " {\n" + cssText + "}\n\n";
}
return result;
}
}
class SpanStyles {
private _italic: boolean;
private _bold: Object;
private _underline: boolean;
private _strikeThrough: boolean;
private _outlineWidthX: number;
private _outlineWidthY: number;
private _fontName: string;
private _fontSize: number;
private _fontScaleX: number;
private _fontScaleY: number;
private _letterSpacing: number;
private _primaryColor: tags.Color;
private _outlineColor: tags.Color;
private _primaryAlpha: number;
private _outlineAlpha: number;
private _blur: number;
constructor(private _style: Style, private _transformOrigin: string, private _scaleX: number, private _scaleY: number, private _dpi: number) {
this.reset();
}
reset(newStyle: Style = this._style): void {
this.italic = newStyle.italic;
this.bold = newStyle.bold;
this.underline = newStyle.underline;
this.strikeThrough = newStyle.strikeThrough;
this.outlineWidthX = newStyle.outlineWidth;
this.outlineWidthY = newStyle.outlineWidth;
this.fontName = newStyle.fontName;
this.fontSize = newStyle.fontSize;
this.fontScaleX = newStyle.fontScaleX;
this.fontScaleY = newStyle.fontScaleY;
this.letterSpacing = newStyle.letterSpacing;
this.primaryColor = newStyle.primaryColor;
this.outlineColor = newStyle.outlineColor;
this.primaryAlpha = null;
this.outlineAlpha = null;
this.blur = null;
}
setStylesOnSpan(span: HTMLSpanElement): void {
if (this._italic) {
span.style.fontStyle = "italic";
}
if (this._bold === true) {
span.style.fontWeight = "bold";
}
else if (this._bold !== false) {
span.style.fontWeight = <string>this._bold;
}
var textDecoration = "";
if (this._underline) {
textDecoration = "underline";
}
if (this._strikeThrough) {
textDecoration += " line-through";
}
span.style.textDecoration = textDecoration.trim();
span.style.fontFamily = this._fontName;
span.style.fontSize = span.style.lineHeight = ((72 / this._dpi) * this._scaleY * this._fontSize) + "px";
span.style.webkitTransform = "scaleX(" + this._fontScaleX + ") scaleY(" + this._fontScaleY + ")";
span.style.webkitTransformOrigin = this._transformOrigin;
span.style.transform = "scaleX(" + this._fontScaleX + ") scaleY(" + this._fontScaleY + ")";
span.style.transformOrigin = this._transformOrigin;
span.style.letterSpacing = (this._scaleX * this._letterSpacing) + "px";
span.style.color = this._primaryColor.withAlpha(this._primaryAlpha).toString();
if (this._outlineWidthX > 0 || this._outlineWidthY > 0) {
var textShadowColor = this._outlineColor.withAlpha(this._outlineAlpha).toString();
var textShadowParts: number[][] = [];
/* Lay out text-shadows in an ellipse with horizontal radius = this._scaleX * this._outlineWidthX
* and vertical radius = this._scaleY * this._outlineWidthY
* Shadows are laid inside the region of the ellipse, separated by 0.5px
*
* The below loop is an unrolled version of the above algorithm that only roams over one quadrant and adds
* four shadows at a time.
*/
var a = this._scaleX * this._outlineWidthX;
var b = this._scaleY * this._outlineWidthY;
for (var x = 0; x < a; x += 0.5) {
for (var y = 0; y < b && ((x / a) * (x / a) + (y / b) * (y / b)) <= 1; y += 0.5) {
textShadowParts.push([x, y, this._scaleX * this._blur]);
if (x !== 0) {
textShadowParts.push([-x, y, this._scaleX * this._blur]);
}
if (x !== 0 && y !== 0) {
textShadowParts.push([-x, -y, this._scaleY * this._blur]);
}
if (y !== 0) {
textShadowParts.push([x, -y, this._scaleY * this._blur]);
}
}
}
// Make sure the four corner shadows exist
textShadowParts.push(
[a, 0, this._scaleX * this._blur],
[0, b, this._scaleX * this._blur],
[-a, 0, this._scaleY * this._blur],
[0, -b, this._scaleY * this._blur]
);
span.style.textShadow =
textShadowParts
.map(triple => triple[0] + "px " + triple[1] + "px " + triple[2] + "px " + textShadowColor)
.join(", ");
}
else if (this._blur > 0) {
// TODO: Blur text
}
}
set italic(value: boolean) {
this._italic = SpanStyles._valueOrDefault(value, this._style.italic);
}
set bold(value: Object) {
this._bold = SpanStyles._valueOrDefault(value, this._style.bold);
}
set underline(value: boolean) {
this._underline = SpanStyles._valueOrDefault(value, this._style.underline);
}
set strikeThrough(value: boolean) {
this._strikeThrough = SpanStyles._valueOrDefault(value, this._style.strikeThrough);
}
set outlineWidthX(value: number) {
this._outlineWidthX = SpanStyles._valueOrDefault(value, this._style.outlineWidth);
}
set outlineWidthY(value: number) {
this._outlineWidthY = SpanStyles._valueOrDefault(value, this._style.outlineWidth);
}
set blur(value: number) {
this._blur = SpanStyles._valueOrDefault(value, 0);
}
set fontName(value: string) {
this._fontName = SpanStyles._valueOrDefault(value, this._style.fontName);
}
set fontSize(value: number) {
this._fontSize = SpanStyles._valueOrDefault(value, this._style.fontSize);
}
set fontScaleX(value: number) {
this._fontScaleX = SpanStyles._valueOrDefault(value, this._style.fontScaleX);
}
set fontScaleY(value: number) {
this._fontScaleY = SpanStyles._valueOrDefault(value, this._style.fontScaleY);
}
set letterSpacing(value: number) {
this._letterSpacing = SpanStyles._valueOrDefault(value, this._style.letterSpacing);
}
set primaryColor(value: tags.Color) {
this._primaryColor = SpanStyles._valueOrDefault(value, this._style.primaryColor);
}
set outlineColor(value: tags.Color) {
this._outlineColor = SpanStyles._valueOrDefault(value, this._style.outlineColor);
}
set primaryAlpha(value: number) {
this._primaryAlpha = value;
}
set outlineAlpha(value: number) {
this._outlineAlpha = value;
}
private static _valueOrDefault = <T>(newValue: T, defaultValue: T): T => ((newValue !== null) ? newValue : defaultValue);
}
}
+665 -7
View File
@@ -1,8 +1,50 @@
@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");
@@ -13,11 +55,90 @@
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");
@@ -33,6 +154,11 @@
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");
@@ -41,6 +167,13 @@
@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 {
@@ -48,49 +181,380 @@
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: "LTFinnegan Medium";
src: url("/fonts/LT.ttf");
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_3italic.ttf");
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;
}
@@ -99,28 +563,157 @@
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: "PaintyPaint";
src: url("/fonts/PAINP.TTF");
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_0.TTF");
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";
font-family: "Profile-Regular";
src: url("/fonts/Profile-Regular.otf");
}
@@ -129,8 +722,73 @@
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");
}
+130
View File
@@ -0,0 +1,130 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require("fs");
var path = require("path");
var gulp = require("gulp");
var Vinyl = require("vinyl");
var helpers = require("./gulplib/helpers.js");
var Transform = helpers.Transform;
var SingletonChildProcess = helpers.SingletonChildProcess;
Object.defineProperties(global, {
Doc: { get: function () { return require("./gulplib/doc.js"); } },
TypeScript: { get: function () { return require("./gulplib/typescript.js"); } },
UglifyJS: { get: function () { return require("./gulplib/uglify.js"); } },
});
gulp.task("default", ["libjass.js", "libjass.min.js"]);
function ASTModifer(namespaces) {
var visitor = function (current) {
var newText = [];
if (current instanceof TypeScript.AST.Namespace) {
current.members.forEach(visitor);
}
else if (current instanceof TypeScript.AST.Constructor) {
newText.push("@constructor");
if (current.baseType !== null) {
newText.push("@extends {" + current.baseType.fullName + "}");
}
if (current.parent !== null) {
newText.push("@memberOf " + current.parent.fullName);
}
current.members.forEach(visitor);
}
if (current.isPrivate) {
newText.push("@private");
}
if (current.isStatic) {
newText.push("@static");
}
if (newText.length > 0) {
current.astNode["gulp-typescript-new-comment"] = newText;
}
};
Object.keys(namespaces).forEach(function (namespaceName) { visitor(namespaces[namespaceName]); });
}
gulp.task("libjass.js", function (callback) {
if (fs.existsSync("./libjass.js")) {
return callback(null);
}
return gulp.src("./libjass.ts", { read: false })
.pipe(TypeScript.gulp("/libjass.js", "/libjass.js.map", ASTModifer))
.pipe(UglifyJS.fixup())
.pipe(gulp.dest("."));
});
gulp.task("libjass.min.js", ["libjass.js"], function (callback) {
if (fs.existsSync("./libjass.min.js")) {
return callback(null);
}
var input = Object.create(null);
return gulp.src(["./libjass.js", "./libjass.js.map"]).pipe(UglifyJS.minify()).pipe(gulp.dest("."));
});
gulp.task("clean", function () {
["libjass.js", "libjass.js.map", "libjass.min.js", "libjass.min.js.map"].forEach(function (file) {
try {
fs.unlinkSync(file);
}
catch (ex) {
if (ex.code !== "ENOENT") {
throw ex;
}
}
});
});
gulp.task("watch", ["clean"], function (callback) {
var gulpTestProcess = SingletonChildProcess(path.resolve("./gulplib/test-runner.js"));
gulp.src("./libjass.ts", { read: false })
.pipe(TypeScript.watch("/libjass.js", "/libjass.js.map"))
.pipe(UglifyJS.fixup())
.pipe(gulp.dest("."))
.pipe(Transform(function (file) {
if (path.basename(file.path) === "libjass.js") {
gulpTestProcess();
}
}));
gulp.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");
}))
}));
});
};
+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;
}
};
};
+41
View File
@@ -0,0 +1,41 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require("fs");
var path = require("path");
var Mocha = require("mocha");
console.log("Running tests...");
var mocha = new Mocha({
ui: "tdd",
reporter: "spec"
});
fs.readdirSync("./tests/")
.filter(function (filename) { return filename.match(/^test-.*\.js$/) !== null; })
.forEach(function (filename) { mocha.addFile(path.resolve("./tests", filename)); });
mocha.run(function (failures) {
if (failures > 0) {
throw new Error(failures + " test(s) failed.");
}
});
File diff suppressed because it is too large Load Diff
+347
View File
@@ -0,0 +1,347 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require("fs");
var path = require("path");
var UglifyJS = require("uglify-js");
var Transform = require("./helpers.js").Transform;
module.exports = {
fixup: function () {
var codeFile = null;
var sourceMapFile = null;
return Transform(function (file) {
switch (path.extname(file.path)) {
case ".js":
codeFile = file;
break;
case ".map":
sourceMapFile = file;
break;
}
if (codeFile !== null && sourceMapFile !== null) {
UglifyJS.base54.reset();
// Parse
var root = UglifyJS.parse(codeFile.contents.toString(), {
filename: path.basename(codeFile.path),
toplevel: null
});
root = root.wrap_enclose(['(typeof module !== "undefined") ? module.exports : (this.libjass = {}):libjass', '(typeof global !== "undefined") ? global : this:global']);
root.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;
}
});
},
minify: function () {
var codeFile = null;
var sourceMapFile = null;
return Transform(function (file) {
switch (path.extname(file.path)) {
case ".js":
codeFile = file;
break;
case ".map":
sourceMapFile = file;
break;
}
if (codeFile !== null && sourceMapFile !== null) {
UglifyJS.base54.reset();
// Parse
var root = null;
root = UglifyJS.parse(codeFile.contents.toString(), {
filename: path.basename(codeFile.path),
toplevel: root
});
root.figure_out_scope({ screw_ie8: true });
// Warnings
root.scope_warnings({
func_arguments: false
});
// Compress
var compressor = UglifyJS.Compressor({
warnings: true,
screw_ie8: true
});
root = root.transform(compressor);
// Mangle
root.figure_out_scope({ screw_ie8: true });
root.compute_char_frequency();
root.mangle_names({ screw_ie8: true });
// Mangle private members
var occurrences = Object.create(null);
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_PropAccess &&
typeof node.property === "string" &&
node.property[0] === "_" &&
node.property[1] !== "_" && // Doesn't start with two leading underscores
node.property !== "_classTag" // webworker serializer uses this property by name, so it shouldn't be changed.
) {
var occurrence = occurrences[node.property];
if (occurrence === undefined) {
occurrences[node.property] = 1;
}
else {
occurrences[node.property]++;
}
}
}));
var identifiers = Object.keys(occurrences);
identifiers.sort(function (first, second) { return occurrences[second] - occurrences[first]; });
var generatedIdentifiers = occurrences;
var validIdentifierCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
var toIdentifier = function (index) {
var result = validIdentifierCharacters[(index % validIdentifierCharacters.length)];
index = (index / validIdentifierCharacters.length) | 0;
while (index > 0) {
index--;
result = validIdentifierCharacters[index % validIdentifierCharacters.length] + result;
index = (index / validIdentifierCharacters.length) | 0;
}
return "_" + result;
};
identifiers.forEach(function (identifier, index) {
generatedIdentifiers[identifier] = toIdentifier(index);
});
root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_PropAccess &&
typeof node.property === "string" &&
node.property in generatedIdentifiers
) {
node.property = generatedIdentifiers[node.property];
}
}));
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
comments: {
test: function (comment) {
if (!firstLicenseHeaderFound && comment.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
return true;
}
return false;
}
},
screw_ie8: true
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.relative)]);
this.push(codeFile);
output.source_map.get()._sources.toArray().forEach(function (filename) {
output.source_map.get().setSourceContent(filename, fs.readFileSync(filename, { encoding: "utf8"}));
});
sourceMapFile.contents = new Buffer(output.source_map.toString());
this.push(sourceMapFile);
codeFile = null;
sourceMapFile = null;
}
});
}
};
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
return false;
}
return originalSymbolUnreferenced.call(this);
};
+1 -70
View File
@@ -22,79 +22,10 @@ body {
margin: 0;
}
.wrapper {
position: relative;
}
.zoomed, .zoomed * {
display: inline-block;
font-size: 0;
}
#subs {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
#subs, #subs * {
pointer-events: none;
.libjass-subs {
z-index: 2147483647;
}
#subs > * {
position: absolute;
width: 100%;
}
#subs.paused * {
-webkit-animation-play-state: paused !important;
animation-play-state: paused !important;
}
.an1, .an2, .an3 {
bottom: 0;
}
.an4, .an5, .an6 {
display: table;
}
.an4 > *, .an5 > *, .an6 > * {
display: table-cell;
top: 50%;
vertical-align: middle;
}
.an7, .an8, .an9 {
top: 0;
}
.an1, .an4, .an7 {
text-align: left;
}
.an2, .an5, .an8 {
text-align: center;
}
.an3, .an6, .an9 {
text-align: right;
}
#subs span {
white-space: pre-wrap;
}
#dpi-div {
position: absolute;
width: 1in;
height: 1in;
visibility: hidden;
}
.settings-form > fieldset {
display: inline-block;
}
+110 -68
View File
@@ -36,87 +36,129 @@ addEventListener("DOMContentLoaded", function () {
var ass = null;
var testVideoAndASSLoaded = function () {
if (videoMetadataLoaded && ass) {
ass.dpi = parseFloat(getComputedStyle(document.querySelector("#dpi-div")).height.match(/(\d+)px/)[1]);
if (videoMetadataLoaded === false || ass === null) {
return;
}
var renderer = new libjass.DefaultRenderer(video, document.querySelector("#subs"), ass, {
preloadFonts: true,
fontMap: libjass.FontMap.fromStyleElement(document.querySelector("#font-map"))
});
// Both video metadata and ASS are loaded
renderer.addEventListener("ready", function () {
debug("All fonts have been preloaded. Beginning autoplay.");
video.play();
});
// 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"))
});
var changeVideoSizeSelection = function (id) {
if (typeof id === "undefined") {
[].some.call(videoSizeSelector.querySelectorAll("input[name='video-size']"), function (option) {
if (option.checked) {
id = option.id;
return true;
}
if (libjass.debugMode) {
// Export the renderer for debugging
window.renderer = renderer;
}
return false;
});
}
// Autoplay the video when the renderer is ready
renderer.addEventListener("ready", function () {
debug("All fonts have been preloaded. Beginning autoplay.");
if (id === "video-size-video-radio") {
renderer.resizeVideo(video.videoWidth, video.videoHeight);
}
video.play();
});
else if (id === "video-size-script-radio") {
renderer.resizeVideo(ass.resolutionX, ass.resolutionY);
}
};
var videoSizeSelector = document.querySelector("#video-size-selector");
videoSizeSelector.addEventListener("change", function (event) { changeVideoSizeSelection(event.target.id); }, false);
renderer.addEventListener("fullScreenChange", function (newState) {
if (newState === false) {
changeVideoSizeSelection();
}
});
changeVideoSizeSelection();
};
}
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
video.addEventListener("loadedmetadata", function () {
debug("Video metadata loaded.");
videoMetadataLoaded = true;
document.querySelector("#video-resolution-label-width").appendChild(document.createTextNode(video.videoWidth));
document.querySelector("#video-resolution-label-height").appendChild(document.createTextNode(video.videoHeight));
testVideoAndASSLoaded();
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
renderer.setEnabled(event.target.checked);
}, false);
}
else {
debug("Video metadata loaded.");
videoMetadataLoaded = true;
testVideoAndASSLoaded();
}
var track = document.querySelector("#video > track[data-format='ass']");
var subsRequest = new XMLHttpRequest();
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
subsRequest.addEventListener("readystatechange", function () {
if (subsRequest.readyState === XMLHttpRequest.DONE) {
debug("ASS script received.");
// 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
ass = new libjass.ASS(subsRequest.responseText);
if (libjass.debugMode) {
window.ass = ass;
[].slice.call(videoSizeSelector.querySelectorAll("input[name='video-size']")).some(function (option) {
if (option.checked) {
id = option.id;
return true;
}
return false;
});
}
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.resolutionY));
if (id === "video-size-video-radio") {
// Resize to video resolution
testVideoAndASSLoaded();
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);
+10 -8
View File
@@ -21,6 +21,7 @@
<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" />
@@ -33,16 +34,13 @@
-->
<script src="index.js" />
<style id="animation-styles" type="text/css" />
</head>
<body>
<div class="wrapper">
<video id="video" src="/video/gc-01.webm" controls="">
<track src="/video/gc-01.ass" kind="metadata" data-format="ass" />
</video>
<div id="subs" />
</div>
<div id="dpi-div" />
<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>
@@ -51,6 +49,10 @@
<input type="radio" name="video-size" id="video-size-script-radio" /><label for="video-size-script-radio">Script resolution <span id="script-resolution-label-width" />x<span id="script-resolution-label-height" /></label>
</div>
</fieldset>
<fieldset>
<legend>Subtitles</legend>
<input type="checkbox" id="enable-disable-subs" checked="checked" /><label for="enable-disable-subs">Subtitles</label>
</fieldset>
</form>
</body>
</html>
-404
View File
@@ -1,404 +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.
*/
///<reference path="libjass.ts" />
interface Iterator {
next(): any
}
declare function Iterator(collection: any, keysOnly?: boolean): Iterator
declare var StopIteration: any
module libjass {
export class LazySequence<T> {
/**
* The base class of all lazy sequences.
*
* @constructor
* @template T
*/
constructor() { }
/**
* @template U
* @param transform {function(T): U} A function (element) -> (transformedElement)
* @return {!LazySequence.<U>} A new LazySequence with the given transform applied
*/
map<U>(transform: (element: T) => U): LazySequence<U> {
return new LazySequenceMap<T, U>(this, transform);
}
/**
* @param {function(T): boolean} filter A function (element) -> (Boolean). Returns true if element should remain in the enumeration.
* @return {!LazySequence.<T>} A new LazySequence with the given filter applied
*/
filter(filter: (element: T) => boolean): LazySequence<T> {
return new LazySequenceFilter<T>(this, filter);
}
/**
* @param {function(T): boolean} filter A function (element) -> (Boolean). Returns false for an element if enumeration of this LazySequence should stop at that element.
* @return {!LazySequence.<T>} A new LazySequence which returns new elements from the underlying sequence as long as the predicate returns true for all of them
*/
takeWhile(filter: (element: T) => boolean): LazySequence<T> {
return new LazySequenceTakeWhile<T>(this, filter);
}
/**
* @param {function(T): boolean} filter A function (element) -> (Boolean). Returns true for an element if enumeration of this LazySequence should skip all elements upto that element.
* @return {!LazySequence.<T>} A new LazySequence which returns new elements from the underlying sequence as soon as the predicate returns true for one of them
*/
skipWhile(filter: (element: T) => boolean): LazySequence<T> {
return new LazySequenceSkipWhile<T>(this, filter);
}
/**
* @return {Array.<T>}
*/
toArray(): Array<T> {
var result: Array = [];
var iterator = Iterator(this);
try {
for (; ;) {
result.push(iterator.next());
}
}
catch (ex) {
if (ex !== StopIteration) {
throw ex;
}
}
return result;
}
}
/**
* @template T
* @param {Array.<T>} array
* @return {!LazySequence.<T>} A LazySequence backed by this Array
*/
export function Lazy<T>(array: Array<T>): LazySequence<T> {
return new LazyArray<T>(array);
}
// If this browser does not have an implementation of StopIteration, mock it
if (typeof StopIteration === "undefined") {
global.StopIteration = Object.create(null);
}
/**
* A default Iterator for arrays in case Iterator(Array) is not defined by the browser.
*
* @constructor
* @param {!Array} array
*/
class ArrayIterator implements Iterator {
// The index of the element which will be returned in the next call to next()
private _currentIndex = 0;
constructor(private _array: Array) { }
/**
* @return {!Array} Returns a tuple [index, element]
*/
next(): any[] {
// Loop through the array looking for an element to return
while (this._currentIndex < this._array.length) {
// If the index is less than the array's length and an element is in the array at that index
if (this._currentIndex in this._array) {
var oldCurrentIndex = this._currentIndex;
this._currentIndex++;
// ... return it
return [oldCurrentIndex, this._array[oldCurrentIndex]];
}
// Else advance to the next index
else {
this._currentIndex++;
}
}
// If there are no more elements in the array, throw StopIteration
throw StopIteration;
}
}
if (typeof Iterator === "undefined") {
/**
* A default function for creating iterators in case it is not defined by the browser.
*
* @param {!*} collection
* @param {boolean=} keysOnly
* @return {!{next: function(): *}}
*/
global.Iterator = (collection: any, keysOnly?: boolean): Iterator => {
if (keysOnly) {
throw new Error("This Iterator implementation doesn't support keysOnly = true.");
}
if (typeof collection.__iterator__ === "function") {
return <Iterator>collection.__iterator__();
}
else if (Array.isArray(collection)) {
return new ArrayIterator(<Array>collection);
}
else {
throw new Error("This Iterator implementation doesn't support iterating arbitrary objects.");
}
}
}
/**
* This class is a LazySequence returned by Lazy(Array) and represents a LazySequence backed by the elements of that array.
*
* @constructor
* @template T
* @extends {LazySequence.<T>}
* @param {!Array} array
*/
class LazyArray<T> extends LazySequence<T> {
constructor(private _array: Array<T>) {
super();
}
/**
* @return {{next: function(): T}}
*/
__iterator__(): LazyArrayIterator<T> {
return new LazyArrayIterator<T>(Iterator(this._array));
}
}
/**
* @constructor
* @template T
* @param {!{next: function(): *}} previous
*/
class LazyArrayIterator<T> implements Iterator {
constructor(private _previous: Iterator) { }
/**
* @return {T}
*/
next(): T {
var result: Array<any> = this._previous.next();
return <T>result[1];
}
}
/**
* A LazySequence returned from LazySequence.map()
*
* @constructor
* @template T, U
* @extends {LazySequence.<U>}
* @param {!*} previous The underlying lazy sequence
* @param {function(T): U} transform The transform function (element) -> (transformedElement)
*/
class LazySequenceMap<T, U> extends LazySequence<U> {
constructor(private _previous: any, private _transform: (element: T) => U) {
super();
}
/**
* @return {!LazySequenceMapIterator.<T, U>}
*/
__iterator__(): LazySequenceMapIterator<T, U> {
return new LazySequenceMapIterator<T, U>(Iterator(this._previous), this._transform);
}
}
/**
* @constructor
* @template T, U
* @param {!{next: function(): T}} previous
* @param {function(T): U} transform
*/
class LazySequenceMapIterator<T, U> implements Iterator {
constructor(private _previous: Iterator, private _transform: (element: T) => U) { }
/**
* @return {U}
*/
next(): U {
// Apply the transform function and return the transformed value
return this._transform(<T>this._previous.next());
}
}
/**
* A LazySequence returned from LazySequence.filter()
*
* @constructor
* @template T
* @extends {LazySequence.<T>}
* @param {!*} previous The underlying lazy sequence
* @param {function(T): boolean} filter The filter function (element) -> (Boolean)
*/
class LazySequenceFilter<T> extends LazySequence<T> {
constructor(private _previous: any, private _filter: (element: T) => boolean) {
super();
}
/**
* @return {!LazySequenceFilterIterator.<T>}
*/
__iterator__(): LazySequenceFilterIterator<T> {
return new LazySequenceFilterIterator(Iterator(this._previous), this._filter);
}
}
/**
* @constructor
* @template T
* @param {!{next: function(): T}} previous
* @param {function(T): boolean} filter
*/
class LazySequenceFilterIterator<T> implements Iterator {
constructor(private _previous: Iterator, private _filter: (element: T) => boolean) { }
/**
* @return {T}
*/
next(): T {
// Loop to find the next element from the underlying lazy sequence which passes the filter and return it
var result: T;
do {
result = <T>this._previous.next();
} while (!this._filter(result));
return result;
}
}
/**
* A LazySequence returned from LazySequence.takeWhile()
*
* @constructor
* @template T
* @extends {LazySequence.<T>}
* @param {!*} previous The underlying lazy sequence
* @param {function(T): boolean} predicate The predicate function (element) -> (Boolean)
*/
class LazySequenceTakeWhile<T> extends LazySequence<T> {
constructor(private _previous: any, private _predicate: (element: T) => boolean) {
super();
}
/**
* @return {!LazySequenceTakeWhileIterator.<T>}
*/
__iterator__(): LazySequenceTakeWhileIterator<T> {
return new LazySequenceTakeWhileIterator<T>(Iterator(this._previous), this._predicate);
}
}
/**
* @constructor
* @template T
* @param {!{next: function(): T}} previous
* @param {function(T): boolean} predicate
*/
class LazySequenceTakeWhileIterator<T> implements Iterator {
// Set to true when an element not matching the predicate is found
private _foundEnd = false;
constructor(private _previous: Iterator, private _predicate: (element: T) => boolean) { }
/**
* @return {T}
*/
next(): T {
var result: T;
// If we haven't already found the end in a previous call to next()
if (!this._foundEnd) {
// Get the next element from the underlying lazy sequence and see if we've found the end now
result = <T>this._previous.next();
this._foundEnd = !this._predicate(result);
}
// If we haven't found the end, return the element
if (!this._foundEnd) {
return result;
}
// Else throw StopIteration
else {
throw StopIteration;
}
}
}
/**
* A LazySequence returned from LazySequence.skipWhile()
*
* @constructor
* @template T
* @extends {LazySequence.<T>}
* @param {!*} previous The underlying lazy sequence
* @param {function(T): boolean} predicate The predicate function (element) -> (Boolean)
*/
class LazySequenceSkipWhile<T> extends LazySequence<T> {
constructor(private _previous: any, private _predicate: (element: T) => boolean) {
super();
}
/**
* @return {!LazySequenceSkipWhileIterator.<T>}
*/
__iterator__(): LazySequenceSkipWhileIterator<T> {
return new LazySequenceSkipWhileIterator<T>(Iterator(this._previous), this._predicate);
}
}
/**
* @constructor
* @template T
* @param {!{next: function(): T}} previous
* @param {function(T): boolean} predicate
*/
class LazySequenceSkipWhileIterator<T> implements Iterator {
// Set to true when an element not matching the predicate is found
private _foundStart = false;
constructor(private _previous: Iterator, private _predicate: (element: T) => boolean) { }
/**
* @return {T}
*/
next(): T {
var result: T;
do {
// Get the next element
result = <T>this._previous.next();
// and see if we've already found the start, or if we've found it now
this._foundStart = this._foundStart || !this._predicate(result);
} while (!this._foundStart); // Keep looping till we find the start
// We've found the start, so return the element
return result;
}
}
}
+101
View File
@@ -0,0 +1,101 @@
/**
* 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.
*/
.libjass-wrapper {
position: relative;
overflow: hidden;
}
.libjass-wrapper.libjass-full-screen {
position: static;
}
.libjass-subs {
position: absolute;
overflow: hidden;
}
.libjass-subs, .libjass-subs * {
pointer-events: none;
}
.libjass-subs.paused * {
-webkit-animation-play-state: paused !important;
animation-play-state: paused !important;
}
.libjass-subs .an {
position: absolute;
}
.libjass-subs .an1, .libjass-subs .an2, .libjass-subs .an3 {
bottom: 0;
}
.libjass-subs .an4, .libjass-subs .an5, .libjass-subs .an6 {
display: table;
width: 100%;
height: 100%;
}
.libjass-subs .an4 > *, .libjass-subs .an5 > *, .libjass-subs .an6 > * {
display: table-cell;
vertical-align: middle;
}
.libjass-subs .an7, .libjass-subs .an8, .libjass-subs .an9 {
top: 0;
}
.libjass-subs .an1, .libjass-subs .an4, .libjass-subs .an7 {
text-align: left;
}
.libjass-subs .an2, .libjass-subs .an5, .libjass-subs .an8 {
text-align: center;
}
.libjass-subs .an3, .libjass-subs .an6, .libjass-subs .an9 {
text-align: right;
}
.libjass-subs {
line-height: 0;
}
/* Filter wrapper span */
.libjass-subs div[data-dialogue-id] {
-webkit-perspective-origin: center;
-webkit-perspective: 200px;
perspective-origin: center;
perspective: 200px;
}
.libjass-font-measure {
position: absolute;
visibility: hidden;
border: 0;
margin: 0;
padding: 0;
}
.libjass-filters {
display: block;
}
+27 -4
View File
@@ -1,6 +1,29 @@
/**
* 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="utility.ts" />
///<reference path="dialogue.ts" />
///<reference path="iterators.ts" />
///<reference path="web-worker.ts" />
///<reference path="parts.ts" />
///<reference path="parser.ts" />
///<reference path="renderer.ts" />
///<reference path="tags.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" />
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "libjass",
"version": "0.2.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",
@@ -8,7 +8,7 @@
"url": "https://github.com/Arnavion/libjass/issues"
},
"licenses": [{
"type": "Apache 2.0",
"type": "Apache-2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}],
"maintainers": [{
@@ -20,14 +20,14 @@
"url": "https://github.com/Arnavion/libjass"
},
"scripts": {
"postinstall": "jake clean && jake --trace",
"test": "jake test"
"postinstall": "gulp clean default",
"test": "node ./gulplib/test-runner.js"
},
"dependencies": {
"jake": "latest",
"mocha": "~1.12",
"pegjs": "0.7.0",
"typescript": "0.9.1-1",
"uglify-js": "~2"
"gulp": "latest",
"mocha": "1.21.x",
"typescript": "1.0.1",
"uglify-js": "2.x >=2.4.14",
"vinyl": "latest"
}
}
+2470 -219
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
-360
View File
@@ -1,360 +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.
*/
///<reference path="libjass.ts" />
module libjass {
export class DefaultRenderer {
private _dialogues: Dialogue[];
private _wrappers: HTMLDivElement[][] = [];
private _currentTime: number;
private _currentSubs: HTMLDivElement[] = [];
// Iterable of subtitle div's that are also to be displayed
private _newSubs: LazySequence<Node>;
private _videoIsFullScreen: boolean = false;
private _eventListeners: Object = Object.create(null);
constructor(private _video: HTMLVideoElement, private _subsWrapper: HTMLDivElement, private _ass: ASS, private _settings: RendererSettings) {
RendererSettings.prototype.initializeUnsetProperties.call(_settings);
// Sort the dialogues array by start time and then by their original position in the script (id)
this._dialogues = this._ass.dialogues.slice(0);
this._dialogues.sort((dialogue1: Dialogue, dialogue2: Dialogue) => {
var result = dialogue1.start - dialogue2.start;
if (result === 0) {
result = dialogue1.id - dialogue2.id;
}
return result;
});
this._newSubs =
Lazy(this._dialogues)
// Skip until dialogues which end at a time later than currentTime
.skipWhile((dialogue: Dialogue) => dialogue.end < this._currentTime)
// Take until dialogue which starts later than currentTime + settings.preRenderTime
.takeWhile((dialogue: Dialogue) => dialogue.start <= (this._currentTime + this._settings.preRenderTime))
.filter((dialogue: Dialogue) => {
// Ignore dialogues which end at a time less than currentTime
if (dialogue.end < this._currentTime) {
return false;
}
// All these dialogues are visible at atleast one time in the range [currentTime, currentTime + settings.preRenderTime]
// Ignore those dialogues which have already been displayed
if (this._currentSubs.some((sub: HTMLDivElement) => parseInt(sub.getAttribute("data-dialogue-id")) === dialogue.id)) {
return false;
}
// If the dialogue is to be displayed, keep it to be drawn...
if (dialogue.start <= this._currentTime) {
return true;
}
// ... otherwise pre-render it and forget it
else {
dialogue.preRender();
return false;
}
})
.map((dialogue: Dialogue) => {
if (libjass.debugMode) {
console.log(dialogue.toString());
}
// Display the dialogue and return the drawn subtitle div
return this._wrappers[dialogue.layer][dialogue.alignment].appendChild(dialogue.draw(this._currentTime));
});
// Create layer wrapper div's and the alignment div's inside each layer div
var layers = new Set<number>();
this._dialogues.forEach((dialogue: Dialogue) => {
layers.add(dialogue.layer);
});
var layersArray: number[] = [];
layers.forEach((layer: number) => { layersArray.push(layer); });
layersArray.sort().forEach((layer: number) => {
this._wrappers[layer] = new Array<HTMLDivElement>(9 + 1); // + 1 because alignments are 1-indexed (1 to 9)
for (var alignment = 1; alignment <= 9; alignment++) {
var wrapperDiv = document.createElement("div");
wrapperDiv.className = "an" + alignment + " layer" + layer;
this._subsWrapper.appendChild(wrapperDiv);
this._wrappers[layer][alignment] = wrapperDiv;
}
});
if (!this._settings.preLoadFonts) {
setTimeout(() => { this._ready(); }, 0);
}
// Preload fonts
else {
var allFonts = new Set<string>();
this._ass.styles.forEach((style: Style) => {
allFonts.add(style.fontName);
});
this._dialogues.forEach((dialogue: Dialogue) => {
dialogue.parts.forEach((part: tags.Tag) => {
if (part instanceof tags.FontName) {
allFonts.add((<tags.FontName>part).value);
}
});
});
var urlsToPreload =
Object.keys(this._settings.fontMap)
.filter((name: string) => allFonts.has(name))
.map((name: string) => <string>this._settings.fontMap[name]);
var urlsLeftToPreload = urlsToPreload.length;
if (libjass.debugMode) {
console.log("Preloading fonts...");
}
urlsToPreload.forEach((url: string) => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (libjass.debugMode) {
console.log("Preloaded " + url + ".");
}
--urlsLeftToPreload;
if (libjass.debugMode) {
console.log(urlsLeftToPreload + " fonts left to preload.");
}
if (urlsLeftToPreload === 0) {
if (libjass.debugMode) {
console.log("All fonts have been preloaded.");
}
this._ready();
}
}
}, false);
xhr.send(null);
return xhr;
});
if (libjass.debugMode) {
console.log(urlsLeftToPreload + " fonts left to preload.");
}
if (urlsLeftToPreload === 0) {
if (libjass.debugMode) {
console.log("All fonts have been preloaded.");
}
this._ready();
}
}
this._eventListeners["ready"] = [];
this._eventListeners["fullScreenChange"] = [];
}
public addEventListener(type: string, listener: Object): void {
var listeners = <Array>this._eventListeners[type];
if (listeners) {
listeners.push(listener);
}
}
public resizeVideo(width: number, height: number): void {
this._currentSubs.forEach(removeElement);
this._currentSubs = [];
this._video.style.width = this._subsWrapper.style.width = width + "px";
this._video.style.height = this._subsWrapper.style.height = height + "px";
this._ass.scaleTo(width, height);
this._video.dispatchEvent(new (<any>Event)("timeupdate"));
}
private _ready(): void {
this._video.addEventListener("timeupdate", this._onVideoTimeUpdate.bind(this), false);
this._video.addEventListener("seeking", this._onVideoSeeking.bind(this), false);
this._video.addEventListener("pause", this._onVideoPause.bind(this), false);
this._video.addEventListener("playing", this._onVideoPlaying.bind(this), false);
document.addEventListener("webkitfullscreenchange", this._onFullScreenChange.bind(this), false);
document.addEventListener("mozfullscreenchange", this._onFullScreenChange.bind(this), false);
document.addEventListener("fullscreenchange", this._onFullScreenChange.bind(this), false);
this._dispatchEvent("ready");
}
private _onVideoTimeUpdate(): void {
this._currentTime = this._video.currentTime;
this._currentSubs = this._currentSubs.filter((sub: HTMLDivElement) => {
var subDialogue = this._ass.dialogues[parseInt(sub.getAttribute("data-dialogue-id"))];
// If the sub should still be displayed at currentTime, keep it...
if (subDialogue.start <= this._currentTime && this._currentTime < subDialogue.end) {
return true;
}
// ... otherwise remove it from the DOM and from this array...
else {
removeElement(sub);
return false;
}
}).concat(this._newSubs.toArray()); // ... and add the new subs that are to be displayed.
if (libjass.debugMode) {
console.log("video.timeupdate: " + this._getVideoStateLogString());
}
}
private _onVideoSeeking(): void {
this._currentSubs.forEach(removeElement);
this._currentSubs = [];
if (libjass.debugMode) {
console.log("video.seeking: " + this._getVideoStateLogString());
}
}
private _onVideoPause(): void {
this._subsWrapper.className = "paused";
if (libjass.debugMode) {
console.log("video.pause: " + this._getVideoStateLogString());
}
}
private _onVideoPlaying(): void {
this._subsWrapper.className = "";
if (libjass.debugMode) {
console.log("video.playing: " + this._getVideoStateLogString());
}
}
private _onFullScreenChange() {
var fullScreenElement = document.fullscreenElement;
if (fullScreenElement === undefined) {
fullScreenElement = document.mozFullScreenElement;
}
if (fullScreenElement === undefined) {
fullScreenElement = document.msFullscreenElement;
}
if (fullScreenElement === undefined) {
fullScreenElement = document.webkitFullscreenElement;
}
if (fullScreenElement === this._video) {
this.resizeVideo(screen.width, screen.height);
this._videoIsFullScreen = true;
}
else if (fullScreenElement === null && this._videoIsFullScreen) {
this._videoIsFullScreen = false;
this._dispatchEvent("fullScreenChange", this._videoIsFullScreen);
}
}
private _getVideoStateLogString(): string {
return "video.currentTime = " + this._video.currentTime + ", video.paused = " + this._video.paused + ", video.seeking = " + this._video.seeking;
}
private _dispatchEvent(type: string, ...args: Object[]): void {
var listeners = <Array>this._eventListeners[type];
if (listeners) {
listeners.forEach((listener: Function) => {
listener.apply(this, args);
});
}
}
}
export class RendererSettings {
public preLoadFonts: boolean;
public fontMap: FontMap;
/**
* Subtitles will be pre-rendered for this amount of time (seconds)
*/
public preRenderTime: number;
public initializeUnsetProperties(): void {
if (this.preLoadFonts === undefined) {
this.preLoadFonts = false;
}
if (this.fontMap === undefined) {
this.fontMap = null;
}
if (this.preRenderTime === undefined) {
this.preRenderTime = 5;
}
}
}
export class FontMap {
constructor(private _map: Object) { }
public static fromStyleElement(styleElement: HTMLStyleElement): FontMap {
var map: Object = Object.create(null);
var styleSheet = <CSSStyleSheet>styleElement.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") ||
rule.cssText.split("\n")
.map((line: string) => line.match(/\s*src: url\((.+)\);/))
.filter((matches: string[]) => matches !== null)
.map((matches: string[]) => FontMap._stripQuotes(matches[1]))[0];
if (src) {
var name = FontMap._stripQuotes(rule.style.getPropertyValue("font-family"));
map[name] = src;
}
});
return new FontMap(map);
}
private static _stripQuotes(str: string): string {
return str.match(/^["']?(.*?)["']?/)[1];
}
}
}
+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
-383
View File
@@ -1,383 +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.
*/
///<reference path="libjass.ts" />
module libjass {
export module tags {
/**
* Represents a CSS color with red, green, blue and alpha components.
*
* Instances of this class are immutable.
*
* @constructor
*/
export class Color {
constructor(private _red: number, private _green: number, private _blue: number, private _alpha: number = 1) { }
get red(): number {
return this._red;
}
get green(): number {
return this._green;
}
get blue(): number {
return this._blue;
}
get alpha(): number {
return this._alpha;
}
/**
* @param {number} value The new alpha. If null, the existing alpha is used.
* @return {Color} Returns a new Color instance with the same color but the provided alpha.
*/
withAlpha(value: number): Color {
return new Color(this._red, this._green, this._blue, (value !== null) ? value : this._alpha);
}
/**
* @return {string} The CSS representation "rgba(...)" of this color.
*/
toString(): string {
return "rgba(" + this._red + ", " + this._green + ", " + this._blue + ", " + this._alpha + ")";
}
}
export interface Tag {
toString(): string;
}
export class TagBase implements Tag {
constructor(private _name: string, ... private _propertyNames: string[]) { }
/**
* @return {string}
*/
toString(): string {
return (
this._name + " { " +
this._propertyNames.map(name => name + ": " + this[name]).join(", ") +
((this._propertyNames.length > 0) ? " " : "") +
"}"
);
}
}
export class Comment extends TagBase {
constructor(private _value: string) {
super("Comment", "value");
}
get value(): string {
return this._value;
}
}
export class HardSpace extends TagBase {
constructor() {
super("HardSpace");
}
}
export class NewLine extends TagBase {
constructor() {
super("NewLine");
}
}
export class Text extends TagBase {
constructor(private _value: string) {
super("Text", "value");
}
get value(): string {
return this._value;
}
}
export class Italic extends TagBase {
constructor(private _value: boolean) {
super("Italic", "value");
}
get value(): boolean {
return this._value;
}
}
export class Bold extends TagBase {
constructor(private _value: Object) {
super("Bold", "value");
}
get value(): Object {
return this._value;
}
}
export class Underline extends TagBase {
constructor(private _value: boolean) {
super("Underline", "value");
}
get value(): boolean {
return this._value;
}
}
export class StrikeThrough extends TagBase {
constructor(private _value: boolean) {
super("StrikeThrough", "value");
}
get value(): boolean {
return this._value;
}
}
export class Border extends TagBase {
constructor(private _value: number) {
super("Border", "value");
}
get value(): number {
return this._value;
}
}
export class BorderX extends TagBase {
constructor(private _value: number) {
super("BorderX", "value");
}
get value(): number {
return this._value;
}
}
export class BorderY extends TagBase {
constructor(private _value: number) {
super("BorderY", "value");
}
get value(): number {
return this._value;
}
}
export class Blur extends TagBase {
constructor(private _value: number) {
super("Blur", "value");
}
get value(): number {
return this._value;
}
}
export class FontName extends TagBase {
constructor(private _value: string) {
super("FontName", "value");
}
get value(): string {
return this._value;
}
}
export class FontSize extends TagBase {
constructor(private _value: number) {
super("FontSize", "value");
}
get value(): number {
return this._value;
}
}
export class FontScaleX extends TagBase {
constructor(private _value: number) {
super("FontScaleX", "value");
}
get value(): number {
return this._value;
}
}
export class FontScaleY extends TagBase {
constructor(private _value: number) {
super("FontScaleX", "value");
}
get value(): number {
return this._value;
}
}
export class LetterSpacing extends TagBase {
constructor(private _value: number) {
super("LetterSpacing", "value");
}
get value(): number {
return this._value;
}
}
export class RotateX extends TagBase {
constructor(private _value: number) {
super("RotateX", "value");
}
get value(): number {
return this._value;
}
}
export class RotateY extends TagBase {
constructor(private _value: number) {
super("RotateY", "value");
}
get value(): number {
return this._value;
}
}
export class RotateZ extends TagBase {
constructor(private _value: number) {
super("RotateZ", "value");
}
get value(): number {
return this._value;
}
}
export class SkewX extends TagBase {
constructor(private _value: number) {
super("SkewX", "value");
}
get value() {
return this._value;
}
}
export class SkewY extends TagBase {
constructor(private _value: number) {
super("SkewY", "value");
}
get value(): number {
return this._value;
}
}
export class PrimaryColor extends TagBase {
constructor(private _value: Color) {
super("PrimaryColor", "value");
}
get value(): Color {
return this._value;
}
}
export class OutlineColor extends TagBase {
constructor(private _value: Color) {
super("OutlineColor", "value");
}
get value(): Color {
return this._value;
}
}
export class Alpha extends TagBase {
constructor(private _value: number) {
super("Alpha", "value");
}
get value(): number {
return this._value;
}
}
export class PrimaryAlpha extends TagBase {
constructor(private _value: number) {
super("PrimaryAlpha", "value");
}
get value(): number {
return this._value;
}
}
export class OutlineAlpha extends TagBase {
constructor(private _value: number) {
super("OutlineAlpha", "value");
}
get value(): number {
return this._value;
}
}
export class Alignment extends TagBase {
constructor(private _value: number) {
super("Alignment", "value");
}
get value(): number {
return this._value;
}
}
export class Reset extends TagBase {
constructor(private _value: string) {
super("Reset", "value");
}
get value(): string {
return this._value;
}
}
export class Pos extends TagBase {
constructor(private _x: number, private _y: number) {
super("Pos", "x", "y");
}
get x(): number {
return this._x;
}
get y(): number {
return this._y;
}
}
export class Fade extends TagBase {
constructor(private _start: number, private _end: number) {
super("Fade", "start", "end");
}
get start(): number {
return this._start;
}
get end(): number {
return this._end;
}
}
}
}
+104 -24
View File
@@ -48,6 +48,8 @@ var require = function (name) {
var Suite = function (name, parent) {
this.name = name;
this.setup = function () { };
this.children = [];
this.parent = parent;
@@ -98,12 +100,25 @@ var require = function (name) {
}, enumerable: true },
});
Suite.prototype.run = function (logger) {
Suite.prototype.run = function (logger, done) {
var _this = this;
logger.beginSuite(this);
this.children.forEach(function (child) { child.run(logger); });
this.setup();
logger.endSuite(this);
var remainingChildren = this.children.slice();
var runChild = function () {
if (remainingChildren.length > 0) {
var nextChild = remainingChildren.shift();
nextChild.run(logger, runChild);
}
else {
logger.endSuite(_this);
done();
}
};
runChild();
};
var Test = function (description, body, parent) {
@@ -118,19 +133,49 @@ var require = function (name) {
this.exception = null;
};
Test.prototype.run = function (logger) {
this.passed = false;
Test.prototype.run = function (logger, done) {
if (this.body) {
this.passed = false;
try {
this.body();
if (this.body.length === 0) {
try {
this.body();
this.passed = true;
this.passed = true;
}
catch (testException) {
this.exception = testException;
}
logger.writeTest(this);
done();
}
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();
}
}
}
catch (testException) {
this.exception = testException;
}
logger.writeTest(this);
};
var currentSuite = null;
@@ -145,6 +190,10 @@ var require = function (name) {
return newSuite;
};
setup = function (body) {
currentSuite.setup = body;
}
test = function (description, body) {
return new Test(description, body, currentSuite);
};
@@ -152,12 +201,16 @@ var require = function (name) {
var assert = new function () {
this.deepEqual = function (actual, expected) {
if (expected === undefined && actual === undefined) {
return;
}
if (expected === undefined) {
throw new Error("Expected should not be undefined");
throw new Error("Expected undefined but got [" + actual + "]");
}
if (actual === undefined) {
throw new Error("Actual should not be undefined");
throw new Error("Expected [" + expected + "] but got undefined")
}
if (expected === null && actual === null) {
@@ -172,7 +225,7 @@ var assert = new function () {
throw new Error("Expected [" + expected + "] but got null")
}
if (expected.constructor !== actual.constructor) {
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 + "].");
}
@@ -248,12 +301,12 @@ var Logger = function (outputDiv) {
var numPassed = suite.passed;
var numFailed = suite.failed;
var message = numPassed + " of " + numTotal + " tests passed.";
var message = numPassed + " of " + numTotal + " tests passed";
console.log(message);
if (numFailed > 0) {
var message = numFailed + " of " + numTotal + " tests failed.";
var message = numFailed + " of " + numTotal + " tests failed";
console.warn(message);
@@ -264,21 +317,34 @@ var Logger = function (outputDiv) {
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) {
var message = "Test " + test.number + " - \"" + test.description + "\" - " + test.customProperties.rule + " [ " + test.customProperties.input + " ] ";
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 {
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) {
@@ -303,6 +369,12 @@ var Logger = function (outputDiv) {
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();
};
@@ -320,7 +392,15 @@ var Logger = function (outputDiv) {
addEventListener("DOMContentLoaded", function () {
var logger = new Logger(document.querySelector("#output"));
rootSuites.forEach(function (suite) { suite.run(logger); });
logger.writeTotal(rootSuites);
var remainingSuites = rootSuites.slice();
var runSuite = function () {
if (remainingSuites.length > 0) {
var nextSuite = remainingSuites.shift();
nextSuite.run(logger, runSuite);
}
else {
logger.writeTotal(rootSuites);
}
};
runSuite();
}, false);
+1
View File
@@ -39,6 +39,7 @@
<script src="test-primitives.js" />
<script src="test-tags.js" />
<script src="test-miscellaneous.js" />
<script src="test-webworker.js" />
</head>
<body>
<div id="output" />
+13 -1
View File
@@ -21,6 +21,8 @@
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;
@@ -34,7 +36,7 @@ var parserTest = function (description, input, rule, expected) {
return;
}
else {
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.message);
throw new Error("Expected parse to succeed but it threw an exception: " + parseException.stack);
}
}
@@ -55,6 +57,16 @@ var parserTest = function (description, input, rule, expected) {
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;
}
+158 -6
View File
@@ -23,11 +23,163 @@ 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.tags.Comment("\\pos(311,4)\\blur0.8\\fs40\\bord0\\c&H3F171F&\\t(3820,3820,\\blur6"),
new libjass.tags.Text("Chi"),
new libjass.tags.PrimaryColor(new libjass.tags.Color(177, 44, 66, 1)),
new libjass.tags.Text("tose "),
new libjass.tags.PrimaryColor(new libjass.tags.Color(31, 23, 63, 1)),
new libjass.tags.Text("Furu")
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"
}
]);
});
+38 -4
View File
@@ -23,12 +23,46 @@ var parserTest = require("./parser-test.js");
suite("Primitives", function () {
suite("Color", function () {
parserTest("BBGGRR", "&H3F171F&", "color", new libjass.tags.Color(31, 23, 63, 1));
parserTest("Starts with &H", "&H3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("AABBGGRR", "&H00434441", "colorWithAlpha", new libjass.tags.Color(65, 68, 67, 1));
parserTest("Starts with H", "&3F171F&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("AABBGGRR", "&HF0434441", "colorWithAlpha", new libjass.tags.Color(65, 68, 67, (1 - 240 / 255)));
parserTest("Eight digits", "&H3F171F00&", "color", new libjass.parts.Color(31, 23, 63, 1));
parserTest("AABBGGRR", "&HFF434441", "colorWithAlpha", new libjass.tags.Color(65, 68, 67, 0));
parserTest("Eight digits, non-zero alpha", "&H3F171FFF&", "color", null);
});
suite("Alpha", function () {
parserTest("Starts with &H", "&HFF&", "alpha", 0);
parserTest("Starts with &", "&FF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&HF&", "alpha", 0);
parserTest("Starts with &H, one digit", "&H0&", "alpha", 1);
parserTest("Starts with &, one digit", "&F&", "alpha", 0);
parserTest("Starts with &, one digit", "&0&", "alpha", 1);
parserTest("Starts with &H, doesn't end with &", "&HF&", "alpha", 0);
parserTest("Starts with &H, doesn't end with &", "&H0&", "alpha", 1);
parserTest("Starts with &, doesn't end with &", "&F", "alpha", 0);
parserTest("Starts with &, doesn't end with &", "&0", "alpha", 1);
parserTest("Doesn't start with &, doesn't end with &", "0", "alpha", 1);
parserTest("Starts with H", "H0", "alpha", null);
});
suite("ColorWithAlpha", function () {
parserTest("AABBGGRR", "&H00434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 1));
parserTest("AABBGGRR", "&HF0434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, (1 - 240 / 255)));
parserTest("AABBGGRR", "&HFF434441", "colorWithAlpha", new libjass.parts.Color(65, 68, 67, 0));
});
});
+120 -19
View File
@@ -23,15 +23,15 @@ var parserTest = require("./parser-test.js");
suite("Tags", function () {
suite("Bold tag - \\b", function () {
parserTest("True", "b1", "tag_b", new libjass.tags.Bold(true));
parserTest("True", "b1", "tag_b", new libjass.parts.Bold(true));
parserTest("False", "b0", "tag_b", new libjass.tags.Bold(false));
parserTest("False", "b0", "tag_b", new libjass.parts.Bold(false));
parserTest("100", "b100", "tag_b", new libjass.tags.Bold(100));
parserTest("100", "b100", "tag_b", new libjass.parts.Bold(100));
parserTest("900", "b900", "tag_b", new libjass.tags.Bold(900));
parserTest("900", "b900", "tag_b", new libjass.parts.Bold(900));
parserTest("null", "b", "tag_b", new libjass.tags.Bold(null));
parserTest("null", "b", "tag_b", new libjass.parts.Bold(null));
parserTest("2", "b2", "tag_b", null);
@@ -39,34 +39,135 @@ suite("Tags", function () {
parserTest("150", "b150", "tag_b", null);
parserTest("Enclosed tag", "{\\b1}", "enclosedTags", [new libjass.tags.Bold(true)]);
parserTest("Enclosed tag", "{\\b1}", "enclosedTags", [new libjass.parts.Bold(true)]);
parserTest("Enclosed tag", "{\\b0}", "enclosedTags", [new libjass.tags.Bold(false)]);
parserTest("Enclosed tag", "{\\b0}", "enclosedTags", [new libjass.parts.Bold(false)]);
parserTest("Enclosed tag", "{\\b100}", "enclosedTags", [new libjass.tags.Bold(100)]);
parserTest("Enclosed tag", "{\\b100}", "enclosedTags", [new libjass.parts.Bold(100)]);
parserTest("Enclosed tag", "{\\b900}", "enclosedTags", [new libjass.tags.Bold(900)]);
parserTest("Enclosed tag", "{\\b900}", "enclosedTags", [new libjass.parts.Bold(900)]);
parserTest("Enclosed tag", "{\\b}", "enclosedTags", [new libjass.tags.Bold(null)]);
parserTest("Enclosed tag", "{\\b}", "enclosedTags", [new libjass.parts.Bold(null)]);
parserTest("Enclosed tag", "{\\b2}", "enclosedTags", [new libjass.tags.Bold(null), new libjass.tags.Comment("2")]);
parserTest("Enclosed tag", "{\\b2}", "enclosedTags", [new libjass.parts.Bold(null), new libjass.parts.Comment("2")]);
parserTest("Enclosed tag", "{\\b10}", "enclosedTags", [new libjass.tags.Bold(null), new libjass.tags.Comment("0")]);
parserTest("Enclosed tag", "{\\b10}", "enclosedTags", [new libjass.parts.Bold(true), new libjass.parts.Comment("0")]);
parserTest("Enclosed tag", "{\\b150}", "enclosedTags", [new libjass.tags.Bold(null), new libjass.tags.Comment("50")]);
parserTest("Enclosed tag", "{\\b150}", "enclosedTags", [new libjass.parts.Bold(true), new libjass.parts.Comment("50")]);
});
suite("Primary color tag - \\c or \\1c", function () {
parserTest("Just the tag", "1c&H3F171F&", "tag_1c", new libjass.tags.PrimaryColor(new libjass.tags.Color(31, 23, 63, 1)));
suite("Italics tag - \\i", function () {
parserTest("True", "i1", "tag_i", new libjass.parts.Italic(true));
parserTest("Just the tag", "c&H3F171F&", "tag_c", new libjass.tags.PrimaryColor(new libjass.tags.Color(31, 23, 63, 1)));
parserTest("False", "i0", "tag_i", new libjass.parts.Italic(false));
parserTest("Enclosed tag", "{\\c&H3F171F&}", "enclosedTags", [new libjass.tags.PrimaryColor(new libjass.tags.Color(31, 23, 63, 1))]);
parserTest("null", "i", "tag_i", new libjass.parts.Italic(null));
});
parserTest("Dialogue", "{\\c&H3F171F&}", "dialogueParts", [new libjass.tags.PrimaryColor(new libjass.tags.Color(31, 23, 63, 1))]);
suite("Primary color tags - \\c and \\1c", function () {
parserTest("Just the tag", "1c&H3F171F&", "tag_1c", new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)));
parserTest("Just the tag", "c&H3F171F&", "tag_c", new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1)));
parserTest("Enclosed tag", "{\\c&H3F171F&}", "enclosedTags", [new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1))]);
parserTest("Dialogue", "{\\c&H3F171F&}", "dialogueParts", [new libjass.parts.PrimaryColor(new libjass.parts.Color(31, 23, 63, 1))]);
});
suite("Alpha tag - \\alpha", function () {
parserTest("Just the tag", "alpha&H00&", "tag_alpha", new libjass.tags.Alpha(1));
parserTest("Just the tag", "alpha&H00&", "tag_alpha", new libjass.parts.Alpha(1));
parserTest("Just the tag", "alpha00", "tag_alpha", new libjass.parts.Alpha(1));
});
suite("Font size tag - \\fs", function () {
parserTest("5", "fs5", "tag_fs", new libjass.parts.FontSize(5));
parserTest("null", "fs", "tag_fs", new libjass.parts.FontSize(null));
parserTest("Plus 5", "fs+5", "tag_fsplus", new libjass.parts.FontSizePlus(5));
parserTest("Plus null", "fs+", "tag_fsplus", null);
parserTest("Minus 5", "fs-5", "tag_fsminus", new libjass.parts.FontSizeMinus(5));
parserTest("Minus null", "fs-", "tag_fsminus", null);
});
suite("Transform tag - \\t", function () {
parserTest("No tags", "{\\t(100,200)}", "enclosedTags", [new libjass.parts.Comment("\\t(100,200)")]);
});
suite("Clip tags - \\clip and \\iclip", function () {
parserTest("Rectangular clip", "{\\clip(100,200,300,400)}", "enclosedTags", [new libjass.parts.RectangularClip(100, 200, 300, 400, true)]);
parserTest("Vector clip", "{\\clip(m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [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)
], true)]);
parserTest("Vector clip with scale", "{\\clip(1, m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [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)
], true)]);
parserTest("Inverted rectangular clip", "{\\iclip(100,200,300,400)}", "enclosedTags", [new libjass.parts.RectangularClip(100, 200, 300, 400, false)]);
parserTest("Inverted vector clip", "{\\iclip(m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [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)]);
parserTest("Inverted vector clip with scale", "{\\iclip(1, m 129 338 l 121 381 110 372 110 331)}", "enclosedTags", [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)]);
});
suite("Drawing instructions", function () {
parserTest("Normal", "m 984 425 l 985 445 l 985 542 l 973 544 b 983 524 987 496 984 459", "drawingInstructions", [
new libjass.parts.drawing.MoveInstruction(984, 425),
new libjass.parts.drawing.LineInstruction(985, 445),
new libjass.parts.drawing.LineInstruction(985, 542),
new libjass.parts.drawing.LineInstruction(973, 544),
new libjass.parts.drawing.CubicBezierCurveInstruction(983, 524, 987, 496, 984, 459)
]);
parserTest("Re-use previous instruction", "m 984 425 l 985 445 985 542 973 544 b 983 524 987 496 984 459", "drawingInstructions", [
new libjass.parts.drawing.MoveInstruction(984, 425),
new libjass.parts.drawing.LineInstruction(985, 445),
new libjass.parts.drawing.LineInstruction(985, 542),
new libjass.parts.drawing.LineInstruction(973, 544),
new libjass.parts.drawing.CubicBezierCurveInstruction(983, 524, 987, 496, 984, 459)
]);
parserTest("Leading space", " m 984 425", "drawingInstructions", [
new libjass.parts.drawing.MoveInstruction(984, 425)
]);
parserTest("Terminal space", "m 984 425 ", "drawingInstructions", [
new libjass.parts.drawing.MoveInstruction(984, 425)
]);
parserTest("Space on both ends", " m 984 425 ", "drawingInstructions", [
new libjass.parts.drawing.MoveInstruction(984, 425)
]);
parserTest("Empty", "", "drawingInstructions", [
]);
parserTest("One space", " ", "drawingInstructions", [
]);
parserTest("Two spaces", " ", "drawingInstructions", [
]);
});
});
+65
View File
@@ -0,0 +1,65 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var libjass = require("../libjass.js");
var assert = require("assert");
suite("Web worker", function () {
var workerChannel = null;
setup(function () {
workerChannel = libjass.webworker.createWorker();
});
suite("Parse", function () {
if (libjass.webworker.supported) {
test("Parse", function (done) {
this.customProperties = {
rule: "dialogueParts",
input: "{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!"
};
workerChannel.request(libjass.webworker.WorkerCommands.Parse, {
input: this.customProperties.input,
rule: this.customProperties.rule
}).then(function (promise) {
try {
assert.deepEqual(promise.result, [
new libjass.parts.Alignment(8),
new libjass.parts.Text("Are "),
new libjass.parts.Italic(true),
new libjass.parts.Text("you"),
new libjass.parts.Italic(false),
new libjass.parts.Text(" the one who stole the clock?!")
]);
done();
}
catch (ex) {
done(ex);
}
});
});
}
else {
test("Parse: Web workers not supported in this environment.");
}
});
});
+799
View File
@@ -0,0 +1,799 @@
/**
* 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 {
/**
* This class represents an ASS script. It contains the script properties, an array of Styles, and an array of Dialogues.
*/
export class ASS {
private _properties: ScriptProperties = new ScriptProperties();
private _styles: Map<string, Style> = new Map<string, Style>();
private _dialogues: Dialogue[] = [];
private _dialoguesFormatSpecifier: string[];
/**
* 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;
}
constructor() {
// Deprecated constructor argument
if (arguments.length === 1) {
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
}
}
/**
* Add an event to this ASS script.
*
* @param {string} line The line from the script that contains the new event.
*/
addEvent(line: string): void {
var typedTemplateArray = <TypedTemplateArray>[];
typedTemplateArray.formatSpecifier = this._dialoguesFormatSpecifier;
var typedTemplate = <TypedTemplate>parser.parse(line, "assScriptProperty", new parser.ParseNode(null, { contents: typedTemplateArray }));
this._addEvent(typedTemplate);
}
private _addEvent(line: TypedTemplate) {
if (line.type === "Dialogue") {
var dialogueTemplate = line.template;
if (libjass.verboseMode) {
console.log("Read dialogue: " + JSON.stringify(dialogueTemplate), dialogueTemplate);
}
// Create the dialogue and add it to the dialogues array
this.dialogues.push(new Dialogue(dialogueTemplate, this));
}
}
/**
* Creates an ASS object from the raw text of an ASS script.
*
* @param {string} raw The raw text of the script.
* @param {number=0} type The type of the script. One of the libjass.Format constants.
* @return {!libjass.ASS}
*/
static fromString(raw: string, type: Format = Format.ASS): ASS {
raw = raw.replace(/\r$/gm, "");
switch (type) {
case Format.ASS:
return ASS._fromASSString(raw);
case Format.SRT:
return ASS._fromSRTString(raw);
default:
throw new Error("Illegal value of type: " + type);
}
}
private static _fromASSString(rawASS: string): ASS {
var script = <Map<string, any>>parser.parse(rawASS, "assScript");
var result = new ASS();
// Get the script info template
var infoTemplate: Map<string, string> = script.get("Script Info");
if (libjass.verboseMode) {
console.log("Read script info: " + JSON.stringify(infoTemplate), infoTemplate);
}
// Parse the script properties
result.properties.resolutionX = parseInt(infoTemplate.get("PlayResX"));
result.properties.resolutionY = parseInt(infoTemplate.get("PlayResY"));
result.properties.wrappingStyle = parseInt(infoTemplate.get("WrapStyle"));
result.properties.scaleBorderAndShadow = (infoTemplate.get("ScaledBorderAndShadow") === "yes");
// Get styles from the styles section
(<TypedTemplateArray>script.get("V4+ Styles")).forEach(line => {
if (line.type === "Style") {
var styleTemplate = line.template;
if (libjass.verboseMode) {
console.log("Read style: " + JSON.stringify(styleTemplate), styleTemplate);
}
// Create the style and add it to the styles map
var newStyle = new Style(styleTemplate);
result.styles.set(newStyle.name, newStyle);
}
});
// Get dialogues from the events section
var events = <TypedTemplateArray>script.get("Events");
result._dialoguesFormatSpecifier = events.formatSpecifier;
events.forEach(line => result._addEvent(line));
return result;
}
private static _fromSRTString(rawSRT: string): ASS {
var script: any[] = parser.parse(rawSRT, "srtScript");
var result = new ASS();
// Parse the script properties
result.properties.resolutionX = 1280;
result.properties.resolutionY = 720;
result.properties.wrappingStyle = 1;
result.properties.scaleBorderAndShadow = true;
var newStyle = new Style(new Map<string, string>()
.set("Name", "Default")
.set("Italic", "0").set("Bold", "0").set("Underline", "0").set("StrikeOut", "0")
.set("Fontname", "").set("Fontsize", "50")
.set("ScaleX", "100").set("ScaleY", "100")
.set("Spacing", "0")
.set("Angle", "0")
.set("PrimaryColour", "&H0000FFFF").set("SecondaryColour", "&H00000000").set("OutlineColour", "&H00000000").set("BackColour", "&H00000000")
.set("Outline", "1").set("BorderStyle", "1")
.set("Shadow", "1")
.set("Alignment", "2")
.set("MarginL", "80").set("MarginR", "80").set("MarginV", "35")
);
result.styles.set(newStyle.name, newStyle);
script.sort((line1, line2) => line1["number"] - line2["number"]).forEach(line => {
result.dialogues.push(new Dialogue(new Map<string, string>()
.set("Style", "Default")
.set("Start", line.start).set("End", line.end)
.set("Layer", "0")
.set("Text",
(<string>line.text)
.replace(/<b>/g, "{\\b1}").replace(/<\/b>/g, "{\\b0}")
.replace(/<i>/g, "{\\i1}").replace(/<\/i>/g, "{\\i0}")
.replace(/<u>/g, "{\\u1}").replace(/<\/u>/g, "{\\u0}")
.replace(
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}"
).replace(/<\/font>/g, "{\\c}")
), result));
});
return result;
}
}
export enum Format {
ASS,
SRT
}
export enum WrappingStyle {
SmartWrappingWithWiderTopLine = 0,
SmartWrappingWithWiderBottomLine = 3,
EndOfLineWrapping = 1,
NoLineWrapping = 2,
}
/**
* This class represents the properties of an 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.
*
* @type {number}
*/
get wrappingStyle(): WrappingStyle {
return this._wrappingStyle;
}
/**
* The wrap style.
*
* @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;
}
}
export enum BorderStyle {
Outline = 1,
OpaqueBox = 3,
}
/**
* This class represents a single global style declaration in an ASS script. The styles can be obtained via the ASS.styles property.
*
* @param {!Map.<string, string>} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Name"] The name of the style
* @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: parts.Color;
private _secondaryColor: parts.Color;
private _outlineColor: parts.Color;
private _shadowColor: parts.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");
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 = parseFloat(template.get("Fontsize"));
this._fontScaleX = parseFloat(template.get("ScaleX")) / 100;
this._fontScaleY = parseFloat(template.get("ScaleY")) / 100;
this._letterSpacing = parseFloat(template.get("Spacing"));
this._rotationZ = parseFloat(template.get("Angle"));
this._primaryColor = <parts.Color>parser.parse(template.get("PrimaryColour"), "colorWithAlpha");
this._secondaryColor = <parts.Color>parser.parse(template.get("SecondaryColour"), "colorWithAlpha");
this._outlineColor = <parts.Color>parser.parse(template.get("OutlineColour"), "colorWithAlpha");
this._shadowColor = <parts.Color>parser.parse(template.get("BackColour"), "colorWithAlpha");
this._outlineThickness = parseFloat(template.get("Outline"));
this._borderStyle = parseInt(template.get("BorderStyle"));
this._shadowDepth = parseFloat(template.get("Shadow"));
this._alignment = parseInt(template.get("Alignment"));
this._marginLeft = parseFloat(template.get("MarginL"));
this._marginRight = parseFloat(template.get("MarginR"));
this._marginVertical = parseFloat(template.get("MarginV"));
}
/**
* 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(): parts.Color {
return this._primaryColor;
}
/**
* The alternate color of this style's font, used in karaoke.
*
* @type {!libjass.parts.Color}
*/
get secondaryColor(): parts.Color {
return this._secondaryColor;
}
/**
* The color of this style's outline.
*
* @type {!libjass.parts.Color}
*/
get outlineColor(): parts.Color {
return this._outlineColor;
}
/**
* The color of this style's shadow.
*
* @type {!libjass.parts.Color}
*/
get shadowColor(): parts.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;
}
};
/**
* This class represents a dialogue in an ASS script.
*
* @param {!Map.<string, string>} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Style"] The name of the default style of this dialogue
* @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;
this._style = ass.styles.get(template.get("Style"));
this._start = Dialogue._toTime(template.get("Start"));
this._end = Dialogue._toTime(template.get("End"));
this._layer = Math.max(parseInt(template.get("Layer")), 0);
this._rawPartsString = template.get("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;
}
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 parts of this dialogue.
*
* @type {!Array.<!libjass.parts.Tag>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
this._parsePartsString();
}
return this._parts;
}
/**
* @return {string} A simple representation of this dialogue's properties and tags.
*/
toString(): string {
return "#" + this._id + " [" + this._start.toFixed(3) + "-" + this._end.toFixed(3) + "] " + ((this._parts !== null) ? this._parts.join(", ") : this._rawPartsString);
}
/**
* Parses this dialogue's parts from the raw parts string.
*/
private _parsePartsString(): void {
this._parts = <parts.Part[]>parser.parse(this._rawPartsString, "dialogueParts");
this._alignment = this._style.alignment;
this._parts.forEach((part, index) => {
if (part instanceof parts.Alignment) {
this._alignment = (<parts.Alignment>part).value;
}
else if (part instanceof parts.Move) {
var movePart = <parts.Move>part;
if (movePart.t1 === null || movePart.t2 === null) {
this._parts[index] =
new parts.Move(
movePart.x1, movePart.y1, movePart.x2, movePart.y2,
0, this._end - this._start
);
}
}
else if (part instanceof parts.Transform) {
var transformPart = <parts.Transform>part;
if (transformPart.start === null || transformPart.end === null || transformPart.accel === null) {
this._parts[index] =
new parts.Transform(
(transformPart.start === null) ? 0 : transformPart.start,
(transformPart.end === null) ? (this._end - this._start) : transformPart.end,
(transformPart.accel === null) ? 1 : transformPart.accel,
transformPart.tags
);
}
}
});
if (libjass.debugMode) {
var possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && (<parts.Comment>part).value.indexOf("\\") !== -1);
if (possiblyIncorrectParses.length > 0) {
console.warn(
"Possible incorrect parse:\n" +
this._rawPartsString + "\n" +
"was parsed as\n" +
this.toString() + "\n" +
"The possibly incorrect parses are:\n" +
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);
}
}
/**
* A template object with a particular type.
*/
export interface TypedTemplate {
type: string;
template: Map<string, string>;
}
/**
* An array of TypedTemplate objects with the format specifier that was used to parse them.
*/
export interface TypedTemplateArray extends Array<TypedTemplate> {
formatSpecifier: string[];
}
/**
* Debug mode. When true, libjass logs some debug messages.
*
* @type {boolean}
*/
export var debugMode: boolean = false;
/**
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of debugMode.
*
* @type {boolean}
*/
export var verboseMode: boolean = false;
}
+204 -55
View File
@@ -22,97 +22,97 @@
"use strict";
interface CSSStyleDeclaration {
webkitAnimationDelay: string
webkitAnimationDuration: string
webkitAnimationName: string
webkitTransform: string
webkitTransformOrigin: string
webkitPerspective: string
interface Global {
Set: { new <T>(): Set<T>; prototype: Set<any> }
Map: { new <K, V>(): Map<K, V>; prototype: Map<any, any> }
}
interface Document {
fullscreenElement: Element
mozFullScreenElement: Element
webkitFullscreenElement: Element
}
var global: any = (0, eval)("this");
declare var global: Global; // Defined as a parameter of the anonymous function wrapper
module libjass {
export function removeElement(element: Element): void {
if (element.parentNode !== null) {
element.parentNode.removeChild(element);
}
}
/**
* 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.
*
* @template T
*/
class SimpleSet<T> implements Set<T> {
private _data: Object = Object.create(null);
private _elements: { [key: string]: T };
private _size: number;
/**
* @constructor
*/
constructor() { }
constructor() {
this.clear();
}
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): Set<T> {
var key = this._toKey(value);
var property = this._toProperty(value);
if (key === null) {
throw new Error("This Set implementation only supports string and number values.");
if (property === null) {
throw new Error("This Set implementation only supports Number and String values.");
}
this._data[key] = value;
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
* @return {boolean}
*/
has(value: T): boolean {
var key = this._toKey(value);
var property = this._toProperty(value);
if (key === null) {
if (property === null) {
return false;
}
return key in this._data;
return property in this._elements;
}
forEach(callbackfn: (value: string, index: string, set: Set<string>) => void, thisArg?: any): void {
Object.keys(this._data).map((key: string) => {
return this._data[key];
}).forEach((value: Object, index: number) => {
callbackfn.call(thisArg, value, value, this);
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
*/
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void {
Object.keys(this._elements).map((property: string) => {
var element = this._elements[property];
callbackfn.call(thisArg, element, element, this);
});
}
delete(value: string): boolean {
delete(value: T): boolean {
throw new Error("This Set implementation doesn't support delete().");
}
clear(): void {
throw new Error("This Set implementation doesn't support clear().");
}
/**
* @type {number}
*/
get size(): number {
throw new Error("This Set implementation doesn't support size.");
return this._size;
}
private _toKey(value: T): string {
if (typeof value == "number") {
private _toProperty(value: T): string {
if (typeof value === "number") {
return "#" + value;
}
else if (typeof value == "string") {
if (typeof value === "string") {
return "'" + value;
}
@@ -120,13 +120,162 @@ module libjass {
}
}
if (typeof Set === "undefined" || typeof Set.prototype.forEach !== "function") {
global.Set = SimpleSet;
/**
* Set to browser's implementation of Set if it has one, else set to libjass.SimpleSet
*
* @type {function(new:Set)}
*/
export var Set = global.Set;
if (Set === undefined || typeof Set.prototype.forEach !== "function") {
Set = <any>SimpleSet;
}
/**
* 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.
*
* @template K, V
*/
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
constructor() {
this.clear();
}
/**
* @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.
*/
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void {
var keysArray = Object.keys(this._keys);
for (var i = 0; i < keysArray.length; i++) {
var property = keysArray[i];
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
}
}
/**
* @type {number}
*/
get size(): number {
return this._size;
}
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;
}
}
/**
* Set to browser's implementation of Map if it has one, else set to libjass.SimpleMap
*
* @type {function(new:Map)}
*/
export var Map = global.Map;
if (Map === undefined || typeof Map.prototype.forEach !== "function") {
Map = <any>SimpleMap;
}
export function mixin(derived: any, mixins: any[]) {
mixins.forEach((mixin: any) => {
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
derived.prototype[name] = mixin.prototype[name];
});
});
}
}
var module: any;
if (module && module.exports) {
module.exports = libjass;
}
+298
View File
@@ -0,0 +1,298 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///<reference path="libjass.ts" />
interface Document {
currentScript: HTMLScriptElement;
}
interface WorkerGlobalScope {
postMessage(message: any): void;
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
}
declare var WorkerGlobalScope: {
prototype: WorkerGlobalScope;
new (): WorkerGlobalScope;
};
module libjass.webworker {
Object.defineProperty(webworker, "supported", {
value: typeof Worker !== "undefined",
configurable: true,
enumerable: true
});
/**
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling libjass.webworker.setup()
*/
export interface WorkerChannel {
/**
* Sends a request to the other side to execute the given command with the given parameters.
*
* @param {number} command
* @param {*} parameters
* @return {!libjass.webworker.WorkerPromise} A promise that will get resolved when the other side computes the result
*/
request(command: WorkerCommands, parameters: any): WorkerPromise;
}
/**
* Create a new web worker.
*
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
*/
export function createWorker(): WorkerChannel {
return new WorkerChannelImpl(new Worker(_scriptNode.src));
}
/**
* A promise returned by libjass.webworker.WorkerChannel.request()
*/
export interface WorkerPromise {
resolved: boolean;
result: any;
/**
* Registers a callback to run when this promise is resolved. If the promise is already resolved, the callback is called immediately.
*
* @param {function(*) } callback A function of the form (promise: *): void
*/
then(callback: WorkerPromiseCallback): void;
cancel(): void;
}
/**
* The signature of a callback called by WorkerPromise when it is resolved.
*/
export interface WorkerPromiseCallback {
(promise: WorkerPromise): void;
}
/**
* The commands that can be sent to or from a web worker.
*/
export enum WorkerCommands {
Response = 0
}
/**
* The signature of a handler registered to handle a particular WorkerCommand.
*/
export interface WorkerCommandHandler {
(parameters: any, response: WorkerResultCallback): void;
}
/**
* The signature of a callback called by a WorkerCommandHandler to report its result back to the caller.
*/
export interface WorkerResultCallback {
(error: any, result: any): void;
}
/**
* Registers a handler for the given worker command.
*
* @param {number} command The command that this handler will handle. Valid values are the values of libjass.webworker.WorkerCommands
* @param {function(*, function(*, *))} handler The handler. A function of the form (parameters: *, response: function(error: *, result: *): void): void
*/
export function _registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler) {
workerCommands.set(command, handler);
}
export function _registerClassPrototype(prototype: any): void {
prototype._classTag = classPrototypes.size;
classPrototypes.set(prototype._classTag, prototype);
}
var _scriptNode: HTMLScriptElement = null;
if (typeof document !== "undefined" && document.currentScript !== undefined) {
_scriptNode = document.currentScript;
}
var workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
var classPrototypes = new Map<number, any>();
interface WorkerCommunication {
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
postMessage(message: any): void;
}
interface WorkerRequestMessage {
requestId: number;
command: WorkerCommands;
parameters: any;
}
interface WorkerResponseMessage {
requestId: number;
error: any;
result: any;
}
/**
* Internal implementation of libjass.webworker.WorkerChannel
*
* @param {!*} comm The other side of the channel. When created by the host, this is the web worker. When created by the web worker, this is its global object.
*/
class WorkerChannelImpl implements WorkerChannel {
private _pendingRequests = new Map<number, WorkerPromiseImpl>();
constructor(private _comm: WorkerCommunication) {
this._comm.addEventListener("message", ev => this._onMessage(<string>ev.data), false);
}
request(command: WorkerCommands, parameters: any): WorkerPromise {
var promise = new WorkerPromiseImpl(this);
var requestId = promise.id;
this._pendingRequests.set(requestId, promise);
var requestMessage: WorkerRequestMessage = { requestId: requestId, command: command, parameters: parameters };
this._comm.postMessage(WorkerChannelImpl._toJSON(requestMessage));
return promise;
}
cancelRequest(requestId: number): void {
this._pendingRequests.delete(requestId);
}
private _respond(message: WorkerResponseMessage): void {
this._comm.postMessage(WorkerChannelImpl._toJSON({ command: WorkerCommands.Response, requestId: message.requestId, error: message.error, result: message.result }));
}
private _onMessage(rawMessage: string): void {
var message = <{ command: WorkerCommands }>WorkerChannelImpl._fromJSON(rawMessage);
if (message.command === WorkerCommands.Response) {
var responseMessage = <WorkerResponseMessage><any>message;
var promise = this._pendingRequests.get(responseMessage.requestId);
if (promise !== undefined) {
this._pendingRequests.delete(responseMessage.requestId);
promise.resolve(responseMessage.error, responseMessage.result);
}
return;
}
var requestMessage = <WorkerRequestMessage>message;
var commandCallback = workerCommands.get(requestMessage.command);
if (commandCallback === undefined) {
this._respond({ requestId: requestMessage.requestId, error: new Error("Unrecognized command: " + requestMessage.command), result: null });
return;
}
commandCallback(requestMessage.parameters, (error: any, result: any) => this._respond({ requestId: requestMessage.requestId, error: error, result: result }));
}
private static _toJSON(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
value._classTag = value._classTag;
}
return value;
});
}
private static _fromJSON(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && value._classTag !== undefined) {
var hydratedValue = Object.create(classPrototypes.get(value._classTag));
Object.keys(value).forEach(key => {
if (key !== "_classTag") {
hydratedValue[key] = value[key];
}
});
value = hydratedValue;
}
return value;
});
}
}
var inWorker = (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope);
if (inWorker) {
new WorkerChannelImpl(<WorkerGlobalScope><any>global);
}
class WorkerPromiseImpl implements WorkerPromise {
private static _lastPromiseId: number = -1;
private _id: number = ++WorkerPromiseImpl._lastPromiseId;
private _resolved: boolean = false;
private _result: any = null;
private _error: any = null;
private _callback: WorkerPromiseCallback = null;
constructor(private _channel: WorkerChannelImpl) { }
get resolved(): boolean {
return this._resolved;
}
get result(): any {
if (!this._resolved) {
throw new Error("Unresolved promise.");
}
if (this._error !== null) {
throw this._error;
}
return this._result;
}
then(callback: WorkerPromiseCallback): void {
this._callback = callback;
if (this._resolved) {
setTimeout(() => this._callback(this), 0);
}
}
cancel(): void {
if (this._resolved) {
return;
}
this._channel.cancelRequest(this._id);
}
get id(): number {
return this._id;
}
resolve(error: any, result: any): void {
this._resolved = true;
this._error = error;
this._result = result;
if (this._callback !== null) {
this._callback(this);
}
}
}
}