931 Commits

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

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

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

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

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

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

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

Fixes #65
2016-01-26 23:54:54 -08:00
Arnavion d2db781f82 Fixed typo in test name. 2016-01-25 13:21:16 -08:00
Arnavion 11d1b36fc3 Travis CI: Remove iojs and add node v4 and v5 2016-01-24 02:42:12 -08:00
Arnavion 509aa3b52d Begin v0.12.0 2016-01-24 02:38:23 -08:00
Arnavion d916a8dece Updated changelog and readme for v0.11.0 2016-01-24 02:30:32 -08:00
Arnavion ff9fa3954d Added doc comment that calling WebRenderer.resize is necessary for new renderers. 2016-01-23 11:00:01 -08:00
Arnavion 9b0e15cd78 Moved letterboxing code from WebRenderer to DefaultRenderer.
The initial implementation of letterboxing incorrectly assumed that subs should always be rendered with the same width:height ratio as the script resolution. In fact desktop renderers stretch the width and height individually, so the presence or absence of letterboxing can only be determined based on the original video dimensions. WebRenderer is abstracted from the original video, so it cannot do this.

So now WebRenderer.resize() takes additional left and top parameters and expects the caller to calculate the letterboxing and adjust width, height, left and top accordingly. DefaultRenderer does this.

Also removed the redundant width and height parameters from DefaultRenderer.resize() since it was always expected to resize to the video's width and height anyway.

Fixes #62
2016-01-23 10:46:47 -08:00
Arnavion 795bef96ac Removed fullscreen handling in DefaultRenderer.
It was hacky when it was first written - it required max z-index on the subs wrapper to be visible over the fullscreen video. Now it works on even fewer browsers.

Users who want subs on fullscreen video should implement their own fullscreen functionality to make both the video and the subs wrapper fullscreen.
2016-01-23 10:42:12 -08:00
Arnavion 45f081ba54 Reintroduce animation-fill-mode: both
If a subtitle with a fade-out doesn't get removed immediately after its animation end time, it would become completely visible for a brief moment and appear as a flash.

libjass used to use `animation-fill-mode: both` to counter this, but that didn't work in all browsers back then. Fades used to be rendered as two animations (one to fade in and the other to fade out) and some browsers would interpret this to mean that opacity should be 100% after the end time instead of 0%, effectively still causing a flash. So libjass [changed to using only one animation for fades and removed the use of animation-fill-mode.](https://github.com/Arnavion/libjass/commit/72df7fc)

The flash was technically still a problem, but libjass also changed to use a higher-resolution clock based on requestAnimationFrame, so the period between the end of the animation and the sub being removed from the DOM was usually too brief to cause a flash. However low-resolution clocks (250-500ms) like the one used by videojs-ass do cause the flash.

Since fades are rendered as a single animation now, they wouldn't have the problem of being interpreted as 100% opacity past the animation end time, so `animation-fill-mode: both` can be reintroduced to solve the flash problem.

Reported in SunnyLi/videojs-ass#3
2016-01-15 01:12:48 -08:00
Arnavion 308715a495 Outline color filter should use sRGB for color interpolation.
Fixes #61
2016-01-09 18:23:28 -08:00
Arnavion abbbd6bf04 Disable Firefox tests again.
Nightly is broken with Selenium's FF driver, and Marionette doesn't allow tests to access the page's JS environment yet.
2016-01-09 18:21:42 -08:00
Arnavion 569fce4072 Just don't add sourcemaps for generated code. Some sourcemap consumers like remap-istanbul get confused by it. 2016-01-05 03:34:14 -08:00
Arnavion 5b88230c32 Fixed typos in pollUntil calls. 2016-01-03 05:05:43 -08:00
Arnavion 097854f30f Removed unnecessary call to play(). Enabling an AutoClock should also make it start playing automatically if the clock source is providing ticks. 2016-01-01 17:06:20 -08:00
Arnavion 6fc6bcc4b2 Fixed {AutoClock, VideoClock}.{setEnabled, toggle} to call their respective enable() and disable() methods instead of just ManualClock's. 2016-01-01 17:06:11 -08:00
Arnavion 0045ebe3fb Propagate errors from failing to load the script from XHR. 2015-12-27 15:40:43 -08:00
Arnavion 1cefb68b47 Treat scripts that never had a Script Info section as malformed. 2015-12-27 15:39:07 -08:00
Arnavion 78bab74e11 Fall back to XHR if fetch() fails to fetch the script URL.
fetch() does not support blob: URLs in Chrome 47 and below - https://code.google.com/p/chromium/issues/detail?id=571286
2015-12-22 11:38:25 -08:00
Arnavion a43f2da4c9 Strongly typed Promise.reject 2015-12-22 11:38:17 -08:00
Arnavion 7ff81409c7 Make sure attached fonts are registered under all their names, but don't override existing declarations. 2015-12-20 18:17:50 -08:00
Arnavion 0661ada8e6 Simpler and safer TTF parser. 2015-12-20 16:16:47 -08:00
Arnavion ec1c180173 Moved data: URL creation out of Attachment. 2015-12-20 16:16:05 -08:00
Arnavion d5969cc2f5 Fixed FontFace constructor to be consistent between FF and Chrome.
Given a font family with strange characters like ? or [, Chrome is okay with using it as-is `new FontFace("???", "url")` but FF wants it quoted like it would be in a CSS @font-face declaration `new FontFace("\"???\"", "url")`

Originally the code always used the latter form to work on both browsers. However this actually doesn't work on Chrome because Chrome treats that as a separate font family from ???. That is, if a piece of text specifies its CSS font-family as `"???"` FF will match it but Chrome won't. Chrome instead treats `???` and `"???"` as separate font families and would want to match the CSS font family only to the former, not the latter.

The fix is to load both families and expect atleast one to succeed. On FF the unquoted one will fail with a SyntaxError and the quoted one will succeed. On Chrome both will succeed and only the unquoted one will be used by the rendered subs.
2015-12-20 04:02:10 -08:00
Arnavion b23f6e4e9f Added support for using attached fonts.
Attached fonts are loaded as data: URIs. Since ASS does not contain font family names for attached fonts (only the original filename which is useless), libjass will parse the font as a TTF to extract the font names from the name tables. The TTF parser is simple and naive, so this feature should not be used with untrusted fonts.

This feature is enabled only if RendererSettings.useAttachedFonts is true. It requires ES6 typed arrays (ArrayBuffer, DataView, Uint8Array, etc).
2015-12-20 03:48:39 -08:00
Arnavion da1e7ed52b Small invisible change with new Chrome. 2015-12-19 22:42:28 -08:00
Arnavion e6d3195df0 Added support for parsing ASS attachments. 2015-12-19 21:34:47 -08:00
Arnavion 81117ce07b Changed ASS parsing slightly to move change-of-section handling into a single place. 2015-12-19 21:30:17 -08:00
Arnavion 6dbee54f6a Fixed ASS.fromUrl to not ignore HTTP errors when using fetch() 2015-12-19 18:19:32 -08:00
Arnavion 5f9890d157 Updated typescript to v1.7.5 2015-12-16 00:11:26 -08:00
Arnavion db1201d84e Updated pngjs to v2.2.0 2015-12-16 00:11:13 -08:00
Arnavion f3ab2bc57f Use ts.parseConfigFile in CompilerHost, and other minor changes. 2015-12-11 01:27:42 -08:00
Arnavion a4c617f1df Removed unused import. 2015-12-11 00:27:28 -08:00
Arnavion e1b993a0ae Use document.fonts if available for easier preloading of fonts. 2015-12-03 22:49:56 -08:00
Arnavion 42db525b30 Preloading fonts improvements.
- RendererSettings.fontMap values can now be a single string that is the src attribute of a @font-face declaration unchanged. RendererSettings.makeFontMapFromStyleElement's returned map now uses this form.
- RendererSettings.fontMap values can now be an array of `url()` or `local()` values.

These two points make it possible for custom font faces to fall back to local fonts.

Bugfixes:
- If multiple URLs were specified for a @font-face, all of them would be downloaded and the last one's metrics would be used. Now as soon as one has been successfully downloaded (in order), the font metrics will be calculated.
- Failing to download any of the URLs specified for a @font-face would prevent calculating metrics from any others that were successfully downloaded, and prevent the renderer from ever becoming ready instead of just using fallback fonts or browser default fonts. Now failures are ignored.
- The font-measuring div would be left behind if calculating metrics failed for any reason. Now it'll always be removed.
2015-12-03 22:40:55 -08:00
Arnavion 76691cc52b Simplified UMD header.
- Removed `System.register()` call. SystemJS treats this as an AMD module and applies AMD shim anyway.
- Moved AMD `define()` call ahead of `module.exports` assignment.
- `require()` only needs to hold the exported value for each module.
2015-11-29 20:07:41 -08:00
Arnavion 82f3706eff Separating union type into overloads helps the compiler to determine generic for Promise.then 2015-11-26 17:24:58 -08:00
Arnavion a0ac2ba83b Updated npm to v3.x 2015-11-26 17:24:08 -08:00
Arnavion b89b5f76bb Append ZWNJ to every text part to force whitespace to force appropriate whitespace between adjacent nodes.
`white-space: pre-wrap` does not completely result in the same amount of whitespace between two letters if they were in the same node.

Since codepoints in separate nodes could not have combined into a ligature anyway, this won't break ligatures.

Fixes #58
2015-11-12 21:28:42 -08:00
Arnavion 2bfdcb582c Use larger default font size for SRT. 2015-11-12 21:04:42 -08:00
Arnavion 604129a10d Swallow BOMs in SRT parser too. 2015-11-12 21:04:24 -08:00
Arnavion 563141f1c4 Update baselines. 2015-11-12 20:42:56 -08:00
Arnavion a0d49f4ee5 Update Selenium and re-enable firefox tests. 2015-11-12 20:42:19 -08:00
Arnavion 23445a82c4 Added test for correct colors for translucent text. 2015-11-12 20:25:01 -08:00
Arnavion 0c51760fc5 Use 1 / primaryAlpha instead of 1e6 to convert SourceAlpha values to 1 for outlines.
Fixes #56
2015-11-04 11:16:53 -08:00
Arnavion 2ce0d7985a Fixed indent. 2015-11-04 01:24:12 -08:00
Arnavion f8eb111f89 \fscx and \fscy does not affect shadows. 2015-11-04 00:57:04 -08:00
Arnavion 9e9ba88ede Updated pngjs to v2.1.0 2015-11-04 00:07:41 -08:00
Arnavion 8ed209bba4 Use proper path to leadfoot. 2015-11-04 00:07:08 -08:00
Arnavion 0260014524 Fixed ScaleX and ScaleY being ignored for styles. 2015-10-13 15:05:52 -07:00
Arnavion c6cafd10d4 Calculate font metrics while preloading fonts.
The sync method of setting the fontSize div to the requested font family and measuring its offsetHeight doesn't work when the request font family is from a @font-face declaration. Atleast some browsers render the div with text in a locally installed fallback font and update it to the actual font in a future tick.

For example, if the metrics are being calculated for `Foo, Arial, Helvetica, sans-serif, 'Segoe UI Symbol'` where Foo is defined via a `@font-face` declaration, then the M glyph is rendered in Arial for that tick, and updated to Foo in a future tick.

This change adds a delay between setting the font on the div and measuring its offsetHeight, which requires making the metric calculation async. Since the renderer pipeline cannot be made async, this change also causes the font metric to be computed while the font is being preloaded, since preloading is already an async operation. The original sync method still remains to be able to calculate metrics for non-preloaded fonts; a note has been added that this will result in wrong sizes for these fonts unless they're already installed on the user's machine.

Reported in #55
2015-09-25 23:50:04 -07:00
Arnavion e46f0322ed (Belatedly) Begin v0.11.0 2015-09-25 22:12:42 -07:00
Arnavion 0ecb96c42a Omit /index from import statements. 2015-09-16 20:41:37 -07:00
Arnavion 191a34d060 Updated TypeScript to v1.6.2 2015-09-16 20:41:10 -07:00
Arnavion 484997e3fc Value of \fs+ and \fs- is not optional. 2015-09-16 20:38:41 -07:00
Arnavion 7482220201 Split PromiseReaction into two interfaces so that it doesn't have a union call signature. 2015-09-02 19:51:07 -07:00
Arnavion 6606413eef Updated intern to v3.x again. 2015-09-02 09:30:01 -07:00
Arnavion 6dce371011 Fixed warning about unused parameter. 2015-09-01 00:27:55 -07:00
Arnavion 6b2800ef70 Clarified the reason for "ujs:unreferenced" comments.
Also removed unused code.
2015-09-01 00:26:12 -07:00
Arnavion 29c187f076 Trailing whitespace. 2015-08-31 23:31:40 -07:00
Arnavion 7d7217007a Don't quote generic font families for the font-family property.
Fixes #50
2015-08-31 23:30:25 -07:00
Arnavion e783afc43c Fixed WebRenderer.preRender() to not violate its return-type contract. 2015-08-26 10:51:35 -07:00
Arnavion 4183228262 Not just for HTML5 video. 2015-08-26 10:26:59 -07:00
Arnavion b92b63f94f Added SimplePromise.reject and .race 2015-08-19 10:19:37 -07:00
Arnavion 4ddab3a2d9 Fixed some missing JSDoc comments.
TS emitter uses a different code path for the first non-copyright-header JSDoc comments in a file. Easier to work around by just not making the first AST node in the file require extra JSDoc comments by reordering them.
2015-08-13 16:06:02 -07:00
Arnavion 3b381ac925 Fixed JSDoc. 2015-08-13 14:07:16 -07:00
Arnavion 1d803dd1b8 Removed unused parameters. 2015-08-13 14:07:06 -07:00
Arnavion 074f3ec723 Use CSS opacity property when not in SVG mode and all alphas are the same value.
This helps to avoid rendering translucent outlines or shadows behind translucent text for the common case where the alpha for primary, outline and shadow colors are the same value.

Reported in #52
2015-08-05 23:07:19 -07:00
Arnavion caf6701c7b Split outline code into separate functions. 2015-08-05 23:05:46 -07:00
Arnavion 99d72711be Added SVG definitions in a .d.ts for safety. 2015-08-05 22:40:11 -07:00
Arnavion 7ed52215cb //refs are unnecessary. 2015-08-05 22:31:02 -07:00
Arnavion 50e9140ee3 Updated async-build to v0.2.0 2015-08-05 09:31:03 -07:00
Arnavion 6f81c4dc17 Revert intern back to v2.x until https://github.com/theintern/intern/issues/459 is fixed.
This reverts commit cec2f13361.
2015-08-04 12:52:52 -07:00
Arnavion 7a1dc0582c Fixed flakiness in AutoClock functional test. 2015-08-04 11:08:07 -07:00
Arnavion d9f2431414 Use UglifyJS.mangle_properties to mangle private properties. 2015-08-04 10:55:24 -07:00
Arnavion cec2f13361 Updated intern to v3.x 2015-08-04 10:33:41 -07:00
Arnavion 6939e10c9b Fixed SVG outlines to not render behind the original text.
Reported in #52
2015-08-03 03:43:38 -07:00
Arnavion 2ee04485d6 Use a colored background for functional tests to catch wrong alpha. 2015-08-03 03:23:23 -07:00
Arnavion d98f3ea94f Fixed \r to use target style's alpha instead of original style's alpha. 2015-08-02 12:08:56 -07:00
Arnavion 83b6ae9f64 Fixed SpanStyles.*alpha to default to the style's alpha.
Fixes #53
2015-08-02 11:55:15 -07:00
Dador 92397aa351 Add getter for SpanStyles.outlineColor and SpanStyles.shadowColor 2015-08-02 11:01:08 -07:00
Arnavion 5e64329a8c Fixed the name of the kfx functional test. 2015-08-01 16:58:47 -07:00
Arnavion c0e0dec1fb Scaling transform was using the wrong scale to modify the font size.
Reported in #47
2015-08-01 16:58:32 -07:00
Arnavion 90c781c4e2 Fixed parser to handle the value of \fscx and \fscy to be optional. 2015-08-01 16:43:28 -07:00
Arnavion 74636c4098 Temporarily disable firefox for functional tests - webdriver is broken with latest nightly. 2015-08-01 16:25:43 -07:00
Arnavion bc989a5761 Fixed SimplePromise.all to reject if any parameter rejects. 2015-08-01 16:07:55 -07:00
Arnavion 586ff43bec Redundant type annotation is no longer needed. The TS glitch has been fixed. 2015-08-01 15:44:58 -07:00
Arnavion e856423ee1 More let -> const 2015-08-01 15:30:10 -07:00
Dador 8e903ba50e Add ability to change fallback fonts. 2015-08-01 15:08:48 -07:00
Dador 68d898764e Add explicit line-height to .libjass-font-measure 2015-08-01 21:48:44 +03:00
Arnavion 9bde3b9982 Fixed typo. 2015-07-20 08:33:20 -07:00
Arnavion c4c935175e Replaced gulp with async-build.
Much smaller and faster.
2015-07-20 08:17:39 -07:00
Arnavion c7986b0eab Rewrote SimplePromise to be more compatible with ES6 spec. 2015-07-20 08:14:52 -07:00
Arnavion a1dfc2f79d Updated TypeScript to v1.5.3 2015-07-20 08:14:13 -07:00
Arnavion bda06a8385 Removed unnecessary parameter. 2015-07-12 00:52:12 -07:00
Arnavion 0dd0df60f5 Added tests for ManualClock and AutoClock. 2015-07-12 00:50:35 -07:00
Arnavion d4956466e4 Fixed ManualClock.disable() calling this.pause() incorrectly.
Fixes #44
2015-07-12 00:48:01 -07:00
Arnavion 0b4c66f88a ManualClock.tick() should not fire a tick event if ticking to the current time. 2015-07-12 00:47:24 -07:00
Dador f21b90b061 Skip unrecognized text in drawing instructions 2015-07-09 22:07:55 -07:00
Dador 1df6c44007 Treat first unnamed section as "Script Info" 2015-07-09 21:24:49 -07:00
Arnavion 53f96a2367 Added test for StreamParser.minimalASS property. 2015-07-09 21:23:26 -07:00
Arnavion f6b4b705e0 Implemented experimental (hacky) support for {\t} 2015-07-03 21:40:21 -07:00
Arnavion fc3c62af0a Added corresponding getters for SpanStyles setters. 2015-07-03 21:29:24 -07:00
Arnavion fe2f7bde9e Use a dialogue-level <svg> element for each subtitle div instead of a renderer-level one under the subs wrapper. 2015-07-03 21:28:38 -07:00
Arnavion 070ef1aee3 Removed unused member. 2015-07-03 20:44:39 -07:00
Arnavion d05e8015b4 Append styles one at a time instead of all at once. 2015-07-03 18:06:16 -07:00
Arnavion cb52d62158 Use a dialogue-level <style> element for each subtitle div instead of a renderer-level one in the document <head>.
The speedup comes from having a tinier <style> element, so appending animation styles to the element is faster.
2015-07-03 15:21:27 -07:00
Arnavion 232dddf6e5 DOM operations are faster than parseFromString. 2015-07-03 14:35:00 -07:00
Arnavion 53d9721f18 Fixed JSDoc type annotations for parts.Transform properties. 2015-06-30 22:29:26 -07:00
Arnavion 981132c08a Use linear interpolation to calculate font size for a target line height.
Since line height is approximately linear with font size, simple interpolation with known points that are sufficiently large is accurate enough. This means only two font sizes need to be calculated per font, instead of one per font per line height.
2015-06-29 04:30:08 -07:00
Arnavion ff1739fe67 Cache subsWrapper's offsetWidth. Getting it with this._subsWrapper.offsetWidth is expensive. 2015-06-28 12:28:31 -07:00
Arnavion 3df0b5bab7 Some other doc fixes for AutoClock 2015-06-26 23:30:52 -07:00
Arnavion f9a8791280 Renamed AutoClock constructor parameter currentTimeUpdateMaxDelay to autoPauseAfter 2015-06-26 23:30:41 -07:00
Arnavion 3140391c16 Render paragraphs in docs. 2015-06-26 23:29:14 -07:00
Arnavion 6fa2b2c581 Split multiple vars per declaration into one var per declaration for readability. 2015-06-19 02:53:58 -07:00
Arnavion c099015f6a Use rest parameters. 2015-06-19 01:51:27 -07:00
Arnavion 9e039ee13d Explicit SystemJS loader support. 2015-06-18 09:59:51 -07:00
Arnavion 8a53a56157 Explicitly tell the AMD loader we don't want require, exports and module by default. 2015-06-18 00:42:56 -07:00
Arnavion ff04dbab24 Added note for using with jspm. 2015-06-18 00:42:06 -07:00
Arnavion a9ec919118 Use loader.packages instead of loader.map 2015-06-17 21:57:44 -07:00
Arnavion 676f167180 Line endings. 2015-06-07 17:48:37 -07:00
Arnavion 3a277f9295 Fixed build break. 2015-06-07 05:24:18 -07:00
Arnavion c469780107 Fixed sporadic functional test failures.
Sometimes the browser view did not update immediately after ticking the video clock, so the screenshot would be of a stale state and not match the expected state.
2015-06-07 05:05:41 -07:00
Arnavion 539fa58daf Normalize style names per libass. 2015-06-07 04:47:54 -07:00
Arnavion 3df3eb7c11 Moved large ASS text to separate files.
The "ASS with BOM" test still needs the BOM added explicitly in the test, since in browser tests, dojo/text uses XHR to fetch the file and that strips the BOM.
2015-06-07 04:33:16 -07:00
Arnavion eebbad7fd1 Fixed Promise constructor to require resolution value and rejection reason. 2015-06-06 15:39:55 -07:00
Arnavion 0cd9dbe150 selenium 2.45.0 -> 2.46.0
Fixes crash on startup with Nightly but still requires e10s disabled.
2015-06-06 02:02:07 -07:00
Arnavion 5b783850e8 let -> const 2015-06-06 02:02:06 -07:00
Arnavion 0c2fecd1cc Updated .gitignore 2015-06-06 02:02:06 -07:00
Arnavion d1b17204e9 Don't persist firefox profile. 2015-05-28 09:38:54 -07:00
Arnavion 0292acc20c Loosen or tighten style property validation based on what libass does. 2015-05-27 01:59:35 -07:00
Arnavion f1e820819c Normalize template property names to lower case to match libass behavior. 2015-05-27 01:58:22 -07:00
Arnavion c28c722263 Use the same defaults for Style properties as libass. 2015-05-27 01:55:02 -07:00
Dador 64ac166485 Use a "Default" style as a fallback for missing styles. 2015-05-27 03:51:50 +03:00
Dador 8cabd87c60 Swallow exceptions from parsing styles and events in the parser. 2015-05-27 03:48:05 +03:00
Dador 6986531992 Add support for "V4 Styles" (same as "V4+ Styles") 2015-05-27 03:43:35 +03:00
Arnavion 42282c2807 Use "sans-serif" as default font name for new styles. 2015-05-26 22:40:11 -07:00
Arnavion f2765085c4 Fixed variable name in valueOrDefault 2015-05-26 22:39:33 -07:00
Arnavion 8e28a95e4e Handle async exceptions thrown by client-side code in functional tests. 2015-05-24 21:52:43 -07:00
Arnavion 782d34c3eb Use require.resolve to get paths inside node_modules. 2015-05-20 00:27:57 -07:00
Arnavion b2d9b757e2 Generate firefox profile dynamically. 2015-05-18 11:38:09 -07:00
Arnavion 41cb005ec6 Use ES6 const and let. 2015-05-13 20:41:04 -07:00
Arnavion d84573038d Made UjsSourceMap more like the original function in UJS. 2015-05-13 20:27:31 -07:00
Arnavion e047bed5b9 Updated sax to v1.x 2015-05-11 21:29:49 -07:00
Arnavion 3a9528479b Updated changelog for v0.10.0 2015-05-05 21:48:54 -07:00
Arnavion ba7f990190 Unnecessary semi-colons. 2015-05-03 22:51:01 -07:00
Arnavion 0c76be560e Make functional tests use Selenium's default firefox profile instead of the user's actual profile.
Nightly needs additional properties to disable e10s, since that breaks the web driver.
2015-05-03 22:20:30 -07:00
Arnavion 01214bb3f6 Build gulplib with -noImplicitAny 2015-05-02 02:25:54 -07:00
Arnavion 2f183071b6 Minor fixups. 2015-05-02 02:22:17 -07:00
Arnavion 8e12db7564 "private" is a boolean, not a string. 2015-04-29 22:13:08 -07:00
Arnavion e2eb93748d SimplePromise doesn't need to hold on to the resolver. 2015-04-28 20:18:32 -07:00
Arnavion c8a2ea8a1e Also run functional tests on Nightly. 2015-04-28 02:15:19 -07:00
Arnavion 124d309a4d Added note on how to run the selenium server for functional tests. 2015-04-28 01:00:50 -07:00
Arnavion f407b40abb Also run functional tests on IE 11. 2015-04-28 00:43:16 -07:00
Arnavion be09db8218 Make the paths to the baselines browser-specific. 2015-04-28 00:36:07 -07:00
Arnavion 36e99a7853 Fixed polyfills tests on IE 11. 2015-04-28 00:20:02 -07:00
Arnavion bfe847e3bd Apply animation delays only on the elements that have animations.
Fixes subs not appearing in IE because node.children is undefined when node is a <svg> element (which didn't need to be iterated over in the first place).
2015-04-27 10:08:52 -07:00
Arnavion 87c1050089 Added Ping WorkerCommand to fix random failure in web worker tests.
The Worker constructor returns before the web worker is ready to respond to messages, and even before its script has been downloaded. This was causing problems with the web worker test because it would take more than the individual test's timeout for the worker to become ready.

This went unnoticed before because the code to create the worker ran as soon as the suite was loaded by the test runner (as opposed to when it was executed), so the worker would begin being created then. Since the web worker suite ran last, by the time the other suites finished running the worker would actually be ready and the tests would have no problem.

However this stopped being true when the code to create the worker channel for the suite was moved to its before() function, since now it would run when the suite was being executed. As a result, the first test would time out while waiting for the worker to be created.
2015-04-27 03:17:00 -07:00
Arnavion f3d7d4b681 Run setup code inside suite setup callback. 2015-04-27 02:08:49 -07:00
Arnavion 2d65d19fc6 baseUrl for intern's loader is $PWD by default. 2015-04-27 02:00:55 -07:00
Arnavion 498a6a7cbe Use ASS.fromReadableStream in ASS.fromUrl if ReadableStream is available. 2015-04-27 01:52:25 -07:00
Arnavion 52bd57395d Added ASS.fromReadableStream to parse an ASS from a ReadableStream. 2015-04-27 01:51:42 -07:00
Arnavion 618ca069fc Added stream implementation for ReadableStream (the response of window.fetch(), etc.) 2015-04-27 01:51:24 -07:00
Arnavion f3a07a3984 Split stream parsers into their own module. 2015-04-27 01:47:13 -07:00
Arnavion eaf2c40e1d Add note that the XHR passed to XhrStream should not have had open() called on it already. 2015-04-26 23:52:49 -07:00
Arnavion aa0f15b348 Moved TS inheritance shim to its own module. 2015-04-22 23:39:02 -07:00
Arnavion 9c5900ebc0 Updated README. 2015-04-21 21:47:28 -07:00
Arnavion b9e6611b1b Swallow utf-8 BOM at the beginning of the string being parsed, if any.
fs.readFileSync does not strip the BOM, so the stream parser needs to ignore it.

Also fixed the remaining async parser tests to work with intern's test timeout feature.
2015-04-21 21:43:26 -07:00
Arnavion dd52b024d0 Fixed indentation in SVG filter text. 2015-04-21 00:22:35 -07:00
Arnavion 5ec1fd9499 Minor fixups. 2015-04-21 00:12:13 -07:00
Arnavion 36d881b601 Actually implement the tweening behavior of AutoClock.
Apparently I never actually made it do what it was supposed to...
2015-04-17 20:51:14 -07:00
Arnavion e9019bd6c0 Rewrote TS tooling and doc generator in TS.
Found and fixed a few bugs as a bonus.
2015-04-16 06:15:58 -07:00
Arnavion 798482c3d1 Added missing implements clause for EventSource. 2015-04-15 16:07:17 -07:00
Arnavion 07641a3842 Removed unused imports. 2015-04-15 16:03:28 -07:00
Arnavion b1b59869ec Added tests for generated docs. 2015-04-15 14:34:14 -07:00
Arnavion 67e963f498 Swallow setRate calls when playback rate doesn't actually change.
Nightly seems to send a ratechanged event on seeking with the rate value unchanged.
2015-04-15 13:20:58 -07:00
Arnavion b6974ce17f Removed EventSource boilerplate from Clock implementations. 2015-04-15 12:18:14 -07:00
Arnavion b0bbfb3b9b Implement AutoClock in terms of ManualClock. 2015-04-15 11:56:28 -07:00
Arnavion 3d383a956a Fixed typo - test-browser depends on libjass.js 2015-04-15 11:53:36 -07:00
Arnavion 79d5ac7789 Implemented AutoClock.
Fixes #38
2015-04-14 21:44:28 -07:00
Arnavion 91524988b4 Split renderers/clocks into modules. 2015-04-14 20:49:10 -07:00
Arnavion 39990739bd Added code for browser tests using ChromeDriver.
Added a test for kfx. More tests coming soon.

Fixes #21
2015-04-13 00:21:32 -07:00
Arnavion dde42d8d8a Send Stop and Play clock events on seek.
Fixes #37
2015-04-13 00:03:33 -07:00
Arnavion c11a9900e8 Fixed JSDoc link. 2015-04-12 16:35:00 -07:00
Arnavion e811f8c05f Also emit module ID next to module number. 2015-04-12 15:49:53 -07:00
Arnavion f6e5a3fd80 Changed the WorkerCommandHandler callback to return a promise of the value instead of taking a response callback. 2015-04-12 15:34:54 -07:00
Arnavion 9958465b34 Split web-worker into modules and reduced its API surface. 2015-04-12 14:50:13 -07:00
Arnavion a9d6700ad6 Allow setting libjass.{Set, Map, Promise} to the polyfills even if the runtime has an implementation. 2015-04-12 12:48:33 -07:00
Arnavion de6cc6bbc4 Fixed JSDoc comments. 2015-04-10 05:36:50 -07:00
Arnavion 292c5c3dee Renaming of underscore-prefixed function args must be done after all figure_out_scope calls, or the renaming is lost. 2015-04-10 04:12:02 -07:00
Arnavion 30cffd6725 Removed Webpack.
UJS is tinier and its output is easier to control.
2015-04-10 03:31:32 -07:00
Arnavion a9275aecd6 Fix build break. 2015-04-09 20:39:30 -07:00
Arnavion e670c491c2 Remove some more unnecessary stuff from webpack boilerplate. 2015-04-09 20:33:49 -07:00
Arnavion 8025fa324e Minor changes. 2015-04-09 19:15:03 -07:00
Arnavion 51d2715e0a Removed some more unnecessary casts inside instanceof checks. 2015-04-09 19:13:31 -07:00
Arnavion 67ecdb8151 Throw an error from SimpleSet and SimpleMap constructors when given a non-array iterable instead of silently ignoring it. 2015-04-09 18:06:51 -07:00
Arnavion 84fa5b6423 Converted most uses of Array.forEach to for-let-of 2015-04-09 18:06:08 -07:00
Arnavion b3290a1bd4 Mark {\q} as supported, and clarify that only smart line wrapping is not implemented. 2015-04-09 14:04:03 -07:00
Arnavion d31f459704 Fix EndOfLineWrapping to use white-space: pre-wrap 2015-04-09 14:03:31 -07:00
Arnavion c401eb4043 Fixed typo. 2015-04-09 13:34:03 -07:00
Arnavion 5c0140cd95 Use ES6 shorthand object literals. 2015-04-09 12:33:44 -07:00
Arnavion 66557f247c Split parser into modules and reduced its API surface. 2015-04-09 12:30:56 -07:00
Arnavion b8aca3fc53 Removed some vars from UJS list of unused vars to ignore. 2015-04-05 02:30:53 -07:00
Arnavion e4671348c5 Updated TypeScript to version 1.5.0-alpha 2015-04-09 10:41:17 -07:00
Arnavion ef223d83a8 Updated TS compiler and walker to work with TS 1.5 and ES6 modules. 2015-04-05 02:26:34 -07:00
Arnavion aa64b76214 Use ES6 modules. 2015-04-05 02:26:33 -07:00
Arnavion 979dec810e Updated TypeScript to version 1.5.0-alpha 2015-04-05 02:26:32 -07:00
Arnavion 866a44d46f Add JSDoc comments to all module members, not just those that are in a namespace. 2015-04-05 00:58:32 -07:00
Arnavion 4c4eea9b47 ParseNode doesn't need to be exported. 2015-04-05 00:24:34 -07:00
Arnavion 696730244c Don't reparent module members into the corresponding namespace unless they're exported. 2015-04-05 00:24:17 -07:00
Arnavion 5065ab4658 Changed how modules and privates are showed in docs. 2015-04-05 00:23:23 -07:00
Arnavion 4f8474aa66 Moved ASTModifier to TypeScript.gulp() 2015-04-05 00:22:31 -07:00
Arnavion f6a9f63213 Fixed indentation. 2015-04-03 22:03:23 -07:00
Arnavion 352b26320e No longer publish the whole project to npm.
- A new build task copies the build outputs to a dist/ directory, along with package.json, README, CHANGELOG and LICENSE. package.json is modified to only have relevant fields. This directory will then get published to npm.
- The dist/ directory will also be used to build the Github release .zip file that gets used with bower.
2015-03-22 16:35:52 -07:00
Arnavion aef4466a03 Minor package.json edits. 2015-03-22 16:34:40 -07:00
Arnavion bb8c106b5a Remove mocha files from .gitignore 2015-03-22 16:33:45 -07:00
Arnavion d399ff88f9 Added a test that libjass.min.js is actually minified.
The other tests already test that it functions identically.
2015-03-21 19:50:09 -07:00
Arnavion 2d5534042b Moved suite-specific variable inside suite. 2015-03-21 19:49:25 -07:00
Arnavion 83bbe54273 Redundant word. 2015-03-21 19:37:22 -07:00
Arnavion f94c95b38a Also run tests against minified library when running npm test. 2015-03-21 17:17:30 -07:00
Arnavion 28491b8268 Make webworker tests work with intern's async tests timeouts.
Timeout doesn't work when directly returning ES6 promises because of https://github.com/theintern/intern/issues/337
2015-03-21 16:49:22 -07:00
Arnavion b7e3b98a13 Test infrastructure fixes.
- Use paths relative to baseUrl instead of the individual test files.
- Use require.toUrl() for getting the path to libjass.js
- Moved parser-test.js under tests/support, per intern's recommended directory structure.
2015-03-21 16:48:12 -07:00
Arnavion 7e69289727 Rename libjass.ts back to index.ts
Watch target has been fixed to run webpack now, so this is no longer needed.
2015-03-21 00:38:56 -07:00
Arnavion aabc163160 Watch task now runs webpack and UJS fixup to work with intern's loader. 2015-03-08 12:04:34 -07:00
Arnavion 099c812e4b Switch from mocha to intern. 2015-03-08 01:47:51 -08:00
Arnavion b98eeadcdd Error objects can't be sent across a WorkerChannel. 2015-03-08 00:42:52 -08:00
Arnavion 83e125fe36 Split web-renderer.ts into multiple files. 2015-03-07 19:10:18 -08:00
Arnavion 80ff9679cf UglifyJS.fixup() now reports the correct positions of unused variables.
Also made the list of unused variables to ignore a parameter.
2015-03-07 19:08:03 -08:00
Arnavion edda987835 Ignore all files ending with "references.d.ts" 2015-03-07 18:39:52 -08:00
Arnavion f48f5736dd Implemented support for {\k} 2015-03-07 17:37:21 -08:00
Arnavion 463f63c1cb Add getters for SpanStyles.secondaryColor and secondaryAlpha. 2015-03-07 17:36:41 -08:00
Arnavion 0c425859b9 Maintain an animation collection for each span. 2015-03-07 17:35:57 -08:00
Arnavion 27136eff07 Allow setting animation properties on any element, not just the top-level one. 2015-03-07 17:33:32 -08:00
Arnavion d32da0126e Always create WebRenderer._animationStyleElement 2015-03-07 17:30:31 -08:00
Arnavion 2a53165151 Determine the start and end times from the set of keyframes, not the dialogue. 2015-03-07 14:26:32 -08:00
Arnavion e17fb75f6c Parse karaoke tags durations as centi-seconds, not seconds. 2015-03-07 14:25:41 -08:00
Arnavion 93a5209e85 TypeScript 1.4 no longer requires explicit casts after an instanceof check. 2015-03-07 14:24:14 -08:00
Arnavion e22a447cfe Fixed call to npm.load() 2015-02-15 19:37:24 -08:00
Arnavion b6e31a68a2 Also test on node 0.12 and iojs. 2015-02-15 19:23:08 -08:00
Arnavion 8a61f500a8 Lock stable depedencies by major version. 2015-02-15 02:17:39 -08:00
Arnavion b1ebe70f3d Watch task works again after external module reorganization. 2015-02-15 01:58:09 -08:00
Arnavion 6d1d6d7aa3 Renamed index.ts to libjass.ts 2015-02-15 01:56:15 -08:00
Arnavion 05fdc87824 Cache dynamically required dependencies and remove unused import. 2015-02-15 01:13:09 -08:00
Arnavion 8e1ab7ed00 Tests reorganization.
- Moved tests into tests/specs/
- Use mocha for browser tests instead of custom code.
2015-02-15 00:40:27 -08:00
Arnavion b2ff78a037 Fixed @link annotations for unreachable members to use the new format. 2015-02-14 02:29:37 -08:00
Arnavion 96c4c09bd7 Fixed default export not replacing the original member. 2015-02-14 02:24:50 -08:00
Arnavion ef8d638494 Added back support for showing docs for things that aren't reachable from the root namespace. 2015-02-14 02:24:15 -08:00
Arnavion 538543b0e8 (Belatedly) begin v0.10.0 2015-02-14 01:39:54 -08:00
Arnavion ed2d262608 Wrap code that checks for iterable parameter support in Map and Set constructors in a try-catch.
Some browsers like Safari throw a TypeError if they don't support this parameter instead of just ignoring it.

Fixes #34
2015-02-13 21:47:36 -08:00
Arnavion bbca3726d3 README update - Added requirements section, removed planned improvements section, moved links section to the end. 2015-01-31 15:48:36 -08:00
Arnavion 63c258b106 Allow overriding libjass.{Set, Map, Promise} again.
Also omit their definitions from default library and use our own for additional safety.
2015-01-29 02:46:47 -08:00
Arnavion 443570baa7 Reorganization.
- Moved source files under src/ and build outputs under lib/
- Switched from internal to external modules
- Use webpack to merge individual modules into one libjass.js
2015-01-28 21:57:08 -08:00
Arnavion b58ec66203 Removed unused variable. 2015-01-16 18:13:10 -08:00
Arnavion a93afd1ffb Use template strings. 2015-01-16 18:03:36 -08:00
Arnavion 71151b265d Updated typescript to v1.4.1
- Union types.
- Switched default lib to lib.es6.d.ts
2015-01-16 18:00:08 -08:00
Arnavion 534c2cfc4d Separated out SetConstructor, MapConstructor and PromiseConstructor. 2015-01-12 22:15:35 -08:00
Arnavion c505b9cea5 Updated mocha to 2.1.x 2015-01-12 22:14:50 -08:00
Arnavion 46bd989252 Fixed JSDoc comments for libjass.renderers.Keyframe 2015-01-12 21:58:41 -08:00
Arnavion 8511c3f39c Fixed Bluebird extension methods.
???
2014-12-29 01:43:07 -08:00
Arnavion baa9bd58b4 Added pointer to parser tests. 2014-12-28 22:46:36 -08:00
Arnavion c45b647cdd Added tests for ASS.fromString() 2014-12-28 22:44:58 -08:00
Arnavion 34b067a18e Added shim for assert(value, message) 2014-12-28 22:43:56 -08:00
Arnavion 39ee4dfd34 Show test lines with original whitespace. 2014-12-28 21:43:03 -08:00
Arnavion 2f2a19ee7e Use classList instead of className. 2014-12-28 21:42:30 -08:00
Arnavion 49e70f68af Use process.nextTick or MutationObserver instead of setTimeout(0), if available. 2014-12-28 19:25:33 -08:00
Arnavion 88149e8dc3 Refactored promise resolution. 2014-12-28 19:16:31 -08:00
Arnavion 0371028b01 Refactored Global interface. 2014-12-28 19:16:30 -08:00
Arnavion 04011260f9 Use before_script instead of script to build docs, or else Travis doesn't run npm test 2014-12-28 02:15:33 -08:00
Arnavion 12f869effc Fixes for libjass.Promise
- Make SimplePromise use the current libjass.Promise implementation in case it has been overridden after a SimplePromise was already created.
- Fixed SimplePromise.all() to work with both promises and non-promises in its parameter.
2014-12-28 01:59:21 -08:00
Arnavion e03f0ea0d6 Use travis container-based infrastructure. 2014-12-27 19:18:39 -08:00
Arnavion 49562a8920 Implemented @implements annotations in JSDoc and documentation generator. 2014-12-23 22:34:27 -08:00
Arnavion 7e4cf218ff Use fake source files to implement auto-generated JSDoc comments. 2014-12-23 22:19:30 -08:00
Arnavion 091a39a900 Various fixes for typescript gulp module.
- Don't cache files that couldn't be read.
- Don't silently ignore some errors.
- Call typechecker while compiling, not while emitting.
- Simplify linker's handling of global namespace.
2014-12-23 22:02:21 -08:00
Arnavion 93753bdcde Don't consider the video paused until 100ms have passed since its currentTime hasn't changed.
requestAnimationFrame callbacks can happen more frequently than the video's currentTime property updates, leading to the pause detector incorrectly thinking the video has been paused and unpaused in the next iteration. On some browsers like Nightly, this happens very frequently because the currentTime is sometimes updated as infrequently as every 60ms. Every time this happens, the clock (and the overlying renderer) switch between paused and playing states, causing errors to gradually accumulate in the times of any animations.

This change adds a timestamp to VideoClock to record when a pause is first suspected. The clock doesn't actually enter the paused state unless the video's currentTime doesn't update for 100ms since this timestamp.

The reason for the large value of 100ms is empirically based on Firefox's above-mentioned behavior. Chrome seems to update it every 30-40ms which is probably once every video frame (23.976fps). IE seems to update it on every iteration of requestAnimationFrame (15-20ms) which suggests it's generating the currentTime independent of actual frames.
2014-12-18 02:43:34 -08:00
Arnavion 859eddb9af Fixed bug introduced from previous refactoring. 2014-12-18 00:11:04 -08:00
Arnavion 2e7ef509a1 Minor refactoring in XhrStream. 2014-12-17 22:05:32 -08:00
Arnavion 16865b5dd8 Ignore non-property lines when parsing script info section. 2014-12-17 01:00:36 -08:00
Arnavion 5c36832fd0 Fixed JSDoc annotation for Stream.nextLine() 2014-12-09 22:41:42 -08:00
Arnavion 879d85ac55 fulfilledHander is optional for Promise.then() 2014-12-09 22:21:51 -08:00
Arnavion e6a2f1b6bc Only look for Constructors and Interfaces when resolving types. 2014-12-09 22:19:32 -08:00
Arnavion 5c69780a87 Handle functions missing a return type specifier when checking for a missing @return annotation. 2014-12-09 22:18:58 -08:00
Arnavion b8706a1dcd Removed unused branch. Interfaces are not emitted. 2014-12-09 22:16:17 -08:00
Arnavion 55d0817352 Fixed output of generics for @extends annotations. 2014-12-09 22:15:52 -08:00
Arnavion 4bfacf2a79 Updated README. 2014-12-07 21:06:51 -08:00
Arnavion ac0faab286 Updated changelog for v0.9.0 2014-11-27 00:21:36 -08:00
Arnavion cdf8c59c81 Updated mocha to v2.0.x 2014-11-27 00:21:08 -08:00
Arnavion 10ad819efc Moved font map example to RendererSettings.makeFontMapFromStyleElement 2014-11-26 23:59:25 -08:00
Arnavion 59a4d471cf Removed leftover mention of demo code. 2014-11-26 23:47:28 -08:00
Arnavion 4d3c37a353 Removed mentions of index.xhtml from BUILD.md and moved font map example to JSDoc.
Fixes #31
2014-11-26 23:41:41 -08:00
Arnavion 4696467fc1 Added support for code blocks in JSDoc descriptions. 2014-11-26 23:41:29 -08:00
Arnavion 87e3e25038 Re-ordered classes based on importance. 2014-11-26 23:10:49 -08:00
Arnavion 46f414aa91 Fixed build break. 2014-11-26 10:30:16 -08:00
Arnavion f47ec8a5d7 Fixed line height passed to SpanStyles._getFontSize 2014-11-26 01:16:40 -08:00
Arnavion 08a9e156d3 Set line-height on consecutive newlines. 2014-11-26 01:15:53 -08:00
Arnavion 106a7872d0 Fixed perspective check and changed the value to closer mimic VSFilter. 2014-11-26 01:15:00 -08:00
Arnavion 47b6613601 Added RendererSettings.enableSvg to control the use of SVG filters for outlines and blur.
This reintroduces some of the pre-SVG filter code that used a ton of text-shadows for the same effect.
2014-11-25 02:02:54 -08:00
Arnavion 8011022c5e Added build task to update the demo in gh-pages. 2014-11-23 22:42:42 -08:00
Arnavion b4d3f21b96 Use Set and Map constructors that taken in an array of initial values.
IE11 doesn't support this parameter and so will fall back to using SimpleSet / SimpleMap even though it does have Set / Map.
2014-11-23 22:33:24 -08:00
Arnavion cc67654d7a Use default values for some Style and Dialogue properties if they're not present in the original template.
Fixes #30
2014-11-18 00:05:45 -08:00
Arnavion 08b76b72ef Actually swap the parameters to WebRenderer. 2014-11-17 23:12:16 -08:00
Arnavion b72fd99871 Warn about unused variables. 2014-11-17 23:11:54 -08:00
Rodger Combs ce5895a932 Parse color and alpha values more loosely; correct tests accordingly
Fixes #28
2014-11-14 21:23:06 -08:00
Arnavion 8bccf4c791 Fixed AMD check to also test for define.amd 2014-11-14 00:59:44 -08:00
Arnavion eb23363bf0 Moved the demo to http://arnavion.github.io/libjass/demo/index.xhtml 2014-11-13 02:36:34 -08:00
Arnavion 69a981ac3f Make the settings parameter optional for the renderer constructors.
This changes the order of parameters for WebRenderer. The constructor tries to detect the old usage to avoid breaking backward compatibility and logs a warning.
2014-11-13 02:34:16 -08:00
Arnavion 2f36f34960 Mark some methods as protected. 2014-11-12 23:58:59 -08:00
Arnavion 18f0d0463c Support protected in JSDoc annotator and doc generator. 2014-11-12 23:58:22 -08:00
Arnavion 2932fbac12 Updated typescript to v1.3.0 2014-11-12 21:18:46 -08:00
Arnavion fec7bd504b Clocks now report a playback rate, and WebRenderer takes this into account for calculating animation timings.
Fixes #26
2014-11-10 04:02:24 -08:00
Arnavion 04699eb800 Removed unused variable. 2014-11-09 23:37:54 -08:00
Arnavion 37fcb59ee8 Fixed libjass.js containing two license headers. 2014-11-08 17:51:17 -08:00
Arnavion d6c66b41ca Added a changelog.
Fixes #25
2014-11-08 17:37:25 -08:00
Arnavion c05ede82b3 Don't rely on undefined being false-y. 2014-11-08 16:27:13 -08:00
Arnavion 72f221db59 Use Promise.all() in some places. 2014-11-08 16:06:55 -08:00
Arnavion 6d1b29628a Added SimplePromise.all() 2014-11-08 16:06:42 -08:00
Arnavion 7111648548 JSDoc fixes. 2014-11-02 17:10:50 -08:00
Arnavion 621d2b7efc Removed unused method left behind in 985e9c8 2014-11-02 16:51:10 -08:00
Arnavion 9c599e4a92 SimplePromise now passes all Promises/A+ tests.
Fixes #24
2014-11-02 04:14:24 -08:00
Arnavion 2608d59885 Generate RequireJS-friendly wrapper. 2014-10-25 22:57:03 -07:00
Arnavion 43d3002b63 Added link to renderer tests issue to README. 2014-10-25 18:01:00 -07:00
Arnavion f7fc52a6d7 Stream parsers for ASS and SRT. 2014-10-25 17:58:27 -07:00
Arnavion d842e4fa3b Updated README for the new API. 2014-10-25 17:55:32 -07:00
Arnavion 985e9c8de8 Changed SRT parser to a stream parser as well. 2014-10-25 17:05:28 -07:00
Arnavion cb2faea9d9 The whole-file ASS parser is now a simpler version that reads line-by-line from a stream.
- Added ASS.fromUrl and ASS.fromStream that can load an ASS from a URL and from a libjass.parser.Stream respectively.
- ASS.fromString, ASS.fromUrl and ASS.fromStream all return a Promise<ASS>. This is a breaking change for ASS.fromString
- Added ASS.addStyle that can be used to dynamically add Style objects to an ASS object.
2014-10-25 16:57:49 -07:00
Arnavion 887655c058 Moved DeferredPromise to utility.ts 2014-10-25 14:36:11 -07:00
Arnavion dc284f9992 Implemented Promise.resolve() in SimplePromise 2014-10-25 14:35:47 -07:00
Arnavion 062dab65d4 Fixed line endings. 2014-10-25 03:13:57 -07:00
Arnavion 73d0b94459 Fixed one instance of accessing a property with string indexer instead of dot-notation. 2014-10-10 23:27:55 -07:00
Arnavion 75ecb533b2 Move the license header out of the wrapper. 2014-10-10 23:27:36 -07:00
Arnavion c4f04b12ee Emit a space after the "function" keyword of anonymous functions. 2014-10-10 23:19:43 -07:00
Arnavion 385d0a7992 Fixed some JSHint errors. 2014-10-10 09:57:09 -07:00
Arnavion 6efd6c4202 Updated TypeScript to v1.1
AST parser
- Support interfaces
- Support enums

JSDoc generator
- Generate @template JSDoc annotations using type information
- Generate @enum JSDoc annotations using type information

Documentation generator
- Show enums
- Show generics in base type references and parameter type references
- Show all private methods and un-exported functions when "Show private" is selected
- Support @{link}
- Bugfix - Don't generate two elements with the same ID for properties.
2014-10-06 19:10:59 -07:00
Arnavion aa05790c8e Drain both fulfill and reject handlers at the same time when resolving or rejecting a SimplePromise. 2014-09-20 21:39:55 -07:00
Arnavion 2916af4972 Reject the corresponding deferred when cancelling a request. 2014-09-20 20:16:15 -07:00
Arnavion 1c5bf4375e Workaround for https://github.com/mishoo/UglifyJS2/pull/549 2014-09-12 21:53:28 -07:00
Arnavion 8810ff2076 Replaced WorkerPromise with browser's Promise implementation if available, and a proper SimplePromise otherwise. 2014-09-12 20:03:35 -07:00
Arnavion ea4fba1fdb Added test for webworker parse failure. 2014-09-12 19:25:03 -07:00
Arnavion 3f5c8b6cfd Added parameter to libjass.createWorker() to specify the path to libjass.js, in case the browser doesn't support document.currentScript 2014-09-12 19:24:49 -07:00
Arnavion 3dda004d23 Don't call response callback with its own exception. 2014-09-12 15:40:08 -07:00
Arnavion ff3e85dda1 Put TOC in a <nav> instead of a <section> 2014-09-03 22:40:06 -07:00
Arnavion 05e692b380 Actually use the parameter passed to the documentation generator task. 2014-08-27 00:09:03 -07:00
Arnavion 9a65557adc Align copyright header in libjass.min.js correctly. 2014-08-25 19:06:38 -07:00
Arnavion 188a349947 Added @link JSDoc annotations. 2014-08-24 12:44:03 -07:00
Arnavion 8497fccdb4 Fixed JS error when location.hash is empty or not a valid id. 2014-08-24 12:03:18 -07:00
Arnavion 199af87d77 Added missing @return and @type JSDoc annotations. 2014-08-24 11:51:38 -07:00
Arnavion 49545fbc00 Fixed missing and incorrect JSDoc. 2014-08-24 00:00:22 -07:00
Arnavion 125b2b280b If usage is too wide, generate horizontal scrollbars on the usage fieldset instead of on the whole page. 2014-08-23 23:59:36 -07:00
Arnavion 54cf2673f4 Build docs in Travis CI build. 2014-08-23 18:44:44 -07:00
Arnavion 8fa92017c5 Added missing JSDoc. 2014-08-23 13:21:30 -07:00
Arnavion a053e6a44a Wrap error-like objects into errors. 2014-08-21 02:24:43 -07:00
Arnavion e3ed3e82fb Removed some unnecessary semi-colons. 2014-08-21 02:24:42 -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
Arnavion 29f2ba12ab v0.2.0 2013-09-11 19:03:46 -07:00
Arnavion 04e2cd83d5 Tests can now be via "jake test" or "npm test" 2013-09-11 19:01:41 -07:00
Arnavion 1a4a75ecd0 Added generics to LazySequence and friends. 2013-09-11 09:34:48 -07:00
Arnavion 4ba4d88fdb More outdated comments. 2013-09-11 01:34:11 -07:00
Arnavion e95a1914d5 Fixed outdated comment. 2013-09-11 01:16:29 -07:00
Arnavion c2130be5ed Major refactoring.
- Polyfills are now registered on a "global" reference instead of "window"
- Added a module.exports line. As a result of this and the above change, libjass can now be loaded as a node module.
- As a side-effect of this change, utility.ts must be the first file in the output. This determinism means the other files don't need to declare "use strict", and the Jakefile doesn't need to strip them.
- Renamed Iterable to LazySequence and replaced Array.ToIterable() with libjass.Lazy(Array)
- Replaced Iterator.forEach() and Iterator.toArray() extensions with LazySequence.toArray()
- Removed polyfill for parseInt that parses strings starting with 0 as octal, since libjass requires ES5 anyway and ES5 prohibits that behavior.
- As a result of the above change, String.startsWith() extension is no longer needed.
- Replaced HTMLDivElement.remove() extension with libjass.removeElement(Element)
2013-09-11 01:07:37 -07:00
Arnavion 02c7eeac57 Don't hide internal compiler errors.
ICE's aren't recognized as instances of Error because TypeScript is loaded in a new V8 context. Because of this, node.js's event emitter displays a generic error instead. By wrapping the ICE in a local Error object and throwing that, the ICE's message and stack can be displayed.
2013-09-10 00:46:55 -07:00
Arnavion 0ba4cccbcc Moved all of the parsing of the ASS format to ass.pegjs 2013-09-08 18:57:03 -07:00
Arnavion c4dfa57e9a "animation-play-state: paused" requires "!important" 2013-09-08 18:43:49 -07:00
Arnavion 27e31d9993 Renamed "dialogue" PEG.js rule to "dialogueParts" 2013-09-08 18:05:44 -07:00
Arnavion 07560acd6a Some more advanced AST fiddling using UglifyJS
- Remove repeated top-level "use strict" directives.
- Remove repeated top-level "var libjass" statements.
- Remove unused variables, unused functions, and terminal unused function arguments.
- Mangle private members.
- Correctly detect the first license header to preserve.

Resulting minified script is 5% smaller than before.
2013-09-08 15:08:19 -07:00
Arnavion 7a95b892cc Use vm API instead of undocumented internal Module._compile method.
Miscellaneous Jakefile tweaking.
2013-09-08 00:40:51 -07:00
Arnavion 559497306b Modify TypeScript's tsc.js in memory to expose a compiler API, and use that instead of shelling out to tsc 2013-09-06 02:20:31 -07:00
Arnavion 42fd3065de libjass now ships with a default renderer that does most of what index.js did. It initializes the layer div's, preloads fonts and draws Dialogue's based on the current video time. 2013-09-03 23:57:06 -07:00
Arnavion baa4db9bea Rewrote tests framework API to be like Mocha and node's assert module. 2013-09-03 00:52:55 -07:00
Arnavion 33092a2049 Show warnings during UglifyJS2 compress stage. 2013-08-31 00:04:54 -07:00
Arnavion f1cb24fafd Fixed outdated comments. 2013-08-30 22:28:24 -07:00
Arnavion ed607fa643 Use UglifyJS2 to combine the output of tsc and the PEG.js parser source. 2013-08-30 14:27:53 -07:00
Arnavion b13bcb22b9 Better Jakefile. 2013-08-30 14:27:27 -07:00
Arnavion e37723771f Be explicit about TypeScript version. Show detailed errors from npm install. 2013-08-30 13:25:50 -07:00
Arnavion 200aad5b6b Fix tests to use the complete libjass.js since individual .js files are no longer generated. 2013-08-30 13:25:30 -07:00
Arnavion 0e3f13e8ed Removed unused method declaration and moved non-module-specific code outside of module definition. 2013-08-30 13:25:08 -07:00
Arnavion 6834bc05b5 Unset private in package.json and added npm's log files to .gitignore 2013-08-29 01:35:34 -07:00
Arnavion 9dda1af3fd Added a Jakefile and package.json to make building easier. Just "npm install" to get both libjass.js and libjass.min.js
UglifyJS2 supports composed source maps, so it's no longer necessary to run with individual .js files for each .ts file.
2013-08-29 01:20:05 -07:00
Arnavion 711afed9a0 Typo. 2013-08-26 23:44:03 -07:00
Arnavion ed6d59c761 Readme had outdated information about how index.js gets the script URL. 2013-08-26 23:43:23 -07:00
Arnavion 66fcdccd3a Fixed harmless off-by-one error. 2013-08-26 02:51:34 -07:00
Arnavion 5a31848b78 Use Object.create(null) instead of {} to create Object's that are used as maps. 2013-08-26 02:50:52 -07:00
Arnavion f2f46a32c5 Typo. 2013-08-25 18:20:05 -07:00
Arnavion ba56740aae Removed the example code from index.js that builds the PEG.js parser in the browser. The parser should now be built on the server.
Since is what a user should do in production anyway, having code that builds the parser in the browser was unnecessary and confusing. This does add an additional build dependency of PEG.js.
The minified script should include the parser in the same file, i.e., libjass.js and ass.pegjs.js should be minifed together into libjass.min.js. The instructions for minification have been updated to reflect this.
Tests have been updated likewise, and now have prettier output.
2013-08-25 18:14:32 -07:00
Arnavion b399acfc6a Don't start a new span after \N 2013-08-22 19:01:09 -07:00
Arnavion d004541668 Make fullscreen work on Nightly - the mozfullscreenchange event is only fired on document, not on the element that becomes full-screen. 2013-08-22 18:59:44 -07:00
Arnav Singh d5c2e75805 Some typos and other minor changes in readme. 2013-08-21 11:25:45 -07:00
Arnavion 913a4ddd90 Added a bunch of info to the readme. 2013-08-21 02:57:45 -07:00
Arnavion 802ba25da6 Implemented support for dynamically scaling subtitles.
The scale factor can now be changed at any time using ASS.scaleTo().
Subtitles are now also visible when the video is made full-screen. (Works on Nightly and latest Chrome dev. Doesn't work on IE 11.)
2013-08-21 01:43:58 -07:00
Arnavion 28a64e574e Added link to IRC channel to readme. 2013-08-20 22:19:47 -07:00
Arnavion e6f7353645 Made BUILD.md more verbose. index.xhtml now uses libjass.js by default instead of the individual compiled JS files. 2013-08-20 21:33:28 -07:00
Arnavion c51b6216dc Updating readme - pre-rendering subtitles has been implemented. 2013-08-20 21:04:05 -07:00
Arnavion a9440dd6b2 Smaller headings in the readme. 2013-08-20 21:03:32 -07:00
Arnavion d26281a38b Typo. 2013-08-20 09:01:56 -07:00
Arnavion 1a49c072e5 Reduce some of the duplicate spam in text shadows. 2013-08-19 03:20:16 -07:00
Arnavion f0cb226672 Don't append empty spans to the subtitle div. Wait until they have content in them. 2013-08-19 03:10:16 -07:00
Arnavion e8a97c5f12 Implemented \xbord and \ybord, which required rewriting the border code.
Also made some fixes in SpanStyles w.r.t. default values.
2013-08-19 03:10:02 -07:00
Arnavion 5c76785971 Fixed regression introduced by 8190573abe - whitespace-only spans no longer collapse to zero width. 2013-08-19 02:43:05 -07:00
Arnavion cfc6e597e8 Minor fixes.
Fixed vertical margin calculation to use scaleY instead of scaleX.
Fixed default font scale values were not being read from style.
Fixed the border being drawn even when no outline or blur is specified.
2013-08-18 23:10:48 -07:00
Arnavion 7e27777341 Renamed tags.StrikeOut to tags.StrikeThrough and Style.strikethrough to Style.strikeThrough 2013-08-18 18:49:37 -07:00
Arnavion bb6997094a Refactored handling styles on the subtitle div's spans into a separate class. 2013-08-18 18:48:32 -07:00
Arnavion 4200e4f4c4 Skip lines beginning with semi-colon in the script as comments. 2013-08-18 17:58:51 -07:00
Arnavion fd710ab29f Implemented \fsp and "Spacing" line style. 2013-08-18 17:55:13 -07:00
Arnavion cf2fabbc9f Renamed Tag classes to reflect their behavior instead of the underlying ASS tag. 2013-08-18 17:53:15 -07:00
Arnavion 37a53bba46 Renamed PEGjs rules to reflect their tags. 2013-08-18 17:33:35 -07:00
Arnavion 8190573abe Implemented \fscx and \fscy 2013-08-18 17:19:05 -07:00
Arnavion 0869d0c1ad Created a libjass.verboseMode flag. 2013-08-18 17:06:55 -07:00
Arnavion 24c8654ed4 Include libjass.ts instead of individual *.ts 2013-08-18 15:44:46 -07:00
Arnavion eb4fb14b79 Renamed DialogueParser interface to libjass.Parser 2013-08-18 15:38:21 -07:00
Arnavion a7dd5e82a7 Refactored ASS-parsing code to be more easily extensible in handling new supported properties.
Style, Info and Dialogue are now passed a template object containing key-value pairs which they can extract values from as they wish, instead of requiring ASS::new() to extract them.
2013-08-18 15:21:27 -07:00
Arnavion 5a8707f81f Renamed KeyframeCollection.toCSS() to toString() 2013-08-18 13:14:36 -07:00
Arnavion bbee8a28f8 Merged the filters in the newSubs pipeline into a single filter. 2013-08-18 13:07:11 -07:00
Arnavion 391ed9c8a5 Implemented the ability to pre-render subs.
Upside: draw() is simply a cloneNode() of the pre-rendered div, so it's faster.
Downside: Each Dialogue now holds a reference to a DOM element, increasing memory usage.

Perhaps a future improvement can use a heuristic of how complex the Dialogue is to render, so that only those Dialogue's that take time to render are pre-rendered and saved, while simpler Dialogue's are re-rendered everytime they're displayed.
2013-08-17 13:35:23 -07:00
Arnavion a81ad14452 Added comments to newSubs pipeline. 2013-08-17 13:25:46 -07:00
Arnavion fed74b69a1 Updated tests to use the new API. 2013-08-17 11:38:04 -07:00
Arnavion 478540260f Added example usage of combined JS to index.xhtml 2013-08-17 00:20:09 -07:00
Arnavion d188075668 Added explanatory comments for the suspicious empty if blocks. 2013-08-16 23:50:51 -07:00
Arnavion b44f846672 Implemented \alpha, \1a and \3a 2013-08-16 23:42:12 -07:00
Arnavion 2c44e25cde Prettification 2013-08-16 22:17:08 -07:00
Arnavion b5a964a4af Set and String.endsWith aren't used in libjass. Moved them to index.js
Also replaced the single use of Set iterator with Set.forEach
2013-08-16 22:12:38 -07:00
Arnavion da9c7650ed String.match() is unnecessary when String.replace() can do the job. 2013-08-16 22:10:59 -07:00
Arnavion e8d11e7938 Don't let custom window.Iterator() support arbitrary objects incorrectly. 2013-08-16 22:08:49 -07:00
Arnavion 6337a07943 Split off tags classes into a separate file. 2013-08-16 20:36:36 -07:00
Arnavion 9e4be57f9b Added a debug-mode warning for dialogues that contain a comment with a backslash as this might be the result of mis-parsing an ASS tag as a comment. 2013-08-16 20:16:19 -07:00
Arnavion 451ad0446e Renamed libjass.ASS.debugMode flag to libjass.debugMode 2013-08-16 20:15:59 -07:00
Arnavion e53b4077c2 Added more debug logging. 2013-08-16 20:15:44 -07:00
Arnavion cb8a5e148b Set the vendor-specific CSS properties before standard ones. 2013-08-16 20:15:31 -07:00
Arnavion 70dac3c492 Renamed SimpleSet's private methods to start with an underscore. 2013-08-16 20:14:54 -07:00
Arnavion 602d71de1a Override native parseInt instead of creating a new parseInteger function. The native function only needs to be replaced if it treats strings starting with 0 as octal.
Wrapped utility.ts in a module wrapper to prevent locals from leaking.
2013-08-16 20:14:30 -07:00
Arnavion dd21352c0b Only check if a \pos wrapper is needed at the end of Dialogue.draw()
This removes the need to maintain a currentSpanContainer reference separate from the main sub wrapper.
Also replaced hard-to-verify \pos and transform-origin calculations with switch-case blocks.
2013-08-16 20:14:05 -07:00
Arnavion 543339574e Replaced static ASS.parse method with constructor. 2013-08-16 01:50:12 -07:00
Arnavion f5e45d708e Added missing return types in function definitions. 2013-08-16 01:49:42 -07:00
Arnavion 51ce2bc209 String.endsWith should not think "abc" ends with "defg" 2013-08-16 01:49:06 -07:00
Arnavion 88edb8cc8c Fixed typo in type of animation styles element. 2013-08-16 01:18:34 -07:00
Arnavion 8ef96274e1 Converted to Typescript because Closure Compiler Advanced gives good warnings but makes the code too ugly and requires too much baby-sitting to be worth it. 2013-08-08 00:04:34 -07:00
Arnavion f986e4e4b2 Removed unused variable. 2013-08-07 16:27:28 -07:00
Arnavion 2f7fc37644 Fixed incorrect transform origin calculation introduced by cfa02c9851 2013-08-07 11:42:13 -07:00
Arnavion ef96da0a81 Typos. 2013-08-03 01:10:46 -07:00
Arnavion 15b886b93f Strict equality check for -1 in ASS.parse() 2013-08-03 00:39:43 -07:00
Arnavion ff4c9221b5 Don't include index.js when building minified file.
Added JSDoc expose's to expose the API and removed incorrect use of templates on Iterable and friends.
2013-08-03 00:21:23 -07:00
Arnavion aab212e199 A Set.iterator() implementation that uses Set.forEach if that is available. Other Iterator- and Set-related JSDoc/CC fixes. 2013-07-29 00:34:53 -07:00
Arnavion ed8513af96 Don't add dialogues as a JS property to the subtitle div's. Use a data attribute containing the dialogue id instead.
The ASS constructor doesn't sort dialogues now. Instead, the renderer makes a shallow copy of the dialogues array and sorts it instead.
2013-07-28 18:32:06 -07:00
Arnavion b73eba2edf Prevent CC from renaming Set.prototype.iterator and minor JSDoc fixes. 2013-07-28 18:24:35 -07:00
Arnavion 4e985c1adf Removed demo web configs. 2013-07-26 09:15:04 -07:00
Arnavion bf32cca5b0 JSDoc and minified file made using Closure Compiler. 2013-07-25 23:23:29 -07:00
Arnavion 2def4b7e4f Moved demo video and fonts directories out of repository. 2013-07-25 23:22:14 -07:00
Arnavion 45c2dfc28f A Dialogue no longer maintains a relationship to the sub div it drew to. 2013-07-17 22:35:03 -07:00
Arnavion 2399f5e34e Added debug logging for video state changes. 2013-07-17 22:34:41 -07:00
Arnavion af7f8d1390 Apache 2.0 license. 2013-07-17 21:09:04 -07:00
Arnavion d4868b8865 Fixed typo in readme. 2013-07-17 02:22:42 -07:00
Arnavion c03f7c034b Prettifying. 2013-07-16 22:32:32 -07:00
Arnavion 2d0f1eb11b Compute Dialogue alignment and animation keyframes on demand. 2013-06-22 23:04:29 -07:00
Arnavion d77537fedd Clamp Dialogue.layer before calling constructor. 2013-06-22 22:55:02 -07:00
Arnavion f693c1ab85 Preloading fonts only used in the subs seems to have already been done. 2013-06-22 15:35:06 -07:00
Arnavion 196f0b3e31 Removed backward reference from Dialogue to parent ASS. 2013-06-22 15:28:56 -07:00
Arnavion e15cd63dac Added debug mode - activated by ?debug in querystring. 2013-06-22 15:14:38 -07:00
Arnavion fbc500fd4b Added animation styles element to the HTML instead of dynamically adding it via Javascript. 2013-06-22 10:55:48 -07:00
Arnavion 3a70e5dd75 Refactoring. 2013-06-22 07:35:31 -07:00
Arnavion a3398f3d80 Revert "Play/pause video on click."
This reverts commit 812c7169bf. It interferes with the browser's in-built video controls.
2013-06-20 02:47:31 -07:00
Arnavion 5fd1f4bd26 Combined Dialogue.create into Dialogue constructor. 2013-06-19 23:54:42 -07:00
Arnavion fc0536b20a Prettier output for parser tests. 2013-06-19 22:59:01 -07:00
Arnavion 1d74884780 Removed unnecessary wrapper around pegjs parser. 2013-06-19 22:57:09 -07:00
Arnavion 6c3045ebbf Updated link to ASS documentation in readme. 2013-06-14 10:07:07 -07:00
Arnavion cbcafe3b3c Started adding parser tests. 2013-06-08 04:14:44 -07:00
Arnavion e9ec9f83fc Moved code to merge consecutive Text tags into ass.pegjs 2013-06-08 04:12:18 -07:00
Arnavion 0243fa4b3e Put spaces between colors in rgba(...). 2013-06-08 04:09:05 -07:00
Arnavion 812c7169bf Play/pause video on click. 2013-06-08 00:41:01 -07:00
Arnavion 20f9ad0973 Moved Tags namespace under ASS. 2013-06-08 00:26:17 -07:00
Arnavion b646a05e1a Style consistency. 2013-06-08 00:19:55 -07:00
Arnavion 6757f074a4 \fn's parameter array was not being converted to a string. 2013-06-08 00:18:41 -07:00
Arnavion a5b4bebdeb \r's parameter array was not being converted to a string.
Conflicts:
	ass.pegjs
2013-06-08 00:13:00 -07:00
Arnavion 64e6d88b19 \an parameter isn't optional. 2013-06-08 00:10:07 -07:00
Arnavion 382317eb69 Added enumerable: true to all properties. 2013-06-07 23:45:42 -07:00
Arnavion e9a3abe717 \b can be parseInt'd instead of parseFloat'd. 2013-06-07 23:38:28 -07:00
Arnavion 815b860eca Moved Tag definitions to parser.js 2013-06-07 23:26:43 -07:00
Arnavion 7bb65cda61 Prettier Dialogue.toString() 2013-06-07 23:26:05 -07:00
Arnavion 3bd865d13d Removed unnecessary Info.ass and Style.ass properties. 2013-06-07 23:24:41 -07:00
Arnavion 23b421d217 Removed redundant custom Set.toArray method. 2013-06-07 23:23:45 -07:00
Arnavion 1079ff1ec6 Continuation of 0fbe91fa0f: PegJS and Tags classes cleanup.
Moved String.toColor() into the pegjs.
Added a pretty-printing toString() to Tag's.
2013-06-06 03:21:29 -07:00
Arnavion 0aa56a4f2c Consolidated String.toRGB() and String.toRGBA() into a single String.toColor() that always returns rgba(...). 2013-06-06 01:10:04 -07:00
Arnavion a2c2343b66 Get the ASS URL from a track element instead of data attribute on video tag. 2013-06-06 00:09:20 -07:00
Arnavion 393448b857 enableDisable values need strict null check too. 2013-06-05 02:33:48 -07:00
Arnavion 0fbe91fa0f Tags are now initialized with proper types of parameters instead of strings, or null to indicate missing parameters. This removes the need to do parse or string conversions on tag parameters. 2013-06-05 02:12:00 -07:00
Arnavion 4ec3aabacd instanceof is fine after all. 2013-06-04 23:38:06 -07:00
Arnavion 68145becf9 Removed unnecessary null-check. 2013-06-04 23:07:40 -07:00
Arnavion a90413311c Pause animations while video is paused. 2013-06-04 22:46:42 -07:00
Arnavion 40335b580c Fixed subtitles flickering after a fade-out before being removed. 2013-06-04 22:40:58 -07:00
Arnavion b8bf5ff5a0 Added wiki page as readme. 2013-06-04 22:39:36 -07:00
Arnavion efb7b4c0a6 Pass the current time to Dialogue.drawTo so that animations are timed correctly when seeking to the middle of a line. 2013-06-04 22:21:00 -07:00
Arnavion a0b7e86e4e Began implementing tags using CSS3 animations: \fad 2013-06-04 22:20:58 -07:00
Arnavion 1051615721 Implemented \fad using animations instead of transitions because dirty setTimeout(0) hack doesn't work reliably in FF. 2013-06-01 16:11:42 -07:00
Arnavion f9105414ae Fixed broken fade-out. 2013-06-01 15:31:11 -07:00
Arnavion ec382f23dc Free functions -> Namespaced functions. 2013-06-01 14:45:49 -07:00
Arnavion 2f3386f6d4 dialogue.js cleanup. 2013-06-01 01:02:52 -07:00
Arnavion c7f4ffb2cf Fixed \h 2013-06-01 00:07:27 -07:00
Arnavion 3b0ea5c893 Added unprefixed style for perspective. 2013-06-01 00:00:35 -07:00
Arnavion 5766dace54 Removed unnecessary vendor-prefixed styles. 2013-05-31 23:57:20 -07:00
Arnavion a5f4144bec Fixed race condition between parser download and ASS download. 2013-05-31 23:46:02 -07:00
Arnav Singh 0e3e545253 Unix newlines. Newline at end. Renamed IEnumerable to Iterable. 2013-05-31 23:11:25 -07:00
Arnavion 42ee073a48 Be consistent with post- vs pre-increment. 2013-02-11 20:18:21 -08:00
Arnavion fc4bf7b62f Fixed typo in script URL. 2013-02-11 20:18:11 -08:00
Arnavion a92ad9b8fa Reimplemented lazy iterator framework on top of Mozilla's Iterator spec. 2013-01-31 05:22:49 -08:00
Arnavion 2b9fdf71f2 Upgraded PEGjs to 0.7.0 2013-01-31 01:20:21 -08:00
Arnavion 091671f98b Fixed doc on String.toRGBA() 2013-01-31 01:13:48 -08:00
Arnavion e2860df42c Fixed broken String.trimLeft() 2013-01-31 01:13:22 -08:00
Arnav Singh b98e046062 Removed IEnumerable.setUserToken.
Added comments for iterators.js, parser.js and utility.js
2012-07-04 23:35:29 +08:00
Arnav Singh 98fad787ff FF complains about accessing properties in rule.style directly. Also, the return value of getPropertyValue has '' in Chrome and "" in FF. 2012-07-01 17:52:42 +08:00
Arnav Singh 1a7ad91e94 On second thought, Chrome complains about cyclic prototype chains. Replaced the "instanceof StopIteration" check with an "=== StopIteration" check, which is also supposed to work. 2012-06-30 09:44:47 +08:00
Arnav Singh a38104813f Fixed how StopIteration is used and its mock implementation to match Mozilla's API and usage. 2012-06-30 06:36:00 +08:00
Arnav Singh 5e52dfe88f Fixed ArrayEnumerable to skip missing array positions. 2012-06-21 14:51:35 +08:00
Arnav Singh a488db4cb6 Fixed Set.forEach and Set.toArray being added to an Object prototype. 2012-06-16 15:25:27 +08:00
Arnav Singh a468e01e8a Added directory specific mime types and gzip settings. Replaced tabs with spaces for better alignment. 2012-06-16 14:58:05 +08:00
Arnav Singh 5a7af93880 Gzip fonts too. 2012-06-16 14:15:23 +08:00
Arnav Singh d5d81c8b2b Added nginx.conf to hiddenSegments for extra safety. 2012-06-15 22:17:23 +08:00
Arnav Singh f61b8f065f Merge branch 'master' of github.com:Arnavion/libjass 2012-06-15 22:14:24 +08:00
Arnav Singh c7fe9691a3 Fixed typo. 2012-06-15 22:12:34 +08:00
Arnav Singh 9d48eb25cb Removed some member variables which didn't need to be. 2012-06-15 22:11:52 +08:00
Arnav Singh 48b673b911 Fixed typo which caused a PEGjs parse error. 2012-06-15 22:10:50 +08:00
Arnav Singh 606bba4adb Added nginx config. 2012-06-15 21:50:57 +08:00
Arnav Singh 9fe1dd3b03 Added support for \r. 2012-05-04 15:47:00 +08:00
Arnav Singh 6558fdc2d4 Removed a bunch of unnecessary "var that = this;" 2012-05-04 15:44:55 +08:00
Arnav Singh 75f4874e0d Rewrote ASS parsing code to use iterators. 2012-04-27 23:16:56 +08:00
Arnav Singh d28c9b107b Implemented Set in terms of the ECMA spec.
Only override Set and StopIteration if they're not adequately defined by the browser runtime already. In the case of Set, Chrome and Nightly define Set but do not have Set.iterator() yet.
2012-04-21 16:34:25 +08:00
Arnav Singh b976137263 Replaced ugly getters and setters with properties.
Only fonts used in the ASS are pre-loaded now.
2012-04-21 15:56:24 +08:00
Arnav Singh e6ade781b2 Fixed a bug in Set where forEach was enumerating elements with a ">" in front of their name. 2012-04-21 15:54:52 +08:00
Arnav Singh f8823cc7c7 Fixed \b100 not working. Parser matched the "1" to the enable bit of enableDisable, then failed on the "0" and fell back to treating the whole rule as a comment. 2012-04-16 12:43:34 +08:00
Arnav Singh fa5236e359 Rewrote the logic which determines the subs to show on the video at t = currentTime to use home-brewed lazy iterators instead of Array's default list comprehension methods. This way avoids creating huge and unnecessary intermediate arrays.
Removed the Array.prototype.partition method because I can't figure out for the life of me what I was doing with it (or even what I :thought: I was doing with it).
2012-04-14 21:44:32 +08:00
Arnav Singh f2d830aa2d Fixed two places where Mozilla-specific CSS properties were still being referred to with a small m in "moz". 2012-04-14 21:39:06 +08:00
Arnav Singh 05a5f409df Support for {\u} (underline) and {\s} (strikeout / strike-through).
Support for tags without a value (like {\b}) which resets the value to the style default (so if the style had bold on by default, {\b} is interpreted as {\b1}).
2012-04-12 16:46:04 +08:00
Arnav Singh 88b5fdb159 Replaced regex-based dialogue line parsing with a parser generated using PEG.js http://pegjs.majda.cz/online
Overrode window.parseInt with a function which defaults to base 10 except for hex strings (strings starting with 0x).
2012-04-07 18:50:10 +08:00
Arnav Singh 1872dcae42 Fixed sub div being invisible. 2012-03-21 01:32:49 +08:00
Arnav Singh a137de2aa9 Fixed zoom only being applied to the video and not the subs.
Added browser DPI detection.
Incomplete implementation of pre-loading only those fonts which are used in the ASS file.
2012-03-21 00:35:12 +08:00
Arnav Singh 3f8c1408fb Replaced zoom with cross-browser CSS transform scale. 2012-03-20 12:38:26 +08:00
Arnav Singh 7167aecf24 JS bindings for Mozilla-specific CSS properties start with "M", not "m". 2012-03-20 12:37:23 +08:00
Arnav Singh 16de73cbd7 Replaced calls to Array.prototype.splice.call with Array.prototype.filter.call since FF10 doesn't like the former on document.styleSheets.
Font URLs can now be extracted from document.styleSheets even if they're quoted.
The page now zooms to make the video the same size as the script resolution.
2012-03-20 01:53:33 +08:00
Arnav Singh 32429f5657 External program can no generate the complete fonts.css automatically, i.e., both TTF and OTF font definitions are back in the same file. Also, spaces don't need to be URL-encoded when the whole URL is quoted. 2012-03-20 01:50:12 +08:00
Arnav Singh 965b9b78ae 84/96 seems to give better font sizes than 0.75.
Not sure why, since the 0.75 value was because of rendering (72) vs browser (96) DPI, but 84 makes no sense.
2012-03-20 01:48:13 +08:00
Arnav Singh e026f92420 Dialogues really sort themselves now. Honest!
PS: I'm a moron.
2012-03-20 01:45:11 +08:00
Arnav Singh afd374b406 Began preliminary support for IE9 (needs WebM plugin). 2012-03-07 14:09:03 -08:00
Arnav Singh b376e63dad Dialogues array is now sorted by time to make it more efficient to query for currently visible dialogues. 2012-03-07 14:07:31 -08:00
Arnav Singh 4362989415 Split OTF fonts into separate file. TTF fonts.css is not generated automatically from a separate Powershell script, which as of now cannot into OpenType (geegee with GDI+, Microsoft... real geegee...) 2012-02-29 23:12:20 -08:00
Arnav Singh e43f801f70 Implemented naive font-preloading (load ALL the fonts instead of just the ones referenced in the script). 2012-02-29 23:00:56 -08:00
Arnav Singh 54b61c3763 fonts.css was never even getting loaded ಠ_ಠ 2012-02-29 12:09:05 -08:00
Arnav Singh cfa02c9851 Moved videos and fonts into subdirectories.
Enabled floats in \blur.
Fixed handling ASS with \r\n newlines.
2012-02-15 23:06:45 +08:00
Arnav Singh beeb4e1447 Added support for {\fn}, {\fs}, {\frx|, {\fry}, {\fax}, {\fay}.
Added support for \N.
Changed font size calculation to be closer to the ASS definition.
Allowed floats in some formerly integer properties.
Fixed subs flashing while seeking through the video.
Fixed font face definitions to work with italics. Split them into a separate CSS file.
Fixed typo in index.css.
2012-01-28 15:21:48 +08:00
Arnav Singh 97b9a53810 Rudimentary web fonts support.
Support for {\pos} {\frz}.
Support for style default margins, bold, italics, underline, outline, outline color.
2012-01-28 12:52:38 +08:00
Arnav Singh 2f6cf1a902 Reduced the god-awful nested-ifs with a much simpler if-else if ladder. I don't know what I was thinking while I wrote it. This is why you don't code your eyes out at 1 in the morning after a long day at work while looping a single Youtube AMV in the background for >4 hours straight. And kids, this is also why you should plan out your software/algorithm design in advance before you write code. 2012-01-26 02:13:36 +08:00
Arnav Singh e17beb72d0 {\an#} was being wrongly parsed for the #. 2012-01-26 01:38:53 +08:00
Arnav Singh f6516d44c0 First commit 2012-01-26 01:11:09 +08:00
173 changed files with 18051 additions and 21610 deletions
+14
View File
@@ -0,0 +1,14 @@
/build/doc.js
/build/typescript/typescript.d.ts
/build/typescript/*.js
/dist/
/lib/
!/lib/libjass.css
/node_modules/
/npm-debug.log
/src/version.ts
+10
View File
@@ -0,0 +1,10 @@
language: node_js
node_js:
- "0.10"
- "0.12"
- "4"
- "5"
- "6"
before_script:
- "node ./build.js doc"
sudo: false
+133
View File
@@ -0,0 +1,133 @@
### v0.11.0 - 2016/01/24
- BREAKING CHANGE - WebRenderer.resize(width, height) used to have a broken implementation of letterboxing to move the subs div right or down. Now it's WebRenderer.resize(width, height, left, top) and expects the caller to calculate letterboxing itself and supply left and top accordingly. DefaultRenderer does it using the video resolution and users of WebRenderer can do the same.
- BREAKING CHANGE - DefaultRenderer.resize() now ignores its parameters and always resizes to its video element's dimensions. It had already stopped resizing the video element when it was renamed from resizeVideo in v0.6.0, so it doesn't make sense to let it take a completely different width and height.
- BREAKING CHANGE - Removed fullscreen support in DefaultRenderer. It started out as a hack using max z-index and works on even fewer browsers now. It probably didn't work for you anyway so it should be no big loss.
- Implemented experimental support for \t
- Added RendererSettings.fallbackFonts to set the fallback fonts for all styles. Defaults to 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"'.
- Better compatibility with loose ASS scripts - assume unnamed first section is Script Info, fall back to Default style for missing styles, recognize arbitrary-case property names, normalize asterisks in style names, etc.
- Various font size improvements - faster calculation, fix for incorrect size when line-height is overridden by site CSS, fix for incorrect scaled sizes for letterboxed subs, fix for incorrect metrics for web fonts, etc. The last one requires that all web fonts be specified in RendererSettings.fontMap to be rendered accurately.
- WebRenderer now supports using local() URLs in addition to url() in CSS font-face rules.
- Added RendererSettings.useAttachedFonts. If true, TTF fonts attached to the script will be used in addition to fonts specified in RendererSettings.fontMap. This setting is false by default, and should only be enabled on trusted fonts since it uses a very naive base64 and TTF parser to extract the font names from the attachment. It also requires ES6 typed arrays - ArrayBuffer, DataView, Uint8Array, etc. in the environment.
- Various pre-render, SVG filter and DOM perf improvements.
- Fixed \fscx and \fscy to not scale shadows.
- Fixed \fscx and \fscy to have optional values.
- Fixed \fs+ and \fs- to have required values.
- Fixed \r<target_style> to use the target style's alpha values instead of 1.
- Fixed \fad subs to not flash after the fade-out ends with low-resolution clocks.
- Fixed outlines to not be darker than they should be.
- Fixed styles to not ignore the ScaleX and ScaleY properties in the script.
- Fixed lack of sufficient space between normal and italic text.
- Fixed SVG filters to interpolate in sRGB space instead of RGB.
- Fixed ASS parser to complain if a script doesn't have a Script Info section at all.
- The promise returned from ASS.from*() is now properly rejected due to errors from loading the script, instead of just remaining unresolved forever.
- Fixed SRT parser to swallow UTF-8 BOM just like the ASS parser.
- Fixed all clocks to suppress redundant ticks if the current timestamp hasn't change from the last tick.
- Fixed {AutoClock, VideoClock}.{setEnabled, toggle} methods to actually enable / disable the high-resolution timer.
### v0.10.0 - 2015/05/05
- Implemented libjass.renderers.AutoClock, a clock that automatically ticks and generates clock events according to the state of an external driver.
- Implemented \k
- libjass.{Set, Map, Promise} can now be set to null to force the use of the polyfills, even if it defaulted to a runtime-provided implementation.
- Added ASS.fromReadableStream(), a function that can be used to parse ASS from a readable stream such as the response of window.fetch().
- ASS.fromUrl() now tries to use window.fetch() if available instead of XMLHttpRequest.
- Fixed constant pausing and playing on Firefox triggered by how slowly it updates video.currentTime (wasn't noticeable but still undesirable).
- Fixed a dialogue's animation state not getting updated while seeking if the start and end times of the seek were within its start and end times.
- Fixed wrapping mode 1 (end-of-line wrapping) to actually wrap.
- Fixed parser to parse the time components of karaoke tags as centiseconds instead of seconds.
- Fixed parser to swallow leading BOM, if any.
- Fixed errors reported by webworker API were empty objects without message and stack properties.
### v0.9.0 - 2014/11/27
- BREAKING CHANGE - ASS.fromString() now returns a Promise of an ASS object, not an ASS object directly. The synchronous ASS parser used by ASS.fromString() is no more.
- BACKWARD-COMPATIBLE CHANGE - WebRenderer constructor parameters order has changed from (ass, clock, settings, libjassSubsWrapper) to (ass, clock, libjassSubsWrapper, settings). The constructor will detect the old order and reorder accordingly.
- Added ASS.fromStream and ASS.fromXhr that read a stream and an XMLHttpRequest object's response respectively and return (a Promise of) an ASS object. Both of these parse the script asynchronously.
- Added RendererSettings.enableSvg that can be used to toggle the use of SVG filter effects for outlines and blur.
- libjass.js now has an AMD wrapper so that it can be used with RequireJS, etc.
- Settings parameter is now optional for WebRenderer and DefaultRenderer.
- Added support for clock rates apart from 1 to clocks and renderers.
- Added a parameter to libjass.createWorker to specify the path to libjass.js that will run in the worker.
- Fixed Style and Dialogue constructors not setting defaults for missing properties.
- Fixed color and alpha parser to support more formats.
- Fixed SRT parser to replace all HTML tags it finds, instead of just the first one.
- Fixed font size calculation to use the vertical scale instead of horizontal.
- Fixed line-height on newlines.
- Fixed missing perspective on X and Y rotations.
### v0.8.0 - 2014/08/16
- Added web worker support. libjass.parse can now be offloaded to a web worker.
- Implemented \fs+ and \fs-
- Added ASS.addEvent() to add dialogue lines to an ASS object.
- Renamed ClockEvent.TimeUpdate to ClockEvent.Tick, and added ClockEvent.Stop
- Clock.enable() and .disable() now return a boolean to indicate whether the function had any effect.
- Added Clock.setEnabled() to force the enabled-state to the given value.
- Renamed ManualClock.timeUpdate() to ManualClock.tick()
- Moved WebRenderer.enable(), .disable() and .enabled to NullRenderer
- Fixed not being able to parse tags with default values.
- Fixed font preloader downloading the same font multiple times because it didn't filter for duplicates.
- Fixed min-width value not taking separate left and right margins into account.
- Fixed absolutely positioned subs were always left-aligned even if they had an alignment tag.
- Fixed blur and outlines getting truncated.
### v0.7.0 - 2014/05/15
- Implemented \be
- Split a new renderer, WebRenderer, off DefaultRenderer that doesn't rely on a video element.
- All renderers now require a Clock to generate time events. VideoClock is a Clock backed by a video element, while ManualClock is a clock that can be used to generate arbitrary time events.
### v0.6.0 - 2014/03/24
- All script properties and style properties are now parsed and stored in the ASS and Style objects.
- Basic SRT support, by passing in a libjass.Format argument to ASS.fromString()
- \clip and \iclip now have their drawing instructions parsed as an array of libjass.parts.drawing.Instruction's instead of just a string.
- Added DefaultRenderer.enable(), DefaultRenderer.disable() and DefaultRenderer.toggle() to change whether the renderer is displaying subtitles or not.
- DefaultRenderer.resizeVideo is now called DefaultRenderer.resize. Now it only resizes the subtitle wrapper div, not the video element.
- Replaced the 41ms setInterval-bsed timer with a requestAnimationFrame-based timer to reduce load on minimized or hidden browser tabs.
- DefaultRenderer now renders dialogues in the correct order according to the script.
- Fixed incorrect font sizes.
- Replaced jake with gulp.
### v0.5.0 - 2014/01/26
- Removed preLoadFonts renderer setting. It was redundant with the actual fontMap setting since the presence or absence of that setting is enough to signal whether the user wants to preload fonts or not.
- Multiple renderers can now be used on the same page without conflicting with each other.
- Implemented \shad, \xshad, \yshad
- Fixed ASS draw scale being used incorrectly.
- ASS.resolutionX and ASS.resolutionY are now properties of ASS.properties, a ScriptProperties object.
### v0.4.0 - 2013/12/27
- All parts moved from the libjass.tags namespace to the libjass.parts namespace.
- Replaced PEG.js parser with a hand-written one. This allows for parsing lines that are strictly invalid grammar but are parsed successfully by VSFilter or libass.
- All ASS tags are now supported by the parser.
- Removed the useHighResolutionTimer setting for DefaultRenderer. DefaultRenderer always uses the 41ms timer now.
- Implemented \move
- Implemented ASS draw
- Fixed subs overflowing the video dimensions still being visible.
- SVG filters are now used for outlines and blur.
- Delay parsing of dialogue lines till they need to be pre-rendered. As a side-effect, all fonts in the font map are preloaded now, not just the ones used in the current script.
### v0.3.0 - 2013/10/28
- Moved libjass.DefaultRenderer to libjass.renderers.DefaultRenderer
- Added libjass.renderers.NullRenderer, a renderer that doesn't render anything.
- DefaultRenderer's fontMap setting is now a Map instead of an Object. It now supports more than one URL for each font name.
- DefaultRenderer now generates the subtitle wrapper div itself.
- DefaultRenderer now takes video letterboxing into account when resizing the subtitles.
- DefaultRenderer has a new setting useHighResolutionTimer that makes it use a 41ms timer instead of video.timeUpdate's 250ms timer.
- div IDs and CSS class names are now prefixed with "libjass-" to avoid collisions with other elements on the page.
- All numeric CSS property values are now truncated to three decimal places.
- Added ```jake watch``` that rebuilds and runs tests on changes to the source.
- Added ```jake doc``` that builds API documentation.
- Added Travis CI build.
### v0.2.0 - 2013/09/11
- Added libjass.DefaultRenderer, a class that handles initializing the layer div's, preloading fonts, and drawing Dialogues based on the current video time.
- libjass.js can now be loaded in node. Only the parser can be used.
- Tests can now be run with ```jake test``` or ```npm test``` using Mocha.
### v0.1.0 - 2013/08/29
- First npm release.
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+171
View File
@@ -0,0 +1,171 @@
[![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 in the browser. [Check out the demo.](http://arnavion.github.io/libjass/demo/index.xhtml)
### What's special about libjass?
* libjass requires no tweaks to the ASS file from the original video.
* libjass uses the browser's native CSS engine by converting the components of each line in the ASS script into a series of styled &lt;div&gt; and &lt;span&gt; elements. This allows all the layout and rendering to be handled by the browser instead of requiring complex and costly drawing and animation code. For example, libjass uses CSS3 animations to simulate tags such as \fad. While a canvas-drawing library would have to re-draw such a subtitle on the canvas for every frame of the video, libjass only renders it once and lets the browser render the fade effect.
As a result, libjass is able to render subtitles with very low CPU usage. The downside to libjass's aproach is that it is hard (and potentially impossible) to map all effects possible in ASS (using \t, ASS draw) etc. into DOM elements. As of now, the subset of tags supported by libjass has no such problems.
### I want to use libjass for my website. What do I need to do?
You can install the latest release of libjass
* using npm with `npm install libjass` and load with `var libjass = require("libjass");`
* using bower with `bower install https://github.com/Arnavion/libjass/releases/download/<release name>/libjass.zip`
* using jspm with `jspm install github:Arnavion/libjass` and load with `import libjass from "Arnavion/libjass";`
Inside the package, you will find libjass.js and libjass.css, which you need to load on your website with your video.
Alternatively, you can build libjass from source by cloning this repository and running `npm install`. This will install the dependencies and run the build. libjass.js and libjass.css will be found in the lib/ directory.
Only libjass.js and libjass.css are needed to use libjass on your website. The other files are only used during the build process and you don't need to deploy them to your website.
### What are all these files?
* The src/ directory contains the source of libjass. They are TypeScript files and get compiled into JavaScript for the browser using the TypeScript compiler.
* build.js is the build script. The build command will use this script to build libjass.js. The build/ directory contains other files used for the build.
* The tests/ directory contains unit and functional tests.
* The lib/ directory contains libjass.js and libjass.css. You will need to deploy these to your website.
### How do I use libjass?
The API documentation is linked in the Links section below. Here's an overview:
* The [ASS.fromUrl()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromUrl) function takes in a URL to an ASS script and returns a promise that resolves to an [ASS](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS) object. This ASS object represents the script properties, the line styles and dialogue lines in it. Alternatively, you can use [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString) to convert a string of the script contents into an ASS object.
* Next, you initialize a renderer to render the subtitles. libjass ships with an easy-to-use renderer, the [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer). It uses information from the ASS object to build up a series of div elements around the video tag. There is a wrapper (.libjass-subs) containing div's corresponding to the layers in the ASS script, and each layer has div's corresponding to the 9 alignment directions. libjass.css contains styles for these div's to render them at the correct location.
* The renderer uses [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) as a source of timer ticks. In each tick, it determines the set of dialogues to be shown at the current video time, renders each of them as a div, and appendChild's the div into the appropriate layer+alignment div.
* The renderer can be told to dynamically change the size of the subtitles based on user input by calling [WebRenderer.resize()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer.resize)
* Lastly, the renderer contains an implementation of preloading fonts before playing the video. It uses a map of font names to URLs - this map can be conveniently created from a CSS file containing @font-face rules using [RendererSettings.makeFontMapFromStyleElement()](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.makeFontMapFromStyleElement)
* For an example of using libjass, check out [the demo.](http://arnavion.github.io/libjass/demo/index.xhtml) It has comments explaining basic usage and pointers to some advanced usage.
### What browser and JavaScript features does libjass need?
* libjass uses some ES5 features like getters and setters (via Object.defineProperty), and assumptions like the behavior of parseInt with leading zeros. It cannot be used with an ES3 environment.
* libjass will use ES6 Set, Map and Promise if they're available on the global object. If they're not present, it will use its own minimal internal implementations. If you have implementations of these that you would like libjass to use but don't want to register them on the global object, you can provide them to libjass specifically by setting the [libjass.Set](http://arnavion.github.io/libjass/api.xhtml#libjass.Set), [libjass.Map](http://arnavion.github.io/libjass/api.xhtml#libjass.Map) and [libjass.Promise](http://arnavion.github.io/libjass/api.xhtml#libjass.Promise) properties.
* [AutoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.AutoClock) and [VideoClock](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.VideoClock) use [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to generate clock ticks.
* [WebRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.WebRenderer) and [DefaultRenderer](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.DefaultRenderer) use [SVG filter effects for HTML](http://caniuse.com/#feat=svg-html) to render outlines and blur. This feature is not available on all browsers, so you can tell them to fall back to more widely available CSS methods by setting the [RendererSettings.enableSvg](http://arnavion.github.io/libjass/api.xhtml#libjass.renderers.RendererSettings.enableSvg) property to false.
* WebRenderer and DefaultRenderer use [CSS3 animations](http://caniuse.com/#feat=css-animation) for effects like \mov and \fad.
* Using fonts attached to the script requires [ES6 typed arrays](http://caniuse.com/#feat=typedarrays) (ArrayBuffer, DataView, Uint8Array, etc).
### Can I use libjass in node?
libjass's parser works in node. Entire scripts can be parsed via [ASS.fromString()](http://arnavion.github.io/libjass/api.xhtml#libjass.ASS.fromString)
```javascript
> var libjass = require("libjass")
undefined
> var ass; libjass.ASS.fromString(fs.readFileSync("mysubs.ass", "utf8")).then(function (result) { ass = result; })
{}
> ass.properties.resolutionX
1280
> ass.dialogues.length
9
> ass.dialogues[0].toString()
'#0 [646.460-652.130] {\\fad(200,0)}Sapien rhoncus, suscipit posuere in nunc pellentesque'
> var parts = ass.dialogues[0].parts
undefined
> parts.length
2
> parts[0] instanceof libjass.parts.Fade
true
> parts[0].toString()
'Fade { start: 0.2, end: 0 }'
```
[libjass.parser.parse](http://arnavion.github.io/libjass/api.xhtml#libjass.parser.parse) parses the first parameter using the second parameter as the rule name. For example, the [dialogueParts](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun.parse_dialogueParts) rule can be used to get an array of [libjass.parts](http://arnavion.github.io/libjass/api.xhtml#libjass.parts) objects that represent the parts of an ASS dialogue line.
```javascript
> var parts = libjass.parser.parse("{\\an8}Are {\\i1}you{\\i0} the one who stole the clock?!", "dialogueParts")
undefined
> parts.join(" ")
'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
```
The rule names are derived from the methods on the [ParserRun class](http://arnavion.github.io/libjass/api.xhtml#./parser/parse.ParserRun).
See the tests, particularly the ones in tests/unit/miscellaneous.js, for examples.
### Can I contribute?
Yes! Feature requests, suggestions, bug reports and pull requests are welcome! I'm especially looking for details and edge-cases of the ASS syntax that libjass doesn't support.
You can also join the IRC channel in the links section below and ask any questions.
### Supported features
* Styles: Italic, Bold, Underline, StrikeOut, FontName, FontSize, ScaleX, ScaleY, Spacing, PrimaryColor, OutlineColor, BackColor, Outline, Shadow, Alignment, MarginL, MarginR, MarginV
* Tags: \i, \b, \u, \s, \bord, \xbord, \ybord, \shad, \xshad, \yshad, \be, \blur, \fn, \fs, \fscx, \fscy, \fsp, \frx, \fry, \frz, \fr, \fax, \fay, \c, \1c, \3c, \4c, \alpha, \1a, \3a, \4a, \an, \a, \k, \q, \r, \pos, \move, \fad, \fade, \t (experimental), \p
* Custom fonts, using CSS web fonts.
### Known issues
* Unsupported tags: \fe, \2c, \2a, \K, \kf, \ko, \org, \clip, \iclip
* \an4, \an5, \an6 aren't positioned correctly.
* Smart line wrapping is not supported. Such lines are rendered as [wrapping style 1 (end-of-line wrapping).](http://docs.aegisub.org/3.0/ASS_Tags/#wrapstyle)
* Lines with multiple rotations aren't rotated the same as VSFilter or libass. See [#14](https://github.com/Arnavion/libjass/issues/14)
- Desktop renderers include borders when calculating space between adjacent lines. libjass doesn't.
### Links
* [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/)
### License
```
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.
```
-10596
View File
File diff suppressed because it is too large Load Diff
+279
View File
@@ -0,0 +1,279 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var fs = require("fs");
var path = require("path");
var stream = require("stream");
var async = require("async");
var task = require("async-build");
(function () {
var _TypeScript = null;
var _UglifyJS = null;
var _npm = null;
Object.defineProperties(global, {
TypeScript: { get: function () { return _TypeScript || (_TypeScript = require("./build/typescript/index.js")); } },
UglifyJS: { get: function () { return _UglifyJS || (_UglifyJS = require("./build/uglify.js")); } },
npm: { get: function () { return _npm || (_npm = require("npm")); } },
});
})();
task("build-tools", function (callback) {
async.every(["./build/typescript/typescript.d.ts", "./build/typescript/index.js", "./build/doc.js"], fs.exists.bind(fs), function (allExist) {
if (allExist) {
callback();
return;
}
var typescriptPath = path.join(require.resolve("typescript"), "..", "..");
async.waterfall([
async.parallel.bind(async, [
fs.readFile.bind(fs, path.join(typescriptPath, "lib", "typescript.d.ts"), "utf8"),
fs.readFile.bind(fs, "./build/typescript/extras.d.ts", "utf8")
]),
function (results, callback) {
var newDts = results[0] + "\n\n" + results[1];
fs.writeFile("./build/typescript/typescript.d.ts", newDts, "utf8", callback);
},
function (callback) {
npm.load(function () {
npm.commands["run-script"](["build"], callback);
});
}
], callback);
});
});
task("default", ["libjass.js", "libjass.min.js"]);
task("version.ts", function (callback) {
fs.exists("./src/version.ts", function (exists) {
if (exists) {
callback();
return;
}
async.waterfall([
fs.readFile.bind(fs, "./package.json"),
function (buffer, callback) {
try {
var packageJson = JSON.parse(buffer);
var versionString = packageJson.version;
var versionParts = versionString.split(".").map(function (num) { return parseInt(num); });
var versionFileContents =
"/**\n" +
" * The version of libjass. An array like\n" +
" *\n" +
" * [\"0.12.0\", 0, 12, 0]\n" +
" *\n" +
" * @type {!Array.<string|number>}\n" +
" */\n" +
"export const version = " + JSON.stringify([versionString].concat(versionParts)) + ";\n";
callback(null, versionFileContents);
}
catch (ex) {
callback(ex);
}
},
function (contents, callback) {
fs.writeFile("./src/version.ts", contents, "utf8", callback);
}
], callback);
});
});
task("libjass.js", ["build-tools", "version.ts"], function (callback) {
fs.exists("./lib/libjass.js", function (exists) {
if (exists) {
callback();
return;
}
callback(null, task.src("./src/tsconfig.json")
.pipe(TypeScript.build("./src/index.ts", "libjass"))
.pipe(UglifyJS.build("libjass", ["AttachmentType", "BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(task.dest("./lib")));
});
});
task("libjass.min.js", ["libjass.js"], function (callback) {
fs.exists("./lib/libjass.min.js", function (exists) {
if (exists) {
callback();
return;
}
callback(null, task.src(["./lib/libjass.js", "./lib/libjass.js.map"], { relativeTo: "./lib" })
.pipe(UglifyJS.minify())
.pipe(task.dest("./lib")));
});
});
task("clean", task.clean(["./src/version.ts", "./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.min.js", "./lib/libjass.min.js.map"]));
task("watch", ["build-tools", "version.ts"], function (callback) {
npm.load(function () {
var testRunning = false;
var rerunTest = false;
var startTest = function () {
npm.commands["run-script"](["test-lib"], function () {
testRunning = false;
if (rerunTest) {
startTest();
rerunTest = false;
}
});
testRunning = true;
};
var runTest = function () {
if (!testRunning) {
startTest();
}
else {
rerunTest = true;
}
};
task.src("./src/tsconfig.json")
.pipe(TypeScript.watch("./src/index.ts", "libjass"))
.pipe(UglifyJS.watch("libjass", ["BorderStyle", "ClockEvent", "Format", "WorkerCommands", "WrappingStyle"]))
.pipe(task.dest("./lib"))
.pipe(new task.FileTransform(function (file) {
if (file.path === "libjass.js") {
runTest();
}
}));
task.watch("./tests/unit/", runTest);
});
});
task("test-lib", ["libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-lib"], callback);
});
});
task("test-minified", ["libjass.min.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-minified"], callback);
});
});
// Start Selenium server with
// java.exe -jar .\selenium-server-standalone-2.48.2.jar -Dwebdriver.ie.driver=IEDriverServer.exe -Dwebdriver.chrome.driver=chromedriver.exe
task("test-browser", ["libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-browser"], callback);
});
});
task("test", ["test-lib", "test-minified"]);
task("demo", ["libjass.js"], function () {
return task.src(["./lib/libjass.js", "./lib/libjass.js.map", "./lib/libjass.css"], { relativeTo: "./lib" }).pipe(task.dest("../libjass-gh-pages/demo/"));
});
task("doc", ["make-doc", "test-doc"]);
task("make-doc", ["build-tools", "version.ts"], function () {
var Doc = require("./build/doc.js");
return task.src("./src/tsconfig.json")
.pipe(Doc.build("./api.xhtml", "./src/index.ts", "libjass"))
.pipe(task.dest("../libjass-gh-pages/"));
});
task("test-doc", ["make-doc", "libjass.js"], function (callback) {
npm.load(function () {
npm.commands["run-script"](["test-doc"], callback);
});
});
task("dist", ["libjass.js", "libjass.min.js", "test", "test-browser", "demo", "doc"], function (callback) {
var inputFiles = [
"./README.md", "./CHANGELOG.md", "./LICENSE",
"./lib/libjass.js", "./lib/libjass.js.map",
"./lib/libjass.min.js", "./lib/libjass.min.js.map",
"./lib/libjass.css"
];
var files = Object.create(null);
inputFiles.forEach(function (filename) {
files["./dist/" + path.basename(filename)] = filename;
});
async.series([
// Clean dist/
task.clean(Object.keys(files).concat(["./dist/package.json"])),
// Create dist/ if necessary
function (callback) {
fs.mkdir("./dist", function (err) {
if (err && err.code !== "EEXIST") {
callback(err);
return;
}
callback();
});
},
// Copy all files except package.json
async.forEachOf.bind(async, files, function (inputFilename, outputFilename, callback) {
async.waterfall([fs.readFile.bind(fs, inputFilename), fs.writeFile.bind(fs, outputFilename)], callback);
}),
// Copy package.json
async.waterfall.bind(async, [
fs.readFile.bind(fs, "./package.json"),
function (data, callback) {
try {
var packageJson = JSON.parse(data);
packageJson.devDependencies = undefined;
packageJson.private = undefined;
packageJson.scripts = undefined;
packageJson.main = "libjass.js";
}
catch (ex) {
callback(ex);
return;
}
callback(null, new Buffer(JSON.stringify(packageJson, null, "\t")));
},
fs.writeFile.bind(fs, "./dist/package.json")
])
], callback);
});
task.runArgv(function (err) {
if (err) {
process.exit(1);
}
});
+884
View File
@@ -0,0 +1,884 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FileTransform } from "async-build";
import * as AST from "./typescript/ast";
import { Compiler } from "./typescript/compiler";
import { walk } from "./typescript/walker";
function flatten<T>(arr: T[][]): T[] {
let result: T[] = [];
for (const a of arr) {
result = result.concat(a);
}
return result;
}
const sorter = (() => {
function visibilitySorter(value1: { isPrivate?: boolean; isProtected?: boolean; }, value2: { isPrivate?: boolean; isProtected?: boolean; }) {
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
return 0;
}
if (value1.isPrivate) {
return 1;
}
if (value2.isPrivate) {
return -1;
}
if (value1.isProtected) {
return 1;
}
if (value2.isProtected) {
return -1;
}
return 0;
}
const types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
function typeSorter(value1: AST.ModuleMember | AST.NamespaceMember, value2: AST.ModuleMember | AST.NamespaceMember) {
let type1Index = -1;
let type2Index = -1;
types.every((type, index) => {
if (value1 instanceof type) {
type1Index = index;
}
if (value2 instanceof type) {
type2Index = index;
}
return (type1Index === -1) || (type2Index === -1);
});
return type1Index - type2Index;
}
function nameSorter(value1: { name: string }, value2: { name: string }) {
return value1.name.localeCompare(value2.name);
}
const sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
return (value1: AST.ModuleMember, value2: AST.ModuleMember) => {
for (const sorter of sorters) {
const result = sorter(value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
};
})();
function indenter(indent: number) {
return (line: string) => ((line === "") ? line : (Array(indent + 1).join("\t") + line));
}
function sanitize(str: string) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function toVariableName(item: { name: string }) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
const name = item.name;
let result = "";
for (let i = 0; i < name.length; i++) {
if (name[i] === name[i].toLowerCase()) {
// This is lower case. Write it as lower case.
result += name[i];
}
else {
// This is upper case.
if (i === 0) {
// This is the first character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i - 1] === name[i - 1].toUpperCase()) {
// The previous character was upper case.
if (i === name.length - 1) {
// This is the last character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i + 1] === name[i + 1].toLowerCase()) {
// The next character is lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
else {
// The next character is upper case. Write this one as lower case.
result += name[i].toLowerCase();
}
}
else {
// Previous character was lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
}
}
return result;
}
function toUsageName(item: AST.Class | AST.Interface | AST.Function | AST.Property | AST.Enum): string {
if (item.parent instanceof AST.Module) {
return item.name;
}
if (item instanceof AST.Class || item instanceof AST.Interface || item instanceof AST.Enum) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.parent instanceof AST.Namespace) {
if ((item as AST.CanBePrivate).isPrivate) {
return item.name;
}
return item.fullName;
}
if ((item as AST.CanBeStatic).isStatic) {
return toUsageName(item.parent as AST.Class | AST.Interface) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
}
function toId(item: { fullName?: string; name: string; }): string {
return sanitize((item.fullName === undefined) ? item.name : item.fullName);
}
function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): string {
let result = `<a href="#${ toId(item) }">${ sanitize(item.name) }`;
if (AST.hasGenerics(item) && item.generics.length > 0) {
const generics = item.generics as (string | AST.TypeReference | AST.IntrinsicTypeReference)[];
result += sanitize(`.<${ generics.map(generic =>
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : generic
).join(', ') }>`);
}
result += '</a>';
return result;
}
function writeDescription(text: string): string {
let result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `<a href="#${ linkTarget }">${ linkTarget }</a>`);
let inCodeBlock = false;
result = result.split("\n").map(line => {
if (line.substr(0, " ".length) === " ") {
line = line.substr(" ".length);
if (!inCodeBlock) {
inCodeBlock = true;
line = `<pre class="code"><code>${ line }`;
}
}
else if (line.length > 0 && inCodeBlock) {
inCodeBlock = false;
line = `</code></pre>${ line }`;
}
else if (line.length === 0 && !inCodeBlock) {
line = "</p><p>";
}
return line;
}).join("\n");
if (inCodeBlock) {
result += '</code></pre>';
}
result = `<p>${ result }</p>`.replace(/<p>\s*<\/p>/g, "").replace(/<p>\s+/g, "<p>").replace(/\s+<\/p>/g, "</p>");
if (result.substr("<p>".length).indexOf("<p>") === -1) {
result = result.substring("<p>".length, result.length - "</p>".length);
}
return result;
}
function writeParameters(parameters: AST.Parameter[]): string[] {
if (parameters.length === 0) {
return [];
}
return [
'<dd class="parameters">',
' <dl>'
].concat(flatten(parameters.map(parameter => {
return [
` <dt class="parameter name">${ sanitize(parameter.name) }</dt>`,
` <dd class="parameter type">${ sanitize(parameter.type) }</dd>`,
` <dd class="parameter description">${ writeDescription(parameter.description) }</dd>`
].concat(writeParameters(parameter.subParameters).map(indenter(2)));
}))).concat([
' </dl>',
'</dd>'
]);
}
function functionToHtml(func: AST.Function): string[] {
return [
`<dl id="${ toId(func) }" class="function${
func.isAbstract ? ' abstract' : '' }${
func.isPrivate ? ' private' : ''}${
func.isProtected ? ' protected' : ''}${
func.isStatic ? ' static' : ''}">`,
` <dt class="name">${ toLink(func) }</dt>`,
' <dd class="description">',
` ${ writeDescription(func.description) }`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${
sanitize(
`${ (func.returnType !== null) ? 'var result = ' : '' }${ toUsageName(func) }(${
func.parameters.map(parameter => parameter.name).join(', ') });`
) }</code></pre></fieldset></dd>`
].concat(writeParameters(func.parameters).map(indenter(1))).concat(
(func.returnType === null) ? [] : [
' <dt>Returns</dt>',
` <dd class="return type">${ sanitize(func.returnType.type) }</dd>`,
` <dd class="return description">${ writeDescription(func.returnType.description) }</dd>`
]
).concat([
'</dl>',
''
]);
}
function interfaceToHtml(interfase: AST.Interface): string[] {
const members: AST.InterfaceMember[] = [];
Object.keys(interfase.members).forEach(memberName => members.push(interfase.members[memberName]));
members.sort(sorter);
return [
`<dl id="${ toId(interfase) }" class="interface${ interfase.isPrivate ? ' private' : '' }">`,
` <dt class="name">interface ${ toLink(interfase) }${ (interfase.baseTypes.length > 0) ? ` extends ${
interfase.baseTypes.map(baseType => baseType instanceof AST.TypeReference ? toLink(baseType) : baseType.name).join(', ')
}` : '' }</dt>`,
' <dd class="description">',
` ${ writeDescription(interfase.description) }`,
' </dd>',
' <dd class="members">'
].concat(flatten(members.map(member => {
if (member instanceof AST.Property) {
return propertyToHtml(member).map(indenter(2));
}
else if (member instanceof AST.Function) {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
}
}))).concat([
' </dd>',
'</dl>',
''
]);
}
function classToHtml(clazz: AST.Class): string[] {
const members: AST.InterfaceMember[] = [];
Object.keys(clazz.members).forEach(memberName => members.push(clazz.members[memberName]));
members.sort(sorter);
return [
`<dl id="${ toId(clazz) }" class="clazz${
clazz.isAbstract ? ' abstract' : ''}${
clazz.isPrivate ? ' private' : ''}">`,
` <dt class="name">class ${ toLink(clazz) }${
(clazz.baseType !== null) ? ` extends ${ (clazz.baseType instanceof AST.TypeReference) ? toLink(clazz.baseType) : clazz.baseType.name }` : '' }${
(clazz.interfaces.length > 0) ? ` implements ${ clazz.interfaces.map(interfase => interfase instanceof AST.TypeReference ? toLink(interfase) : interfase.name).join(', ') }` : ''}</dt>`,
' <dd class="description">',
` ${ writeDescription(clazz.description) }`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${
sanitize(
`var ${ toVariableName(clazz) } = new ${ toUsageName(clazz) }(${
clazz.parameters.map(parameter => parameter.name).join(', ') });`
) }</code></pre></fieldset></dd>`
].concat(writeParameters(clazz.parameters).map(indenter(1))).concat([
' <dd class="members">'
]).concat(flatten(members.map(member => {
if (member instanceof AST.Property) {
return propertyToHtml(member).map(indenter(2));
}
else if (member instanceof AST.Function) {
return functionToHtml(member).map(indenter(2));
}
else {
throw new Error(`Unrecognized member type: ${ (member as any).constructor.name }`);
}
}))).concat([
' </dd>',
'</dl>',
''
]);
}
function enumToHtml(enumType: AST.Enum): string[] {
return [
`<dl id="${ toId(enumType) }" class="enum${ enumType.isPrivate ? ' private' : '' }">`,
` <dt class="name">enum <a href="#${ sanitize(enumType.fullName) }">${ sanitize(enumType.name) }</a></dt>`,
' <dd class="description">',
` ${ writeDescription(enumType.description) }`,
' </dd>'
].concat([
' <dd class="members">'
]).concat(flatten(enumType.members.map(member => [
` <dl id="${ toId(member) }" class="member">`,
` <dt class="name">${ toLink(member) } = ${ member.value }</dt>`,
' <dd class="description">',
` ${ writeDescription(member.description) }`,
' </dd>',
' </dl>'
]))).concat([
' </dd>',
'</dl>',
''
]);
}
function propertyToHtml(property: AST.Property): string[] {
return [
`<dl id="${ toId(property) }" class="property">`,
` <dt class="name">${ toLink(property) }</dt>`
].concat((property.getter === null) ? [] : [
` <dt class="getter${ property.getter.isPrivate ? ' private' : '' }">Getter</dt>`,
' <dd class="description">',
` ${ writeDescription(property.getter.description) }`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`var result = ${ toUsageName(property) };`) }</code></pre></fieldset></dd>`,
` <dd class="return type">${ sanitize(property.getter.type) }</dd>`
]).concat((property.setter === null) ? [] : [
` <dt class="setter${ property.setter.isPrivate ? ' private' : '' }">Setter</dt>`,
' <dd class="description">',
` ${ writeDescription(property.setter.description) }`,
' </dd>',
` <dd class="usage"><fieldset><legend /><pre><code>${ sanitize(`${ toUsageName(property) } = value;`) }</code></pre></fieldset></dd>`
].concat(writeParameters([new AST.Parameter("value", "", property.setter.type)]).map(indenter(1)))).concat([
'</dl>',
''
]);
}
export function build(outputFilePath: string, root: string, rootNamespaceName: string): FileTransform {
const compiler = new Compiler();
return new FileTransform(function (file): void {
// Compile
compiler.compile(file);
// Walk
const walkResult = walk(compiler, root, rootNamespaceName);
const namespaces = walkResult.namespaces;
const modules = walkResult.modules;
// Make HTML
const namespaceNames = Object.keys(namespaces)
.filter(namespaceName => namespaceName.substr(0, rootNamespaceName.length) === rootNamespaceName)
.sort((ns1, ns2) => ns1.localeCompare(ns2));
const moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2)).filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
this.push({
path: outputFilePath,
contents: Buffer.concat([new Buffer(
`<?xml version="1.0" encoding="utf-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>${ rootNamespaceName } API Documentation</title>
<style type="text/css">
<![CDATA[
html, body, .navigation, .content {
height: 100%;
margin: 0;
}
.navigation, .content {
overflow-y: scroll;
}
.navigation {
float: left;
background-color: white;
padding: 0 20px;
margin-right: 20px;
}
.navigation .namespace, .navigation .module {
margin-top: 1em;
}
.navigation .elements {
margin: 0;
}
.content > section:not(:last-child) {
border-bottom: 1px solid black;
}
.clazz, .enum, .function, .interface, .property {
margin-left: 30px;
padding: 10px;
}
.getter, .setter {
font-size: large;
}
section > .clazz:nth-child(2n), section > .enum:nth-child(2n), section > .function:nth-child(2n), section > .interface:nth-child(2n), section > .property:nth-child(2n) {
background-color: rgb(221, 250, 238);
}
section > .clazz:nth-child(2n + 1), section > .enum:nth-child(2n + 1), section > .function:nth-child(2n + 1), section > .interface:nth-child(2n + 1), section > .property:nth-child(2n + 1) {
background-color: rgb(244, 250, 221);
}
.name {
font-size: x-large;
}
.usage {
font-size: large;
font-style: italic;
}
.usage legend:before {
content: "Usage";
}
.usage fieldset {
min-width: initial;
overflow-x: auto;
}
.usage pre {
margin: 0;
}
.clazz .function, .clazz .property, .interface .function, .interface .property, .enum .member {
background-color: rgb(250, 241, 221);
}
.parameter.name {
font-size: large;
}
.type {
font-style: italic;
}
.type:before {
content: "Type: ";
}
.abstract > .name:before {
content: "abstract ";
}
.clazz .private > .name:before {
content: "private ";
}
.clazz .protected > .name:before {
content: "protected ";
}
.static > .name:before {
content: "static ";
}
.abstract.private > .name:before {
content: "abstract private ";
}
.private.static > .name:before {
content: "static private ";
}
.abstract.protected > .name:before {
content: "abstract protected ";
}
.protected.static > .name:before {
content: "static protected ";
}
body:not(.show-private) .clazz .private, body:not(.show-private) .clazz .protected, body:not(.show-private) .module {
display: none;
}
.description .code {
margin-left: 30px;
}
]]>
</style>
<script>
<![CDATA[
addEventListener("DOMContentLoaded", function () {
document.querySelector("#show-private").addEventListener("change", function (event) {
document.body.className = (event.target.checked ? "show-private" : "");
}, false);
showPrivateIfNecessary();
}, false);
function showPrivateIfNecessary() {
var jumpToElement = document.querySelector("[id=\\"" + location.hash.substr(1) + "\\"]");
if (jumpToElement !== null && jumpToElement.offsetHeight === 0) {
document.querySelector("#show-private").click()
jumpToElement.scrollIntoView();
}
}
addEventListener("hashchange", showPrivateIfNecessary, false);
]]>
</script>
</head>
<body>
<nav class="navigation">
<label><input type="checkbox" id="show-private" />Show private</label>
`
)].concat(namespaceNames.map(namespaceName => {
const namespace = namespaces[namespaceName];
const namespaceMembers: AST.NamespaceMember[] = [];
for (const memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
return Buffer.concat([new Buffer(
` <fieldset class="namespace">
<legend><a href="#${ sanitize(namespaceName) }">${ sanitize(namespaceName) }</a></legend>
<ul class="elements">
`
)].concat(namespaceMembers.map(member => new Buffer(
` <li><a href="#${ sanitize(member.fullName) }">${ sanitize(member.name) }</a></li>
`
))).concat([new Buffer(
` </ul>
</fieldset>
`
)]));
})).concat(moduleNames.map(moduleName => {
const module = modules[moduleName];
const moduleMembers: AST.ModuleMemberWithoutReference[] = [];
for (const memberName of Object.keys(module.members)) {
const member = module.members[memberName];
if ((member as AST.HasParent).parent === module) {
moduleMembers.push(member as AST.ModuleMemberWithoutReference);
}
}
if (moduleMembers.length === 0) {
return new Buffer("");
}
moduleMembers.sort(sorter);
return Buffer.concat([new Buffer(
` <fieldset class="module">
<legend><a href="#${ sanitize(moduleName) }">${ sanitize(moduleName) }</a></legend>
<ul class="elements">
`
)].concat(moduleMembers.map(member => new Buffer(
` <li><a href="#${ sanitize(member.fullName) }">${ sanitize(member.name) }</a></li>
`
))).concat([new Buffer(
` </ul>
</fieldset>
`
)]));
})).concat([new Buffer(
` </nav>
<div class="content">
`
)]).concat(flatten(namespaceNames.map(namespaceName => {
const namespace = namespaces[namespaceName];
const namespaceMembers: AST.NamespaceMember[] = [];
for (const memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
const properties = namespaceMembers.filter(member => member instanceof AST.Property) as AST.Property[];
const functions = namespaceMembers.filter(member => member instanceof AST.Function) as AST.Function[];
const interfaces = namespaceMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
const classes = namespaceMembers.filter(member => member instanceof AST.Class) as AST.Class[];
const enums = namespaceMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
const result = [new Buffer(
` <section class="namespace">
<h1 id="${ sanitize(namespaceName) }">Namespace ${ sanitize(namespaceName) }</h1>
`
)];
if (properties.length > 0) {
result.push(new Buffer(
` <section>
<h2>Properties</h2>
`
));
for (const property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
` <section>
<h2>Free functions</h2>
`
));
for (const func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
` <section>
<h2>Interfaces</h2>
`
));
for (const interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
` <section>
<h2>Classes</h2>
`
));
for (const clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
` <section>
<h2>Enums</h2>
`
));
for (const enumType of enums) {
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
result.push(new Buffer(
` </section>
`
));
return result;
}))).concat(flatten(moduleNames.map(moduleName => {
const module = modules[moduleName];
const moduleMembers: AST.ModuleMember[] = [];
for (const memberName of Object.keys(module.members)) {
const member = module.members[memberName];
if ((member as AST.HasParent).parent === module) {
moduleMembers.push(member);
}
}
if (moduleMembers.length === 0) {
return [];
}
moduleMembers.sort(sorter);
const properties = moduleMembers.filter(member => member instanceof AST.Property) as AST.Property[];
const functions = moduleMembers.filter(member => member instanceof AST.Function) as AST.Function[];
const interfaces = moduleMembers.filter(member => member instanceof AST.Interface) as AST.Interface[];
const classes = moduleMembers.filter(member => member instanceof AST.Class) as AST.Class[];
const enums = moduleMembers.filter(member => member instanceof AST.Enum) as AST.Enum[];
const result = [new Buffer(
` <section class="module">
<h1 id="${ sanitize(moduleName) }">Module ${ sanitize(moduleName) }</h1>
`
)];
if (properties.length > 0) {
result.push(new Buffer(
` <section>
<h2>Properties</h2>
`
));
for (const property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
` <section>
<h2>Free functions</h2>
`
));
for (const func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
` <section>
<h2>Interfaces</h2>
`
));
for (const interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
` <section>
<h2>Classes</h2>
`
));
for (const clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
` <section>
<h2>Enums</h2>
`
));
for (const enumType of enums) {
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
` </section>
`
));
}
result.push(new Buffer(
` </section>
`
));
return result;
}))).concat([new Buffer(
` </div>
</body>
</html>
`
)]))
});
});
}
+24
View File
@@ -0,0 +1,24 @@
// Type definitions for Node.js v0.12.0
// Project: http://nodejs.org/
// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/borisyankov/DefinitelyTyped>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
declare var require: {
resolve(id: string): string;
};
interface BufferConstructor {
new (str: string): Buffer;
prototype: Buffer;
concat(list: Buffer[]): Buffer;
}
declare module "fs" {
export function existsSync(filename: string): boolean;
export function readFileSync(filename: string, options: { encoding: string }): string;
}
declare module "path" {
export function basename(p: string, ext?: string): string;
export function extname(p: string): string;
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"strictNullChecks": false,
"target": "es5",
"module": "commonjs",
"moduleResolution": "classic",
"noImplicitUseStrict": false
},
"files": [
"./typescript/index.ts",
"./doc.ts",
"./node.d.ts",
"./typescript/typescript.d.ts",
"../node_modules/async-build/typings.d.ts"
]
}
+204
View File
@@ -0,0 +1,204 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ts = require("typescript");
export class HasParent {
public parent: HasParent = null;
constructor(public name: string) { }
get fullName(): string {
if (this.parent === null) {
return this.name;
}
const parent = this.parent;
if (parent instanceof Namespace) {
return parent.getMemberFullName(this);
}
return `${ this.parent.fullName }.${ this.name }`;
}
}
export class Module extends HasParent {
public parent: Module = null;
public members: { [name: string]: ModuleMember } = Object.create(null);
constructor(public fileName: string) {
super(fileName);
}
}
export type ModuleMember = Class | Interface | Function | Property | Enum | Reference;
export type ModuleMemberWithoutReference = Class | Interface | Function | Property | Enum;
export class Reference {
constructor(public moduleName: string, public name: string, public isPrivate: boolean) { }
}
export class Namespace extends HasParent {
public parent: Namespace = null;
public members: { [name: string]: NamespaceMember } = Object.create(null);
constructor(name: string) {
super(name);
}
getMemberFullName(member: HasParent) {
return `${ this.fullName }.${ member.name }`;
}
}
export type NamespaceMember = Class | Interface | Function | Property | Enum;
export class Class extends HasParent {
public parent: Module | Namespace = null;
public unresolvedBaseType: UnresolvedType | TypeReference | IntrinsicTypeReference;
public baseType: TypeReference | IntrinsicTypeReference = null;
public unresolvedInterfaces: (UnresolvedType | TypeReference | IntrinsicTypeReference)[];
public interfaces: (TypeReference | IntrinsicTypeReference)[] = [];
public members: { [name: string]: InterfaceMember } = Object.create(null);
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], public parameters: Parameter[], baseType: UnresolvedType | TypeReference | IntrinsicTypeReference, interfaces: (UnresolvedType | TypeReference | IntrinsicTypeReference)[], public isAbstract: boolean, public isPrivate: boolean) {
super(name);
this.unresolvedBaseType = baseType;
this.unresolvedInterfaces = interfaces;
}
}
export type InterfaceMember = Property | Function;
export class Interface extends HasParent {
public parent: Module | Namespace = null;
public unresolvedBaseTypes: (UnresolvedType | TypeReference | IntrinsicTypeReference)[];
public baseTypes: (TypeReference | IntrinsicTypeReference)[] = [];
public members: { [name: string]: InterfaceMember } = Object.create(null);
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], baseTypes: (UnresolvedType | TypeReference | IntrinsicTypeReference)[], public isPrivate: boolean) {
super(name);
this.unresolvedBaseTypes = baseTypes;
}
}
export class Function extends HasParent {
public parent: Module | Namespace | Class | Interface = null;
constructor(name: string, public astNode: ts.Node, public description: string, public generics: string[], public parameters: Parameter[], public returnType: ReturnType, public isAbstract: boolean, public isPrivate: boolean, public isProtected: boolean, public isStatic: boolean) {
super(name);
}
}
export class Property extends HasParent {
public parent: Module | Namespace | Class | Interface = null;
public getter: Getter = null;
public setter: Setter = null;
constructor(name: string) {
super(name);
}
}
export class Getter {
constructor(public astNode: ts.Node, public description: string, public type: string, public isPrivate: boolean) { }
}
export class Setter {
constructor(public astNode: ts.Node, public description: string, public type: string, public isPrivate: boolean) { }
}
export class Parameter {
public subParameters: Parameter[] = [];
constructor(public name: string, public description: string, public type: string) { }
}
export class ReturnType {
constructor(public description: string, public type: string) { }
}
export class Enum extends HasParent {
public parent: Module | Namespace = null;
public members: EnumMember[] = [];
constructor(name: string, public astNode: ts.Node, public description: string, public isPrivate: boolean) {
super(name);
}
}
export class EnumMember extends HasParent {
public parent: Enum = null;
constructor(name: string, public description: string, public value: number) {
super(name);
}
}
export class TypeReference {
constructor(public type: NamespaceMember, public generics: (TypeReference | IntrinsicTypeReference)[]) { }
get name(): string {
return this.type.name;
}
get fullName(): string {
return this.type.fullName;
}
}
export class IntrinsicTypeReference {
public fullName: string;
constructor(public name: string) {
this.fullName = name;
}
}
export class UnresolvedType {
constructor(public symbol: ts.Symbol, public generics: (UnresolvedType | IntrinsicTypeReference)[]) { }
}
export type HasStringGenerics = Class | Interface | Function;
export function hasStringGenerics(item: NamespaceMember): item is HasStringGenerics {
return (item as HasGenerics).generics !== undefined;
}
export type HasGenerics = HasStringGenerics | TypeReference;
export function hasGenerics(item: ModuleMember | EnumMember | TypeReference): item is HasGenerics {
return (item as HasGenerics).generics !== undefined;
}
export type CanBePrivate = Class | Interface | Function | Getter | Setter | Enum | Reference;
export type CanBeProtected = Function;
export type CanBeStatic = Function;
+312
View File
@@ -0,0 +1,312 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path = require("path");
import ts = require("typescript");
import { File, FileTransform } from "async-build";
import * as AST from "./ast";
import { walk } from "./walker";
export interface StreamingCompilerHost extends ts.CompilerHost {
setOutputStream(outputStream: FileTransform): void;
}
function createCompilerHost(options: ts.CompilerOptions): StreamingCompilerHost {
const host = ts.createCompilerHost(options) as StreamingCompilerHost;
let _outputStream: FileTransform = null;
host.setOutputStream = outputStream => _outputStream = outputStream;
host.writeFile = (fileName, data, writeByteOrderMark, onError?, sourceFiles?): void => {
_outputStream.push({
path: fileName,
contents: new Buffer(data)
});
};
host.useCaseSensitiveFileNames = () => true;
host.getNewLine = () => "\n";
return host;
}
export class Compiler {
private _projectRoot: string = null;
private _host: StreamingCompilerHost;
private _program: ts.Program = null;
compile(projectConfigFile: File) {
this._projectRoot = path.dirname(projectConfigFile.path);
const projectConfig = ts.parseJsonConfigFileContent(JSON.parse(projectConfigFile.contents.toString()), ts.sys, this._projectRoot);
this._host = createCompilerHost(projectConfig.options);
this._program = ts.createProgram(projectConfig.fileNames, projectConfig.options, this._host);
const syntacticDiagnostics = this._program.getSyntacticDiagnostics();
if (syntacticDiagnostics.length > 0) {
this._reportDiagnostics(syntacticDiagnostics);
throw new Error("There were one or more syntactic diagnostics.");
}
const optionsDiagnostics = this._program.getOptionsDiagnostics();
if (optionsDiagnostics.length > 0) {
this._reportDiagnostics(optionsDiagnostics);
throw new Error("There were one or more options diagnostics.");
}
const globalDiagnostics = this._program.getGlobalDiagnostics();
if (globalDiagnostics.length > 0) {
this._reportDiagnostics(globalDiagnostics);
throw new Error("There were one or more global diagnostics.");
}
const semanticDiagnostics = this._program.getSemanticDiagnostics();
if (semanticDiagnostics.length > 0) {
this._reportDiagnostics(semanticDiagnostics);
throw new Error("There were one or more semantic diagnostics.");
}
};
writeFiles(outputStream: FileTransform) {
this._host.setOutputStream(outputStream);
const emitDiagnostics = this._program.emit().diagnostics;
if (emitDiagnostics.length > 0) {
this._reportDiagnostics(emitDiagnostics);
throw new Error("There were one or more emit diagnostics.");
}
};
get projectRoot(): string {
return this._projectRoot;
}
get typeChecker(): ts.TypeChecker {
return this._program.getTypeChecker();
}
get sourceFiles(): ts.SourceFile[] {
return this._program.getSourceFiles();
}
private _reportDiagnostics(diagnostics: ts.Diagnostic[]) {
for (const diagnostic of diagnostics) {
let message = "";
if (diagnostic.file) {
const location = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
message = `${ diagnostic.file.fileName }(${ location.line + 1 },${ location.character }): `;
}
message +=
ts.DiagnosticCategory[diagnostic.category].toLowerCase() +
` TS${ diagnostic.code }: ` +
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
console.error(message);
}
};
}
export function build(root: string, rootNamespaceName: string): FileTransform {
const compiler = new Compiler();
return new FileTransform(function (projectConfigFile): void {
console.log("Compiling " + projectConfigFile.path + "...");
compiler.compile(projectConfigFile);
const walkResult = walk(compiler, root, rootNamespaceName);
addJSDocComments(walkResult.modules);
compiler.writeFiles(this);
console.log("Compile succeeded.");
});
}
function addJSDocComments(modules: { [name: string]: AST.Module }): void {
function visitor(current: AST.Module | AST.ModuleMember | AST.InterfaceMember) {
if (current instanceof AST.Module) {
for (const memberName of Object.keys(current.members)) {
visitor(current.members[memberName]);
}
return;
}
const newComments: string[] = [];
if (current instanceof AST.Class) {
newComments.push("@constructor");
if (current.baseType !== null) {
const baseType = current.baseType;
newComments.push(
"@extends {" +
baseType.fullName + (
(baseType instanceof AST.TypeReference && baseType.generics.length) > 0 ?
(".<" + (baseType as AST.TypeReference).generics.map(generic => generic.fullName).join(", ") + ">") :
""
) +
"}"
);
}
if (current.interfaces.length > 0) {
current.interfaces.forEach(interfase => {
newComments.push(
"@implements {" +
interfase.fullName + (
(interfase instanceof AST.TypeReference && interfase.generics.length > 0) ?
(".<" + interfase.generics.map(generic => generic.fullName).join(", ") + ">") :
""
) +
"}"
);
});
}
Object.keys(current.members).forEach(memberName => visitor(current.members[memberName]));
}
else if (current instanceof AST.Enum) {
newComments.push("@enum");
}
else if (current instanceof AST.Reference) {
return;
}
if (current.parent instanceof AST.Namespace) {
newComments.push("@memberOf " + current.parent.fullName);
}
if (AST.hasStringGenerics(current) && current.generics.length > 0) {
newComments.push("@template " + current.generics.join(", "));
}
if ((current as AST.CanBePrivate).isPrivate) {
newComments.push("@private");
}
if ((current as AST.CanBeProtected).isProtected) {
newComments.push("@protected");
}
if ((current as AST.CanBeStatic).isStatic) {
newComments.push("@static");
}
if (newComments.length > 0) {
if (current instanceof AST.Property) {
const nodes: ts.Node[] = [];
if (current.getter !== null) { nodes.push(current.getter.astNode); }
if (current.setter !== null && nodes[0] !== current.setter.astNode) { nodes.push(current.setter.astNode); }
for (const node of nodes) {
(node as any)["typescript-new-comment"] = newComments;
}
}
else {
(current.astNode as any)["typescript-new-comment"] = newComments;
}
}
}
for (const moduleName of Object.keys(modules)) {
visitor(modules[moduleName]);
}
}
/*
class FakeSourceFile {
public text: string;
public lineMap: number[];
constructor(originalSourceFile: ts.SourceFile) {
this.text = originalSourceFile.text;
this.lineMap = ts.getLineStarts(originalSourceFile).slice();
}
addComment(originalComment: ts.CommentRange, newComments: string[]): ts.CommentRange & { sourceFile: FakeSourceFile } {
var pos = this.text.length;
this.text += "/**\n";
this.lineMap.push(this.text.length);
var originalCommentLines = this.text.substring(originalComment.pos, originalComment.end).split("\n");
originalCommentLines.shift();
originalCommentLines = originalCommentLines.map(line => line.replace(/^\s+/, " "));
if (originalCommentLines.length > 1) {
originalCommentLines.splice(originalCommentLines.length - 1, 0, " *");
}
for (const newComment of newComments) {
originalCommentLines.splice(originalCommentLines.length - 1, 0, " * " + newComment);
}
for (const newCommentLine of originalCommentLines) {
this.text += newCommentLine + "\n";
this.lineMap.push(this.text.length);
}
var end = this.text.length;
return { pos, end, hasTrailingNewLine: originalComment.hasTrailingNewLine, kind: ts.SyntaxKind.MultiLineCommentTrivia, sourceFile: this };
}
}
var fakeSourceFiles: { [name: string]: FakeSourceFile } = Object.create(null);
*/
export const oldGetLeadingCommentRangesOfNodeFromText: typeof ts.getLeadingCommentRangesOfNodeFromText = ts.getLeadingCommentRangesOfNodeFromText.bind(ts);
/*
ts.getLeadingCommentRangesOfNodeFromText = (node: ts.Node, text: string) => {
const originalComments = oldGetLeadingCommentRangesOfNodeFromText(node, text);
if (originalComments !== undefined && (<any>node)["typescript-new-comment"] !== undefined) {
const sourceFileOfNode = ts.getSourceFileOfNode(node);
let fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName];
if (fakeSourceFile === undefined) {
fakeSourceFile = fakeSourceFiles[sourceFileOfNode.fileName] = new FakeSourceFile(sourceFileOfNode);
}
originalComments[originalComments.length - 1] = fakeSourceFile.addComment(originalComments[originalComments.length - 1], (<any>node)["typescript-new-comment"]);
}
return originalComments;
};
var oldWriteCommentRange: typeof ts.writeCommentRange = ts.writeCommentRange.bind(ts);
ts.writeCommentRange = (text: string, lineMap: number[], writer: ts.EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => {
if ((<{ sourceFile: ts.SourceFile }><any>comment).sourceFile) {
const currentSourceFile = (<{ sourceFile: ts.SourceFile }><any>comment).sourceFile;
text = currentSourceFile.text;
lineMap = currentSourceFile.lineMap;
}
return oldWriteCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
};
*/
+23
View File
@@ -0,0 +1,23 @@
declare namespace ts {
interface EmitTextWriter { }
interface IntrinsicType extends Type {
intrinsicName: string;
}
interface SourceFile {
lineMap: number[];
}
function forEachProperty<T, U>(map: Map<T>, callback: (value: T, key: string) => U): U;
function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration): ExpressionWithTypeArguments;
function getClassImplementsHeritageClauseElements(node: ClassLikeDeclaration): ExpressionWithTypeArguments[];
function getInterfaceBaseTypeNodes(node: InterfaceDeclaration): ExpressionWithTypeArguments[];
function getLeadingCommentRangesOfNodeFromText(node: Node, text: string): CommentRange[];
function getLineStarts(sourceFile: SourceFile): number[];
function getSourceFileOfNode(node: Node): SourceFile;
function getTextOfNode(node: Node, includeTrivia?: boolean): string;
function normalizeSlashes(path: string): string;
function writeCommentRange(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string): void;
function hasModifier(node: Node, flags: ModifierFlags): boolean;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { build } from "./compiler";
+879
View File
@@ -0,0 +1,879 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path = require("path");
import ts = require("typescript");
import { Compiler, oldGetLeadingCommentRangesOfNodeFromText } from "./compiler";
import * as AST from "./ast";
interface JSDoc {
description: string;
isAbstract: boolean;
parameters: { [name: string]: AST.Parameter };
returnType: AST.ReturnType;
typeAnnotation: string;
}
class WalkerScope {
private _scopes: AST.HasParent[] = [];
get current(): AST.HasParent {
return (this._scopes.length > 0) ? this._scopes[this._scopes.length - 1] : null;
}
enter<T extends AST.HasParent>(scope: T): T {
scope.parent = this.current;
this._scopes.push(scope);
return scope;
}
leave(): void {
this._scopes.pop();
}
}
class Walker {
private _typeChecker: ts.TypeChecker;
private _globalNS: AST.Namespace = new AST.Namespace("Global");
private _scope: WalkerScope = new WalkerScope();
private _currentSourceFile: ts.SourceFile;
public modules: { [name: string]: AST.Module } = Object.create(null);
public namespaces: { [name: string]: AST.Namespace } = Object.create(null);
constructor(private _compiler: Compiler) {
this._typeChecker = _compiler.typeChecker;
this._globalNS.getMemberFullName = member => member.name;
}
walk(sourceFile: ts.SourceFile): void {
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
if (!(moduleName in this.modules)) {
this.modules[moduleName] = new AST.Module(moduleName);
}
const module = this._scope.enter(this.modules[moduleName]);
this._currentSourceFile = sourceFile;
for (const statement of sourceFile.statements) {
this._walk(statement, module);
}
this._scope.leave();
}
private _walk(node: ts.Node, parent: AST.Module): void {
switch (node.kind) {
case ts.SyntaxKind.VariableStatement:
this._visitVariableStatement(node as ts.VariableStatement, parent);
break;
case ts.SyntaxKind.FunctionDeclaration:
this._visitFunctionDeclaration(node as ts.FunctionDeclaration, parent);
break;
case ts.SyntaxKind.ClassDeclaration:
this._visitClassDeclaration(node as ts.ClassDeclaration, parent);
break;
case ts.SyntaxKind.InterfaceDeclaration:
this._visitInterfaceDeclaration(node as ts.InterfaceDeclaration, parent);
break;
case ts.SyntaxKind.EnumDeclaration:
this._visitEnumDeclaration(node as ts.EnumDeclaration, parent);
break;
case ts.SyntaxKind.ImportDeclaration:
this._visitImportDeclaration(node as ts.ImportDeclaration, parent);
break;
case ts.SyntaxKind.ExportDeclaration:
this._visitExportDeclaration(node as ts.ExportDeclaration, parent);
break;
case ts.SyntaxKind.ExpressionStatement:
case ts.SyntaxKind.ForOfStatement:
case ts.SyntaxKind.IfStatement:
case ts.SyntaxKind.TypeAliasDeclaration:
break;
default:
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _walkClassMember(node: ts.Node, clazz: AST.Class): void {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(node as ts.PropertyDeclaration, clazz);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(node as ts.MethodDeclaration, clazz);
break;
case ts.SyntaxKind.GetAccessor:
this._visitGetAccessor(node as ts.AccessorDeclaration, clazz);
break;
case ts.SyntaxKind.SetAccessor:
this._visitSetAccessor(node as ts.AccessorDeclaration, clazz);
break;
case ts.SyntaxKind.TypeParameter:
case ts.SyntaxKind.Parameter:
case ts.SyntaxKind.Constructor:
break;
default:
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _walkInterfaceMember(node: ts.Node, interfase: AST.Interface): void {
switch (node.kind) {
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.PropertyDeclaration:
this._visitProperty(node as ts.PropertyDeclaration, interfase);
break;
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.MethodDeclaration:
this._visitMethod(node as ts.MethodDeclaration, interfase);
break;
case ts.SyntaxKind.TypeParameter:
case ts.SyntaxKind.CallSignature:
case ts.SyntaxKind.ConstructSignature:
case ts.SyntaxKind.IndexSignature:
break;
default:
console.error(node.kind, ts.SyntaxKind[node.kind], node);
throw new Error("Unrecognized node.");
}
}
private _visitProperty(node: ts.PropertyDeclaration, parent: AST.Class | AST.Interface) {
if (ts.hasModifier(node, ts.ModifierFlags.Private)) {
return;
}
const jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Field ${ ts.getTextOfNode(node.name) } has no @type annotation.`);
jsDoc.typeAnnotation = "*";
}
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(node.name)));
parent.members[property.name] = property;
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, false);
this._scope.leave();
}
private _visitMethod(node: ts.MethodDeclaration, parent: AST.Class | AST.Interface) {
const jsDoc = this._parseJSDoc(node);
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on method ${ ts.getTextOfNode(node.name) }`
);
if (jsDoc.returnType === null && (node.type === undefined || node.type.kind !== ts.SyntaxKind.VoidKeyword)) {
this._notifyIncorrectJsDoc(`Missing @return annotation for method ${ ts.getTextOfNode(node.name) }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
const isProtected = ts.hasModifier(node, ts.ModifierFlags.Protected);
const isStatic = ts.hasModifier(node, ts.ModifierFlags.Static);
const generics = this._getGenericsOfSignatureDeclaration(node);
const method = this._scope.enter(new AST.Function(ts.getTextOfNode(node.name), node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, isProtected, isStatic));
parent.members[method.name] = method;
this._scope.leave();
}
private _visitGetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
const jsDoc = this._parseJSDoc(node);
const name = ts.getTextOfNode(node.name);
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
let property = clazz.members[name] as AST.Property;
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
clazz.members[property.name] = property;
this._scope.leave();
}
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Getter ${ name } has no @type annotation.`);
}
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, isPrivate);
}
private _visitSetAccessor(node: ts.AccessorDeclaration, clazz: AST.Class): void {
const jsDoc = this._parseJSDoc(node);
const name = ts.getTextOfNode(node.name);
const isPrivate = ts.hasModifier(node, ts.ModifierFlags.Private);
let property = clazz.members[name] as AST.Property;
if (property === undefined) {
this._scope.enter(property = new AST.Property(name));
clazz.members[property.name] = property;
this._scope.leave();
}
if (jsDoc.typeAnnotation === null) {
this._notifyIncorrectJsDoc(`Setter ${ name } has no @type annotation.`);
}
property.setter = new AST.Setter(node, jsDoc.description, jsDoc.typeAnnotation, isPrivate);
}
private _visitVariableStatement(node: ts.VariableStatement, parent: AST.Module): void {
if (node.declarationList.declarations.length > 1) {
return;
}
const declaration = node.declarationList.declarations[0];
if (ts.hasModifier(declaration, ts.ModifierFlags.Ambient)) {
return;
}
const jsDoc = this._parseJSDoc(node);
if (jsDoc.typeAnnotation === null) {
return;
}
const property = this._scope.enter(new AST.Property(ts.getTextOfNode(declaration.name)));
property.getter = new AST.Getter(node, jsDoc.description, jsDoc.typeAnnotation, false);
parent.members[property.name] = property;
this._scope.leave();
}
private _visitFunctionDeclaration(node: ts.FunctionDeclaration, parent: AST.Module): void {
const jsDoc = this._parseJSDoc(node);
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
const generics = this._getGenericsOfSignatureDeclaration(node);
const parameters = this._connectParameters(node.parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on function ${ node.name.text }`
);
if (node.type === undefined) {
this._notifyIncorrectJsDoc(`Missing return type annotation for function ${ node.name.text }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
else if (jsDoc.returnType === null && node.type.kind !== ts.SyntaxKind.VoidKeyword) {
this._notifyIncorrectJsDoc(`Missing @return annotation for function ${ node.name.text }`);
jsDoc.returnType = new AST.ReturnType("", "*");
}
const freeFunction = this._scope.enter(new AST.Function(node.name.text, node, jsDoc.description, generics, parameters, jsDoc.returnType, jsDoc.isAbstract, isPrivate, false, false));
parent.members[freeFunction.name] = freeFunction;
this._scope.leave();
}
private _visitClassDeclaration(node: ts.ClassDeclaration, parent: AST.Module): void {
const jsDoc = this._parseJSDoc(node);
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
const generics = this._getGenericsOfInterfaceType(type);
const baseTypeHeritageClauseElement = ts.getClassExtendsHeritageClauseElement(node) || null;
let baseType: AST.UnresolvedType = null;
if (baseTypeHeritageClauseElement !== null) {
baseType = new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(baseTypeHeritageClauseElement).symbol,
this._getGenericsOfTypeReferenceNode(baseTypeHeritageClauseElement, generics)
);
}
const interfaces = (ts.getClassImplementsHeritageClauseElements(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type, generics)
));
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
let parameters: AST.Parameter[] = [];
if (type.symbol.members["__constructor"] !== undefined) {
parameters = this._connectParameters((type.symbol.members["__constructor"].declarations[0] as ts.ConstructorDeclaration).parameters, jsDoc.parameters,
parameterName => `Could not find @param annotation for ${ parameterName } on constructor in class ${ node.name.text }`
);
}
else if (Object.keys(jsDoc.parameters).length > 0) {
this._notifyIncorrectJsDoc("There are @param annotations on this class but it has no constructors.");
}
const clazz = this._scope.enter(new AST.Class(node.name.text, node, jsDoc.description, generics, parameters, baseType, interfaces, jsDoc.isAbstract, isPrivate));
parent.members[clazz.name] = clazz;
ts.forEachProperty(type.symbol.exports, symbol => {
if (symbol.name === "prototype") {
return;
}
for (const declaration of symbol.declarations) {
this._walkClassMember(declaration, clazz);
}
});
ts.forEachProperty(type.symbol.members, symbol => {
for (const declaration of symbol.declarations) {
this._walkClassMember(declaration, clazz);
}
});
this._scope.leave();
}
private _visitInterfaceDeclaration(node: ts.InterfaceDeclaration, parent: AST.Module): void {
const jsDoc = this._parseJSDoc(node);
const type = this._typeChecker.getTypeAtLocation(node) as ts.InterfaceType;
const generics = this._getGenericsOfInterfaceType(type);
const baseTypes = (ts.getInterfaceBaseTypeNodes(node) || []).map(type => new AST.UnresolvedType(
this._typeChecker.getTypeAtLocation(type).symbol,
this._getGenericsOfTypeReferenceNode(type, generics)
));
const existingInterfaceType = parent.members[node.name.text];
if (existingInterfaceType !== undefined) {
return;
}
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
const interfase = this._scope.enter(new AST.Interface(node.name.text, node, jsDoc.description, generics, baseTypes, isPrivate));
parent.members[interfase.name] = interfase;
ts.forEachProperty(type.symbol.members, symbol => {
for (const declaration of symbol.declarations) {
this._walkInterfaceMember(declaration, interfase);
}
});
this._scope.leave();
}
private _visitEnumDeclaration(node: ts.EnumDeclaration, parent: AST.Module): void {
const jsDoc = this._parseJSDoc(node);
const existingEnumType = parent.members[node.name.text];
if (existingEnumType !== undefined) {
return;
}
const isPrivate = !ts.hasModifier(node, ts.ModifierFlags.Export);
const type = this._typeChecker.getTypeAtLocation(node);
const enumType = this._scope.enter(new AST.Enum(node.name.text, node, jsDoc.description, isPrivate));
parent.members[enumType.name] = enumType;
ts.forEachProperty(type.symbol.exports, symbol => {
this._visitEnumMember(symbol.declarations[0] as ts.EnumMember, enumType);
});
this._scope.leave();
}
private _visitEnumMember(node: ts.EnumMember, parent: AST.Enum): void {
const jsDoc = this._parseJSDoc(node);
const value = (node.initializer === undefined) ? null : parseInt((node.initializer as ts.LiteralExpression).text);
const enumMember = this._scope.enter(new AST.EnumMember(ts.getTextOfNode(node.name), (jsDoc === null) ? "" : jsDoc.description, value));
parent.members.push(enumMember);
this._scope.leave();
}
private _visitImportDeclaration(node: ts.ImportDeclaration, parent: AST.Module): void {
if (node.importClause === undefined) {
// import "foo";
return;
}
if (node.importClause.namedBindings === undefined) {
throw new Error("Default import is not supported.");
}
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
if ((node.importClause.namedBindings as ts.NamespaceImport).name !== undefined) {
// import * as foo from "baz";
parent.members[(node.importClause.namedBindings as ts.NamespaceImport).name.text] = new AST.Reference(moduleName, "*", true);
}
else if ((node.importClause.namedBindings as ts.NamedImports).elements !== undefined) {
// import { foo, bar } from "baz";
for (const element of (node.importClause.namedBindings as ts.NamedImports).elements) {
const importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, true);
}
}
else {
throw new Error("Unrecognized import declaration syntax.");
}
}
private _visitExportDeclaration(node: ts.ExportDeclaration, parent: AST.Module): void {
if (node.moduleSpecifier !== undefined) {
// export { foo } from "bar";
const moduleName = this._resolve((node.moduleSpecifier as ts.LiteralExpression).text, parent);
for (const element of node.exportClause.elements) {
const importedName = element.propertyName && element.propertyName.text || element.name.text;
parent.members[element.name.text] = new AST.Reference(moduleName, importedName, false);
}
}
else {
// export { foo };
for (const element of node.exportClause.elements) {
(parent.members[element.name.text] as AST.CanBePrivate).isPrivate = false;
}
}
}
private _resolve(relativeModuleName: string, currentModule: AST.Module): string {
let result = ts.normalizeSlashes(path.join(currentModule.name, `../${ relativeModuleName }`));
if (result[0] !== ".") {
result = `./${ result }`;
}
return result;
}
private _parseJSDoc(node: ts.Node): JSDoc {
let comments = oldGetLeadingCommentRangesOfNodeFromText(node, this._currentSourceFile.text);
if (comments === undefined) {
comments = [];
}
if (comments.length > 1) {
comments = [comments[comments.length - 1]];
}
const comment =
(comments.length === 0) ?
"" :
this._currentSourceFile.text.substring(comments[0].pos, comments[0].end);
const commentStartIndex = comment.indexOf("/**");
const commentEndIndex = comment.lastIndexOf("*/");
const lines =
(commentStartIndex === -1 || commentEndIndex === -1) ?
[] :
comment.substring(commentStartIndex + 2, commentEndIndex).split("\n").map(line => {
const match = line.match(/^[ \t]*\* (.*)/);
if (match === null) {
return "";
}
return match[1];
});
let rootDescription = "";
const parameters: { [name: string]: AST.Parameter } = Object.create(null);
let typeAnnotation: string = null;
let returnType: AST.ReturnType = null;
let isAbstract = false;
let lastRead: { description: string } = null;
for (const line of lines) {
const firstWordMatch = line.match(/^\s*(\S+)(\s*)/);
const firstWord = (firstWordMatch !== null) ? firstWordMatch[1] : "";
let remainingLine = (firstWordMatch !== null) ? line.substring(firstWordMatch[0].length) : "";
if (firstWord[0] === "@") {
lastRead = null;
}
switch (firstWord) {
case "@abstract":
isAbstract = true;
break;
case "@param": {
let type: string;
[type, remainingLine] = this._readType(remainingLine);
const [, name, description] = remainingLine.match(/(\S+)\s*(.*)/);
const subParameterMatch = name.match(/^(?:(.+)\.([^\.]+))|(?:(.+)\[("[^\[\]"]+")\])$/);
if (subParameterMatch === null) {
parameters[name] = lastRead = new AST.Parameter(name, description, type);
}
else {
const parentName = subParameterMatch[1] || subParameterMatch[3];
const childName = subParameterMatch[2] || subParameterMatch[4];
const parentParameter = parameters[parentName];
parentParameter.subParameters.push(lastRead = new AST.Parameter(childName, description, type));
}
break;
}
case "@return": {
const [type, description] = this._readType(remainingLine);
returnType = lastRead = new AST.ReturnType(description, type);
break;
}
case "@type":
[typeAnnotation] = this._readType(remainingLine);
break;
default:
if (lastRead !== null) {
lastRead.description += "\n" + line;
}
else {
rootDescription += ((rootDescription.length > 0) ? "\n" : "") + line;
}
break;
}
}
return {
description: rootDescription,
isAbstract: isAbstract,
parameters: parameters,
returnType: returnType,
typeAnnotation: typeAnnotation,
};
}
private _readType(remainingLine: string): [string, string] {
if (remainingLine[0] !== "{") {
return ["*", remainingLine];
}
let index = -1;
let numberOfUnterminatedBraces = 0;
for (let i = 0; i < remainingLine.length; i++) {
if (remainingLine[i] === "{") {
numberOfUnterminatedBraces++;
}
else if (remainingLine[i] === "}") {
numberOfUnterminatedBraces--;
if (numberOfUnterminatedBraces === 0) {
index = i;
break;
}
}
}
if (index === -1) {
throw new Error("Unterminated type specifier.");
}
const type = remainingLine.substr(1, index - 1);
remainingLine = remainingLine.substr(index + 1).replace(/^\s+/, "");
return [type, remainingLine];
}
private _getGenericsOfSignatureDeclaration(signatureDeclaration: ts.SignatureDeclaration): string[] {
if (signatureDeclaration.typeParameters === undefined) {
return [];
}
return signatureDeclaration.typeParameters.map(typeParameter => typeParameter.name.text);
}
private _getGenericsOfTypeReferenceNode(typeReferenceNode: ts.ExpressionWithTypeArguments, intrinsicGenerics: string[]): (AST.UnresolvedType | AST.IntrinsicTypeReference)[] {
if (typeReferenceNode.typeArguments === undefined) {
return [];
}
const typeReference = this._typeChecker.getTypeAtLocation(typeReferenceNode) as ts.TypeReference;
return typeReference.typeArguments.map(typeArgument => {
if ((typeArgument as ts.IntrinsicType).intrinsicName !== undefined) {
return new AST.IntrinsicTypeReference((typeArgument as ts.IntrinsicType).intrinsicName);
}
if (typeArgument.flags & ts.TypeFlags.TypeParameter) {
if (intrinsicGenerics.indexOf(typeArgument.symbol.name) !== -1) {
return new AST.IntrinsicTypeReference(typeArgument.symbol.name);
}
throw new Error(`Unbound type parameter ${ typeArgument.symbol.name }`);
}
return new AST.UnresolvedType(typeArgument.symbol, []);
});
}
private _getGenericsOfInterfaceType(interfaceType: ts.InterfaceType): string[] {
if (interfaceType.typeParameters === undefined) {
return [];
}
return interfaceType.typeParameters.map(typeParameter => {
return typeParameter.symbol.name;
});
}
private _connectParameters(astParameters: ts.ParameterDeclaration[], jsDocParameters: { [name: string]: AST.Parameter }, onMissingMessageCallback: (parameterName: string) => string) {
return astParameters.map(parameter => {
let parameterName = (parameter.name as ts.Identifier).text;
if (parameterName[0] === "_") {
parameterName = parameterName.substr(1);
}
let jsDocParameter = jsDocParameters[parameterName];
if (jsDocParameter === undefined) {
this._notifyIncorrectJsDoc(onMissingMessageCallback.call(this, parameterName));
jsDocParameter = new AST.Parameter(parameterName, "*", "");
}
return jsDocParameter;
});
}
private _notifyIncorrectJsDoc(message: string): void {
const fileName = path.basename(this._currentSourceFile.fileName);
if (fileName === "lib.es5.d.ts" || fileName === "lib.dom.d.ts") {
return;
}
throw new Error(`${ fileName }: ${ this._scope.current.fullName }: ${ message }`);
}
link(rootNamespaceName: string): void {
for (const moduleName of Object.keys(this.modules)) {
const module = this.modules[moduleName];
for (const memberName of Object.keys(module.members)) {
const member = module.members[memberName];
if (member instanceof AST.Class) {
if (member.unresolvedBaseType instanceof AST.UnresolvedType) {
member.baseType = this._resolveTypeReference(member.unresolvedBaseType);
}
else {
member.baseType = member.unresolvedBaseType;
}
member.interfaces = member.unresolvedInterfaces.map(interfase => {
if (interfase instanceof AST.UnresolvedType) {
return this._resolveTypeReference(interfase);
}
return interfase;
});
}
else if (member instanceof AST.Interface) {
member.baseTypes = member.unresolvedBaseTypes.map(baseType => {
if (baseType instanceof AST.UnresolvedType) {
return this._resolveTypeReference(baseType);
}
return baseType;
});
}
else if (member instanceof AST.Enum) {
let value = 0;
for (const enumMember of member.members) {
if (enumMember.value === null) {
enumMember.value = value;
}
else {
value = enumMember.value;
}
value++;
}
}
}
}
this.namespaces[rootNamespaceName] = this._scope.enter(new AST.Namespace(rootNamespaceName));
this._moduleToNamespace(this.modules["./index"]);
this._scope.leave();
}
private _moduleToNamespace(module: AST.Module): void {
for (const memberName of Object.keys(module.members)) {
let member = module.members[memberName];
if (member instanceof AST.Reference) {
if (member.isPrivate) {
continue;
}
if (member.name === "*") {
const newNamespace = this._scope.enter(new AST.Namespace(memberName));
const existingNamespace = this.namespaces[newNamespace.fullName];
if (existingNamespace !== undefined) {
this._scope.leave();
this._scope.enter(existingNamespace);
}
else {
this.namespaces[newNamespace.fullName] = newNamespace;
}
let referencedModuleName = member.moduleName;
let referencedModule = this.modules[referencedModuleName];
if (referencedModule === undefined && ((referencedModuleName + "/index") in this.modules)) {
member.moduleName = referencedModuleName = referencedModuleName + "/index";
referencedModule = this.modules[referencedModuleName];
}
this._moduleToNamespace(referencedModule);
this._scope.leave();
}
else {
while (member instanceof AST.Reference) {
member = this.modules[member.moduleName].members[member.name];
}
this._scope.enter(member);
this._scope.leave();
(this._scope.current as AST.Namespace).members[member.name] = member;
}
}
else if (!(member as AST.CanBePrivate).isPrivate) {
this._scope.enter(member);
this._scope.leave();
(this._scope.current as AST.Namespace).members[member.name] = member;
}
}
}
private _resolveTypeReference(unresolvedType: AST.UnresolvedType): AST.TypeReference {
let node: ts.Node = unresolvedType.symbol.declarations[0];
while (node.kind !== ts.SyntaxKind.SourceFile) {
node = node.parent;
}
const sourceFile = node as ts.SourceFile;
const moduleName = this._moduleNameFromFileName(sourceFile.fileName);
const module = this.modules[moduleName];
let result = module.members[unresolvedType.symbol.name];
if (result === undefined) {
throw new Error(`Type ${ unresolvedType.symbol.name } could not be resolved.`);
}
while (result instanceof AST.Reference) {
result = this.modules[result.moduleName].members[result.name];
}
const resultGenerics = unresolvedType.generics.map(generic => {
if (generic instanceof AST.UnresolvedType) {
return this._resolveTypeReference(generic);
}
return generic;
});
return new AST.TypeReference(result, resultGenerics);
}
private _moduleNameFromFileName(fileName: string): string {
let result = ts.normalizeSlashes(path.relative(this._compiler.projectRoot, fileName));
result = result.substr(0, result.length - ".ts".length);
if (result[0] !== ".") {
result = `./${ result }`;
}
return result;
}
}
export function walk(compiler: Compiler, root: string, rootNamespaceName: string) {
const sourceFiles = compiler.sourceFiles;
const walker = new Walker(compiler);
// Walk
for (const sourceFile of sourceFiles) {
if (
path.basename(sourceFile.fileName) === "lib.es5.d.ts" ||
path.basename(sourceFile.fileName) === "lib.dom.d.ts" ||
sourceFile.fileName.substr(-"references.d.ts".length) === "references.d.ts"
) {
continue;
}
walker.walk(sourceFile);
}
// Link base types and set enum member values if unspecified.
walker.link(rootNamespaceName);
// Return types
return { namespaces: walker.namespaces, modules: walker.modules };
}
+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.
*/
var fs = require("fs");
var path = require("path");
var UglifyJS = require("uglify-js");
var FileTransform = require("async-build").FileTransform;
var Run = (function () {
function Run(outputLibraryName, unusedVarsToIgnore) {
this._outputLibraryName = outputLibraryName;
this._unusedVarsToIgnore = unusedVarsToIgnore;
this._root = UglifyJS.parse(fs.readFileSync(path.resolve(__filename, "..", "umd-wrapper.js"), "utf8"));
this._root.figure_out_scope({ screw_ie8: true });
this._toInsert = null;
this._rootSourceMap = null;
}
Run.prototype.addFile = function (file) {
switch (path.extname(file.path)) {
case ".js":
try {
this._toInsert = UglifyJS.parse(file.contents.toString(), {
filename: path.basename(file.path),
toplevel: null,
}).body;
}
catch (ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
throw new Error("UglifyJS parse error: " + ex.toString() + "\n");
}
throw ex;
}
break;
case ".map":
var rawSourceMap = JSON.parse(file.contents.toString());
this._rootSourceMap = UglifyJS.SourceMap({
file: this._outputLibraryName + ".js",
root: "",
orig: rawSourceMap,
});
var generator = this._rootSourceMap.get();
rawSourceMap.sources.forEach(function (sourceRelativePath, index) {
generator.setSourceContent(sourceRelativePath, rawSourceMap.sourcesContent[index]);
});
break;
}
};
Run.prototype.build = function (outputStream) {
var _this = this;
// Splice in the TS output into the UMD wrapper.
var insertionParent = this._root.body[0].body.args[1].body;
this._toInsert.reverse();
for (var i = this._toInsert.length - 1; i >= 0; i--) {
var node = this._toInsert[i];
if (node instanceof UglifyJS.AST_Var) {
for (var j = 0; j < node.definitions.length; j++) {
var definition = node.definitions[j];
if (definition.name.name === "__extends" || definition.name.name === "__decorate") {
definition.value = definition.value.right;
}
}
insertionParent.splice(-1, 0, node);
this._toInsert.splice(i, 1);
}
}
insertionParent.splice.apply(insertionParent, [-1, 0].concat(this._toInsert));
// Fixups
for (var i = 0; i < this._toInsert.length; i++) {
var node = this._toInsert[i];
if (node instanceof UglifyJS.AST_Statement && node.body instanceof UglifyJS.AST_Call && node.body.expression.name === "define") {
var defineCall = node.body;
defineCall.expression.name = "def";
if (defineCall.args[1].elements[0].value !== "require") {
throw new Error("Expected first dep to be require");
}
defineCall.args[1].elements.shift();
defineCall.args[2].argnames.shift();
if (defineCall.args[1].elements[0].value !== "exports") {
throw new Error("Expected second dep to be exports");
}
defineCall.args[1].elements.shift();
defineCall.args[2].argnames.push(defineCall.args[2].argnames.shift());
}
}
// Remove all license headers except the one from the UMD wrapper
var firstLicenseHeader = null;
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node.start) {
(node.start.comments_before || []).some(function (comment, i) {
if (comment.value.indexOf("Copyright") !== -1) {
if (firstLicenseHeader === null) {
firstLicenseHeader = comment;
}
else if (comment !== firstLicenseHeader) {
node.start.comments_before.splice(i, 1);
}
return true;
}
return false;
});
}
}));
// Fixup anonymous functions to print a space after "function"
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_Lambda && !node.name) {
node.name = Object.create(UglifyJS.AST_Node.prototype);
node.name.print = function () { };
}
}));
// Fix alignment of multi-line block comments
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node.start && node.start.comments_before) {
node.start.comments_before.forEach(function (comment) {
if (comment.value.indexOf("Copyright") !== -1) {
return;
}
var lines = comment.value.split("\n");
if (lines.length < 2) {
return;
}
var indent = " "; // 5 spaces
for (var i = 0; i < comment.col; i++) {
indent += " ";
}
lines[lines.length - 1] = lines[lines.length - 1].replace(/\s+$/, indent);
comment.value = [lines[0]].concat(lines.slice(1).map(function (line) { return line.replace(/^\s+/, indent); })).join("\n");
});
}
}));
this._root.figure_out_scope({ screw_ie8: true });
// Remove some things from the AST
var nodesToRemove = [];
// Set if there are any unused variables apart from the ones in unusedVarsToIgnore
var haveUnusedVars = false;
// Repeat because removing some declarations may make others unreferenced
for (;;) {
this._root.figure_out_scope({ screw_ie8: true });
// Unreferenced variable and function declarations, and unreferenced terminal function arguments
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolDeclaration && node.unreferenced()) {
if (node instanceof UglifyJS.AST_SymbolFunarg) {
if (this.parent().argnames.indexOf(node) === this.parent().argnames.length - 1) {
nodesToRemove.push({ node: node, parent: this.parent().argnames });
}
}
else if (node instanceof UglifyJS.AST_SymbolVar) {
if (_this._unusedVarsToIgnore.indexOf(node.name) !== -1) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).definitions });
if (this.parent(1).definitions.length === 1) {
nodesToRemove.push({ node: this.parent(1), parent: this.parent(2).body });
}
}
else {
haveUnusedVars = true;
}
}
else if (node instanceof UglifyJS.AST_SymbolDefun) {
nodesToRemove.push({ node: this.parent(), parent: this.parent(1).body });
}
}
}));
if (nodesToRemove.length === 0) {
break;
}
nodesToRemove.forEach(function (tuple) {
tuple.parent.splice(tuple.parent.indexOf(tuple.node), 1);
});
nodesToRemove = [];
}
// Move var statements at the end of blocks (generated by TS for rest parameters) to the start of the block.
// This is needed to prevent unreachable-code warnings from UJS
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_Block &&
node.body[node.body.length - 1] instanceof UglifyJS.AST_Var
) {
node.body.unshift(node.body.pop());
}
}));
// Split multiple vars per declaration into one var per declaration
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_Var &&
node.definitions.length > 1 &&
this.parent() instanceof UglifyJS.AST_Block
) {
var parent = this.parent().body;
parent.splice.apply(parent, [parent.indexOf(node), 1].concat(node.definitions.map(function (definition) {
return new UglifyJS.AST_Var({ start: node.start, end: node.end, definitions: [definition] });
})));
}
}));
// Rename all function arguments that begin with _ to not have the _.
// This converts the TypeScript syntax of declaring private members in the constructor declaration `function Color(private _red: number, ...)` to `function Color(red, ...)`
// so that it matches the JSDoc (and looks nicer).
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (
node instanceof UglifyJS.AST_SymbolFunarg &&
node.thedef.name[0] === "_" &&
node.thedef.name[1] !== "_" &&
node.thedef.name !== "_super" // Don't rename _super (used in TypeScript's inheritance shim) to super. super is a reserved word.
) {
node.thedef.name = node.thedef.name.slice(1);
}
}));
// Output
var output = {
source_map: this._rootSourceMap,
ascii_only: true,
beautify: true,
comments: true,
};
var stream = UglifyJS.OutputStream(output);
this._root.print(stream);
outputStream.push({
path: this._outputLibraryName + ".js",
contents: Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL=" + this._outputLibraryName + ".js.map")])
});
outputStream.push({
path: this._outputLibraryName + ".js.map",
contents: new Buffer(this._rootSourceMap.get().toString())
});
// Print unused variables
if (haveUnusedVars) {
this._root.walk(new UglifyJS.TreeWalker(function (node, descend) {
if (node instanceof UglifyJS.AST_SymbolVar && node.unreferenced()) {
if (_this._unusedVarsToIgnore.indexOf(node.name) === -1) {
console.warn("Unused variable %s at %s:%s:%s", node.name, node.start.file, node.start.line, node.start.col);
}
}
}));
}
};
return Run;
})();
module.exports = {
build: function (outputLibraryName, unusedVarsToIgnore) {
var run = new Run(outputLibraryName, unusedVarsToIgnore);
return new FileTransform(function (file) {
run.addFile(file);
}, function () {
run.build(this);
});
},
watch: function (outputLibraryName, unusedVarsToIgnore) {
var files = Object.create(null);
return new FileTransform(function (file) {
if (file.path !== "END") {
files[file.path] = file;
}
else {
var run = new Run(outputLibraryName, unusedVarsToIgnore);
Object.keys(files).forEach(function (filename) {
run.addFile(files[filename]);
});
run.build(this);
}
});
},
minify: function () {
var codeFile = null;
var sourceMapFile = null;
return new FileTransform(function (file) {
switch (path.extname(file.path)) {
case ".js":
codeFile = file;
break;
case ".map":
sourceMapFile = file;
break;
}
if (codeFile !== null && sourceMapFile !== null) {
UglifyJS.base54.reset();
// Parse
var root = null;
root = UglifyJS.parse(codeFile.contents.toString(), {
filename: path.basename(codeFile.path),
toplevel: root
});
root.figure_out_scope({ screw_ie8: true });
// Warnings
root.scope_warnings({
func_arguments: false
});
// Compress
var compressor = UglifyJS.Compressor({
warnings: true,
screw_ie8: true
});
root = root.transform(compressor);
// Mangle
root.figure_out_scope({ screw_ie8: true });
root.compute_char_frequency();
root.mangle_names({ screw_ie8: true });
root = UglifyJS.mangle_properties(root, {
regex: /^_/
});
// Output
var firstLicenseHeaderFound = false; // To detect and preserve the first license header
var output = {
source_map: UglifyJS.SourceMap({
file: path.basename(sourceMapFile.path),
orig: sourceMapFile.contents.toString()
}),
ascii_only: true,
comments: function (node, comment) {
if (!firstLicenseHeaderFound && comment.value.indexOf("Copyright") !== -1) {
firstLicenseHeaderFound = true;
return true;
}
return false;
},
screw_ie8: true
};
var stream = UglifyJS.OutputStream(output);
root.print(stream);
codeFile.path = codeFile.path.replace(/\.js$/, ".min.js");
sourceMapFile.path = sourceMapFile.path.replace(/\.js\.map$/, ".min.js.map");
codeFile.contents = Buffer.concat([new Buffer(stream.toString()), new Buffer("\n//# sourceMappingURL="), new Buffer(sourceMapFile.path)]);
this.push(codeFile);
var inputSourceMapObject = JSON.parse(sourceMapFile.contents.toString());
var outputSourceMapObject = output.source_map.get();
outputSourceMapObject._sources.toArray().forEach(function (filename, i) {
outputSourceMapObject.setSourceContent(filename, inputSourceMapObject.sourcesContent[i]);
});
sourceMapFile.contents = new Buffer(output.source_map.toString());
this.push(sourceMapFile);
codeFile = null;
sourceMapFile = null;
}
});
}
};
var originalSymbolUnreferenced = UglifyJS.AST_Symbol.prototype.unreferenced;
// Workaround for https://github.com/mishoo/UglifyJS2/issues/789 - Nodes explicitly marked with ujs:unreferenced will not be warned for.
UglifyJS.AST_Symbol.prototype.unreferenced = function () {
if (this.start.comments_before.length > 0 && this.start.comments_before[0].value.trim() === "ujs:unreferenced") {
return false;
}
return originalSymbolUnreferenced.call(this);
};
+65
View File
@@ -0,0 +1,65 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @license
*/
(function (root, factory) {
var global = this;
if (typeof define === "function" && define.amd) {
define([], function() {
return factory(global);
});
}
else if (typeof exports === "object" && typeof module === "object") {
module.exports = factory(global);
}
else if (typeof exports === "object") {
exports.libjass = factory(global);
}
else {
root.libjass = factory(global);
}
})(this, function (global) {
"use strict";
var registeredModules = Object.create(null);
var installedModules = Object.create(null);
function def(moduleId, deps, body) {
installedModules[moduleId] = { deps: deps, body: body, };
}
function req(moduleId) {
if (moduleId in registeredModules) {
return registeredModules[moduleId];
}
var exports = registeredModules[moduleId] = Object.create(null);
var deps = installedModules[moduleId].deps.map(req);
deps.push(exports);
installedModules[moduleId].body.apply(null, deps);
return exports;
}
return req("index");
});
-204
View File
@@ -1,204 +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.
*/
"use strict";
/**
* Creates a video of the given color, dimensions and duration, and prepares the given video element to play it.
*/
function makeDummyVideo(video, width, height, color, duration) {
return new libjass.Promise(function (resolve, reject) {
video.width = width;
video.height = height;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
context.fillStyle = color;
context.fillRect(0, 0, width, height);
var stream = canvas.captureStream(0);
var recorder = new MediaRecorder(stream);
recorder.start(1); // Get as many events as possible to have a chance at getting the smallest possible chunk.
var blob = null;
recorder.addEventListener("dataavailable", function (event) {
if (recorder.state === "inactive") {
// Being called after recorder.stop(). Do nothing.
return;
}
if (event.data.size === 0) {
console.warn("No new data.");
return;
}
recorder.pause(); // Don't get flooded with new blobs while parsing the current blob.
if (blob === null) {
blob = event.data;
if (!MediaSource.isTypeSupported(blob.type)) {
/* MediaRecorder may record a format that MediaSource doesn't support. As of Nightly 46, this is true, since MediaRecorder
* records webm which MediaSource doesn't play unless media.mediasource.webm.enabled is true in about:config
*/
recorder.stop();
reject(new Error("MediaRecorder is recording video in " + blob.type + " but MediaSource doesn't support it. Make sure media.mediasource.webm.enabled is on in about:config"));
return;
}
}
else {
blob = new Blob([blob, event.data], { type: blob.type });
}
// Data is available but may not contain any frames. Test for that.
libjass.Promise.all([newMediaSourceAndBuffer(video, blob.type), blobToArrayBuffer(blob)]).then(function (results) {
var mediaSource = results[0][0];
var sourceBuffer = results[0][1];
var buffer = results[1];
return appendBuffer(sourceBuffer, buffer).then(function () {
console.log("Got enough data for " + getEndTime(sourceBuffer) + " seconds.");
return [mediaSource, sourceBuffer, buffer];
});
}).then(function (result) {
resolve(result);
recorder.stop();
}, function (reason) {
console.warn(reason);
console.warn("Waiting for more data...");
recorder.resume();
});
});
}).then(function (results) {
var mediaSource = results[0];
var sourceBuffer = results[1];
var buffer = results[2];
return appendBufferUntil(sourceBuffer, buffer, duration).then(function () {
return mediaSource.endOfStream();
});
});
}
/**
* Sets up the given `video` to use a new MediaSource, and appends a new SourceBuffer of the given `type`.
*/
function newMediaSourceAndBuffer(video, type) {
return new libjass.Promise(function (resolve, reject) {
var mediaSource = new MediaSource();
function onSourceOpen() {
mediaSource.removeEventListener("sourceopen", onSourceOpen, false);
try {
var sourceBuffer = mediaSource.addSourceBuffer(type);
resolve([mediaSource, sourceBuffer]);
}
catch (ex) {
reject(ex);
}
}
mediaSource.addEventListener("sourceopen", onSourceOpen, false);
video.src = URL.createObjectURL(mediaSource);
});
}
/**
* Converts a Blob to an ArrayBuffer
*/
function blobToArrayBuffer(blob) {
return new libjass.Promise(function (resolve, reject) {
var fileReader = new FileReader();
fileReader.addEventListener("load", function () {
resolve(fileReader.result);
}, false);
fileReader.addEventListener("error", function (event) {
reject(event);
});
fileReader.readAsArrayBuffer(blob);
});
}
/**
* Appends the given video data `buffer` to the given `sourceBuffer`.
*/
function appendBuffer(sourceBuffer, buffer) {
return new libjass.Promise(function (resolve, reject) {
var currentEndTime = getEndTime(sourceBuffer);
function onUpdateEnd() {
sourceBuffer.removeEventListener("updateend", onUpdateEnd, false);
if (sourceBuffer.buffered.length === 0) {
reject(new Error("buffer of length " + buffer.byteLength + " could not be appended to sourceBuffer. It's probably too small and doesn't contain any frames."));
return;
}
var newEndTime = getEndTime(sourceBuffer);
if (newEndTime === currentEndTime) {
reject(new Error("sourceBuffer is not increasing in size. Perhaps buffer is too small?"));
return;
}
resolve();
}
sourceBuffer.addEventListener("updateend", onUpdateEnd, false);
sourceBuffer.timestampOffset = currentEndTime;
sourceBuffer.appendBuffer(buffer);
});
}
/**
* Repeatedly appends the given video data `buffer` to the given `sourceBuffer` until it is of `duration` length.
*/
function appendBufferUntil(sourceBuffer, buffer, duration) {
var currentEndTime = getEndTime(sourceBuffer);
if (currentEndTime < duration) {
return appendBuffer(sourceBuffer, buffer).then(function () {
return appendBufferUntil(sourceBuffer, buffer, duration);
});
}
else {
return libjass.Promise.resolve();
}
}
/**
* Gets the end time of a SourceBuffer.
*/
function getEndTime(sourceBuffer) {
return (sourceBuffer.buffered.length === 0) ? 0 : sourceBuffer.buffered.end(0);
}
-566
View File
@@ -1,566 +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.
*/
/* This page demonstrates how to use libjass. It allows you to choose a video that will be played in a <video> element, and an ASS script.
*
*
* Below you will find the basics of using libjass - the ASS.fromUrl() and ASS.fromString() functions, and the DefaultRenderer class - and some advanced concepts:
* - handling resizable video
* - autoplaying video
* - toggling subs on user input
*
* The advanced concepts are optional and marked with "(Advanced)"
*
*
* Some more advanced uses not demonstrated here include:
* - renderer settings, such as using custom fonts or controlling the pre-render time
* - using WebRenderer instead of DefaultRenderer for control over the placement of the subs <div>, enabling fullscreen video, etc.
* - using ASS.fromString() instead of ASS.fromUrl() when you have the ASS string already, such as in an online subs editor
* - using libjass.parser.StreamParser and its minimalAss promise directly, such as for dynamically generated scripts
* - working with SRT subs by specifying libjass.Format.SRT to the ASS.from*() functions
*
*
* API documentation is available at http://arnavion.github.io/libjass/api.xhtml
*/
"use strict";
/* This section sets up the default look of the page - a bunch of input controls for the video and ASS script, and the
* "Go" button that will create a <video> element and start rendering subs over it. If you already have URLs for a video and script on your website,
* you would just generate the <video> element and start using libjass directly.
*/
addEventListener("DOMContentLoaded", function () {
var htmlConsole = document.querySelector("#console");
htmlConsoleLog = htmlConsoleLog(htmlConsole);
htmlConsoleWarn = htmlConsoleWarn(htmlConsole);
htmlConsoleError = htmlConsoleError(htmlConsole);
var originalConsoleLog = console.log.bind(console);
var originalConsoleWarn = console.warn.bind(console);
var originalConsoleError = console.log.bind(console);
console.log = function () {
htmlConsoleLog([].slice.call(arguments, 0));
originalConsoleLog.apply(null, arguments);
};
console.warn = function () {
htmlConsoleWarn([].slice.call(arguments, 0));
originalConsoleWarn.apply(null, arguments);
};
console.error = function () {
htmlConsoleError([].slice.call(arguments, 0));
originalConsoleError.apply(null, arguments);
};
var content = document.querySelector("#content");
var videoChoiceLocalFileInput = document.querySelector("#video-choice-local-file");
var videoInputLocalFile = document.querySelector("#video-input-local-file");
var videoChoiceUrlInput = document.querySelector("#video-choice-url");
var videoInputUrl = document.querySelector("#video-input-url");
var videoChoiceSampleInput = document.querySelector("#video-choice-sample");
var videoChoiceDummyInput = document.querySelector("#video-choice-dummy");
var videoInputDummyResolution = document.querySelector("#video-input-dummy-resolution");
var videoInputDummyWidth = document.querySelector("#video-input-dummy-width");
var videoInputDummyHeight = document.querySelector("#video-input-dummy-height");
var videoInputDummyColor = document.querySelector("#video-input-dummy-color");
var videoInputDummyDuration = document.querySelector("#video-input-dummy-duration");
var assChoiceLocalFileInput = document.querySelector("#ass-choice-local-file");
var assInputLocalFile = document.querySelector("#ass-input-local-file");
var assChoiceUrlInput = document.querySelector("#ass-choice-url");
var assInputUrl = document.querySelector("#ass-input-url");
var assChoiceTextInput = document.querySelector("#ass-choice-text");
var assInputText = document.querySelector("#ass-input-text");
var enableSvgChoiceInputYes = document.querySelector("#enable-svg-yes");
var enableSvgChoiceInputNo = document.querySelector("#enable-svg-no");
// Local file input requires URL.createObjectURL, so disable those inputs if the function doesn't exist.
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
[
videoChoiceLocalFileInput,
assChoiceLocalFileInput
].forEach(function (input) {
input.disabled = true;
input.parentElement.appendChild(document.createTextNode(" (This browser doesn't support URL.createObjectURL)"));
});
[
videoInputLocalFile,
assInputLocalFile
].forEach(function (input) {
input.disabled = true;
});
}
if (
typeof HTMLCanvasElement.prototype.captureStream !== "function" ||
typeof MediaRecorder === "undefined" ||
typeof MediaSource === "undefined" ||
typeof MediaSource.isTypeSupported !== "function"/* ||
!MediaSource.isTypeSupported("video/webm")*/
) {
[
videoChoiceDummyInput,
videoInputDummyResolution,
videoInputDummyWidth,
videoInputDummyHeight,
videoInputDummyColor,
videoInputDummyDuration
].forEach(function (input) {
input.disabled = true;
});
videoChoiceDummyInput.parentElement.appendChild(document.createTextNode(
" (This browser doesn't support generating dummy video. Consider using Firefox 46 or newer and enabling media.mediasource.webm.enabled in about:config)"
));
}
// Update dummy video width and height inputs when the dropdown selection changes
function updateDummyWidthAndHeight() {
var resolution = videoInputDummyResolution.value.split("x");
videoInputDummyWidth.value = resolution[0];
videoInputDummyHeight.value = resolution[1];
}
videoInputDummyResolution.addEventListener("change", updateDummyWidthAndHeight, false);
updateDummyWidthAndHeight();
// Register event handlers to enable the Go button if all inputs are valid.
[
videoChoiceLocalFileInput,
videoChoiceUrlInput,
videoChoiceSampleInput,
videoChoiceDummyInput,
assChoiceLocalFileInput,
assChoiceUrlInput,
assChoiceTextInput
].forEach(function (input) {
input.addEventListener("change", updateGoButton, false);
});
[
videoInputLocalFile,
assInputLocalFile,
].forEach(function (input) {
input.addEventListener("change", updateGoButton, false);
});
[
videoInputUrl,
videoInputDummyDuration,
assInputUrl,
assInputText
].forEach(function (input) {
input.addEventListener("input", updateGoButton, false);
});
// libjass.debugMode and libjass.verboseMode are two properties that can be set to true to have libjass print some debug information.
var debugModeCheckbox = document.querySelector("#debug-mode");
debugModeCheckbox.addEventListener("change", function () {
console.log((debugModeCheckbox.checked ? "Enabling" : "Disabling") + " debug mode.");
libjass.configure({ debugMode: debugModeCheckbox.checked });
}, false);
var verboseModeCheckbox = document.querySelector("#verbose-mode");
verboseModeCheckbox.addEventListener("change", function () {
console.log((debugModeCheckbox.checked ? "Enabling" : "Disabling") + " verbose mode.");
libjass.configure({ verboseMode: verboseModeCheckbox.checked });
}, false);
var goButton = document.querySelector("#go-button");
function updateGoButton() {
var videoOk = false;
var assOk = false;
var videoChoice = document.querySelector('input[name="video-choice"]:checked');
switch (videoChoice) {
case videoChoiceLocalFileInput:
videoOk = videoInputLocalFile.files.length === 1;
break;
case videoChoiceUrlInput:
videoOk = document.querySelector("#video-input-url:invalid") === null && videoInputUrl.value.length > 0;
break;
case videoChoiceSampleInput:
videoOk = true;
case videoChoiceDummyInput:
videoOk =
parseInt(videoInputDummyWidth.value) > 0 &&
parseInt(videoInputDummyHeight.value) > 0 &&
videoInputDummyDuration.value.length > 0 &&
parseInt(videoInputDummyDuration.value) > 0;
break;
}
var assChoice = document.querySelector('input[name="ass-choice"]:checked');
switch (assChoice) {
case assChoiceLocalFileInput:
assOk = assInputLocalFile.files.length === 1;
break;
case assChoiceUrlInput:
assOk = document.querySelector("#ass-input-url:invalid") === null && assInputUrl.value.length > 0;
break;
case assChoiceTextInput:
assOk = document.querySelector("#ass-input-choice:invalid") === null && assInputText.value.length > 0;
break;
}
goButton.disabled = !videoOk || !assOk;
}
goButton.addEventListener("click", function () {
updateGoButton();
if (this.disabled) {
return;
}
var videoChoice = document.querySelector('input[name="video-choice"]:checked');
var assChoice = document.querySelector('input[name="ass-choice"]:checked');
var enableSvgChoice = document.querySelector('input[name="enable-svg"]:checked');
while (content.firstChild) {
content.removeChild(content.firstChild);
}
var template = document.querySelector("#template").cloneNode(true);
[].slice.call(template.querySelectorAll("[data-id]")).forEach(function (element) {
element.id = element.dataset.id;
});
[].slice.call(template.children).forEach(function (element) {
content.appendChild(element);
});
var videoPromise = null;
switch (videoChoice) {
case videoChoiceLocalFileInput:
// Video is a local file. Convert it into a blob URL.
videoPromise = prepareVideo(0 /* URL */, URL.createObjectURL(videoInputLocalFile.files[0]));
break;
case videoChoiceUrlInput:
videoPromise = prepareVideo(0 /* URL */, videoInputUrl.value);
break;
case videoChoiceSampleInput:
videoPromise = prepareVideo(1 /* sample */);
break;
case videoChoiceDummyInput:
var width = parseInt(videoInputDummyWidth.value);
var height = parseInt(videoInputDummyHeight.value);
var color = videoInputDummyColor.value;
var duration = parseInt(videoInputDummyDuration.value) * 60;
videoPromise = prepareVideo(2 /* dummy */, width, height, color, duration);
break;
}
var assPromise = null;
switch (assChoice) {
case assChoiceLocalFileInput:
assPromise = libjass.ASS.fromUrl(URL.createObjectURL(assInputLocalFile.files[0]));
break;
case assChoiceUrlInput:
assPromise = libjass.ASS.fromUrl(assInputUrl.value);
break;
case assChoiceTextInput:
assPromise = libjass.ASS.fromString(assInputText.value);
break;
}
var enableSvg = null;
switch (enableSvgChoice) {
case enableSvgChoiceInputYes:
enableSvg = true;
break;
case enableSvgChoiceInputNo:
enableSvg = false;
break;
}
go(videoPromise, assPromise, enableSvg);
});
updateGoButton();
}, false);
function prepareVideo(videoType /*, ...parameters */) {
var video = document.querySelector("#video");
var videoMetadataLoadedPromise = null;
if (videoType === 0 /* URL */ || videoType === 1 /* sample */) {
if (videoType === 0 /* URL */) {
var videoUrl = arguments[1];
/* Set the <video> element's src to the given URL
*/
video.src = videoUrl;
}
else {
/* Add <source> elements for the two sample videos.
*/
var webmSource = document.createElement("source");
video.appendChild(webmSource);
webmSource.type = "video/webm";
webmSource.src = "sample.webm";
var mp4Source = document.createElement("source");
video.appendChild(mp4Source);
mp4Source.type = "video/mp4";
mp4Source.src = "sample.mp4";
}
/* (Advanced)
*
* This demo lets you resize the video to its original resolution or the subs resolution. If you have control over the video, you
* might already know the resolution of the video, and any alternative resolutions you want to provide, so you don't need to do this.
*
* We'll get the original video resolution from the video metadata. We can see if the video has metadata already by comparing
* video.readyState to HTMLMediaElement.HAVE_METADATA. If not already loaded, we can wait for the loadedmetadata event.
*
* This code creates a promise that will be resolved when the video metadata is available.
*/
videoMetadataLoadedPromise = new libjass.Promise(function (resolve, reject) {
if (video.readyState < HTMLMediaElement.HAVE_METADATA) {
// Video metadata isn't available yet. Register an event handler for it.
video.addEventListener("loadedmetadata", resolve, false);
video.addEventListener("error", function (event) { reject(video.error); }, false);
}
else {
// Video metadata is already available.
resolve();
}
});
}
else if (videoType === 2 /* dummy */) {
var width = arguments[1];
var height = arguments[2];
var color = arguments[3];
var duration = arguments[4];
videoMetadataLoadedPromise = makeDummyVideo(video, width, height, color, duration);
}
return videoMetadataLoadedPromise.then(function () {
console.log("Video metadata loaded.");
// 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));
}).catch(function (reason) {
var errorCode = (reason.code !== undefined) ? [null, "MEDIA_ERR_ABORTED", "MEDIA_ERR_NETWORK", "MEDIA_ERR_DECODE", "MEDIA_ERR_SRC_NOT_SUPPORTED"][reason.code] : "";
console.error("Video could not be loaded: %o %o", errorCode, reason);
throw reason;
});
}
/* This is a function that sets up libjass to render the subs.
*/
function go(videoPromise, assPromise, enableSvg) {
var video = document.querySelector("#video");
/* Now we need to fetch the ASS script at the given URL and convert it into a libjass.ASS object. We use the libjass.ASS.fromUrl()
* function for this. It fetches the ASS script asynchronously and returns a promise that will be resolved when the script is fully
* parsed.
*/
var assLoadedPromise = assPromise.then(function (ass) {
console.log("Script received.");
// 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));
return ass;
}).catch(function (reason) {
console.error("ASS could not be loaded: %o", reason);
throw reason;
});
/* Next, we wait for both the video and the libjass.ASS object to be available, i.e., for their respective promises to be
* resolved. Once they have, we can create the libjass.renderers.DefaultRenderer object. This is the object that will display subs
* on the <video> element.
*/
libjass.Promise.all([videoPromise, assLoadedPromise]).then(function (results) {
var ass = results[1];
var rendererSettings = { };
if (enableSvg !== null) {
rendererSettings.enableSvg = enableSvg;
}
// else unset, which means libjass will try to auto-detect it.
// Create a DefaultRenderer using the video element and the ASS object
var renderer = new libjass.renderers.DefaultRenderer(video, ass, rendererSettings);
// Export the renderer for debugging
window.renderer = renderer;
/* (Advanced)
*
* The renderer sets some internal things up, and then fires a "ready" event when it's done. If you want to have autoplaying video,
* but want to wait for the renderer to set up first, you should only play the video when the "ready" event fires.
*/
renderer.addEventListener("ready", function () {
console.log("Beginning autoplay.");
video.play();
});
/* (Advanced)
*
* This demo page also has a checkbox that can be used to turn the subs off. We'll use the checkbox's value with renderer.setEnabled()
* to do this.
*/
document.querySelector("#enable-disable-subs").addEventListener("change", function (event) {
renderer.setEnabled(event.target.checked);
}, false);
/* (Advanced)
*
* As mentioned above, this demo page allows the user to resize the video with two presets - the original video resolution and the subs
* resolution. DefaultRenderer needs to be told of changes to the size of the <video> element because it needs the size information to
* calculate the positions and sizes of the subs.
*
* This next function is called whenever the user chooses a different preset size for the video.
*/
var applyVideoSizeSelection = function () {
// Find which option is selected
var id = videoSizeSelector.querySelector("input[name='video-size']:checked").id;
if (id === "video-size-video-radio") {
// Resize to video resolution
video.style.width = video.videoWidth + "px";
video.style.height = video.videoHeight + "px";
renderer.resize();
}
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();
}
};
var videoSizeSelector = document.querySelector("#video-size-selector");
videoSizeSelector.addEventListener("change", function (event) { applyVideoSizeSelection(); }, false);
// Set the resolution to whatever's selected by default
applyVideoSizeSelection();
});
}
var htmlConsoleLog = function (htmlConsole) {
return function (items) {
var message = document.createElement("div");
htmlConsole.appendChild(message);
message.className = "log";
var text = new Date().toString() + ": ";
items.forEach(function (item) {
switch (typeof item) {
case "boolean":
case "number":
case "string":
text += item + " ";
break;
default:
text += item + "[Check browser console for more details.] ";
break;
}
});
message.appendChild(document.createTextNode(text));
};
};
var htmlConsoleWarn = function (htmlConsole) {
return function (items) {
var message = document.createElement("div");
htmlConsole.appendChild(message);
message.className = "warning";
var text = new Date().toString() + ": ";
items.forEach(function (item) {
switch (typeof item) {
case "boolean":
case "number":
case "string":
text += item + " ";
break;
default:
text += item + "[Check browser console for more details.] ";
break;
}
});
message.appendChild(document.createTextNode(text));
};
};
var htmlConsoleError = function (htmlConsole) {
return function (items) {
var message = document.createElement("div");
htmlConsole.appendChild(message);
message.className = "error";
var text = new Date().toString() + ": ";
items.forEach(function (item) {
switch (typeof item) {
case "boolean":
case "number":
case "string":
text += item + " ";
break;
default:
text += item + "[Check browser console for more details.] ";
break;
}
});
message.appendChild(document.createTextNode(text));
};
};
addEventListener("error", function (event) {
console.error(event.message, event.error);
});
-227
View File
@@ -1,227 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
libjass
https://github.com/Arnavion/libjass
Copyright 2013 Arnav Singh
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>libjass demo</title>
<link rel="stylesheet" href="libjass.css" />
<link rel="stylesheet" href="index.css" />
<script src="libjass.js" />
<script src="index.js" />
<script src="dummy-video.js" />
</head>
<body>
<p>This is a demo page for the libjass library - a library for displaying ASS subtitles in the browser. See the source of index.js for an explanation of how to use the library.</p>
<!-- Default look of the page - input controls to let the user choose a video and an ASS script. -->
<div id="content">
<fieldset>
<legend>Choose a video</legend>
<ul class="choices-list">
<li>
<label>
<input type="radio" id="video-choice-local-file" name="video-choice" />
Local file (The file won't be uploaded. It will be used directly within your browser.)
</label>
<input type="file" id="video-input-local-file" accept="video/*" />
</li>
<li>
<label>
<input type="radio" id="video-choice-url" name="video-choice" />
Direct video URL (webm / MP4)
</label>
<input type="url" id="video-input-url" />
</li>
<li>
<label>
<input type="radio" id="video-choice-sample" name="video-choice" checked="checked" />
Sample video (75s long 1280x720, meant to be used with the default "Text" ASS option below)
</label>
</li>
<li>
<label>
<input type="radio" id="video-choice-dummy" name="video-choice" />
Dummy video
</label>
<select id="video-input-dummy-resolution">
<option value="640x480">640 x 480 (SD fullscreen)</option>
<option value="704x480">704 x 480 (SD anamorphic)</option>
<option value="640x360">640 x 360 (SD widescreen)</option>
<option value="704x396">704 x 396 (SD widescreen)</option>
<option value="640x352">640 x 352 (SD widescreen MOD16)</option>
<option value="704x400">704 x 400 (SD widescreen MOD16)</option>
<option value="1280x720" selected="selected">1280 x 720 (HD 720p)</option>
<option value="1920x1080">1920 x 1080 (HD 1080p)</option>
<option value="1024x576">1024 x 576 (SuperPAL widescreen)</option>
</select>
<input type="number" id="video-input-dummy-width" />
<input type="number" id="video-input-dummy-height" />
<input type="color" id="video-input-dummy-color" value="#2fa3fe"></input>
<label><input type="number" id="video-input-dummy-duration" value="25"></input> mins</label>
</li>
</ul>
</fieldset>
<fieldset>
<legend>Choose an ASS script</legend>
<ul class="choices-list">
<li>
<label>
<input type="radio" id="ass-choice-local-file" name="ass-choice" />
Local file (The file won't be uploaded. It will be used directly within your browser.)
</label>
<input type="file" id="ass-input-local-file" accept=".ass" />
</li>
<li>
<label>
<input type="radio" id="ass-choice-url" name="ass-choice" />
Direct script URL (must be accessible via CORS)
</label>
<input type="url" id="ass-input-url" />
</li>
<li>
<label>
<input type="radio" id="ass-choice-text" name="ass-choice" checked="checked" />
Text
</label>
<textarea id="ass-input-text"><![CDATA[[Script Info]
; Script generated by Aegisub 2.1.8
; http://www.aegisub.org/
Title: Default Aegisub file
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 1280
PlayResY: 720
ScaledBorderAndShadow: yes
Collisions: Normal
Video Aspect Ratio: 0
Video Zoom: 6
Video Position: 0
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,50,&H00F1F4F9,&H000000FF,&H000F1115,&H96000000,0,0,0,0,100,100,0,0,1,1.5,0,2,80,80,35,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:01:15.00,Default,,0000,0000,0000,,{\an8}This is a sample ASS file\Nthat showcases some of libjass's capabilities.
Dialogue: 0,0:00:05.00,0:00:10.00,Default,,0000,0000,0000,,{\c&H0000FF&}Red text with \c
Dialogue: 0,0:00:10.00,0:00:15.00,Default,,0000,0000,0000,,{\c&H0000FF&\3c&H00FF00&}Red text with green outline with \c and \3c
Dialogue: 0,0:00:15.00,0:00:20.00,Default,,0000,0000,0000,,{\alpha&H80&}Translucent text with \alpha
Dialogue: 0,0:00:20.00,0:00:25.00,Default,,0000,0000,0000,,{\fad(2000,2000)}Fade-in and out text with \fad
Dialogue: 0,0:00:25.00,0:00:30.00,Default,,0000,0000,0000,,{\move(0,0,1280,720)}Moving text with \mov
Dialogue: 0,0:00:30.00,0:00:35.00,Default,,0000,0000,0000,,{\pos(640,360)}Positioned text with \pos
Dialogue: 0,0:00:35.00,0:00:40.00,Default,,0000,0000,0000,,{\i1}Italic,{\i} {\u1}underlined{\u} and {\s1}strikeout{\s} text with \i, \u and \s
Dialogue: 0,0:00:40.00,0:00:45.00,Default,,0000,0000,0000,,{\bord10}Borders with \bord
Dialogue: 0,0:00:45.00,0:00:50.00,Default,,0000,0000,0000,,{\shad10}Shadows with \shad
Dialogue: 0,0:00:50.00,0:00:55.00,Default,,0000,0000,0000,,{\fs20}Tiny text{\fs} and {\fs100}large text{\fs} with \fs
Dialogue: 0,0:00:55.00,0:01:00.00,Default,,0000,0000,0000,,{\fscx200}Wide text{\fscx} and {\fscy200}tall text{\fscy} with \fscx and \fscy
Dialogue: 0,0:01:00.00,0:01:05.00,Default,,0000,0000,0000,,{\pos(640,360)\frz60}Rotated text with \frz
Dialogue: 0,0:01:05.00,0:01:10.00,Default,,0000,0000,0000,,{\pos(640,360)\t(\frz720)}You spin me right round baby right round
Dialogue: 0,0:01:05.00,0:01:10.00,Default,,0000,0000,0000,,Spinning text with \t and \frz
Dialogue: 0,0:01:10.00,0:01:15.00,Default,,0000,0000,0000,,The End
]]></textarea>
</li>
</ul>
</fieldset>
<fieldset>
<legend>Other options</legend>
<ul>
<li>
Does the M at the end of this question appear red or black?
<span style="color: black; -webkit-filter: url('#redtext'); filter: url('#redtext');">M</span>
<label><input type="radio" id="enable-svg-yes" name="enable-svg" /> Red</label>
<label><input type="radio" id="enable-svg-no" name="enable-svg" /> Black</label>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
<defs>
<filter id="redtext" x="-50%" y="-50%" width="200%" height="200%">
<feComponentTransfer in="SourceAlpha">
<feFuncR type="linear" slope="0" intercept="1" />
<feFuncG type="linear" slope="0" intercept="0" />
<feFuncB type="linear" slope="0" intercept="0" />
<feFuncA type="linear" slope="1" intercept="0" />
</feComponentTransfer>
</filter>
</defs>
</svg>
</li>
</ul>
</fieldset>
<button type="button" id="go-button" disabled="disabled">Go</button>
</div>
<fieldset id="console">
<legend>
Console output
<label>
<input type="checkbox" id="debug-mode" />
Enable debug mode
</label>
<label>
<input type="checkbox" id="verbose-mode" />
Enable verbose mode
</label>
</legend>
</fieldset>
<div>
<p>Found a bug? Please check if there's already a similar issue already reported at <a href="https://github.com/Arnavion/libjass/issues">https://github.com/Arnavion/libjass/issues</a> If there isn't, please open a new issue. You can also report it in the #libjass channel on the Rizon IRC network.</p>
<p>Please include the following information in your bug report:
<ul>
<li>Your OS and browser versions. Eg: "Chrome 49 on Windows 7"</li>
<li>A description of the bug. What did you expect to see? What happened instead? Eg: "All the subtitles are visible except the one at 00:00:05 'Was it you who broke the clock?'" or "The subtitle at 00:00:05 should be red but instead it's blue."</li>
<li>If possible, a URL to the video and script that I can access for testing.</li>
<li>Any text from the "Console output" section above.</li>
</ul>
</p>
</div>
<script type="text/template" id="template">
<!-- The video element that will be generated -->
<video data-id="video" controls="" />
<!-- These controls will be placed next to the video to allow changing its size, and for turning the subs off and on. -->
<form data-id="settings-form">
<fieldset>
<legend>Video size</legend>
<div data-id="video-size-selector">
<label>
<input type="radio" name="video-size" data-id="video-size-video-radio" checked="checked" />
Video resolution <span data-id="video-resolution-label-width" />x<span data-id="video-resolution-label-height" />
</label>
<label>
<input type="radio" name="video-size" data-id="video-size-script-radio" />
Script resolution <span data-id="script-resolution-label-width" />x<span data-id="script-resolution-label-height" />
</label>
</div>
</fieldset>
<fieldset>
<legend>Subtitles</legend>
<label>
<input type="checkbox" data-id="enable-disable-subs" checked="checked" />
Subtitles
</label>
</fieldset>
</form>
</script>
</body>
</html>
-9980
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>libjass</title>
</head>
<body>
<h1>libjass - Renders ASS subs in the browser</h1>
<ul>
<li><a href="https://github.com/Arnavion/libjass">GitHub</a></li>
<li>IRC channel - #libjass on irc.rizon.net</li>
<li><a href="https://arnavion.github.io/libjass/api.xhtml">API documentation</a></li>
<li><a href="https://arnavion.github.io/libjass/demo/">Demo</a></li>
</ul>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
{
"name": "libjass",
"version": "0.12.0",
"description": "A library to render ASS subtitles on HTML5 video in the browser.",
"keywords": ["browser", "html5", "subtitles"],
"homepage": "https://github.com/Arnavion/libjass",
"bugs": "https://github.com/Arnavion/libjass/issues",
"license": "Apache-2.0",
"contributors": [{
"name": "Arnav Singh",
"email": "arnavion@gmail.com"
}],
"repository": {
"type": "git",
"url": "https://github.com/Arnavion/libjass"
},
"main": "lib/libjass.js",
"scripts": {
"prepublish": "node ./build.js clean default",
"build": "tsc -p ./build/tsconfig.json",
"test": "node ./build.js test",
"test-lib": "intern-client config=tests/intern reporters=Pretty",
"test-minified": "intern-client config=tests/intern reporters=Pretty minified=true",
"test-browser": "intern-runner config=tests/intern",
"test-doc": "intern-client config=tests/intern-doc reporters=Pretty"
},
"devDependencies": {
"async": "1.x >=1.4",
"async-build": "0.3.1",
"intern": "3.x >=3.2.0",
"npm": "3.x",
"pngjs": "3.x",
"sax": "1.x",
"typescript": "next",
"uglify-js": "2.x >=2.4.24"
},
"private": true
}
+139
View File
@@ -0,0 +1,139 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as settings from "./settings";
export { debugMode, verboseMode } from "./settings";
import * as set from "./utility/set";
export { Set } from "./utility/set";
import * as map from "./utility/map";
export { Map } from "./utility/map";
import * as promise from "./utility/promise";
export { Promise, DeferredPromise } from "./utility/promise";
import * as webworker from "./webworker";
export { webworker };
import * as parts from "./parts";
export { parts };
import * as parser from "./parser";
export { parser };
import * as renderers from "./renderers";
export { renderers };
export { serialize, deserialize } from "./serialization";
export { ASS } from "./types/ass";
export { Attachment, AttachmentType } from "./types/attachment";
export { Dialogue } from "./types/dialogue";
export { ScriptProperties } from "./types/script-properties";
export { Style } from "./types/style";
export { BorderStyle, Format, WrappingStyle } from "./types/misc";
export { version } from "./version";
/**
* Configures libjass with the given properties.
*
* @param {!*} newConfig
* @param {?boolean} newConfig["debugMode"] When true, libjass logs some debug messages.
* @param {?boolean} newConfig["verboseMode"] When true, libjass logs some more debug messages. This setting is independent of {@link libjass.debugMode}
* @param {?function(new:Set, !Array.<T>=)} newConfig["Set"] Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
* @param {?function(new:Map, !Array.<!Array.<*>>=)} newConfig["Map"] Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
* @param {?function(new:Promise)} newConfig["Promise"] Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
*/
export function configure(newConfig: {
debugMode?: boolean,
verboseMode?: boolean,
Set?: typeof set.Set | null,
Map?: typeof map.Map | null,
Promise?: typeof promise.Promise | null,
}): void {
if (typeof newConfig.debugMode === "boolean") {
settings.setDebugMode(newConfig.debugMode);
}
if (typeof newConfig.verboseMode === "boolean") {
settings.setVerboseMode(newConfig.verboseMode);
}
if (typeof newConfig.Set === "function" || newConfig.Set === null) {
set.setImplementation(newConfig.Set);
}
if (typeof newConfig.Map === "function" || newConfig.Map === null) {
map.setImplementation(newConfig.Map);
}
if (typeof newConfig.Promise === "function" || newConfig.Promise === null) {
promise.setImplementation(newConfig.Promise);
}
}
declare const exports: any;
// Getters below are to work around https://github.com/Microsoft/TypeScript/issues/6366
Object.defineProperties(exports, {
debugMode: {
get: () => settings.debugMode,
set: value => {
console.warn("Setter `libjass.debugMode = value` has been deprecated. Use `libjass.configure({ debugMode: value })` instead.");
settings.setDebugMode(value);
},
},
verboseMode: {
get: () => settings.verboseMode,
set: value => {
console.warn("Setter `libjass.verboseMode = value` has been deprecated. Use `libjass.configure({ verboseMode: value })` instead.");
settings.setVerboseMode(value);
},
},
Set: {
get: () => set.Set,
set: value => {
console.warn("Setter `libjass.Set = value` has been deprecated. Use `libjass.configure({ Set: value })` instead.");
set.setImplementation(value);
},
},
Map: {
get: () => map.Map,
set: value => {
console.warn("Setter `libjass.Map = value` has been deprecated. Use `libjass.configure({ Map: value })` instead.");
map.setImplementation(value);
},
},
Promise: {
get: () => promise.Promise,
set: value => {
console.warn("Setter `libjass.Promise = value` has been deprecated. Use `libjass.configure({ Promise: value })` instead.");
promise.setImplementation(value);
},
},
});
+23
View File
@@ -0,0 +1,23 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { parse } from "./parse";
export { BrowserReadableStream, Stream, StringStream, XhrStream } from "./streams";
export { StreamParser, SrtStreamParser } from "./stream-parsers";
+68
View File
@@ -0,0 +1,68 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Property, TypedTemplate } from "../types/misc";
import { Map } from "../utility/map";
/**
* Parses a line into a {@link ./types/misc.Property}.
*
* @param {string} line
* @return {Property}
*/
export function parseLineIntoProperty(line: string): Property | null {
const colonPos = line.indexOf(":");
if (colonPos === -1) {
return null;
}
const name = line.substr(0, colonPos);
const value = line.substr(colonPos + 1).replace(/^\s+/, "");
return { name, value };
}
/**
* Parses a line into a {@link ./types/misc.TypedTemplate} according to the given format specifier.
*
* @param {string} line
* @param {!Array.<string>} formatSpecifier
* @return {TypedTemplate}
*/
export function parseLineIntoTypedTemplate(line: string, formatSpecifier: string[]): TypedTemplate | null {
const property = parseLineIntoProperty(line);
if (property === null) {
return null;
}
const value = property.value.split(",");
if (value.length > formatSpecifier.length) {
value[formatSpecifier.length - 1] = value.slice(formatSpecifier.length - 1).join(",");
}
const template = new Map<string, string>();
formatSpecifier.forEach((formatKey, index) => {
template.set(formatKey, value[index]);
});
return { type: property.name, template };
}
+2079
View File
File diff suppressed because it is too large Load Diff
+433
View File
@@ -0,0 +1,433 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { debugMode } from "../settings";
import { ASS } from "../types/ass";
import { Style } from "../types/style";
import { Dialogue } from "../types/dialogue";
import { Attachment, AttachmentType } from "../types/attachment";
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { parseLineIntoProperty } from "./misc";
import { Stream } from "./streams";
enum Section {
ScriptInfo,
Styles,
Events,
Fonts,
Graphics,
Other,
EOF,
}
/**
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The {@link libjass.parser.Stream} to parse
*/
export class StreamParser {
private _ass: ASS = new ASS();
private _minimalDeferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _shouldSwallowBom: boolean = true;
private _currentSection: Section = Section.ScriptInfo;
private _currentAttachment: Attachment | null = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
this._minimalDeferred.reject(reason);
this._deferred.reject(reason);
});
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the script properties of the ASS script have been parsed from the stream. Styles and events have not necessarily been
* parsed at the point this promise becomes resolved.
*/
get minimalASS(): Promise<ASS> {
return this._minimalDeferred.promise;
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the entire stream has been parsed.
*/
get ass(): Promise<ASS> {
return this._deferred.promise;
}
/**
* @type {number}
*/
private get currentSection(): Section {
return this._currentSection;
}
/**
* @type {number}
*/
private set currentSection(value: Section) {
if (this._currentAttachment !== null) {
this._ass.addAttachment(this._currentAttachment);
this._currentAttachment = null;
}
if (this._currentSection === Section.ScriptInfo && value !== Section.ScriptInfo) {
// Exiting script info section
this._minimalDeferred.resolve(this._ass);
}
if (value === Section.EOF) {
const scriptProperties = this._ass.properties;
if (scriptProperties.resolutionX === undefined || scriptProperties.resolutionY === undefined) {
// Malformed script.
this._minimalDeferred.reject("Malformed ASS script.");
this._deferred.reject("Malformed ASS script.");
}
else {
this._minimalDeferred.resolve(this._ass);
this._deferred.resolve(this._ass);
}
}
this._currentSection = value;
}
/**
* @param {string} line
*/
private _onNextLine(line: string | null): void {
if (line === null) {
this.currentSection = Section.EOF;
return;
}
if (line[line.length - 1] === "\r") {
line = line.substr(0, line.length - 1);
}
if (line.charCodeAt(0) === 0xfeff && this._shouldSwallowBom) {
line = line.substr(1);
}
this._shouldSwallowBom = false;
if (line === "") {
// Ignore empty lines.
}
else if (line[0] === ";" && this._currentAttachment === null) {
// Lines starting with ; are comments, unless reading an attachment.
}
else if (line === "[Script Info]") {
this.currentSection = Section.ScriptInfo;
}
else if (line === "[V4+ Styles]" || line === "[V4 Styles]") {
this.currentSection = Section.Styles;
}
else if (line === "[Events]") {
this.currentSection = Section.Events;
}
else if (line === "[Fonts]") {
this.currentSection = Section.Fonts;
}
else if (line === "[Graphics]") {
this.currentSection = Section.Graphics;
}
else {
if (this._currentAttachment === null && line[0] === "[" && line[line.length - 1] === "]") {
/* This looks like the start of a new section. The section name is unrecognized if it is.
* Since there's no current attachment being parsed it's definitely the start of a new section.
* If an attachment is being parsed, this might be part of the attachment.
*/
this.currentSection = Section.Other;
}
switch (this.currentSection) {
case Section.ScriptInfo:
const property = parseLineIntoProperty(line);
if (property !== null) {
switch (property.name) {
case "PlayResX":
this._ass.properties.resolutionX = parseInt(property.value);
break;
case "PlayResY":
this._ass.properties.resolutionY = parseInt(property.value);
break;
case "WrapStyle":
this._ass.properties.wrappingStyle = parseInt(property.value);
break;
case "ScaledBorderAndShadow":
this._ass.properties.scaleBorderAndShadow = (property.value === "yes");
break;
}
}
break;
case Section.Styles:
if (this._ass.stylesFormatSpecifier === null) {
const property = parseLineIntoProperty(line);
if (property !== null && property.name === "Format") {
this._ass.stylesFormatSpecifier = property.value.split(",").map(str => str.trim());
}
else {
// Ignore any non-format lines
}
}
else {
try {
this._ass.addStyle(line);
}
catch (ex) {
if (debugMode) {
console.error(`Could not parse style from line ${ line } - ${ ex.stack || ex }`);
}
}
}
break;
case Section.Events:
if (this._ass.dialoguesFormatSpecifier === null) {
const property = parseLineIntoProperty(line);
if (property !== null && property.name === "Format") {
this._ass.dialoguesFormatSpecifier = property.value.split(",").map(str => str.trim());
}
else {
// Ignore any non-format lines
}
}
else {
try {
this._ass.addEvent(line);
}
catch (ex) {
if (debugMode) {
console.error(`Could not parse event from line ${ line } - ${ ex.stack || ex }`);
}
}
}
break;
case Section.Fonts:
case Section.Graphics:
const startOfNewAttachmentRegex = (this.currentSection === Section.Fonts) ? /^fontname:(.+)/ : /^filename:(.+)/;
const startOfNewAttachment = startOfNewAttachmentRegex.exec(line);
if (startOfNewAttachment !== null) {
// Start of new attachment
if (this._currentAttachment !== null) {
this._ass.addAttachment(this._currentAttachment);
this._currentAttachment = null;
}
this._currentAttachment = new Attachment(startOfNewAttachment[1].trim(), (this.currentSection === Section.Fonts) ? AttachmentType.Font : AttachmentType.Graphic);
}
else if (this._currentAttachment !== null) {
try {
this._currentAttachment.contents += uuencodedToBase64(line);
}
catch (ex) {
if (debugMode) {
console.error(`Encountered error while reading font ${ this._currentAttachment.filename }: %o`, ex);
}
this._currentAttachment = null;
}
}
else {
// Ignore.
}
break;
case Section.Other:
// Ignore other sections.
break;
default:
throw new Error(`Unhandled state ${ this.currentSection }`);
}
}
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
this._minimalDeferred.reject(reason);
this._deferred.reject(reason);
});
}
}
/**
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream} of an SRT script.
*
* @param {!libjass.parser.Stream} stream The {@link libjass.parser.Stream} to parse
*/
export class SrtStreamParser {
private _ass: ASS = new ASS();
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _shouldSwallowBom: boolean = true;
private _currentDialogueNumber: string | null = null;
private _currentDialogueStart: string | null = null;
private _currentDialogueEnd: string | null = null;
private _currentDialogueText: string | null = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
this._deferred.reject(reason);
});
this._ass.properties.resolutionX = 1280;
this._ass.properties.resolutionY = 720;
this._ass.properties.wrappingStyle = 1;
this._ass.properties.scaleBorderAndShadow = true;
const newStyle = new Style(new Map([["Name", "Default"], ["FontSize", "36"]]));
this._ass.styles.set(newStyle.name, newStyle);
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the entire stream has been parsed.
*/
get ass(): Promise<ASS> {
return this._deferred.promise;
}
/**
* @param {string} line
*/
private _onNextLine(line: string | null): void {
if (line === null) {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map([
["Style", "Default"],
["Start", this._currentDialogueStart],
["End", this._currentDialogueEnd],
["Text", this._currentDialogueText],
]), this._ass));
}
this._deferred.resolve(this._ass);
return;
}
if (line[line.length - 1] === "\r") {
line = line.substr(0, line.length - 1);
}
if (line.charCodeAt(0) === 0xfeff && this._shouldSwallowBom) {
line = line.substr(1);
}
this._shouldSwallowBom = false;
if (line === "") {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map([
["Style", "Default"],
["Start", this._currentDialogueStart],
["End", this._currentDialogueEnd],
["Text", this._currentDialogueText],
]), this._ass));
}
this._currentDialogueNumber = this._currentDialogueStart = this._currentDialogueEnd = this._currentDialogueText = null;
}
else {
if (this._currentDialogueNumber === null) {
if (/^\d+$/.test(line)) {
this._currentDialogueNumber = line;
}
}
else if (this._currentDialogueStart === null && this._currentDialogueEnd === null) {
const match = /^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/.exec(line);
if (match !== null) {
this._currentDialogueStart = match[1].replace(",", ".");
this._currentDialogueEnd = match[2].replace(",", ".");
}
}
else {
line = line
.replace(/<b>/g, "{\\b1}").replace(/\{b\}/g, "{\\b1}")
.replace(/<\/b>/g, "{\\b0}").replace(/\{\/b\}/g, "{\\b0}")
.replace(/<i>/g, "{\\i1}").replace(/\{i\}/g, "{\\i1}")
.replace(/<\/i>/g, "{\\i0}").replace(/\{\/i\}/g, "{\\i0}")
.replace(/<u>/g, "{\\u1}").replace(/\{u\}/g, "{\\u1}")
.replace(/<\/u>/g, "{\\u0}").replace(/\{\/u\}/g, "{\\u0}")
.replace(
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => `{\c&H${ blue }${ green }${ red }&}`
).replace(/<\/font>/g, "{\\c}");
if (this._currentDialogueText !== null) {
this._currentDialogueText += "\\N" + line;
}
else {
this._currentDialogueText = line;
}
}
}
this._stream.nextLine().then(line => this._onNextLine(line), reason => {
this._deferred.reject(reason);
});
}
}
/**
* Converts a uuencoded string to a base64 string.
*
* @param {string} str
* @return {string}
*/
function uuencodedToBase64(str: string): string {
let result = "";
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i) - 33;
if (charCode < 0 || charCode > 63) {
throw new Error(`Out-of-range character code ${ charCode } at index ${ i } in string ${ str }`);
}
if (charCode < 26) {
result += String.fromCharCode("A".charCodeAt(0) + charCode);
}
else if (charCode < 52) {
result += String.fromCharCode("a".charCodeAt(0) + charCode - 26);
}
else if (charCode < 62) {
result += String.fromCharCode("0".charCodeAt(0) + charCode - 52);
}
else if (charCode === 62) {
result += "+";
}
else {
result += "/";
}
}
return result;
}
+281
View File
@@ -0,0 +1,281 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Promise, DeferredPromise } from "../utility/promise";
export interface ReadableStream {
/**
* @return {!ReadableStreamReader}
*/
getReader(): ReadableStreamReader;
}
export interface ReadableStreamReader {
/**
* @return {!Promise.<{ value?: Uint8Array, done: boolean }>}
*/
read(): Promise<{ value: Uint8Array; done: boolean; }>;
}
export interface TextDecoder {
/**
* @param {!ArrayBuffer|!ArrayBufferView} input
* @param {{ stream: boolean }} options
* @return {string}
*/
decode(input: ArrayBuffer | ArrayBufferView, options: { stream: boolean }): string;
}
export interface TextDecoderConstructor {
new (encoding: string, options: { ignoreBOM: boolean }): TextDecoder;
/**
* @type {!TextDecoder}
*/
prototype: TextDecoder;
}
declare const global: {
/**
* @type {!TextDecoderConstructor}
*/
TextDecoder?: TextDecoderConstructor;
};
/**
* An interface for a stream.
*/
export interface Stream {
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string | null>;
}
/**
* A {@link libjass.parser.Stream} that reads from a string in memory.
*
* @param {string} str The string
*/
export class StringStream implements Stream {
private _readTill: number = 0;
constructor(private _str: string) { }
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the string has been completely read.
*/
nextLine(): Promise<string | null> {
let result: Promise<string | null>;
if (this._readTill < this._str.length) {
const nextNewLinePos = this._str.indexOf("\n", this._readTill);
if (nextNewLinePos !== -1) {
result = Promise.resolve(this._str.substring(this._readTill, nextNewLinePos));
this._readTill = nextNewLinePos + 1;
}
else {
result = Promise.resolve(this._str.substr(this._readTill));
this._readTill = this._str.length;
}
}
else {
result = Promise.resolve<string | null>(null);
}
return result;
}
}
/**
* A {@link libjass.parser.Stream} that reads from an XMLHttpRequest object.
*
* @param {!XMLHttpRequest} xhr The XMLHttpRequest object. Make sure to not call .open() on this object before passing it in here,
* since event handlers cannot be registered after open() has been called.
*/
export class XhrStream implements Stream {
private _readTill: number = 0;
private _pendingDeferred: DeferredPromise<string | null> | null = null;
private _failedError: ErrorEvent | null = null;
constructor(private _xhr: XMLHttpRequest) {
_xhr.addEventListener("progress", () => this._onXhrProgress(), false);
_xhr.addEventListener("load", () => this._onXhrLoad(), false);
_xhr.addEventListener("error", event => this._onXhrError(event), false);
}
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string> {
if (this._pendingDeferred !== null) {
throw new Error("XhrStream only supports one pending unfulfilled read at a time.");
}
const deferred = this._pendingDeferred = new DeferredPromise<string>();
this._tryResolveNextLine();
return deferred.promise;
}
/**
*/
private _onXhrProgress(): void {
if (this._pendingDeferred === null) {
return;
}
if (this._xhr.readyState === XMLHttpRequest.DONE) {
/* Suppress resolving next line here. Let the "load" or "error" event handlers do it.
*
* This is required because a failed XHR fires the progress event with readyState === DONE before it fires the error event.
* This would confuse _tryResolveNextLine() into thinking the request succeeded with no data if it was called here.
*/
return;
}
this._tryResolveNextLine();
}
/**
*/
private _onXhrLoad(): void {
if (this._pendingDeferred === null) {
return;
}
this._tryResolveNextLine();
}
/**
* @param {!ErrorEvent} event
*/
private _onXhrError(event: ErrorEvent): void {
this._failedError = event;
if (this._pendingDeferred === null) {
return;
}
this._tryResolveNextLine();
}
/**
*/
private _tryResolveNextLine(): void {
if (this._failedError !== null) {
this._pendingDeferred!.reject(this._failedError);
return;
}
const response = this._xhr.responseText;
const nextNewLinePos = response.indexOf("\n", this._readTill);
if (nextNewLinePos !== -1) {
this._pendingDeferred!.resolve(response.substring(this._readTill, nextNewLinePos));
this._readTill = nextNewLinePos + 1;
this._pendingDeferred = null;
}
else if (this._xhr.readyState === XMLHttpRequest.DONE) {
if (this._failedError !== null) {
this._pendingDeferred!.reject(this._failedError);
}
// No more data. This is the last line.
else if (this._readTill < response.length) {
this._pendingDeferred!.resolve(response.substr(this._readTill));
this._readTill = response.length;
}
else {
this._pendingDeferred!.resolve(null);
}
this._pendingDeferred = null;
}
}
}
/**
* A {@link libjass.parser.Stream} that reads from a ReadableStream object.
*
* @param {!ReadableStream} stream
* @param {string} encoding
*/
export class BrowserReadableStream implements Stream {
private _reader: ReadableStreamReader;
private _decoder: TextDecoder;
private _buffer: string = "";
private _pendingDeferred: DeferredPromise<string | null> | null = null;
constructor(stream: ReadableStream, encoding: string) {
this._reader = stream.getReader();
this._decoder = new global.TextDecoder!(encoding, { ignoreBOM: true });
}
/**
* @return {!Promise.<?string>} A promise that will be resolved with the next line, or null if the stream is exhausted.
*/
nextLine(): Promise<string> {
if (this._pendingDeferred !== null) {
throw new Error("BrowserReadableStream only supports one pending unfulfilled read at a time.");
}
const deferred = this._pendingDeferred = new DeferredPromise<string>();
this._tryResolveNextLine();
return deferred.promise;
}
/**
*/
private _tryResolveNextLine(): void {
const nextNewLinePos = this._buffer.indexOf("\n");
if (nextNewLinePos !== -1) {
this._pendingDeferred!.resolve(this._buffer.substr(0, nextNewLinePos));
this._buffer = this._buffer.substr(nextNewLinePos + 1);
this._pendingDeferred = null;
}
else {
this._reader.read().then(next => {
const { value, done } = next;
if (!done) {
this._buffer += this._decoder.decode(value, { stream: true });
this._tryResolveNextLine();
}
else {
// No more data.
if (this._buffer.length === 0) {
this._pendingDeferred!.resolve(null);
}
else {
this._pendingDeferred!.resolve(this._buffer);
this._buffer = "";
}
this._pendingDeferred = null;
}
});
}
}
}
+224
View File
@@ -0,0 +1,224 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Attachment } from "../types/attachment";
import { Map } from "../utility/map";
import { Set } from "../utility/set";
type DataReader = { dataView: DataView; position: number; };
enum DataType {
Char,
Uint16,
Uint32,
}
type StructMemberDefinition = { type: DataType; field: string; };
const fieldDecorators = new Map<DataType, (proto: any, field: string) => void>();
@struct
class OffsetTable {
/** @type {number} */ @field(DataType.Uint16) majorVersion: number;
/** @type {number} */ @field(DataType.Uint16) minorVersion: number;
/** @type {number} */ @field(DataType.Uint16) numTables: number;
/** @type {number} */ @field(DataType.Uint16) searchRange: number;
/** @type {number} */ @field(DataType.Uint16) entrySelector: number;
/** @type {number} */ @field(DataType.Uint16) rangeShift: number;
/** @type {function(!{ dataView: DataView, position: number }): OffsetTable} */
static read: (reader: DataReader) => OffsetTable;
}
@struct
class TableRecord {
/** @type {string} */ @field(DataType.Char) c1: string;
/** @type {string} */ @field(DataType.Char) c2: string;
/** @type {string} */ @field(DataType.Char) c3: string;
/** @type {string} */ @field(DataType.Char) c4: string;
/** @type {number} */ @field(DataType.Uint32) checksum: number;
/** @type {number} */ @field(DataType.Uint32) offset: number;
/** @type {number} */ @field(DataType.Uint32) length: number;
/** @type {function(!{ dataView: DataView, position: number }): TableRecord} */
static read: (reader: DataReader) => TableRecord;
}
@struct
class NameTableHeader {
/** @type {number} */ @field(DataType.Uint16) formatSelector: number;
/** @type {number} */ @field(DataType.Uint16) count: number;
/** @type {number} */ @field(DataType.Uint16) stringOffset: number;
/** @type {function(!{ dataView: DataView, position: number }): NameTableHeader} */
static read: (reader: DataReader) => NameTableHeader;
}
@struct
class NameRecord {
/** @type {number} */ @field(DataType.Uint16) platformId: number;
/** @type {number} */ @field(DataType.Uint16) encodingId: number;
/** @type {number} */ @field(DataType.Uint16) languageId: number;
/** @type {number} */ @field(DataType.Uint16) nameId: number;
/** @type {number} */ @field(DataType.Uint16) length: number;
/** @type {number} */ @field(DataType.Uint16) offset: number;
/** @type {function(!{ dataView: DataView, position: number }): NameRecord} */
static read: (reader: DataReader) => NameRecord;
}
/**
* Gets all the font names from the given font attachment.
*
* @param {!libjass.Attachment} attachment
* @return {!libjass.Set.<string>}
*/
export function getTtfNames(attachment: Attachment): Set<string> {
const decoded = atob(attachment.contents);
const bytes = new Uint8Array(new ArrayBuffer(decoded.length));
for (let i = 0; i < decoded.length; i++) {
bytes[i] = decoded.charCodeAt(i);
}
const reader = { dataView: new DataView(bytes.buffer), position: 0 };
const offsetTable = OffsetTable.read(reader);
let nameTableRecord: TableRecord | null = null;
for (let i = 0; i < offsetTable.numTables; i++) {
const tableRecord = TableRecord.read(reader);
if (tableRecord.c1 + tableRecord.c2 + tableRecord.c3 + tableRecord.c4 === "name") {
nameTableRecord = tableRecord;
break;
}
}
if (nameTableRecord === null) {
throw new Error('Could not find "name" table record.');
}
reader.position = nameTableRecord.offset;
const nameTableHeader = NameTableHeader.read(reader);
const result = new Set<string>();
for (let i = 0; i < nameTableHeader.count; i++) {
const nameRecord = NameRecord.read(reader);
switch (nameRecord.nameId) {
case 1:
case 4:
case 6:
const recordOffset = nameTableRecord.offset + nameTableHeader.stringOffset + nameRecord.offset;
const nameBytes = bytes.subarray(recordOffset, recordOffset + nameRecord.length);
switch (nameRecord.platformId) {
case 1: {
let name = "";
for (let j = 0; j < nameBytes.length; j++) {
name += String.fromCharCode(nameBytes[j]);
}
result.add(name);
}
break;
case 3: {
let name = "";
for (let j = 0; j < nameBytes.length; j += 2) {
name += String.fromCharCode((nameBytes[j] << 8) + nameBytes[j + 1]);
}
result.add(name);
}
break;
}
break;
default:
break;
}
}
return result;
}
/**
* @param {!function(new(): T)} clazz
* @return {!function(new(): T)}
*/
function struct<T>(clazz: { new (): T; read(reader: DataReader): T; }): { new (): T; read(reader: DataReader): T; } {
const fields: StructMemberDefinition[] = (clazz as any).__fields;
clazz.read = (reader: DataReader) => {
const result: any = new clazz();
for (const field of fields) {
let value: any;
switch (field.type) {
case DataType.Char:
value = String.fromCharCode(reader.dataView.getInt8(reader.position));
reader.position += 1;
break;
case DataType.Uint16:
value = reader.dataView.getUint16(reader.position);
reader.position += 2;
break;
case DataType.Uint32:
value = reader.dataView.getUint32(reader.position);
reader.position += 4;
break;
}
result[field.field] = value;
}
return result;
};
return clazz;
}
/**
* @param {number} type
* @return {function(T, string)}
*/
function field<T>(type: DataType): (proto: T, field: string) => void {
let existingDecorator = fieldDecorators.get(type);
if (existingDecorator === undefined) {
existingDecorator = (proto: T, field: string) => {
const ctor: { __fields?: StructMemberDefinition[] } = proto.constructor;
if (ctor.__fields === undefined) {
ctor.__fields = [];
}
ctor.__fields.push({ type, field });
};
fieldDecorators.set(type, existingDecorator);
}
return existingDecorator;
}
+148
View File
@@ -0,0 +1,148 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The base interface of the drawing instructions.
*/
export interface Instruction { }
/**
* An instruction to move to a particular position.
*
* @param {number} x
* @param {number} y
*/
export class MoveInstruction implements Instruction {
constructor(private _x: number, private _y: number) { }
/**
* The X position of this move instruction.
*
* @type {number}
*/
get x(): number {
return this._x;
}
/**
* The Y position of this move instruction.
*
* @type {number}
*/
get y(): number {
return this._y;
}
}
/**
* An instruction to draw a line to a particular position.
*
* @param {number} x
* @param {number} y
*/
export class LineInstruction implements Instruction {
constructor(private _x: number, private _y: number) { }
/**
* The X position of this line instruction.
*
* @type {number}
*/
get x(): number {
return this._x;
}
/**
* The Y position of this line instruction.
*
* @type {number}
*/
get y(): number {
return this._y;
}
}
/**
* An instruction to draw a cubic bezier curve to a particular position, with two given control points.
*
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
*/
export class CubicBezierCurveInstruction implements Instruction {
constructor(private _x1: number, private _y1: number, private _x2: number, private _y2: number, private _x3: number, private _y3: number) { }
/**
* The X position of the first control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get x1(): number {
return this._x1;
}
/**
* The Y position of the first control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get y1(): number {
return this._y1;
}
/**
* The X position of the second control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get x2(): number {
return this._x2;
}
/**
* The Y position of the second control point of this cubic bezier curve instruction.
*
* @type {number}
*/
get y2(): number {
return this._y2;
}
/**
* The ending X position of this cubic bezier curve instruction.
*
* @type {number}
*/
get x3(): number {
return this._x3;
}
/**
* The ending Y position of this cubic bezier curve instruction.
*
* @type {number}
*/
get y3(): number {
return this._y3;
}
}
+1331
View File
File diff suppressed because it is too large Load Diff
+252
View File
@@ -0,0 +1,252 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { debugMode } from "../../settings";
import { Clock, ClockEvent } from "./base";
import { ManualClock } from "./manual";
/**
* An implementation of {@link libjass.renderers.Clock} that automatically ticks and generates {@link libjass.renderers.ClockEvent}s according to the state of an external driver.
*
* For example, if you're using libjass to render subtitles on a canvas with your own video controls, these video controls will function as the driver to this AutoClock.
* It would call {@link libjass.renderers.AutoClock.play}, {@link libjass.renderers.AutoClock.pause}, etc. when the user pressed the corresponding video controls.
*
* The difference from ManualClock is that AutoClock does not require the driver to call something like {@link libjass.renderers.ManualClock.tick}. Instead it keeps its
* own time with a high-resolution requestAnimationFrame-based timer.
*
* If using libjass with a <video> element, consider using {@link libjass.renderers.VideoClock} that uses the video element as a driver.
*
* @param {function():number} getCurrentTime A callback that will be invoked to get the current time of the external driver.
* @param {number} autoPauseAfter If two calls to getCurrentTime are more than autoPauseAfter milliseconds apart but return the same time, then the external driver will be
* considered to have paused.
*/
export class AutoClock implements Clock {
private _manualClock: ManualClock = new ManualClock();
private _nextAnimationFrameRequestId: number | null = null;
private _lastKnownExternalTime: number | null = null;
private _lastKnownExternalTimeObtainedAt: number | null = null;
constructor(private _getCurrentTime: () => number, private _autoPauseAfter: number) { }
/**
* Tells the clock to start generating ticks.
*/
play(): void {
if (!this._manualClock.enabled) {
return;
}
this._startTicking();
this._manualClock.play();
}
/**
* Tells the clock to pause.
*/
pause(): void {
if (!this._manualClock.enabled) {
return;
}
if (this._nextAnimationFrameRequestId === null) {
if (debugMode) {
console.warn("AutoClock.pause: Abnormal state detected. AutoClock._nextAnimationFrameRequestId should not have been null.");
}
return;
}
this._stopTicking();
this._manualClock.pause();
}
/**
* Tells the clock that the external driver is seeking.
*/
seeking(): void {
this._manualClock.seek(this._getCurrentTime());
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._manualClock.currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._manualClock.enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._manualClock.paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._manualClock.rate;
}
/**
* Sets the rate of the clock - how fast the clock ticks compared to real time.
*
* @param {number} rate The new rate of the clock.
*/
setRate(rate: number): void {
this._manualClock.setRate(rate);
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (!this._manualClock.enable()) {
return false;
}
this._startTicking();
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._manualClock.disable()) {
return false;
}
this._stopTicking();
return true;
}
/**
* Toggle the clock.
*/
toggle(): void {
if (this._manualClock.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();
}
}
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void {
this._manualClock.addEventListener(type, listener);
};
/**
* @param {number} timeStamp
*/
private _onTimerTick(timeStamp: number): void {
if (!this._manualClock.enabled) {
if (debugMode) {
console.warn("AutoClock._onTimerTick: Called when disabled.");
}
return;
}
const currentTime = this._manualClock.currentTime;
const currentExternalTime = this._getCurrentTime();
if (!this._manualClock.paused) {
if (this._lastKnownExternalTime !== null && currentExternalTime === this._lastKnownExternalTime) {
if (timeStamp - this._lastKnownExternalTimeObtainedAt > this._autoPauseAfter) {
this._lastKnownExternalTimeObtainedAt = null;
this._manualClock.seek(currentExternalTime);
}
else {
this._manualClock.tick((timeStamp - this._lastKnownExternalTimeObtainedAt) / 1000 * this._manualClock.rate + this._lastKnownExternalTime);
}
}
else {
this._lastKnownExternalTime = currentExternalTime;
this._lastKnownExternalTimeObtainedAt = timeStamp;
this._manualClock.tick(currentExternalTime);
}
}
else {
if (currentTime !== currentExternalTime) {
this._lastKnownExternalTime = currentExternalTime;
this._lastKnownExternalTimeObtainedAt = timeStamp;
this._manualClock.tick(currentExternalTime);
}
}
this._nextAnimationFrameRequestId = requestAnimationFrame(timeStamp => this._onTimerTick(timeStamp));
}
private _startTicking(): void {
if (this._nextAnimationFrameRequestId === null) {
this._nextAnimationFrameRequestId = requestAnimationFrame(timeStamp => this._onTimerTick(timeStamp));
}
}
private _stopTicking(): void {
if (this._nextAnimationFrameRequestId !== null) {
cancelAnimationFrame(this._nextAnimationFrameRequestId);
this._nextAnimationFrameRequestId = null;
}
}
}
+137
View File
@@ -0,0 +1,137 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../../utility/map";
/**
* A mixin class that represents an event source.
*/
export class EventSource<T> {
/**
* A map from event type to an array of all the listeners registered for that event type.
*
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<T, Function[]>;
/**
* Add a listener for the given event.
*
* @param {!T} type The type of event to attach the listener for
* @param {!Function} listener The listener
*/
addEventListener(type: T, listener: Function): void {
let listeners = this._eventListeners.get(type);
if (listeners === undefined) {
this._eventListeners.set(type, listeners = []);
}
listeners.push(listener);
}
/**
* Calls all listeners registered for the given event type.
*
* @param {!T} type The type of event to dispatch
* @param {!Array.<*>} args Arguments for the listeners of the event
*/
_dispatchEvent(type: T, args: Object[]): void {
const listeners = this._eventListeners.get(type);
if (listeners !== undefined) {
for (const listener of listeners) {
listener.apply(this, args);
}
}
}
}
/**
* The type of clock event.
*/
export enum ClockEvent {
Play,
Tick,
Pause,
Stop,
RateChange,
}
/**
* The clock interface. A clock is used by a renderer as a source of {@link libjass.renderers.ClockEvent}s.
*/
export interface Clock {
/**
* @type {number}
*/
currentTime: number;
/**
* @type {boolean}
*/
enabled: boolean;
/**
* @type {boolean}
*/
paused: boolean;
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
rate: number;
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean;
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean;
/**
* Toggle the clock.
*/
toggle(): void;
/**
* Enable or disable the clock.
*
* @param {boolean} enabled If true, the clock is enabled, otherwise it's disabled.
* @return {boolean} True if the clock is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean;
// EventSource members
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void;
}
+246
View File
@@ -0,0 +1,246 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mixin } from "../../utility/mixin";
import { Map } from "../../utility/map";
import { Clock, ClockEvent, EventSource } from "./base";
/**
* An implementation of {@link libjass.renderers.Clock} that allows user script to manually trigger {@link libjass.renderers.ClockEvent}s.
*/
export class ManualClock implements Clock, EventSource<ClockEvent> {
private _currentTime: number = -1;
private _rate: number = 1;
private _enabled: boolean = true;
private _paused: boolean = true;
/**
* Trigger a {@link libjass.renderers.ClockEvent.Play}
*/
play(): void {
if (!this._enabled) {
return;
}
if (!this._paused) {
return;
}
this._paused = false;
this._dispatchEvent(ClockEvent.Play, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Tick} with the given time.
*
* @param {number} currentTime
*/
tick(currentTime: number): void {
if (!this._enabled) {
return;
}
if (this._currentTime === currentTime) {
return;
}
this.play();
this._currentTime = currentTime;
this._dispatchEvent(ClockEvent.Tick, []);
}
/**
* Seek to the given time. Unlike {@link libjass.renderers.ManualClock.tick} this is used to represent a discontinuous jump, such as the user seeking
* via the video element's position bar.
*
* @param {number} time
*/
seek(time: number): void {
if (!this._enabled) {
return;
}
this.pause();
if (this._currentTime === time) {
return;
}
this.stop();
this.tick(time);
this.pause();
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Pause}
*/
pause(): void {
if (!this._enabled) {
return;
}
if (this._paused) {
return;
}
this._paused = true;
this._dispatchEvent(ClockEvent.Pause, []);
}
/**
* Trigger a {@link libjass.renderers.ClockEvent.Stop}
*/
stop(): void {
this._dispatchEvent(ClockEvent.Stop, []);
}
// Clock members
/**
* @type {number}
*/
get currentTime(): number {
return this._currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._rate;
}
/**
* Sets the rate of the clock - how fast the clock ticks compared to real time.
*
* @param {number} rate The new rate of the clock.
*/
setRate(rate: number): void {
if (this._rate === rate) {
return;
}
this._rate = rate;
this._dispatchEvent(ClockEvent.RateChange, []);
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
if (this._enabled) {
return false;
}
this._enabled = true;
return true;
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
if (!this._enabled) {
return false;
}
this.pause();
this.stop();
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
/**
* @type {!Map.<T, !Array.<Function>>}
*/
_eventListeners: Map<ClockEvent, Function[]> = new Map<ClockEvent, Function[]>();
/**
* @type {function(number, !Function)}
*/
addEventListener: (type: ClockEvent, listener: Function) => void;
/**
* @type {function(number, Array.<*>)}
*/
_dispatchEvent: (type: ClockEvent, args: Object[]) => void;
}
mixin(ManualClock, [EventSource]);
+122
View File
@@ -0,0 +1,122 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AutoClock } from "./auto";
import { Clock, ClockEvent } from "./base";
/**
* An implementation of libjass.renderers.Clock that generates {@link libjass.renderers.ClockEvent}s according to the state of a <video> element.
*
* @param {!HTMLVideoElement} video
*/
export class VideoClock implements Clock {
private _autoClock: AutoClock;
constructor(video: HTMLVideoElement) {
this._autoClock = new AutoClock(() => video.currentTime, 100);
video.addEventListener("playing", () => this._autoClock.play(), false);
video.addEventListener("pause", () => this._autoClock.pause(), false);
video.addEventListener("seeking", () => this._autoClock.seeking(), false);
video.addEventListener("ratechange", () => this._autoClock.setRate(video.playbackRate), false);
}
/**
* @type {number}
*/
get currentTime(): number {
return this._autoClock.currentTime;
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._autoClock.enabled;
}
/**
* @type {boolean}
*/
get paused(): boolean {
return this._autoClock.paused;
}
/**
* Gets the rate of the clock - how fast the clock ticks compared to real time.
*
* @type {number}
*/
get rate(): number {
return this._autoClock.rate;
}
/**
* Enable the clock.
*
* @return {boolean} True if the clock is now enabled, false if it was already enabled.
*/
enable(): boolean {
return this._autoClock.enable();
}
/**
* Disable the clock.
*
* @return {boolean} True if the clock is now disabled, false if it was already disabled.
*/
disable(): boolean {
return this._autoClock.disable();
}
/**
* Toggle the clock.
*/
toggle(): void {
if (this._autoClock.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();
}
}
/**
* @param {number} type
* @param {!Function} listener
*/
addEventListener(type: ClockEvent, listener: Function): void {
this._autoClock.addEventListener(type, listener);
}
}
+77
View File
@@ -0,0 +1,77 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { VideoClock } from "./clocks/video";
import { RendererSettings } from "./settings";
import { WebRenderer } from "./web/renderer";
import { ASS } from "../types/ass";
/**
* A default renderer implementation.
*
* @param {!HTMLVideoElement} video
* @param {!libjass.ASS} ass
* @param {libjass.renderers.RendererSettings} settings
*/
export class DefaultRenderer extends WebRenderer {
constructor(private _video: HTMLVideoElement, ass: ASS, settings?: RendererSettings) {
super(ass, new VideoClock(_video), document.createElement("div"), settings);
this._video.parentElement.replaceChild(this.libjassSubsWrapper, this._video);
this.libjassSubsWrapper.insertBefore(this._video, this.libjassSubsWrapper.firstElementChild);
}
/**
* Resize the subtitles to the dimensions of the video element.
*
* This method accounts for letterboxing if the video element's size is not the same ratio as the video resolution.
*/
resize(): void {
// Handle letterboxing around the video. If the width or height are greater than the video can be, then consider that dead space.
const videoWidth = this._video.videoWidth;
const videoHeight = this._video.videoHeight;
const videoOffsetWidth = this._video.offsetWidth;
const videoOffsetHeight = this._video.offsetHeight;
const ratio = Math.min(videoOffsetWidth / videoWidth, videoOffsetHeight / videoHeight);
const subsWrapperWidth = videoWidth * ratio;
const subsWrapperHeight = videoHeight * ratio;
const subsWrapperLeft = (videoOffsetWidth - subsWrapperWidth) / 2;
const subsWrapperTop = (videoOffsetHeight - subsWrapperHeight) / 2;
super.resize(subsWrapperWidth, subsWrapperHeight, subsWrapperLeft, subsWrapperTop);
}
/**
* @deprecated
*/
resizeVideo(): void {
console.warn("`DefaultRenderer.resizeVideo(width, height)` has been deprecated. Use `DefaultRenderer.resize()` instead.");
this.resize();
}
protected _ready(): void {
this.resize();
super._ready();
}
}
+29
View File
@@ -0,0 +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.
*/
export { Clock, ClockEvent, EventSource } from "./clocks/base";
export { AutoClock } from "./clocks/auto";
export { ManualClock } from "./clocks/manual";
export { VideoClock } from "./clocks/video";
export { DefaultRenderer } from "./default";
export { NullRenderer } from "./null";
export { WebRenderer } from "./web/renderer";
export { RendererSettings } from "./settings";
+208
View File
@@ -0,0 +1,208 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Clock, ClockEvent } from "./clocks/base";
import { RendererSettings } from "./settings";
import { debugMode, verboseMode } from "../settings";
import { ASS } from "../types/ass";
import { Dialogue } from "../types/dialogue";
/**
* A renderer implementation that doesn't output anything.
*
* @param {!libjass.ASS} ass
* @param {!libjass.renderers.Clock} clock
* @param {libjass.renderers.RendererSettings} settings
*/
export class NullRenderer {
private static _lastRendererId = -1;
private _id: number;
private _settings: RendererSettings;
constructor(private _ass: ASS, private _clock: Clock, settings?: RendererSettings) {
this._id = ++NullRenderer._lastRendererId;
this._settings = RendererSettings.from(settings);
this._clock.addEventListener(ClockEvent.Play, () => this._onClockPlay());
this._clock.addEventListener(ClockEvent.Tick, () => this._onClockTick());
this._clock.addEventListener(ClockEvent.Pause, () => this._onClockPause());
this._clock.addEventListener(ClockEvent.Stop, () => this._onClockStop());
this._clock.addEventListener(ClockEvent.RateChange, () => this._onClockRateChange());
}
/**
* The unique ID of this renderer. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* @type {!libjass.ASS}
*/
get ass(): ASS {
return this._ass;
}
/**
* @type {!libjass.renderers.Clock}
*/
get clock(): Clock {
return this._clock;
}
/**
* @type {!libjass.renderers.RendererSettings}
*/
get settings(): RendererSettings {
return this._settings;
}
/**
* Pre-render a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
preRender(dialogue: Dialogue): void { }
/**
* Draw a dialogue. This is a no-op for this type.
*
* @param {!libjass.Dialogue} dialogue
*/
draw(dialogue: Dialogue): void { }
/**
* Enable the renderer.
*
* @return {boolean} True if the renderer is now enabled, false if it was already enabled.
*/
enable(): boolean {
return this._clock.enable();
}
/**
* Disable the renderer.
*
* @return {boolean} True if the renderer is now disabled, false if it was already disabled.
*/
disable(): boolean {
return this._clock.disable();
}
/**
* Toggle the renderer.
*/
toggle(): void {
this._clock.toggle();
}
/**
* Enable or disable the renderer.
*
* @param {boolean} enabled If true, the renderer is enabled, otherwise it's disabled.
* @return {boolean} True if the renderer is now in the given state, false if it was already in that state.
*/
setEnabled(enabled: boolean): boolean {
return this._clock.setEnabled(enabled);
}
/**
* @type {boolean}
*/
get enabled(): boolean {
return this._clock.enabled;
}
/**
* Runs when the clock is enabled, or starts playing, or is resumed from pause.
*/
protected _onClockPlay(): void {
if (verboseMode) {
console.log("NullRenderer._onClockPlay");
}
}
/**
* Runs when the clock's current time changed. This might be a result of either regular playback or seeking.
*/
protected _onClockTick(): void {
const currentTime = this._clock.currentTime;
if (verboseMode) {
console.log(`NullRenderer._onClockTick: currentTime = ${ currentTime }`);
}
for (const dialogue of this._ass.dialogues) {
try {
if (dialogue.end > currentTime) {
if (dialogue.start <= currentTime) {
// This dialogue is visible right now. Draw it.
this.draw(dialogue);
}
else if (dialogue.start <= (currentTime + this._settings.preRenderTime)) {
// This dialogue will be visible soon. Pre-render it.
this.preRender(dialogue);
}
}
}
catch (ex) {
if (debugMode) {
console.error(`Rendering dialogue ${ dialogue.id } failed.`, ex);
}
}
}
}
/**
* Runs when the clock is paused.
*/
protected _onClockPause(): void {
if (verboseMode) {
console.log("NullRenderer._onClockPause");
}
}
/**
* Runs when the clock is disabled.
*/
protected _onClockStop(): void {
if (verboseMode) {
console.log("NullRenderer._onClockStop");
}
}
/**
* Runs when the clock changes its rate.
*/
protected _onClockRateChange(): void {
if (verboseMode) {
console.log("NullRenderer._onClockRateChange");
}
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface Array<T> {
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
}
+239
View File
@@ -0,0 +1,239 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { debugMode } from "../settings";
import { Map } from "../utility/map";
/**
* Settings for the renderer.
*/
export class RendererSettings {
/**
* A map of font name to one or more URLs of that font. If provided, the fonts in this map are pre-loaded by the WebRenderer when it's created.
*
* The key of each entry of the map is the font name used in the ASS script. There are three choices for the value:
*
* - A single string that you would use for the src attribute of a @font-face rule. Eg: `'url("/fonts.foo.ttf"), url("/fonts/foo-fallback.ttf"), local("Arial.ttf")'`
*
* - An array of the individual sources that you would use for the src attribute of a @font-face rule. Eg: `['url("/fonts.foo.ttf")', 'url("/fonts/foo-fallback.ttf")', 'local("Arial")']`
*
* - An array of URLs. Eg: `["/fonts.foo.ttf", "/fonts/foo-fallback.ttf"]`
*
* Only the first and second forms allow you to use local fonts. The third form only allows you to use remote fonts.
*
* If you have a <style> or <link> element on the page containing @font-face rules, you can use the {@link libjass.renderers.RendererSettings.makeFontMapFromStyleElement}
* convenience method to create a font map.
*
* Defaults to null.
*
* @type {Map.<string, (string|!Array.<string>)>}
*/
fontMap: Map<string, string | string[]> | null;
/**
* Subtitles will be pre-rendered for this amount of time (seconds).
*
* Defaults to 5.
*
* @type {number}
*/
preRenderTime: number;
/**
* Subtitle outlines will be rendered in full detail. When false, the value of blur is used to draw less outlines for better performance and (hopefully) similar output.
*
* Defaults to false.
*
* @type {boolean}
*/
preciseOutlines: boolean;
/**
* Outlines and blur are implemented using SVG filters by default. When false, they will be rendered using alternative means.
*
* IE 11 and below do not support SVG filters on HTML elements so this should be set to false there. See http://caniuse.com/svg-html for details.
*
* Defaults to true.
*
* @type {boolean}
*/
enableSvg: boolean;
/**
* Comma-separated list of fonts to be used when font specified in ASS Styles not loaded.
*
* The value should be a valid CSS font-family property (i.e. comma-separated and individual names in quotes if necessary). Use empty string to disable fallback.
*
* Defaults to 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"'.
*
* @type {string}
*/
fallbackFonts: string;
/**
* If true, attached TTF fonts in the ASS script will be used. The font is loaded as a data: URI. Requires ES6 typed arrays (ArrayBuffer, DataView, Uint8Array, etc).
*
* The font is naively parsed to extract the strings that will be used as the font family. Do not use this option with untrusted fonts or scripts.
*
* Defaults to false.
*
* @type {boolean}
*/
useAttachedFonts: boolean;
/**
* A convenience method to create a font map from a <style> or <link> element that contains @font-face rules. There should be one @font-face rule for each font name, mapping to a font file URL.
*
* For example:
*
* @font-face {
* font-family: "Helvetica";
* src: url("/fonts/helvetica.ttf"), local("Arial");
* }
*
* More complicated @font-face syntax like format() or multi-line src are not supported.
*
* @param {!LinkStyle} linkStyle
* @return {!Map.<string, string>}
*/
static makeFontMapFromStyleElement(linkStyle: LinkStyle): Map<string, string> {
const fontMap = new Map<string, string>();
const styleSheet = linkStyle.sheet as CSSStyleSheet;
for (let i = 0; i < styleSheet.cssRules.length; i++) {
const rule = styleSheet.cssRules[i];
if (isFontFaceRule(rule)) {
const name = rule.style.getPropertyValue("font-family").match(/^["']?(.*?)["']?$/)![1];
let src = rule.style.getPropertyValue("src");
if (!src) {
src = rule.cssText.split("\n")
.map(line => line.match(/src:\s*([^;]+?)\s*;/))
.filter((matches): matches is RegExpMatchArray => matches !== null)
.map(matches => matches[1])[0];
}
fontMap.set(name, src);
}
}
return fontMap;
}
/**
* Converts an arbitrary object into a {@link libjass.renderers.RendererSettings} object.
*
* @param {*} object
* @return {!libjass.renderers.RendererSettings}
*/
static from(object?: any): RendererSettings {
if (object === undefined || object === null) {
object = {};
}
const {
fontMap = null,
preRenderTime = 5,
preciseOutlines = false,
enableSvg = testSupportsSvg(),
fallbackFonts = 'Arial, Helvetica, sans-serif, "Segoe UI Symbol"',
useAttachedFonts = false,
} = object as RendererSettings;
const result = new RendererSettings();
result.fontMap = fontMap;
result.preRenderTime = preRenderTime;
result.preciseOutlines = preciseOutlines;
result.enableSvg = enableSvg;
result.fallbackFonts = fallbackFonts;
result.useAttachedFonts = useAttachedFonts;
return result;
}
}
/**
* @param {!CSSRule} rule
* @return {boolean}
*/
function isFontFaceRule(rule: CSSRule): rule is CSSFontFaceRule {
return rule.type === CSSRule.FONT_FACE_RULE;
}
/**
* Returns true if this environment may support SVG filter effects. May return false positives.
*
* @return {boolean}
*/
function testSupportsSvg(): boolean {
if (debugMode) {
console.log("Testing whether SVG filter effects are supported.");
}
if (typeof document === "undefined") {
if (debugMode) {
console.log("This doesn't look like a browser. Assuming it doesn't support SVG filter effects.");
}
return false;
}
const morphologyFilter = document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
// https://connect.microsoft.com/IE/feedback/details/2375800
try {
morphologyFilter.radiusX.baseVal = 1;
}
catch (ex) {
if (debugMode) {
if (ex instanceof DOMException) {
const domException = ex as DOMException;
if (domException.code === DOMException.NO_MODIFICATION_ALLOWED_ERR) {
console.log("Setting SVGFEMorphologyElement.radiusX.baseVal threw NoModificationAllowedError. This browser doesn't support SVG DOM correctly.");
}
else {
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected DOMException code ${ domException.code }. This browser doesn't support SVG DOM correctly.`);
}
}
else {
console.log(`Setting SVGFEMorphologyElement.radiusX.baseVal threw unexpected exception ${ ex }. This browser doesn't support SVG SVG DOM correctly.`);
}
}
return false;
}
// https://connect.microsoft.com/IE/feedback/details/2375757
morphologyFilter.setAttribute("radius", "1");
if (morphologyFilter.cloneNode().getAttribute("radius") !== "1") {
if (debugMode) {
console.log("SVGFEMorphologyElement's radius attribute was corrupted when cloned. This browser doesn't support SVG DOM correctly.");
}
return false;
}
if (debugMode) {
console.log("This browser may support SVG filter effects.");
}
return true;
}
+126
View File
@@ -0,0 +1,126 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Keyframe } from "./keyframe";
import { NullRenderer } from "../null";
import { Map } from "../../utility/map";
/**
* This class represents a collection of animations. Each animation contains one or more keyframes.
* The collection can then be converted to a CSS3 representation.
*
* @param {!libjass.renderers.NullRenderer} renderer The renderer that this collection is associated with
* @param {!HTMLStyleElement} style A <style> element to insert the animation rules into
*/
export class AnimationCollection {
private static _nextId: number = 0;
private _id: string;
private _rate: number;
private _animationStyle: string = "";
private _animationDelays: Map<string, number> = new Map<string, number>();
private _numAnimations: number = 0;
constructor(renderer: NullRenderer, private _style: HTMLStyleElement) {
this._id = `${ renderer.id }-${ AnimationCollection._nextId++ }`;
this._rate = renderer.clock.rate;
}
/**
* This string should be set as the "animation" CSS property of the target element.
*
* @type {string}
*/
get animationStyle(): string {
return this._animationStyle;
}
/**
* This array should be used to set the "animation-delay" CSS property of the target element.
*
* @type {!Array.<number>}
*/
get animationDelays(): Map<string, number> {
return this._animationDelays;
}
/**
* Add an animation to this collection. The given keyframes together make one animation.
*
* @param {string} timingFunction One of the acceptable values for the "animation-timing-function" CSS property
* @param {!Array.<!libjass.renderers.Keyframe>} keyframes
*/
add(timingFunction: string, keyframes: Keyframe[]): void {
let start: number | null = null;
let end: number | null = null;
for (const keyframe of keyframes) {
if (start === null) {
start = keyframe.time;
}
end = keyframe.time;
}
if (start === null || end === null) {
throw new Error("Atleast one keyframe must be provided.");
}
let ruleCssText = "";
for (const keyframe of keyframes) {
ruleCssText +=
` ${ (100 * ((end - start === 0) ? 1 : ((keyframe.time - start) / (end - start)))).toFixed(3) }% {
`;
keyframe.properties.forEach((value, name) => {
ruleCssText +=
` ${ name }: ${ value };
`;
});
ruleCssText +=
` }
`;
}
const animationName = `animation-${ this._id }-${ this._numAnimations++ }`;
this._style.appendChild(document.createTextNode(
`@-webkit-keyframes ${ animationName } {
${ ruleCssText }
}`));
this._style.appendChild(document.createTextNode(
`@keyframes ${ animationName } {
${ ruleCssText }
}`));
if (this._animationStyle !== "") {
this._animationStyle += ",";
}
this._animationStyle += `${ animationName } ${ ((end - start) / this._rate).toFixed(3) }s ${ timingFunction }`;
this._animationDelays.set(animationName, start);
}
}
+131
View File
@@ -0,0 +1,131 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as parts from "../../parts";
/**
* This class represents an ASS drawing - a set of drawing instructions between {\p} tags.
*
* @param {number} outputScaleX
* @param {number} outputScaleY
*/
export class DrawingStyles {
private _scale: number = 1;
private _baselineOffset: number = 0;
constructor(private _outputScaleX: number, private _outputScaleY: number) { }
/**
* @type {number}
*/
set scale(value: number) {
this._scale = value;
}
/**
* @type {number}
*/
set baselineOffset(value: number) {
this._baselineOffset = value;
}
/**
* Converts this drawing to an <svg> element.
*
* @param {!libjass.parts.DrawingInstructions} drawingInstructions
* @param {!libjass.parts.Color} fillColor
* @return {!SVGSVGElement}
*/
toSVG(drawingInstructions: parts.DrawingInstructions, fillColor: parts.Color): SVGSVGElement {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("version", "1.1");
if (drawingInstructions.instructions.length === 0) {
return svg;
}
const scaleFactor = Math.pow(2, this._scale - 1);
const scaleX = this._outputScaleX / scaleFactor;
const scaleY = this._outputScaleY / scaleFactor;
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
let pathString = "";
let bboxMinX = Infinity;
let bboxMaxX = -Infinity;
let bboxMinY = Infinity;
let bboxMaxY = -Infinity;
for (const instruction of drawingInstructions.instructions) {
if (instruction instanceof parts.drawing.MoveInstruction) {
pathString += ` M ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x);
bboxMaxX = Math.max(bboxMaxX, instruction.x);
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.LineInstruction) {
pathString += ` L ${ instruction.x.toFixed(3) } ${ (instruction.y + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x);
bboxMaxX = Math.max(bboxMaxX, instruction.x);
bboxMinY = Math.min(bboxMinY, instruction.y + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y + this._baselineOffset);
}
else if (instruction instanceof parts.drawing.CubicBezierCurveInstruction) {
pathString += ` C ${ instruction.x1.toFixed(3) } ${ (instruction.y1 + this._baselineOffset).toFixed(3) } ${ instruction.x2.toFixed(3) } ${ (instruction.y2 + this._baselineOffset).toFixed(3) } ${ instruction.x3.toFixed(3) } ${ (instruction.y3 + this._baselineOffset).toFixed(3) }`;
bboxMinX = Math.min(bboxMinX, instruction.x1, instruction.x2, instruction.x3);
bboxMaxX = Math.max(bboxMaxX, instruction.x1, instruction.x2, instruction.x3);
bboxMinY = Math.min(bboxMinY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
bboxMaxY = Math.max(bboxMaxY, instruction.y1 + this._baselineOffset, instruction.y2 + this._baselineOffset, instruction.y3 + this._baselineOffset);
}
}
bboxMinX *= scaleX;
bboxMaxX *= scaleX;
bboxMinY *= scaleY;
bboxMaxY *= scaleY;
const bboxWidth = bboxMaxX - bboxMinX;
const bboxHeight = bboxMaxY - bboxMinY;
svg.width.baseVal.valueAsString = `${ bboxWidth.toFixed(3) }px`;
svg.height.baseVal.valueAsString = `${ bboxHeight.toFixed(3) }px`;
// svg.viewBox.baseVal is null in atleast FF. See https://bugzilla.mozilla.org/show_bug.cgi?id=888307 which justifies it with SVG 1.2 spec.
svg.setAttribute("viewBox", `${ bboxMinX } ${ bboxMinY } ${ bboxWidth } ${ bboxHeight }`);
svg.style.position = "relative";
svg.style.left = `${ bboxMinX.toFixed(3) }px`;
svg.style.top = `${ bboxMinY.toFixed(3) }px`;
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
svg.appendChild(g);
g.setAttribute("transform", `scale(${ scaleX.toFixed(3) } ${ scaleY.toFixed(3) })`);
g.appendChild(path);
path.setAttribute("d", pathString);
path.setAttribute("fill", fillColor.toString());
return svg;
}
}
+124
View File
@@ -0,0 +1,124 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../../utility/map";
import { Promise } from "../../utility/promise";
/**
* @param {string} fontFamily
* @param {number} fontSize
* @param {string} fallbackFonts
* @param {!HTMLDivElement} fontSizeElement
*/
function prepareFontSizeElement(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): void {
let fonts = `"${ fontFamily }"`;
if (fallbackFonts !== "") {
fonts += `, ${ fallbackFonts }`;
}
fontSizeElement.style.fontFamily = fonts;
fontSizeElement.style.fontSize = `${ fontSize }px`;
}
/**
* @param {string} fontFamily
* @param {number} fontSize
* @param {string} fallbackFonts
* @param {!HTMLDivElement} fontSizeElement
* @return {!Promise.<number>}
*/
function lineHeightForFontSize(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<number> {
prepareFontSizeElement(fontFamily, fontSize, fallbackFonts, fontSizeElement);
return new Promise(resolve => setTimeout(() => resolve(fontSizeElement.offsetHeight), 1000));
}
/**
* @param {string} fontFamily
* @param {number} fontSize
* @param {string} fallbackFonts
* @param {!HTMLDivElement} fontSizeElement
* @return {number}
*/
function lineHeightForFontSizeSync(fontFamily: string, fontSize: number, fallbackFonts: string, fontSizeElement: HTMLDivElement): number {
prepareFontSizeElement(fontFamily, fontSize, fallbackFonts, fontSizeElement);
return fontSizeElement.offsetHeight;
}
/**
* @param {number} lowerLineHeight
* @param {number} upperLineHeight
* @return {[number, number]}
*/
function fontMetricsFromLineHeights(lowerLineHeight: number, upperLineHeight: number): [number, number] {
return [lowerLineHeight, (360 - 180) / (upperLineHeight - lowerLineHeight)];
}
/**
* Calculates font metrics for the given font family.
*
* @param {string} fontFamily
* @param {string} fallbackFonts
* @param {!HTMLDivElement} fontSizeElement
* @return {!Promise.<number>}
*/
export function calculateFontMetrics(fontFamily: string, fallbackFonts: string, fontSizeElement: HTMLDivElement): Promise<[number, number]> {
return lineHeightForFontSize(fontFamily, 180, fallbackFonts, fontSizeElement).then(lowerLineHeight =>
lineHeightForFontSize(fontFamily, 360, fallbackFonts, fontSizeElement).then(upperLineHeight =>
fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight)
)
);
}
/**
* @param {number} lineHeight
* @param {number} lowerLineHeight
* @param {number} factor
* @return {number}
*/
function fontSizeFromMetrics(lineHeight: number, lowerLineHeight: number, factor: number): number {
return 180 + (lineHeight - lowerLineHeight) * factor;
}
/**
* Uses linear interpolation to calculate the CSS font size that would give the specified line height for the specified font family.
*
* WARNING: If fontMetricsCache doesn't already contain a cached value for this font family, and it is not a font already installed on the user's device, then this function
* may return wrong values. To avoid this, make sure to preload the font using the {@link libjass.renderers.RendererSettings.fontMap} property when constructing the renderer.
*
* @param {string} fontFamily
* @param {number} lineHeight
* @param {string} fallbackFonts
* @param {!HTMLDivElement} fontSizeElement
* @param {!Map.<string, [number, number]>} fontMetricsCache
* @return {number}
*/
export function fontSizeForLineHeight(fontFamily: string, lineHeight: number, fallbackFonts: string, fontSizeElement: HTMLDivElement, fontMetricsCache: Map<string, [number, number]>): number {
let existingMetrics = fontMetricsCache.get(fontFamily);
if (existingMetrics === undefined) {
const lowerLineHeight = lineHeightForFontSizeSync(fontFamily, 180, fallbackFonts, fontSizeElement);
const upperLineHeight = lineHeightForFontSizeSync(fontFamily, 360, fallbackFonts, fontSizeElement);
fontMetricsCache.set(fontFamily, existingMetrics = fontMetricsFromLineHeights(lowerLineHeight, upperLineHeight));
}
const [lowerLineHeight, factor] = existingMetrics;
return fontSizeFromMetrics(lineHeight, lowerLineHeight, factor);
}
+45
View File
@@ -0,0 +1,45 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../../utility/map";
/**
* This class represents a single keyframe. It has a list of CSS properties (names and values) associated with a point in time. Multiple keyframes make up an animation.
*
* @param {number} time
* @param {!Map.<string, string>} properties
*/
export class Keyframe {
constructor(private _time: number, private _properties: Map<string, string>) { }
/**
* @type {number}
*/
get time(): number {
return this._time;
}
/**
* @type {!Map.<string, string>}
*/
get properties(): Map<string, string> {
return this._properties;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface Node {
cloneNode(deep?: boolean): this;
}
interface SVGFEComponentTransferElement {
appendChild(newChild: SVGFEFuncAElement): SVGFEFuncAElement;
appendChild(newChild: SVGFEFuncBElement): SVGFEFuncBElement;
appendChild(newChild: SVGFEFuncGElement): SVGFEFuncGElement;
appendChild(newChild: SVGFEFuncRElement): SVGFEFuncRElement;
}
interface SVGFEMergeElement {
appendChild(newChild: SVGFEMergeNodeElement): SVGFEMergeNodeElement;
}
+78
View File
@@ -0,0 +1,78 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "./utility/map";
const classes = new Map<number, Function & { fromJSON?: (obj: any) => any }>();
/**
* Registers a class as a serializable type.
*
* @param {function(new:*)} clazz
*/
export function registerClass(clazz: Function & { fromJSON?: (obj: any) => any }): void {
clazz.prototype._classTag = classes.size;
classes.set(clazz.prototype._classTag, clazz);
}
/**
* Serializes the given object.
*
* @param {*} obj
* @return {string}
*/
export function serialize(obj: any): string {
return JSON.stringify(obj, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && (value._classTag !== undefined) && !Object.prototype.hasOwnProperty.call(value, "_classTag")) {
// Copy the _classTag from this object's prototype to itself, so that it will be serialized.
value._classTag = value._classTag;
}
return value;
});
}
/**
* @param {string} str
* @return {*}
*/
export function deserialize(str: string): any {
return JSON.parse(str, (/* ujs:unreferenced */ key: string, value: any) => {
if (value && (value._classTag !== undefined)) {
const clazz = classes.get(value._classTag);
if (clazz === undefined) {
throw new Error(`Unknown class of tag ${ value._classTag } cannot be deserialized.`);
}
if (typeof clazz.fromJSON === "function") {
value = clazz.fromJSON(value);
}
else {
const hydratedValue = Object.create(clazz.prototype);
for (const key of Object.keys(value)) {
hydratedValue[key] = value[key];
}
value = hydratedValue;
}
}
return value;
});
}
+51
View File
@@ -0,0 +1,51 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Debug mode. When true, libjass logs some debug messages.
*
* @type {boolean}
*/
export let debugMode: boolean = false;
/**
* Verbose debug mode. When true, libjass logs some more debug messages. This setting is independent of {@link libjass.debugMode}
*
* @type {boolean}
*/
export let verboseMode: boolean = false;
/**
* Sets the debug mode.
*
* @param {boolean} value
*/
export function setDebugMode(value: boolean): void {
debugMode = value;
}
/**
* Sets the verbose debug mode.
*
* @param {boolean} value
*/
export function setVerboseMode(value: boolean): void {
verboseMode = value;
}
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["es5", "dom"],
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"strictNullChecks": true,
"target": "es5",
"module": "amd",
"moduleResolution": "node",
"outFile": "../lib/libjass.js",
"noImplicitUseStrict": true,
"sourceMap": true,
"inlineSources": true
}
}
+333
View File
@@ -0,0 +1,333 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Attachment } from "./attachment";
import { Dialogue } from "./dialogue";
import { Style } from "./style";
import { ScriptProperties } from "./script-properties";
import { Format } from "./misc";
import { debugMode, verboseMode } from "../settings";
import * as parser from "../parser";
import { parseLineIntoTypedTemplate } from "../parser/misc";
import { ReadableStream, TextDecoderConstructor } from "../parser/streams";
import { Map } from "../utility/map";
import { Promise } from "../utility/promise";
import { registerClass as serializable } from "../serialization";
declare const global: {
fetch?(url: string): Promise<{ body: ReadableStream; ok?: boolean; status?: number; }>;
ReadableStream?: Function & { prototype: ReadableStream; };
TextDecoder?: TextDecoderConstructor;
};
/**
* This class represents an ASS script. It contains the {@link libjass.ScriptProperties}, an array of {@link libjass.Style}s, and an array of {@link libjass.Dialogue}s.
*/
@serializable
export class ASS {
private _properties: ScriptProperties = new ScriptProperties();
private _styles: Map<string, Style> = new Map<string, Style>();
private _dialogues: Dialogue[] = [];
private _attachments: Attachment[] = [];
private _stylesFormatSpecifier: string[] | null = null;
private _dialoguesFormatSpecifier: string[] | null = null;
/**
* The properties of this script.
*
* @type {!libjass.ScriptProperties}
*/
get properties(): ScriptProperties {
return this._properties;
}
/**
* The styles in this script.
*
* @type {!Map.<string, !libjass.Style>}
*/
get styles(): Map<string, Style> {
return this._styles;
}
/**
* The dialogues in this script.
*
* @type {!Array.<!libjass.Dialogue>}
*/
get dialogues(): Dialogue[] {
return this._dialogues;
}
/**
* The attachments of this script.
*
* @type {!Array.<!libjass.Attachment>}
*/
get attachments(): Attachment[] {
return this._attachments;
}
/**
* The format specifier for the styles section.
*
* @type {Array.<string>}
*/
get stylesFormatSpecifier(): string[] | null {
return this._stylesFormatSpecifier;
}
/**
* The format specifier for the styles section.
*
* @type {Array.<string>}
*/
get dialoguesFormatSpecifier(): string[] | null {
return this._dialoguesFormatSpecifier;
}
/**
* The format specifier for the events section.
*
* @type {Array.<string>}
*/
set stylesFormatSpecifier(value: string[] | null) {
this._stylesFormatSpecifier = value;
}
/**
* The format specifier for the events section.
*
* @type {Array.<string>}
*/
set dialoguesFormatSpecifier(value: string[] | null) {
this._dialoguesFormatSpecifier = value;
}
constructor() {
// Deprecated constructor argument
if (arguments.length === 1) {
throw new Error("Constructor `new ASS(rawASS)` has been deprecated. Use `ASS.fromString(rawASS)` instead.");
}
this._styles.set("Default", new Style(new Map([["Name", "Default"]])));
}
/**
* Add a style to this ASS script.
*
* @param {string} line The line from the script that contains the new style.
*/
addStyle(line: string): void {
if (this._stylesFormatSpecifier === null) {
throw new Error("stylesFormatSpecifier is not set.");
}
const styleLine = parseLineIntoTypedTemplate(line, this._stylesFormatSpecifier);
if (styleLine === null || styleLine.type !== "Style") {
return;
}
const styleTemplate = styleLine.template;
if (verboseMode) {
let repr = "";
styleTemplate.forEach((value, key) => repr += `${ key } = ${ value }, `);
console.log(`Read style: ${ repr }`);
}
// Create the dialogue and add it to the dialogues array
const style = new Style(styleTemplate);
this._styles.set(style.name, style);
}
/**
* Add an event to this ASS script.
*
* @param {string} line The line from the script that contains the new event.
*/
addEvent(line: string): void {
if (this._dialoguesFormatSpecifier === null) {
throw new Error("dialoguesFormatSpecifier is not set.");
}
const dialogueLine = parseLineIntoTypedTemplate(line, this._dialoguesFormatSpecifier);
if (dialogueLine === null || dialogueLine.type !== "Dialogue") {
return;
}
const dialogueTemplate = dialogueLine.template;
if (verboseMode) {
let repr = "";
dialogueTemplate.forEach((value, key) => repr += `${ key } = ${ value }, `);
console.log(`Read dialogue: ${ repr }`);
}
// Create the dialogue and add it to the dialogues array
this.dialogues.push(new Dialogue(dialogueTemplate, this));
}
/**
* Add an attachment to this ASS script.
*
* @param {!libjass.Attachment} attachment
*/
addAttachment(attachment: Attachment): void {
this._attachments.push(attachment);
}
/**
* Custom JSON serialization for ASS objects.
*
* @return {!*}
*/
toJSON(): any {
const result = Object.create(null);
result._properties = this._properties;
result._styles = Object.create(null);
this._styles.forEach((style, name) => result._styles[name] = style);
result._dialogues = this._dialogues;
result._attachments = this._attachments;
result._stylesFormatSpecifier = this._stylesFormatSpecifier;
result._dialoguesFormatSpecifier = this._dialoguesFormatSpecifier;
result._classTag = (ASS.prototype as any)._classTag;
return result;
}
/**
* Creates an ASS object from the raw text of an ASS script.
*
* @param {string} raw The raw text of the script.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>}
*/
static fromString(raw: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
return ASS.fromStream(new parser.StringStream(raw), type);
}
/**
* Creates an ASS object from the given {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The stream to parse the script from
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromStream(stream: parser.Stream, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
case "ass":
return new parser.StreamParser(stream).ass;
case Format.SRT:
case "srt":
return new parser.SrtStreamParser(stream).ass;
default:
throw new Error(`Invalid value of type: ${ type }`);
}
}
/**
* Creates an ASS object from the given URL.
*
* @param {string} url The URL of the script.
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromUrl(url: string, type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
let fetchPromise: Promise<ASS>;
if (
typeof global.fetch === "function" &&
typeof global.ReadableStream === "function" && typeof global.ReadableStream.prototype.getReader === "function" &&
typeof global.TextDecoder === "function"
) {
fetchPromise = global.fetch(url).then(response => {
if (response.ok === false || (response.ok === undefined && (response.status < 200 || response.status > 299))) {
throw new Error(`HTTP request for ${ url } failed with status code ${ response.status }`);
}
return ASS.fromReadableStream(response.body, "utf-8", type);
});
}
else {
fetchPromise = Promise.reject<ASS>(new Error("Not supported."));
}
return fetchPromise.catch(reason => {
if (debugMode) {
console.log("fetch() failed, falling back to XHR: %o", reason);
}
const xhr = new XMLHttpRequest();
const result = ASS.fromStream(new parser.XhrStream(xhr), type);
xhr.open("GET", url, true);
xhr.send();
return result;
});
}
/**
* Creates an ASS object from the given ReadableStream.
*
* @param {!ReadableStream} stream
* @param {string="utf-8"} encoding
* @param {(number|string)=0} type The type of the script. One of the {@link libjass.Format} constants, or one of the strings "ass" and "srt".
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromReadableStream(stream: ReadableStream, encoding: string = "utf-8", type: Format | "ass" | "srt" = Format.ASS): Promise<ASS> {
return ASS.fromStream(new parser.BrowserReadableStream(stream, encoding), type);
}
/**
* Custom deserialization for ASS objects.
*
* @param {!*} obj
* @return {!libjass.ASS}
*/
static fromJSON(obj: any): ASS {
const result: ASS = Object.create(ASS.prototype);
result._properties = obj._properties;
result._styles = new Map<string, Style>();
for (const name of Object.keys(obj._styles)) {
const style = obj._styles[name];
result._styles.set(name, style);
}
result._dialogues = obj._dialogues;
result._attachments = obj._attachments;
result._stylesFormatSpecifier = obj._stylesFormatSpecifier;
result._dialoguesFormatSpecifier = obj._dialoguesFormatSpecifier;
return result;
}
}
+78
View File
@@ -0,0 +1,78 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { registerClass as serializable } from "../serialization";
/**
* The type of an attachment.
*/
export enum AttachmentType {
Font,
Graphic,
}
/**
* This class represents an attachment in a {@link libjass.ASS} script.
*
* @param {string} filename The filename of this attachment.
* @param {number} type The type of this attachment.
*/
@serializable
export class Attachment {
private _contents: string = "";
constructor(private _filename: string, private _type: AttachmentType) { }
/**
* The filename of this attachment.
*
* @type {number}
*/
get filename(): string {
return this._filename;
}
/**
* The type of this attachment.
*
* @type {number}
*/
get type(): AttachmentType {
return this._type;
}
/**
* The contents of this attachment in base64 encoding.
*
* @type {number}
*/
get contents(): string {
return this._contents;
}
/**
* The contents of this attachment in base64 encoding.
*
* @type {number}
*/
set contents(value: string) {
this._contents = value;
}
}
+270
View File
@@ -0,0 +1,270 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ASS } from "./ass";
import { Style } from "./style";
import { valueOrDefault } from "./misc";
import { parse } from "../parser/parse";
import * as parts from "../parts";
import { registerClass as serializable } from "../serialization";
import { debugMode } from "../settings";
import { Map } from "../utility/map";
/**
* This class represents a dialogue in a {@link libjass.ASS} script.
*
* @param {!Map.<string, string>} template The template object that contains the dialogue's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Style"] The name of the default style of this dialogue
* @param {string} template["Start"] The start time
* @param {string} template["End"] The end time
* @param {string} template["Layer"] The layer number
* @param {string} template["Text"] The text of this dialogue
* @param {!libjass.ASS} ass The ASS object to which this dialogue belongs
*/
@serializable
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 = null;
private _containsTransformTag: boolean = false;
constructor(template: Map<string, string>, ass: ASS) {
{
const normalizedTemplate = new Map<string, string>();
template.forEach((value, key) => {
normalizedTemplate.set(key.toLowerCase(), value);
});
template = normalizedTemplate;
}
this._id = ++Dialogue._lastDialogueId;
let styleName = template.get("style");
if (typeof styleName === "string") {
styleName = styleName.replace(/^\*+/, "");
if (styleName.match(/^Default$/i) !== null) {
styleName = "Default";
}
}
let style = (styleName !== undefined) ? ass.styles.get(styleName) : undefined;
if (style === undefined) {
if (debugMode) {
console.warn(`Unrecognized style ${ styleName }. Falling back to "Default"`);
}
style = ass.styles.get("Default");
if (style === undefined) {
throw new Error(`Unrecognized style ${ styleName }. Could not fall back to "Default" style since it doesn't exist.`);
}
}
this._style = style;
const start = template.get("start");
if (typeof start !== "string") {
throw new Error(`Dialogue start time ${ start } is not a string.`);
}
this._start = toTime(start);
const end = template.get("end");
if (typeof end !== "string") {
throw new Error(`Dialogue end time ${ end } is not a string.`);
}
this._end = toTime(end);
this._layer = Math.max(valueOrDefault(template, "layer", parseInt, value => !isNaN(value), "0"), 0);
const text = template.get("text");
if (typeof text !== "string") {
throw new Error(`Dialogue text ${ text } is not a string.`);
}
this._rawPartsString = text;
}
/**
* The unique ID of this dialogue. Auto-generated.
*
* @type {number}
*/
get id(): number {
return this._id;
}
/**
* The start time of this dialogue.
*
* @type {number}
*/
get start(): number {
return this._start;
}
/**
* The end time of this dialogue.
*
* @type {number}
*/
get end(): number {
return this._end;
}
/**
* The default style of this dialogue.
*
* @type {!libjass.Style}
*/
get style(): Style {
return this._style;
}
/**
* The alignment number of this dialogue.
*
* @type {number}
*/
get alignment(): number {
if (this._parts === null) {
this._parsePartsString();
}
return this._alignment;
}
/**
* The layer number of this dialogue.
*
* @type {number}
*/
get layer(): number {
return this._layer;
}
/**
* The {@link libjass.parts} of this dialogue.
*
* @type {!Array.<!libjass.parts.Part>}
*/
get parts(): parts.Part[] {
if (this._parts === null) {
this._parsePartsString();
}
return this._parts!;
}
/**
* Convenience getter for whether this dialogue contains a {\t} tag.
*
* @type {boolean}
*/
get containsTransformTag(): boolean {
if (this._parts === null) {
this._parsePartsString();
}
return this._containsTransformTag;
}
/**
* @return {string} A simple representation of this dialogue's properties and parts.
*/
toString(): string {
return `#${ this._id } [${ this._start.toFixed(3) }-${ this._end.toFixed(3) }] ${ (this._parts !== null) ? this._parts.join(", ") : this._rawPartsString }`;
}
/**
* Parses this dialogue's parts from the raw parts string.
*/
private _parsePartsString(): void {
this._parts = parse(this._rawPartsString, "dialogueParts") as parts.Part[];
this._alignment = this._style.alignment;
this._parts.forEach((part, index) => {
if (part instanceof parts.Alignment) {
this._alignment = part.value;
}
else if (part instanceof parts.Move) {
if (part.t1 === null || part.t2 === null) {
this._parts![index] =
new parts.Move(
part.x1, part.y1, part.x2, part.y2,
0, this._end - this._start
);
}
}
else if (part instanceof parts.Transform) {
if (part.start === null || part.end === null || part.accel === null) {
this._parts![index] =
new parts.Transform(
(part.start === null) ? 0 : part.start,
(part.end === null) ? (this._end - this._start) : part.end,
(part.accel === null) ? 1 : part.accel,
part.tags
);
}
this._containsTransformTag = true;
}
});
if (debugMode) {
const possiblyIncorrectParses = this._parts.filter(part => part instanceof parts.Comment && part.value.indexOf("\\") !== -1);
if (possiblyIncorrectParses.length > 0) {
console.warn(
`Possible incorrect parse:
${ this._rawPartsString }
was parsed as
${ this.toString() }
The possibly incorrect parses are:
${ possiblyIncorrectParses.join("\n") }`
);
}
}
}
}
/**
* Converts this string into the number of seconds it represents. This string must be in the form of hh:mm:ss.MMM
*
* @param {string} str
* @return {number}
*/
function toTime(str: string): number {
return str.split(":").reduce<number>((previousValue, currentValue) => previousValue * 60 + parseFloat(currentValue), 0);
}
+105
View File
@@ -0,0 +1,105 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
/**
* The format of the string passed to {@link libjass.ASS.fromString}
*/
export enum Format {
ASS,
SRT,
}
/**
* The wrapping style defined in the {@link libjass.ScriptProperties}
*/
export enum WrappingStyle {
SmartWrappingWithWiderTopLine = 0,
SmartWrappingWithWiderBottomLine = 3,
EndOfLineWrapping = 1,
NoLineWrapping = 2,
}
/**
* The border style defined in the {@link libjass.Style} properties.
*/
export enum BorderStyle {
Outline = 1,
OpaqueBox = 3,
}
/**
* A property.
*/
export interface Property {
/**
* @type {string}
*/
name: string;
/**
* @type {string}
*/
value: string;
}
/**
* A template object with a particular type.
*/
export interface TypedTemplate {
/**
* @type {string}
*/
type: string;
/**
* @type {!Map.<string, string>}
*/
template: Map<string, string>;
}
/**
* @param {!Map.<string, string>} template
* @param {string} key
* @param {function(string):T} converter
* @param {?function(T):boolean} validator
* @param {T} defaultValue
* @return {T}
*/
export function valueOrDefault<T>(template: Map<string, string>, key: string, converter: (str: string) => T, validator: ((value: T) => boolean) | null, defaultValue: string): T {
const value = template.get(key);
if (value === undefined) {
return converter(defaultValue);
}
try {
const result = converter(value);
if (validator !== null && !validator(result)) {
throw new Error("Validation failed.");
}
return result;
}
catch (ex) {
throw new Error(`Property ${ key } has invalid value ${ value } - ${ ex.stack }`);
}
}
+106
View File
@@ -0,0 +1,106 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { registerClass as serializable } from "../serialization";
import { WrappingStyle } from "./misc";
/**
* This class represents the properties of a {@link libjass.ASS} script.
*/
@serializable
export class ScriptProperties {
private _resolutionX: number;
private _resolutionY: number;
private _wrappingStyle: WrappingStyle;
private _scaleBorderAndShadow: boolean;
/**
* The horizontal script resolution.
*
* @type {number}
*/
get resolutionX(): number {
return this._resolutionX;
}
/**
* The horizontal script resolution.
*
* @type {number}
*/
set resolutionX(value: number) {
this._resolutionX = value;
}
/**
* The vertical script resolution.
*
* @type {number}
*/
get resolutionY(): number {
return this._resolutionY;
}
/**
* The vertical script resolution.
*
* @type {number}
*/
set resolutionY(value: number) {
this._resolutionY = value;
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
*
* @type {number}
*/
get wrappingStyle(): WrappingStyle {
return this._wrappingStyle;
}
/**
* The wrap style. One of the {@link libjass.WrappingStyle} constants.
*
* @type {number}
*/
set wrappingStyle(value: WrappingStyle) {
this._wrappingStyle = value;
}
/**
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
*
* @type {boolean}
*/
get scaleBorderAndShadow(): boolean {
return this._scaleBorderAndShadow;
}
/**
* Whether to scale outline widths and shadow depths from script resolution to video resolution or not. If true, widths and depths are scaled.
*
* @type {boolean}
*/
set scaleBorderAndShadow(value: boolean) {
this._scaleBorderAndShadow = value;
}
}
+334
View File
@@ -0,0 +1,334 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { valueOrDefault, BorderStyle } from "./misc";
import { parse } from "../parser/parse";
import { Color } from "../parts";
import { registerClass as serializable } from "../serialization";
import { Map } from "../utility/map";
/**
* This class represents a single global style declaration in a {@link libjass.ASS} script. The styles can be obtained via the {@link libjass.ASS.styles} property.
*
* @param {!Map.<string, string>} template The template object that contains the style's properties. It is a map of the string values read from the ASS file.
* @param {string} template["Name"] The name of the style
* @param {string} template["Italic"] -1 if the style is italicized
* @param {string} template["Bold"] -1 if the style is bold
* @param {string} template["Underline"] -1 if the style is underlined
* @param {string} template["StrikeOut"] -1 if the style is struck-through
* @param {string} template["Fontname"] The name of the font
* @param {string} template["Fontsize"] The size of the font
* @param {string} template["ScaleX"] The horizontal scaling of the font
* @param {string} template["ScaleY"] The vertical scaling of the font
* @param {string} template["Spacing"] The letter spacing of the font
* @param {string} template["PrimaryColour"] The primary color
* @param {string} template["OutlineColour"] The outline color
* @param {string} template["BackColour"] The shadow color
* @param {string} template["Outline"] The outline thickness
* @param {string} template["Shadow"] The shadow depth
* @param {string} template["Alignment"] The alignment number
* @param {string} template["MarginL"] The left margin
* @param {string} template["MarginR"] The right margin
* @param {string} template["MarginV"] The vertical margin
*/
@serializable
export class Style {
private _name: string;
private _italic: boolean;
private _bold: boolean;
private _underline: boolean;
private _strikeThrough: boolean;
private _fontName: string;
private _fontSize: number;
private _fontScaleX: number;
private _fontScaleY: number;
private _letterSpacing: number;
private _rotationZ: number;
private _primaryColor: Color;
private _secondaryColor: Color;
private _outlineColor: Color;
private _shadowColor: Color;
private _outlineThickness: number;
private _borderStyle: BorderStyle;
private _shadowDepth: number;
private _alignment: number;
private _marginLeft: number;
private _marginRight: number;
private _marginVertical: number;
constructor(template: Map<string, string>) {
{
const normalizedTemplate = new Map<string, string>();
template.forEach((value, key) => {
normalizedTemplate.set(key.toLowerCase(), value);
});
template = normalizedTemplate;
}
const name = template.get("name");
if (typeof name !== "string") {
throw new Error(`Style name ${ name } is not a string.`);
}
this._name = name.replace(/^\*+/, "");
this._italic = !!valueOrDefault(template, "italic", parseFloat, value => !isNaN(value), "0");
this._bold = !!valueOrDefault(template, "bold", parseFloat, value => !isNaN(value), "0");
this._underline = !!valueOrDefault(template, "underline", parseFloat, value => !isNaN(value), "0");
this._strikeThrough = !!valueOrDefault(template, "strikeout", parseFloat, value => !isNaN(value), "0");
this._fontName = valueOrDefault(template, "fontname", str => str, value => value.constructor === String, "sans-serif");
this._fontSize = valueOrDefault(template, "fontsize", parseFloat, value => !isNaN(value), "18");
this._fontScaleX = valueOrDefault(template, "scalex", parseFloat, value => value >= 0, "100") / 100;
this._fontScaleY = valueOrDefault(template, "scaley", parseFloat, value => value >= 0, "100") / 100;
this._letterSpacing = valueOrDefault(template, "spacing", parseFloat, value => value >= 0, "0");
this._rotationZ = valueOrDefault(template, "angle", parseFloat, value => !isNaN(value), "0");
this._primaryColor = valueOrDefault(template, "primarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFFFF");
this._secondaryColor = valueOrDefault(template, "secondarycolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00FFFF00");
this._outlineColor = valueOrDefault(template, "outlinecolour", str => parse(str, "colorWithAlpha") as Color, null, "&H00000000");
this._shadowColor = valueOrDefault(template, "backcolour", str => parse(str, "colorWithAlpha") as Color, null, "&H80000000");
this._outlineThickness = valueOrDefault(template, "outline", parseFloat, value => value >= 0, "2");
this._borderStyle = valueOrDefault(template, "borderstyle", parseInt, value => (BorderStyle as any)[(BorderStyle as any)[value]] === value, "1");
this._shadowDepth = valueOrDefault(template, "shadow", parseFloat, value => value >= 0, "3");
this._alignment = valueOrDefault(template, "alignment", parseInt, value => value >= 1 && value <= 9, "2");
this._marginLeft = valueOrDefault(template, "marginl", parseFloat, value => !isNaN(value), "20");
this._marginRight = valueOrDefault(template, "marginr", parseFloat, value => !isNaN(value), "20");
this._marginVertical = valueOrDefault(template, "marginv", parseFloat, value => !isNaN(value), "20");
}
/**
* The name of this style.
*
* @type {string}
*/
get name(): string {
return this._name;
}
/**
* Whether this style is italicized or not.
*
* @type {string}
*/
get italic(): boolean {
return this._italic;
}
/**
* Whether this style is bold or not.
*
* @type {boolean}
*/
get bold(): boolean {
return this._bold;
}
/**
* Whether this style is underlined or not.
*
* @type {boolean}
*/
get underline(): boolean {
return this._underline;
}
/**
* Whether this style is struck-through or not.
*
* @type {boolean}
*/
get strikeThrough(): boolean {
return this._strikeThrough;
}
/**
* The name of this style's font.
*
* @type {string}
*/
get fontName(): string {
return this._fontName;
}
/**
* The size of this style's font.
*
* @type {number}
*/
get fontSize(): number {
return this._fontSize;
}
/**
* The horizontal scaling of this style's font.
*
* @type {number}
*/
get fontScaleX(): number {
return this._fontScaleX;
}
/**
* The vertical scaling of this style's font.
*
* @type {number}
*/
get fontScaleY(): number {
return this._fontScaleY;
}
/**
* The letter spacing scaling of this style's font.
*
* @type {number}
*/
get letterSpacing(): number {
return this._letterSpacing;
}
/**
* The default Z-rotation of this style.
*
* @type {number}
*/
get rotationZ(): number {
return this._rotationZ;
}
/**
* The color of this style's font.
*
* @type {!libjass.parts.Color}
*/
get primaryColor(): Color {
return this._primaryColor;
}
/**
* The alternate color of this style's font, used in karaoke.
*
* @type {!libjass.parts.Color}
*/
get secondaryColor(): Color {
return this._secondaryColor;
}
/**
* The color of this style's outline.
*
* @type {!libjass.parts.Color}
*/
get outlineColor(): Color {
return this._outlineColor;
}
/**
* The color of this style's shadow.
*
* @type {!libjass.parts.Color}
*/
get shadowColor(): Color {
return this._shadowColor;
}
/**
* The thickness of this style's outline.
*
* @type {number}
*/
get outlineThickness(): number {
return this._outlineThickness;
}
/**
* The border style of this style.
*
* @type {number}
*/
get borderStyle(): BorderStyle {
return this._borderStyle;
}
/**
* The depth of this style's shadow.
*
* @type {number}
*/
get shadowDepth(): number {
return this._shadowDepth;
}
/**
* The alignment of dialogues of this style.
*
* @type {number}
*/
get alignment(): number {
return this._alignment;
}
/**
* The left margin of dialogues of this style.
*
* @type {number}
*/
get marginLeft(): number {
return this._marginLeft;
}
/**
* The right margin of dialogues of this style.
*
* @type {number}
*/
get marginRight(): number {
return this._marginRight;
}
/**
* The vertical margin of dialogues of this style.
*
* @type {number}
*/
get marginVertical(): number {
return this._marginVertical;
}
}
+252
View File
@@ -0,0 +1,252 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Map<K, V> {
/**
* @param {K} key
* @return {?V}
*/
get(key: K): V | undefined;
/**
* @param {K} key
* @return {boolean}
*/
has(key: K): boolean;
/**
* @param {K} key
* @param {V} value
* @return {libjass.Map.<K, V>} This map
*/
set(key: K, value: V): this;
/**
* @param {K} key
* @return {boolean} true if the key was present before being deleted, false otherwise
*/
delete(key: K): boolean;
/**
*/
clear(): void;
/**
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void;
/**
* @type {number}
*/
size: number;
}
/**
* Map implementation for browsers that don't support it. Only supports keys which are of Number or String type, or which have a property called "id".
*
* Keys and values are stored as properties of an object, with property names derived from the key type.
*
* @param {!Array.<!Array.<*>>=} iterable Only an array of elements (where each element is a 2-tuple of key and value) is supported.
*/
class SimpleMap<K, V> implements Map<K, V> {
private _keys: { [key: string]: K };
private _values: { [key: string]: V };
private _size: number;
constructor(iterable?: [K, V][]) {
this.clear();
if (iterable === undefined) {
return;
}
if (!Array.isArray(iterable)) {
throw new Error("Non-array iterables are not supported by the SimpleMap constructor.");
}
for (const element of iterable) {
this.set(element[0], element[1]);
}
}
/**
* @param {K} key
* @return {?V}
*/
get(key: K): V | undefined {
const property = this._keyToProperty(key);
if (property === null) {
return undefined;
}
return this._values[property];
}
/**
* @param {K} key
* @return {boolean}
*/
has(key: K): boolean {
const 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): this {
const property = this._keyToProperty(key);
if (property === null) {
throw new Error("This Map implementation only supports Number and String keys, or keys with an id property.");
}
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 {
const property = this._keyToProperty(key);
if (property === null) {
return false;
}
const result = property in this._keys;
if (result) {
delete this._keys[property];
delete this._values[property];
this._size--;
}
return result;
}
/**
*/
clear(): void {
this._keys = Object.create(null);
this._values = Object.create(null);
this._size = 0;
}
/**
* @param {function(V, K, libjass.Map.<K, V>)} callbackfn A function that is called with each key and value in the map.
* @param {*} thisArg
*/
forEach(callbackfn: (value: V, index: K, map: this) => void, thisArg?: any): void {
for (const property of Object.keys(this._keys)) {
callbackfn.call(thisArg, this._values[property], this._keys[property], this);
}
}
/**
* @type {number}
*/
get size(): number {
return this._size;
}
/**
* Converts the given key into a property name for the internal map.
*
* @param {K} key
* @return {?string}
*/
private _keyToProperty(key: K): string | null {
if (typeof key === "number") {
return `#${ key }`;
}
if (typeof key === "string") {
return `'${ key }`;
}
if ((key as any).id !== undefined) {
return `!${ (key as any).id }`;
}
return null;
}
}
/**
* Set to the global implementation of Map if the environment has one, else set to {@link ./utility/map.SimpleMap}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/map.SimpleMap} to be used even if a global Map is present.
*
* @type {function(new:Map, !Array.<!Array.<*>>=)}
*/
export var Map: {
new <K, V>(iterable?: [K, V][]): Map<K, V>;
prototype: Map<any, any>;
} = global.Map || SimpleMap;
if (typeof Map.prototype.forEach !== "function" || (() => {
try {
return new Map([[1, "foo"], [2, "bar"]]).size !== 2;
}
catch (ex) {
return true;
}
})()) {
Map = SimpleMap;
}
declare var global: {
Map?: typeof Map;
};
/**
* Sets the Map implementation used by libjass to the provided one. If null, {@link ./utility/map.SimpleMap} is used.
*
* @param {?function(new:Map, !Array.<!Array.<*>>=)} value
*/
export function setImplementation(value: typeof Map | null): void {
if (value !== null) {
Map = value;
}
else {
Map = SimpleMap;
}
}
+33
View File
@@ -0,0 +1,33 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Adds properties of the given mixins' prototypes to the given class's prototype.
*
* @param {!*} clazz
* @param {!Array.<*>} mixins
*/
export function mixin(clazz: any, mixins: any[]): void {
for (const mixin of mixins) {
for (const name of Object.getOwnPropertyNames(mixin.prototype)) {
clazz.prototype[name] = mixin.prototype[name];
}
}
}
+535
View File
@@ -0,0 +1,535 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Thenable<T> {
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
then: ThenableThen<T>;
}
export interface ThenableThen<T> {
/** @type {function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))} */
(this: Thenable<T>, resolve: ((resolution: T | Thenable<T>) => void) | undefined, reject: ((reason: any) => void) | undefined): void;
}
export interface Promise<T> extends Thenable<T> {
/**
* @param {function(T):!Thenable.<U>} onFulfilled
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled: (value: T) => Thenable<U> | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
/**
* @param {function(T):U} onFulfilled
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled: (value: T) => U | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U>;
/**
* @param {function(*):(T|!Thenable.<T>)} onRejected
* @return {!Promise.<T>}
*/
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T>;
}
// Based on https://github.com/petkaantonov/bluebird/blob/1b1467b95442c12378d0ea280ede61d640ab5510/src/schedule.js
const enqueueJob: (callback: () => void) => void = (function () {
const MutationObserver = global.MutationObserver || global.WebkitMutationObserver;
if (global.process !== undefined && typeof global.process.nextTick === "function") {
const process = global.process;
return (callback: () => void) => {
process.nextTick(callback);
};
}
else if (MutationObserver !== undefined) {
const pending: (() => void)[] = [];
let currentlyPending = false;
const div = document.createElement("div");
const observer = new MutationObserver(() => {
const processing = pending.splice(0, pending.length);
for (const callback of processing) {
callback();
}
currentlyPending = false;
if (pending.length > 0) {
div.classList.toggle("foo");
currentlyPending = true;
}
});
observer.observe(div, { attributes: true });
return (callback: () => void) => {
pending.push(callback);
if (!currentlyPending) {
div.classList.toggle("foo");
currentlyPending = true;
}
};
}
else {
return (callback: () => void) => setTimeout(callback, 0);
}
})();
/**
* Promise implementation for browsers that don't support it.
*
* @param {function(function(T|!Thenable.<T>), function(*))} executor
*/
class SimplePromise<T> {
private _state: SimplePromiseState = SimplePromiseState.PENDING;
private _fulfillReactions: FulfilledPromiseReaction<T, any>[] = [];
private _rejectReactions: RejectedPromiseReaction<any>[] = [];
private _fulfilledValue: T | null = null;
private _rejectedReason: any = null;
constructor(executor: (resolve: (resolution: T | Thenable<T>) => void, reject: (reason: any) => void) => void) {
if (typeof executor !== "function") {
throw new TypeError(`typeof executor !== "function"`);
}
const { resolve, reject } = this._createResolvingFunctions();
try {
executor(resolve, reject);
}
catch (ex) {
reject(ex);
}
}
/**
* @param {?function(T):(U|!Thenable.<U>)} onFulfilled
* @param {?function(*):(U|!Thenable.<U>)} onRejected
* @return {!Promise.<U>}
*/
then<U>(onFulfilled: ((value: T) => U | Thenable<U>) | undefined, onRejected?: (reason: any) => U | Thenable<U>): Promise<U> {
const resultCapability = new DeferredPromise<U>();
if (typeof onFulfilled !== "function") {
onFulfilled = (value: T) => value as any as U;
}
if (typeof onRejected !== "function") {
onRejected = (reason: any): U => { throw reason; };
}
const fulfillReaction: FulfilledPromiseReaction<T, U> = {
capabilities: resultCapability,
handler: onFulfilled,
};
const rejectReaction: RejectedPromiseReaction<U> = {
capabilities: resultCapability,
handler: onRejected,
};
switch (this._state) {
case SimplePromiseState.PENDING:
this._fulfillReactions.push(fulfillReaction);
this._rejectReactions.push(rejectReaction);
break;
case SimplePromiseState.FULFILLED:
this._enqueueFulfilledReactionJob(fulfillReaction, this._fulfilledValue!);
break;
case SimplePromiseState.REJECTED:
this._enqueueRejectedReactionJob(rejectReaction, this._rejectedReason);
break;
}
return resultCapability.promise;
}
/**
* @param {function(*):(T|!Thenable.<T>)} onRejected
* @return {!Promise.<T>}
*/
catch(onRejected: (reason: any) => T | Thenable<T>): Promise<T> {
return this.then(undefined, onRejected);
}
/**
* @param {T|!Thenable.<T>} value
* @return {!Promise.<T>}
*/
static resolve<T>(value: T | Thenable<T>): Promise<T> {
if (value instanceof SimplePromise) {
return value;
}
return new Promise<T>(resolve => resolve(value));
}
/**
* @param {*} reason
* @return {!Promise.<T>}
*/
static reject<T>(reason: any): Promise<T> {
return new Promise<T>((/* ujs:unreferenced */ resolve, reject) => reject(reason));
}
/**
* @param {!Array.<T|!Thenable.<T>>} values
* @return {!Promise.<!Array.<T>>}
*/
static all<T>(values: (T | Thenable<T>)[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
const result: T[] = [];
let numUnresolved = values.length;
if (numUnresolved === 0) {
resolve(result);
return;
}
values.forEach((value, index) => Promise.resolve(value).then(value => {
result[index] = value;
numUnresolved--;
if (numUnresolved === 0) {
resolve(result);
}
}, reject));
});
}
/**
* @param {!Array.<T|!Thenable.<T>>} values
* @return {!Promise.<T>}
*/
static race<T>(values: (T | Thenable<T>)[]): Promise<T> {
return new Promise<T>((resolve, reject) => {
for (const value of values) {
Promise.resolve(value).then(resolve, reject);
}
});
}
/**
* @return {{ resolve(T|!Thenable.<T>), reject(*) }}
*/
private _createResolvingFunctions(): { resolve(resolution: T | Thenable<T>): void; reject(reason: any): void; } {
let alreadyResolved = false;
const resolve = (resolution: T | Thenable<T>): void => {
if (alreadyResolved) {
return;
}
alreadyResolved = true;
if (resolution === this) {
this._reject(new TypeError(`resolution === this`));
return;
}
if (resolution === null || (typeof resolution !== "object" && typeof resolution !== "function")) {
this._fulfill(resolution as T);
return;
}
try {
var then = (resolution as Thenable<T>).then;
}
catch (ex) {
this._reject(ex);
return;
}
if (typeof then !== "function") {
this._fulfill(resolution as T);
return;
}
enqueueJob(() => this._resolveWithThenable(resolution as Thenable<T>, then));
};
const reject = (reason: any): void => {
if (alreadyResolved) {
return;
}
alreadyResolved = true;
this._reject(reason);
};
return { resolve, reject };
}
/**
* @param {!Thenable.<T>} thenable
* @param {{function(this:!Thenable.<T>, function(T|!Thenable.<T>), function(*))}} then
*/
private _resolveWithThenable(thenable: Thenable<T>, then: ThenableThen<T>): void {
const { resolve, reject } = this._createResolvingFunctions();
try {
then.call(thenable, resolve, reject);
}
catch (ex) {
reject(ex);
}
}
/**
* @param {T} value
*/
private _fulfill(value: T): void {
const reactions = this._fulfillReactions;
this._fulfilledValue = value;
this._fulfillReactions = [];
this._rejectReactions = [];
this._state = SimplePromiseState.FULFILLED;
for (const reaction of reactions) {
this._enqueueFulfilledReactionJob(reaction, value);
}
}
/**
* @param {*} reason
*/
private _reject(reason: any): void {
const reactions = this._rejectReactions;
this._rejectedReason = reason;
this._fulfillReactions = [];
this._rejectReactions = [];
this._state = SimplePromiseState.REJECTED;
for (const reaction of reactions) {
this._enqueueRejectedReactionJob(reaction, reason);
}
}
/**
* @param {!FulfilledPromiseReaction.<T, *>} reaction
* @param {T} value
*/
private _enqueueFulfilledReactionJob(reaction: FulfilledPromiseReaction<T, any>, value: T): void {
enqueueJob(() => {
const { capabilities: { resolve, reject }, handler } = reaction;
let handlerResult: any | Thenable<any>;
try {
handlerResult = handler(value);
}
catch (ex) {
reject(ex);
return;
}
resolve(handlerResult);
});
}
/**
* @param {!RejectedPromiseReaction.<*>} reaction
* @param {*} reason
*/
private _enqueueRejectedReactionJob(reaction: RejectedPromiseReaction<any>, reason: any): void {
enqueueJob(() => {
const { capabilities: { resolve, reject }, handler } = reaction;
let handlerResult: any | Thenable<any>;
try {
handlerResult = handler(reason);
}
catch (ex) {
reject(ex);
return;
}
resolve(handlerResult);
});
}
}
/**
* Set to the global implementation of Promise if the environment has one, else set to {@link ./utility/promise.SimplePromise}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/promise.SimplePromise} to be used even if a global Promise is present.
*
* @type {function(new:Promise)}
*/
export var Promise: {
new <T>(init: (resolve: (value: T | Thenable<T>) => void, reject: (reason: any) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | Thenable<T>): Promise<T>;
reject<T>(reason: any): Promise<T>;
all<T>(values: (T | Thenable<T>)[]): Promise<T[]>;
race<T>(values: (T | Thenable<T>)[]): Promise<T>;
} = global.Promise || SimplePromise;
declare var global: {
Promise?: typeof Promise;
MutationObserver?: typeof MutationObserver;
WebkitMutationObserver?: typeof MutationObserver;
process?: {
nextTick(callback: () => void): void;
}
};
interface FulfilledPromiseReaction<T, U> {
/** @type {!libjass.DeferredPromise.<U>} */
capabilities: DeferredPromise<U>;
/**
* @param {T} value
* @return {U|!Thenable.<U>}
*/
handler(value: T): U | Thenable<U>;
}
interface RejectedPromiseReaction<U> {
/** @type {!libjass.DeferredPromise.<U>} */
capabilities: DeferredPromise<U>;
/**
* @param {*} reason
* @return {U|!Thenable.<U>}
*/
handler(reason: any): U | Thenable<U>;
}
/**
* The state of the {@link ./utility/promise.SimplePromise}
*/
enum SimplePromiseState {
PENDING = 0,
FULFILLED = 1,
REJECTED = 2,
}
/**
* Sets the Promise implementation used by libjass to the provided one. If null, {@link ./utility/promise.SimplePromise} is used.
*
* @param {?function(new:Promise)} value
*/
export function setImplementation(value: typeof Promise | null): void {
if (value !== null) {
Promise = value;
}
else {
Promise = SimplePromise;
}
}
/**
* A deferred promise.
*/
export class DeferredPromise<T> {
private _promise: Promise<T>;
/**
* @type {function(T|!Thenable.<T>)}
*/
resolve: (value: T | Thenable<T>) => void;
/**
* @type {function(*)} reason
*/
reject: (reason: any) => void;
constructor() {
this._promise = new Promise<T>((resolve, reject) => {
Object.defineProperties(this, {
resolve: { value: resolve, enumerable: true },
reject: { value: reject, enumerable: true },
});
});
}
/**
* @type {!Promise.<T>}
*/
get promise(): Promise<T> {
return this._promise;
}
}
/**
* Returns a promise that resolves to the first (in iteration order) promise that fulfills, and rejects if all the promises reject.
*
* @param {!Array.<!Promise.<T>>} promises
* @return {!Promise.<T>}
*/
export function first<T>(promises: Promise<T>[]): Promise<T> {
return first_rec(promises, []);
}
/**
* @param {!Array.<!Promise.<T>>} promises
* @param {!Array.<*>} previousRejections
* @return {!Promise.<T>}
*/
function first_rec<T>(promises: Promise<T>[], previousRejections: any[]): Promise<T> {
if (promises.length === 0) {
return Promise.reject(previousRejections);
}
const [head, ...tail] = promises;
return head.catch(reason => first_rec(tail, previousRejections.concat(reason)));
}
/**
* Returns a promise that resolves to the first (in time order) promise that fulfills, and rejects if all the promises reject.
*
* @param {!Array.<!Promise.<T>>} promises
* @return {!Promise.<T>}
*/
export function any<T>(promises: Promise<T>[]): Promise<T> {
return new Promise<T>((resolve, reject) =>
Promise.all<any>(promises.map(promise => promise.then(resolve, reason => reason))).then(reject));
}
/**
* Returns a promise that runs the given callback when the promise has resolved regardless of whether it fulfilled or rejected.
*
* @param {!Promise.<T>} promise
* @param {function()} body
* @return {!Promise.<T>}
*/
export function lastly<T>(promise: Promise<T>, body: () => void): Promise<T> {
return promise.then<any>(value => {
body();
return value;
}, reason => {
body();
throw reason;
});
}
+196
View File
@@ -0,0 +1,196 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Set<T> {
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): this;
/**
*/
clear(): void;
/**
* @param {T} value
* @return {boolean}
*/
has(value: T): boolean;
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void;
/**
* @type {number}
*/
size: number;
}
/**
* Set implementation for browsers that don't support it. Only supports Number and String elements.
*
* Elements are stored as properties of an object, with names derived from their type.
*
* @param {!Array.<T>=} iterable Only an array of values is supported.
*/
class SimpleSet<T> implements Set<T> {
private _elements: { [key: string]: T };
private _size: number;
constructor(iterable?: T[]) {
this.clear();
if (iterable === undefined) {
return;
}
if (!Array.isArray(iterable)) {
throw new Error("Non-array iterables are not supported by the SimpleSet constructor.");
}
for (const value of iterable) {
this.add(value);
}
}
/**
* @param {T} value
* @return {libjass.Set.<T>} This set
*/
add(value: T): this {
const property = this._toProperty(value);
if (property === null) {
throw new Error("This Set implementation only supports Number and String values.");
}
if (!(property in this._elements)) {
this._size++;
}
this._elements[property] = value;
return this;
}
/**
*/
clear(): void {
this._elements = Object.create(null);
this._size = 0;
}
/**
* @param {T} value
* @return {boolean}
*/
has(value: T): boolean {
const property = this._toProperty(value);
if (property === null) {
return false;
}
return property in this._elements;
}
/**
* @param {function(T, T, libjass.Set.<T>)} callbackfn A function that is called with each value in the set.
* @param {*} thisArg
*/
forEach(callbackfn: (value: T, index: T, set: this) => void, thisArg?: any): void {
for (const property of Object.keys(this._elements)) {
const element = this._elements[property];
callbackfn.call(thisArg, element, element, this);
}
}
/**
* @type {number}
*/
get size(): number {
return this._size;
}
/**
* Converts the given value into a property name for the internal map.
*
* @param {T} value
* @return {?string}
*/
private _toProperty(value: T): string | null {
if (typeof value === "number") {
return `#${ value }`;
}
if (typeof value === "string") {
return `'${ value }`;
}
return null;
}
}
/**
* Set to the global implementation of Set if the environment has one, else set to {@link ./utility/set.SimpleSet}
*
* Can be set to a value using {@link libjass.configure}
*
* Set it to null to force {@link ./utility/set.SimpleSet} to be used even if a global Set is present.
*
* @type {function(new:Set, !Array.<T>=)}
*/
export var Set: {
new <T>(iterable?: T[]): Set<T>;
prototype: Set<any>;
} = global.Set || SimpleSet;
if (typeof Set.prototype.forEach !== "function" || (() => {
try {
return new Set([1, 2]).size !== 2;
}
catch (ex) {
return true;
}
})()) {
Set = SimpleSet;
}
declare var global: {
Set?: typeof Set;
};
/**
* Sets the Set implementation used by libjass to the provided one. If null, {@link ./utility/set.SimpleSet} is used.
*
* @param {?function(new:Set, !Array.<T>=)} value
*/
export function setImplementation(value: typeof Set | null): void {
if (value !== null) {
Set = value;
}
else {
Set = SimpleSet;
}
}
+220
View File
@@ -0,0 +1,220 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { serialize, deserialize } from "../serialization";
import { Map } from "../utility/map";
import { Promise, DeferredPromise } from "../utility/promise";
import { WorkerCommands } from "./commands";
import { getWorkerCommandHandler, registerWorkerCommand } from "./misc";
/**
* Represents a communication channel between the host and the web worker. An instance of this class is created by calling {@link libjass.webworker.createWorker}
*/
export interface WorkerChannel {
/**
* Sends a request to the other side to execute the given command with the given parameters.
*
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>} A promise that will get resolved when the other side computes the result
*/
request(command: WorkerCommands, parameters: any): Promise<any>;
}
/**
* The signature of a handler registered to handle a particular command in {@link libjass.webworker.WorkerCommands}
*/
export interface WorkerCommandHandler {
(parameters: any): Promise<any>;
}
/**
* The interface implemented by a communication channel to the other side.
*/
export interface WorkerCommunication {
/**
* @param {"message"} type
* @param {function(!MessageEvent): *} listener
* @param {?boolean} useCapture
*/
addEventListener(type: "message", listener: (ev: MessageEvent) => any, useCapture?: boolean): void;
/**
* @param {string} type
* @param {!EventListener} listener
* @param {?boolean} useCapture
*/
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
/**
* @param {*} message
*/
postMessage(message: any): void;
}
/**
* The interface implemented by a request sent to the other side of the communication channel.
*/
interface WorkerRequestMessage {
/**
* An internal identifier for this request. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* The command type of this request.
*
* @type {number}
*/
command: WorkerCommands;
/**
* Any parameters serialized with this request.
*
* @type {*}
*/
parameters: any;
}
/**
* The interface implemented by a response received from the other side of the communication channel.
*/
interface WorkerResponseMessage {
/**
* An internal identifier for this response. Used to connect responses to their corresponding requests.
*
* @type {number}
*/
requestId: number;
/**
* Set if the computation of this response resulted in an error.
*
* @type {*}
*/
error: any;
/**
* The result of computing this response.
*
* @type {*}
*/
result: any;
}
/**
* Internal implementation of libjass.webworker.WorkerChannel
*
* @param {!*} comm The object used to talk to the other side of the channel. When created by the main thread, this is the Worker object.
* When created by the web worker, this is its global object.
*/
export class WorkerChannelImpl implements WorkerChannel {
private static _lastRequestId: number = -1;
private _pendingRequests = new Map<number, DeferredPromise<any>>();
constructor(private _comm: WorkerCommunication) {
this._comm.addEventListener("message", ev => this._onMessage(ev.data as string), false);
}
/**
* @param {number} command
* @param {*} parameters
* @return {!Promise.<*>}
*/
request(command: WorkerCommands, parameters: any): Promise<any> {
const deferred = new DeferredPromise<any>();
const requestId = ++WorkerChannelImpl._lastRequestId;
this._pendingRequests.set(requestId, deferred);
const requestMessage: WorkerRequestMessage = { requestId, command, parameters };
this._comm.postMessage(serialize(requestMessage));
return deferred.promise;
}
/**
* @param {number} requestId
*/
cancelRequest(requestId: number): void {
const deferred = this._pendingRequests.get(requestId);
if (deferred === undefined) {
return;
}
this._pendingRequests.delete(requestId);
deferred.reject(new Error("Cancelled."));
}
/**
* @param {!WorkerResponseMessage} message
*/
private _respond(message: WorkerResponseMessage): void {
let { requestId, error, result } = message;
if (error instanceof Error) {
error = { message: error.message, stack: error.stack };
}
this._comm.postMessage(serialize({ command: WorkerCommands.Response, requestId, error, result }));
}
/**
* @param {string} rawMessage
*/
private _onMessage(rawMessage: string): void {
const message = deserialize(rawMessage) as { command: WorkerCommands };
if (message.command === WorkerCommands.Response) {
const responseMessage = message as any as WorkerResponseMessage;
const deferred = this._pendingRequests.get(responseMessage.requestId);
if (deferred !== undefined) {
this._pendingRequests.delete(responseMessage.requestId);
if (responseMessage.error === null) {
deferred.resolve(responseMessage.result);
}
else {
deferred.reject(responseMessage.error);
}
}
}
else {
const requestMessage = message as WorkerRequestMessage;
const requestId = requestMessage.requestId;
const commandCallback = getWorkerCommandHandler(requestMessage.command);
if (commandCallback === undefined) {
this._respond({ requestId, error: new Error(`No handler registered for command ${ requestMessage.command }`), result: null });
return;
}
commandCallback(requestMessage.parameters).then<WorkerResponseMessage>(
result => ({ requestId, error: null, result }),
error => ({ requestId, error, result: null })
).then(responseMessage => this._respond(responseMessage));
}
}
}
registerWorkerCommand(WorkerCommands.Ping, parameters => Promise.resolve(null));
+28
View File
@@ -0,0 +1,28 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The commands that can be sent to or from a web worker.
*/
export enum WorkerCommands {
Response = 0,
Parse = 1,
Ping = 2,
}
+59
View File
@@ -0,0 +1,59 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WorkerChannel, WorkerChannelImpl } from "./channel";
export { WorkerChannel } from "./channel";
export { WorkerCommands } from "./commands";
/**
* Indicates whether web workers are supposed in this environment or not.
*
* @type {boolean}
*/
export const supported = typeof Worker !== "undefined";
const _scriptNode = (typeof document !== "undefined" && document.currentScript !== undefined) ? (document.currentScript as HTMLScriptElement) : null;
/**
* Create a new web worker and returns a {@link libjass.webworker.WorkerChannel} to it.
*
* @param {string=} scriptPath The path to libjass.js to be loaded in the web worker. If the browser supports document.currentScript, the parameter is optional and, if not provided,
* the path will be determined from the src attribute of the <script> element that contains the currently running copy of libjass.js
* @return {!libjass.webworker.WorkerChannel} A communication channel to the new web worker.
*/
export function createWorker(scriptPath?: string): WorkerChannel {
if (scriptPath === undefined) {
if (_scriptNode === null) {
throw new Error("Could not auto-detect path of libjass.js, and explicit path was not passed in.");
}
scriptPath = _scriptNode.src;
}
return new WorkerChannelImpl(new Worker(scriptPath));
}
declare const global: any;
if (typeof WorkerGlobalScope !== "undefined" && global instanceof WorkerGlobalScope) {
// This is a web worker. Set up a channel to talk back to the main thread.
new WorkerChannelImpl(global);
}
+46
View File
@@ -0,0 +1,46 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Map } from "../utility/map";
import { WorkerCommands } from "./commands";
import { WorkerCommandHandler } from "./channel";
const workerCommands = new Map<WorkerCommands, WorkerCommandHandler>();
/**
* Registers a handler for the given worker command.
*
* @param {number} command The command that this handler will handle. One of the {@link libjass.webworker.WorkerCommands} constants.
* @param {function(*, function(*, *))} handler The handler. A function of the form (parameters: *, response: function(error: *, result: *): void): void
*/
export function registerWorkerCommand(command: WorkerCommands, handler: WorkerCommandHandler): void {
workerCommands.set(command, handler);
}
/**
* Gets the handler for the given worker command.
*
* @param {number} command
* @return {?function(*, function(*, *))}
*/
export function getWorkerCommandHandler(command: WorkerCommands): WorkerCommandHandler | undefined {
return workerCommands.get(command);
}
+37
View File
@@ -0,0 +1,37 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface WorkerGlobalScope {
/**
* @param {*} message
*/
postMessage(message: any): void;
/**
* @param {string} type
* @param {function(*)} listener
* @param {boolean} useCapture
*/
addEventListener(type: string, listener: (message: any) => void, useCapture: boolean): void;
}
declare var WorkerGlobalScope: {
prototype: WorkerGlobalScope;
new (): WorkerGlobalScope;
};
+84
View File
@@ -0,0 +1,84 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "intern/chai!assert", "intern/dojo/node!fs", "intern/dojo/node!path", "intern/dojo/node!sax"], function (tdd, assert, fs, path, sax) {
tdd.suite("Documentation tests", function () {
var ids = [];
var hrefs = [];
tdd.before(function () {
var parser = sax.parser(true);
parser.onopentag = function (node) {
if (node.name === "a" && node.attributes.href[0] === "#") {
hrefs.push(node.attributes.href.substr(1));
}
if (node.attributes.id !== undefined) {
ids.push(node.attributes.id);
}
};
parser.onerror = function (error) { throw error; };
parser.write(fs.readFileSync(require.toUrl("../libjass-gh-pages/api.xhtml"), { encoding: "utf8" })).close();
});
tdd.test("api.xhtml", function () {
var brokenLinks = hrefs.filter(function (href) {
return ids.indexOf(href) === -1;
});
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
tdd.test("README.md", function () {
var regex = /\[[^\]]+\]\(http:\/\/arnavion\.github\.io\/libjass\/api\.xhtml#([^)]+)\)/g;
var brokenLinks = [];
var readme = fs.readFileSync(require.toUrl("./README.md"), { encoding: "utf8" });
var match;
while ((match = regex.exec(readme)) !== null) {
if (ids.indexOf(match[1]) === -1) {
brokenLinks.push(match[1]);
}
}
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
tdd.test("{@link} tags in the source", function () {
var regex = /\{@link ([^}]+)\}/g;
var brokenLinks = [];
var libjassJs = fs.readFileSync(require.toUrl("./lib/libjass.js"), { encoding: "utf8" });
var match;
while ((match = regex.exec(libjassJs)) !== null) {
if (ids.indexOf(match[1]) === -1) {
brokenLinks.push(match[1]);
}
}
assert.equal(brokenLinks.length, 0, "Broken link(s): " + brokenLinks.map(function (href) { return '"' + href + '"'; }).join(", "));
});
});
});
+18
View File
@@ -0,0 +1,18 @@
[Script Info]
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 1280
PlayResY: 720
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,500,&H7F0000FF,&H00FFFFFF,&H7F000000,&H7F000000,0,0,0,0,100,100,0,0,1,10,0,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:10:00.00,Default,,0,0,0,,X
+31
View File
@@ -0,0 +1,31 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("alpha", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/alpha/alpha.ass", 1280, 720, "rgb(47, 163, 254)");
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1, require.toUrl("./alpha-1.png")); })
.then(function (testPage) { return testPage.done(); });
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+154
View File
@@ -0,0 +1,154 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
define(["intern!tdd", "intern/chai!assert", "require", "intern/dojo/node!leadfoot/helpers/pollUntil"], function (tdd, assert, require, pollUntil) {
tdd.suite("Auto clock", function () {
tdd.test("Operations", function () {
this.remote.session.setExecuteAsyncTimeout(10000);
return this.remote
.get(require.toUrl("tests/support/browser-test-page.html"))
.then(pollUntil('return (document.readyState === "complete") ? true : null;', 100))
.execute(function () {
window.driverStartTime = 0;
window.driverStartTimeAt = new Date();
window.driver = function () {
return (new Date() - driverStartTimeAt) / 1000 + driverStartTime;
};
window.clock = new libjass.renderers.AutoClock(driver);
window.events = [];
clock.addEventListener(libjass.renderers.ClockEvent.Play, function () {
events.push("play");
});
clock.addEventListener(libjass.renderers.ClockEvent.Tick, function () {
events.push("tick");
});
clock.addEventListener(libjass.renderers.ClockEvent.Pause, function () {
events.push("pause");
});
clock.addEventListener(libjass.renderers.ClockEvent.Stop, function () {
events.push("stop");
});
return { enabled: clock.enabled, paused: clock.paused, rate: clock.rate, events: events.slice() };
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, true);
assert.strictEqual(clock.rate, 1);
assert.deepEqual(clock.events, []);
})
.executeAsync(function (callback) {
clock.play();
setTimeout(function () {
callback({ enabled: clock.enabled, paused: clock.paused, events: events.slice() });
}, 1000);
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, false);
assert(clock.events.length >= 2);
assert.strictEqual(clock.events[0], "play");
for (var i = 1; i < clock.events.length; i++) {
assert.strictEqual(clock.events[i], "tick");
}
})
.execute(function () {
events = [];
clock.disable();
return { enabled: clock.enabled, paused: clock.paused, events: events.slice() };
})
.then(function (clock) {
assert.strictEqual(clock.enabled, false);
assert.strictEqual(clock.paused, true);
assert.deepEqual(clock.events, ["pause", "stop"]);
})
.execute(function () {
events = [];
clock.enable();
return { enabled: clock.enabled, paused: clock.paused, events: events.slice() };
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, true);
assert.deepEqual(clock.events, []);
})
.executeAsync(function (callback) {
setTimeout(function () {
callback({ enabled: clock.enabled, paused: clock.paused, events: events.slice() });
}, 1000);
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, false);
assert(clock.events.length >= 2);
assert.strictEqual(clock.events[0], "play");
for (var i = 1; i < clock.events.length; i++) {
assert.strictEqual(clock.events[i], "tick");
}
})
.execute(function () {
events = [];
driverStartTime = 30000;
clock.seeking();
var result = { enabled: clock.enabled, paused: clock.paused, currentTime: clock.currentTime, events: events.slice() };
events = [];
return result;
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, true);
assert(clock.currentTime >= 30);
assert.deepEqual(clock.events, ["pause", "stop", "play", "tick", "pause"]);
})
.executeAsync(function (callback) {
setTimeout(function () {
callback({ enabled: clock.enabled, paused: clock.paused, currentTime: clock.currentTime, events: events.slice() });
}, 1000);
})
.then(function (clock) {
assert.strictEqual(clock.enabled, true);
assert.strictEqual(clock.paused, false);
assert(clock.currentTime >= 31);
assert(clock.events.length >= 2);
assert.strictEqual(clock.events[0], "play");
for (var i = 1; i < clock.events.length; i++) {
assert.strictEqual(clock.events[i], "tick");
}
});
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

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

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+18
View File
@@ -0,0 +1,18 @@
[Script Info]
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 1280
PlayResY: 720
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,180,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,10,6,2,75,75,75,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:10:00.00,Default,,0,0,0,,xxx{\fscx50}xxx{\fscx\fscy50}xxx{\fscx50\fscy50}xxx

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