commit e00fdad14dc607417326564878d3d25b46c130c7 Author: Alfred Egger Date: Tue Apr 21 22:37:42 2020 +0200 Initial commit from existing app diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8e483ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,116 @@ +## 1.2.3 - 2018-02-24 +### Fixed + - (#76) typo plus some missing code kept Reader from being used to preview shared files + - (#79) typo kept Reader from being used by default for CBx + - (#82) missing setDefault kept actual style settings from being saved + +## 1.2.2 - 2018-02-02 +### Fixed + - (#75) NC and OC are diverging, NC encodes everything on $settings as JSON, OC does not yet. + +## 1.2.1 - 2018-01-31 +### Changed + - change default settings to enabled for all supported mime types + +## 1.2.0 - 2018-01-31 +### Added + - PDF: (#73) new preference 'scroll to top of page on page turn' + - PDF: defaults and per-document settings are now saved and restored + - PDF: nightmode (using CSS3 filters, only works in recent browsers), toggle with 'd', by clicking nightmode button or clicking in empty area on button bar, adjust in settings + +### Changed + - remove from templates to avoid warning in console, statement was ineffective anyway de to (overly restrictive) hardcoded policy in NC/OC. + - removed (or rather disabled) merging of PDF annotations into user bookmarks as it only served to mess up the bookmark list and slowed things down. This feature can be re-enabled once Reader gains a functional PDF annotation editor. + +### Fixed + - PDF: (#72) $title not ['title'] in pdfreader template, hopefully the last remaining bug related to template refactoring + - PDF: browsing the thumbnail list in single-page mode did not work as intended due to datatype mismatch in page calculation routine, fixed with explicit toString() + - PDF: page 0 does not exist so don't try to go there + +## 1.1.1 - 2018-01-19 +### Added + - signed package for publication in Owncloud marketplace + +### Changed + - updated bitjs unrar.js and rarvm.js + +## 1.1.0 - 2018-01-18 +### Added + - Reader now supports PDF + - PDF double page spreads are supported + - optional double-buffering for faster rendering, can be disabled for low-memory devices + - optional selectable text layer, can be disabled for low-memory devices + +### Changed + - #38: moved declarations in js/ready.js one level lower to work around a bug in the Palemoon browser + - new version bitjs archive tools, fixes compatibility problems with some CBR files + - increased maximum supported version for OC and NC + +## 1.0.4 - 2017-04-09 +### Fixed + - #43, remove table aliases in hooks to avoid being bit by querybuilder/doctrine/MySQL incompatibility/idiosyncracy + - #39, #41 and #42, NOTE: if you're on MySQL or MariaDB you might need to enable 4-byte support if this has not been done yet, otherwise you'll get a '1071 Specified key was too long' error on install. More information on this issue - which also occurs when trying to use Emoji characters in a NC/OC installation on a MySQL or MariaDB database - can be found here: https://docs.nextcloud.com/server/11/admin_manual/maintenance/mysql_4byte_support.html + +## 1.0.3 - 2017-03-29 +### Fixed + - #40, detect shared file OR folder and (try to) get fileId for such when applicable + +## 1.0.2 - 2017-03-25 +### Fixed + - #37, use getAppManager()->isInstalled('files_opds') instead of class_exists to avoid log spam + +### Changed + - new version bitjs unarchiver, increases compatibility with CBR files (at the cost of some speed) + - move function declarations in js/ready.js down one block level so browsers which do not support + ES6 (e.g. Palemoon) can find them. Unfortunately the above new version of bitjs uses another ES6 + feature (classes) which Palemoon does not support so this change may be moot... + +## 1.0.1 - 2017-03-19 +### Fixed + - #35: Internal Server Error: fixed path resolution so app works when NC/OC hosted in subdirectory + +## 1.0.0 - 2017-03-15 +### Added + - Reader now supports CBR/CBZ ('comics') files + - Book position ('cursor') is saved on server and restored on next invocation + - Default settings (independent of fileid) and file-specific settings are saved and restored + - Bookmarks and annotations (notes) are saved and restored (bookmarks are a type of annotation). + - Full-text search implemented. + - Framework to support more file format renderers + - hooks added to remove defaults, settings and annotations/bookmarks for deleted files or users + - epubreader + * night mode now works more reliably + * new 'day mode', ie. user-defined colours + * new font settings: font weight + * column width user-configurable + * new mode: maximize reader area, for small-screen devices + * page turn arrows optional, hidden by default + - cbreader + * supports CBR (rar) and CBZ (zip) archives + * single and double page (spread) mode, auto-adjusts to screen geometry + * optional image enhancement filters + * seamless full screen mode (where browser allows user full control of experience, ie. not on apple) + +## 0.8.3 - 2017-02-02 +### Fixed + . #31: ReferenceError: cleanStartTextContent is not defined, caused by failure to declare local var in epub.js + +## 0.8.3 - 2017-02-01 +### Fixed + - missing $title parameter in template/reader.php caused warnings in log, fixed + +## 0.8.2 - 2017-01-10 +### Fixed + - Nextcloud-port broke compatibility with Owncloud due to OC not supporting CSPv3, workaround implemented + +## 0.8.1 - 2017-01-09 +### Added + - Modified info.xml, added screenshots + +## 0.8.0 - 2017-01-09 +### Added + - new version 0.2.15 of Futurepress epub.js renderer + +### Changed + - New logo + - First release to be compatible with Nextcloud diff --git a/README.md b/README.md new file mode 100644 index 0000000..af56862 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +files_reader +------------ + +Reader is an ebook reader based on pure javascript renderers. It works for publications formatted according to the following standards: + + - [Epub] + - [PDF] + - [CBR and CBZ] ('comics') + +For Epub Reader uses the futurepress [epub.js] renderer to provide near-native looks, especially when used full-screen. Turn pages by pressing the left/right hand side of the screen/window or using the cursor keys (if you have those), use the sidebar to browse through chapters or bookmarks and add annotations. + +PDF is handled by Mozilla's [pdf.js] renderer in combination with a custom reader app to enable side-by-side display, batch search and more. Controls are like those used in the Epub renderer with a few exceptions, e.g. night mode has not been implemented yet. + +CBR and CBZ are supported using a custom renderer inspired by [balaclark]'s work. As with Epub, books can be navigated using the keyboard as well as mouse or touch navigation. Reader generates a visual index of all pages, show in the sidebar (thumbnail generation can be disabled for low-memory and/or -speed devices). As CBx files are often of varying quality, a set of image enhancement filters are provided. + +# Features +Reader remembers the last-visited page in a book and returns to that page when the book is re-opened. As all settings are stored on the server these features are device-independent, ie. you can start reading on a mobile device, continue on a PC to finish the book on a tablet. + +### Text-based formats incl. PDF + + - seamless full-screen mode supported on browsers which allow full user-control, ie. not on Apple) + - single- and double-page viewing mode + - user-configurable font and colour settings + - night mode, toggled by clicking the book title/author on top of the viewer or the night mode button (PDF) + - full-text search with keyword highlighting + - bookmarks (with automatic snippet generation) + - annotations (not yet implemented for PDF) + - keyboard and pointer/touch-based navigation + +### CBR/CBZ ('Comics') + + - seamless full-screen mode supported on browsers which allow full user-control, ie. not on Apple) + - single- and double-page viewing mode + - optional image enhancement filters + - Left-to-right and right-to-left (_manga_) modes + - visual index (thumbnail size user-configurable, can be disabled for low-memory or -cpu devices) + - keyboard and pointer/touch-based navigation + +PDF support is still somewhat rough around the edges, not all features have been implemented yet. There is a known cosmetical issue in that in spread mode the (invisible but selectable) text layer for the left page is offset from the left when opening a document. As soon as a page is turned this problem disappears. + +### Keyboard navigation +Reader supports both pointer/touch-based as well as keyboard-based navigation. Pointer/touch based is mostly self-explanatory, + +| key | function | +| ---:| --- | +|_left_, _page-up_ | move to previous page; move to next page in RTL(_manga_) mode | +|_right_, _page-down_, _space_ | move to next page; move to previous page in RTL (_mange_) mode | +|_home_| move to first page | +|_end_| move to last page | +|s| toggle side bar | +|_esc_| close sidebar | +|f| toggle full screen| +|t| toggle toolbar | +|l| _CBR_: toggle layout | +|a| _EPUB_: annotate | +|b| _EPUB_: bookmark | +|r| _EPUB_: reflow text when sidebar is open | +|d| _EPUB_: toggle day (custom colour) mode | +|n| _EPUB_: toggle night mode | + +### Defaults and Preferences + +Reader stores __defaults__ - settings which are independent of _fileId_ (ie. independent of the book currently open) - and __preferences__ - _fileId_-dependent (ie. different for every book) - on the server. Defaults are not shared between renderers, ie. the CBR renderer does not share defaults with the EPUB or PDF renderer. Defaults and preferences are removed from the server when the associated book or user is deleted. + +### Annotations and Bookmarks + +Reader supports _annotations_ (notes linked to a given position in a book) and _bookmarks_ (position markers with automatically generated text snippets). An automatically generated bookmark (called '__ CURSOR __', not visible in the bookmarks list) is used to keep track of the current reading position. Annotations and bookmark snippets can be edited or deleted in the sidebar. + + +# Screenshots +### Epub +| | | +---|--- +Reader showing page spread in 'night mode'|![Reader showing page spread in 'night mode'][SS01] +Epub single page, full screen on a small-screen device |![Epub single page, full screen][SS02] +Day mode color selector|![Day mode color selector][SS03] +Longing for that olde-time terminal feeling...|![Longing for that olde-time terminal feeling...][SS04] +Full-text search|![Full-text search][SS05] +Small screen device, __maximize text area__ enabled|![Small screen device, maximize text area enabled][SS06] +Search on small-screen device|![Search on small-screen device][SS07] +As close to full-screen as you can get on iOS|![As close to full-screen as you can get on iOS][SS08] +Android supports true fullscreen (as do most other systems)|![Android supports true fullscreen (as do most other systems)][SS09] + +### PDF +| | | +---|--- +Reader showing PDF Reference document in spread mode (pages side by side)|![Reader showing PDF Reference document in spread mode (pages side by side)][SS20] +Search through a document, showing all results in the sidebar|![Search through a document, showing all results in the sidebar][SS19] +Dropdown showing page format options - spread, single page, page width and zoom options|![Dropdown showing page format options - spread, single page, page width and zoom options][SS21] +Reader showing PDF in spread mode, thumbnails in the sidebar|![Reader showing PDF in spread mode, thumbnails in the sidebar][SS22] + +### CBR/CBZ +| | | +---|--- + iOS approximation of full screen, CBR|![iOS approximation of full screen, CBR][SS10] +The same book, now in landscape mode, switch to 2-page spread is automatic|![The same book, now in landscape mode, switch to 2-page spread is automatic][SS11] +Sidebar open, showing index, landscape mode|![Sidebar open, showing index, landscape mode][SS12] +Sidebar open, showing index, portrait mode|![Sidebar open, showing index, portrait mode][SS13] +Image enhancement filters, desaturate (grayscale) active|![Image enhancement filters, desaturate (grayscale) active][SS14] +full screen (apart from iOS restrictions), controls hidden|![full screen (apart from iOS restrictions), controls hidden][SS15] +Same page, zoomed in|![Same page, zoomed in][SS16] +Small-screen, low memory (Android) device showing full-page book cover|![Small-screen, low memory Android device showing full-page book cover][SS17] +The same Android device showing a zoomed-in part of a page|![The same Android device showing a zoomed-in part of a page][SS18] + + + + [epub.js]: https://github.com/futurepress/epub.js + [Epub]: http://idpf.org/epub + [CBR and CBZ]: https://wiki.mobileread.com/wiki/CBR_and_CBZ + [balaclark]: https://github.com/balaclark/HTML5-Comic-Book-Reader + [PDF]: https://en.wikipedia.org/wiki/Portable_Document_Format + [pdf.js]: https://github.com/mozilla/pdf.js + [SS01]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-1.png "Reader showing day/nighyt mode" + [SS02]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-3.png "Single page full screen on a small-screen device" + [SS03]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-39.jpg "Day mode color selector" + [SS04]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-41.jpg?raw=true "Longing For that olde-time terminal feeling..." + [SS05]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-53.jpg "Full-text search" + [SS06]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_18-28-46.jpg "Small screen device, __maximize text area__ enabled" + [SS07]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-49.jpg?raw=true "Search on small-screen device" + [SS08]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-21-52.jpg?raw=true "As close to full-screen as you can get on iOS" + [SS09]: https://github.com/e-alfred/epubreader/blob/master/screenshots/Screenshot_2014-09-29-20-21-50.png?raw=true "Android supports true fullscreen (as do most other systems)" + [SS10]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-21-59.jpg?raw=true "iOS approximation of full screen, CBR" + [SS11]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-00.jpg?raw=true "The same book, now in landscape mode, switch to 2-page spread is automatic" + [SS12]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-01.jpg?raw=true "Sidebar open, showing index, landscape mode" + [SS13]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-02.jpg?raw=true "Sidebar open, showing index, portrait mode" + [SS14]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-05.jpg?raw=true "Image enhancement filters, desaturate (grayscale) active" + [SS15]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-08.jpg?raw=true "full screen (apart from iOS restrictions), controls hidden" + [SS16]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-10.jpg?raw=true "Same page, zoomed in" + [SS17]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-54.jpg?raw=true "Small-screen, low memory (Android) device showing full-page book cover" + [SS18]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-56.jpg?raw=true "The same Android device showing a zoomed-in part of a page" + [SS19]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_001.png?raw=true "Search through a document, showing all results in the sidebar" + [SS20]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_002.png?raw=true "Reader showing PDF Reference document in spread mode (pages side by side)" + [SS21]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_005.png?raw=true "Dropdown showing page format options - spread, single page, page width and zoom options" + [SS22]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_006.png?raw=true "Reader showing PDF in spread mode, thumbnails in the sidebar" diff --git a/TODO b/TODO new file mode 100644 index 0000000..184985c --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ + - annotations + - rtl and ltr + - test canvas size restriction + - add IDs to highlights so they can be marked when hovered - or when related list item is hovered + diff --git a/ajax/personal.php b/ajax/personal.php new file mode 100644 index 0000000..c3a3602 --- /dev/null +++ b/ajax/personal.php @@ -0,0 +1,35 @@ +getL10N('files_reader'); + +$EpubEnable = isset($_POST['EpubEnable']) ? $_POST['EpubEnable'] : 'false'; +$PdfEnable = isset($_POST['PdfEnable']) ? $_POST['PdfEnable'] : 'false'; +$CbxEnable = isset($_POST['CbxEnable']) ? $_POST['CbxEnable'] : 'false'; + +Config::set('epub_enable', $EpubEnable); +Config::set('pdf_enable', $PdfEnable); +Config::set('cbx_enable', $CbxEnable); + +\OC_JSON::success( + array( + 'data' => array('message'=> $l->t('Settings updated successfully.')) + ) +); + +exit(); + diff --git a/appinfo/app.php b/appinfo/app.php new file mode 100644 index 0000000..1183d77 --- /dev/null +++ b/appinfo/app.php @@ -0,0 +1,22 @@ +getL10N('files_reader'); + +\OCA\Files_Reader\Hooks::register(); +Util::addscript('files_reader', 'plugin'); +\OCP\App::registerPersonal('files_reader', 'personal'); diff --git a/appinfo/database.xml b/appinfo/database.xml new file mode 100644 index 0000000..562acb3 --- /dev/null +++ b/appinfo/database.xml @@ -0,0 +1,203 @@ + + + *dbname* + true + false + utf8 + + + *dbprefix*reader_bookmarks + + + + id + integer + true + true + true + true + 8 + + + + + user_id + text + + true + 64 + + + + + file_id + integer + true + true + 11 + + + + + type + text + + true + 32 + + + + + name + text + + true + 512 + + + + + value + text + + true + 512 + + + + + content + text + + false + 4096 + + + + + last_modified + integer + 0 + 8 + false + true + + + + reader_bookmarks_file_id_index + + file_id + + + + + reader_bookmarks_user_id_index + + user_id + + + + + reader_bookmarks_name_index + + name + + + + +
+ + + *dbprefix*reader_preferences + + + + id + integer + true + true + true + true + 8 + + + + + user_id + text + + true + 64 + + + + + file_id + integer + true + true + 11 + + + + + scope + text + + true + 32 + + + + + name + text + + true + 128 + + + + + value + text + + true + 4096 + + + + + last_modified + integer + 0 + 8 + false + true + + + + reader_preferences_file_id_index + + file_id + + + + + reader_preferences_user_id_index + + user_id + + + + + reader_preferences_scope_index + + scope + + + + +
+ +
diff --git a/appinfo/info.xml b/appinfo/info.xml new file mode 100644 index 0000000..94249ef --- /dev/null +++ b/appinfo/info.xml @@ -0,0 +1,51 @@ + + + epubreader + Reader (ebook reader) + A multi-format browser-based ebook reader, supports EPUB and CBR/CBZ + + + + 1.4.0 + AGPL + Frank de Lange + e-alfred + https://github.com/e-alfred/epubreader + https://github.com/e-alfred/epubreader + https://github.com/e-alfred/epubreader + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-1.png + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-3.png + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader_PDF_005.png + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader_PDF_006.png + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-22-00.jpg + https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-22-02.jpg + files + multimedia + office + + + + diff --git a/appinfo/routes.php b/appinfo/routes.php new file mode 100644 index 0000000..4be67bc --- /dev/null +++ b/appinfo/routes.php @@ -0,0 +1,40 @@ +create('reader_personal_settings', 'ajax/personal.php')->actionInclude('files_reader/ajax/personal.php'); + +return ['routes' => [ + // Page + ['name' => 'page#showReader', 'url' => '/', 'verb' => 'GET'], + + // Bookmarks + ['name' => 'bookmark#get_cursor', 'url' => '/bookmark/cursor/{fileId}', 'verb' => 'GET'], + ['name' => 'bookmark#set_cursor', 'url' => '/bookmark/cursor', 'verb' => 'POST'], + ['name' => 'bookmark#delete_cursor', 'url' => '/bookmark/cursor/{fileId}', 'verb' => 'DELETE'], + ['name' => 'bookmark#get', 'url' => '/bookmark/{fileId}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']], + ['name' => 'bookmark#get', 'url' => '/bookmark/{fileId}/{type}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']], + ['name' => 'bookmark#set', 'url' => '/bookmark', 'verb' => 'POST'], + ['name' => 'bookmark#delete', 'url' => '/bookmark/{fileId}/{name}', 'verb' => 'DELETE'], + + // Metadata + ['name' => 'metadata#get', 'url' => '/metadata/{fileId}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']], + ['name' => 'metadata#set', 'url' => '/metadata/{fileId}/{name}/{value}', 'verb' => 'POST'], + + // Preferences + ['name' => 'preference#get_default', 'url' => '/preference/default/{scope}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']], + ['name' => 'preference#set_default', 'url' => '/preference/default', 'verb' => 'POST'], + ['name' => 'preference#delete_default', 'url' => '/preference/default/{scope}/{name}', 'verb' => 'DELETE'], + ['name' => 'preference#get', 'url' => '/preference/{fileId}/{scope}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']], + ['name' => 'preference#set', 'url' => '/preference', 'verb' => 'POST'], + ['name' => 'preference#delete', 'url' => '/preference/{fileId}/{scope}/{name}', 'verb' => 'DELETE'], +]]; + diff --git a/css/settings.css b/css/settings.css new file mode 100644 index 0000000..ed80d66 --- /dev/null +++ b/css/settings.css @@ -0,0 +1,3 @@ +.nav-icon-reader { + background-image: url('../img/app.svg?v=2'); +} diff --git a/img/app.svg b/img/app.svg new file mode 100644 index 0000000..8c89d34 --- /dev/null +++ b/img/app.svg @@ -0,0 +1,50 @@ + + + + + + image/svg+xml + + + + + + + + + +   + + diff --git a/img/book.png b/img/book.png new file mode 100644 index 0000000..a7b8079 Binary files /dev/null and b/img/book.png differ diff --git a/img/loading.gif b/img/loading.gif new file mode 100644 index 0000000..f8f3dff Binary files /dev/null and b/img/loading.gif differ diff --git a/js/lib/Blob.js b/js/lib/Blob.js new file mode 100644 index 0000000..3b44c65 --- /dev/null +++ b/js/lib/Blob.js @@ -0,0 +1,197 @@ +/* Blob.js + * A Blob implementation. + * 2014-07-24 + * + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/dsamarin + * License: X11/MIT + * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md + */ + +/*global self, unescape */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ + +(function (view) { + "use strict"; + + view.URL = view.URL || view.webkitURL; + + if (view.Blob && view.URL) { + try { + new Blob; + return; + } catch (e) {} + } + + // Internally we use a BlobBuilder implementation to base Blob off of + // in order to support older browsers that only have BlobBuilder + var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { + var + get_class = function(object) { + return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; + } + , FakeBlobBuilder = function BlobBuilder() { + this.data = []; + } + , FakeBlob = function Blob(data, type, encoding) { + this.data = data; + this.size = data.length; + this.type = type; + this.encoding = encoding; + } + , FBB_proto = FakeBlobBuilder.prototype + , FB_proto = FakeBlob.prototype + , FileReaderSync = view.FileReaderSync + , FileException = function(type) { + this.code = this[this.name = type]; + } + , file_ex_codes = ( + "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" + ).split(" ") + , file_ex_code = file_ex_codes.length + , real_URL = view.URL || view.webkitURL || view + , real_create_object_URL = real_URL.createObjectURL + , real_revoke_object_URL = real_URL.revokeObjectURL + , URL = real_URL + , btoa = view.btoa + , atob = view.atob + + , ArrayBuffer = view.ArrayBuffer + , Uint8Array = view.Uint8Array + + , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ + ; + FakeBlob.fake = FB_proto.fake = true; + while (file_ex_code--) { + FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; + } + // Polyfill URL + if (!real_URL.createObjectURL) { + URL = view.URL = function(uri) { + var + uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") + , uri_origin + ; + uri_info.href = uri; + if (!("origin" in uri_info)) { + if (uri_info.protocol.toLowerCase() === "data:") { + uri_info.origin = null; + } else { + uri_origin = uri.match(origin); + uri_info.origin = uri_origin && uri_origin[1]; + } + } + return uri_info; + }; + } + URL.createObjectURL = function(blob) { + var + type = blob.type + , data_URI_header + ; + if (type === null) { + type = "application/octet-stream"; + } + if (blob instanceof FakeBlob) { + data_URI_header = "data:" + type; + if (blob.encoding === "base64") { + return data_URI_header + ";base64," + blob.data; + } else if (blob.encoding === "URI") { + return data_URI_header + "," + decodeURIComponent(blob.data); + } if (btoa) { + return data_URI_header + ";base64," + btoa(blob.data); + } else { + return data_URI_header + "," + encodeURIComponent(blob.data); + } + } else if (real_create_object_URL) { + return real_create_object_URL.call(real_URL, blob); + } + }; + URL.revokeObjectURL = function(object_URL) { + if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { + real_revoke_object_URL.call(real_URL, object_URL); + } + }; + FBB_proto.append = function(data/*, endings*/) { + var bb = this.data; + // decode data to a binary string + if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { + var + str = "" + , buf = new Uint8Array(data) + , i = 0 + , buf_len = buf.length + ; + for (; i < buf_len; i++) { + str += String.fromCharCode(buf[i]); + } + bb.push(str); + } else if (get_class(data) === "Blob" || get_class(data) === "File") { + if (FileReaderSync) { + var fr = new FileReaderSync; + bb.push(fr.readAsBinaryString(data)); + } else { + // async FileReader won't work as BlobBuilder is sync + throw new FileException("NOT_READABLE_ERR"); + } + } else if (data instanceof FakeBlob) { + if (data.encoding === "base64" && atob) { + bb.push(atob(data.data)); + } else if (data.encoding === "URI") { + bb.push(decodeURIComponent(data.data)); + } else if (data.encoding === "raw") { + bb.push(data.data); + } + } else { + if (typeof data !== "string") { + data += ""; // convert unsupported types to strings + } + // decode UTF-16 to binary string + bb.push(unescape(encodeURIComponent(data))); + } + }; + FBB_proto.getBlob = function(type) { + if (!arguments.length) { + type = null; + } + return new FakeBlob(this.data.join(""), type, "raw"); + }; + FBB_proto.toString = function() { + return "[object BlobBuilder]"; + }; + FB_proto.slice = function(start, end, type) { + var args = arguments.length; + if (args < 3) { + type = null; + } + return new FakeBlob( + this.data.slice(start, args > 1 ? end : this.data.length) + , type + , this.encoding + ); + }; + FB_proto.toString = function() { + return "[object Blob]"; + }; + FB_proto.close = function() { + this.size = 0; + delete this.data; + }; + return FakeBlobBuilder; + }(view)); + + view.Blob = function(blobParts, options) { + var type = options ? (options.type || "") : ""; + var builder = new BlobBuilder(); + if (blobParts) { + for (var i = 0, len = blobParts.length; i < len; i++) { + builder.append(blobParts[i]); + } + } + return builder.getBlob(type); + }; +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); diff --git a/js/lib/blob.js b/js/lib/blob.js new file mode 100644 index 0000000..b20ed0b --- /dev/null +++ b/js/lib/blob.js @@ -0,0 +1,31 @@ +Blob = (function() { + var nativeBlob = Blob; + + // Add unprefixed slice() method. + if (Blob.prototype.webkitSlice) { + Blob.prototype.slice = Blob.prototype.webkitSlice; + } + else if (Blob.prototype.mozSlice) { + Blob.prototype.slice = Blob.prototype.mozSlice; + } + + // Temporarily replace Blob() constructor with one that checks support. + return function(parts, properties) { + try { + // Restore native Blob() constructor, so this check is only evaluated once. + Blob = nativeBlob; + return new Blob(parts || [], properties || {}); + } + catch (e) { + // If construction fails provide one that uses BlobBuilder. + Blob = function (parts, properties) { + var bb = new (WebKitBlobBuilder || MozBlobBuilder), i; + for (i in parts) { + bb.append(parts[i]); + } + + return bb.getBlob(properties && properties.type ? properties.type : undefined); + }; + } + }; +}()); diff --git a/js/lib/typedarray.js b/js/lib/typedarray.js new file mode 100644 index 0000000..9c02368 --- /dev/null +++ b/js/lib/typedarray.js @@ -0,0 +1,629 @@ +/* +$LicenseInfo:firstyear=2010&license=mit$ + +Copyright (c) 2010, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +*/ +/*global document*/ + +// +// ES3/ES5 implementation of the Krhonos TypedArray Working Draft (work in progress): +// Ref: https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/doc/spec/TypedArray-spec.html +// Date: 2011-02-01 +// +// Variations: +// * Float/Double -> Float32/Float64, per WebGL-Public mailing list conversations (post 5/17) +// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) + +var ArrayBuffer, ArrayBufferView, + Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, + DataView; + +(function () { + "use strict"; + /*jslint bitwise: false, nomen: false */ + + // Approximations of internal ECMAScript conversion functions + var ECMAScript = { + ToInt32: function (v) { return v >> 0; }, + ToUint32: function (v) { return v >>> 0; } + }; + + // Raise an INDEX_SIZE_ERR event - intentionally induces a DOM error + function raise_INDEX_SIZE_ERR() { + if (document) { + // raises DOMException(INDEX_SIZE_ERR) + document.createTextNode("").splitText(1); + } + throw new RangeError("INDEX_SIZE_ERR"); + } + + // ES5: lock down object properties + function configureProperties(obj) { + if (Object.getOwnPropertyNames && Object.defineProperty) { + var props = Object.getOwnPropertyNames(obj), i; + for (i = 0; i < props.length; i += 1) { + Object.defineProperty(obj, props[i], { + value: obj[props[i]], + writable: false, + enumerable: false, + configurable: false + }); + } + } + } + + // emulate ES5 getter/setter API using legacy APIs + // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx + if (Object.prototype.__defineGetter__ && !Object.defineProperty) { + Object.defineProperty = function (obj, prop, desc) { + if (desc.hasOwnProperty('get')) { obj.__defineGetter__(prop, desc.get); } + if (desc.hasOwnProperty('set')) { obj.__defineSetter__(prop, desc.set); } + }; + } + + // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) + // for index in 0 ... obj.length + function makeArrayAccessors(obj) { + if (!Object.defineProperty) { return; } + + function makeArrayAccessor(index) { + Object.defineProperty(obj, index, { + 'get': function () { return obj._getter(index); }, + 'set': function (v) { obj._setter(index, v); }, + enumerable: true, + configurable: false + }); + } + + var i; + for (i = 0; i < obj.length; i += 1) { + makeArrayAccessor(i); + } + } + + // Internal conversion functions: + // pack() - take a number (interpreted as Type), output a byte array + // unpack() - take a byte array, output a Type-like number + + function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } + function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } + + function packInt8(n) { return [n & 0xff]; } + function unpackInt8(bytes) { return as_signed(bytes[0], 8); } + + function packUint8(n) { return [n & 0xff]; } + function unpackUint8(bytes) { return as_unsigned(bytes[0], 8); } + + function packInt16(n) { return [(n >> 8) & 0xff, n & 0xff]; } + function unpackInt16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); } + + function packUint16(n) { return [(n >> 8) & 0xff, n & 0xff]; } + function unpackUint16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); } + + function packInt32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } + function unpackInt32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + + function packUint32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } + function unpackUint32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); } + + function packIEEE754(v, ebits, fbits) { + + var bias = (1 << (ebits - 1)) - 1, + s, e, f, ln, + i, bits, str, bytes; + + // Compute sign, exponent, fraction + if (isNaN(v)) { + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + e = (1 << bias) - 1; f = Math.pow(2, fbits - 1); s = 0; + } + else if (v === Infinity || v === -Infinity) { + e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0; + } + else if (v === 0) { + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; + } + else { + s = v < 0; + v = Math.abs(v); + + if (v >= Math.pow(2, 1 - bias)) { + // Normalized + ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias); + e = ln + bias; + f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits)); + } + else { + // Denormalized + e = 0; + f = Math.round(v / Math.pow(2, 1 - bias - fbits)); + } + } + + // Pack sign, exponent, fraction + bits = []; + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); } + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); } + bits.push(s ? 1 : 0); + bits.reverse(); + str = bits.join(''); + + // Bits to bytes + bytes = []; + while (str.length) { + bytes.push(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; + } + + function unpackIEEE754(bytes, ebits, fbits) { + + // Bytes to bits + var bits = [], i, j, b, str, + bias, s, e, f; + + for (i = bytes.length; i; i -= 1) { + b = bytes[i - 1]; + for (j = 8; j; j -= 1) { + bits.push(b % 2 ? 1 : 0); b = b >> 1; + } + } + bits.reverse(); + str = bits.join(''); + + // Unpack sign, exponent, fraction + bias = (1 << (ebits - 1)) - 1; + s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + e = parseInt(str.substring(1, 1 + ebits), 2); + f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } + else if (e > 0) { + // Normalized + return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits)); + } + else if (f !== 0) { + // Denormalized + return s * Math.pow(2, -(bias - 1)) * (f / Math.pow(2, fbits)); + } + else { + return s < 0 ? -0 : 0; + } + } + + function unpackFloat64(b) { return unpackIEEE754(b, 11, 52); } + function packFloat64(v) { return packIEEE754(v, 11, 52); } + function unpackFloat32(b) { return unpackIEEE754(b, 8, 23); } + function packFloat32(v) { return packIEEE754(v, 8, 23); } + + + if (!ArrayBuffer) { + (function () { + + // + // 3 The ArrayBuffer Type + // + + ArrayBuffer = function (length) { + length = ECMAScript.ToInt32(length); + if (length < 0) { throw new RangeError('ArrayBuffer size is not a small enough positive integer.'); } + + this.byteLength = length; + this._bytes = []; + this._bytes.length = length; + + var i; + for (i = 0; i < this.byteLength; i += 1) { + this._bytes[i] = 0; + } + + configureProperties(this); + }; + + + // + // 4 The ArrayBufferView Type + // + + // NOTE: this constructor is not exported + ArrayBufferView = function () { + //this.buffer = null; + //this.byteOffset = 0; + //this.byteLength = 0; + }; + + // + // 5 The Typed Array View Types + // + + function makeTypedArrayConstructor(bytesPerElement, pack, unpack) { + // Each TypedArray type requires a distinct constructor instance with + // identical logic, which this produces. + + var ctor; + ctor = function (buffer, byteOffset, length) { + var array, sequence, i, s; + + // Constructor(unsigned long length) + if (!arguments.length || typeof arguments[0] === 'number') { + this.length = ECMAScript.ToInt32(arguments[0]); + if (length < 0) { throw new RangeError('ArrayBufferView size is not a small enough positive integer.'); } + + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + } + + // Constructor(TypedArray array) + else if (typeof arguments[0] === 'object' && arguments[0].constructor === ctor) { + array = arguments[0]; + + this.length = array.length; + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + this._setter(i, array._getter(i)); + } + } + + // Constructor(sequence array) + else if (typeof arguments[0] === 'object' && !(arguments[0] instanceof ArrayBuffer)) { + sequence = arguments[0]; + + this.length = ECMAScript.ToUint32(sequence.length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + this.buffer = new ArrayBuffer(this.byteLength); + this.byteOffset = 0; + + for (i = 0; i < this.length; i += 1) { + s = sequence[i]; + this._setter(i, Number(s)); + } + } + + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, optional unsigned long length) + else if (typeof arguments[0] === 'object' && arguments[0] instanceof ArrayBuffer) { + this.buffer = buffer; + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + raise_INDEX_SIZE_ERR(); // byteOffset out of range + } + + if (this.byteOffset % this.BYTES_PER_ELEMENT) { + // The given byteOffset must be a multiple of the element + // size of the specific type, otherwise an exception is raised. + //raise_INDEX_SIZE_ERR(); + throw new RangeError("ArrayBuffer length minus the byteOffset is not a multiple of the element size."); + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + + if (this.byteLength % this.BYTES_PER_ELEMENT) { + raise_INDEX_SIZE_ERR(); // length of buffer minus byteOffset not a multiple of the element size + } + this.length = this.byteLength / this.BYTES_PER_ELEMENT; + } + else { + this.length = ECMAScript.ToUint32(length); + this.byteLength = this.length * this.BYTES_PER_ELEMENT; + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + raise_INDEX_SIZE_ERR(); // byteOffset and length reference an area beyond the end of the buffer + } + } + else { + throw new TypeError("Unexpected argument type(s)"); + } + + this.constructor = ctor; + + // ES5-only magic + configureProperties(this); + makeArrayAccessors(this); + }; + + ctor.prototype = new ArrayBufferView(); + ctor.prototype.BYTES_PER_ELEMENT = bytesPerElement; + ctor.prototype._pack = pack; + ctor.prototype._unpack = unpack; + ctor.BYTES_PER_ELEMENT = bytesPerElement; + + // getter type (unsigned long index); + ctor.prototype._getter = function (index) { + if (arguments.length < 1) { throw new SyntaxError("Not enough arguments"); } + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + //raise_INDEX_SIZE_ERR(); // Array index out of range + return; // undefined + } + + var bytes = [], i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + bytes.push(this.buffer._bytes[o]); + } + return this._unpack(bytes); + }; + + // NONSTANDARD: convenience alias for getter: type get(unsigned long index); + ctor.prototype.get = ctor.prototype._getter; + + // setter void (unsigned long index, type value); + ctor.prototype._setter = function (index, value) { + if (arguments.length < 2) { throw new SyntaxError("Not enough arguments"); } + + index = ECMAScript.ToUint32(index); + if (index >= this.length) { + //raise_INDEX_SIZE_ERR(); // Array index out of range + return; + } + + var bytes = this._pack(value), i, o; + for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; + i < this.BYTES_PER_ELEMENT; + i += 1, o += 1) { + this.buffer._bytes[o] = bytes[i]; + } + }; + + // void set(TypedArray array, optional unsigned long offset); + // void set(sequence array, optional unsigned long offset); + ctor.prototype.set = function (index, value) { + if (arguments.length < 1) { throw new SyntaxError("Not enough arguments"); } + var array, sequence, offset, len, + i, s, d, + byteOffset, byteLength, tmp; + + // void set(TypedArray array, optional unsigned long offset); + if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { + array = arguments[0]; + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + array.length > this.length) { + raise_INDEX_SIZE_ERR(); // Offset plus length of array is out of range + } + + byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; + byteLength = array.length * this.BYTES_PER_ELEMENT; + + if (array.buffer === this.buffer) { + tmp = []; + for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { + tmp[i] = array.buffer._bytes[s]; + } + for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { + this.buffer._bytes[d] = tmp[i]; + } + } + else { + for (i = 0, s = array.byteOffset, d = byteOffset; + i < byteLength; i += 1, s += 1, d += 1) { + this.buffer._bytes[d] = array.buffer._bytes[s]; + } + } + } + + // void set(sequence array, optional unsigned long offset); + else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { + sequence = arguments[0]; + len = ECMAScript.ToUint32(sequence.length); + offset = ECMAScript.ToUint32(arguments[1]); + + if (offset + len > this.length) { + raise_INDEX_SIZE_ERR(); // Offset plus length of array is out of range + } + + for (i = 0; i < len; i += 1) { + s = sequence[i]; + this._setter(offset + i, Number(s)); + } + } + + else { + throw new TypeError("Unexpected argument type(s)"); + } + }; + + // TypedArray subarray(long begin, optional long end); + ctor.prototype.subarray = function (start, end) { + function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } + + start = ECMAScript.ToInt32(start); + end = ECMAScript.ToInt32(end); + + if (arguments.length < 1) { start = 0; } + if (arguments.length < 2) { end = this.length; } + + if (start < 0) { start = this.length + start; } + if (end < 0) { end = this.length + end; } + + start = clamp(start, 0, this.length); + end = clamp(end, 0, this.length); + + var len = end - start; + if (len < 0) { + len = 0; + } + + return new this.constructor(this.buffer, start * this.BYTES_PER_ELEMENT, len); + }; + + return ctor; + } + + Int8Array = Int8Array || makeTypedArrayConstructor(1, packInt8, unpackInt8); + Uint8Array = Uint8Array || makeTypedArrayConstructor(1, packUint8, unpackUint8); + Int16Array = Int16Array || makeTypedArrayConstructor(2, packInt16, unpackInt16); + Uint16Array = Uint16Array || makeTypedArrayConstructor(2, packUint16, unpackUint16); + Int32Array = Int32Array || makeTypedArrayConstructor(4, packInt32, unpackInt32); + Uint32Array = Uint32Array || makeTypedArrayConstructor(4, packUint32, unpackUint32); + Float32Array = Float32Array || makeTypedArrayConstructor(4, packFloat32, unpackFloat32); + Float64Array = Float64Array || makeTypedArrayConstructor(8, packFloat64, unpackFloat64); + + } ()); + } + + + if (!DataView) { + (function () { + + // + // 6 The DataView View Type + // + + function r(array, index) { + if (typeof array.get === 'function') { + return array.get(index); + } + else { + return array[index]; + } + } + + + var IS_BIG_ENDIAN = (function () { + var u16array = new Uint16Array([0x1234]), + u8array = new Uint8Array(u16array.buffer); + return r(u8array, 0) === 0x12; + } ()); + + // Constructor(ArrayBuffer buffer, + // optional unsigned long byteOffset, + // optional unsigned long byteLength) + DataView = function (buffer, byteOffset, byteLength) { + if (!(typeof buffer === 'object' && buffer instanceof ArrayBuffer)) { + throw new TypeError("TypeError"); + } + + this.buffer = buffer; + + this.byteOffset = ECMAScript.ToUint32(byteOffset); + if (this.byteOffset > this.buffer.byteLength) { + raise_INDEX_SIZE_ERR(); // byteOffset out of range + } + + if (arguments.length < 3) { + this.byteLength = this.buffer.byteLength - this.byteOffset; + } + else { + this.byteLength = ECMAScript.ToUint32(byteLength); + } + + if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) { + raise_INDEX_SIZE_ERR(); // byteOffset and length reference an area beyond the end of the buffer + } + + // ES5-only magic + configureProperties(this); + }; + + if (ArrayBufferView) { + DataView.prototype = new ArrayBufferView(); + } + + function makeDataView_getter(arrayType) { + return function (byteOffset, littleEndian) { + /*jslint newcap: false*/ + byteOffset = ECMAScript.ToUint32(byteOffset); + + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + raise_INDEX_SIZE_ERR(); // Array index out of range + } + byteOffset += this.byteOffset; + + var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), + bytes = [], i; + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(uint8Array, i)); + } + + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + return r(new arrayType(new Uint8Array(bytes).buffer), 0); + }; + } + + DataView.prototype.getUint8 = makeDataView_getter(Uint8Array); + DataView.prototype.getInt8 = makeDataView_getter(Int8Array); + DataView.prototype.getUint16 = makeDataView_getter(Uint16Array); + DataView.prototype.getInt16 = makeDataView_getter(Int16Array); + DataView.prototype.getUint32 = makeDataView_getter(Uint32Array); + DataView.prototype.getInt32 = makeDataView_getter(Int32Array); + DataView.prototype.getFloat32 = makeDataView_getter(Float32Array); + DataView.prototype.getFloat64 = makeDataView_getter(Float64Array); + + function makeDataView_setter(arrayType) { + return function (byteOffset, value, littleEndian) { + /*jslint newcap: false*/ + byteOffset = ECMAScript.ToUint32(byteOffset); + if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) { + raise_INDEX_SIZE_ERR(); // Array index out of range + } + + // Get bytes + var typeArray = new arrayType([value]), + byteArray = new Uint8Array(typeArray.buffer), + bytes = [], i, byteView; + + for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) { + bytes.push(r(byteArray, i)); + } + + // Flip if necessary + if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) { + bytes.reverse(); + } + + // Write them + byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); + byteView.set(bytes); + }; + } + + DataView.prototype.setUint8 = makeDataView_setter(Uint8Array); + DataView.prototype.setInt8 = makeDataView_setter(Int8Array); + DataView.prototype.setUint16 = makeDataView_setter(Uint16Array); + DataView.prototype.setInt16 = makeDataView_setter(Int16Array); + DataView.prototype.setUint32 = makeDataView_setter(Uint32Array); + DataView.prototype.setInt32 = makeDataView_setter(Int32Array); + DataView.prototype.setFloat32 = makeDataView_setter(Float32Array); + DataView.prototype.setFloat64 = makeDataView_setter(Float64Array); + + } ()); + } + +} ()); + diff --git a/js/lib/typedarray.min.js b/js/lib/typedarray.min.js new file mode 100644 index 0000000..d47ad9e --- /dev/null +++ b/js/lib/typedarray.min.js @@ -0,0 +1 @@ +var ArrayBuffer,ArrayBufferView,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,DataView;(function(){"use strict";function t(){if(document){document.createTextNode("").splitText(1)}throw new RangeError("INDEX_SIZE_ERR")}function n(e){if(Object.getOwnPropertyNames&&Object.defineProperty){var t=Object.getOwnPropertyNames(e),n;for(n=0;n>n}function s(e,t){var n=32-t;return e<>>n}function o(e){return[e&255]}function u(e){return i(e[0],8)}function a(e){return[e&255]}function f(e){return s(e[0],8)}function l(e){return[e>>8&255,e&255]}function c(e){return i(e[0]<<8|e[1],16)}function h(e){return[e>>8&255,e&255]}function p(e){return s(e[0]<<8|e[1],16)}function d(e){return[e>>24&255,e>>16&255,e>>8&255,e&255]}function v(e){return i(e[0]<<24|e[1]<<16|e[2]<<8|e[3],32)}function m(e){return[e>>24&255,e>>16&255,e>>8&255,e&255]}function g(e){return s(e[0]<<24|e[1]<<16|e[2]<<8|e[3],32)}function y(e,t,n){var r=(1<=Math.pow(2,1-r)){u=Math.min(Math.floor(Math.log(e)/Math.LN2),r);s=u+r;o=Math.round(e*Math.pow(2,n-u)-Math.pow(2,n))}else{s=0;o=Math.round(e/Math.pow(2,1-r-n))}}f=[];for(a=n;a;a-=1){f.push(o%2?1:0);o=Math.floor(o/2)}for(a=t;a;a-=1){f.push(s%2?1:0);s=Math.floor(s/2)}f.push(i?1:0);f.reverse();l=f.join("");c=[];while(l.length){c.push(parseInt(l.substring(0,8),2));l=l.substring(8)}return c}function b(e,t,n){var r=[],i,s,o,u,a,f,l,c;for(i=e.length;i;i-=1){o=e[i-1];for(s=8;s;s-=1){r.push(o%2?1:0);o=o>>1}}r.reverse();u=r.join("");a=(1<0){return f*Math.pow(2,l-a)*(1+c/Math.pow(2,n))}else if(c!==0){return f*Math.pow(2,-(a-1))*(c/Math.pow(2,n))}else{return f<0?-0:0}}function w(e){return b(e,11,52)}function E(e){return y(e,11,52)}function S(e){return b(e,8,23)}function x(e){return y(e,8,23)}var e={ToInt32:function(e){return e>>0},ToUint32:function(e){return e>>>0}};if(Object.prototype.__defineGetter__&&!Object.defineProperty){Object.defineProperty=function(e,t,n){if(n.hasOwnProperty("get")){e.__defineGetter__(t,n.get)}if(n.hasOwnProperty("set")){e.__defineSetter__(t,n.set)}}}if(!ArrayBuffer){(function(){function i(i,s,o){var u;u=function(i,s,o){var a,f,l,c;if(!arguments.length||typeof arguments[0]==="number"){this.length=e.ToInt32(arguments[0]);if(o<0){throw new RangeError("ArrayBufferView size is not a small enough positive integer.")}this.byteLength=this.length*this.BYTES_PER_ELEMENT;this.buffer=new ArrayBuffer(this.byteLength);this.byteOffset=0}else if(typeof arguments[0]==="object"&&arguments[0].constructor===u){a=arguments[0];this.length=a.length;this.byteLength=this.length*this.BYTES_PER_ELEMENT;this.buffer=new ArrayBuffer(this.byteLength);this.byteOffset=0;for(l=0;lthis.buffer.byteLength){t()}if(this.byteOffset%this.BYTES_PER_ELEMENT){throw new RangeError("ArrayBuffer length minus the byteOffset is not a multiple of the element size.")}if(arguments.length<3){this.byteLength=this.buffer.byteLength-this.byteOffset;if(this.byteLength%this.BYTES_PER_ELEMENT){t()}this.length=this.byteLength/this.BYTES_PER_ELEMENT}else{this.length=e.ToUint32(o);this.byteLength=this.length*this.BYTES_PER_ELEMENT}if(this.byteOffset+this.byteLength>this.buffer.byteLength){t()}}else{throw new TypeError("Unexpected argument type(s)")}this.constructor=u;n(this);r(this)};u.prototype=new ArrayBufferView;u.prototype.BYTES_PER_ELEMENT=i;u.prototype._pack=s;u.prototype._unpack=o;u.BYTES_PER_ELEMENT=i;u.prototype._getter=function(t){if(arguments.length<1){throw new SyntaxError("Not enough arguments")}t=e.ToUint32(t);if(t>=this.length){return}var n=[],r,i;for(r=0,i=this.byteOffset+t*this.BYTES_PER_ELEMENT;r=this.length){return}var r=this._pack(n),i,s;for(i=0,s=this.byteOffset+t*this.BYTES_PER_ELEMENT;ithis.length){t()}c=this.byteOffset+o*this.BYTES_PER_ELEMENT;h=i.length*this.BYTES_PER_ELEMENT;if(i.buffer===this.buffer){p=[];for(a=0,f=i.byteOffset;athis.length){t()}for(a=0;an?n:e}t=e.ToInt32(t);n=e.ToInt32(n);if(arguments.length<1){t=0}if(arguments.length<2){n=this.length}if(t<0){t=this.length+t}if(n<0){n=this.length+n}t=r(t,0,this.length);n=r(n,0,this.length);var i=n-t;if(i<0){i=0}return new this.constructor(this.buffer,t*this.BYTES_PER_ELEMENT,i)};return u}ArrayBuffer=function(t){t=e.ToInt32(t);if(t<0){throw new RangeError("ArrayBuffer size is not a small enough positive integer.")}this.byteLength=t;this._bytes=[];this._bytes.length=t;var r;for(r=0;rthis.byteLength){t()}s+=this.byteOffset;var u=new Uint8Array(this.buffer,s,n.BYTES_PER_ELEMENT),a=[],f;for(f=0;fthis.byteLength){t()}var a=new n([o]),f=new Uint8Array(a.buffer),l=[],c,h;for(c=0;cthis.buffer.byteLength){t()}if(arguments.length<3){this.byteLength=this.buffer.byteLength-this.byteOffset}else{this.byteLength=e.ToUint32(s)}if(this.byteOffset+this.byteLength>this.buffer.byteLength){t()}n(this)};if(ArrayBufferView){DataView.prototype=new ArrayBufferView}DataView.prototype.getUint8=s(Uint8Array);DataView.prototype.getInt8=s(Int8Array);DataView.prototype.getUint16=s(Uint16Array);DataView.prototype.getInt16=s(Int16Array);DataView.prototype.getUint32=s(Uint32Array);DataView.prototype.getInt32=s(Int32Array);DataView.prototype.getFloat32=s(Float32Array);DataView.prototype.getFloat64=s(Float64Array);DataView.prototype.setUint8=o(Uint8Array);DataView.prototype.setInt8=o(Int8Array);DataView.prototype.setUint16=o(Uint16Array);DataView.prototype.setInt16=o(Int16Array);DataView.prototype.setUint32=o(Uint32Array);DataView.prototype.setInt32=o(Int32Array);DataView.prototype.setFloat32=o(Float32Array);DataView.prototype.setFloat64=o(Float64Array)})()}})() diff --git a/js/lib/wgxpath.install.js b/js/lib/wgxpath.install.js new file mode 100644 index 0000000..f070e5f --- /dev/null +++ b/js/lib/wgxpath.install.js @@ -0,0 +1,75 @@ +(function(){function h(a){return function(){return this[a]}}function l(a){return function(){return a}}var m=this; +function ba(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function n(a){return"string"==typeof a}function ca(a,b,c){return a.call.apply(a.bind,arguments)}function da(a,b,c){if(!a)throw Error();if(2b?1:0};var v=Array.prototype,ia=v.indexOf?function(a,b,c){return v.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(n(a))return n(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:n(a)?a.charAt(c):a[c]}function ma(a){return v.concat.apply(v,arguments)}function na(a,b,c){return 2>=arguments.length?v.slice.call(a,b):v.slice.call(a,b,c)};var y;a:{var oa=m.navigator;if(oa){var pa=oa.userAgent;if(pa){y=pa;break a}}y=""};var qa=u(y,"Opera")||u(y,"OPR"),A=u(y,"Trident")||u(y,"MSIE"),ra=u(y,"Edge"),sa=u(y,"Gecko")&&!(u(y.toLowerCase(),"webkit")&&!u(y,"Edge"))&&!(u(y,"Trident")||u(y,"MSIE"))&&!u(y,"Edge"),ta=u(y.toLowerCase(),"webkit")&&!u(y,"Edge");function ua(){var a=y;if(sa)return/rv\:([^\);]+)(\)|;)/.exec(a);if(ra)return/Edge\/([\d\.]+)/.exec(a);if(A)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(ta)return/WebKit\/(\S+)/.exec(a)}function va(){var a=m.document;return a?a.documentMode:void 0} +var wa=function(){if(qa&&m.opera){var a=m.opera.version;return"function"==ba(a)?a():a}var a="",b=ua();b&&(a=b?b[1]:"");return A&&(b=va(),b>parseFloat(a))?String(b):a}(),xa={}; +function ya(a){if(!xa[a]){for(var b=0,c=ga(String(wa)).split("."),d=ga(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f]=|\\s+|.","g"),Ga=/^\s/;function D(a,b){return a.b[a.a+(b||0)]}function F(a){return a.b[a.a++]}function Ha(a){return a.b.length<=a.a};!sa&&!A||A&&9<=Aa||sa&&ya("1.9.1");A&&ya("9");function Ia(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a} +function Ja(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(A&&!(9<=Aa)){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ka(a,b):!c&&Ia(e,b)?-1*La(a,b):!d&&Ia(f,a)?La(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=9==a.nodeType?a:a.ownerDocument|| +a.document;c=d.createRange();c.selectNode(a);c.collapse(!0);d=d.createRange();d.selectNode(b);d.collapse(!0);return c.compareBoundaryPoints(m.Range.START_TO_END,d)}function La(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return Ka(d,a)}function Ka(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1};function G(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(B&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;for(var c=0,d=[],b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),B&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return""+b} +function H(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ba&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function Ma(a,b,c,d,e){return(B?Na:Oa).call(null,a,b,n(c)?c:null,n(d)?d:null,e||new I)} +function Na(a,b,c,d,e){if(a instanceof J||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=Pa(a);if("*"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],k=0;b=f[k++];)H(b,c,d)&&g.push(b);f=g}for(k=0;b=f[k++];)"*"==a&&"!"==b.tagName||K(e,b);return e}Qa(a,b,c,d,e);return e} +function Oa(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!A?(b=b.getElementsByName(d),w(b,function(b){a.a(b)&&K(e,b)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),w(b,function(b){b.className==d&&a.a(b)&&K(e,b)})):a instanceof L?Qa(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),w(b,function(a){H(a,c,d)&&K(e,a)}));return e} +function Ra(a,b,c,d,e){var f;if((a instanceof J||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=Pa(a);if("*"!=g&&(f=ja(f,function(a){return a.tagName&&a.tagName.toLowerCase()==g}),!f))return e;c&&(f=ja(f,function(a){return H(a,c,d)}));w(f,function(a){"*"==g&&("!"==a.tagName||"*"==g&&1!=a.nodeType)||K(e,a)});return e}return Sa(a,b,c,d,e)}function Sa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&K(e,b);return e} +function Qa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&K(e,b),Qa(a,b,c,d,e)}function Pa(a){if(a instanceof L){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function I(){this.b=this.a=null;this.l=0}function Ta(a){this.node=a;this.a=this.b=null}function Ua(a,b){if(!a.a)return b;if(!b.a)return a;for(var c=a.a,d=b.a,e=null,f=null,g=0;c&&d;){var f=c.node,k=d.node;f==k||f instanceof C&&k instanceof C&&f.a==k.a?(f=c,c=c.a,d=d.a):0",4,2,function(a,b,c){return S(function(a,b){return a>b},a,b,c)});T("<=",4,2,function(a,b,c){return S(function(a,b){return a<=b},a,b,c)}); +T(">=",4,2,function(a,b,c){return S(function(a,b){return a>=b},a,b,c)});var eb=T("=",3,2,function(a,b,c){return S(function(a,b){return a==b},a,b,c,!0)});T("!=",3,2,function(a,b,c){return S(function(a,b){return a!=b},a,b,c,!0)});T("and",2,2,function(a,b,c){return R(a,c)&&R(b,c)});T("or",1,2,function(a,b,c){return R(a,c)||R(b,c)});function hb(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");r.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}q(hb);hb.prototype.a=function(a){a=this.c.a(a);return ib(this.h,a)};hb.prototype.toString=function(){var a;a="Filter:"+O(this.c);return a+=O(this.h)};function jb(a,b){if(b.lengtha.v)throw Error("Function "+a.j+" expects at most "+a.v+" arguments, "+b.length+" given");a.B&&w(b,function(b,d){if(4!=b.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+b);});r.call(this,a.i);this.h=a;this.c=b;bb(this,a.g||ka(b,function(a){return a.g}));cb(this,a.D&&!b.length||a.C&&!!b.length||ka(b,function(a){return a.b}))}q(jb); +jb.prototype.a=function(a){return this.h.m.apply(null,ma(a,this.c))};jb.prototype.toString=function(){var a="Function: "+this.h;if(this.c.length)var b=x(this.c,function(a,b){return a+O(b)},"Arguments:"),a=a+O(b);return a};function kb(a,b,c,d,e,f,g,k,p){this.j=a;this.i=b;this.g=c;this.D=d;this.C=e;this.m=f;this.A=g;this.v=void 0!==k?k:g;this.B=!!p}kb.prototype.toString=h("j");var lb={}; +function U(a,b,c,d,e,f,g,k){if(lb.hasOwnProperty(a))throw Error("Function already created: "+a+".");lb[a]=new kb(a,b,c,d,!1,e,f,g,k)}U("boolean",2,!1,!1,function(a,b){return R(b,a)},1);U("ceiling",1,!1,!1,function(a,b){return Math.ceil(P(b,a))},1);U("concat",3,!1,!1,function(a,b){var c=na(arguments,1);return x(c,function(b,c){return b+Q(c,a)},"")},2,null);U("contains",2,!1,!1,function(a,b,c){return u(Q(b,a),Q(c,a))},2);U("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0); +U("false",2,!1,!1,l(!1),0);U("floor",1,!1,!1,function(a,b){return Math.floor(P(b,a))},1);U("id",4,!1,!1,function(a,b){function c(a){if(B){var b=e.all[a];if(b){if(b.nodeType&&a==b.id)return b;if(b.length)return la(b,function(b){return a==b.id})}return null}return e.getElementById(a)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument,d=Q(b,a).split(/\s+/),f=[];w(d,function(a){a=c(a);!a||0<=ia(f,a)||f.push(a)});f.sort(Ja);var g=new I;w(f,function(a){K(g,a)});return g},1);U("lang",2,!1,!1,l(!1),1); +U("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);U("local-name",3,!1,!0,function(a,b){var c=b?Wa(b.a(a)):a.a;return c?c.localName||c.nodeName.toLowerCase():""},0,1,!0);U("name",3,!1,!0,function(a,b){var c=b?Wa(b.a(a)):a.a;return c?c.nodeName.toLowerCase():""},0,1,!0);U("namespace-uri",3,!0,!1,l(""),0,1,!0);U("normalize-space",3,!1,!0,function(a,b){return(b?Q(b,a):G(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1); +U("not",2,!1,!1,function(a,b){return!R(b,a)},1);U("number",1,!1,!0,function(a,b){return b?P(b,a):+G(a.a)},0,1);U("position",1,!0,!1,function(a){return a.b},0);U("round",1,!1,!1,function(a,b){return Math.round(P(b,a))},1);U("starts-with",2,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);return 0==b.lastIndexOf(a,0)},2);U("string",3,!1,!0,function(a,b){return b?Q(b,a):G(a.a)},0,1);U("string-length",1,!1,!0,function(a,b){return(b?Q(b,a):G(a.a)).length},0,1); +U("substring",3,!1,!1,function(a,b,c,d){c=P(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?P(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=Q(b,a);if(Infinity==d)return a.substring(e);b=Math.round(d);return a.substring(e,c+b)},2,3);U("substring-after",3,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2); +U("substring-before",3,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);U("sum",1,!1,!1,function(a,b){for(var c=M(b.a(a)),d=0,e=N(c);e;e=N(c))d+=+G(e);return d},1,1,!0);U("translate",3,!1,!1,function(a,b,c,d){b=Q(b,a);c=Q(c,a);var e=Q(d,a);a=[];for(d=0;da.length)throw Error("Unclosed literal string");return new nb(a)}function Lb(a){var b=F(a.a),c=b.indexOf(":");if(-1==c)return new J(b);var d=b.substring(0,c);a=a.b(d);if(!a)throw Error("Namespace prefix not declared: "+d);b=b.substr(c+1);return new J(b,a)} +function Mb(a){var b,c=[],d;if(tb(D(a.a))){b=F(a.a);d=D(a.a);if("/"==b&&(Ha(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new rb;d=new rb;X(a,"Missing next location step.");b=Nb(a,b);c.push(b)}else{a:{b=D(a.a);d=b.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":F(a.a);b=Gb(a);X(a,'unclosed "("');Ib(a,")");break;case '"':case "'":b=Kb(a);break;default:if(isNaN(+b))if(!mb(b)&&/(?![0-9])[\w]/.test(d)&&"("==D(a.a,1)){b=F(a.a); +b=lb[b]||null;F(a.a);for(d=[];")"!=D(a.a);){X(a,"Missing function argument list.");d.push(Gb(a));if(","!=D(a.a))break;F(a.a)}X(a,"Unclosed function argument list.");Jb(a);b=new jb(b,d)}else{b=null;break a}else b=new ob(+F(a.a))}"["==D(a.a)&&(d=new wb(Ob(a)),b=new hb(b,d))}if(b)if(tb(D(a.a)))d=b;else return b;else b=Nb(a,"/"),d=new sb,c.push(b)}for(;tb(D(a.a));)b=F(a.a),X(a,"Missing next location step."),b=Nb(a,b),c.push(b);return new pb(d,c)} +function Nb(a,b){var c,d,e;if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==D(a.a))return d=new V(Cb,new L("node")),F(a.a),d;if(".."==D(a.a))return d=new V(Bb,new L("node")),F(a.a),d;var f;if("@"==D(a.a))f=qb,F(a.a),X(a,"Missing attribute name");else if("::"==D(a.a,1)){if(!/(?![0-9])[\w]/.test(D(a.a).charAt(0)))throw Error("Bad token: "+F(a.a));c=F(a.a);f=Ab[c]||null;if(!f)throw Error("No axis with name: "+c);F(a.a);X(a,"Missing node name")}else f=xb;c=D(a.a);if(/(?![0-9])[\w]/.test(c.charAt(0)))if("("== +D(a.a,1)){if(!mb(c))throw Error("Invalid node type: "+c);c=F(a.a);if(!mb(c))throw Error("Invalid type name: "+c);Ib(a,"(");X(a,"Bad nodetype");e=D(a.a).charAt(0);var g=null;if('"'==e||"'"==e)g=Kb(a);X(a,"Bad nodetype");Jb(a);c=new L(c,g)}else c=Lb(a);else if("*"==c)c=Lb(a);else throw Error("Bad token: "+F(a.a));e=new wb(Ob(a),f.a);return d||new V(f,c,e,"//"==b)} +function Ob(a){for(var b=[];"["==D(a.a);){F(a.a);X(a,"Missing predicate expression.");var c=Gb(a);b.push(c);X(a,"Unclosed predicate expression.");Ib(a,"]")}return b}function Hb(a){if("-"==D(a.a))return F(a.a),new Db(Hb(a));var b=Mb(a);if("|"!=D(a.a))a=b;else{for(b=[b];"|"==F(a.a);)X(a,"Missing next union location path."),b.push(Mb(a));a.a.a--;a=new Eb(b)}return a};function Pb(a,b){if(!a.length)throw Error("Empty XPath expression.");var c=Ea(a);if(Ha(c))throw Error("Invalid XPath expression.");b?"function"==ba(b)||(b=ea(b.lookupNamespaceURI,b)):b=l(null);var d=Gb(new Fb(c,b));if(!Ha(c))throw Error("Bad token: "+F(c));this.evaluate=function(a,b){var c=d.a(new t(a));return new Y(c,b)}} +function Y(a,b){if(0==b)if(a instanceof I)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof I))throw Error("value could not be converted to the specified type");this.resultType=b;var c;switch(b){case 2:this.stringValue=a instanceof I?Xa(a):""+a;break;case 1:this.numberValue=a instanceof I?+Xa(a):+a;break;case 3:this.booleanValue=a instanceof I?0=c.length?null:c[f++]};this.snapshotItem=function(a){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return a>=c.length|| +0>a?null:c[a]}}Y.ANY_TYPE=0;Y.NUMBER_TYPE=1;Y.STRING_TYPE=2;Y.BOOLEAN_TYPE=3;Y.UNORDERED_NODE_ITERATOR_TYPE=4;Y.ORDERED_NODE_ITERATOR_TYPE=5;Y.UNORDERED_NODE_SNAPSHOT_TYPE=6;Y.ORDERED_NODE_SNAPSHOT_TYPE=7;Y.ANY_UNORDERED_NODE_TYPE=8;Y.FIRST_ORDERED_NODE_TYPE=9;function Qb(a){this.lookupNamespaceURI=Za(a)} +function Rb(a){a=a||m;var b=a.document;b.evaluate||(a.XPathResult=Y,b.evaluate=function(a,b,e,f){return(new Pb(a,e)).evaluate(b,f)},b.createExpression=function(a,b){return new Pb(a,b)},b.createNSResolver=function(a){return new Qb(a)})}var Sb=["wgxpath","install"],Z=m;Sb[0]in Z||!Z.execScript||Z.execScript("var "+Sb[0]);for(var Tb;Sb.length&&(Tb=Sb.shift());)Sb.length||void 0===Rb?Z[Tb]?Z=Z[Tb]:Z=Z[Tb]={}:Z[Tb]=Rb;})() diff --git a/js/personal.js b/js/personal.js new file mode 100644 index 0000000..bf39caf --- /dev/null +++ b/js/personal.js @@ -0,0 +1,22 @@ +$(document).ready(function(){ + // save settings + var readerSettings = { + save : function() { + var data = { + EpubEnable: document.getElementById('EpubEnable').checked ? 'true' : 'false', + PdfEnable: document.getElementById('PdfEnable').checked ? 'true' : 'false', + CbxEnable: document.getElementById('CbxEnable').checked ? 'true' : 'false' + }; + + OC.msg.startSaving('#reader-personal .msg'); + $.post(OC.filePath('files_reader', 'lib', 'personal-back.php'), data, readerSettings.afterSave); + }, + afterSave : function(data){ + OC.msg.finishedSaving('#reader-personal .msg', data); + } + }; + $('#EpubEnable').on("change", readerSettings.save); + $('#PdfEnable').on("change", readerSettings.save); + $('#CbxEnable').on("change", readerSettings.save); +}); + diff --git a/js/plugin.js b/js/plugin.js new file mode 100644 index 0000000..2df289e --- /dev/null +++ b/js/plugin.js @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2015-2017 Frank de Lange + * Copyright (c) 2013-2014 Lukas Reschke + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + + +(function(OCA) { + + OCA.Files_Reader = OCA.Files_Reader || {}; + + var isMobile = navigator.userAgent.match(/Mobi/i); + var hasTouch = 'ontouchstart' in document.documentElement; + + function actionHandler(fileName, mime, context) { + var downloadUrl = ''; + if($('#isPublic').val()) { + var sharingToken = $('#sharingToken').val(); + downloadUrl = OC.generateUrl('/s/{token}/download?files={files}&path={path}', { + token: sharingToken, + files: fileName, + path: context.dir + }); + } else { + downloadUrl = Files.getDownloadUrl(fileName, context.dir); + } + OCA.Files_Reader.Plugin.show(downloadUrl, mime, true); + } + + /** + * @namespace OCA.Files_Reader.Plugin + */ + OCA.Files_Reader.Plugin = { + + /** + * @param fileList + */ + attach: function(fileList) { + this._extendFileActions(fileList.fileActions); + }, + + hideControls: function() { + $('#app-content #controls').hide(); + // and, for NC12... + $('#app-navigation').css("display", "none"); + }, + + hide: function() { + if ($('#fileList').length) { + FileList.setViewerMode(false); + } + $("#controls").show(); + $('#app-content #controls').removeClass('hidden'); + // NC12... + $('#app-navigation').css("display", ""); + if ($('#isPublic').val()) { + $('#imgframe').show(); + $('footer').show(); + $('.directLink').show(); + $('.directDownload').show(); + } + $('iframe').remove(); + $('body').off('focus.filesreader'); + $(window).off('popstate.filesreader'); + }, + + /** + * @param downloadUrl + * @param isFileList + */ + show: function(downloadUrl, mimeType, isFileList) { + var self = this; + var viewer = OC.generateUrl('/apps/files_reader/?file={file}&type={type}', {file: downloadUrl, type: mimeType}); + // launch in new window on all devices + window.open(viewer, downloadUrl); + }, + + /** + * @param fileActions + * @private + */ + _extendFileActions: function(fileActions) { + var self = this; + var cbxMime = [ + 'application/x-cbr', + 'application/comicbook+7z', + 'application/comicbook+ace', + 'application/comicbook+rar', + 'application/comicbook+tar', + 'application/comicbook+truecrypt', + 'application/comicbook+zip' + ]; + + fileActions.registerAction({ + name: 'view-epub', + displayName: 'View', + mime: 'application/epub+zip', + permissions: OC.PERMISSION_READ, + actionHandler: function(fileName, context){ + return actionHandler(fileName, 'application/epub+zip', context); + } + }); + + cbxMime.forEach(function(mime, i){ + fileActions.registerAction({ + name: 'view-cbr-' + i, + displayName: 'View', + mime: mime, + permissions: OC.PERMISSION_READ, + actionHandler: function (fileName, context) { + return actionHandler(fileName, 'application/x-cbr', context); + } + }); + + if (oc_appconfig.filesReader.enableCbx === 'true') + fileActions.setDefault(mime, 'view-cbr-' + i); + }); + + fileActions.registerAction({ + name: 'view-pdf', + displayName: 'View', + mime: 'application/pdf', + permissions: OC.PERMISSION_READ, + actionHandler: function(fileName, context) { + return actionHandler(fileName, 'application/pdf', context); + } + }); + + if (oc_appconfig.filesReader.enableEpub === 'true') + fileActions.setDefault('application/epub+zip', 'view-epub'); + if (oc_appconfig.filesReader.enablePdf === 'true') + fileActions.setDefault('application/pdf', 'view-pdf'); + } + }; + +})(OCA); + +OC.Plugins.register('OCA.Files.FileList', OCA.Files_Reader.Plugin); + +// FIXME: Hack for single public file view since it is not attached to the fileslist +$(document).ready(function(){ + if ($('#isPublic').val() + && ($('#mimetype').val() === 'application/epub+zip' + || $('#mimetype').val() === 'application/pdf' + || $('#mimetype').val() === 'application/x-cbr') + ) { + var sharingToken = $('#sharingToken').val(); + var downloadUrl = OC.generateUrl('/s/{token}/download', {token: sharingToken}); + var viewer = OCA.Files_Reader.Plugin; + var mime = $('#mimetype').val(); + viewer.show(downloadUrl, mime, false); + } +}); diff --git a/js/ready.js b/js/ready.js new file mode 100644 index 0000000..721c1df --- /dev/null +++ b/js/ready.js @@ -0,0 +1,154 @@ +document.onreadystatechange = function () { + + if (document.readyState == "complete") { + + var type=decodeURIComponent(getUrlParameter('type')), + file=decodeURIComponent(getUrlParameter('file')), + options = {}, + $session = $('.session'); + + options.session = {}; + options.session.filename = decodeURI($session.data('filename')); + options.session.format = $session.data('filetype'); + options.session.fileId = $session.data('fileid'); + options.session.title = options.session.filename; + options.session.nonce = $session.data('nonce') || ""; + options.session.version = $session.data('version') || ""; + options.session.metadata = $session.data('metadata') || {}; + options.session.annotations = $session.data('annotations') || {}; + options.session.fileId = $session.data('fileid') || ""; + options.session.scope = $session.data('scope') || ""; + options.session.cursor = $session.data('cursor') || {}; + options.session.defaults = $session.data('defaults') || {}; + options.session.preferences = $session.data('preferences') || {}; + options.session.defaults = $session.data('defaults') || {}; + options.session.basePath = $session.data('basepath'); + options.session.downloadLink = $session.data('downloadlink'); + + console.log(options.session.basePath); + + + /* functions return jquery promises */ + options.session.getPreference = function(name) { + return $.get(options.session.basePath + "preference/" + options.session.fileId + "/" + options.session.scope + "/" + name); + }; + options.session.setPreference = function(name, value) { + return $.post(options.session.basePath + "preference", + { + "fileId": options.session.fileId, + "scope": options.session.scope, + "name": name, + "value": JSON.stringify(value) + }); + }; + options.session.deletePreference = function(name) { + return $.delete(options.session.basePath + "preference/" + options.session.fileId + "/" + options.session.scope + "/" + name); + }; + options.session.getDefault = function(name) { + return $.get(options.session.basePath + "preference/default/" + options.session.scope + "/" + name); + }; + options.session.setDefault = function(name, value) { + return $.post(options.session.basePath + "preference/default", + { + "scope": options.session.scope, + "name": name, + "value": JSON.stringify(value) + }); + }; + options.session.deleteDefault = function(name) { + return $.delete(options.session.basePath + "preference/default/" + options.session.scope + "/" + name); + }; + options.session.getBookmark = function(name, type) { + return $.get(options.session.basePath + "bookmark/" + options.session.fileId + "/" + type + "/" + name); + }; + options.session.setBookmark = function(name, value, type, content) { + return $.post(options.session.basePath + "bookmark", + { + "fileId": options.session.fileId, + "name": name, + "value": JSON.stringify(value), + "type": type, + "content": JSON.stringify(content) + }); + }; + options.session.deleteBookmark = function(name) { + return $.delete(options.session.basePath + "bookmark/" + options.session.fileId + "/" + name); + }; + options.session.getCursor = function() { + return $.get(options.session.basePath + "bookmark/cursor/" + options.session.fileId); + }; + options.session.setCursor = function(value) { + return $.post(options.session.basePath + "bookmark/cursor", + { + "fileId": options.session.fileId, + "value": JSON.stringify(value) + }); + }; + options.session.deleteCursor = function() { + return $.delete(options.session.basePath + "bookmark/cursor/" + options.session.fileId); + }; + + switch (type) { + case 'application/epub+zip': + options['contained'] = true; + renderEpub(file, options); + break; + case 'application/x-cbr': + renderCbr(file, options); + break; + case 'application/pdf': + renderPdf(file, options); + break; + default: + console.log(type + ' is not supported by Reader'); + } + } + + // why is there no standard library function for this? + function getUrlParameter (param) { + var pattern = new RegExp('[?&]'+param+'((=([^&]*))|(?=(&|$)))','i'); + var m = window.location.search.match(pattern); + return m && ( typeof(m[3])==='undefined' ? '' : m[3] ); + } + + // start epub.js renderer + function renderEpub(file, options) { + + // some parameters... + EPUBJS.filePath = "vendor/epubjs/"; + EPUBJS.cssPath = "vendor/epubjs/css/"; + EPUBJS.basePath = $('.session').data('basepath'); + + /* device-specific boilerplate */ + + // IE < 11 + if (navigator.userAgent.indexOf("MSIE") != -1) { + EPUBJS.Hooks.register("beforeChapterDisplay").wgxpath = function(callback, renderer){ + wgxpath.install(renderer.render.window); + if(callback) + callback(); + }; + wgxpath.install(window); + } + + var reader = ePubReader(file, options); + } + + // start cbr.js renderer + function renderCbr(file, options) { + CBRJS.filePath = "vendor/cbrjs/"; + + var reader = cbReader(file, options); + } + + // start pdf.js renderer + function renderPdf(file, options) { + PDFJS.filePath = "vendor/pdfjs/"; + PDFJS.imageResourcesPath = "vendor/pdfjs/css/images/"; + PDFJS.workerSrc = options.session.basePath + 'vendor/pdfjs/lib/pdf.worker.js'; + + var reader = pdfReader(file, options); + } + +}; + diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..156ec03 --- /dev/null +++ b/js/settings.js @@ -0,0 +1,22 @@ +$(document).ready(function(){ + // save settings + var readerSettings = { + save : function() { + var data = { + EpubEnable: document.getElementById('EpubEnable').checked ? 'true' : 'false', + PdfEnable: document.getElementById('PdfEnable').checked ? 'true' : 'false', + CbxEnable: document.getElementById('CbxEnable').checked ? 'true' : 'false' + }; + + OC.msg.startSaving('#reader-personal .msg'); + $.post(OC.filePath('files_reader', 'ajax', 'personal.php'), data, readerSettings.afterSave); + }, + afterSave : function(data){ + OC.msg.finishedSaving('#reader-personal .msg', data); + } + }; + $('#EpubEnable').on("change", readerSettings.save); + $('#PdfEnable').on("change", readerSettings.save); + $('#CbxEnable').on("change", readerSettings.save); +}); + diff --git a/lib/Controller/BookmarkController.php b/lib/Controller/BookmarkController.php new file mode 100644 index 0000000..1c6851c --- /dev/null +++ b/lib/Controller/BookmarkController.php @@ -0,0 +1,124 @@ +bookmarkService = $bookmarkService; + } + + /** + * @brief return bookmark + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * @param string $name + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function get($fileId, $name, $type=null) { + return $this->bookmarkService->get($fileId, $name, $type); + } + + /** + * @brief write bookmark + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * @param string $name + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function set($fileId, $name, $value, $type=null, $content=null) { + return $this->bookmarkService->set($fileId, $name, $value, $type, $content); + } + + + /** + * @brief return cursor for $fileId + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function getCursor($fileId) { + return $this->bookmarkService->getCursor($fileId); + } + + /** + * @brief write cursor for $fileId + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function setCursor($fileId, $value) { + return $this->bookmarkService->setCursor($fileId, $value); + } + + /** + * @brief delete bookmark + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * @param string name + * + */ + public function delete($fileId, $name) { + return $this->bookmarkService->delete($fileId, $name); + } + + /** + * @brief delete cursor + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * + */ + public function deleteCursor($fileId) { + return $this->bookmarkService->deleteCursor($fileId); + } +} diff --git a/lib/Controller/MetadataController.php b/lib/Controller/MetadataController.php new file mode 100644 index 0000000..15eafad --- /dev/null +++ b/lib/Controller/MetadataController.php @@ -0,0 +1,80 @@ +metadataService = $metadataService; + } + + + /** + * @brief write metadata + * + * @NoAdminRequired + * + * @param int $fileId + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function setAll($fileId, $value) { + return $this->metadataService->setAll($fileId, $value); + } + + /** + * @brief return metadata item + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $fileId + * @param string $name + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function get($fileId, $name) { + return $this->metadataService->get($fileId, $name); + } + + /** + * @brief write metadata item + * + * @NoAdminRequired + * + * @param int $fileId + * @param string $name + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function set($fileId, $name, $value) { + return $this->metadataService->set($fileId, $name, $value); + } + +} diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php new file mode 100644 index 0000000..8a500d1 --- /dev/null +++ b/lib/Controller/PageController.php @@ -0,0 +1,177 @@ +urlGenerator = $urlGenerator; + $this->rootFolder = $rootFolder; + $this->shareManager = $shareManager; + $this->userId = $UserId; + $this->bookmarkService = $bookmarkService; + $this->metadataService = $metadataService; + $this->preferenceService = $preferenceService; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function showReader() { + $templates= [ + 'application/epub+zip' => 'epubreader', + 'application/x-cbr' => 'cbreader', + 'application/pdf' => 'pdfreader' + ]; + + /** + * $fileInfo = [ + * fileId => null, + * fileName => null, + * fileType => null + * ]; + */ + $fileInfo = $this->getFileInfo($this->request->get['file']); + $fileId = $fileInfo['fileId']; + $type = $this->request->get["type"]; + $scope = $template = $templates[$type]; + + $params = [ + 'urlGenerator' => $this->urlGenerator, + 'downloadLink' => $this->request->get['file'], + 'scope' => $scope, + 'fileId' => $fileInfo['fileId'], + 'fileName' => $fileInfo['fileName'], + 'fileType' => $fileInfo['fileType'], + 'cursor' => $this->toJson($this->bookmarkService->getCursor($fileId)), + 'defaults' => $this->toJson($this->preferenceService->getDefault($scope)), + 'preferences' => $this->toJson($this->preferenceService->get($scope, $fileId)), + 'defaults' => $this->toJson($this->preferenceService->getDefault($scope)), + 'metadata' => $this->toJson($this->metadataService->get($fileId)), + 'annotations' => $this->toJson($this->bookmarkService->get($fileId)) + ]; + + $policy = new ContentSecurityPolicy(); + $policy->addAllowedStyleDomain('\'self\''); + $policy->addAllowedStyleDomain('blob:'); + $policy->addAllowedScriptDomain('\'self\''); + $policy->addAllowedFrameDomain('\'self\''); + $policy->addAllowedChildSrcDomain('\'self\''); + $policy->addAllowedFontDomain('\'self\''); + $policy->addAllowedFontDomain('data:'); + $policy->addAllowedFontDomain('blob:'); + $policy->addAllowedImageDomain('blob:'); + + $response = new TemplateResponse($this->appName, $template, $params, 'blank'); + $response->setContentSecurityPolicy($policy); + + return $response; + } + + /** + * @brief sharing-aware file info retriever + * + * Work around the differences between normal and shared file access + * (this should be abstracted away in OC/NC IMnsHO) + * + * @param string $path path-fragment from url + * @return array + * @throws NotFoundException + */ + private function getFileInfo($path) { + $count = 0; + $shareToken = preg_replace("/(?:\/index\.php)?\/s\/([A-Za-z0-9]{15,32})\/download.*/", "$1", $path, 1,$count); + + if ($count === 1) { + + /* shared file or directory */ + $node = $this->shareManager->getShareByToken($shareToken)->getNode(); + $type = $node->getType(); + + /* shared directory, need file path to continue, */ + if ($type == \OCP\Files\FileInfo::TYPE_FOLDER) { + $query = []; + parse_str(parse_url($path, PHP_URL_QUERY), $query); + if (isset($query['path']) && isset($query['files'])) { + $node = $node->get($query['path'])->get($query['files']); + } else { + throw new NotFoundException('Shared file path or name not set'); + } + } + $filePath = $node->getPath(); + $fileId = $node->getId(); + } else { + $filePath = $path; + $fileId = $this->rootFolder->getUserFolder($this->userId) + ->get(preg_replace("/.*\/remote.php\/webdav(.*)/", "$1", rawurldecode($this->request->get['file']))) + ->getFileInfo() + ->getId(); + } + + return [ + 'fileName' => pathInfo($filePath, PATHINFO_FILENAME), + 'fileType' => strtolower(pathInfo($filePath, PATHINFO_EXTENSION)), + 'fileId' => $fileId + ]; + } + + private function toJson($value) { + return htmlspecialchars(json_encode($value), ENT_QUOTES, 'UTF-8'); + } +} diff --git a/lib/Controller/PreferenceController.php b/lib/Controller/PreferenceController.php new file mode 100644 index 0000000..e7d6d14 --- /dev/null +++ b/lib/Controller/PreferenceController.php @@ -0,0 +1,128 @@ +urlGenerator = $urlGenerator; + $this->preferenceService = $preferenceService; + } + + /** + * @brief return preference for $fileId + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $scope + * @param int $fileId + * @param string $name if null, return all preferences for $scope + $fileId + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function get($scope, $fileId, $name) { + return $this->preferenceService->get($scope, $fileId, $name); + } + + /** + * @brief write preference for $fileId + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $scope + * @param int $fileId + * @param string $name + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function set($scope, $fileId, $name, $value) { + return $this->preferenceService->set($scope, $fileId, $name, $value); + } + + + /** + * @brief return default preference + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $scope + * @param string $name if null, return all default preferences for scope + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function getDefault($scope, $name) { + return $this->preferenceService->getDefault($scope, $name); + } + + /** + * @brief write default preference + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param string $scope + * @param string $name + * @param string $value + * + * @return array|\OCP\AppFramework\Http\JSONResponse + */ + public function setDefault($scope, $name, $value) { + return $this->preferenceService->setDefault($scope, $name, $value); + } + + /** + * @brief delete preference + * + * @param string $scope + * @param int $fileId + * @param string $name + * + */ + public function delete($scope, $fileId, $name) { + return $this->preferenceService->delete($scope, $fileId, $name); + } + + /** + * @brief delete default preference + * + * @param $scope + * @param $name + * + */ + public function deleteDefault($scope, $name) { + return $this->preferenceService->deleteDefault($scope, $name); + } +} diff --git a/lib/Db/Bookmark.php b/lib/Db/Bookmark.php new file mode 100644 index 0000000..0390ab8 --- /dev/null +++ b/lib/Db/Bookmark.php @@ -0,0 +1,48 @@ + $this->getId(), + 'userId' => $this->getUserId(), + 'fileId' => $this->getFileId(), + 'type' => $this->getType(), + 'name' => $this->getName(), + 'value' => static::conditional_json_decode($this->getValue()), + 'content' => static::conditional_json_decode($this->getContent()), + 'lastModified' => $this->getLastModified() + ]; + } + + public function toService() { + return [ + 'name' => $this->getName(), + 'type' => $this->getType(), + 'value' => $this->conditional_json_decode($this->getValue()), + 'content' => $this->conditional_json_decode($this->getContent()), + 'lastModified' => $this->getLastModified(), + ]; + } +} + diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php new file mode 100644 index 0000000..292db93 --- /dev/null +++ b/lib/Db/BookmarkMapper.php @@ -0,0 +1,121 @@ +userId = $UserId; + } + + /** + * @brief get bookmarks for $fileId+$userId(+$name) + * @param $fileId + * @param string $name + * @return array + */ + public function get($fileId, $name, $type=null) { + $sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE file_id=? AND `user_id`=?"; + $args = [ $fileId, $this->userId ]; + if (!(null === $type)) { + $sql .= " AND `type`=?"; + $args[] = $type; + } + if (!(null === $name)) { + $sql .= " AND `name`=?"; + $args[] = $name; + } + + return $this->findEntities($sql, $args); + } + + /** + * @brief write bookmark to database + * + * @param int $fileId + * @param string $name + * @param string $value + * + * @return Bookmark the newly created or updated bookmark + */ + public function set($fileId, $name, $value, $type, $content=null) { + + $result = $this->get($fileId, $name); + + if(empty($result)) { + + // anonymous bookmarks are named after their contents + if (null === $name) { + $name = $value; + } + + // default type is "bookmark" + if (null === $type) { + $type = "bookmark"; + } + + $bookmark = new Bookmark(); + $bookmark->setFileId($fileId); + $bookmark->setUserId($this->userId); + $bookmark->setType($type); + $bookmark->setName($name); + $bookmark->setValue($value); + $bookmark->setContent($content); + + $this->insert($bookmark); + } else { + $bookmark = $result[0]; + $bookmark->setValue($value); + $bookmark->setContent($content); + + $this->update($bookmark); + } + + return $bookmark; + } + + /* currently not used */ + public function deleteForFileId($fileId) { + $sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE file_id=?"; + $args = [ $fileId ]; + array_map( + function($entity) { + $this->delete($entity); + }, $this->findEntities($sql, $args) + ); + } + + /* currently not used */ + public function deleteForUserId($userId) { + $sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE user_id=?"; + $args = [ $userId ]; + array_map( + function($entity) { + $this->delete($entity); + }, $this->findEntities($sql, $args) + ); + } +} + diff --git a/lib/Db/Preference.php b/lib/Db/Preference.php new file mode 100644 index 0000000..1baf1f6 --- /dev/null +++ b/lib/Db/Preference.php @@ -0,0 +1,42 @@ + $this->getId(), + 'scope' => $this->getScope(), + 'fileId' => $this->getFileId(), + 'name' => $this->getName(), + 'value' => $this->conditional_json_decode($this->getValue()), + 'lastModified' => $this->getLastModified(), + ]; + } + + public function toService() { + return [ + 'name' => $this->getName(), + 'value' => $this->conditional_json_decode($this->getValue()), + ]; + } +} + diff --git a/lib/Db/PreferenceMapper.php b/lib/Db/PreferenceMapper.php new file mode 100644 index 0000000..350ea00 --- /dev/null +++ b/lib/Db/PreferenceMapper.php @@ -0,0 +1,105 @@ +userId = $UserId; + } + + /** + * @brief get preferences for $scope+$fileId+$userId(+$name) + * + * @param string $scope + * @param int $fileId + * @param string $name + * @return array + */ + public function get($scope, $fileId, $name=null) { + if(!empty($name)) { + $sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE `scope`=? AND `file_id`=? AND `user_id`=? AND `name`=?"; + $args = array( + $scope, + $fileId, + $this->userId, + $name); + } else { + $sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE `scope`=? AND `file_id`=? AND `user_id`=?"; + $args = array( + $scope, + $fileId, + $this->userId); + } + + return $this->findEntities($sql, $args); + } + + /** + * @brief write preference to database + * + * @param string $scope + * @param int $fileId + * @param string $name + * @param string $value + * + * @return Preference the newly created or updated preference + */ + public function set($scope, $fileId, $name, $value) { + + $result = $this->get($scope, $fileId, $name); + + if(empty($result)) { + + $preference = new Preference(); + $preference->setScope($scope); + $preference->setFileId($fileId); + $preference->setUserId($this->userId); + $preference->setName($name); + $preference->setValue($value); + + $this->insert($preference); + } else { + $preference = $result[0]; + $preference->setValue($value); + + $this->update($preference); + } + + return $preference; + } + + /* currently not used*/ + public function deleteForFileId($fileId) { + $sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE file_id=?"; + $args = [ $fileId ]; + array_map( + function($entity) { + $this->delete($entity); + }, $this->findEntities($sql, $args) + ); + } + + /* currently not used*/ + public function deleteForUserId($userId) { + $sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE user_id=?"; + $args = [ $userId ]; + array_map( + function($entity) { + $this->delete($entity); + }, $this->findEntities($sql, $args) + ); + } +} diff --git a/lib/Db/ReaderEntity.php b/lib/Db/ReaderEntity.php new file mode 100644 index 0000000..c6f3a58 --- /dev/null +++ b/lib/Db/ReaderEntity.php @@ -0,0 +1,39 @@ + $this->getName(), + 'type' => $this->getType(), + 'value' => $this->conditional_json_decode($this->getValue()), + 'content' => $this->conditional_json_decode($this->getContent()), + 'lastModified' => $this->getLastModified(), + ]; + } +*/ +} + diff --git a/lib/Db/ReaderMapper.php b/lib/Db/ReaderMapper.php new file mode 100644 index 0000000..efe7bf1 --- /dev/null +++ b/lib/Db/ReaderMapper.php @@ -0,0 +1,40 @@ +time = $time; + } + + public function update(Entity $entity) { + $entity->setLastModified($this->time->getMicroTime()); + return parent::update($entity); + } + + public function insert(Entity $entity) { + $entity->setLastModified($this->time->getMicroTime()); + return parent::insert($entity); + } +} diff --git a/lib/Hooks.php b/lib/Hooks.php new file mode 100644 index 0000000..15025e2 --- /dev/null +++ b/lib/Hooks.php @@ -0,0 +1,70 @@ +getRootFolder()->listen('\OC\Files', 'preDelete', function (Node $node) { + $fileId = $node->getId(); + $connection = \OC::$server->getDatabaseConnection(); + self::deleteFile($connection, $fileId); + }); + \OC::$server->getUserManager()->listen('\OC\User', 'preDelete', function (User $user) { + $userId = $user->getUID(); + $connection = \OC::$server->getDatabaseConnection(); + self::deleteUser($connection, $userId); + }); + } + + public static function announce_settings(array $settings) { + // Nextcloud encodes this as JSON, Owncloud does not (yet) (#75) + // TODO: rmeove this when Owncloud starts encoding oc_appconfig as JSON just like it already encodes most other properties + $isJson = self::isJson($settings['array']['oc_appconfig']); + $array = ($isJson) ? json_decode($settings['array']['oc_appconfig'], true) : $settings['array']['oc_appconfig']; + $array['filesReader']['enableEpub'] = Config::get('epub_enable', 'true'); + $array['filesReader']['enablePdf'] = Config::get('pdf_enable', 'true'); + $array['filesReader']['enableCbx'] = Config::get('cbx_enable', 'true'); + $settings['array']['oc_appconfig'] = ($isJson) ? json_encode($array) : $array; + } + + protected static function deleteFile(IDBConnection $connection, $fileId) { + $queryBuilder = $connection->getQueryBuilder(); + $queryBuilder->delete('reader_bookmarks')->where('file_id = :file_id')->setParameter(':file_id', $fileId); + $queryBuilder->execute(); + + $queryBuilder = $connection->getQueryBuilder(); + $queryBuilder->delete('reader_preferences')->where('file_id = :file_id')->setParameter(':file_id', $fileId); + $queryBuilder->execute(); + } + + protected static function deleteUser(IDBConnection $connection, $userId) { + $queryBuilder = $connection->getQueryBuilder(); + $queryBuilder->delete('reader_bookmarks')->where('user_id = :user_id')->setParameter(':user_id', $userId); + $queryBuilder->execute(); + + $queryBuilder = $connection->getQueryBuilder(); + $queryBuilder->delete('reader_preferences')->where('user_id = :user_id')->setParameter(':user_id', $userId); + $queryBuilder->execute(); + } + + private static function isJson($string) { + return is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE) ? true : false; + } +} diff --git a/lib/Service/BookmarkService.php b/lib/Service/BookmarkService.php new file mode 100644 index 0000000..9f147b3 --- /dev/null +++ b/lib/Service/BookmarkService.php @@ -0,0 +1,113 @@ +bookmarkMapper = $bookmarkMapper; + $this->userId = $UserId; + } + + /** + * @brief get bookmark + * + * bookmark type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc + * + * @param int $fileId + * @param string $name + * + * @return array + */ + public function get($fileId, $name=null, $type=null) { + $result = $this->bookmarkMapper->get($fileId, $name, $type); + return array_map( + function($entity) { + return $entity->toService(); + }, $result); + } + + /** + * @brief write bookmark + * + * position type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc + * + * @param int $fileId + * @param string $name + * @param string $value + * + * @return array + */ + public function set($fileId, $name, $value, $type=null, $content=null) { + return $this->bookmarkMapper->set($fileId, $name, $value, $type, $content); + } + + /** + * @brief get cursor (current position in book) + * + * @param int $fileId + * + * @return array + */ + public function getCursor($fileId) { + $result = $this->get($fileId, static::CURSOR); + if (count($result) === 1) { + return $result[0]; + } + } + + /** + * @brief set cursor (current position in book) + * + * @param int $fileId + * @param string $value + * + * @return array + */ + public function setCursor($fileId, $value) { + return $this->bookmarkMapper->set($fileId, static::CURSOR, $value, static::bookmark_type); + } + + /** + * @brief delete bookmark + * + * @param int $fileId + * @param string $name + * + */ + public function delete($fileId, $name, $type=null) { + foreach ($this->bookmarkMapper->get($fileId, $name, $type) as $bookmark) { + $this->bookmarkMapper->delete($bookmark); + } + } + + /** + * @brief delete cursor + * + * @param int $fileId + * + */ + public function deleteCursor($fileId) { + $this->delete($fileId, static::CURSOR, static::bookmark_type); + } +} + diff --git a/lib/Service/MetadataService.php b/lib/Service/MetadataService.php new file mode 100644 index 0000000..934682e --- /dev/null +++ b/lib/Service/MetadataService.php @@ -0,0 +1,75 @@ +appManager = $appManager; + } + + /** + * @brief get metadata item(s) + * + * @param int $fileId + * @param string $name + * + * @return array + */ + public function get($fileId, $name=null) { + if ($this->appManager->isInstalled('files_opds')) { + if ($meta = \OCA\Files_Opds\Meta::get($fileId)) { + if (!empty($name) && array_key_exists($name, $meta)) { + return [$item => $meta[$name]]; + } else { + return $meta; + } + } + } + + return []; + } + + /** + * @brief write metadata to database + * + * @param int $fileId + * @param array $value + * + * @return array + */ + public function setAll($fileId, $value) { + // no-op for now + return []; + } + + /** + * @brief write metadata item to database + * + * @param int $fileId + * @param string $name + * @param array $value + * + * @return array + */ + public function set($fileId, $name, $value) { + // no-op for now + return []; + } +} + diff --git a/lib/Service/PreferenceService.php b/lib/Service/PreferenceService.php new file mode 100644 index 0000000..8a86fb2 --- /dev/null +++ b/lib/Service/PreferenceService.php @@ -0,0 +1,117 @@ +preferenceMapper = $preferenceMapper; + } + + /** + * @brief get preference + * + * scope identifies preference source, i.e. which renderer the preference applies to + * preference type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc + * + * @param string $scope + * @param int $fileId + * @param string $name + * + * @return array + */ + public function get($scope, $fileId, $name=null) { + $result = $this->preferenceMapper->get($scope, $fileId, $name); + return array_map( + function($entity) { + return $entity->toService(); + }, $result); + } + + /** + * @brief write preference + * + * scope identifies preference source, i.e. which renderer the preference applies to + * position type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc + * + * @param string $scope + * @param int $fileId + * @param string $name + * @param string $value + * + * @return array + */ + public function set($scope, $fileId, $name, $value) { + return $this->preferenceMapper->set($scope, $fileId, $name, $value); + } + + /** + * @brief get default preference + * + * @param string $scope + * @param string $name + * + * @return array + */ + public function getDefault($scope, $name=null) { + return $this->get($scope, static::DEFAULTS, $name); + } + + /** + * @brief set default preference + * + * @param string $scope + * @param string $name + * @param string $value + * + * @return array + */ + public function setDefault($scope, $name, $value) { + return $this->preferenceMapper->set($scope, static::DEFAULTS, $name, $value); + } + + /** + * @brief delete preference + * + * @param string $scope + * @param int $fileId + * @param string $name + * + */ + public function delete($scope, $fileId, $name) { + foreach($this->preferenceMapper->get($scope, $fileId, $name) as $preference) { + $this->preferenceMapper->delete($preference); + } + } + + /** + * @brief delete default + * + * @param string $scope + * @param string $name + * + */ + public function deleteDefault($scope, $name) { + $this->delete($scope, static::DEFAULTS, $name); + } +} diff --git a/lib/Service/Service.php b/lib/Service/Service.php new file mode 100644 index 0000000..42c6caf --- /dev/null +++ b/lib/Service/Service.php @@ -0,0 +1,25 @@ +mapper = $mapper; + } +} + + + diff --git a/lib/Utility/Time.php b/lib/Utility/Time.php new file mode 100644 index 0000000..71fe633 --- /dev/null +++ b/lib/Utility/Time.php @@ -0,0 +1,27 @@ +getConfig()->getUserValue(\OCP\User::getUser(), 'files_reader', $key, $default); + } + + /** + * @brief set user config value + * + * @param string $key key for value to change + * @param string $value value to use + * @return bool success + */ + public static function set($key, $value) { + return \OC::$server->getConfig()->setUserValue(\OCP\User::getUser(), 'files_reader', $key, $value); + } + + /** + * @brief get app config value + * + * @param string $key value to retrieve + * @param string $default default value to use + * @return string retrieved value or default + */ + public static function getApp($key, $default) { + return \OC::$server->getConfig()->getAppValue('files_reader', $key, $default); + } + + /** + * @brief set app config value + * + * @param string $key key for value to change + * @param string $value value to use + * @return bool success + */ + public static function setApp($key, $value) { + return \OC::$server->getConfig()->setAppValue('files_reader', $key, $value); + } +} diff --git a/personal.php b/personal.php new file mode 100644 index 0000000..2b0dd18 --- /dev/null +++ b/personal.php @@ -0,0 +1,25 @@ +getL10N('files_reader'); + +$tmpl = new \OCP\Template('files_reader', 'settings-personal'); +$EpubEnable = Config::get('epub_enable', 'true'); +$PdfEnable = Config::get('pdf_enable', 'true'); +$CbxEnable = Config::get('cbx_enable', 'true'); +$tmpl->assign('EpubEnable', $EpubEnable); +$tmpl->assign('PdfEnable', $PdfEnable); +$tmpl->assign('CbxEnable', $CbxEnable); + +return $tmpl->fetchPage(); diff --git a/screenshots/Screenshot_2014-09-29-20-21-50.png b/screenshots/Screenshot_2014-09-29-20-21-50.png new file mode 100644 index 0000000..1de42ce Binary files /dev/null and b/screenshots/Screenshot_2014-09-29-20-21-50.png differ diff --git a/screenshots/Screenshot_2014-10-02-19-16-18.png b/screenshots/Screenshot_2014-10-02-19-16-18.png new file mode 100644 index 0000000..d1fa554 Binary files /dev/null and b/screenshots/Screenshot_2014-10-02-19-16-18.png differ diff --git a/screenshots/Screenshot_2017-03-15_195840_593917640.png b/screenshots/Screenshot_2017-03-15_195840_593917640.png new file mode 100644 index 0000000..dd37bd5 Binary files /dev/null and b/screenshots/Screenshot_2017-03-15_195840_593917640.png differ diff --git a/screenshots/Screenshot_2017-03-15_200124_360946567.png b/screenshots/Screenshot_2017-03-15_200124_360946567.png new file mode 100644 index 0000000..8a710d3 Binary files /dev/null and b/screenshots/Screenshot_2017-03-15_200124_360946567.png differ diff --git a/screenshots/files_reader-1.png b/screenshots/files_reader-1.png new file mode 100644 index 0000000..0439d7a Binary files /dev/null and b/screenshots/files_reader-1.png differ diff --git a/screenshots/files_reader-2.png b/screenshots/files_reader-2.png new file mode 100644 index 0000000..4618447 Binary files /dev/null and b/screenshots/files_reader-2.png differ diff --git a/screenshots/files_reader-3.png b/screenshots/files_reader-3.png new file mode 100644 index 0000000..0f2136d Binary files /dev/null and b/screenshots/files_reader-3.png differ diff --git a/screenshots/files_reader_PDF_001.png b/screenshots/files_reader_PDF_001.png new file mode 100644 index 0000000..f78cf0f Binary files /dev/null and b/screenshots/files_reader_PDF_001.png differ diff --git a/screenshots/files_reader_PDF_002.png b/screenshots/files_reader_PDF_002.png new file mode 100644 index 0000000..859aac2 Binary files /dev/null and b/screenshots/files_reader_PDF_002.png differ diff --git a/screenshots/files_reader_PDF_003.png b/screenshots/files_reader_PDF_003.png new file mode 100644 index 0000000..7b92e3a Binary files /dev/null and b/screenshots/files_reader_PDF_003.png differ diff --git a/screenshots/files_reader_PDF_004.png b/screenshots/files_reader_PDF_004.png new file mode 100644 index 0000000..b6b3084 Binary files /dev/null and b/screenshots/files_reader_PDF_004.png differ diff --git a/screenshots/files_reader_PDF_005.png b/screenshots/files_reader_PDF_005.png new file mode 100644 index 0000000..6d0b47e Binary files /dev/null and b/screenshots/files_reader_PDF_005.png differ diff --git a/screenshots/files_reader_PDF_006.png b/screenshots/files_reader_PDF_006.png new file mode 100644 index 0000000..ff455aa Binary files /dev/null and b/screenshots/files_reader_PDF_006.png differ diff --git a/screenshots/photo_2017-03-15_17-21-30.jpg b/screenshots/photo_2017-03-15_17-21-30.jpg new file mode 100644 index 0000000..413f706 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-30.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-37.jpg b/screenshots/photo_2017-03-15_17-21-37.jpg new file mode 100644 index 0000000..c8ed0fd Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-37.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-39.jpg b/screenshots/photo_2017-03-15_17-21-39.jpg new file mode 100644 index 0000000..17692de Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-39.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-40.jpg b/screenshots/photo_2017-03-15_17-21-40.jpg new file mode 100644 index 0000000..ed57a24 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-40.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-41.jpg b/screenshots/photo_2017-03-15_17-21-41.jpg new file mode 100644 index 0000000..57f9c74 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-41.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-43.jpg b/screenshots/photo_2017-03-15_17-21-43.jpg new file mode 100644 index 0000000..d4044fb Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-43.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-49.jpg b/screenshots/photo_2017-03-15_17-21-49.jpg new file mode 100644 index 0000000..982980f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-49.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-51.jpg b/screenshots/photo_2017-03-15_17-21-51.jpg new file mode 100644 index 0000000..040e516 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-51.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-52.jpg b/screenshots/photo_2017-03-15_17-21-52.jpg new file mode 100644 index 0000000..9687c2b Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-52.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-53.jpg b/screenshots/photo_2017-03-15_17-21-53.jpg new file mode 100644 index 0000000..034bb90 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-53.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-54.jpg b/screenshots/photo_2017-03-15_17-21-54.jpg new file mode 100644 index 0000000..ba0a7c1 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-54.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-56.jpg b/screenshots/photo_2017-03-15_17-21-56.jpg new file mode 100644 index 0000000..ba0a7c1 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-56.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-57.jpg b/screenshots/photo_2017-03-15_17-21-57.jpg new file mode 100644 index 0000000..1f33945 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-57.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-58.jpg b/screenshots/photo_2017-03-15_17-21-58.jpg new file mode 100644 index 0000000..b6c17cc Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-58.jpg differ diff --git a/screenshots/photo_2017-03-15_17-21-59.jpg b/screenshots/photo_2017-03-15_17-21-59.jpg new file mode 100644 index 0000000..0fb8ea7 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-21-59.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-00.jpg b/screenshots/photo_2017-03-15_17-22-00.jpg new file mode 100644 index 0000000..f5a1acf Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-00.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-01.jpg b/screenshots/photo_2017-03-15_17-22-01.jpg new file mode 100644 index 0000000..824a7d4 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-01.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-02.jpg b/screenshots/photo_2017-03-15_17-22-02.jpg new file mode 100644 index 0000000..e8e19e6 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-02.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-03.jpg b/screenshots/photo_2017-03-15_17-22-03.jpg new file mode 100644 index 0000000..0179561 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-03.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-04.jpg b/screenshots/photo_2017-03-15_17-22-04.jpg new file mode 100644 index 0000000..69d1311 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-04.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-05.jpg b/screenshots/photo_2017-03-15_17-22-05.jpg new file mode 100644 index 0000000..453a84b Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-05.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-06.jpg b/screenshots/photo_2017-03-15_17-22-06.jpg new file mode 100644 index 0000000..4e68810 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-06.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-08.jpg b/screenshots/photo_2017-03-15_17-22-08.jpg new file mode 100644 index 0000000..ec6824e Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-08.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-09.jpg b/screenshots/photo_2017-03-15_17-22-09.jpg new file mode 100644 index 0000000..d4fae84 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-09.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-10.jpg b/screenshots/photo_2017-03-15_17-22-10.jpg new file mode 100644 index 0000000..1ed58d0 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-10.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-11.jpg b/screenshots/photo_2017-03-15_17-22-11.jpg new file mode 100644 index 0000000..21d870c Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-11.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-12.jpg b/screenshots/photo_2017-03-15_17-22-12.jpg new file mode 100644 index 0000000..37e0f8f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-12.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-13.jpg b/screenshots/photo_2017-03-15_17-22-13.jpg new file mode 100644 index 0000000..9b8ae68 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-13.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-14.jpg b/screenshots/photo_2017-03-15_17-22-14.jpg new file mode 100644 index 0000000..c90c026 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-14.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-15.jpg b/screenshots/photo_2017-03-15_17-22-15.jpg new file mode 100644 index 0000000..72766a6 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-15.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-16.jpg b/screenshots/photo_2017-03-15_17-22-16.jpg new file mode 100644 index 0000000..0b9bc56 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-16.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-17.jpg b/screenshots/photo_2017-03-15_17-22-17.jpg new file mode 100644 index 0000000..89bc9f8 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-17.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-18.jpg b/screenshots/photo_2017-03-15_17-22-18.jpg new file mode 100644 index 0000000..e6125fc Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-18.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-19.jpg b/screenshots/photo_2017-03-15_17-22-19.jpg new file mode 100644 index 0000000..ef4fde1 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-19.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-20.jpg b/screenshots/photo_2017-03-15_17-22-20.jpg new file mode 100644 index 0000000..1719253 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-20.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-21.jpg b/screenshots/photo_2017-03-15_17-22-21.jpg new file mode 100644 index 0000000..dc5bf29 Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-21.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-22.jpg b/screenshots/photo_2017-03-15_17-22-22.jpg new file mode 100644 index 0000000..641218e Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-22.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-23.jpg b/screenshots/photo_2017-03-15_17-22-23.jpg new file mode 100644 index 0000000..c56122a Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-23.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-24.jpg b/screenshots/photo_2017-03-15_17-22-24.jpg new file mode 100644 index 0000000..91c969f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-24.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-26.jpg b/screenshots/photo_2017-03-15_17-22-26.jpg new file mode 100644 index 0000000..91c969f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-26.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-27.jpg b/screenshots/photo_2017-03-15_17-22-27.jpg new file mode 100644 index 0000000..91c969f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-27.jpg differ diff --git a/screenshots/photo_2017-03-15_17-22-28.jpg b/screenshots/photo_2017-03-15_17-22-28.jpg new file mode 100644 index 0000000..91c969f Binary files /dev/null and b/screenshots/photo_2017-03-15_17-22-28.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-36.jpg b/screenshots/photo_2017-03-15_18-28-36.jpg new file mode 100644 index 0000000..8d71824 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-36.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-37.jpg b/screenshots/photo_2017-03-15_18-28-37.jpg new file mode 100644 index 0000000..d1602ea Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-37.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-43.jpg b/screenshots/photo_2017-03-15_18-28-43.jpg new file mode 100644 index 0000000..6e41ab7 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-43.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-46.jpg b/screenshots/photo_2017-03-15_18-28-46.jpg new file mode 100644 index 0000000..7a730c8 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-46.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-49.jpg b/screenshots/photo_2017-03-15_18-28-49.jpg new file mode 100644 index 0000000..b69b7eb Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-49.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-52.jpg b/screenshots/photo_2017-03-15_18-28-52.jpg new file mode 100644 index 0000000..c0ea154 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-52.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-54.jpg b/screenshots/photo_2017-03-15_18-28-54.jpg new file mode 100644 index 0000000..68035d7 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-54.jpg differ diff --git a/screenshots/photo_2017-03-15_18-28-56.jpg b/screenshots/photo_2017-03-15_18-28-56.jpg new file mode 100644 index 0000000..91cc6f2 Binary files /dev/null and b/screenshots/photo_2017-03-15_18-28-56.jpg differ diff --git a/templates/cbreader.php b/templates/cbreader.php new file mode 100644 index 0000000..6e47a48 --- /dev/null +++ b/templates/cbreader.php @@ -0,0 +1,240 @@ +getContentSecurityPolicyNonceManager()->getNonce() + : 'nonce_not_implemented'; +?> + + + + + + + + + <?php p($title);?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+   / +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/epubreader.php b/templates/epubreader.php new file mode 100644 index 0000000..da6ca87 --- /dev/null +++ b/templates/epubreader.php @@ -0,0 +1,299 @@ +getContentSecurityPolicyNonceManager()->getNonce() + : 'nonce_not_implemented'; +?> + + + + + + + + + <?php p($title);?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + +
+ +
+ + + +   –   + + + +
+ +
+ + + + + +
+
+ + + + + + +
+
+ +
+ +
+ + + +
+ + + +
+
+
+ + diff --git a/templates/pdfreader.php b/templates/pdfreader.php new file mode 100644 index 0000000..a47410e --- /dev/null +++ b/templates/pdfreader.php @@ -0,0 +1,326 @@ +getContentSecurityPolicyNonceManager()->getNonce() + : 'nonce_not_implemented'; +?> + + + + + + + + + <?php p($title);?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ + + + + + + + + + + +
+ + + +
+ +
+ + + +   –   + + + + / + + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
25%
+
50%
+
75%
+
100%
+
125%
+
150%
+
200%
+
300%
+
400%
+
+ + +
+
+
+
+
+
+ + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + + +
+ +
+
+ +
+
+
+ +
+ +
+ + + +
+ + + +
+
+
+ + diff --git a/templates/settings-personal.php b/templates/settings-personal.php new file mode 100644 index 0000000..f6c5880 --- /dev/null +++ b/templates/settings-personal.php @@ -0,0 +1,42 @@ + + +
+

t('Reader'));?>

 
+

t('Select file types for which Reader should be the default viewer.')); ?>

+ +

+ /> + +

+ +

+ /> +
+

+

+ /> +
+

+
diff --git a/vendor/bartaz/jquery.highlight.js b/vendor/bartaz/jquery.highlight.js new file mode 100644 index 0000000..9dcf3c7 --- /dev/null +++ b/vendor/bartaz/jquery.highlight.js @@ -0,0 +1,108 @@ +/* + * jQuery Highlight plugin + * + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * + * Code a little bit refactored and cleaned (in my humble opinion). + * Most important changes: + * - has an option to highlight only entire words (wordsOnly - false by default), + * - has an option to be case sensitive (caseSensitive - false by default) + * - highlight element tag and class names can be specified in options + * + * Usage: + * // wrap every occurrance of text 'lorem' in content + * // with (default options) + * $('#content').highlight('lorem'); + * + * // search for and highlight more terms at once + * // so you can save some time on traversing DOM + * $('#content').highlight(['lorem', 'ipsum']); + * $('#content').highlight('lorem ipsum'); + * + * // search only for entire word 'lorem' + * $('#content').highlight('lorem', { wordsOnly: true }); + * + * // don't ignore case during search of term 'lorem' + * $('#content').highlight('lorem', { caseSensitive: true }); + * + * // wrap every occurrance of term 'ipsum' in content + * // with + * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); + * + * // remove default highlight + * $('#content').unhighlight(); + * + * // remove custom highlight + * $('#content').unhighlight({ element: 'em', className: 'important' }); + * + * + * Copyright (c) 2009 Bartek Szopka + * + * Licensed under MIT license. + * + */ + +jQuery.extend({ + highlight: function (node, re, nodeName, className) { + if (node.nodeType === 3) { + var match = node.data.match(re); + if (match) { + var highlight = document.createElement(nodeName || 'span'); + highlight.className = className || 'highlight'; + var wordNode = node.splitText(match.index); + wordNode.splitText(match[0].length); + var wordClone = wordNode.cloneNode(true); + highlight.appendChild(wordClone); + wordNode.parentNode.replaceChild(highlight, wordNode); + return 1; //skip added node in parent + } + } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children + !/(script|style)/i.test(node.tagName) && // ignore script and style nodes + !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted + for (var i = 0; i < node.childNodes.length; i++) { + i += jQuery.highlight(node.childNodes[i], re, nodeName, className); + } + } + return 0; + } +}); + +jQuery.fn.unhighlight = function (options) { + var settings = { className: 'highlight', element: 'span' }; + jQuery.extend(settings, options); + + return this.find(settings.element + "." + settings.className).each(function () { + var parent = this.parentNode; + parent.replaceChild(this.firstChild, this); + parent.normalize(); + }).end(); +}; + +jQuery.fn.highlight = function (words, options) { + var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; + jQuery.extend(settings, options); + + if (words.constructor === String) { + words = [words]; + } + words = jQuery.grep(words, function(word, i){ + return word != ''; + }); + words = jQuery.map(words, function(word, i) { + return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }); + if (words.length == 0) { return this; }; + + var flag = settings.caseSensitive ? "" : "i"; + var pattern = "(" + words.join("|") + ")"; + if (settings.wordsOnly) { + pattern = "\\b" + pattern + "\\b"; + } + var re = new RegExp(pattern, flag); + + return this.each(function () { + jQuery.highlight(this, re, settings.element, settings.className); + }); +}; + diff --git a/vendor/bgrins/spectrum.css b/vendor/bgrins/spectrum.css new file mode 100644 index 0000000..a8ad9e4 --- /dev/null +++ b/vendor/bgrins/spectrum.css @@ -0,0 +1,507 @@ +/*** +Spectrum Colorpicker v1.8.0 +https://github.com/bgrins/spectrum +Author: Brian Grinstead +License: MIT +***/ + +.sp-container { + position:absolute; + top:0; + left:0; + display:inline-block; + *display: inline; + *zoom: 1; + /* https://github.com/bgrins/spectrum/issues/40 */ + z-index: 9999994; + overflow: hidden; +} +.sp-container.sp-flat { + position: relative; +} + +/* Fix for * { box-sizing: border-box; } */ +.sp-container, +.sp-container * { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ +.sp-top { + position:relative; + width: 100%; + display:inline-block; +} +.sp-top-inner { + position:absolute; + top:0; + left:0; + bottom:0; + right:0; +} +.sp-color { + position: absolute; + top:0; + left:0; + bottom:0; + right:20%; +} +.sp-hue { + position: absolute; + top:0; + right:0; + bottom:0; + left:84%; + height: 100%; +} + +.sp-clear-enabled .sp-hue { + top:33px; + height: 77.5%; +} + +.sp-fill { + padding-top: 80%; +} +.sp-sat, .sp-val { + position: absolute; + top:0; + left:0; + right:0; + bottom:0; +} + +.sp-alpha-enabled .sp-top { + margin-bottom: 18px; +} +.sp-alpha-enabled .sp-alpha { + display: block; +} +.sp-alpha-handle { + position:absolute; + top:-4px; + bottom: -4px; + width: 6px; + left: 50%; + cursor: pointer; + border: 1px solid black; + background: white; + opacity: .8; +} +.sp-alpha { + display: none; + position: absolute; + bottom: -14px; + right: 0; + left: 0; + height: 8px; +} +.sp-alpha-inner { + border: solid 1px #333; +} + +.sp-clear { + display: none; +} + +.sp-clear.sp-clear-display { + background-position: center; +} + +.sp-clear-enabled .sp-clear { + display: block; + position:absolute; + top:0px; + right:0; + bottom:0; + left:84%; + height: 28px; +} + +/* Don't allow text selection */ +.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { + -webkit-user-select:none; + -moz-user-select: -moz-none; + -o-user-select:none; + user-select: none; +} + +.sp-container.sp-input-disabled .sp-input-container { + display: none; +} +.sp-container.sp-buttons-disabled .sp-button-container { + display: none; +} +.sp-container.sp-palette-buttons-disabled .sp-palette-button-container { + display: none; +} +.sp-palette-only .sp-picker-container { + display: none; +} +.sp-palette-disabled .sp-palette-container { + display: none; +} + +.sp-initial-disabled .sp-initial { + display: none; +} + + +/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ +.sp-sat { + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); + background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); + background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); + background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); + background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); + background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; + filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); +} +.sp-val { + background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); + background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); + background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); + background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); + background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); + background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; + filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); +} + +.sp-hue { + background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); + background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); + background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); +} + +/* IE filters do not support multiple color stops. + Generate 6 divs, line them up, and do two color gradients for each. + Yes, really. + */ +.sp-1 { + height:17%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); +} +.sp-2 { + height:16%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); +} +.sp-3 { + height:17%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); +} +.sp-4 { + height:17%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); +} +.sp-5 { + height:16%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); +} +.sp-6 { + height:17%; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); +} + +.sp-hidden { + display: none !important; +} + +/* Clearfix hack */ +.sp-cf:before, .sp-cf:after { content: ""; display: table; } +.sp-cf:after { clear: both; } +.sp-cf { *zoom: 1; } + +/* Mobile devices, make hue slider bigger so it is easier to slide */ +@media (max-device-width: 480px) { + .sp-color { right: 40%; } + .sp-hue { left: 63%; } + .sp-fill { padding-top: 60%; } +} +.sp-dragger { + border-radius: 5px; + height: 5px; + width: 5px; + border: 1px solid #fff; + background: #000; + cursor: pointer; + position:absolute; + top:0; + left: 0; +} +.sp-slider { + position: absolute; + top:0; + cursor:pointer; + height: 3px; + left: -1px; + right: -1px; + border: 1px solid #000; + background: white; + opacity: .8; +} + +/* +Theme authors: +Here are the basic themeable display options (colors, fonts, global widths). +See http://bgrins.github.io/spectrum/themes/ for instructions. +*/ + +.sp-container { + border-radius: 0; + background-color: #ECECEC; + border: solid 1px #f0c49B; + padding: 0; +} +.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { + font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} +.sp-top { + margin-bottom: 3px; +} +.sp-color, .sp-hue, .sp-clear { + border: solid 1px #666; +} + +/* Input */ +.sp-input-container { + float:right; + width: 100px; + margin-bottom: 4px; +} +.sp-initial-disabled .sp-input-container { + width: 100%; +} +.sp-input { + font-size: 12px !important; + border: 1px inset; + padding: 4px 5px; + margin: 0; + width: 100%; + background:transparent; + border-radius: 3px; + color: #222; +} +.sp-input:focus { + border: 1px solid orange; +} +.sp-input.sp-validation-error { + border: 1px solid red; + background: #fdd; +} +.sp-picker-container , .sp-palette-container { + float:left; + position: relative; + padding: 10px; + padding-bottom: 300px; + margin-bottom: -290px; +} +.sp-picker-container { + width: 172px; + border-left: solid 1px #fff; +} + +/* Palettes */ +.sp-palette-container { + border-right: solid 1px #ccc; +} + +.sp-palette-only .sp-palette-container { + border: 0; +} + +.sp-palette .sp-thumb-el { + display: block; + position:relative; + float:left; + width: 24px; + height: 15px; + margin: 3px; + cursor: pointer; + border:solid 2px transparent; +} +.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { + border-color: orange; +} +.sp-thumb-el { + position:relative; +} + +/* Initial */ +.sp-initial { + float: left; + border: solid 1px #333; +} +.sp-initial span { + width: 30px; + height: 25px; + border:none; + display:block; + float:left; + margin:0; +} + +.sp-initial .sp-clear-display { + background-position: center; +} + +/* Buttons */ +.sp-palette-button-container, +.sp-button-container { + float: right; +} + +/* Replacer (the little preview div that shows up instead of the ) */ +.sp-replacer { + margin:0; + overflow:hidden; + cursor:pointer; + padding: 4px; + display:inline-block; + *zoom: 1; + *display: inline; + border: solid 1px #91765d; + background: #eee; + color: #333; + vertical-align: middle; +} +.sp-replacer:hover, .sp-replacer.sp-active { + border-color: #F0C49B; + color: #111; +} +.sp-replacer.sp-disabled { + cursor:default; + border-color: silver; + color: silver; +} +.sp-dd { + padding: 2px 0; + height: 16px; + line-height: 16px; + float:left; + font-size:10px; +} +.sp-preview { + position:relative; + width:25px; + height: 20px; + border: solid 1px #222; + margin-right: 5px; + float:left; + z-index: 0; +} + +.sp-palette { + *width: 220px; + max-width: 220px; +} +.sp-palette .sp-thumb-el { + width:16px; + height: 16px; + margin:2px 1px; + border: solid 1px #d0d0d0; +} + +.sp-container { + padding-bottom:0; +} + + +/* Buttons: http://hellohappy.org/css3-buttons/ */ +.sp-container button { + background-color: #eeeeee; + background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); + background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); + background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); + background-image: -o-linear-gradient(top, #eeeeee, #cccccc); + background-image: linear-gradient(to bottom, #eeeeee, #cccccc); + border: 1px solid #ccc; + border-bottom: 1px solid #bbb; + border-radius: 3px; + color: #333; + font-size: 14px; + line-height: 1; + padding: 5px 4px; + text-align: center; + text-shadow: 0 1px 0 #eee; + vertical-align: middle; +} +.sp-container button:hover { + background-color: #dddddd; + background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); + background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); + background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); + background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); + background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); + border: 1px solid #bbb; + border-bottom: 1px solid #999; + cursor: pointer; + text-shadow: 0 1px 0 #ddd; +} +.sp-container button:active { + border: 1px solid #aaa; + border-bottom: 1px solid #888; + -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; + -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; + -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; + -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; + box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; +} +.sp-cancel { + font-size: 11px; + color: #d93f3f !important; + margin:0; + padding:2px; + margin-right: 5px; + vertical-align: middle; + text-decoration:none; + +} +.sp-cancel:hover { + color: #d93f3f !important; + text-decoration: underline; +} + + +.sp-palette span:hover, .sp-palette span.sp-thumb-active { + border-color: #000; +} + +.sp-preview, .sp-alpha, .sp-thumb-el { + position:relative; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); +} +.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { + display:block; + position:absolute; + top:0;left:0;bottom:0;right:0; +} + +.sp-palette .sp-thumb-inner { + background-position: 50% 50%; + background-repeat: no-repeat; +} + +.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); +} + +.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); +} + +.sp-clear-display { + background-repeat:no-repeat; + background-position: center; + background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); +} diff --git a/vendor/bgrins/spectrum.js b/vendor/bgrins/spectrum.js new file mode 100644 index 0000000..7200978 --- /dev/null +++ b/vendor/bgrins/spectrum.js @@ -0,0 +1,2323 @@ +// Spectrum Colorpicker v1.8.0 +// https://github.com/bgrins/spectrum +// Author: Brian Grinstead +// License: MIT + +(function (factory) { + "use strict"; + + if (typeof define === 'function' && define.amd) { // AMD + define(['jquery'], factory); + } + else if (typeof exports == "object" && typeof module == "object") { // CommonJS + module.exports = factory(require('jquery')); + } + else { // Browser + factory(jQuery); + } +})(function($, undefined) { + "use strict"; + + var defaultOpts = { + + // Callbacks + beforeShow: noop, + move: noop, + change: noop, + show: noop, + hide: noop, + + // Options + color: false, + flat: false, + showInput: false, + allowEmpty: false, + showButtons: true, + clickoutFiresChange: true, + showInitial: false, + showPalette: false, + showPaletteOnly: false, + hideAfterPaletteSelect: false, + togglePaletteOnly: false, + showSelectionPalette: true, + localStorageKey: false, + appendTo: "body", + maxSelectionSize: 7, + cancelText: "cancel", + chooseText: "choose", + togglePaletteMoreText: "more", + togglePaletteLessText: "less", + clearText: "Clear Color Selection", + noColorSelectedText: "No Color Selected", + preferredFormat: false, + className: "", // Deprecated - use containerClassName and replacerClassName instead. + containerClassName: "", + replacerClassName: "", + showAlpha: false, + theme: "sp-light", + palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], + selectionPalette: [], + disabled: false, + offset: null + }, + spectrums = [], + IE = !!/msie/i.exec( window.navigator.userAgent ), + rgbaSupport = (function() { + function contains( str, substr ) { + return !!~('' + str).indexOf(substr); + } + + var elem = document.createElement('div'); + var style = elem.style; + style.cssText = 'background-color:rgba(0,0,0,.5)'; + return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); + })(), + replaceInput = [ + "
", + "
", + "
", + "
" + ].join(''), + markup = (function () { + + // IE does not support gradients with multiple stops, so we need to simulate + // that for the rainbow slider with 8 divs that each have a single gradient + var gradientFix = ""; + if (IE) { + for (var i = 1; i <= 6; i++) { + gradientFix += "
"; + } + } + + return [ + "
", + "
", + "
", + "
", + "", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + gradientFix, + "
", + "
", + "
", + "
", + "
", + "", + "
", + "
", + "
", + "", + "", + "
", + "
", + "
" + ].join(""); + })(); + + function paletteTemplate (p, color, className, opts) { + var html = []; + for (var i = 0; i < p.length; i++) { + var current = p[i]; + if(current) { + var tiny = tinycolor(current); + var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; + c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; + var formattedString = tiny.toString(opts.preferredFormat || "rgb"); + var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); + html.push(''); + } else { + var cls = 'sp-clear-display'; + html.push($('
') + .append($('') + .attr('title', opts.noColorSelectedText) + ) + .html() + ); + } + } + return "
" + html.join('') + "
"; + } + + function hideAll() { + for (var i = 0; i < spectrums.length; i++) { + if (spectrums[i]) { + spectrums[i].hide(); + } + } + } + + function instanceOptions(o, callbackContext) { + var opts = $.extend({}, defaultOpts, o); + opts.callbacks = { + 'move': bind(opts.move, callbackContext), + 'change': bind(opts.change, callbackContext), + 'show': bind(opts.show, callbackContext), + 'hide': bind(opts.hide, callbackContext), + 'beforeShow': bind(opts.beforeShow, callbackContext) + }; + + return opts; + } + + function spectrum(element, o) { + + var opts = instanceOptions(o, element), + flat = opts.flat, + showSelectionPalette = opts.showSelectionPalette, + localStorageKey = opts.localStorageKey, + theme = opts.theme, + callbacks = opts.callbacks, + resize = throttle(reflow, 10), + visible = false, + isDragging = false, + dragWidth = 0, + dragHeight = 0, + dragHelperHeight = 0, + slideHeight = 0, + slideWidth = 0, + alphaWidth = 0, + alphaSlideHelperWidth = 0, + slideHelperHeight = 0, + currentHue = 0, + currentSaturation = 0, + currentValue = 0, + currentAlpha = 1, + palette = [], + paletteArray = [], + paletteLookup = {}, + selectionPalette = opts.selectionPalette.slice(0), + maxSelectionSize = opts.maxSelectionSize, + draggingClass = "sp-dragging", + shiftMovementDirection = null; + + var doc = element.ownerDocument, + body = doc.body, + boundElement = $(element), + disabled = false, + container = $(markup, doc).addClass(theme), + pickerContainer = container.find(".sp-picker-container"), + dragger = container.find(".sp-color"), + dragHelper = container.find(".sp-dragger"), + slider = container.find(".sp-hue"), + slideHelper = container.find(".sp-slider"), + alphaSliderInner = container.find(".sp-alpha-inner"), + alphaSlider = container.find(".sp-alpha"), + alphaSlideHelper = container.find(".sp-alpha-handle"), + textInput = container.find(".sp-input"), + paletteContainer = container.find(".sp-palette"), + initialColorContainer = container.find(".sp-initial"), + cancelButton = container.find(".sp-cancel"), + clearButton = container.find(".sp-clear"), + chooseButton = container.find(".sp-choose"), + toggleButton = container.find(".sp-palette-toggle"), + isInput = boundElement.is("input"), + isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(), + shouldReplace = isInput && !flat, + replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), + offsetElement = (shouldReplace) ? replacer : boundElement, + previewElement = replacer.find(".sp-preview-inner"), + initialColor = opts.color || (isInput && boundElement.val()), + colorOnShow = false, + currentPreferredFormat = opts.preferredFormat, + clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, + isEmpty = !initialColor, + allowEmpty = opts.allowEmpty && !isInputTypeColor; + + function applyOptions() { + + if (opts.showPaletteOnly) { + opts.showPalette = true; + } + + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); + + if (opts.palette) { + palette = opts.palette.slice(0); + paletteArray = $.isArray(palette[0]) ? palette : [palette]; + paletteLookup = {}; + for (var i = 0; i < paletteArray.length; i++) { + for (var j = 0; j < paletteArray[i].length; j++) { + var rgb = tinycolor(paletteArray[i][j]).toRgbString(); + paletteLookup[rgb] = true; + } + } + } + + container.toggleClass("sp-flat", flat); + container.toggleClass("sp-input-disabled", !opts.showInput); + container.toggleClass("sp-alpha-enabled", opts.showAlpha); + container.toggleClass("sp-clear-enabled", allowEmpty); + container.toggleClass("sp-buttons-disabled", !opts.showButtons); + container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); + container.toggleClass("sp-palette-disabled", !opts.showPalette); + container.toggleClass("sp-palette-only", opts.showPaletteOnly); + container.toggleClass("sp-initial-disabled", !opts.showInitial); + container.addClass(opts.className).addClass(opts.containerClassName); + + reflow(); + } + + function initialize() { + + if (IE) { + container.find("*:not(input)").attr("unselectable", "on"); + } + + applyOptions(); + + if (shouldReplace) { + boundElement.after(replacer).hide(); + } + + if (!allowEmpty) { + clearButton.hide(); + } + + if (flat) { + boundElement.after(container).hide(); + } + else { + + var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); + if (appendTo.length !== 1) { + appendTo = $("body"); + } + + appendTo.append(container); + } + + updateSelectionPaletteFromStorage(); + + offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { + if (!disabled) { + toggle(); + } + + e.stopPropagation(); + + if (!$(e.target).is("input")) { + e.preventDefault(); + } + }); + + if(boundElement.is(":disabled") || (opts.disabled === true)) { + disable(); + } + + // Prevent clicks from bubbling up to document. This would cause it to be hidden. + container.click(stopPropagation); + + // Handle user typed input + textInput.change(setFromTextInput); + textInput.bind("paste", function () { + setTimeout(setFromTextInput, 1); + }); + textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); + + cancelButton.text(opts.cancelText); + cancelButton.bind("click.spectrum", function (e) { + e.stopPropagation(); + e.preventDefault(); + revert(); + hide(); + }); + + clearButton.attr("title", opts.clearText); + clearButton.bind("click.spectrum", function (e) { + e.stopPropagation(); + e.preventDefault(); + isEmpty = true; + move(); + + if(flat) { + //for the flat style, this is a change event + updateOriginalInput(true); + } + }); + + chooseButton.text(opts.chooseText); + chooseButton.bind("click.spectrum", function (e) { + e.stopPropagation(); + e.preventDefault(); + + if (IE && textInput.is(":focus")) { + textInput.trigger('change'); + } + + if (isValid()) { + updateOriginalInput(true); + hide(); + } + }); + + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); + toggleButton.bind("click.spectrum", function (e) { + e.stopPropagation(); + e.preventDefault(); + + opts.showPaletteOnly = !opts.showPaletteOnly; + + // To make sure the Picker area is drawn on the right, next to the + // Palette area (and not below the palette), first move the Palette + // to the left to make space for the picker, plus 5px extra. + // The 'applyOptions' function puts the whole container back into place + // and takes care of the button-text and the sp-palette-only CSS class. + if (!opts.showPaletteOnly && !flat) { + container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); + } + applyOptions(); + }); + + draggable(alphaSlider, function (dragX, dragY, e) { + currentAlpha = (dragX / alphaWidth); + isEmpty = false; + if (e.shiftKey) { + currentAlpha = Math.round(currentAlpha * 10) / 10; + } + + move(); + }, dragStart, dragStop); + + draggable(slider, function (dragX, dragY) { + currentHue = parseFloat(dragY / slideHeight); + isEmpty = false; + if (!opts.showAlpha) { + currentAlpha = 1; + } + move(); + }, dragStart, dragStop); + + draggable(dragger, function (dragX, dragY, e) { + + // shift+drag should snap the movement to either the x or y axis. + if (!e.shiftKey) { + shiftMovementDirection = null; + } + else if (!shiftMovementDirection) { + var oldDragX = currentSaturation * dragWidth; + var oldDragY = dragHeight - (currentValue * dragHeight); + var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); + + shiftMovementDirection = furtherFromX ? "x" : "y"; + } + + var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; + var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; + + if (setSaturation) { + currentSaturation = parseFloat(dragX / dragWidth); + } + if (setValue) { + currentValue = parseFloat((dragHeight - dragY) / dragHeight); + } + + isEmpty = false; + if (!opts.showAlpha) { + currentAlpha = 1; + } + + move(); + + }, dragStart, dragStop); + + if (!!initialColor) { + set(initialColor); + + // In case color was black - update the preview UI and set the format + // since the set function will not run (default color is black). + updateUI(); + currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format; + + addColorToSelectionPalette(initialColor); + } + else { + updateUI(); + } + + if (flat) { + show(); + } + + function paletteElementClick(e) { + if (e.data && e.data.ignore) { + set($(e.target).closest(".sp-thumb-el").data("color")); + move(); + } + else { + set($(e.target).closest(".sp-thumb-el").data("color")); + move(); + updateOriginalInput(true); + if (opts.hideAfterPaletteSelect) { + hide(); + } + } + + return false; + } + + var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; + paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); + initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); + } + + function updateSelectionPaletteFromStorage() { + + if (localStorageKey && window.localStorage) { + + // Migrate old palettes over to new format. May want to remove this eventually. + try { + var oldPalette = window.localStorage[localStorageKey].split(",#"); + if (oldPalette.length > 1) { + delete window.localStorage[localStorageKey]; + $.each(oldPalette, function(i, c) { + addColorToSelectionPalette(c); + }); + } + } + catch(e) { } + + try { + selectionPalette = window.localStorage[localStorageKey].split(";"); + } + catch (e) { } + } + } + + function addColorToSelectionPalette(color) { + if (showSelectionPalette) { + var rgb = tinycolor(color).toRgbString(); + if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { + selectionPalette.push(rgb); + while(selectionPalette.length > maxSelectionSize) { + selectionPalette.shift(); + } + } + + if (localStorageKey && window.localStorage) { + try { + window.localStorage[localStorageKey] = selectionPalette.join(";"); + } + catch(e) { } + } + } + } + + function getUniqueSelectionPalette() { + var unique = []; + if (opts.showPalette) { + for (var i = 0; i < selectionPalette.length; i++) { + var rgb = tinycolor(selectionPalette[i]).toRgbString(); + + if (!paletteLookup[rgb]) { + unique.push(selectionPalette[i]); + } + } + } + + return unique.reverse().slice(0, opts.maxSelectionSize); + } + + function drawPalette() { + + var currentColor = get(); + + var html = $.map(paletteArray, function (palette, i) { + return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); + }); + + updateSelectionPaletteFromStorage(); + + if (selectionPalette) { + html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); + } + + paletteContainer.html(html.join("")); + } + + function drawInitial() { + if (opts.showInitial) { + var initial = colorOnShow; + var current = get(); + initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); + } + } + + function dragStart() { + if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { + reflow(); + } + isDragging = true; + container.addClass(draggingClass); + shiftMovementDirection = null; + boundElement.trigger('dragstart.spectrum', [ get() ]); + } + + function dragStop() { + isDragging = false; + container.removeClass(draggingClass); + boundElement.trigger('dragstop.spectrum', [ get() ]); + } + + function setFromTextInput() { + + var value = textInput.val(); + + if ((value === null || value === "") && allowEmpty) { + set(null); + updateOriginalInput(true); + } + else { + var tiny = tinycolor(value); + if (tiny.isValid()) { + set(tiny); + updateOriginalInput(true); + } + else { + textInput.addClass("sp-validation-error"); + } + } + } + + function toggle() { + if (visible) { + hide(); + } + else { + show(); + } + } + + function show() { + var event = $.Event('beforeShow.spectrum'); + + if (visible) { + reflow(); + return; + } + + boundElement.trigger(event, [ get() ]); + + if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { + return; + } + + hideAll(); + visible = true; + + $(doc).bind("keydown.spectrum", onkeydown); + $(doc).bind("click.spectrum", clickout); + $(window).bind("resize.spectrum", resize); + replacer.addClass("sp-active"); + container.removeClass("sp-hidden"); + + reflow(); + updateUI(); + + colorOnShow = get(); + + drawInitial(); + callbacks.show(colorOnShow); + boundElement.trigger('show.spectrum', [ colorOnShow ]); + } + + function onkeydown(e) { + // Close on ESC + if (e.keyCode === 27) { + hide(); + } + } + + function clickout(e) { + // Return on right click. + if (e.button == 2) { return; } + + // If a drag event was happening during the mouseup, don't hide + // on click. + if (isDragging) { return; } + + if (clickoutFiresChange) { + updateOriginalInput(true); + } + else { + revert(); + } + hide(); + } + + function hide() { + // Return if hiding is unnecessary + if (!visible || flat) { return; } + visible = false; + + $(doc).unbind("keydown.spectrum", onkeydown); + $(doc).unbind("click.spectrum", clickout); + $(window).unbind("resize.spectrum", resize); + + replacer.removeClass("sp-active"); + container.addClass("sp-hidden"); + + callbacks.hide(get()); + boundElement.trigger('hide.spectrum', [ get() ]); + } + + function revert() { + set(colorOnShow, true); + } + + function set(color, ignoreFormatChange) { + if (tinycolor.equals(color, get())) { + // Update UI just in case a validation error needs + // to be cleared. + updateUI(); + return; + } + + var newColor, newHsv; + if (!color && allowEmpty) { + isEmpty = true; + } else { + isEmpty = false; + newColor = tinycolor(color); + newHsv = newColor.toHsv(); + + currentHue = (newHsv.h % 360) / 360; + currentSaturation = newHsv.s; + currentValue = newHsv.v; + currentAlpha = newHsv.a; + } + updateUI(); + + if (newColor && newColor.isValid() && !ignoreFormatChange) { + currentPreferredFormat = opts.preferredFormat || newColor.getFormat(); + } + } + + function get(opts) { + opts = opts || { }; + + if (allowEmpty && isEmpty) { + return null; + } + + return tinycolor.fromRatio({ + h: currentHue, + s: currentSaturation, + v: currentValue, + a: Math.round(currentAlpha * 100) / 100 + }, { format: opts.format || currentPreferredFormat }); + } + + function isValid() { + return !textInput.hasClass("sp-validation-error"); + } + + function move() { + updateUI(); + + callbacks.move(get()); + boundElement.trigger('move.spectrum', [ get() ]); + } + + function updateUI() { + + textInput.removeClass("sp-validation-error"); + + updateHelperLocations(); + + // Update dragger background color (gradients take care of saturation and value). + var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 }); + dragger.css("background-color", flatColor.toHexString()); + + // Get a format that alpha will be included in (hex and names ignore alpha) + var format = currentPreferredFormat; + if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { + if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { + format = "rgb"; + } + } + + var realColor = get({ format: format }), + displayColor = ''; + + //reset background info for preview element + previewElement.removeClass("sp-clear-display"); + previewElement.css('background-color', 'transparent'); + + if (!realColor && allowEmpty) { + // Update the replaced elements background with icon indicating no color selection + previewElement.addClass("sp-clear-display"); + } + else { + var realHex = realColor.toHexString(), + realRgb = realColor.toRgbString(); + + // Update the replaced elements background color (with actual selected color) + if (rgbaSupport || realColor.alpha === 1) { + previewElement.css("background-color", realRgb); + } + else { + previewElement.css("background-color", "transparent"); + previewElement.css("filter", realColor.toFilter()); + } + + if (opts.showAlpha) { + var rgb = realColor.toRgb(); + rgb.a = 0; + var realAlpha = tinycolor(rgb).toRgbString(); + var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; + + if (IE) { + alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); + } + else { + alphaSliderInner.css("background", "-webkit-" + gradient); + alphaSliderInner.css("background", "-moz-" + gradient); + alphaSliderInner.css("background", "-ms-" + gradient); + // Use current syntax gradient on unprefixed property. + alphaSliderInner.css("background", + "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); + } + } + + displayColor = realColor.toString(format); + } + + // Update the text entry input as it changes happen + if (opts.showInput) { + textInput.val(displayColor); + } + + if (opts.showPalette) { + drawPalette(); + } + + drawInitial(); + } + + function updateHelperLocations() { + var s = currentSaturation; + var v = currentValue; + + if(allowEmpty && isEmpty) { + //if selected color is empty, hide the helpers + alphaSlideHelper.hide(); + slideHelper.hide(); + dragHelper.hide(); + } + else { + //make sure helpers are visible + alphaSlideHelper.show(); + slideHelper.show(); + dragHelper.show(); + + // Where to show the little circle in that displays your current selected color + var dragX = s * dragWidth; + var dragY = dragHeight - (v * dragHeight); + dragX = Math.max( + -dragHelperHeight, + Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) + ); + dragY = Math.max( + -dragHelperHeight, + Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) + ); + dragHelper.css({ + "top": dragY + "px", + "left": dragX + "px" + }); + + var alphaX = currentAlpha * alphaWidth; + alphaSlideHelper.css({ + "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" + }); + + // Where to show the bar that displays your current selected hue + var slideY = (currentHue) * slideHeight; + slideHelper.css({ + "top": (slideY - slideHelperHeight) + "px" + }); + } + } + + function updateOriginalInput(fireCallback) { + var color = get(), + displayColor = '', + hasChanged = !tinycolor.equals(color, colorOnShow); + + if (color) { + displayColor = color.toString(currentPreferredFormat); + // Update the selection palette with the current color + addColorToSelectionPalette(color); + } + + if (isInput) { + boundElement.val(displayColor); + } + + if (fireCallback && hasChanged) { + callbacks.change(color); + boundElement.trigger('change', [ color ]); + } + } + + function reflow() { + if (!visible) { + return; // Calculations would be useless and wouldn't be reliable anyways + } + dragWidth = dragger.width(); + dragHeight = dragger.height(); + dragHelperHeight = dragHelper.height(); + slideWidth = slider.width(); + slideHeight = slider.height(); + slideHelperHeight = slideHelper.height(); + alphaWidth = alphaSlider.width(); + alphaSlideHelperWidth = alphaSlideHelper.width(); + + if (!flat) { + container.css("position", "absolute"); + if (opts.offset) { + container.offset(opts.offset); + } else { + container.offset(getOffset(container, offsetElement)); + } + } + + updateHelperLocations(); + + if (opts.showPalette) { + drawPalette(); + } + + boundElement.trigger('reflow.spectrum'); + } + + function destroy() { + boundElement.show(); + offsetElement.unbind("click.spectrum touchstart.spectrum"); + container.remove(); + replacer.remove(); + spectrums[spect.id] = null; + } + + function option(optionName, optionValue) { + if (optionName === undefined) { + return $.extend({}, opts); + } + if (optionValue === undefined) { + return opts[optionName]; + } + + opts[optionName] = optionValue; + + if (optionName === "preferredFormat") { + currentPreferredFormat = opts.preferredFormat; + } + applyOptions(); + } + + function enable() { + disabled = false; + boundElement.attr("disabled", false); + offsetElement.removeClass("sp-disabled"); + } + + function disable() { + hide(); + disabled = true; + boundElement.attr("disabled", true); + offsetElement.addClass("sp-disabled"); + } + + function setOffset(coord) { + opts.offset = coord; + reflow(); + } + + initialize(); + + var spect = { + show: show, + hide: hide, + toggle: toggle, + reflow: reflow, + option: option, + enable: enable, + disable: disable, + offset: setOffset, + set: function (c) { + set(c); + updateOriginalInput(); + }, + get: get, + destroy: destroy, + container: container + }; + + spect.id = spectrums.push(spect) - 1; + + return spect; + } + + /** + * checkOffset - get the offset below/above and left/right element depending on screen position + * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js + */ + function getOffset(picker, input) { + var extraY = 0; + var dpWidth = picker.outerWidth(); + var dpHeight = picker.outerHeight(); + var inputHeight = input.outerHeight(); + var doc = picker[0].ownerDocument; + var docElem = doc.documentElement; + var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); + var viewHeight = docElem.clientHeight + $(doc).scrollTop(); + var offset = input.offset(); + offset.top += inputHeight; + + offset.left -= + Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + + offset.top -= + Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight - extraY) : extraY)); + + return offset; + } + + /** + * noop - do nothing + */ + function noop() { + + } + + /** + * stopPropagation - makes the code only doing this a little easier to read in line + */ + function stopPropagation(e) { + e.stopPropagation(); + } + + /** + * Create a function bound to a given object + * Thanks to underscore.js + */ + function bind(func, obj) { + var slice = Array.prototype.slice; + var args = slice.call(arguments, 2); + return function () { + return func.apply(obj, args.concat(slice.call(arguments))); + }; + } + + /** + * Lightweight drag helper. Handles containment within the element, so that + * when dragging, the x is within [0,element.width] and y is within [0,element.height] + */ + function draggable(element, onmove, onstart, onstop) { + onmove = onmove || function () { }; + onstart = onstart || function () { }; + onstop = onstop || function () { }; + var doc = document; + var dragging = false; + var offset = {}; + var maxHeight = 0; + var maxWidth = 0; + var hasTouch = ('ontouchstart' in window); + + var duringDragEvents = {}; + duringDragEvents["selectstart"] = prevent; + duringDragEvents["dragstart"] = prevent; + duringDragEvents["touchmove mousemove"] = move; + duringDragEvents["touchend mouseup"] = stop; + + function prevent(e) { + if (e.stopPropagation) { + e.stopPropagation(); + } + if (e.preventDefault) { + e.preventDefault(); + } + e.returnValue = false; + } + + function move(e) { + if (dragging) { + // Mouseup happened outside of window + if (IE && doc.documentMode < 9 && !e.button) { + return stop(); + } + + var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0]; + var pageX = t0 && t0.pageX || e.pageX; + var pageY = t0 && t0.pageY || e.pageY; + + var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); + var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); + + if (hasTouch) { + // Stop scrolling in iOS + prevent(e); + } + + onmove.apply(element, [dragX, dragY, e]); + } + } + + function start(e) { + var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); + + if (!rightclick && !dragging) { + if (onstart.apply(element, arguments) !== false) { + dragging = true; + maxHeight = $(element).height(); + maxWidth = $(element).width(); + offset = $(element).offset(); + + $(doc).bind(duringDragEvents); + $(doc.body).addClass("sp-dragging"); + + move(e); + + prevent(e); + } + } + } + + function stop() { + if (dragging) { + $(doc).unbind(duringDragEvents); + $(doc.body).removeClass("sp-dragging"); + + // Wait a tick before notifying observers to allow the click event + // to fire in Chrome. + setTimeout(function() { + onstop.apply(element, arguments); + }, 0); + } + dragging = false; + } + + $(element).bind("touchstart mousedown", start); + } + + function throttle(func, wait, debounce) { + var timeout; + return function () { + var context = this, args = arguments; + var throttler = function () { + timeout = null; + func.apply(context, args); + }; + if (debounce) clearTimeout(timeout); + if (debounce || !timeout) timeout = setTimeout(throttler, wait); + }; + } + + function inputTypeColorSupport() { + return $.fn.spectrum.inputTypeColorSupport(); + } + + /** + * Define a jQuery plugin + */ + var dataID = "spectrum.id"; + $.fn.spectrum = function (opts, extra) { + + if (typeof opts == "string") { + + var returnValue = this; + var args = Array.prototype.slice.call( arguments, 1 ); + + this.each(function () { + var spect = spectrums[$(this).data(dataID)]; + if (spect) { + var method = spect[opts]; + if (!method) { + throw new Error( "Spectrum: no such method: '" + opts + "'" ); + } + + if (opts == "get") { + returnValue = spect.get(); + } + else if (opts == "container") { + returnValue = spect.container; + } + else if (opts == "option") { + returnValue = spect.option.apply(spect, args); + } + else if (opts == "destroy") { + spect.destroy(); + $(this).removeData(dataID); + } + else { + method.apply(spect, args); + } + } + }); + + return returnValue; + } + + // Initializing a new instance of spectrum + return this.spectrum("destroy").each(function () { + var options = $.extend({}, opts, $(this).data()); + var spect = spectrum(this, options); + $(this).data(dataID, spect.id); + }); + }; + + $.fn.spectrum.load = true; + $.fn.spectrum.loadOpts = {}; + $.fn.spectrum.draggable = draggable; + $.fn.spectrum.defaults = defaultOpts; + $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() { + if (typeof inputTypeColorSupport._cachedResult === "undefined") { + var colorInput = $("")[0]; // if color element is supported, value will default to not null + inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== ""; + } + return inputTypeColorSupport._cachedResult; + }; + + $.spectrum = { }; + $.spectrum.localization = { }; + $.spectrum.palettes = { }; + + $.fn.spectrum.processNativeColorInputs = function () { + var colorInputs = $("input[type=color]"); + if (colorInputs.length && !inputTypeColorSupport()) { + colorInputs.spectrum({ + preferredFormat: "hex6" + }); + } + }; + + // TinyColor v1.1.2 + // https://github.com/bgrins/TinyColor + // Brian Grinstead, MIT License + + (function() { + + var trimLeft = /^[\s,#]+/, + trimRight = /\s+$/, + tinyCounter = 0, + math = Math, + mathRound = math.round, + mathMin = math.min, + mathMax = math.max, + mathRandom = math.random; + + var tinycolor = function(color, opts) { + + color = (color) ? color : ''; + opts = opts || { }; + + // If input is already a tinycolor, return itself + if (color instanceof tinycolor) { + return color; + } + // If we are called as a function, call using new instead + if (!(this instanceof tinycolor)) { + return new tinycolor(color, opts); + } + + var rgb = inputToRGB(color); + this._originalInput = color, + this._r = rgb.r, + this._g = rgb.g, + this._b = rgb.b, + this._a = rgb.a, + this._roundA = mathRound(100*this._a) / 100, + this._format = opts.format || rgb.format; + this._gradientType = opts.gradientType; + + // Don't let the range of [0,255] come back in [0,1]. + // Potentially lose a little bit of precision here, but will fix issues where + // .5 gets interpreted as half of the total, instead of half of 1 + // If it was supposed to be 128, this was already taken care of by `inputToRgb` + if (this._r < 1) { this._r = mathRound(this._r); } + if (this._g < 1) { this._g = mathRound(this._g); } + if (this._b < 1) { this._b = mathRound(this._b); } + + this._ok = rgb.ok; + this._tc_id = tinyCounter++; + }; + + tinycolor.prototype = { + isDark: function() { + return this.getBrightness() < 128; + }, + isLight: function() { + return !this.isDark(); + }, + isValid: function() { + return this._ok; + }, + getOriginalInput: function() { + return this._originalInput; + }, + getFormat: function() { + return this._format; + }, + getAlpha: function() { + return this._a; + }, + getBrightness: function() { + var rgb = this.toRgb(); + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; + }, + setAlpha: function(value) { + this._a = boundAlpha(value); + this._roundA = mathRound(100*this._a) / 100; + return this; + }, + toHsv: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; + }, + toHsvString: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); + return (this._a == 1) ? + "hsv(" + h + ", " + s + "%, " + v + "%)" : + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; + }, + toHsl: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; + }, + toHslString: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); + return (this._a == 1) ? + "hsl(" + h + ", " + s + "%, " + l + "%)" : + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; + }, + toHex: function(allow3Char) { + return rgbToHex(this._r, this._g, this._b, allow3Char); + }, + toHexString: function(allow3Char) { + return '#' + this.toHex(allow3Char); + }, + toHex8: function() { + return rgbaToHex(this._r, this._g, this._b, this._a); + }, + toHex8String: function() { + return '#' + this.toHex8(); + }, + toRgb: function() { + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; + }, + toRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; + }, + toPercentageRgb: function() { + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; + }, + toPercentageRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; + }, + toName: function() { + if (this._a === 0) { + return "transparent"; + } + + if (this._a < 1) { + return false; + } + + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; + }, + toFilter: function(secondColor) { + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); + var secondHex8String = hex8String; + var gradientType = this._gradientType ? "GradientType = 1, " : ""; + + if (secondColor) { + var s = tinycolor(secondColor); + secondHex8String = s.toHex8String(); + } + + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; + }, + toString: function(format) { + var formatSet = !!format; + format = format || this._format; + + var formattedString = false; + var hasAlpha = this._a < 1 && this._a >= 0; + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); + + if (needsAlphaFormat) { + // Special case for "transparent", all other non-alpha formats + // will return rgba when there is transparency. + if (format === "name" && this._a === 0) { + return this.toName(); + } + return this.toRgbString(); + } + if (format === "rgb") { + formattedString = this.toRgbString(); + } + if (format === "prgb") { + formattedString = this.toPercentageRgbString(); + } + if (format === "hex" || format === "hex6") { + formattedString = this.toHexString(); + } + if (format === "hex3") { + formattedString = this.toHexString(true); + } + if (format === "hex8") { + formattedString = this.toHex8String(); + } + if (format === "name") { + formattedString = this.toName(); + } + if (format === "hsl") { + formattedString = this.toHslString(); + } + if (format === "hsv") { + formattedString = this.toHsvString(); + } + + return formattedString || this.toHexString(); + }, + + _applyModification: function(fn, args) { + var color = fn.apply(null, [this].concat([].slice.call(args))); + this._r = color._r; + this._g = color._g; + this._b = color._b; + this.setAlpha(color._a); + return this; + }, + lighten: function() { + return this._applyModification(lighten, arguments); + }, + brighten: function() { + return this._applyModification(brighten, arguments); + }, + darken: function() { + return this._applyModification(darken, arguments); + }, + desaturate: function() { + return this._applyModification(desaturate, arguments); + }, + saturate: function() { + return this._applyModification(saturate, arguments); + }, + greyscale: function() { + return this._applyModification(greyscale, arguments); + }, + spin: function() { + return this._applyModification(spin, arguments); + }, + + _applyCombination: function(fn, args) { + return fn.apply(null, [this].concat([].slice.call(args))); + }, + analogous: function() { + return this._applyCombination(analogous, arguments); + }, + complement: function() { + return this._applyCombination(complement, arguments); + }, + monochromatic: function() { + return this._applyCombination(monochromatic, arguments); + }, + splitcomplement: function() { + return this._applyCombination(splitcomplement, arguments); + }, + triad: function() { + return this._applyCombination(triad, arguments); + }, + tetrad: function() { + return this._applyCombination(tetrad, arguments); + } + }; + + // If input is an object, force 1 into "1.0" to handle ratios properly + // String input requires "1.0" as input, so 1 will be treated as 1 + tinycolor.fromRatio = function(color, opts) { + if (typeof color == "object") { + var newColor = {}; + for (var i in color) { + if (color.hasOwnProperty(i)) { + if (i === "a") { + newColor[i] = color[i]; + } + else { + newColor[i] = convertToPercentage(color[i]); + } + } + } + color = newColor; + } + + return tinycolor(color, opts); + }; + + // Given a string or object, convert that input to RGB + // Possible string inputs: + // + // "red" + // "#f00" or "f00" + // "#ff0000" or "ff0000" + // "#ff000000" or "ff000000" + // "rgb 255 0 0" or "rgb (255, 0, 0)" + // "rgb 1.0 0 0" or "rgb (1, 0, 0)" + // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" + // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" + // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" + // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" + // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" + // + function inputToRGB(color) { + + var rgb = { r: 0, g: 0, b: 0 }; + var a = 1; + var ok = false; + var format = false; + + if (typeof color == "string") { + color = stringInputToObject(color); + } + + if (typeof color == "object") { + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { + rgb = rgbToRgb(color.r, color.g, color.b); + ok = true; + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { + color.s = convertToPercentage(color.s); + color.v = convertToPercentage(color.v); + rgb = hsvToRgb(color.h, color.s, color.v); + ok = true; + format = "hsv"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { + color.s = convertToPercentage(color.s); + color.l = convertToPercentage(color.l); + rgb = hslToRgb(color.h, color.s, color.l); + ok = true; + format = "hsl"; + } + + if (color.hasOwnProperty("a")) { + a = color.a; + } + } + + a = boundAlpha(a); + + return { + ok: ok, + format: color.format || format, + r: mathMin(255, mathMax(rgb.r, 0)), + g: mathMin(255, mathMax(rgb.g, 0)), + b: mathMin(255, mathMax(rgb.b, 0)), + a: a + }; + } + + + // Conversion Functions + // -------------------- + + // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: + // + + // `rgbToRgb` + // Handle bounds / percentage checking to conform to CSS color spec + // + // *Assumes:* r, g, b in [0, 255] or [0, 1] + // *Returns:* { r, g, b } in [0, 255] + function rgbToRgb(r, g, b){ + return { + r: bound01(r, 255) * 255, + g: bound01(g, 255) * 255, + b: bound01(b, 255) * 255 + }; + } + + // `rgbToHsl` + // Converts an RGB color value to HSL. + // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] + // *Returns:* { h, s, l } in [0,1] + function rgbToHsl(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + + return { h: h, s: s, l: l }; + } + + // `hslToRgb` + // Converts an HSL color value to RGB. + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] + // *Returns:* { r, g, b } in the set [0, 255] + function hslToRgb(h, s, l) { + var r, g, b; + + h = bound01(h, 360); + s = bound01(s, 100); + l = bound01(l, 100); + + function hue2rgb(p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + if(s === 0) { + r = g = b = l; // achromatic + } + else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: r * 255, g: g * 255, b: b * 255 }; + } + + // `rgbToHsv` + // Converts an RGB color value to HSV + // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] + // *Returns:* { h, s, v } in [0,1] + function rgbToHsv(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max === 0 ? 0 : d / max; + + if(max == min) { + h = 0; // achromatic + } + else { + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h, s: s, v: v }; + } + + // `hsvToRgb` + // Converts an HSV color value to RGB. + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] + // *Returns:* { r, g, b } in the set [0, 255] + function hsvToRgb(h, s, v) { + + h = bound01(h, 360) * 6; + s = bound01(s, 100); + v = bound01(v, 100); + + var i = math.floor(h), + f = h - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + mod = i % 6, + r = [v, q, p, p, t, v][mod], + g = [t, v, v, q, p, p][mod], + b = [p, p, t, v, v, q][mod]; + + return { r: r * 255, g: g * 255, b: b * 255 }; + } + + // `rgbToHex` + // Converts an RGB color to hex + // Assumes r, g, and b are contained in the set [0, 255] + // Returns a 3 or 6 character hex + function rgbToHex(r, g, b, allow3Char) { + + var hex = [ + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + // Return a 3 character hex if possible + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); + } + + return hex.join(""); + } + // `rgbaToHex` + // Converts an RGBA color plus alpha transparency to hex + // Assumes r, g, b and a are contained in the set [0, 255] + // Returns an 8 character hex + function rgbaToHex(r, g, b, a) { + + var hex = [ + pad2(convertDecimalToHex(a)), + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + return hex.join(""); + } + + // `equals` + // Can be called with any tinycolor input + tinycolor.equals = function (color1, color2) { + if (!color1 || !color2) { return false; } + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); + }; + tinycolor.random = function() { + return tinycolor.fromRatio({ + r: mathRandom(), + g: mathRandom(), + b: mathRandom() + }); + }; + + + // Modification Functions + // ---------------------- + // Thanks to less.js for some of the basics here + // + + function desaturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s -= amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); + } + + function saturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s += amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); + } + + function greyscale(color) { + return tinycolor(color).desaturate(100); + } + + function lighten (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l += amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); + } + + function brighten(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var rgb = tinycolor(color).toRgb(); + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); + return tinycolor(rgb); + } + + function darken (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l -= amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); + } + + // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. + // Values outside of this range will be wrapped into this range. + function spin(color, amount) { + var hsl = tinycolor(color).toHsl(); + var hue = (mathRound(hsl.h) + amount) % 360; + hsl.h = hue < 0 ? 360 + hue : hue; + return tinycolor(hsl); + } + + // Combination Functions + // --------------------- + // Thanks to jQuery xColor for some of the ideas behind these + // + + function complement(color) { + var hsl = tinycolor(color).toHsl(); + hsl.h = (hsl.h + 180) % 360; + return tinycolor(hsl); + } + + function triad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) + ]; + } + + function tetrad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) + ]; + } + + function splitcomplement(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) + ]; + } + + function analogous(color, results, slices) { + results = results || 6; + slices = slices || 30; + + var hsl = tinycolor(color).toHsl(); + var part = 360 / slices; + var ret = [tinycolor(color)]; + + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { + hsl.h = (hsl.h + part) % 360; + ret.push(tinycolor(hsl)); + } + return ret; + } + + function monochromatic(color, results) { + results = results || 6; + var hsv = tinycolor(color).toHsv(); + var h = hsv.h, s = hsv.s, v = hsv.v; + var ret = []; + var modification = 1 / results; + + while (results--) { + ret.push(tinycolor({ h: h, s: s, v: v})); + v = (v + modification) % 1; + } + + return ret; + } + + // Utility Functions + // --------------------- + + tinycolor.mix = function(color1, color2, amount) { + amount = (amount === 0) ? 0 : (amount || 50); + + var rgb1 = tinycolor(color1).toRgb(); + var rgb2 = tinycolor(color2).toRgb(); + + var p = amount / 100; + var w = p * 2 - 1; + var a = rgb2.a - rgb1.a; + + var w1; + + if (w * a == -1) { + w1 = w; + } else { + w1 = (w + a) / (1 + w * a); + } + + w1 = (w1 + 1) / 2; + + var w2 = 1 - w1; + + var rgba = { + r: rgb2.r * w1 + rgb1.r * w2, + g: rgb2.g * w1 + rgb1.g * w2, + b: rgb2.b * w1 + rgb1.b * w2, + a: rgb2.a * p + rgb1.a * (1 - p) + }; + + return tinycolor(rgba); + }; + + + // Readability Functions + // --------------------- + // + + // `readability` + // Analyze the 2 colors and returns an object with the following properties: + // `brightness`: difference in brightness between the two colors + // `color`: difference in color/hue between the two colors + tinycolor.readability = function(color1, color2) { + var c1 = tinycolor(color1); + var c2 = tinycolor(color2); + var rgb1 = c1.toRgb(); + var rgb2 = c2.toRgb(); + var brightnessA = c1.getBrightness(); + var brightnessB = c2.getBrightness(); + var colorDiff = ( + Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + + Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + + Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) + ); + + return { + brightness: Math.abs(brightnessA - brightnessB), + color: colorDiff + }; + }; + + // `readable` + // http://www.w3.org/TR/AERT#color-contrast + // Ensure that foreground and background color combinations provide sufficient contrast. + // *Example* + // tinycolor.isReadable("#000", "#111") => false + tinycolor.isReadable = function(color1, color2) { + var readability = tinycolor.readability(color1, color2); + return readability.brightness > 125 && readability.color > 500; + }; + + // `mostReadable` + // Given a base color and a list of possible foreground or background + // colors for that base, returns the most readable color. + // *Example* + // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" + tinycolor.mostReadable = function(baseColor, colorList) { + var bestColor = null; + var bestScore = 0; + var bestIsReadable = false; + for (var i=0; i < colorList.length; i++) { + + // We normalize both around the "acceptable" breaking point, + // but rank brightness constrast higher than hue. + + var readability = tinycolor.readability(baseColor, colorList[i]); + var readable = readability.brightness > 125 && readability.color > 500; + var score = 3 * (readability.brightness / 125) + (readability.color / 500); + + if ((readable && ! bestIsReadable) || + (readable && bestIsReadable && score > bestScore) || + ((! readable) && (! bestIsReadable) && score > bestScore)) { + bestIsReadable = readable; + bestScore = score; + bestColor = tinycolor(colorList[i]); + } + } + return bestColor; + }; + + + // Big List of Colors + // ------------------ + // + var names = tinycolor.names = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + burntsienna: "ea7e5d", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + grey: "808080", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + rebeccapurple: "663399", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32" + }; + + // Make it easy to access colors via `hexNames[hex]` + var hexNames = tinycolor.hexNames = flip(names); + + + // Utilities + // --------- + + // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` + function flip(o) { + var flipped = { }; + for (var i in o) { + if (o.hasOwnProperty(i)) { + flipped[o[i]] = i; + } + } + return flipped; + } + + // Return a valid alpha value [0,1] with all invalid values being set to 1 + function boundAlpha(a) { + a = parseFloat(a); + + if (isNaN(a) || a < 0 || a > 1) { + a = 1; + } + + return a; + } + + // Take input from [0, n] and return it as [0, 1] + function bound01(n, max) { + if (isOnePointZero(n)) { n = "100%"; } + + var processPercent = isPercentage(n); + n = mathMin(max, mathMax(0, parseFloat(n))); + + // Automatically convert percentage into number + if (processPercent) { + n = parseInt(n * max, 10) / 100; + } + + // Handle floating point rounding errors + if ((math.abs(n - max) < 0.000001)) { + return 1; + } + + // Convert into [0, 1] range if it isn't already + return (n % max) / parseFloat(max); + } + + // Force a number between 0 and 1 + function clamp01(val) { + return mathMin(1, mathMax(0, val)); + } + + // Parse a base-16 hex value into a base-10 integer + function parseIntFromHex(val) { + return parseInt(val, 16); + } + + // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 + // + function isOnePointZero(n) { + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; + } + + // Check to see if string passed in is a percentage + function isPercentage(n) { + return typeof n === "string" && n.indexOf('%') != -1; + } + + // Force a hex value to have 2 characters + function pad2(c) { + return c.length == 1 ? '0' + c : '' + c; + } + + // Replace a decimal with it's percentage value + function convertToPercentage(n) { + if (n <= 1) { + n = (n * 100) + "%"; + } + + return n; + } + + // Converts a decimal to a hex value + function convertDecimalToHex(d) { + return Math.round(parseFloat(d) * 255).toString(16); + } + // Converts a hex value to a decimal + function convertHexToDecimal(h) { + return (parseIntFromHex(h) / 255); + } + + var matchers = (function() { + + // + var CSS_INTEGER = "[-\\+]?\\d+%?"; + + // + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; + + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; + + // Actual matching. + // Parentheses and commas are optional, but not required. + // Whitespace can take the place of commas or opening paren + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + + return { + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), + hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), + hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, + hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ + }; + })(); + + // `stringInputToObject` + // Permissive string parsing. Take in a number of formats, and output an object + // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` + function stringInputToObject(color) { + + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); + var named = false; + if (names[color]) { + color = names[color]; + named = true; + } + else if (color == 'transparent') { + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; + } + + // Try to match string input using regular expressions. + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] + // Just return an object and let the conversion functions handle that. + // This way the result will be the same whether the tinycolor is initialized with string or object. + var match; + if ((match = matchers.rgb.exec(color))) { + return { r: match[1], g: match[2], b: match[3] }; + } + if ((match = matchers.rgba.exec(color))) { + return { r: match[1], g: match[2], b: match[3], a: match[4] }; + } + if ((match = matchers.hsl.exec(color))) { + return { h: match[1], s: match[2], l: match[3] }; + } + if ((match = matchers.hsla.exec(color))) { + return { h: match[1], s: match[2], l: match[3], a: match[4] }; + } + if ((match = matchers.hsv.exec(color))) { + return { h: match[1], s: match[2], v: match[3] }; + } + if ((match = matchers.hsva.exec(color))) { + return { h: match[1], s: match[2], v: match[3], a: match[4] }; + } + if ((match = matchers.hex8.exec(color))) { + return { + a: convertHexToDecimal(match[1]), + r: parseIntFromHex(match[2]), + g: parseIntFromHex(match[3]), + b: parseIntFromHex(match[4]), + format: named ? "name" : "hex8" + }; + } + if ((match = matchers.hex6.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + format: named ? "name" : "hex" + }; + } + if ((match = matchers.hex3.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + format: named ? "name" : "hex" + }; + } + + return false; + } + + window.tinycolor = tinycolor; + })(); + + $(function () { + if ($.fn.spectrum.load) { + $.fn.spectrum.processNativeColorInputs(); + } + }); + +}); diff --git a/vendor/bitjs/archive/archive.js b/vendor/bitjs/archive/archive.js new file mode 100644 index 0000000..52fc2a2 --- /dev/null +++ b/vendor/bitjs/archive/archive.js @@ -0,0 +1,341 @@ +/** + * archive.js + * + * Provides base functionality for unarchiving. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + */ + +var bitjs = bitjs || {}; +bitjs.archive = bitjs.archive || {}; + +/** + * An unarchive event. + */ +bitjs.archive.UnarchiveEvent = class { + /** + * @param {string} type The event type. + */ + constructor(type) { + /** + * The event type. + * @type {string} + */ + this.type = type; + } +} + +/** + * The UnarchiveEvent types. + */ +bitjs.archive.UnarchiveEvent.Type = { + START: 'start', + PROGRESS: 'progress', + EXTRACT: 'extract', + FINISH: 'finish', + INFO: 'info', + ERROR: 'error' +}; + +/** + * Useful for passing info up to the client (for debugging). + */ +bitjs.archive.UnarchiveInfoEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The info message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.INFO); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * An unrecoverable error has occured. + */ +bitjs.archive.UnarchiveErrorEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The error message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.ERROR); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * Start event. + */ +bitjs.archive.UnarchiveStartEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.START); + } +} + +/** + * Finish event. + */ +bitjs.archive.UnarchiveFinishEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.FINISH); + } +} + +/** + * Progress event. + */ +bitjs.archive.UnarchiveProgressEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} currentFilename + * @param {number} currentFileNumber + * @param {number} currentBytesUnarchivedInFile + * @param {number} currentBytesUnarchived + * @param {number} totalUncompressedBytesInArchive + * @param {number} totalFilesInArchive + */ + constructor(currentFilename, currentFileNumber, currentBytesUnarchivedInFile, + currentBytesUnarchived, totalUncompressedBytesInArchive, totalFilesInArchive) { + super(bitjs.archive.UnarchiveEvent.Type.PROGRESS); + + this.currentFilename = currentFilename; + this.currentFileNumber = currentFileNumber; + this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; + this.totalFilesInArchive = totalFilesInArchive; + this.currentBytesUnarchived = currentBytesUnarchived; + this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; + } +} + +/** + * Extract event. + */ +bitjs.archive.UnarchiveExtractEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {UnarchivedFile} unarchivedFile + */ + constructor(unarchivedFile) { + super(bitjs.archive.UnarchiveEvent.Type.EXTRACT); + + /** + * @type {UnarchivedFile} + */ + this.unarchivedFile = unarchivedFile; + } +} + +/** + * All extracted files returned by an Unarchiver will implement + * the following interface: + * + * interface UnarchivedFile { + * string filename + * TypedArray fileData + * } + * + */ + +/** + * Base class for all Unarchivers. + */ +bitjs.archive.Unarchiver = class { + /** + * @param {ArrayBuffer} arrayBuffer The Array Buffer. + * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located. + */ + constructor(arrayBuffer, opt_pathToBitJS) { + /** + * The ArrayBuffer object. + * @type {ArrayBuffer} + * @protected + */ + this.ab = arrayBuffer; + + /** + * The path to the BitJS files. + * @type {string} + * @private + */ + this.pathToBitJS_ = opt_pathToBitJS || '/'; + + /** + * A map from event type to an array of listeners. + * @type {Map.} + */ + this.listeners_ = {}; + for (let type in bitjs.archive.UnarchiveEvent.Type) { + this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; + } + + /** + * Private web worker initialized during start(). + * @type {Worker} + * @private + */ + this.worker_ = null; + } + + /** + * This method must be overridden by the subclass to return the script filename. + * @return {string} The script filename. + * @protected. + */ + getScriptFileName() { + throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()'; + } + + /** + * Adds an event listener for UnarchiveEvents. + * + * @param {string} Event type. + * @param {function} An event handler function. + */ + addEventListener(type, listener) { + if (type in this.listeners_) { + if (this.listeners_[type].indexOf(listener) == -1) { + this.listeners_[type].push(listener); + } + } + } + + /** + * Removes an event listener. + * + * @param {string} Event type. + * @param {EventListener|function} An event listener or handler function. + */ + removeEventListener(type, listener) { + if (type in this.listeners_) { + const index = this.listeners_[type].indexOf(listener); + if (index != -1) { + this.listeners_[type].splice(index, 1); + } + } + } + + /** + * Receive an event and pass it to the listener functions. + * + * @param {bitjs.archive.UnarchiveEvent} e + * @private + */ + handleWorkerEvent_(e) { + if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && + this.listeners_[e.type] instanceof Array) { + this.listeners_[e.type].forEach(function (listener) { listener(e) }); + if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { + this.worker_.terminate(); + } + } else { + console.log(e); + } + } + + /** + * Starts the unarchive in a separate Web Worker thread and returns immediately. + */ + start() { + const me = this; + const scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); + if (scriptFileName) { + this.worker_ = new Worker(scriptFileName); + + this.worker_.onerror = function(e) { + console.log('Worker error: message = ' + e.message); + throw e; + }; + + this.worker_.onmessage = function(e) { + if (typeof e.data == 'string') { + // Just log any strings the workers pump our way. + console.log(e.data); + } else { + // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' + // so that instanceof UnarchiveEvent returns true, but others do not. + me.handleWorkerEvent_(e.data); + } + }; + + this.worker_.postMessage({file: this.ab}); + } + } + + /** + * Terminates the Web Worker for this Unarchiver and returns immediately. + */ + stop() { + if (this.worker_) { + this.worker_.terminate(); + } + } +} + + +/** + * Unzipper + */ +bitjs.archive.Unzipper = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unzip.js'; } +} + + +/** + * Unrarrer + */ +bitjs.archive.Unrarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unrar.js'; } +} + +/** + * Untarrer + * @extends {bitjs.archive.Unarchiver} + * @constructor + */ +bitjs.archive.Untarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/untar.js'; }; +} + +/** + * Factory method that creates an unarchiver based on the byte signature found + * in the arrayBuffer. + * @param {ArrayBuffer} ab + * @param {string=} opt_pathToBitJS Path to the unarchiver script files. + * @return {bitjs.archive.Unarchiver} + */ +bitjs.archive.GetUnarchiver = function(ab, opt_pathToBitJS) { + let unarchiver = null; + const pathToBitJS = opt_pathToBitJS || ''; + const h = new Uint8Array(ab, 0, 10); + + if (h[0] == 0x52 && h[1] == 0x61 && h[2] == 0x72 && h[3] == 0x21) { // Rar! + unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS); + } else if (h[0] == 0x50 && h[1] == 0x4B) { // PK (Zip) + unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS); + } else { // Try with tar + unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS); + } + return unarchiver; +}; diff --git a/vendor/bitjs/archive/rarvm.js b/vendor/bitjs/archive/rarvm.js new file mode 100644 index 0000000..98a1cb8 --- /dev/null +++ b/vendor/bitjs/archive/rarvm.js @@ -0,0 +1,1009 @@ +/** + * rarvm.js + * + * Licensed under the MIT License + * + * Copyright(c) 2017 Google Inc. + */ + +/** + * CRC Implementation. + */ +const CRCTab = new Array(256).fill(0); + +// Helper functions between signed and unsigned integers. + +/** + * -1 becomes 0xffffffff + */ +function fromSigned32ToUnsigned32(val) { + return (val < 0) ? (val += 0x100000000) : val; +} + +/** + * 0xffffffff becomes -1 + */ +function fromUnsigned32ToSigned32(val) { + return (val >= 0x80000000) ? (val -= 0x100000000) : val; +} + +/** + * -1 becomes 0xff + */ +function fromSigned8ToUnsigned8(val) { + return (val < 0) ? (val += 0x100) : val; +} + +/** + * 0xff becomes -1 + */ +function fromUnsigned8ToSigned8(val) { + return (val >= 0x80) ? (val -= 0x100) : val; +} + +function InitCRC() { + for (let i = 0; i < 256; ++i) { + let c = i; + for (let j = 0; j < 8; ++j) { + // Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints + // for the bitwise operator issue (JS interprets operands as 32-bit signed + // integers and we need to deal with unsigned ones here). + c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0; + } + CRCTab[i] = c; + } +} + +/** + * @param {number} startCRC + * @param {Uint8Array} arr + * @return {number} + */ +function CRC(startCRC, arr) { + if (CRCTab[1] == 0) { + InitCRC(); + } + +/* +#if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT) + while (Size>0 && ((long)Data & 7)) + { + StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8); + Size--; + Data++; + } + while (Size>=8) + { + StartCRC^=*(uint32 *)Data; + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC^=*(uint32 *)(Data+4); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + Data+=8; + Size-=8; + } +#endif +*/ + + for (let i = 0; i < arr.length; ++i) { + const byte = ((startCRC ^ arr[i]) >>> 0) & 0xff; + startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0; + } + + return startCRC; +} + +// ============================================================================================== // + + +/** + * RarVM Implementation. + */ +const VM_MEMSIZE = 0x40000; +const VM_MEMMASK = (VM_MEMSIZE - 1); +const VM_GLOBALMEMADDR = 0x3C000; +const VM_GLOBALMEMSIZE = 0x2000; +const VM_FIXEDGLOBALSIZE = 64; +const MAXWINSIZE = 0x400000; +const MAXWINMASK = (MAXWINSIZE - 1); + +/** + */ +const VM_Commands = { + VM_MOV: 0, + VM_CMP: 1, + VM_ADD: 2, + VM_SUB: 3, + VM_JZ: 4, + VM_JNZ: 5, + VM_INC: 6, + VM_DEC: 7, + VM_JMP: 8, + VM_XOR: 9, + VM_AND: 10, + VM_OR: 11, + VM_TEST: 12, + VM_JS: 13, + VM_JNS: 14, + VM_JB: 15, + VM_JBE: 16, + VM_JA: 17, + VM_JAE: 18, + VM_PUSH: 19, + VM_POP: 20, + VM_CALL: 21, + VM_RET: 22, + VM_NOT: 23, + VM_SHL: 24, + VM_SHR: 25, + VM_SAR: 26, + VM_NEG: 27, + VM_PUSHA: 28, + VM_POPA: 29, + VM_PUSHF: 30, + VM_POPF: 31, + VM_MOVZX: 32, + VM_MOVSX: 33, + VM_XCHG: 34, + VM_MUL: 35, + VM_DIV: 36, + VM_ADC: 37, + VM_SBB: 38, + VM_PRINT: 39, + +/* +#ifdef VM_OPTIMIZE + VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD, + + VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD, + VM_NEGB, VM_NEGD, +#endif +*/ + + // TODO: This enum value would be much larger if VM_OPTIMIZE. + VM_STANDARD: 40, +}; + +/** + */ +const VM_StandardFilters = { + VMSF_NONE: 0, + VMSF_E8: 1, + VMSF_E8E9: 2, + VMSF_ITANIUM: 3, + VMSF_RGB: 4, + VMSF_AUDIO: 5, + VMSF_DELTA: 6, + VMSF_UPCASE: 7, +}; + +/** + */ +const VM_Flags = { + VM_FC: 1, + VM_FZ: 2, + VM_FS: 0x80000000, +}; + +/** + */ +const VM_OpType = { + VM_OPREG: 0, + VM_OPINT: 1, + VM_OPREGMEM: 2, + VM_OPNONE: 3, +}; + +/** + * Finds the key that maps to a given value in an object. This function is useful in debugging + * variables that use the above enums. + * @param {Object} obj + * @param {number} val + * @return {string} The key/enum value as a string. + */ +function findKeyForValue(obj, val) { + for (let key in obj) { + if (obj[key] === val) { + return key; + } + } + return null; +} + +function getDebugString(obj, val) { + let s = 'Unknown.'; + if (obj === VM_Commands) { + s = 'VM_Commands.'; + } else if (obj === VM_StandardFilters) { + s = 'VM_StandardFilters.'; + } else if (obj === VM_Flags) { + s = 'VM_OpType.'; + } else if (obj === VM_OpType) { + s = 'VM_OpType.'; + } + + return s + findKeyForValue(obj, val); +} + +/** + */ +class VM_PreparedOperand { + constructor() { + /** @type {VM_OpType} */ + this.Type; + + /** @type {number} */ + this.Data = 0; + + /** @type {number} */ + this.Base = 0; + + // TODO: In C++ this is a uint* + /** @type {Array} */ + this.Addr = null; + }; + + /** @return {string} */ + toString() { + if (this.Type === null) { + return 'Error: Type was null in VM_PreparedOperand'; + } + return '{ ' + + 'Type: ' + getDebugString(VM_OpType, this.Type) + + ', Data: ' + this.Data + + ', Base: ' + this.Base + + ' }'; + } +} + +/** + */ +class VM_PreparedCommand { + constructor() { + /** @type {VM_Commands} */ + this.OpCode; + + /** @type {boolean} */ + this.ByteMode = false; + + /** @type {VM_PreparedOperand} */ + this.Op1 = new VM_PreparedOperand(); + + /** @type {VM_PreparedOperand} */ + this.Op2 = new VM_PreparedOperand(); + } + + /** @return {string} */ + toString(indent) { + if (this.OpCode === null) { + return 'Error: OpCode was null in VM_PreparedCommand'; + } + indent = indent || ''; + return indent + '{\n' + + indent + ' OpCode: ' + getDebugString(VM_Commands, this.OpCode) + ',\n' + + indent + ' ByteMode: ' + this.ByteMode + ',\n' + + indent + ' Op1: ' + this.Op1.toString() + ',\n' + + indent + ' Op2: ' + this.Op2.toString() + ',\n' + + indent + '}'; + } +} + +/** + */ +class VM_PreparedProgram { + constructor() { + /** @type {Array} */ + this.Cmd = []; + + /** @type {Array} */ + this.AltCmd = null; + + /** @type {Uint8Array} */ + this.GlobalData = new Uint8Array(); + + /** @type {Uint8Array} */ + this.StaticData = new Uint8Array(); // static data contained in DB operators + + /** @type {Uint32Array} */ + this.InitR = new Uint32Array(7); + + /** + * A pointer to bytes that have been filtered by a program. + * @type {Uint8Array} + */ + this.FilteredData = null; + } + + /** @return {string} */ + toString() { + let s = '{\n Cmd: [\n'; + for (let i = 0; i < this.Cmd.length; ++i) { + s += this.Cmd[i].toString(' ') + ',\n'; + } + s += '],\n'; + // TODO: Dump GlobalData, StaticData, InitR? + s += ' }\n'; + return s; + } +} + +/** + */ +class UnpackFilter { + constructor() { + /** @type {number} */ + this.BlockStart = 0; + + /** @type {number} */ + this.BlockLength = 0; + + /** @type {number} */ + this.ExecCount = 0; + + /** @type {boolean} */ + this.NextWindow = false; + + // position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + /** @type {number} */ + this.ParentFilter = null; + + /** @type {VM_PreparedProgram} */ + this.Prg = new VM_PreparedProgram(); + } +} + +const VMCF_OP0 = 0; +const VMCF_OP1 = 1; +const VMCF_OP2 = 2; +const VMCF_OPMASK = 3; +const VMCF_BYTEMODE = 4; +const VMCF_JUMP = 8; +const VMCF_PROC = 16; +const VMCF_USEFLAGS = 32; +const VMCF_CHFLAGS = 64; + +const VM_CmdFlags = [ + /* VM_MOV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_CMP */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_ADD */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SUB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_INC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_DEC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JMP */ VMCF_OP1 | VMCF_JUMP , + /* VM_XOR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_AND */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_OR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_TEST */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JB */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JBE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JA */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JAE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_PUSH */ VMCF_OP1 , + /* VM_POP */ VMCF_OP1 , + /* VM_CALL */ VMCF_OP1 | VMCF_PROC , + /* VM_RET */ VMCF_OP0 | VMCF_PROC , + /* VM_NOT */ VMCF_OP1 | VMCF_BYTEMODE , + /* VM_SHL */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SHR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SAR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_NEG */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_PUSHA */ VMCF_OP0 , + /* VM_POPA */ VMCF_OP0 , + /* VM_PUSHF */ VMCF_OP0 | VMCF_USEFLAGS , + /* VM_POPF */ VMCF_OP0 | VMCF_CHFLAGS , + /* VM_MOVZX */ VMCF_OP2 , + /* VM_MOVSX */ VMCF_OP2 , + /* VM_XCHG */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_MUL */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_DIV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_ADC */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_SBB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_PRINT */ VMCF_OP0 , +]; + + +/** + */ +class StandardFilterSignature { + /** + * @param {number} length + * @param {number} crc + * @param {VM_StandardFilters} type + */ + constructor(length, crc, type) { + /** @type {number} */ + this.Length = length; + + /** @type {number} */ + this.CRC = crc; + + /** @type {VM_StandardFilters} */ + this.Type = type; + } +} + +/** + * @type {Array} + */ +const StdList = [ + new StandardFilterSignature(53, 0xad576887, VM_StandardFilters.VMSF_E8), + new StandardFilterSignature(57, 0x3cd7e57e, VM_StandardFilters.VMSF_E8E9), + new StandardFilterSignature(120, 0x3769893f, VM_StandardFilters.VMSF_ITANIUM), + new StandardFilterSignature(29, 0x0e06077d, VM_StandardFilters.VMSF_DELTA), + new StandardFilterSignature(149, 0x1c2c5dc8, VM_StandardFilters.VMSF_RGB), + new StandardFilterSignature(216, 0xbc85e701, VM_StandardFilters.VMSF_AUDIO), + new StandardFilterSignature(40, 0x46b9c560, VM_StandardFilters.VMSF_UPCASE), +]; + +/** + * @constructor + */ +class RarVM { + constructor() { + /** @private {Uint8Array} */ + this.mem_ = null; + + /** @private {Uint32Array} */ + this.R_ = new Uint32Array(8); + + /** @private {number} */ + this.flags_ = 0; + } + + /** + * Initializes the memory of the VM. + */ + init() { + if (!this.mem_) { + this.mem_ = new Uint8Array(VM_MEMSIZE); + } + } + + /** + * @param {Uint8Array} code + * @return {VM_StandardFilters} + */ + isStandardFilter(code) { + const codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0; + for (let i = 0; i < StdList.length; ++i) { + if (StdList[i].CRC == codeCRC && StdList[i].Length == code.length) + return StdList[i].Type; + } + + return VM_StandardFilters.VMSF_NONE; + } + + /** + * @param {VM_PreparedOperand} op + * @param {boolean} byteMode + * @param {bitjs.io.BitStream} bstream A rtl bit stream. + */ + decodeArg(op, byteMode, bstream) { + const data = bstream.peekBits(16); + if (data & 0x8000) { + op.Type = VM_OpType.VM_OPREG; // Operand is register (R[0]..R[7]) + bstream.readBits(1); // 1 flag bit and... + op.Data = bstream.readBits(3); // ... 3 register number bits + op.Addr = [this.R_[op.Data]] // TODO &R[Op.Data] // Register address + } else { + if ((data & 0xc000) == 0) { + op.Type = VM_OpType.VM_OPINT; // Operand is integer + bstream.readBits(2); // 2 flag bits + if (byteMode) { + op.Data = bstream.readBits(8); // Byte integer. + } else { + op.Data = RarVM.readData(bstream); // 32 bit integer. + } + } else { + // Operand is data addressed by register data, base address or both. + op.Type = VM_OpType.VM_OPREGMEM; + if ((data & 0x2000) == 0) { + bstream.readBits(3); // 3 flag bits + // Base address is zero, just use the address from register. + op.Data = bstream.readBits(3); // (Data>>10)&7 + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + op.Base = 0; + } else { + bstream.readBits(4); // 4 flag bits + if ((data & 0x1000) == 0) { + // Use both register and base address. + op.Data = bstream.readBits(3); + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + } else { + // Use base address only. Access memory by fixed address. + op.Data = 0; + } + op.Base = RarVM.readData(bstream); // Read base address. + } + } + } + } + + /** + * @param {VM_PreparedProgram} prg + */ + execute(prg) { + this.R_.set(prg.InitR); + + const globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE); + if (globalSize) { + this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR); + } + + const staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize); + if (staticSize) { + this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize); + } + + this.R_[7] = VM_MEMSIZE; + this.flags_ = 0; + + const preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd; + if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) { + // Invalid VM program. Let's replace it with 'return' command. + preparedCode.OpCode = VM_Commands.VM_RET; + } + + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + let newBlockPos = dataView.getUint32(0x20, true /* little endian */) & VM_MEMMASK; + const newBlockSize = dataView.getUint32(0x1c, true /* little endian */) & VM_MEMMASK; + if (newBlockPos + newBlockSize >= VM_MEMSIZE) { + newBlockPos = newBlockSize = 0; + } + prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize); + + prg.GlobalData = new Uint8Array(0); + + const dataSize = Math.min(dataView.getUint32(0x30), (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)); + if (dataSize != 0) { + const len = dataSize + VM_FIXEDGLOBALSIZE; + prg.GlobalData = new Uint8Array(len); + prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len)); + } + } + + /** + * @param {Array} preparedCodes + * @return {boolean} + */ + executeCode(preparedCodes) { + let codeIndex = 0; + let cmd = preparedCodes[codeIndex]; + // TODO: Why is this an infinite loop instead of just returning + // when a VM_RET is hit? + while (1) { + switch (cmd.OpCode) { + case VM_Commands.VM_RET: + if (this.R_[7] >= VM_MEMSIZE) { + return true; + } + //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); + this.R_[7] += 4; + continue; + + case VM_Commands.VM_STANDARD: + this.executeStandardFilter(cmd.Op1.Data); + break; + + default: + console.error('RarVM OpCode not supported: ' + getDebugString(VM_Commands, cmd.OpCode)); + break; + } // switch (cmd.OpCode) + codeIndex++; + cmd = preparedCodes[codeIndex]; + } + } + + /** + * @param {number} filterType + */ + executeStandardFilter(filterType) { + switch (filterType) { + case VM_StandardFilters.VMSF_RGB: { + const dataSize = this.R_[4]; + const width = this.R_[0] - 3; + const posR = this.R_[1]; + const Channels = 3; + let srcOffset = 0; + let destOffset = dataSize; + + // byte *SrcData=Mem,*DestData=SrcData+DataSize; + // SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR /* offset */); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= (VM_GLOBALMEMADDR / 2) || posR < 0) { + break; + } + + for (let curChannel = 0; curChannel < Channels; ++curChannel) { + let prevByte=0; + + for (let i = curChannel; i < dataSize; i += Channels) { + let predicted; + const upperPos = i - width; + if (upperPos >= 3) { + const upperByte = this.mem_[destOffset + upperPos]; + const upperLeftByte = this.mem_[destOffset + upperPos - 3]; + predicted = prevByte + upperByte - upperLeftByte; + + const pa = Math.abs(predicted - prevByte); + const pb = Math.abs(predicted - upperByte); + const pc = Math.abs(predicted - upperLeftByte); + if (pa <= pb && pa <= pc) { + predicted = prevByte; + } else if (pb <= pc) { + predicted = upperByte; + } else { + predicted = upperLeftByte; + } + } else { + predicted = prevByte; + } + //DestData[I]=PrevByte=(byte)(Predicted-*(SrcData++)); + prevByte = (predicted - this.mem_[srcOffset++]) & 0xff; + this.mem_[destOffset + i] = prevByte; + } + } + for (let i = posR, border = dataSize - 2; i < border; i += 3) { + const g = this.mem_[destOffset + i + 1]; + this.mem_[destOffset + i] += g; + this.mem_[destOffset + i + 2] += g; + } + + break; + } + + // The C++ version of this standard filter uses an odd mixture of + // signed and unsigned integers, bytes and various casts. Careful! + case VM_StandardFilters.VMSF_AUDIO: { + const dataSize = this.R_[4]; + const channels = this.R_[0]; + let srcOffset = 0; + let destOffset = dataSize; + + //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= VM_GLOBALMEMADDR / 2) { + break; + } + + for (let curChannel = 0; curChannel < channels; ++curChannel) { + let prevByte = 0; // uint + let prevDelta = 0; // uint + let dif = [0, 0, 0, 0, 0, 0, 0]; + let d1 = 0, d2 = 0, d3; // ints + let k1 = 0, k2 = 0, k3 = 0; // ints + + for (var i = curChannel, byteCount = 0; + i < dataSize; + i += channels, ++byteCount) { + d3 = d2; + d2 = fromUnsigned32ToSigned32(prevDelta - d1); + d1 = fromUnsigned32ToSigned32(prevDelta); + + let predicted = fromSigned32ToUnsigned32(8*prevByte + k1*d1 + k2*d2 + k3*d3); // uint + predicted = (predicted >>> 3) & 0xff; + + let curByte = this.mem_[srcOffset++]; // uint + + // Predicted-=CurByte; + predicted = fromSigned32ToUnsigned32(predicted - curByte); + this.mem_[destOffset + i] = (predicted & 0xff); + + // PrevDelta=(signed char)(Predicted-PrevByte); + // where Predicted, PrevByte, PrevDelta are all unsigned int (32) + // casting this subtraction to a (signed char) is kind of invalid + // but it does the following: + // - do the subtraction + // - get the bottom 8 bits of the result + // - if it was >= 0x80, then the value is negative (subtract 0x100) + // - if the value is now negative, add 0x100000000 to make unsigned + // + // Example: + // predicted = 101 + // prevByte = 4294967158 + // (predicted - prevByte) = -4294967057 + // take lower 8 bits: 1110 1111 = 239 + // since > 127, subtract 256 = -17 + // since < 0, add 0x100000000 = 4294967279 + prevDelta = fromSigned32ToUnsigned32( + fromUnsigned8ToSigned8((predicted - prevByte) & 0xff)); + prevByte = predicted; + + // int D=((signed char)CurByte)<<3; + let curByteAsSignedChar = fromUnsigned8ToSigned8(curByte); // signed char + let d = (curByteAsSignedChar << 3); + + dif[0] += Math.abs(d); + dif[1] += Math.abs(d-d1); + dif[2] += Math.abs(d+d1); + dif[3] += Math.abs(d-d2); + dif[4] += Math.abs(d+d2); + dif[5] += Math.abs(d-d3); + dif[6] += Math.abs(d+d3); + + if ((byteCount & 0x1f) == 0) { + let minDif = dif[0], numMinDif = 0; + dif[0] = 0; + for (let j = 1; j < 7; ++j) { + if (dif[j] < minDif) { + minDif = dif[j]; + numMinDif = j; + } + dif[j] = 0; + } + switch (numMinDif) { + case 1: if (k1>=-16) k1--; break; + case 2: if (k1 < 16) k1++; break; + case 3: if (k2>=-16) k2--; break; + case 4: if (k2 < 16) k2++; break; + case 5: if (k3>=-16) k3--; break; + case 6: if (k3 < 16) k3++; break; + } + } + } + } + + break; + } + + case VM_StandardFilters.VMSF_DELTA: { + const dataSize = this.R_[4]; + const channels = this.R_[0]; + let srcPos = 0; + const border = dataSize * 2; + + //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= VM_GLOBALMEMADDR / 2) { + break; + } + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (let curChannel = 0; curChannel < channels; ++curChannel) { + let prevByte = 0; + for (let destPos = dataSize + curChannel; destPos < border; destPos += channels) { + prevByte = (prevByte - this.mem_[srcPos++]) & 0xff; + this.mem_[destPos] = prevByte; + } + } + + break; + } + + default: + console.error('RarVM Standard Filter not supported: ' + getDebugString(VM_StandardFilters, filterType)); + break; + } + } + + /** + * @param {Uint8Array} code + * @param {VM_PreparedProgram} prg + */ + prepare(code, prg) { + let codeSize = code.length; + + //InitBitInput(); + //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); + const bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */); + + // Calculate the single byte XOR checksum to check validity of VM code. + let xorSum = 0; + for (let i = 1; i < codeSize; ++i) { + xorSum ^= code[i]; + } + + bstream.readBits(8); + + prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp. + + // VM code is valid if equal. + if (xorSum == code[0]) { + const filterType = this.isStandardFilter(code); + if (filterType != VM_StandardFilters.VMSF_NONE) { + // VM code is found among standard filters. + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); + + curCmd.OpCode = VM_Commands.VM_STANDARD; + curCmd.Op1.Data = filterType; + // TODO: Addr=&CurCmd->Op1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + codeSize = 0; + } + + const dataFlag = bstream.readBits(1); + + // Read static data contained in DB operators. This data cannot be + // changed, it is a part of VM code, not a filter parameter. + + if (dataFlag & 0x8000) { + const dataSize = RarVM.readData(bstream) + 1; + // TODO: This accesses the byte pointer of the bstream directly. Is that ok? + for (let i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) { + // Append a byte to the program's static data. + const newStaticData = new Uint8Array(prg.StaticData.length + 1); + newStaticData.set(prg.StaticData); + newStaticData[newStaticData.length - 1] = bstream.readBits(8); + prg.StaticData = newStaticData; + } + } + + while (bstream.bytePtr < codeSize) { + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); // Prg->Cmd.Add(1) + const flag = bstream.peekBits(1); + if (!flag) { // (Data&0x8000)==0 + curCmd.OpCode = bstream.readBits(4); + } else { + curCmd.OpCode = (bstream.readBits(6) - 24); + } + + if (VM_CmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) { + curCmd.ByteMode = (bstream.readBits(1) != 0); + } else { + curCmd.ByteMode = 0; + } + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + const opNum = (VM_CmdFlags[curCmd.OpCode] & VMCF_OPMASK); + curCmd.Op1.Addr = null; + curCmd.Op2.Addr = null; + if (opNum > 0) { + this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand + if (opNum == 2) { + this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand + } else { + if (curCmd.Op1.Type == VM_OpType.VM_OPINT && (VM_CmdFlags[curCmd.OpCode] & (VMCF_JUMP|VMCF_PROC))) { + // Calculating jump distance. + let distance = curCmd.Op1.Data; + if (distance >= 256) { + distance -= 256; + } else { + if (distance >= 136) { + distance -= 264; + } else { + if (distance >= 16) { + distance -= 8; + } else { + if (distance >= 8) { + distance -= 16; + } + } + } + distance += prg.Cmd.length; + } + curCmd.Op1.Data = distance; + } + } + } // if (OpNum>0) + } // while ((uint)InAddrOp1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [curCmd.Op2.Data]; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + + // If operand 'Addr' field has not been set by DecodeArg calls above, + // let's set it to point to operand 'Data' field. It is necessary for + // VM_OPINT type operands (usual integers) or maybe if something was + // not set properly for other operands. 'Addr' field is required + // for quicker addressing of operand data. + for (let i = 0; i < prg.Cmd.length; ++i) { + const cmd = prg.Cmd[i]; + if (cmd.Op1.Addr == null) { + cmd.Op1.Addr = [cmd.Op1.Data]; + } + if (cmd.Op2.Addr == null) { + cmd.Op2.Addr = [cmd.Op2.Data]; + } + } + + /* + #ifdef VM_OPTIMIZE + if (CodeSize!=0) + Optimize(Prg); + #endif + */ + } + + /** + * @param {Uint8Array} arr The byte array to set a value in. + * @param {number} value The unsigned 32-bit value to set. + * @param {number} offset Offset into arr to start setting the value, defaults to 0. + */ + setLowEndianValue(arr, value, offset) { + const i = offset || 0; + arr[i] = value & 0xff; + arr[i + 1] = (value >>> 8) & 0xff; + arr[i + 2] = (value >>> 16) & 0xff; + arr[i + 3] = (value >>> 24) & 0xff; + } + + /** + * Sets a number of bytes of the VM memory at the given position from a + * source buffer of bytes. + * @param {number} pos The position in the VM memory to start writing to. + * @param {Uint8Array} buffer The source buffer of bytes. + * @param {number} dataSize The number of bytes to set. + */ + setMemory(pos, buffer, dataSize) { + if (pos < VM_MEMSIZE) { + const numBytes = Math.min(dataSize, VM_MEMSIZE - pos); + for (let i = 0; i < numBytes; ++i) { + this.mem_[pos + i] = buffer[i]; + } + } + } + + /** + * Static function that reads in the next set of bits for the VM + * (might return 4, 8, 16 or 32 bits). + * @param {bitjs.io.BitStream} bstream A RTL bit stream. + * @return {number} The value of the bits read. + */ + static readData(bstream) { + // Read in the first 2 bits. + const flags = bstream.readBits(2); + switch (flags) { // Data&0xc000 + // Return the next 4 bits. + case 0: + return bstream.readBits(4); // (Data>>10)&0xf + + case 1: // 0x4000 + // 0x3c00 => 0011 1100 0000 0000 + if (bstream.peekBits(4) == 0) { // (Data&0x3c00)==0 + // Skip the 4 zero bits. + bstream.readBits(4); + // Read in the next 8 and pad with 1s to 32 bits. + return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff) + } + + // Else, read in the next 8. + return bstream.readBits(8); + + // Read in the next 16. + case 2: // 0x8000 + const val = bstream.getBits(); + bstream.readBits(16); + return val; //bstream.readBits(16); + + // case 3 + default: + return (bstream.readBits(16) << 16) | bstream.readBits(16); + } + } +} + +// ============================================================================================== // diff --git a/vendor/bitjs/archive/unrar.js b/vendor/bitjs/archive/unrar.js new file mode 100644 index 0000000..a2e4c7c --- /dev/null +++ b/vendor/bitjs/archive/unrar.js @@ -0,0 +1,3065 @@ +/** + * unrar.js + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +// This file expects to be invoked as a Worker (see onmessage below). +//importScripts('../io/bitstream.js'); + +/* + * bitstream.js + * + * Provides readers for bitstreams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * This bit stream peeks and consumes bits out of a binary stream. + */ +bitjs.io.BitStream = class { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { + throw "Error! BitArray constructed with an invalid ArrayBuffer object"; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.bytePtr = 0; // tracks which byte we are on + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + const mask = (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + n -= numBitsLeftInThisByte; + } + else { + const mask = (bitjs.io.BitStream.BITMASK[n] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += n; + bitsIn += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + n -= numBitsLeftInThisByte; + } + else { + result <<= n; + result |= ((bytes[bytePtr] & (bitjs.io.BitStream.BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); + + bitPtr += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr+1] & 0xff) << 8) + + ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); + } + + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const movePointers = opt_movePointers || false; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + + const result = this.bytes.subarray(bytePtr, bytePtr + n); + + if (movePointers) { + this.bytePtr += n; + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } +} + +// mask for getting N number of bits (0-8) +bitjs.io.BitStream.BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; + + +//importScripts('../io/bytebuffer.js'); + +/* + * bytestream.js + * + * Provides a writer for bytes. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. + */ +bitjs.io.ByteBuffer = class { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; + } + this.data = new Uint8Array(numBytes); + this.ptr = 0; + } + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; +} + +//importScripts('archive.js'); + +/** + * archive.js + * + * Provides base functionality for unarchiving. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + */ + +var bitjs = bitjs || {}; +bitjs.archive = bitjs.archive || {}; + +/** + * An unarchive event. + */ +bitjs.archive.UnarchiveEvent = class { + /** + * @param {string} type The event type. + */ + constructor(type) { + /** + * The event type. + * @type {string} + */ + this.type = type; + } +} + +/** + * The UnarchiveEvent types. + */ +bitjs.archive.UnarchiveEvent.Type = { + START: 'start', + PROGRESS: 'progress', + EXTRACT: 'extract', + FINISH: 'finish', + INFO: 'info', + ERROR: 'error' +}; + +/** + * Useful for passing info up to the client (for debugging). + */ +bitjs.archive.UnarchiveInfoEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The info message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.INFO); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * An unrecoverable error has occured. + */ +bitjs.archive.UnarchiveErrorEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The error message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.ERROR); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * Start event. + */ +bitjs.archive.UnarchiveStartEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.START); + } +} + +/** + * Finish event. + */ +bitjs.archive.UnarchiveFinishEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.FINISH); + } +} + +/** + * Progress event. + */ +bitjs.archive.UnarchiveProgressEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} currentFilename + * @param {number} currentFileNumber + * @param {number} currentBytesUnarchivedInFile + * @param {number} currentBytesUnarchived + * @param {number} totalUncompressedBytesInArchive + * @param {number} totalFilesInArchive + */ + constructor(currentFilename, currentFileNumber, currentBytesUnarchivedInFile, + currentBytesUnarchived, totalUncompressedBytesInArchive, totalFilesInArchive) { + super(bitjs.archive.UnarchiveEvent.Type.PROGRESS); + + this.currentFilename = currentFilename; + this.currentFileNumber = currentFileNumber; + this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; + this.totalFilesInArchive = totalFilesInArchive; + this.currentBytesUnarchived = currentBytesUnarchived; + this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; + } +} + +/** + * Extract event. + */ +bitjs.archive.UnarchiveExtractEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {UnarchivedFile} unarchivedFile + */ + constructor(unarchivedFile) { + super(bitjs.archive.UnarchiveEvent.Type.EXTRACT); + + /** + * @type {UnarchivedFile} + */ + this.unarchivedFile = unarchivedFile; + } +} + +/** + * All extracted files returned by an Unarchiver will implement + * the following interface: + * + * interface UnarchivedFile { + * string filename + * TypedArray fileData + * } + * + */ + +/** + * Base class for all Unarchivers. + */ +bitjs.archive.Unarchiver = class { + /** + * @param {ArrayBuffer} arrayBuffer The Array Buffer. + * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located. + */ + constructor(arrayBuffer, opt_pathToBitJS) { + /** + * The ArrayBuffer object. + * @type {ArrayBuffer} + * @protected + */ + this.ab = arrayBuffer; + + /** + * The path to the BitJS files. + * @type {string} + * @private + */ + this.pathToBitJS_ = opt_pathToBitJS || '/'; + + /** + * A map from event type to an array of listeners. + * @type {Map.} + */ + this.listeners_ = {}; + for (let type in bitjs.archive.UnarchiveEvent.Type) { + this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; + } + + /** + * Private web worker initialized during start(). + * @type {Worker} + * @private + */ + this.worker_ = null; + } + + /** + * This method must be overridden by the subclass to return the script filename. + * @return {string} The script filename. + * @protected. + */ + getScriptFileName() { + throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()'; + } + + /** + * Adds an event listener for UnarchiveEvents. + * + * @param {string} Event type. + * @param {function} An event handler function. + */ + addEventListener(type, listener) { + if (type in this.listeners_) { + if (this.listeners_[type].indexOf(listener) == -1) { + this.listeners_[type].push(listener); + } + } + } + + /** + * Removes an event listener. + * + * @param {string} Event type. + * @param {EventListener|function} An event listener or handler function. + */ + removeEventListener(type, listener) { + if (type in this.listeners_) { + const index = this.listeners_[type].indexOf(listener); + if (index != -1) { + this.listeners_[type].splice(index, 1); + } + } + } + + /** + * Receive an event and pass it to the listener functions. + * + * @param {bitjs.archive.UnarchiveEvent} e + * @private + */ + handleWorkerEvent_(e) { + if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && + this.listeners_[e.type] instanceof Array) { + this.listeners_[e.type].forEach(function (listener) { listener(e) }); + if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { + this.worker_.terminate(); + } + } else { + console.log(e); + } + } + + /** + * Starts the unarchive in a separate Web Worker thread and returns immediately. + */ + start() { + const me = this; + const scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); + if (scriptFileName) { + this.worker_ = new Worker(scriptFileName); + + this.worker_.onerror = function(e) { + console.log('Worker error: message = ' + e.message); + throw e; + }; + + this.worker_.onmessage = function(e) { + if (typeof e.data == 'string') { + // Just log any strings the workers pump our way. + console.log(e.data); + } else { + // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' + // so that instanceof UnarchiveEvent returns true, but others do not. + me.handleWorkerEvent_(e.data); + } + }; + + this.worker_.postMessage({file: this.ab}); + } + } + + /** + * Terminates the Web Worker for this Unarchiver and returns immediately. + */ + stop() { + if (this.worker_) { + this.worker_.terminate(); + } + } +} + + +/** + * Unzipper + */ +bitjs.archive.Unzipper = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unzip.js'; } +} + + +/** + * Unrarrer + */ +bitjs.archive.Unrarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unrar.js'; } +} + +/** + * Untarrer + * @extends {bitjs.archive.Unarchiver} + * @constructor + */ +bitjs.archive.Untarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/untar.js'; }; +} + +/** + * Factory method that creates an unarchiver based on the byte signature found + * in the arrayBuffer. + * @param {ArrayBuffer} ab + * @param {string=} opt_pathToBitJS Path to the unarchiver script files. + * @return {bitjs.archive.Unarchiver} + */ +bitjs.archive.GetUnarchiver = function(ab, opt_pathToBitJS) { + let unarchiver = null; + const pathToBitJS = opt_pathToBitJS || ''; + const h = new Uint8Array(ab, 0, 10); + + if (h[0] == 0x52 && h[1] == 0x61 && h[2] == 0x72 && h[3] == 0x21) { // Rar! + unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS); + } else if (h[0] == 0x50 && h[1] == 0x4B) { // PK (Zip) + unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS); + } else { // Try with tar + unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS); + } + return unarchiver; +}; + +//importScripts('rarvm.js'); + +/** + * rarvm.js + * + * Licensed under the MIT License + * + * Copyright(c) 2017 Google Inc. + */ + +/** + * CRC Implementation. + */ +const CRCTab = new Array(256).fill(0); + +// Helper functions between signed and unsigned integers. + +/** + * -1 becomes 0xffffffff + */ +function fromSigned32ToUnsigned32(val) { + return (val < 0) ? (val += 0x100000000) : val; +} + +/** + * 0xffffffff becomes -1 + */ +function fromUnsigned32ToSigned32(val) { + return (val >= 0x80000000) ? (val -= 0x100000000) : val; +} + +/** + * -1 becomes 0xff + */ +function fromSigned8ToUnsigned8(val) { + return (val < 0) ? (val += 0x100) : val; +} + +/** + * 0xff becomes -1 + */ +function fromUnsigned8ToSigned8(val) { + return (val >= 0x80) ? (val -= 0x100) : val; +} + +function InitCRC() { + for (let i = 0; i < 256; ++i) { + let c = i; + for (let j = 0; j < 8; ++j) { + // Read http://stackoverflow.com/questions/6798111/bitwise-operations-on-32-bit-unsigned-ints + // for the bitwise operator issue (JS interprets operands as 32-bit signed + // integers and we need to deal with unsigned ones here). + c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)) >>> 0; + } + CRCTab[i] = c; + } +} + +/** + * @param {number} startCRC + * @param {Uint8Array} arr + * @return {number} + */ +function CRC(startCRC, arr) { + if (CRCTab[1] == 0) { + InitCRC(); + } + +/* +#if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT) + while (Size>0 && ((long)Data & 7)) + { + StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8); + Size--; + Data++; + } + while (Size>=8) + { + StartCRC^=*(uint32 *)Data; + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC^=*(uint32 *)(Data+4); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + Data+=8; + Size-=8; + } +#endif +*/ + + for (let i = 0; i < arr.length; ++i) { + const byte = ((startCRC ^ arr[i]) >>> 0) & 0xff; + startCRC = (CRCTab[byte] ^ (startCRC >>> 8)) >>> 0; + } + + return startCRC; +} + +// ============================================================================================== // + + +/** + * RarVM Implementation. + */ +const VM_MEMSIZE = 0x40000; +const VM_MEMMASK = (VM_MEMSIZE - 1); +const VM_GLOBALMEMADDR = 0x3C000; +const VM_GLOBALMEMSIZE = 0x2000; +const VM_FIXEDGLOBALSIZE = 64; +const MAXWINSIZE = 0x400000; +const MAXWINMASK = (MAXWINSIZE - 1); + +/** + */ +const VM_Commands = { + VM_MOV: 0, + VM_CMP: 1, + VM_ADD: 2, + VM_SUB: 3, + VM_JZ: 4, + VM_JNZ: 5, + VM_INC: 6, + VM_DEC: 7, + VM_JMP: 8, + VM_XOR: 9, + VM_AND: 10, + VM_OR: 11, + VM_TEST: 12, + VM_JS: 13, + VM_JNS: 14, + VM_JB: 15, + VM_JBE: 16, + VM_JA: 17, + VM_JAE: 18, + VM_PUSH: 19, + VM_POP: 20, + VM_CALL: 21, + VM_RET: 22, + VM_NOT: 23, + VM_SHL: 24, + VM_SHR: 25, + VM_SAR: 26, + VM_NEG: 27, + VM_PUSHA: 28, + VM_POPA: 29, + VM_PUSHF: 30, + VM_POPF: 31, + VM_MOVZX: 32, + VM_MOVSX: 33, + VM_XCHG: 34, + VM_MUL: 35, + VM_DIV: 36, + VM_ADC: 37, + VM_SBB: 38, + VM_PRINT: 39, + +/* +#ifdef VM_OPTIMIZE + VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD, + + VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD, + VM_NEGB, VM_NEGD, +#endif +*/ + + // TODO: This enum value would be much larger if VM_OPTIMIZE. + VM_STANDARD: 40, +}; + +/** + */ +const VM_StandardFilters = { + VMSF_NONE: 0, + VMSF_E8: 1, + VMSF_E8E9: 2, + VMSF_ITANIUM: 3, + VMSF_RGB: 4, + VMSF_AUDIO: 5, + VMSF_DELTA: 6, + VMSF_UPCASE: 7, +}; + +/** + */ +const VM_Flags = { + VM_FC: 1, + VM_FZ: 2, + VM_FS: 0x80000000, +}; + +/** + */ +const VM_OpType = { + VM_OPREG: 0, + VM_OPINT: 1, + VM_OPREGMEM: 2, + VM_OPNONE: 3, +}; + +/** + * Finds the key that maps to a given value in an object. This function is useful in debugging + * variables that use the above enums. + * @param {Object} obj + * @param {number} val + * @return {string} The key/enum value as a string. + */ +function findKeyForValue(obj, val) { + for (let key in obj) { + if (obj[key] === val) { + return key; + } + } + return null; +} + +function getDebugString(obj, val) { + let s = 'Unknown.'; + if (obj === VM_Commands) { + s = 'VM_Commands.'; + } else if (obj === VM_StandardFilters) { + s = 'VM_StandardFilters.'; + } else if (obj === VM_Flags) { + s = 'VM_OpType.'; + } else if (obj === VM_OpType) { + s = 'VM_OpType.'; + } + + return s + findKeyForValue(obj, val); +} + +/** + */ +class VM_PreparedOperand { + constructor() { + /** @type {VM_OpType} */ + this.Type; + + /** @type {number} */ + this.Data = 0; + + /** @type {number} */ + this.Base = 0; + + // TODO: In C++ this is a uint* + /** @type {Array} */ + this.Addr = null; + }; + + /** @return {string} */ + toString() { + if (this.Type === null) { + return 'Error: Type was null in VM_PreparedOperand'; + } + return '{ ' + + 'Type: ' + getDebugString(VM_OpType, this.Type) + + ', Data: ' + this.Data + + ', Base: ' + this.Base + + ' }'; + } +} + +/** + */ +class VM_PreparedCommand { + constructor() { + /** @type {VM_Commands} */ + this.OpCode; + + /** @type {boolean} */ + this.ByteMode = false; + + /** @type {VM_PreparedOperand} */ + this.Op1 = new VM_PreparedOperand(); + + /** @type {VM_PreparedOperand} */ + this.Op2 = new VM_PreparedOperand(); + } + + /** @return {string} */ + toString(indent) { + if (this.OpCode === null) { + return 'Error: OpCode was null in VM_PreparedCommand'; + } + indent = indent || ''; + return indent + '{\n' + + indent + ' OpCode: ' + getDebugString(VM_Commands, this.OpCode) + ',\n' + + indent + ' ByteMode: ' + this.ByteMode + ',\n' + + indent + ' Op1: ' + this.Op1.toString() + ',\n' + + indent + ' Op2: ' + this.Op2.toString() + ',\n' + + indent + '}'; + } +} + +/** + */ +class VM_PreparedProgram { + constructor() { + /** @type {Array} */ + this.Cmd = []; + + /** @type {Array} */ + this.AltCmd = null; + + /** @type {Uint8Array} */ + this.GlobalData = new Uint8Array(); + + /** @type {Uint8Array} */ + this.StaticData = new Uint8Array(); // static data contained in DB operators + + /** @type {Uint32Array} */ + this.InitR = new Uint32Array(7); + + /** + * A pointer to bytes that have been filtered by a program. + * @type {Uint8Array} + */ + this.FilteredData = null; + } + + /** @return {string} */ + toString() { + let s = '{\n Cmd: [\n'; + for (let i = 0; i < this.Cmd.length; ++i) { + s += this.Cmd[i].toString(' ') + ',\n'; + } + s += '],\n'; + // TODO: Dump GlobalData, StaticData, InitR? + s += ' }\n'; + return s; + } +} + +/** + */ +class UnpackFilter { + constructor() { + /** @type {number} */ + this.BlockStart = 0; + + /** @type {number} */ + this.BlockLength = 0; + + /** @type {number} */ + this.ExecCount = 0; + + /** @type {boolean} */ + this.NextWindow = false; + + // position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + /** @type {number} */ + this.ParentFilter = null; + + /** @type {VM_PreparedProgram} */ + this.Prg = new VM_PreparedProgram(); + } +} + +const VMCF_OP0 = 0; +const VMCF_OP1 = 1; +const VMCF_OP2 = 2; +const VMCF_OPMASK = 3; +const VMCF_BYTEMODE = 4; +const VMCF_JUMP = 8; +const VMCF_PROC = 16; +const VMCF_USEFLAGS = 32; +const VMCF_CHFLAGS = 64; + +const VM_CmdFlags = [ + /* VM_MOV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_CMP */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_ADD */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SUB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_INC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_DEC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JMP */ VMCF_OP1 | VMCF_JUMP , + /* VM_XOR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_AND */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_OR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_TEST */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JB */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JBE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JA */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JAE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_PUSH */ VMCF_OP1 , + /* VM_POP */ VMCF_OP1 , + /* VM_CALL */ VMCF_OP1 | VMCF_PROC , + /* VM_RET */ VMCF_OP0 | VMCF_PROC , + /* VM_NOT */ VMCF_OP1 | VMCF_BYTEMODE , + /* VM_SHL */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SHR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SAR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_NEG */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_PUSHA */ VMCF_OP0 , + /* VM_POPA */ VMCF_OP0 , + /* VM_PUSHF */ VMCF_OP0 | VMCF_USEFLAGS , + /* VM_POPF */ VMCF_OP0 | VMCF_CHFLAGS , + /* VM_MOVZX */ VMCF_OP2 , + /* VM_MOVSX */ VMCF_OP2 , + /* VM_XCHG */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_MUL */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_DIV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_ADC */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_SBB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_PRINT */ VMCF_OP0 , +]; + + +/** + */ +class StandardFilterSignature { + /** + * @param {number} length + * @param {number} crc + * @param {VM_StandardFilters} type + */ + constructor(length, crc, type) { + /** @type {number} */ + this.Length = length; + + /** @type {number} */ + this.CRC = crc; + + /** @type {VM_StandardFilters} */ + this.Type = type; + } +} + +/** + * @type {Array} + */ +const StdList = [ + new StandardFilterSignature(53, 0xad576887, VM_StandardFilters.VMSF_E8), + new StandardFilterSignature(57, 0x3cd7e57e, VM_StandardFilters.VMSF_E8E9), + new StandardFilterSignature(120, 0x3769893f, VM_StandardFilters.VMSF_ITANIUM), + new StandardFilterSignature(29, 0x0e06077d, VM_StandardFilters.VMSF_DELTA), + new StandardFilterSignature(149, 0x1c2c5dc8, VM_StandardFilters.VMSF_RGB), + new StandardFilterSignature(216, 0xbc85e701, VM_StandardFilters.VMSF_AUDIO), + new StandardFilterSignature(40, 0x46b9c560, VM_StandardFilters.VMSF_UPCASE), +]; + +/** + * @constructor + */ +class RarVM { + constructor() { + /** @private {Uint8Array} */ + this.mem_ = null; + + /** @private {Uint32Array} */ + this.R_ = new Uint32Array(8); + + /** @private {number} */ + this.flags_ = 0; + } + + /** + * Initializes the memory of the VM. + */ + init() { + if (!this.mem_) { + this.mem_ = new Uint8Array(VM_MEMSIZE); + } + } + + /** + * @param {Uint8Array} code + * @return {VM_StandardFilters} + */ + isStandardFilter(code) { + const codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0; + for (let i = 0; i < StdList.length; ++i) { + if (StdList[i].CRC == codeCRC && StdList[i].Length == code.length) + return StdList[i].Type; + } + + return VM_StandardFilters.VMSF_NONE; + } + + /** + * @param {VM_PreparedOperand} op + * @param {boolean} byteMode + * @param {bitjs.io.BitStream} bstream A rtl bit stream. + */ + decodeArg(op, byteMode, bstream) { + const data = bstream.peekBits(16); + if (data & 0x8000) { + op.Type = VM_OpType.VM_OPREG; // Operand is register (R[0]..R[7]) + bstream.readBits(1); // 1 flag bit and... + op.Data = bstream.readBits(3); // ... 3 register number bits + op.Addr = [this.R_[op.Data]] // TODO &R[Op.Data] // Register address + } else { + if ((data & 0xc000) == 0) { + op.Type = VM_OpType.VM_OPINT; // Operand is integer + bstream.readBits(2); // 2 flag bits + if (byteMode) { + op.Data = bstream.readBits(8); // Byte integer. + } else { + op.Data = RarVM.readData(bstream); // 32 bit integer. + } + } else { + // Operand is data addressed by register data, base address or both. + op.Type = VM_OpType.VM_OPREGMEM; + if ((data & 0x2000) == 0) { + bstream.readBits(3); // 3 flag bits + // Base address is zero, just use the address from register. + op.Data = bstream.readBits(3); // (Data>>10)&7 + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + op.Base = 0; + } else { + bstream.readBits(4); // 4 flag bits + if ((data & 0x1000) == 0) { + // Use both register and base address. + op.Data = bstream.readBits(3); + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + } else { + // Use base address only. Access memory by fixed address. + op.Data = 0; + } + op.Base = RarVM.readData(bstream); // Read base address. + } + } + } + } + + /** + * @param {VM_PreparedProgram} prg + */ + execute(prg) { + this.R_.set(prg.InitR); + + const globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE); + if (globalSize) { + this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR); + } + + const staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize); + if (staticSize) { + this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize); + } + + this.R_[7] = VM_MEMSIZE; + this.flags_ = 0; + + const preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd; + if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) { + // Invalid VM program. Let's replace it with 'return' command. + preparedCode.OpCode = VM_Commands.VM_RET; + } + + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + let newBlockPos = dataView.getUint32(0x20, true /* little endian */) & VM_MEMMASK; + const newBlockSize = dataView.getUint32(0x1c, true /* little endian */) & VM_MEMMASK; + if (newBlockPos + newBlockSize >= VM_MEMSIZE) { + newBlockPos = newBlockSize = 0; + } + prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize); + + prg.GlobalData = new Uint8Array(0); + + const dataSize = Math.min(dataView.getUint32(0x30), (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)); + if (dataSize != 0) { + const len = dataSize + VM_FIXEDGLOBALSIZE; + prg.GlobalData = new Uint8Array(len); + prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len)); + } + } + + /** + * @param {Array} preparedCodes + * @return {boolean} + */ + executeCode(preparedCodes) { + let codeIndex = 0; + let cmd = preparedCodes[codeIndex]; + // TODO: Why is this an infinite loop instead of just returning + // when a VM_RET is hit? + while (1) { + switch (cmd.OpCode) { + case VM_Commands.VM_RET: + if (this.R_[7] >= VM_MEMSIZE) { + return true; + } + //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); + this.R_[7] += 4; + continue; + + case VM_Commands.VM_STANDARD: + this.executeStandardFilter(cmd.Op1.Data); + break; + + default: + console.error('RarVM OpCode not supported: ' + getDebugString(VM_Commands, cmd.OpCode)); + break; + } // switch (cmd.OpCode) + codeIndex++; + cmd = preparedCodes[codeIndex]; + } + } + + /** + * @param {number} filterType + */ + executeStandardFilter(filterType) { + switch (filterType) { + case VM_StandardFilters.VMSF_RGB: { + const dataSize = this.R_[4]; + const width = this.R_[0] - 3; + const posR = this.R_[1]; + const Channels = 3; + let srcOffset = 0; + let destOffset = dataSize; + + // byte *SrcData=Mem,*DestData=SrcData+DataSize; + // SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR /* offset */); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= (VM_GLOBALMEMADDR / 2) || posR < 0) { + break; + } + + for (let curChannel = 0; curChannel < Channels; ++curChannel) { + let prevByte=0; + + for (let i = curChannel; i < dataSize; i += Channels) { + let predicted; + const upperPos = i - width; + if (upperPos >= 3) { + const upperByte = this.mem_[destOffset + upperPos]; + const upperLeftByte = this.mem_[destOffset + upperPos - 3]; + predicted = prevByte + upperByte - upperLeftByte; + + const pa = Math.abs(predicted - prevByte); + const pb = Math.abs(predicted - upperByte); + const pc = Math.abs(predicted - upperLeftByte); + if (pa <= pb && pa <= pc) { + predicted = prevByte; + } else if (pb <= pc) { + predicted = upperByte; + } else { + predicted = upperLeftByte; + } + } else { + predicted = prevByte; + } + //DestData[I]=PrevByte=(byte)(Predicted-*(SrcData++)); + prevByte = (predicted - this.mem_[srcOffset++]) & 0xff; + this.mem_[destOffset + i] = prevByte; + } + } + for (let i = posR, border = dataSize - 2; i < border; i += 3) { + const g = this.mem_[destOffset + i + 1]; + this.mem_[destOffset + i] += g; + this.mem_[destOffset + i + 2] += g; + } + + break; + } + + // The C++ version of this standard filter uses an odd mixture of + // signed and unsigned integers, bytes and various casts. Careful! + case VM_StandardFilters.VMSF_AUDIO: { + const dataSize = this.R_[4]; + const channels = this.R_[0]; + let srcOffset = 0; + let destOffset = dataSize; + + //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= VM_GLOBALMEMADDR / 2) { + break; + } + + for (let curChannel = 0; curChannel < channels; ++curChannel) { + let prevByte = 0; // uint + let prevDelta = 0; // uint + let dif = [0, 0, 0, 0, 0, 0, 0]; + let d1 = 0, d2 = 0, d3; // ints + let k1 = 0, k2 = 0, k3 = 0; // ints + + for (var i = curChannel, byteCount = 0; + i < dataSize; + i += channels, ++byteCount) { + d3 = d2; + d2 = fromUnsigned32ToSigned32(prevDelta - d1); + d1 = fromUnsigned32ToSigned32(prevDelta); + + let predicted = fromSigned32ToUnsigned32(8*prevByte + k1*d1 + k2*d2 + k3*d3); // uint + predicted = (predicted >>> 3) & 0xff; + + let curByte = this.mem_[srcOffset++]; // uint + + // Predicted-=CurByte; + predicted = fromSigned32ToUnsigned32(predicted - curByte); + this.mem_[destOffset + i] = (predicted & 0xff); + + // PrevDelta=(signed char)(Predicted-PrevByte); + // where Predicted, PrevByte, PrevDelta are all unsigned int (32) + // casting this subtraction to a (signed char) is kind of invalid + // but it does the following: + // - do the subtraction + // - get the bottom 8 bits of the result + // - if it was >= 0x80, then the value is negative (subtract 0x100) + // - if the value is now negative, add 0x100000000 to make unsigned + // + // Example: + // predicted = 101 + // prevByte = 4294967158 + // (predicted - prevByte) = -4294967057 + // take lower 8 bits: 1110 1111 = 239 + // since > 127, subtract 256 = -17 + // since < 0, add 0x100000000 = 4294967279 + prevDelta = fromSigned32ToUnsigned32( + fromUnsigned8ToSigned8((predicted - prevByte) & 0xff)); + prevByte = predicted; + + // int D=((signed char)CurByte)<<3; + let curByteAsSignedChar = fromUnsigned8ToSigned8(curByte); // signed char + let d = (curByteAsSignedChar << 3); + + dif[0] += Math.abs(d); + dif[1] += Math.abs(d-d1); + dif[2] += Math.abs(d+d1); + dif[3] += Math.abs(d-d2); + dif[4] += Math.abs(d+d2); + dif[5] += Math.abs(d-d3); + dif[6] += Math.abs(d+d3); + + if ((byteCount & 0x1f) == 0) { + let minDif = dif[0], numMinDif = 0; + dif[0] = 0; + for (let j = 1; j < 7; ++j) { + if (dif[j] < minDif) { + minDif = dif[j]; + numMinDif = j; + } + dif[j] = 0; + } + switch (numMinDif) { + case 1: if (k1>=-16) k1--; break; + case 2: if (k1 < 16) k1++; break; + case 3: if (k2>=-16) k2--; break; + case 4: if (k2 < 16) k2++; break; + case 5: if (k3>=-16) k3--; break; + case 6: if (k3 < 16) k3++; break; + } + } + } + } + + break; + } + + case VM_StandardFilters.VMSF_DELTA: { + const dataSize = this.R_[4]; + const channels = this.R_[0]; + let srcPos = 0; + const border = dataSize * 2; + + //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + dataView.setUint32(0x20 /* byte offset */, + dataSize /* value */, + true /* little endian */); + + if (dataSize >= VM_GLOBALMEMADDR / 2) { + break; + } + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (let curChannel = 0; curChannel < channels; ++curChannel) { + let prevByte = 0; + for (let destPos = dataSize + curChannel; destPos < border; destPos += channels) { + prevByte = (prevByte - this.mem_[srcPos++]) & 0xff; + this.mem_[destPos] = prevByte; + } + } + + break; + } + + default: + console.error('RarVM Standard Filter not supported: ' + getDebugString(VM_StandardFilters, filterType)); + break; + } + } + + /** + * @param {Uint8Array} code + * @param {VM_PreparedProgram} prg + */ + prepare(code, prg) { + let codeSize = code.length; + + //InitBitInput(); + //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); + const bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */); + + // Calculate the single byte XOR checksum to check validity of VM code. + let xorSum = 0; + for (let i = 1; i < codeSize; ++i) { + xorSum ^= code[i]; + } + + bstream.readBits(8); + + prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp. + + // VM code is valid if equal. + if (xorSum == code[0]) { + const filterType = this.isStandardFilter(code); + if (filterType != VM_StandardFilters.VMSF_NONE) { + // VM code is found among standard filters. + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); + + curCmd.OpCode = VM_Commands.VM_STANDARD; + curCmd.Op1.Data = filterType; + // TODO: Addr=&CurCmd->Op1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + codeSize = 0; + } + + const dataFlag = bstream.readBits(1); + + // Read static data contained in DB operators. This data cannot be + // changed, it is a part of VM code, not a filter parameter. + + if (dataFlag & 0x8000) { + const dataSize = RarVM.readData(bstream) + 1; + // TODO: This accesses the byte pointer of the bstream directly. Is that ok? + for (let i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) { + // Append a byte to the program's static data. + const newStaticData = new Uint8Array(prg.StaticData.length + 1); + newStaticData.set(prg.StaticData); + newStaticData[newStaticData.length - 1] = bstream.readBits(8); + prg.StaticData = newStaticData; + } + } + + while (bstream.bytePtr < codeSize) { + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); // Prg->Cmd.Add(1) + const flag = bstream.peekBits(1); + if (!flag) { // (Data&0x8000)==0 + curCmd.OpCode = bstream.readBits(4); + } else { + curCmd.OpCode = (bstream.readBits(6) - 24); + } + + if (VM_CmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) { + curCmd.ByteMode = (bstream.readBits(1) != 0); + } else { + curCmd.ByteMode = 0; + } + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + const opNum = (VM_CmdFlags[curCmd.OpCode] & VMCF_OPMASK); + curCmd.Op1.Addr = null; + curCmd.Op2.Addr = null; + if (opNum > 0) { + this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand + if (opNum == 2) { + this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand + } else { + if (curCmd.Op1.Type == VM_OpType.VM_OPINT && (VM_CmdFlags[curCmd.OpCode] & (VMCF_JUMP|VMCF_PROC))) { + // Calculating jump distance. + let distance = curCmd.Op1.Data; + if (distance >= 256) { + distance -= 256; + } else { + if (distance >= 136) { + distance -= 264; + } else { + if (distance >= 16) { + distance -= 8; + } else { + if (distance >= 8) { + distance -= 16; + } + } + } + distance += prg.Cmd.length; + } + curCmd.Op1.Data = distance; + } + } + } // if (OpNum>0) + } // while ((uint)InAddrOp1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [curCmd.Op2.Data]; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + + // If operand 'Addr' field has not been set by DecodeArg calls above, + // let's set it to point to operand 'Data' field. It is necessary for + // VM_OPINT type operands (usual integers) or maybe if something was + // not set properly for other operands. 'Addr' field is required + // for quicker addressing of operand data. + for (let i = 0; i < prg.Cmd.length; ++i) { + const cmd = prg.Cmd[i]; + if (cmd.Op1.Addr == null) { + cmd.Op1.Addr = [cmd.Op1.Data]; + } + if (cmd.Op2.Addr == null) { + cmd.Op2.Addr = [cmd.Op2.Data]; + } + } + + /* + #ifdef VM_OPTIMIZE + if (CodeSize!=0) + Optimize(Prg); + #endif + */ + } + + /** + * @param {Uint8Array} arr The byte array to set a value in. + * @param {number} value The unsigned 32-bit value to set. + * @param {number} offset Offset into arr to start setting the value, defaults to 0. + */ + setLowEndianValue(arr, value, offset) { + const i = offset || 0; + arr[i] = value & 0xff; + arr[i + 1] = (value >>> 8) & 0xff; + arr[i + 2] = (value >>> 16) & 0xff; + arr[i + 3] = (value >>> 24) & 0xff; + } + + /** + * Sets a number of bytes of the VM memory at the given position from a + * source buffer of bytes. + * @param {number} pos The position in the VM memory to start writing to. + * @param {Uint8Array} buffer The source buffer of bytes. + * @param {number} dataSize The number of bytes to set. + */ + setMemory(pos, buffer, dataSize) { + if (pos < VM_MEMSIZE) { + const numBytes = Math.min(dataSize, VM_MEMSIZE - pos); + for (let i = 0; i < numBytes; ++i) { + this.mem_[pos + i] = buffer[i]; + } + } + } + + /** + * Static function that reads in the next set of bits for the VM + * (might return 4, 8, 16 or 32 bits). + * @param {bitjs.io.BitStream} bstream A RTL bit stream. + * @return {number} The value of the bits read. + */ + static readData(bstream) { + // Read in the first 2 bits. + const flags = bstream.readBits(2); + switch (flags) { // Data&0xc000 + // Return the next 4 bits. + case 0: + return bstream.readBits(4); // (Data>>10)&0xf + + case 1: // 0x4000 + // 0x3c00 => 0011 1100 0000 0000 + if (bstream.peekBits(4) == 0) { // (Data&0x3c00)==0 + // Skip the 4 zero bits. + bstream.readBits(4); + // Read in the next 8 and pad with 1s to 32 bits. + return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff) + } + + // Else, read in the next 8. + return bstream.readBits(8); + + // Read in the next 16. + case 2: // 0x8000 + const val = bstream.getBits(); + bstream.readBits(16); + return val; //bstream.readBits(16); + + // case 3 + default: + return (bstream.readBits(16) << 16) | bstream.readBits(16); + } + } +} + +// ============================================================================================== // + + +// Progress variables. +let currentFilename = ""; +let currentFileNumber = 0; +let currentBytesUnarchivedInFile = 0; +let currentBytesUnarchived = 0; +let totalUncompressedBytesInArchive = 0; +let totalFilesInArchive = 0; + +// Helper functions. +const info = function(str) { + postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); +}; +const err = function(str) { + postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); +}; +const postProgress = function() { + postMessage(new bitjs.archive.UnarchiveProgressEvent( + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive)); +}; + +// shows a byte value as its hex representation +const nibble = "0123456789ABCDEF"; +const byteValueToHexString = function(num) { + return nibble[num>>4] + nibble[num&0xF]; +}; +const twoByteValueToHexString = function(num) { + return nibble[(num>>12)&0xF] + nibble[(num>>8)&0xF] + nibble[(num>>4)&0xF] + nibble[num&0xF]; +}; + + +// Volume Types +const MARK_HEAD = 0x72; +const MAIN_HEAD = 0x73; +const FILE_HEAD = 0x74; +const COMM_HEAD = 0x75; +const AV_HEAD = 0x76; +const SUB_HEAD = 0x77; +const PROTECT_HEAD = 0x78; +const SIGN_HEAD = 0x79; +const NEWSUB_HEAD = 0x7a; +const ENDARC_HEAD = 0x7b; + +// ============================================================================================== // + +/** + */ +class RarVolumeHeader { + /** + * @param {bitjs.io.BitStream} bstream + */ + constructor(bstream) { + const headPos = bstream.bytePtr; + // byte 1,2 + info("Rar Volume Header @"+bstream.bytePtr); + + this.crc = bstream.readBits(16); + info(" crc=" + this.crc); + + // byte 3 + this.headType = bstream.readBits(8); + info(" headType=" + this.headType); + + // Get flags + // bytes 4,5 + this.flags = {}; + this.flags.value = bstream.peekBits(16); + + info(" flags=" + twoByteValueToHexString(this.flags.value)); + switch (this.headType) { + case MAIN_HEAD: + this.flags.MHD_VOLUME = !!bstream.readBits(1); + this.flags.MHD_COMMENT = !!bstream.readBits(1); + this.flags.MHD_LOCK = !!bstream.readBits(1); + this.flags.MHD_SOLID = !!bstream.readBits(1); + this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1); + this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT; + this.flags.MHD_AV = !!bstream.readBits(1); + this.flags.MHD_PROTECT = !!bstream.readBits(1); + this.flags.MHD_PASSWORD = !!bstream.readBits(1); + this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1); + this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1); + bstream.readBits(6); // unused + break; + case FILE_HEAD: + this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001 + this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002 + this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004 + this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008 + this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010 + bstream.readBits(3); // unused + this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100 + this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200 + this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400 + this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800 + this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000 + this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000 + bstream.readBits(2); // unused + info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE); + break; + default: + bstream.readBits(16); + } + + // byte 6,7 + this.headSize = bstream.readBits(16); + info(" headSize=" + this.headSize); + switch (this.headType) { + case MAIN_HEAD: + this.highPosAv = bstream.readBits(16); + this.posAv = bstream.readBits(32); + if (this.flags.MHD_ENCRYPTVER) { + this.encryptVer = bstream.readBits(8); + } + info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv); + break; + case FILE_HEAD: + this.packSize = bstream.readBits(32); + this.unpackedSize = bstream.readBits(32); + this.hostOS = bstream.readBits(8); + this.fileCRC = bstream.readBits(32); + this.fileTime = bstream.readBits(32); + this.unpVer = bstream.readBits(8); + this.method = bstream.readBits(8); + this.nameSize = bstream.readBits(16); + this.fileAttr = bstream.readBits(32); + + if (this.flags.LHD_LARGE) { + info("Warning: Reading in LHD_LARGE 64-bit size values"); + this.HighPackSize = bstream.readBits(32); + this.HighUnpSize = bstream.readBits(32); + } else { + this.HighPackSize = 0; + this.HighUnpSize = 0; + if (this.unpackedSize == 0xffffffff) { + this.HighUnpSize = 0x7fffffff + this.unpackedSize = 0xffffffff; + } + } + this.fullPackSize = 0; + this.fullUnpackSize = 0; + this.fullPackSize |= this.HighPackSize; + this.fullPackSize <<= 32; + this.fullPackSize |= this.packSize; + + // read in filename + + this.filename = bstream.readBytes(this.nameSize); + let _s = ''; + for (let _i = 0; _i < this.filename.length; _i++) { + _s += String.fromCharCode(this.filename[_i]); + } + + this.filename = _s; + + if (this.flags.LHD_SALT) { + info("Warning: Reading in 64-bit salt value"); + this.salt = bstream.readBits(64); // 8 bytes + } + + if (this.flags.LHD_EXTTIME) { + // 16-bit flags + const extTimeFlags = bstream.readBits(16); + + // this is adapted straight out of arcread.cpp, Archive::ReadHeader() + for (let I = 0; I < 4; ++I) { + const rmode = extTimeFlags >> ((3 - I) * 4); + if ((rmode & 8) == 0) { + continue; + } + if (I != 0) + bstream.readBits(16); + const count = (rmode & 3); + for (let J = 0; J < count; ++J) { + bstream.readBits(8); + } + } + } + + if (this.flags.LHD_COMMENT) { + info("Found a LHD_COMMENT"); + } + + while (headPos + this.headSize > bstream.bytePtr) { + bstream.readBits(1); + } + + info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename); + + break; + default: + info("Found a header of type 0x" + byteValueToHexString(this.headType)); + // skip the rest of the header bytes (for now) + bstream.readBytes(this.headSize - 7); + break; + } + } +} + +const BLOCK_LZ = 0; +const BLOCK_PPM = 1; + +const rLDecode = [0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224]; +const rLBits = [0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]; +const rDBitLengthCounts = [4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12]; +const rSDDecode = [0,4,8,16,32,64,128,192]; +const rSDBits = [2,2,3, 4, 5, 6, 6, 6]; + +const rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, + 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, + 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, + 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, + 655360, 720896, 786432, 851968, 917504, 983040]; + +const rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, + 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; + +const rLOW_DIST_REP_COUNT = 16; + +const rNC = 299; +const rDC = 60; +const rLDC = 17; +const rRC = 28; +const rBC = 20; +const rHUFF_TABLE_SIZE = (rNC+rDC+rRC+rLDC); + +const UnpOldTable = new Array(rHUFF_TABLE_SIZE); + +const BD = { //bitdecode + DecodeLen: new Array(16), + DecodePos: new Array(16), + DecodeNum: new Array(rBC) +}; +const LD = { //litdecode + DecodeLen: new Array(16), + DecodePos: new Array(16), + DecodeNum: new Array(rNC) +}; +const DD = { //distdecode + DecodeLen: new Array(16), + DecodePos: new Array(16), + DecodeNum: new Array(rDC) +}; +const LDD = { //low dist decode + DecodeLen: new Array(16), + DecodePos: new Array(16), + DecodeNum: new Array(rLDC) +}; +const RD = { //rep decode + DecodeLen: new Array(16), + DecodePos: new Array(16), + DecodeNum: new Array(rRC) +}; + +/** + * @type {Array} + */ +const rOldBuffers = []; + +/** + * The current buffer we are unpacking to. + * @type {bitjs.io.ByteBuffer} + */ +let rBuffer; + +/** + * The buffer of the final bytes after filtering (only used in Unpack29). + * @type {bitjs.io.ByteBuffer} + */ +let wBuffer; + + +/** + * In unpack.cpp, UnpPtr keeps track of what bytes have been unpacked + * into the Window buffer and WrPtr keeps track of what bytes have been + * actually written to disk after the unpacking and optional filtering + * has been done. + * + * In our case, rBuffer is the buffer for the unpacked bytes and wBuffer is + * the final output bytes. + */ + + +/** + * Read in Huffman tables for RAR + * @param {bitjs.io.BitStream} bstream + */ +function RarReadTables(bstream) { + const BitLength = new Array(rBC); + const Table = new Array(rHUFF_TABLE_SIZE); + + // before we start anything we need to get byte-aligned + bstream.readBits( (8 - bstream.bitPtr) & 0x7 ); + + if (bstream.readBits(1)) { + info("Error! PPM not implemented yet"); + return; + } + + if (!bstream.readBits(1)) { //discard old table + for (let i = UnpOldTable.length; i--;) { + UnpOldTable[i] = 0; + } + } + + // read in bit lengths + for (let I = 0; I < rBC; ++I) { + const Length = bstream.readBits(4); + if (Length == 15) { + let ZeroCount = bstream.readBits(4); + if (ZeroCount == 0) { + BitLength[I] = 15; + } + else { + ZeroCount += 2; + while (ZeroCount-- > 0 && I < rBC) + BitLength[I++] = 0; + --I; + } + } + else { + BitLength[I] = Length; + } + } + + // now all 20 bit lengths are obtained, we construct the Huffman Table: + + RarMakeDecodeTables(BitLength, 0, BD, rBC); + + const TableSize = rHUFF_TABLE_SIZE; + for (let i = 0; i < TableSize;) { + const num = RarDecodeNumber(bstream, BD); + if (num < 16) { + Table[i] = (num + UnpOldTable[i]) & 0xf; + i++; + } else if (num < 18) { + let N = (num == 16) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11); + + while (N-- > 0 && i < TableSize) { + Table[i] = Table[i - 1]; + i++; + } + } else { + let N = (num == 18) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11); + + while (N-- > 0 && i < TableSize) { + Table[i++] = 0; + } + } + } + + RarMakeDecodeTables(Table, 0, LD, rNC); + RarMakeDecodeTables(Table, rNC, DD, rDC); + RarMakeDecodeTables(Table, rNC + rDC, LDD, rLDC); + RarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC); + + for (let i = UnpOldTable.length; i--;) { + UnpOldTable[i] = Table[i]; + } + return true; +} + + +function RarDecodeNumber(bstream, dec) { + const DecodeLen = dec.DecodeLen; + const DecodePos = dec.DecodePos; + const DecodeNum = dec.DecodeNum; + const bitField = bstream.getBits() & 0xfffe; + //some sort of rolled out binary search + const bits = ((bitField < DecodeLen[8])? + ((bitField < DecodeLen[4])? + ((bitField < DecodeLen[2])? + ((bitField < DecodeLen[1])?1:2) + :((bitField < DecodeLen[3])?3:4)) + :(bitField < DecodeLen[6])? + ((bitField < DecodeLen[5])?5:6) + :((bitField < DecodeLen[7])?7:8)) + :((bitField < DecodeLen[12])? + ((bitField < DecodeLen[10])? + ((bitField < DecodeLen[9])?9:10) + :((bitField < DecodeLen[11])?11:12)) + :(bitField < DecodeLen[14])? + ((bitField < DecodeLen[13])?13:14) + :15)); + bstream.readBits(bits); + const N = DecodePos[bits] + ((bitField - DecodeLen[bits -1]) >>> (16 - bits)); + + return DecodeNum[N]; +} + + +function RarMakeDecodeTables(BitLength, offset, dec, size) { + const DecodeLen = dec.DecodeLen; + const DecodePos = dec.DecodePos; + const DecodeNum = dec.DecodeNum; + const LenCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + const TmpPos = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let N = 0; + let M = 0; + + for (let i = DecodeNum.length; i--;) { + DecodeNum[i] = 0; + } + for (let i = 0; i < size; i++) { + LenCount[BitLength[i + offset] & 0xF]++; + } + LenCount[0] = 0; + TmpPos[0] = 0; + DecodePos[0] = 0; + DecodeLen[0] = 0; + + for (let I = 1; I < 16; ++I) { + N = 2 * (N+LenCount[I]); + M = (N << (15-I)); + if (M > 0xFFFF) { + M = 0xFFFF; + } + DecodeLen[I] = M; + DecodePos[I] = DecodePos[I-1] + LenCount[I-1]; + TmpPos[I] = DecodePos[I]; + } + for (let I = 0; I < size; ++I) { + if (BitLength[I + offset] != 0) { + DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I; + } + } + +} + +// TODO: implement +/** + * @param {bitjs.io.BitStream} bstream + * @param {boolean} Solid + */ +function Unpack15(bstream, Solid) { + info("ERROR! RAR 1.5 compression not supported"); +} + +/** + * Unpacks the bit stream into rBuffer using the Unpack20 algorithm. + * @param {bitjs.io.BitStream} bstream + * @param {boolean} Solid + */ +function Unpack20(bstream, Solid) { + const destUnpSize = rBuffer.data.length; + let oldDistPtr = 0; + + if (!Solid) { + RarReadTables20(bstream); + } + while (destUnpSize > rBuffer.ptr) { + let num = RarDecodeNumber(bstream, LD); + if (num < 256) { + rBuffer.insertByte(num); + continue; + } + if (num > 269) { + let Length = rLDecode[num -= 270] + 3; + if ((Bits = rLBits[num]) > 0) { + Length += bstream.readBits(Bits); + } + let DistNumber = RarDecodeNumber(bstream, DD); + let Distance = rDDecode[DistNumber] + 1; + if ((Bits = rDBits[DistNumber]) > 0) { + Distance += bstream.readBits(Bits); + } + if (Distance >= 0x2000) { + Length++; + if (Distance >= 0x40000) { + Length++; + } + } + lastLength = Length; + lastDist = rOldDist[oldDistPtr++ & 3] = Distance; + RarCopyString(Length, Distance); + continue; + } + if (num == 269) { + RarReadTables20(bstream); + RarUpdateProgress(); + continue; + } + if (num == 256) { + lastDist = rOldDist[oldDistPtr++ & 3] = lastDist; + RarCopyString(lastLength, lastDist); + continue; + } + if (num < 261) { + const Distance = rOldDist[(oldDistPtr - (num - 256)) & 3]; + const LengthNumber = RarDecodeNumber(bstream, RD); + let Length = rLDecode[LengthNumber] +2; + if ((Bits = rLBits[LengthNumber]) > 0) { + Length += bstream.readBits(Bits); + } + if (Distance >= 0x101) { + Length++; + if (Distance >= 0x2000) { + Length++ + if (Distance >= 0x40000) { + Length++; + } + } + } + lastLength = Length; + lastDist = rOldDist[oldDistPtr++ & 3] = Distance; + RarCopyString(Length, Distance); + continue; + } + if (num < 270) { + let Distance = rSDDecode[num -= 261] + 1; + if ((Bits = rSDBits[num]) > 0) { + Distance += bstream.readBits(Bits); + } + lastLength = 2; + lastDist = rOldDist[oldDistPtr++ & 3] = Distance; + RarCopyString(2, Distance); + continue; + } + + } + RarUpdateProgress(); +} + +function RarUpdateProgress() { + const change = rBuffer.ptr - currentBytesUnarchivedInFile; + currentBytesUnarchivedInFile = rBuffer.ptr; + currentBytesUnarchived += change; + postProgress(); +} + +const rNC20 = 298; +const rDC20 = 48; +const rRC20 = 28; +const rBC20 = 19; +const rMC20 = 257; + +const UnpOldTable20 = new Array(rMC20 * 4); + +// TODO: This function should return a boolean value, see unpack20.cpp. +function RarReadTables20(bstream) { + const BitLength = new Array(rBC20); + const Table = new Array(rMC20 * 4); + let TableSize; + let N; + let I; + const AudioBlock = bstream.readBits(1); + if (!bstream.readBits(1)) { + for (let i = UnpOldTable20.length; i--;) { + UnpOldTable20[i] = 0; + } + } + TableSize = rNC20 + rDC20 + rRC20; + for (I = 0; I < rBC20; I++) { + BitLength[I] = bstream.readBits(4); + } + RarMakeDecodeTables(BitLength, 0, BD, rBC20); + I = 0; + while (I < TableSize) { + const num = RarDecodeNumber(bstream, BD); + if (num < 16) { + Table[I] = num + UnpOldTable20[I] & 0xf; + I++; + } else if (num == 16) { + N = bstream.readBits(2) + 3; + while (N-- > 0 && I < TableSize) { + Table[I] = Table[I - 1]; + I++; + } + } else { + if (num == 17) { + N = bstream.readBits(3) + 3; + } else { + N = bstream.readBits(7) + 11; + } + while (N-- > 0 && I < TableSize) { + Table[I++] = 0; + } + } + } + RarMakeDecodeTables(Table, 0, LD, rNC20); + RarMakeDecodeTables(Table, rNC20, DD, rDC20); + RarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20); + for (let i = UnpOldTable20.length; i--;) { + UnpOldTable20[i] = Table[i]; + } +} + +let lowDistRepCount = 0; +let prevLowDist = 0; + +let rOldDist = [0,0,0,0]; +let lastDist; +let lastLength; + +// ============================================================================================== // + +// Unpack code specific to RarVM +const VM = new RarVM(); + +/** + * Filters code, one entry per filter. + * @type {Array} + */ +let Filters = []; + +/** + * Filters stack, several entrances of same filter are possible. + * @type {Array} + */ +let PrgStack = []; + +/** + * Lengths of preceding blocks, one length per filter. Used to reduce + * size required to write block length if lengths are repeating. + * @type {Array} + */ +let OldFilterLengths = []; + +let LastFilter = 0; + +function InitFilters() { + OldFilterLengths = []; + LastFilter = 0; + Filters = []; + PrgStack = []; +} + + +/** + * @param {number} firstByte The first byte (flags). + * @param {Uint8Array} vmCode An array of bytes. + */ +function RarAddVMCode(firstByte, vmCode) { + VM.init(); + const bstream = new bitjs.io.BitStream(vmCode.buffer, true /* rtl */); + + let filtPos; + if (firstByte & 0x80) { + filtPos = RarVM.readData(bstream); + if (filtPos == 0) { + InitFilters(); + } else { + filtPos--; + } + } else { + filtPos = LastFilter; + } + + if (filtPos > Filters.length || filtPos > OldFilterLengths.length) { + return false; + } + + LastFilter = filtPos; + const newFilter = (filtPos == Filters.length); + + // new filter for PrgStack + const stackFilter = new UnpackFilter(); + let filter = null; + // new filter code, never used before since VM reset + if (newFilter) { + // too many different filters, corrupt archive + if (filtPos > 1024) { + return false; + } + + filter = new UnpackFilter(); + Filters.push(filter); + stackFilter.ParentFilter = (Filters.length - 1); + OldFilterLengths.push(0); // OldFilterLengths.Add(1) + filter.ExecCount = 0; + } else { // filter was used in the past + filter = Filters[filtPos]; + stackFilter.ParentFilter = filtPos; + filter.ExecCount++; + } + + let emptyCount = 0; + for (let i = 0; i < PrgStack.length; ++i) { + PrgStack[i - emptyCount] = PrgStack[i]; + + if (PrgStack[i] == null) { + emptyCount++; + } + if (emptyCount > 0) { + PrgStack[i] = null; + } + } + + if (emptyCount == 0) { + PrgStack.push(null); //PrgStack.Add(1); + emptyCount = 1; + } + + const stackPos = PrgStack.length - emptyCount; + PrgStack[stackPos] = stackFilter; + stackFilter.ExecCount = filter.ExecCount; + + let blockStart = RarVM.readData(bstream); + if (firstByte & 0x40) { + blockStart += 258; + } + stackFilter.BlockStart = (blockStart + rBuffer.ptr) & MAXWINMASK; + + if (firstByte & 0x20) { + stackFilter.BlockLength = RarVM.readData(bstream); + } else { + stackFilter.BlockLength = filtPos < OldFilterLengths.length + ? OldFilterLengths[filtPos] + : 0; + } + stackFilter.NextWindow = (wBuffer.ptr != rBuffer.ptr) && + (((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart); + + OldFilterLengths[filtPos] = stackFilter.BlockLength; + + for (let i = 0; i < 7; ++i) { + stackFilter.Prg.InitR[i] = 0; + } + stackFilter.Prg.InitR[3] = VM_GLOBALMEMADDR; + stackFilter.Prg.InitR[4] = stackFilter.BlockLength; + stackFilter.Prg.InitR[5] = stackFilter.ExecCount; + + // set registers to optional parameters if any + if (firstByte & 0x10) { + const initMask = bstream.readBits(7); + for (let i = 0; i < 7; ++i) { + if (initMask & (1 << i)) { + stackFilter.Prg.InitR[i] = RarVM.readData(bstream); + } + } + } + + if (newFilter) { + const vmCodeSize = RarVM.readData(bstream); + if (vmCodeSize >= 0x10000 || vmCodeSize == 0) { + return false; + } + const vmCode = new Uint8Array(vmCodeSize); + for (let i = 0; i < vmCodeSize; ++i) { + //if (Inp.Overflow(3)) + // return(false); + vmCode[i] = bstream.readBits(8); + } + VM.prepare(vmCode, filter.Prg); + } + stackFilter.Prg.Cmd = filter.Prg.Cmd; + stackFilter.Prg.AltCmd = filter.Prg.Cmd; + + const staticDataSize = filter.Prg.StaticData.length; + if (staticDataSize > 0 && staticDataSize < VM_GLOBALMEMSIZE) { + // read statically defined data contained in DB commands + for (let i = 0; i < staticDataSize; ++i) { + stackFilter.Prg.StaticData[i] = filter.Prg.StaticData[i]; + } + } + + if (stackFilter.Prg.GlobalData.length < VM_FIXEDGLOBALSIZE) { + stackFilter.Prg.GlobalData = new Uint8Array(VM_FIXEDGLOBALSIZE); + } + + const globalData = stackFilter.Prg.GlobalData; + for (let i = 0; i < 7; ++i) { + VM.setLowEndianValue(globalData, stackFilter.Prg.InitR[i], i * 4); + } + + VM.setLowEndianValue(globalData, stackFilter.BlockLength, 0x1c); + VM.setLowEndianValue(globalData, 0, 0x20); + VM.setLowEndianValue(globalData, stackFilter.ExecCount, 0x2c); + for (let i = 0; i < 16; ++i) { + globalData[0x30 + i] = 0; + } + + // put data block passed as parameter if any + if (firstByte & 8) { + //if (Inp.Overflow(3)) + // return(false); + const dataSize = RarVM.readData(bstream); + if (dataSize > (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)) { + return false; + } + + const curSize = stackFilter.Prg.GlobalData.length; + if (curSize < dataSize + VM_FIXEDGLOBALSIZE) { + // Resize global data and update the stackFilter and local variable. + const numBytesToAdd = dataSize + VM_FIXEDGLOBALSIZE - curSize; + const newGlobalData = new Uint8Array(globalData.length + numBytesToAdd); + newGlobalData.set(globalData); + + stackFilter.Prg.GlobalData = newGlobalData; + globalData = newGlobalData; + } + //byte *GlobalData=&StackFilter->Prg.GlobalData[VM_FIXEDGLOBALSIZE]; + for (let i = 0; i < dataSize; ++i) { + //if (Inp.Overflow(3)) + // return(false); + globalData[VM_FIXEDGLOBALSIZE + i] = bstream.readBits(8); + } + } + + return true; +} + + +/** + * @param {!bitjs.io.BitStream} bstream + */ +function RarReadVMCode(bstream) { + const firstByte = bstream.readBits(8); + let length = (firstByte & 7) + 1; + if (length == 7) { + length = bstream.readBits(8) + 7; + } else if (length == 8) { + length = bstream.readBits(16); + } + + // Read all bytes of VM code into an array. + const vmCode = new Uint8Array(length); + for (let i = 0; i < length; i++) { + // Do something here with checking readbuf. + vmCode[i] = bstream.readBits(8); + } + return RarAddVMCode(firstByte, vmCode); +} + +/** + * Unpacks the bit stream into rBuffer using the Unpack29 algorithm. + * @param {bitjs.io.BitStream} bstream + * @param {boolean} Solid + */ +function Unpack29(bstream, Solid) { + // lazy initialize rDDecode and rDBits + + const DDecode = new Array(rDC); + const DBits = new Array(rDC); + + let Dist = 0; + let BitLength = 0; + let Slot = 0; + + for (let I = 0; I < rDBitLengthCounts.length; I++,BitLength++) { + for (let J = 0; J < rDBitLengthCounts[I]; J++,Slot++,Dist+=(1<= 271) { + let Length = rLDecode[num -= 271] + 3; + if ((Bits = rLBits[num]) > 0) { + Length += bstream.readBits(Bits); + } + const DistNumber = RarDecodeNumber(bstream, DD); + let Distance = DDecode[DistNumber] + 1; + if ((Bits = DBits[DistNumber]) > 0) { + if (DistNumber > 9) { + if (Bits > 4) { + Distance += ((bstream.getBits() >>> (20 - Bits)) << 4); + bstream.readBits(Bits - 4); + //todo: check this + } + if (lowDistRepCount > 0) { + lowDistRepCount--; + Distance += prevLowDist; + } else { + const LowDist = RarDecodeNumber(bstream, LDD); + if (LowDist == 16) { + lowDistRepCount = rLOW_DIST_REP_COUNT - 1; + Distance += prevLowDist; + } else { + Distance += LowDist; + prevLowDist = LowDist; + } + } + } else { + Distance += bstream.readBits(Bits); + } + } + if (Distance >= 0x2000) { + Length++; + if (Distance >= 0x40000) { + Length++; + } + } + RarInsertOldDist(Distance); + RarInsertLastMatch(Length, Distance); + RarCopyString(Length, Distance); + continue; + } + if (num == 256) { + if (!RarReadEndOfBlock(bstream)) { + break; + } + continue; + } + if (num == 257) { + if (!RarReadVMCode(bstream)) { + break; + } + continue; + } + if (num == 258) { + if (lastLength != 0) { + RarCopyString(lastLength, lastDist); + } + continue; + } + if (num < 263) { + const DistNum = num - 259; + const Distance = rOldDist[DistNum]; + + for (let I = DistNum; I > 0; I--) { + rOldDist[I] = rOldDist[I-1]; + } + rOldDist[0] = Distance; + + const LengthNumber = RarDecodeNumber(bstream, RD); + let Length = rLDecode[LengthNumber] + 2; + if ((Bits = rLBits[LengthNumber]) > 0) { + Length += bstream.readBits(Bits); + } + RarInsertLastMatch(Length, Distance); + RarCopyString(Length, Distance); + continue; + } + if (num < 272) { + let Distance = rSDDecode[num -= 263] + 1; + if ((Bits = rSDBits[num]) > 0) { + Distance += bstream.readBits(Bits); + } + RarInsertOldDist(Distance); + RarInsertLastMatch(2, Distance); + RarCopyString(2, Distance); + continue; + } + } // while (true) + RarUpdateProgress(); + RarWriteBuf(); +} + +/** + * Does stuff to the current byte buffer (rBuffer) based on + * the filters loaded into the RarVM and writes out to wBuffer. + */ +function RarWriteBuf() { + let writeSize = (rBuffer.ptr & MAXWINMASK); + + for (let i = 0; i < PrgStack.length; ++i) { + const flt = PrgStack[i]; + if (flt == null) { + continue; + } + + if (flt.NextWindow) { + flt.NextWindow = false; + continue; + } + + const blockStart = flt.BlockStart; + const blockLength = flt.BlockLength; + + // WrittenBorder = wBuffer.ptr + if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize) { + if (wBuffer.ptr != blockStart) { + // Copy blockStart bytes from rBuffer into wBuffer. + RarWriteArea(wBuffer.ptr, blockStart); + writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK; + } + if (blockLength <= writeSize) { + const blockEnd = (blockStart + blockLength) & MAXWINMASK; + if (blockStart < blockEnd || blockEnd == 0) { + VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + blockLength), blockLength); + } else { + const firstPartLength = MAXWINSIZE - blockStart; + VM.setMemory(0, rBuffer.data.subarray(blockStart, blockStart + firstPartLength), firstPartLength); + VM.setMemory(firstPartLength, rBuffer.data, blockEnd); + } + + const parentPrg = Filters[flt.ParentFilter].Prg; + const prg = flt.Prg; + + if (parentPrg.GlobalData.length > VM_FIXEDGLOBALSIZE) { + // Copy global data from previous script execution if any. + prg.GlobalData = new Uint8Array(parentPrg.GlobalData); + } + + RarExecuteCode(prg); + + if (prg.GlobalData.length > VM_FIXEDGLOBALSIZE) { + // Save global data for next script execution. + const globalDataLen = prg.GlobalData.length; + if (parentPrg.GlobalData.length < globalDataLen) { + parentPrg.GlobalData = new Uint8Array(globalDataLen); + } + parentPrg.GlobalData.set( + this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), + VM_FIXEDGLOBALSIZE); + } else { + parentPrg.GlobalData = new Uint8Array(0); + } + + let filteredData = prg.FilteredData; + + PrgStack[i] = null; + while (i + 1 < PrgStack.length) { + const nextFilter = PrgStack[i + 1]; + if (nextFilter == null || nextFilter.BlockStart != blockStart || + nextFilter.BlockLength != filteredData.length || nextFilter.NextWindow) { + break; + } + + // Apply several filters to same data block. + + VM.setMemory(0, filteredData, filteredData.length); + + const innerParentPrg = Filters[nextFilter.ParentFilter].Prg; + const nextPrg = nextFilter.Prg; + + const globalDataLen = innerParentPrg.GlobalData.length; + if (globalDataLen > VM_FIXEDGLOBALSIZE) { + // Copy global data from previous script execution if any. + nextPrg.GlobalData = new Uint8Array(globalDataLen); + nextPrg.GlobalData.set(innerParentPrg.GlobalData.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), VM_FIXEDGLOBALSIZE); + } + + RarExecuteCode(nextPrg); + + if (nextPrg.GlobalData.length > VM_GLOBALMEMSIZE) { + // Save global data for next script execution. + const globalDataLen = nextPrg.GlobalData.length; + if (innerParentPrg.GlobalData.length < globalDataLen) { + innerParentPrg.GlobalData = new Uint8Array(globalDataLen); + } + innerParentPrg.GlobalData.set( + this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), + VM_FIXEDGLOBALSIZE); + } else { + innerParentPrg.GlobalData = new Uint8Array(0); + } + + filteredData = nextPrg.FilteredData; + i++; + PrgStack[i] = null; + } // while (i + 1 < PrgStack.length) + + for (let j = 0; j < filteredData.length; ++j) { + wBuffer.insertByte(filteredData[j]); + } + writeSize = (rBuffer.ptr - wBuffer.ptr) & MAXWINMASK; + } // if (blockLength <= writeSize) + else { + for (let j = i; j < PrgStack.length; ++j) { + const theFlt = PrgStack[j]; + if (theFlt != null && theFlt.NextWindow) { + theFlt.NextWindow = false; + } + } + return; + } + } // if (((blockStart - wBuffer.ptr) & MAXWINMASK) < writeSize) + } // for (let i = 0; i < PrgStack.length; ++i) + + // Write any remaining bytes from rBuffer to wBuffer; + RarWriteArea(wBuffer.ptr, rBuffer.ptr); + + // Now that the filtered buffer has been written, swap it back to rBuffer. + rBuffer = wBuffer; +} + +/** + * Copy bytes from rBuffer to wBuffer. + * @param {number} startPtr The starting point to copy from rBuffer. + * @param {number} endPtr The ending point to copy from rBuffer. + */ +function RarWriteArea(startPtr, endPtr) { + if (endPtr < startPtr) { + console.error('endPtr < startPtr, endPtr=' + endPtr + ', startPtr=' + startPtr); +// RarWriteData(startPtr, -(int)StartPtr & MAXWINMASK); +// RarWriteData(0, endPtr); + return; + } else if (startPtr < endPtr) { + RarWriteData(startPtr, endPtr - startPtr); + } +} + +/** + * Writes bytes into wBuffer from rBuffer. + * @param {number} offset The starting point to copy bytes from rBuffer. + * @param {number} numBytes The number of bytes to copy. + */ +function RarWriteData(offset, numBytes) { + if (wBuffer.ptr >= rBuffer.data.length) { + return; + } + const leftToWrite = rBuffer.data.length - wBuffer.ptr; + if (numBytes > leftToWrite) { + numBytes = leftToWrite; + } + for (let i = 0; i < numBytes; ++i) { + wBuffer.insertByte(rBuffer.data[offset + i]); + } +} + +/** + * @param {VM_PreparedProgram} prg + */ +function RarExecuteCode(prg) +{ + if (prg.GlobalData.length > 0) { + const writtenFileSize = wBuffer.ptr; + prg.InitR[6] = writtenFileSize; + VM.setLowEndianValue(prg.GlobalData, writtenFileSize, 0x24); + VM.setLowEndianValue(prg.GlobalData, (writtenFileSize >>> 32) >> 0, 0x28); + VM.execute(prg); + } +} + +function RarReadEndOfBlock(bstream) { + RarUpdateProgress(); + + let NewTable = false; + let NewFile = false; + if (bstream.readBits(1)) { + NewTable = true; + } else { + NewFile = true; + NewTable = !!bstream.readBits(1); + } + //tablesRead = !NewTable; + return !(NewFile || NewTable && !RarReadTables(bstream)); +} + +function RarInsertLastMatch(length, distance) { + lastDist = distance; + lastLength = length; +} + +function RarInsertOldDist(distance) { + rOldDist.splice(3,1); + rOldDist.splice(0,0,distance); +} + +/** + * Copies len bytes from distance bytes ago in the buffer to the end of the + * current byte buffer. + * @param {number} length How many bytes to copy. + * @param {number} distance How far back in the buffer from the current write + * pointer to start copying from. + */ +function RarCopyString(len, distance) { + let srcPtr = rBuffer.ptr - distance; + // If we need to go back to previous buffers, then seek back. + if (srcPtr < 0) { + let l = rOldBuffers.length; + while (srcPtr < 0) { + srcPtr = rOldBuffers[--l].data.length + srcPtr; + } + // TODO: lets hope that it never needs to read across buffer boundaries + while (len--) { + rBuffer.insertByte(rOldBuffers[l].data[srcPtr++]); + } + } + if (len > distance) { + while (len--) { + rBuffer.insertByte(rBuffer.data[srcPtr++]); + } + } else { + rBuffer.insertBytes(rBuffer.data.subarray(srcPtr, srcPtr + len)); + } +} + +/** + * @param {RarLocalFile} v + */ +function unpack(v) { + // TODO: implement what happens when unpVer is < 15 + const Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer; + const Solid = v.header.flags.LHD_SOLID; + const bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); + + rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); + + info("Unpacking " + v.filename + " RAR v" + Ver); + + switch (Ver) { + case 15: // rar 1.5 compression + Unpack15(bstream, Solid); + break; + case 20: // rar 2.x compression + case 26: // files larger than 2GB + Unpack20(bstream, Solid); + break; + case 29: // rar 3.x compression + case 36: // alternative hash + wBuffer = new bitjs.io.ByteBuffer(rBuffer.data.length); + Unpack29(bstream, Solid); + break; + } // switch(method) + + rOldBuffers.push(rBuffer); + // TODO: clear these old buffers when there's over 4MB of history + return rBuffer.data; +} + +/** + */ +class RarLocalFile { + /** + * @param {bitjs.io.BitStream} bstream + */ + constructor(bstream) { + this.header = new RarVolumeHeader(bstream); + this.filename = this.header.filename; + + if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { + this.isValid = false; + info("Error! RAR Volume did not include a FILE_HEAD header "); + } + else { + // read in the compressed data + this.fileData = null; + if (this.header.packSize > 0) { + this.fileData = bstream.readBytes(this.header.packSize); + this.isValid = true; + } + } + } + + unrar() { + if (!this.header.flags.LHD_SPLIT_BEFORE) { + // unstore file + if (this.header.method == 0x30) { + info("Unstore "+this.filename); + this.isValid = true; + + currentBytesUnarchivedInFile += this.fileData.length; + currentBytesUnarchived += this.fileData.length; + + // Create a new buffer and copy it over. + const len = this.header.packSize; + const newBuffer = new bitjs.io.ByteBuffer(len); + newBuffer.insertBytes(this.fileData); + this.fileData = newBuffer.data; + } else { + this.isValid = true; + this.fileData = unpack(this); + } + } + } +} + +const unrar = function(arrayBuffer) { + currentFilename = ""; + currentFileNumber = 0; + currentBytesUnarchivedInFile = 0; + currentBytesUnarchived = 0; + totalUncompressedBytesInArchive = 0; + totalFilesInArchive = 0; + + postMessage(new bitjs.archive.UnarchiveStartEvent()); + const bstream = new bitjs.io.BitStream(arrayBuffer, false /* rtl */); + + const header = new RarVolumeHeader(bstream); + if (header.crc == 0x6152 && + header.headType == 0x72 && + header.flags.value == 0x1A21 && + header.headSize == 7) { + info("Found RAR signature"); + + const mhead = new RarVolumeHeader(bstream); + if (mhead.headType != MAIN_HEAD) { + info("Error! RAR did not include a MAIN_HEAD header"); + } + else { + let localFiles = []; + let localFile = null; + do { + try { + localFile = new RarLocalFile(bstream); + info("RAR localFile isValid=" + localFile.isValid + ", volume packSize=" + localFile.header.packSize); + if (localFile && localFile.isValid && localFile.header.packSize > 0) { + totalUncompressedBytesInArchive += localFile.header.unpackedSize; + localFiles.push(localFile); + } else if (localFile.header.packSize == 0 && localFile.header.unpackedSize == 0) { + localFile.isValid = true; + } + } catch(err) { + break; + } + //info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length); + } while (localFile.isValid); + totalFilesInArchive = localFiles.length; + + // now we have all information but things are unpacked + localFiles = localFiles.sort((a,b) => a.filename.toLowerCase() > b.filename.toLowerCase() ? 1 : -1); + + info(localFiles.map(function(a){return a.filename}).join(', ')); + for (let i = 0; i < localFiles.length; ++i) { + const localfile = localFiles[i]; + + // update progress + currentFilename = localfile.header.filename; + currentBytesUnarchivedInFile = 0; + + // actually do the unzipping + localfile.unrar(); + + if (localfile.isValid) { + postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); + postProgress(); + } + } + + postProgress(); + } + } + else { + err("Invalid RAR file"); + } + postMessage(new bitjs.archive.UnarchiveFinishEvent()); +}; + +// event.data.file has the ArrayBuffer. +onmessage = function(event) { + const ab = event.data.file; + unrar(ab, true); +}; diff --git a/vendor/bitjs/archive/untar.js b/vendor/bitjs/archive/untar.js new file mode 100644 index 0000000..24cabf9 --- /dev/null +++ b/vendor/bitjs/archive/untar.js @@ -0,0 +1,168 @@ +/** + * untar.js + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * + * Reference Documentation: + * + * TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html + */ + +// This file expects to be invoked as a Worker (see onmessage below). +importScripts('../io/bytestream.js'); +importScripts('archive.js'); + +// Progress variables. +let currentFilename = ""; +let currentFileNumber = 0; +let currentBytesUnarchivedInFile = 0; +let currentBytesUnarchived = 0; +let totalUncompressedBytesInArchive = 0; +let totalFilesInArchive = 0; + +// Helper functions. +const info = function(str) { + postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); +}; +const err = function(str) { + postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); +}; +const postProgress = function() { + postMessage(new bitjs.archive.UnarchiveProgressEvent( + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive)); +}; + +// Removes all characters from the first zero-byte in the string onwards. +const readCleanString = function(bstr, numBytes) { + const str = bstr.readString(numBytes); + const zIndex = str.indexOf(String.fromCharCode(0)); + return zIndex != -1 ? str.substr(0, zIndex) : str; +}; + +class TarLocalFile { + // takes a ByteStream and parses out the local file information + constructor(bstream) { + this.isValid = false; + + // Read in the header block + this.name = readCleanString(bstream, 100); + this.mode = readCleanString(bstream, 8); + this.uid = readCleanString(bstream, 8); + this.gid = readCleanString(bstream, 8); + this.size = parseInt(readCleanString(bstream, 12), 8); + this.mtime = readCleanString(bstream, 12); + this.chksum = readCleanString(bstream, 8); + this.typeflag = readCleanString(bstream, 1); + this.linkname = readCleanString(bstream, 100); + this.maybeMagic = readCleanString(bstream, 6); + + if (this.maybeMagic == "ustar") { + this.version = readCleanString(bstream, 2); + this.uname = readCleanString(bstream, 32); + this.gname = readCleanString(bstream, 32); + this.devmajor = readCleanString(bstream, 8); + this.devminor = readCleanString(bstream, 8); + this.prefix = readCleanString(bstream, 155); + + if (this.prefix.length) { + this.name = this.prefix + this.name; + } + bstream.readBytes(12); // 512 - 500 + } else { + bstream.readBytes(255); // 512 - 257 + } + + // Done header, now rest of blocks are the file contents. + this.filename = this.name; + this.fileData = null; + + info("Untarring file '" + this.filename + "'"); + info(" size = " + this.size); + info(" typeflag = " + this.typeflag); + + // A regular file. + if (this.typeflag == 0) { + info(" This is a regular file."); + const sizeInBytes = parseInt(this.size); + this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes)); + if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) { + this.isValid = true; + } + + bstream.readBytes(this.size); + + // Round up to 512-byte blocks. + const remaining = 512 - bstream.ptr % 512; + if (remaining > 0 && remaining < 512) { + bstream.readBytes(remaining); + } + } else if (this.typeflag == 5) { + info(" This is a directory.") + } + } +} + +// Takes an ArrayBuffer of a tar file in +// returns null on error +// returns an array of DecompressedFile objects on success +const untar = function(arrayBuffer) { + currentFilename = ""; + currentFileNumber = 0; + currentBytesUnarchivedInFile = 0; + currentBytesUnarchived = 0; + totalUncompressedBytesInArchive = 0; + totalFilesInArchive = 0; + + postMessage(new bitjs.archive.UnarchiveStartEvent()); + const bstream = new bitjs.io.ByteStream(arrayBuffer); + const localFiles = []; + + // While we don't encounter an empty block, keep making TarLocalFiles. + while (bstream.peekNumber(4) != 0) { + const oneLocalFile = new TarLocalFile(bstream); + if (oneLocalFile && oneLocalFile.isValid) { + localFiles.push(oneLocalFile); + totalUncompressedBytesInArchive += oneLocalFile.size; + } + } + totalFilesInArchive = localFiles.length; + + // got all local files, now sort them + localFiles.sort((a,b) => a.filename > b.filename ? 1 : -1); + + // report # files and total length + if (localFiles.length > 0) { + postProgress(); + } + + // now do the shipping of each file + for (let i = 0; i < localFiles.length; ++i) { + const localfile = localFiles[i]; + info("Sending file '" + localfile.filename + "' up"); + + // update progress + currentFilename = localfile.filename; + currentFileNumber = i; + currentBytesUnarchivedInFile = localfile.size; + currentBytesUnarchived += localfile.size; + postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); + postProgress(); + } + + postProgress(); + + postMessage(new bitjs.archive.UnarchiveFinishEvent()); +}; + +// event.data.file has the ArrayBuffer. +onmessage = function(event) { + const ab = event.data.file; + untar(ab); +}; diff --git a/vendor/bitjs/archive/unzip.js b/vendor/bitjs/archive/unzip.js new file mode 100644 index 0000000..7d7234f --- /dev/null +++ b/vendor/bitjs/archive/unzip.js @@ -0,0 +1,1467 @@ +/** + * unzip.js + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + * + * Reference Documentation: + * + * ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT + * DEFLATE format: http://tools.ietf.org/html/rfc1951 + */ + +// This file expects to be invoked as a Worker (see onmessage below). +// ... +// ... but importScripts does not work when CSP2 nonce is active, so inline these instead... +//importScripts('../io/bitstream.js'); +// + +/* + * bitstream.js + * + * Provides readers for bitstreams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * This bit stream peeks and consumes bits out of a binary stream. + */ +bitjs.io.BitStream = class { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { + throw "Error! BitArray constructed with an invalid ArrayBuffer object"; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.bytePtr = 0; // tracks which byte we are on + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + const mask = (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + n -= numBitsLeftInThisByte; + } + else { + const mask = (bitjs.io.BitStream.BITMASK[n] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += n; + bitsIn += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + n -= numBitsLeftInThisByte; + } + else { + result <<= n; + result |= ((bytes[bytePtr] & (bitjs.io.BitStream.BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); + + bitPtr += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr+1] & 0xff) << 8) + + ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); + } + + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const movePointers = opt_movePointers || false; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + + const result = this.bytes.subarray(bytePtr, bytePtr + n); + + if (movePointers) { + this.bytePtr += n; + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } +} + +// mask for getting N number of bits (0-8) +bitjs.io.BitStream.BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; + +//importScripts('../io/bytebuffer.js'); +// + +/* + * bytestream.js + * + * Provides a writer for bytes. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. + */ +bitjs.io.ByteBuffer = class { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; + } + this.data = new Uint8Array(numBytes); + this.ptr = 0; + } + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; +} +//importScripts('../io/bytestream.js'); +// + +/* + * bytestream.js + * + * Provides readers for byte streams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * This object allows you to peek and consume bytes as numbers and strings + * out of an ArrayBuffer. In this buffer, everything must be byte-aligned. + */ +bitjs.io.ByteStream = class { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this BitStream + */ + constructor(ab, opt_offset, opt_length) { + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.ptr = 0; + } + + + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer + * TODO: This apparently cannot read more than 4 bytes as a number? + * @param {number} n The number of bytes to peek at. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + // TODO: return error if n would go past the end of the stream? + if (n <= 0 || typeof n != typeof 1) { + return -1; + } + + let result = 0; + // read from last byte to first byte and roll them in + let curByte = this.ptr + n - 1; + while (curByte >= this.ptr) { + result <<= 8; + result |= this.bytes[curByte]; + --curByte; + } + return result; + } + + + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber( n ); + this.ptr += n; + return num; + } + + + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; + + if (num >= HALF) num -= FULL; + + return num; + } + + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.ptr += n; + return num; + } + + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return null; + } + + const result = this.bytes.subarray(this.ptr, this.ptr + n); + + if (movePointers) { + this.ptr += n; + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as a string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + if (n <= 0 || typeof n != typeof 1) { + return ""; + } + + let result = ""; + for (let p = this.ptr, end = this.ptr + n; p < end; ++p) { + result += String.fromCharCode(this.bytes[p]); + } + return result; + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.ptr += n; + return strToReturn; + } +} +//importScripts('archive.js'); +// + +/** + * archive.js + * + * Provides base functionality for unarchiving. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + */ + +var bitjs = bitjs || {}; +bitjs.archive = bitjs.archive || {}; + +/** + * An unarchive event. + */ +bitjs.archive.UnarchiveEvent = class { + /** + * @param {string} type The event type. + */ + constructor(type) { + /** + * The event type. + * @type {string} + */ + this.type = type; + } +} + +/** + * The UnarchiveEvent types. + */ +bitjs.archive.UnarchiveEvent.Type = { + START: 'start', + PROGRESS: 'progress', + EXTRACT: 'extract', + FINISH: 'finish', + INFO: 'info', + ERROR: 'error' +}; + +/** + * Useful for passing info up to the client (for debugging). + */ +bitjs.archive.UnarchiveInfoEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The info message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.INFO); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * An unrecoverable error has occured. + */ +bitjs.archive.UnarchiveErrorEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} msg The error message. + */ + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.ERROR); + + /** + * The information message. + * @type {string} + */ + this.msg = msg; + } +} + +/** + * Start event. + */ +bitjs.archive.UnarchiveStartEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.START); + } +} + +/** + * Finish event. + */ +bitjs.archive.UnarchiveFinishEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.FINISH); + } +} + +/** + * Progress event. + */ +bitjs.archive.UnarchiveProgressEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} currentFilename + * @param {number} currentFileNumber + * @param {number} currentBytesUnarchivedInFile + * @param {number} currentBytesUnarchived + * @param {number} totalUncompressedBytesInArchive + * @param {number} totalFilesInArchive + */ + constructor(currentFilename, currentFileNumber, currentBytesUnarchivedInFile, + currentBytesUnarchived, totalUncompressedBytesInArchive, totalFilesInArchive) { + super(bitjs.archive.UnarchiveEvent.Type.PROGRESS); + + this.currentFilename = currentFilename; + this.currentFileNumber = currentFileNumber; + this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; + this.totalFilesInArchive = totalFilesInArchive; + this.currentBytesUnarchived = currentBytesUnarchived; + this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; + } +} + +/** + * Extract event. + */ +bitjs.archive.UnarchiveExtractEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {UnarchivedFile} unarchivedFile + */ + constructor(unarchivedFile) { + super(bitjs.archive.UnarchiveEvent.Type.EXTRACT); + + /** + * @type {UnarchivedFile} + */ + this.unarchivedFile = unarchivedFile; + } +} + +/** + * All extracted files returned by an Unarchiver will implement + * the following interface: + * + * interface UnarchivedFile { + * string filename + * TypedArray fileData + * } + * + */ + +/** + * Base class for all Unarchivers. + */ +bitjs.archive.Unarchiver = class { + /** + * @param {ArrayBuffer} arrayBuffer The Array Buffer. + * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located. + */ + constructor(arrayBuffer, opt_pathToBitJS) { + /** + * The ArrayBuffer object. + * @type {ArrayBuffer} + * @protected + */ + this.ab = arrayBuffer; + + /** + * The path to the BitJS files. + * @type {string} + * @private + */ + this.pathToBitJS_ = opt_pathToBitJS || '/'; + + /** + * A map from event type to an array of listeners. + * @type {Map.} + */ + this.listeners_ = {}; + for (let type in bitjs.archive.UnarchiveEvent.Type) { + this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; + } + + /** + * Private web worker initialized during start(). + * @type {Worker} + * @private + */ + this.worker_ = null; + } + + /** + * This method must be overridden by the subclass to return the script filename. + * @return {string} The script filename. + * @protected. + */ + getScriptFileName() { + throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()'; + } + + /** + * Adds an event listener for UnarchiveEvents. + * + * @param {string} Event type. + * @param {function} An event handler function. + */ + addEventListener(type, listener) { + if (type in this.listeners_) { + if (this.listeners_[type].indexOf(listener) == -1) { + this.listeners_[type].push(listener); + } + } + } + + /** + * Removes an event listener. + * + * @param {string} Event type. + * @param {EventListener|function} An event listener or handler function. + */ + removeEventListener(type, listener) { + if (type in this.listeners_) { + const index = this.listeners_[type].indexOf(listener); + if (index != -1) { + this.listeners_[type].splice(index, 1); + } + } + } + + /** + * Receive an event and pass it to the listener functions. + * + * @param {bitjs.archive.UnarchiveEvent} e + * @private + */ + handleWorkerEvent_(e) { + if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && + this.listeners_[e.type] instanceof Array) { + this.listeners_[e.type].forEach(function (listener) { listener(e) }); + if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { + this.worker_.terminate(); + } + } else { + console.log(e); + } + } + + /** + * Starts the unarchive in a separate Web Worker thread and returns immediately. + */ + start() { + const me = this; + const scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); + if (scriptFileName) { + this.worker_ = new Worker(scriptFileName); + + this.worker_.onerror = function(e) { + console.log('Worker error: message = ' + e.message); + throw e; + }; + + this.worker_.onmessage = function(e) { + if (typeof e.data == 'string') { + // Just log any strings the workers pump our way. + console.log(e.data); + } else { + // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' + // so that instanceof UnarchiveEvent returns true, but others do not. + me.handleWorkerEvent_(e.data); + } + }; + + this.worker_.postMessage({file: this.ab}); + } + } + + /** + * Terminates the Web Worker for this Unarchiver and returns immediately. + */ + stop() { + if (this.worker_) { + this.worker_.terminate(); + } + } +} + + +/** + * Unzipper + */ +bitjs.archive.Unzipper = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unzip.js'; } +} + + +/** + * Unrarrer + */ +bitjs.archive.Unrarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unrar.js'; } +} + +/** + * Untarrer + * @extends {bitjs.archive.Unarchiver} + * @constructor + */ +bitjs.archive.Untarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/untar.js'; }; +} + +/** + * Factory method that creates an unarchiver based on the byte signature found + * in the arrayBuffer. + * @param {ArrayBuffer} ab + * @param {string=} opt_pathToBitJS Path to the unarchiver script files. + * @return {bitjs.archive.Unarchiver} + */ +bitjs.archive.GetUnarchiver = function(ab, opt_pathToBitJS) { + let unarchiver = null; + const pathToBitJS = opt_pathToBitJS || ''; + const h = new Uint8Array(ab, 0, 10); + + if (h[0] == 0x52 && h[1] == 0x61 && h[2] == 0x72 && h[3] == 0x21) { // Rar! + unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS); + } else if (h[0] == 0x50 && h[1] == 0x4B) { // PK (Zip) + unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS); + } else { // Try with tar + unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS); + } + return unarchiver; +}; + +// Progress variables. +let currentFilename = ""; +let currentFileNumber = 0; +let currentBytesUnarchivedInFile = 0; +let currentBytesUnarchived = 0; +let totalUncompressedBytesInArchive = 0; +let totalFilesInArchive = 0; + +// Helper functions. +const info = function(str) { + postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); +}; +const err = function(str) { + postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); +}; +const postProgress = function() { + postMessage(new bitjs.archive.UnarchiveProgressEvent( + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive)); +}; + +const zLocalFileHeaderSignature = 0x04034b50; +const zArchiveExtraDataSignature = 0x08064b50; +const zCentralFileHeaderSignature = 0x02014b50; +const zDigitalSignatureSignature = 0x05054b50; +const zEndOfCentralDirSignature = 0x06064b50; +const zEndOfCentralDirLocatorSignature = 0x07064b50; + +// mask for getting the Nth bit (zero-based) +const BIT = [ 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80, + 0x100, 0x200, 0x400, 0x800, + 0x1000, 0x2000, 0x4000, 0x8000]; + + +class ZipLocalFile { + // takes a ByteStream and parses out the local file information + constructor(bstream) { + if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) { + return null; + } + + bstream.readNumber(4); // swallow signature + this.version = bstream.readNumber(2); + this.generalPurpose = bstream.readNumber(2); + this.compressionMethod = bstream.readNumber(2); + this.lastModFileTime = bstream.readNumber(2); + this.lastModFileDate = bstream.readNumber(2); + this.crc32 = bstream.readNumber(4); + this.compressedSize = bstream.readNumber(4); + this.uncompressedSize = bstream.readNumber(4); + this.fileNameLength = bstream.readNumber(2); + this.extraFieldLength = bstream.readNumber(2); + + this.filename = null; + if (this.fileNameLength > 0) { + this.filename = bstream.readString(this.fileNameLength); + } + + info("Zip Local File Header:"); + info(" version=" + this.version); + info(" general purpose=" + this.generalPurpose); + info(" compression method=" + this.compressionMethod); + info(" last mod file time=" + this.lastModFileTime); + info(" last mod file date=" + this.lastModFileDate); + info(" crc32=" + this.crc32); + info(" compressed size=" + this.compressedSize); + info(" uncompressed size=" + this.uncompressedSize); + info(" file name length=" + this.fileNameLength); + info(" extra field length=" + this.extraFieldLength); + info(" filename = '" + this.filename + "'"); + + this.extraField = null; + if (this.extraFieldLength > 0) { + this.extraField = bstream.readString(this.extraFieldLength); + info(" extra field=" + this.extraField); + } + + // read in the compressed data + this.fileData = null; + if (this.compressedSize > 0) { + this.fileData = new Uint8Array(bstream.readBytes(this.compressedSize)); + } + + // TODO: deal with data descriptor if present (we currently assume no data descriptor!) + // "This descriptor exists only if bit 3 of the general purpose bit flag is set" + // But how do you figure out how big the file data is if you don't know the compressedSize + // from the header?!? + if ((this.generalPurpose & BIT[3]) != 0) { + this.crc32 = bstream.readNumber(4); + this.compressedSize = bstream.readNumber(4); + this.uncompressedSize = bstream.readNumber(4); + } + } + + // determine what kind of compressed data we have and decompress + unzip() { + // Zip Version 1.0, no compression (store only) + if (this.compressionMethod == 0 ) { + info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); + currentBytesUnarchivedInFile = this.compressedSize; + currentBytesUnarchived += this.compressedSize; + } + // version == 20, compression method == 8 (DEFLATE) + else if (this.compressionMethod == 8) { + info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); + this.fileData = inflate(this.fileData, this.uncompressedSize); + } + else { + err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); + this.fileData = null; + } + } +} + +// Takes an ArrayBuffer of a zip file in +// returns null on error +// returns an array of DecompressedFile objects on success +const unzip = function(arrayBuffer) { + postMessage(new bitjs.archive.UnarchiveStartEvent()); + + currentFilename = ""; + currentFileNumber = 0; + currentBytesUnarchivedInFile = 0; + currentBytesUnarchived = 0; + totalUncompressedBytesInArchive = 0; + totalFilesInArchive = 0; + currentBytesUnarchived = 0; + + const bstream = new bitjs.io.ByteStream(arrayBuffer); + // detect local file header signature or return null + if (bstream.peekNumber(4) == zLocalFileHeaderSignature) { + const localFiles = []; + // loop until we don't see any more local files + while (bstream.peekNumber(4) == zLocalFileHeaderSignature) { + const oneLocalFile = new ZipLocalFile(bstream); + // this should strip out directories/folders + if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) { + localFiles.push(oneLocalFile); + totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize; + } + } + totalFilesInArchive = localFiles.length; + + // got all local files, now sort them + localFiles.sort((a,b) => a.filename > b.filename ? 1 : -1); + + // archive extra data record + if (bstream.peekNumber(4) == zArchiveExtraDataSignature) { + info(" Found an Archive Extra Data Signature"); + + // skipping this record for now + bstream.readNumber(4); + const archiveExtraFieldLength = bstream.readNumber(4); + bstream.readString(archiveExtraFieldLength); + } + + // central directory structure + // TODO: handle the rest of the structures (Zip64 stuff) + if (bstream.peekNumber(4) == zCentralFileHeaderSignature) { + info(" Found a Central File Header"); + + // read all file headers + while (bstream.peekNumber(4) == zCentralFileHeaderSignature) { + bstream.readNumber(4); // signature + bstream.readNumber(2); // version made by + bstream.readNumber(2); // version needed to extract + bstream.readNumber(2); // general purpose bit flag + bstream.readNumber(2); // compression method + bstream.readNumber(2); // last mod file time + bstream.readNumber(2); // last mod file date + bstream.readNumber(4); // crc32 + bstream.readNumber(4); // compressed size + bstream.readNumber(4); // uncompressed size + const fileNameLength = bstream.readNumber(2); // file name length + const extraFieldLength = bstream.readNumber(2); // extra field length + const fileCommentLength = bstream.readNumber(2); // file comment length + bstream.readNumber(2); // disk number start + bstream.readNumber(2); // internal file attributes + bstream.readNumber(4); // external file attributes + bstream.readNumber(4); // relative offset of local header + + bstream.readString(fileNameLength); // file name + bstream.readString(extraFieldLength); // extra field + bstream.readString(fileCommentLength); // file comment + } + } + + // digital signature + if (bstream.peekNumber(4) == zDigitalSignatureSignature) { + info(" Found a Digital Signature"); + + bstream.readNumber(4); + const sizeOfSignature = bstream.readNumber(2); + bstream.readString(sizeOfSignature); // digital signature data + } + + // report # files and total length + if (localFiles.length > 0) { + postProgress(); + } + + // now do the unzipping of each file + for (let i = 0; i < localFiles.length; ++i) { + const localfile = localFiles[i]; + + // update progress + currentFilename = localfile.filename; + currentFileNumber = i; + currentBytesUnarchivedInFile = 0; + + // actually do the unzipping + localfile.unzip(); + + if (localfile.fileData != null) { + postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); + postProgress(); + } + } + postProgress(); + postMessage(new bitjs.archive.UnarchiveFinishEvent()); + } +} + +// returns a table of Huffman codes +// each entry's index is its code and its value is a JavaScript object +// containing {length: 6, symbol: X} +function getHuffmanCodes(bitLengths) { + // ensure bitLengths is an array containing at least one element + if (typeof bitLengths != typeof [] || bitLengths.length < 1) { + err("Error! getHuffmanCodes() called with an invalid array"); + return null; + } + + // Reference: http://tools.ietf.org/html/rfc1951#page-8 + const numLengths = bitLengths.length; + const bl_count = []; + let MAX_BITS = 1; + + // Step 1: count up how many codes of each length we have + for (let i = 0; i < numLengths; ++i) { + const length = bitLengths[i]; + // test to ensure each bit length is a positive, non-zero number + if (typeof length != typeof 1 || length < 0) { + err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length)); + return null; + } + // increment the appropriate bitlength count + if (bl_count[length] == undefined) bl_count[length] = 0; + // a length of zero means this symbol is not participating in the huffman coding + if (length > 0) bl_count[length]++; + if (length > MAX_BITS) MAX_BITS = length; + } + + // Step 2: Find the numerical value of the smallest code for each code length + const next_code = []; + let code = 0; + for (let bits = 1; bits <= MAX_BITS; ++bits) { + const length = bits-1; + // ensure undefined lengths are zero + if (bl_count[length] == undefined) bl_count[length] = 0; + code = (code + bl_count[bits-1]) << 1; + next_code[bits] = code; + } + + // Step 3: Assign numerical values to all codes + const table = {}; + let tableLength = 0; + for (let n = 0; n < numLengths; ++n) { + const len = bitLengths[n]; + if (len != 0) { + table[next_code[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(next_code[len],len) }; + tableLength++; + next_code[len]++; + } + } + table.maxLength = tableLength; + + return table; +} + +/* + The Huffman codes for the two alphabets are fixed, and are not + represented explicitly in the data. The Huffman code lengths + for the literal/length alphabet are: + + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 +*/ +// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits +let fixedHCtoLiteral = null; +let fixedHCtoDistance = null; +function getFixedLiteralTable() { + // create once + if (!fixedHCtoLiteral) { + const bitlengths = new Array(288); + for (let i = 0; i <= 143; ++i) bitlengths[i] = 8; + for (let i = 144; i <= 255; ++i) bitlengths[i] = 9; + for (let i = 256; i <= 279; ++i) bitlengths[i] = 7; + for (let i = 280; i <= 287; ++i) bitlengths[i] = 8; + + // get huffman code table + fixedHCtoLiteral = getHuffmanCodes(bitlengths); + } + return fixedHCtoLiteral; +} + +function getFixedDistanceTable() { + // create once + if (!fixedHCtoDistance) { + const bitlengths = new Array(32); + for (let i = 0; i < 32; ++i) { bitlengths[i] = 5; } + + // get huffman code table + fixedHCtoDistance = getHuffmanCodes(bitlengths); + } + return fixedHCtoDistance; +} + +// extract one bit at a time until we find a matching Huffman Code +// then return that symbol +function decodeSymbol(bstream, hcTable) { + let code = 0; + let len = 0; + let match = false; + + // loop until we match + for (;;) { + // read in next bit + const bit = bstream.readBits(1); + code = (code<<1) | bit; + ++len; + + // check against Huffman Code table and break if found + if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) { + break; + } + if (len > hcTable.maxLength) { + err("Bit stream out of sync, didn't find a Huffman Code, length was " + len + + " and table only max code length of " + hcTable.maxLength); + break; + } + } + return hcTable[code].symbol; +} + + +const CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + +/* + Extra Extra Extra +Code Bits Length(s) Code Bits Lengths Code Bits Length(s) +---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 +*/ +const LengthLookupTable = [ + [0,3], [0,4], [0,5], [0,6], + [0,7], [0,8], [0,9], [0,10], + [1,11], [1,13], [1,15], [1,17], + [2,19], [2,23], [2,27], [2,31], + [3,35], [3,43], [3,51], [3,59], + [4,67], [4,83], [4,99], [4,115], + [5,131], [5,163], [5,195], [5,227], + [0,258] +]; + +/* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 +*/ +const DistLookupTable = [ + [0,1], [0,2], [0,3], [0,4], + [1,5], [1,7], + [2,9], [2,13], + [3,17], [3,25], + [4,33], [4,49], + [5,65], [5,97], + [6,129], [6,193], + [7,257], [7,385], + [8,513], [8,769], + [9,1025], [9,1537], + [10,2049], [10,3073], + [11,4097], [11,6145], + [12,8193], [12,12289], + [13,16385], [13,24577] +]; + +function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { + /* + loop (until end of block code recognized) + decode literal/length value from input stream + if value < 256 + copy value (literal byte) to output stream + otherwise + if value = end of block (256) + break from loop + otherwise (value = 257..285) + decode distance from input stream + + move backwards distance bytes in the output + stream, and copy length bytes from this + position to the output stream. + */ + let numSymbols = 0; + let blockSize = 0; + for (;;) { + const symbol = decodeSymbol(bstream, hcLiteralTable); + ++numSymbols; + if (symbol < 256) { + // copy literal byte to output + buffer.insertByte(symbol); + blockSize++; + } else { + // end of block reached + if (symbol == 256) { + break; + } else { + const lengthLookup = LengthLookupTable[symbol - 257]; + let length = lengthLookup[1] + bstream.readBits(lengthLookup[0]); + const distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)]; + let distance = distLookup[1] + bstream.readBits(distLookup[0]); + + // now apply length and distance appropriately and copy to output + + // TODO: check that backward distance < data.length? + + // http://tools.ietf.org/html/rfc1951#page-11 + // "Note also that the referenced string may overlap the current + // position; for example, if the last 2 bytes decoded have values + // X and Y, a string reference with + // adds X,Y,X,Y,X to the output stream." + // + // loop for each character + let ch = buffer.ptr - distance; + blockSize += length; + if(length > distance) { + const data = buffer.data; + while (length--) { + buffer.insertByte(data[ch++]); + } + } else { + buffer.insertBytes(buffer.data.subarray(ch, ch + length)) + } + } // length-distance pair + } // length-distance pair or end-of-block + } // loop until we reach end of block + return blockSize; +} + +// {Uint8Array} compressedData A Uint8Array of the compressed file data. +// compression method 8 +// deflate: http://tools.ietf.org/html/rfc1951 +function inflate(compressedData, numDecompressedBytes) { + // Bit stream representing the compressed data. + const bstream = new bitjs.io.BitStream(compressedData.buffer, + false /* rtl */, + compressedData.byteOffset, + compressedData.byteLength); + const buffer = new bitjs.io.ByteBuffer(numDecompressedBytes); + let numBlocks = 0; + let blockSize = 0; + + // block format: http://tools.ietf.org/html/rfc1951#page-9 + let bFinal = 0; + do { + bFinal = bstream.readBits(1); + let bType = bstream.readBits(2); + blockSize = 0; + ++numBlocks; + // no compression + if (bType == 0) { + // skip remaining bits in this byte + while (bstream.bitPtr != 0) bstream.readBits(1); + const len = bstream.readBits(16); + const nlen = bstream.readBits(16); + // TODO: check if nlen is the ones-complement of len? + if (len > 0) buffer.insertBytes(bstream.readBytes(len)); + blockSize = len; + } + // fixed Huffman codes + else if(bType == 1) { + blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer); + } + // dynamic Huffman codes + else if(bType == 2) { + const numLiteralLengthCodes = bstream.readBits(5) + 257; + const numDistanceCodes = bstream.readBits(5) + 1; + const numCodeLengthCodes = bstream.readBits(4) + 4; + + // populate the array of code length codes (first de-compaction) + const codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + for (let i = 0; i < numCodeLengthCodes; ++i) { + codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3); + } + + // get the Huffman Codes for the code lengths + const codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths); + + // now follow this mapping + /* + 0 - 15: Represent code lengths of 0 - 15 + 16: Copy the previous code length 3 - 6 times. + The next 2 bits indicate repeat length + (0 = 3, ... , 3 = 6) + Example: Codes 8, 16 (+2 bits 11), + 16 (+2 bits 10) will expand to + 12 code lengths of 8 (1 + 6 + 5) + 17: Repeat a code length of 0 for 3 - 10 times. + (3 bits of length) + 18: Repeat a code length of 0 for 11 - 138 times + (7 bits of length) + */ + // to generate the true code lengths of the Huffman Codes for the literal + // and distance tables together + const literalCodeLengths = []; + let prevCodeLength = 0; + while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) { + const symbol = decodeSymbol(bstream, codeLengthsCodes); + if (symbol <= 15) { + literalCodeLengths.push(symbol); + prevCodeLength = symbol; + } else if (symbol == 16) { + let repeat = bstream.readBits(2) + 3; + while (repeat--) { + literalCodeLengths.push(prevCodeLength); + } + } else if (symbol == 17) { + let repeat = bstream.readBits(3) + 3; + while (repeat--) { + literalCodeLengths.push(0); + } + } else if (symbol == 18) { + let repeat = bstream.readBits(7) + 11; + while (repeat--) { + literalCodeLengths.push(0); + } + } + } + + // now split the distance code lengths out of the literal code array + const distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes); + + // now generate the true Huffman Code tables using these code lengths + const hcLiteralTable = getHuffmanCodes(literalCodeLengths); + const hcDistanceTable = getHuffmanCodes(distanceCodeLengths); + blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer); + } else { // error + err("Error! Encountered deflate block of type 3"); + return null; + } + + // update progress + currentBytesUnarchivedInFile += blockSize; + currentBytesUnarchived += blockSize; + postProgress(); + } while (bFinal != 1); + // we are done reading blocks if the bFinal bit was set for this block + + // return the buffer data bytes + return buffer.data; +} + +// event.data.file has the ArrayBuffer. +onmessage = function(event) { + unzip(event.data.file, true); +}; diff --git a/vendor/bitjs/io/bitstream.js b/vendor/bitjs/io/bitstream.js new file mode 100644 index 0000000..28b7dbc --- /dev/null +++ b/vendor/bitjs/io/bitstream.js @@ -0,0 +1,222 @@ +/* + * bitstream.js + * + * Provides readers for bitstreams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * This bit stream peeks and consumes bits out of a binary stream. + */ +bitjs.io.BitStream = class { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { + throw "Error! BitArray constructed with an invalid ArrayBuffer object"; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.bytePtr = 0; // tracks which byte we are on + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + const mask = (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + n -= numBitsLeftInThisByte; + } + else { + const mask = (bitjs.io.BitStream.BITMASK[n] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += n; + bitsIn += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + n -= numBitsLeftInThisByte; + } + else { + result <<= n; + result |= ((bytes[bytePtr] & (bitjs.io.BitStream.BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); + + bitPtr += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; + } + + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr+1] & 0xff) << 8) + + ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); + } + + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const movePointers = opt_movePointers || false; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + + const result = this.bytes.subarray(bytePtr, bytePtr + n); + + if (movePointers) { + this.bytePtr += n; + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } +} + +// mask for getting N number of bits (0-8) +bitjs.io.BitStream.BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; + diff --git a/vendor/bitjs/io/bytebuffer.js b/vendor/bitjs/io/bytebuffer.js new file mode 100644 index 0000000..444c9e2 --- /dev/null +++ b/vendor/bitjs/io/bytebuffer.js @@ -0,0 +1,117 @@ +/* + * bytestream.js + * + * Provides a writer for bytes. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. + */ +bitjs.io.ByteBuffer = class { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; + } + this.data = new Uint8Array(numBytes); + this.ptr = 0; + } + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; +} diff --git a/vendor/bitjs/io/bytestream.js b/vendor/bitjs/io/bytestream.js new file mode 100644 index 0000000..2af361e --- /dev/null +++ b/vendor/bitjs/io/bytestream.js @@ -0,0 +1,159 @@ +/* + * bytestream.js + * + * Provides readers for byte streams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; + + +/** + * This object allows you to peek and consume bytes as numbers and strings + * out of an ArrayBuffer. In this buffer, everything must be byte-aligned. + */ +bitjs.io.ByteStream = class { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this BitStream + */ + constructor(ab, opt_offset, opt_length) { + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.ptr = 0; + } + + + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer + * TODO: This apparently cannot read more than 4 bytes as a number? + * @param {number} n The number of bytes to peek at. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + // TODO: return error if n would go past the end of the stream? + if (n <= 0 || typeof n != typeof 1) { + return -1; + } + + let result = 0; + // read from last byte to first byte and roll them in + let curByte = this.ptr + n - 1; + while (curByte >= this.ptr) { + result <<= 8; + result |= this.bytes[curByte]; + --curByte; + } + return result; + } + + + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber( n ); + this.ptr += n; + return num; + } + + + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; + + if (num >= HALF) num -= FULL; + + return num; + } + + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.ptr += n; + return num; + } + + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return null; + } + + const result = this.bytes.subarray(this.ptr, this.ptr + n); + + if (movePointers) { + this.ptr += n; + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as a string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + if (n <= 0 || typeof n != typeof 1) { + return ""; + } + + let result = ""; + for (let p = this.ptr, end = this.ptr + n; p < end; ++p) { + result += String.fromCharCode(this.bytes[p]); + } + return result; + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.ptr += n; + return strToReturn; + } +} diff --git a/vendor/cbrjs/cbr.js b/vendor/cbrjs/cbr.js new file mode 100644 index 0000000..077369c --- /dev/null +++ b/vendor/cbrjs/cbr.js @@ -0,0 +1,1476 @@ +var CBRJS = CBRJS || {}; +CBRJS.VERSION = "0.0.1"; + +CBRJS.basePath = CBRJS.basePath || ""; +CBRJS.session = CBRJS.session || {}; + +CBRJS.Reader = function(bookPath, _options) { + + var reader = this, + $progressbar = $('.bar'), + search = window.location.search, + parameters, + options, + found; + + this.options = options = $.extend(true, _options || {}, { + bookPath: bookPath, + session: {} + }); + + // Overide options with search parameters + if(search) { + parameters = search.slice(1).split("&"); + parameters.forEach(function(p){ + var split = p.split("="); + var name = split[0]; + var value = split[1] || ''; + reader.options[name] = decodeURIComponent(value); + }); + } + + function extractImages(url, opts) { + + var images = [], + xhr = new XMLHttpRequest(), + filename = decodeURIComponent(url.split('/').pop()), + re_file_ext = new RegExp(/\.([a-z]+)$/), + format = filename.toLowerCase().match(re_file_ext)[1], + archive_class = ({ cbz: 'Unzipper', cbr: 'Unrarrer' })[format], + options = $.extend({ + start: function () {}, + extract: function (page_url) {}, + progress: function (percent_complete) {}, + finish: function (images) {} + }, opts); + + if (!archive_class) { + alert('invalid file type, only cbz and cbr are supported.'); + return false; + } + + xhr.open('GET',url, true); + + options.start(filename); + + xhr.responseType = "arraybuffer"; + + xhr.onprogress = function (e) { + if (e.lengthComputable) { + $progressbar.css('width', Math.floor((e.loaded / e.total) * 100) + '%'); + } + }; + + xhr.onloadstart = function (e) { + $progressbar.css('width', '0%'); + }; + + xhr.onloadend = function (e) { + $('.icon-cloud_download').addClass('ok'); + reader.options.session.size = e.total; + }; + + xhr.onload = function () { + if ((this.status === 200) && this.response) { + var done = false; + var ua = new bitjs.archive[archive_class](this.response, document.head.dataset.basepath + 'vendor/bitjs/'); + + ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.START, function (e) { + $progressbar.css('width', '0%'); + $('.icon-unarchive').addClass('active'); + }); + + ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT, function (e) { + + var mimetype, blob, url; + var file_extension = e.unarchivedFile.filename.toLowerCase().match(re_file_ext)[1]; + + switch (file_extension) { + case 'jpg': + case 'jpeg': + mimetype = 'image/jpeg'; + break; + case 'png': + mimetype = 'image/png'; + break; + case 'gif': + mimetype = 'image/gif'; + break; + default: + return false; + } + + blob = new Blob([e.unarchivedFile.fileData], { type: mimetype }); + url = window.URL.createObjectURL(blob); + + images.push(url); + + options.extract(url, blob); + }); + + ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, function (e) { + options.progress(Math.floor(e.currentBytesUnarchived / e.totalUncompressedBytesInArchive * 100)); + }); + + ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.FINISH, function (e) { + options.finish(images); + }); + + ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.ERROR, function (e) { + $('.icon-unarchive').removeClass('active'); + $('.icon-unarchive').addClass('error'); + $('#message').text('Failed to extract images from archive, file corrupted?'); + + }); + } + + ua.start(); + }; + + xhr.send(); + } + + function openComicArchive(url, options) { + + var title, page = 0; + + extractImages(url, { + start: function (filename) { + this.filename = filename; + $('.toolbar').addClass('hide'); + $('.navigation').addClass('hide'); + $('.icon-cloud_download').addClass('active'); + $('.message-text').text(filename); + $('#progressbar').show(); + }, + extract: function (url, blob) { + $('.message-text').text('extracting page #' + ++page); + }, + progress: function (percent_complete) { + $progressbar.css('width', percent_complete + '%'); + }, + finish: function (pages) { + + $('.icon-unarchive').addClass('ok'); + var name = this.filename.replace(/\.[a-z]+$/, ''); + var id = encodeURIComponent(name.toLowerCase()); + var book = new ComicBook('viewer', pages, options); + + document.title = name; + + $('.toolbar').removeClass('hide'); + $('.navigation').removeClass('hide'); + $('#progressbar').hide(); + $('#viewer').show(); + + book.draw(); + + $(window).on('resize', function () { + book.draw(); + }); + $(window).on('beforeunload', function(e) { + book.destroy(); + }); + } + }); + + } + + + function getPref (arr, name) { + if ((arr.constructor === Array) && (found = arr.filter(function(e) { return e.name === name; }))) { + if (found.hasOwnProperty("value")) { + return found.value; + } + } + }; + + openComicArchive(bookPath, { + currentPage: parseInt(options.session.cursor.value) || 0, + enhance: getPref(options.session.preferences, "enhance") || {}, + manga: getPref(options.session.preferences, "manga") || false, + thumbnails: getPref(options.session.defaults, "thumbnails"), + thumbnailWidth: parseInt(getPref(options.session.defaults, "thumbnailWidth")) || 200, + session: options.session + }); + + return this; +}; + +var ComicBook; +ComicBook = (function ($) { + + + 'use strict'; + + /** + * Merge two arrays. Any properties in b will replace the same properties in + * a. New properties from b will be added to a. + * + * @param a {Object} + * @param b {Object} + */ + function merge(a, b) { + + var prop; + + if (typeof b === 'undefined') { + b = {}; + } + + for (prop in a) { + if (a.hasOwnProperty(prop)) { + if (prop in b) { + continue; + } + b[prop] = a[prop]; + } + } + + return b; + } + + /** + * Exception class. Always throw an instance of this when throwing exceptions. + * + * @param {String} type + * @param {Object} object + * @returns {ComicBookException} + */ + var ComicBookException = { + INVALID_ACTION: 'invalid action', + INVALID_PAGE: 'invalid page', + INVALID_PAGE_TYPE: 'invalid page type', + UNDEFINED_CONTROL: 'undefined control', + INVALID_ZOOM_MODE: 'invalid zoom mode', + INVALID_NAVIGATION_EVENT: 'invalid navigation event' + }; + + function ComicBook(id, srcs, opts) { + + var self = this; + var canvas_id = id; // canvas element id + this.srcs = srcs; // array of image srcs for pages + + var defaults = { + displayMode: (window.innerWidth > window.innerHeight) ? 'double' : 'single', // single / double + zoomMode: 'fitWindow', // manual / fitWidth / fitWindow + manga: false, // true / false + fullscreen: false, // true / false + enhance: {}, // image filters to use + thumbnails: true, // true / false (use thumbnails in index) + thumbnailWidth: 200, // width of thumbnail + sidebarWide: false, // use wide sidbar + currentPage: 0, // current page + keyboard: { + 32: 'next', // space + 34: 'next', // page-down + 39: 'next', // cursor-right + 33: 'previous', // page-up + 37: 'previous', // cursor-left + 36: 'first', // home + 35: 'last', // end + 83: 'sidebar', // s + 84: 'toolbar', // t + 76: 'toggleLayout', // l + 70: 'toggleFullscreen', // f + 27: 'closeSidebar' // esc + }, + vendorPath: document.head.dataset.basepath + 'vendor/', + forward_buffer: 3, + session: { + getCursor: function() {}, + setCursor: function(value) {}, + getBookmark: function(name, type) {}, + setBookmark: function(name, value, type, content) {}, + getDefault: function(name) {}, + setDefault: function(name, value) {}, + getPreference: function(name) {}, + setPreference: function(name, value) {} + } + }; + + var options = {}; + + this.isMobile = false; + + // mobile enhancements + if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(navigator.userAgent)) { + this.isMobile = true; + document.body.classList.add('mobile'); + + window.addEventListener('load', function () { + setTimeout(function () { + window.scrollTo(0, 1); + }, 0); + }); + } + + window.addEventListener('resize', function () { + self.setLayout((window.innerWidth > window.innerHeight) ? 'double' : 'single'); + }); + + $.extend(true, options, defaults, opts); // options array for internal use + + var no_pages = srcs.length; + var pages = []; // array of preloaded Image objects + var canvas; // the HTML5 canvas object + var context; // the 2d drawing context + var tcv = document.createElement("canvas"); // canvas used for thumbnailer + var tctx = tcv.getContext('2d'); // context used for thumbnailer + var toc = document.getElementById('toc'); // context used for thumbnailer + var loaded = []; // the images that have been loaded so far + var scale = 1; // page zoom scale, 1 = 100% + var is_double_page_spread = false; + var controlsRendered = false; // have the user controls been inserted into the dom yet? + var page_requested = false; // used to request non preloaded pages + var shiv = false; + + /** + * Gets the window.innerWidth - scrollbars + */ + function windowWidth() { + + var height = window.innerHeight + 1; + + if (shiv === false) { + shiv = $(document.createElement('div')) + .attr('id', 'cbr-width-shiv') + .css({ + width: '100%', + position: 'absolute', + top: 0, + zIndex: '-1000' + }); + + $('body').append(shiv); + } + + shiv.height(height); + + return shiv.innerWidth(); + } + + /** + * enables the back button + */ + function checkHash() { + + var hash = getHash(); + + if (hash !== options.currentPage && loaded.indexOf(hash) > -1) { + options.currentPage = hash; + self.draw(); + } + } + + function getHash() { + var hash = parseInt(location.hash.substring(1), 10) - 1 || 0; + if (hash < 0) { + setHash(0); + hash = 0; + } + return hash; + } + + function setHash(pageNo) { + location.hash = pageNo; + } + + // page hash on first load + var hash = getHash(); + + /** + * Setup the canvas element for use throughout the class. + */ + function init() { + + // setup canvas + canvas = document.getElementById(canvas_id); + context = canvas.getContext('2d'); + + // render user controls + if (controlsRendered === false) { + self.renderControls(); + self.tocCreate(no_pages); + controlsRendered = true; + } + + // add page controls + window.addEventListener('keydown', self.navigation, false); + window.addEventListener('hashchange', checkHash, false); + + // fill in metadata + options.session.pagecount = srcs.length; + $('.book-title').text(options.session.title); + $('.book-format').text(options.session.format); + $('.book-pagecount').text(options.session.pagecount); + $('.book-size').text(options.session.size); + } + + window.addEventListener('touchstart', function (e) { + var $el = $(e.target); + if ($el.attr('id') === 'viewer') { + self.toggleToolbar(); + } + }, false); + + /** + * Connect controls to events + */ + ComicBook.prototype.renderControls = function () { + + var controls = {}, $toolbar; + + // set values from preferences or defaults + // do this before connecting listeners to avoid triggering callbacks + for (var prop in options.enhance) { + if(options.enhance.hasOwnProperty(prop)) { + switch (prop) { + case 'brightness': + document.getElementById('brightness').value = options.enhance.brightness['brightness']; + document.getElementById('contrast').value = options.enhance.brightness['contrast']; + break; + case 'sharpen': + document.getElementById('sharpen').value = options.enhance.sharpen['strength']; + break; + case 'desaturate': + $('#image-desaturate').prop('checked', true); + break; + case 'removenoise': + $('#image-removenoise').prop('checked', true); + break; + default: + console.log("unknown enhancement: " + JSON.stringify(prop)); + } + } + }; + + // thumbnail controls + $('#thumbnail-generate').prop('checked', options.thumbnails); + $('#thumbnail-width').val(options.thumbnailWidth); + if (!options.thumbnails) { + $('#toc-populate').addClass('open'); + $('#thumbnail-width').prop('disabled', true); + } + + // connect callbacks + $('.control').each(function () { + + controls[$(this).attr('name')] = $(this); + + // add event listeners to controls that specify callbacks + $(this).find('*').andSelf().filter('[data-action][data-trigger]').each(function () { + + var $this = $(this); + var trigger = $this.data('trigger'); + var action = $this.data('action'); + + // trigger a direct method if exists + if (typeof self[$this.data('action')] === 'function') { + $this.on(trigger, self[action]); + } + + // throw an event to be caught outside if the app code + $this.on(trigger, function (e) { + $(self).trigger(trigger, e); + }); + }); + }); + + this.controls = controls; + + $toolbar = this.getControl('toolbar'); + $toolbar + .find('.manga-' + options.manga).show().end() + .find('.manga-' + !options.manga).hide().end() + .find('.layout').hide().end().find('.layout-' + options.displayMode).show().end() + .find('.fullscreen-' + options.fullscreen).show().end() + .find('.fullscreen-' + !options.fullscreen).hide(); + + if (parent !== window) { + $('.close').removeClass('hide'); + $('.close').on('click', function() { parent.OCA.Files_Reader.Plugin.hide(); }); + } + }; + + ComicBook.prototype.getControl = function (control) { + if (typeof this.controls[control] !== 'object') { + throw ComicBookException.UNDEFINED_CONTROL + ' ' + control; + } + return this.controls[control]; + }; + + ComicBook.prototype.showControl = function (control) { + this.getControl(control).show().addClass('open'); + }; + + ComicBook.prototype.hideControl = function (control) { + this.getControl(control).removeClass('open').hide(); + }; + + ComicBook.prototype.toggleControl = function (control) { + this.getControl(control).toggle().toggleClass('open'); + }; + + ComicBook.prototype.toggleLayout = function () { + self.setLayout((options.displayMode === 'single') ? 'double' : 'single'); + }; + + ComicBook.prototype.setLayout = function (layout) { + var $toolbar = self.getControl('toolbar'); + options.displayMode = (layout === 'single') ? 'single' : 'double'; + + $toolbar.find('.layout').hide().end().find('.layout-' + options.displayMode).show(); + + self.drawPage(); + }; + + + /** + * Create thumbnail for image + * + * @return Image + */ + ComicBook.prototype.getThumb = function (image) { + var thumb = new Image(); + var scale = image.width / options.thumbnailWidth; + tcv.width = options.thumbnailWidth; + tcv.height = Math.floor(image.height / scale); + tctx.drawImage(image, 0, 0, tcv.width, tcv.height); + thumb.src = tcv.toDataURL(); + tctx.clearRect(0, 0, tcv.width, tcv.height); + + return thumb; + }; + + /** + * Create empty TOC with placeholder images + */ + ComicBook.prototype.tocCreate = function (no_pages) { + // use small image with reasonable aspect ratio + tcv.width = 5; + tcv.height = 7; + // transparent, style with .placeholder in CSS + tctx.fillStyle = "rgba(200, 200, 200, 0)"; + tctx.fillRect(0, 0, tcv.width, tcv.height); + var imgsrc = tcv.toDataURL(); + + for(var i = 0; i < no_pages; i++) { + var item = document.createElement('li'); + item.setAttribute("id", "page-" + parseInt(i + 1)); + var placeholder = new Image(); + placeholder.src = imgsrc; + var label = document.createElement('span'); + label.innerHTML = i + 1; + item.appendChild(placeholder); + item.appendChild(label); + toc.appendChild(item); + } + }; + + /** + * Insert thumbnail into TOC + */ + ComicBook.prototype.tocInsert = function (image, page, replace) { + var placeholder = toc.children[page].firstChild; + if (replace === true) { + placeholder.parentNode.replaceChild( + self.getThumb(image), + placeholder + ); + } + + toc.children[page].addEventListener('click', function (e) { + self.drawPage(page + 1, true); + }); + }; + + /** + * Populate TOC on demand + */ + ComicBook.prototype.tocPopulate = function () { + var i = 0; + while (i < srcs.length) { + self.tocInsert(pages[i], i, true); + i++; + } + + // set, but don't save for future sessions + options.thumbnails = true; + $('#toc-populate').removeClass('open'); + }; + + /** + * Get the image for a given page. + * + * @return Image + */ + ComicBook.prototype.getPage = function (i) { + + if (i < 0 || i > srcs.length - 1) { + throw ComicBookException.INVALID_PAGE + ' ' + i; + } + + if (typeof pages[i] === 'object') { + return pages[i]; + } else { + page_requested = i; + this.showControl('loadingOverlay'); + } + }; + + /** + * @see #preload + */ + ComicBook.prototype.draw = function () { + + init(); + + // resize navigation controls + $('.navigate').outerHeight(window.innerHeight); + $('.overlay').outerWidth(windowWidth()).height(window.innerHeight); + + // preload images if needed + if (pages.length !== no_pages) { + this.preload(); + } else { + this.drawPage(); + } + }; + + /** + * Zoom the canvas + * + * @param new_scale {Number} Scale the canvas to this ratio + */ + ComicBook.prototype.zoom = function (new_scale) { + options.zoomMode = 'manual'; + scale = new_scale; + if (typeof this.getPage(options.currentPage) === 'object') { + this.drawPage(); + } + }; + + ComicBook.prototype.zoomIn = function () { + self.zoom(scale + 0.1); + }; + + ComicBook.prototype.zoomOut = function () { + self.zoom(scale - 0.1); + }; + + ComicBook.prototype.fitWidth = function () { + options.zoomMode = 'fitWidth'; + self.drawPage(); + }; + + ComicBook.prototype.fitWindow = function () { + options.zoomMode = 'fitWindow'; + self.drawPage(); + }; + + /** + * Preload all images, draw the page only after a given number have been loaded. + * + * @see #drawPage + */ + ComicBook.prototype.preload = function () { + + var i = options.currentPage; // the current page counter for this method + var rendered = false; + var queue = []; + + this.showControl('loadingOverlay'); + + function loadImage(i) { + + var page = new Image(); + page.src = srcs[i]; + + page.onload = function () { + + pages[i] = this; + + self.tocInsert(this, i, options.thumbnails); + + loaded.push(i); + + $('#cbr-progress-bar .progressbar-value').css('width', Math.floor((loaded.length / no_pages) * 100) + '%'); + + // double page mode needs an extra page added + var buffer = (options.displayMode === 'double' && options.currentPage < srcs.length - 1) ? 1 : 0; + + // start rendering the comic when the requested page is ready + if ((rendered === false && ($.inArray(options.currentPage + buffer, loaded) !== -1) || + (typeof page_requested === 'number' && $.inArray(page_requested, loaded) !== -1))) { + // if the user is waiting for a page to be loaded, render that one instead of the default options.currentPage + if (typeof page_requested === 'number') { + options.currentPage = page_requested - 1; + page_requested = false; + } + + self.drawPage(); + self.hideControl('loadingOverlay'); + rendered = true; + } + + if (queue.length) { + loadImage(queue[0]); + queue.splice(0, 1); + } else { + $('#cbr-status').delay(500).fadeOut(); + } + }; + } + + // loads pages in both directions so you don't have to wait for all pages + // to be loaded before you can scroll backwards + function preload(start, stop) { + + var j = 0; + var count = 1; + var forward = start; + var backward = start - 1; + + while (forward <= stop) { + + if (count > options.forward_buffer && backward > -1) { + queue.push(backward); + backward--; + count = 0; + } else { + queue.push(forward); + forward++; + } + count++; + } + + while (backward > -1) { + queue.push(backward); + backward--; + } + + loadImage(queue[j]); + } + + preload(i, srcs.length - 1); + }; + + ComicBook.prototype.pageLoaded = function (page_no) { + return (typeof loaded[page_no - 1] !== 'undefined'); + }; + + /** + * Draw the current page in the canvas + */ + ComicBook.prototype.drawPage = function (page_no, reset_scroll) { + + var scrollY; + + reset_scroll = (typeof reset_scroll !== 'undefined') ? reset_scroll : true; + scrollY = reset_scroll ? 0 : window.scrollY; + + // if a specific page is given try to render it, if not bail and wait for preload() to render it + if (typeof page_no === 'number' && page_no < srcs.length && page_no > 0) { + options.currentPage = page_no - 1; + if (!this.pageLoaded(page_no)) { + this.showControl('loadingOverlay'); + return; + } + } + + if (options.currentPage < 0) { + options.currentPage = 0; + } + + var zoom_scale; + var offsetW = 0, + offsetH = 0; + + var page = self.getPage(options.currentPage); + var page2 = false; + + if (options.displayMode === 'double' && options.currentPage < srcs.length - 1) { + page2 = self.getPage(options.currentPage + 1); + } + + if (typeof page !== 'object') { + throw ComicBookException.INVALID_PAGE_TYPE + ' ' + typeof page; + } + + var width = page.width, + height = page.height; + + // reset the canvas to stop duplicate pages showing + canvas.width = 0; + canvas.height = 0; + + // show double page spreads on a single page + is_double_page_spread = ( + typeof page2 === 'object' && + (page.width > page.height || page2.width > page2.height) && + options.displayMode === 'double' + ); + if (is_double_page_spread) { + options.displayMode = 'single'; + } + + if (options.displayMode === 'double') { + + // for double page spreads, factor in the width of both pages + if (typeof page2 === 'object') { + width += page2.width; + } + + // if this is the last page and there is no page2, still keep the canvas wide + else { + width += width; + } + } + + // update the page scale if a non manual mode has been chosen + switch (options.zoomMode) { + + case 'manual': + document.body.style.overflowX = 'auto'; + zoom_scale = (options.displayMode === 'double') ? scale * 2 : scale; + break; + + case 'fitWidth': + document.body.style.overflowX = 'hidden'; + + // scale up if the window is wider than the page, scale down if the window + // is narrower than the page + zoom_scale = (windowWidth() > width) ? ((windowWidth() - width) / windowWidth()) + 1 : windowWidth() / width; + + // update the interal scale var so switching zoomModes while zooming will be smooth + scale = zoom_scale; + break; + + case 'fitWindow': + document.body.style.overflowX = 'hidden'; + + var width_scale = (windowWidth() > width) ? + ((windowWidth() - width) / windowWidth()) + 1 // scale up if the window is wider than the page + : + windowWidth() / width; // scale down if the window is narrower than the page + var windowHeight = window.innerHeight; + var height_scale = (windowHeight > height) ? + ((windowHeight - height) / windowHeight) + 1 // scale up if the window is wider than the page + : + windowHeight / height; // scale down if the window is narrower than the page + + zoom_scale = (width_scale > height_scale) ? height_scale : width_scale; + scale = zoom_scale; + break; + + default: + throw ComicBookException.INVALID_ZOOM_MODE + ' ' + options.zoomMode; + } + + var canvas_width = page.width * zoom_scale; + var canvas_height = page.height * zoom_scale; + + var page_width = (options.zoomMode === 'manual') ? page.width * scale : canvas_width; + var page_height = (options.zoomMode === 'manual') ? page.height * scale : canvas_height; + + canvas_height = page_height; + + // make sure the canvas is always at least full screen, even if the page is more narrow than the screen + canvas.width = (canvas_width < windowWidth()) ? windowWidth() : canvas_width; + canvas.height = (canvas_height < window.innerHeight) ? window.innerHeight : canvas_height; + + // always keep pages centered + if (options.zoomMode === 'manual' || options.zoomMode === 'fitWindow') { + + // work out a horizontal position + if (canvas_width < windowWidth()) { + offsetW = (windowWidth() - page_width) / 2; + if (options.displayMode === 'double') { + offsetW = offsetW - page_width / 2; + } + } + + // work out a vertical position + if (canvas_height < window.innerHeight) { + offsetH = (window.innerHeight - page_height) / 2; + } + } + + // in manga double page mode reverse the page(s) + if (options.manga && options.displayMode === 'double' && typeof page2 === 'object') { + var tmpPage = page; + var tmpPage2 = page2; + page = tmpPage2; + page2 = tmpPage; + } + + // draw the page(s) + context.drawImage(page, offsetW, offsetH, page_width, page_height); + if (options.displayMode === 'double' && typeof page2 === 'object') { + context.drawImage(page2, page_width + offsetW, offsetH, page_width, page_height); + } + + this.pixastic = new Pixastic(context, options.vendorPath + 'pixastic/'); + + // apply any image enhancements previously defined + $.each(options.enhance, function (action, options) { + self.enhance[action](options); + }); + + var current_page = + (options.displayMode === 'double' && + options.currentPage + 2 <= srcs.length) ? (options.currentPage + 1) + '-' + (options.currentPage + 2) : options.currentPage + 1; + + this.getControl('toolbar') + .find('.current-page').text(current_page) + .end() + .find('.page-count').text(srcs.length); + + // revert page mode back to double if it was auto switched for a double page spread + if (is_double_page_spread) { + options.displayMode = 'double'; + } + + // disable the fit width button if needed + $('button.cbr-fit-width').attr('disabled', (options.zoomMode === 'fitWidth')); + $('button.cbr-fit-window').attr('disabled', (options.zoomMode === 'fitWindow')); + + // disable prev/next buttons if not needed + $('.navigate').show(); + if (options.currentPage === 0) { + if (options.manga) { + $('.navigate-left').show(); + $('.navigate-right').hide(); + } else { + $('.navigate-left').hide(); + $('.navigate-right').show(); + } + } + + if (options.currentPage === srcs.length - 1 || (typeof page2 === 'object' && options.currentPage === srcs.length - 2)) { + if (options.manga) { + $('.navigate-left').hide(); + $('.navigate-right').show(); + } else { + $('.navigate-left').show(); + $('.navigate-right').hide(); + } + } + + if (options.currentPage !== getHash()) { + $(this).trigger('navigate'); + } + + // update hash location + if (getHash() !== options.currentPage) { + setHash(options.currentPage + 1); + } + + options.session.setCursor(options.currentPage); + }; + + /** + * Increment the counter and draw the page in the canvas + * + * @see #drawPage + */ + ComicBook.prototype.drawNextPage = function () { + + var page; + + try { + page = self.getPage(options.currentPage + 1); + } catch (e) { + } + + if (!page) { + return false; + } + + if (options.currentPage + 1 < pages.length) { + options.currentPage += (options.displayMode === 'single' || is_double_page_spread) ? 1 : 2; + try { + self.drawPage(); + } catch (e) { + } + } + + // make sure the top of the page is in view + window.scroll(0, 0); + }; + + /** + * Decrement the counter and draw the page in the canvas + * + * @see #drawPage + */ + ComicBook.prototype.drawPrevPage = function () { + + var page; + + try { + page = self.getPage(options.currentPage - 1); + } catch (e) { + } + + if (!page) { + return false; + } + + is_double_page_spread = (page.width > page.height); // need to run double page check again here as we are going backwards + + if (options.currentPage > 0) { + options.currentPage -= (options.displayMode === 'single' || is_double_page_spread) ? 1 : 2; + self.drawPage(); + } + + // make sure the top of the page is in view + window.scroll(0, 0); + }; + + /* default settings */ + + ComicBook.prototype.thumbnails = function() { + if ($(this).is(':checked')) { + options.thumbnails = true; + document.getElementById('thumbnail-width').disabled = false; + } else { + options.thumbnails = false; + document.getElementById('thumbnail-width').disabled = true; + } + + options.session.setDefault("thumbnails", options.thumbnails); + }; + + ComicBook.prototype.thumbnailWidth = function() { + options.thumbnailWidth = $(this).val(); + options.session.setDefault("thumbnailWidth", options.thumbnailWidth); + }; + + ComicBook.prototype.sidebarWide = function (wide) { + if (typeof(wide) !== "boolean") { + wide = ($(this).is(':checked') === true); + } + + if (wide) { + options.sidebarWide = true; + document.getElementById('sidebar').classList.add('wide'); + } else { + options.sidebarWide = false; + document.getElementById('sidebar').classList.remove('wide'); + self.sidebarWidth(0); + } + + options.session.setDefault("sidebarWide", options.sidebarWide); + }; + + ComicBook.prototype.sidebarWidth = function(width) { + if (typeof(width) !== "number") { + width = $(this).val(); + } + options.sidebarWidth = width; + + // width === 0 is interpreted as 'use value from CSS' + if (options.sidebarWidth > 0) { + document.getElementById('sidebar').style.width = options.sidebarWidth + "%"; + } else { + document.getElementById('sidebar').style.width = ""; + } + + options.session.setDefault("sidebarWidth", options.sidebarWidth); + }; + + ComicBook.prototype.resetSidebar = function () { + self.sidebarWide(false); + self.sidebarWidth(0); + }; + + /* book-specific settings */ + + ComicBook.prototype.brightness = function () { + var $brightness = { + brightness: $('#brightness').val(), + contrast: $('#contrast').val() + }; + + self.enhance.brightness($brightness); + options.enhance.brightness = $brightness; + options.session.setPreference("enhance",options.enhance); + }; + + ComicBook.prototype.sharpen = function () { + options.enhance.sharpen = $(this).val(); + self.enhance.sharpen({ + strength: options.enhance.sharpen + }); + + options.session.setPreference("enhance",options.enhance); + }; + + ComicBook.prototype.desaturate = function () { + if ($(this).is(':checked')) { + options.enhance.desaturate = {}; + self.enhance.desaturate(); + } else { + delete options.enhance.desaturate; + self.enhance.resaturate(); + } + + options.session.setPreference("enhance",options.enhance); + }; + + ComicBook.prototype.removenoise = function () { + if ($(this).is(':checked')) { + options.enhance.removenoise = {}; + self.enhance.removenoise(); + } else { + delete options.enhance.removenoise; + self.enhance.unremovenoise(); + } + + options.session.setPreference("enhance",options.enhance); + }; + + ComicBook.prototype.resetEnhancements = function () { + self.enhance.reset(); + options.session.setPreference("enhance",options.enhance); + }; + + /** + * Apply image enhancements to the canvas. + */ + ComicBook.prototype.enhance = { + + /** + * Reset enhancements. + * This can reset a specific enhancement if the method name is passed, or + * it will reset all. + * + * @param method {string} the specific enhancement to reset + */ + reset: function (method) { + if (!method) { + options.enhance = {}; + } else { + delete options.enhance[method]; + } + self.drawPage(null, false); + }, + + /** + * Pixastic progress callback + * @param {float} progress + */ + // progress: function (progress) { + progress: function () { + // console.info(Math.floor(progress * 100)); + }, + + /** + * Pixastic on complete callback + */ + done: function () { + + }, + + /** + * Adjust brightness / contrast + * + * params + * brightness (int) -150 to 150 + * contrast: (float) -1 to infinity + * + * @param {Object} params Brightness & contrast levels + * @param {Boolean} reset Reset before applying more enhancements? + */ + brightness: function (params, reset) { + + if (reset !== false) { + this.reset('brightness'); + } + + // merge user options with defaults + var opts = merge({ + brightness: 0, + contrast: 0 + }, params); + + options.enhance.brightness = opts; + + // run the enhancement + self.pixastic.brightness({ + brightness: opts.brightness, + contrast: opts.contrast + }).done(this.done, this.progress); + }, + + /** + * Force black and white + */ + desaturate: function () { + options.enhance.desaturate = {}; + self.pixastic.desaturate().done(this.done, this.progress); + }, + + /** + * Undo desaturate + */ + resaturate: function () { + delete options.enhance.desaturate; + self.drawPage(null, false); + }, + + /** + * Sharpen + * + * options: + * strength: number (-1 to infinity) + * + * @param {Object} options + */ + sharpen: function (params) { + + this.desharpen(); + + var opts = merge({ + strength: 0 + }, params); + + options.enhance.sharpen = opts; + + self.pixastic.sharpen3x3({ + strength: opts.strength + }).done(this.done, this.progress); + }, + + desharpen: function () { + delete options.enhance.sharpen; + self.drawPage(null, false); + }, + + /** + * Remove noise + */ + removenoise: function () { + options.enhance.removenoise = {}; + self.pixastic.removenoise().done(this.done, this.progress); + }, + + unremovenoise: function () { + delete options.enhance.removenoise; + self.drawPage(null, false); + } + }; + + ComicBook.prototype.navigation = function (e) { + + // disable navigation when the overlay is showing + if ($('#cbr-loading-overlay').is(':visible')) { + return false; + } + + var side = false, page_no = false; + + switch (e.type) { + + case 'click': + side = e.currentTarget.getAttribute('data-navigate-side'); + break; + + case 'keydown': + + // console.log("keydown: " + e.keyCode); + + switch (options.keyboard[e.keyCode]) { + case 'previous': + side = 'left'; + break; + case 'next': + side = 'right'; + break; + case 'first': + page_no = 1; + break; + case 'last': + page_no = srcs.length - 1; + break; + case 'sidebar': + self.toggleSidebar(); + break; + case 'toolbar': + self.toggleToolbar(); + break; + case 'toggleLayout': + self.toggleLayout(); + break; + case 'toggleFullscreen': + self.toggleFullscreen(); + break; + case 'closeSidebar': + self.closeSidebar(); + break; + default: + /* + throw ComicBookException.INVALID_NAVIGATION_EVENT + ' ' + e.type; + */ + } + break; + + default: + throw ComicBookException.INVALID_NAVIGATION_EVENT + ' ' + e.type; + } + + if (side) { + + e.stopPropagation(); + + // western style (left to right) + if (!options.manga) { + if (side === 'left') { + self.drawPrevPage(); + } + if (side === 'right') { + self.drawNextPage(); + } + } + // manga style (right to left) + else { + if (side === 'left') { + self.drawNextPage(); + } + if (side === 'right') { + self.drawPrevPage(); + } + } + + return false; + } + + if (page_no) { + self.drawPage(page_no, true); + return false; + } + }; + + ComicBook.prototype.toggleReadingMode = function () { + options.manga = !options.manga; + self.getControl('toolbar') + .find('.manga-' + options.manga).show().end() + .find('.manga-' + !options.manga).hide(); + options.session.setPreference("manga",options.manga); + }; + + ComicBook.prototype.toggleToolbar = function () { + self.toggleControl('toolbar'); + }; + + ComicBook.prototype.openSidebar = function () { + $('.sidebar').addClass('open'); + $('.toolbar').addClass('open'); + self.showControl('busyOverlay'); + self.scrollToc(); + }; + + ComicBook.prototype.closeSidebar = function () { + $('.sidebar').removeClass('open'); + $('.toolbar').removeClass('open'); + self.toggleToolbar(); + self.hideControl('busyOverlay'); + }; + + ComicBook.prototype.toggleSidebar = function () { + $('.sidebar').hasClass('open') + ? self.closeSidebar() + : self.openSidebar(); + }; + + ComicBook.prototype.toggleFullscreen = function () { + options.fullscreen = !options.fullscreen; + self.getControl('toolbar') + .find('.fullscreen-' + options.fullscreen).show().end() + .find('.fullscreen-' + !options.fullscreen).hide(); + if (options.fullscreen) { + screenfull.request($('#container')[0]); + } else { + screenfull.exit($('#container')[0]); + } + }; + + + /* + * Scroll TOC to page (default: current page) + */ + ComicBook.prototype.scrollToc = function (page) { + if (page === undefined) { + page = options.currentPage; + } + + document.getElementById('toc').parentNode.scrollTop = + document.getElementById('page-' + String(page + 1)).offsetTop + - Math.floor($('.panels').height() * 1.5); + }; + + ComicBook.prototype.showToc = function () { + self.getControl('sidebar') + .find('.open').removeClass('open').end() + .find('.toc-view').addClass('open'); + if (!options.thumbnails) { + $('#toc-populate').addClass('open'); + } + }; + + ComicBook.prototype.showBookSettings = function () { + self.getControl('sidebar') + .find('.open').removeClass('open').end() + .find('.book-settings-view').addClass('open'); + }; + + ComicBook.prototype.showSettings = function () { + self.getControl('sidebar') + .find('.open').removeClass('open').end() + .find('.settings-view').addClass('open'); + }; + + ComicBook.prototype.destroy = function () { + + $.each(this.controls, function (name, $control) { + $control.remove(); + }); + + canvas.width = 0; + canvas.height = 0; + + window.removeEventListener('keydown', this.navigation, false); + window.removeEventListener('hashchange', checkHash, false); + + setHash(''); + + // $(this).trigger('destroy'); + }; + + } + + return ComicBook; + +})(jQuery); + +(function(root, $) { + + var previousReader = root.cbReader || {}; + + var cbReader = root.cbReader = function(path, options) { + return new CBRJS.Reader(path, options); + }; + + //exports to multiple environments + if (typeof define === 'function' && define.amd) { + //AMD + define(function(){ return Reader; }); + } else if (typeof module != "undefined" && module.exports) { + //Node + module.exports = cbReader; + } + +})(window, jQuery); diff --git a/vendor/cbrjs/css/cbr.css b/vendor/cbrjs/css/cbr.css new file mode 100644 index 0000000..00d3164 --- /dev/null +++ b/vendor/cbrjs/css/cbr.css @@ -0,0 +1,454 @@ +/* reader */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +/* remember to define focus styles! */ +:focus { + outline: 0; +} + +/* remember to highlight inserts somehow! */ +ins { + text-decoration: none; +} +del { + text-decoration: line-through; +} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +.cbr-control { + font-family: helvetica, arial, sans-serif; + font-size: 12px; +} + +.cbr-control { + color: #fff; + background-color: #111; + padding: 10px; + position: fixed !important; + box-shadow: 0 0 4px #000; +} + +.navigate { + top: 0; + margin: 0; + cursor: pointer; + width: 20%; + opacity: 0; + background: center no-repeat; + box-shadow: none; + padding: 0 3em; +} + +.navigate > span { + color: #000; + font-size: 10em; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 1em; + top: 45%; + position: relative; +} + +body:not(.mobile) .navigate:hover { + opacity: 1; +} + +.navigate-left { + left: 0; +} + +.navigate-left > span { + float: left; +} + +.navigate-right { + right: 0; +} + +.navigate-right > span { + float: right; +} + +.toggle-controls { + cursor: pointer; + width: 20%; + height: 20%; + left: 40%; + top: 40%; + border: none; + position: fixed; +} + +#cbr-loading-overlay { + z-index: 100; + padding: 0; + width: 20px; + height: 20px; +} + +#cbr-loading-overlay > img { + width: 100%; +} + +.overlay { + opacity: 0.7; + box-shadow: none; +} + +#cbr-status { + z-index: 101; + font-size: 12px; + right: 0; + bottom: 0; + margin: 8px; + border-radius: 4px; +} + +#cbr-progress-bar { + width: 200px; +} + +#cbr-progress-bar, +#cbr-progress-bar .progressbar-value { + height: 3px; +} + +#cbr-progress-bar .progressbar-value { + width: 0; + background: #86C441; + border-color: #3E7600; +} + +* { + -webkit-user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + line-height: 20px; + color: #333; +} + +button, +input, +label { + cursor: pointer; +} + +.pull-left { + float: left; +} + +.pull-right { + float: right; +} + +.toolbar, +.panels { + position: absolute; + color: white; + /* overflow: visible; */ + background: #4e4e4e; + /* left: 0; */ + /* right: 0; */ + width: 100%; +} + +.toolbar { + /* position: fixed; */ + z-index: 99; + /* margin-bottom: 0; */ + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); + opacity: 0; + transition: opacity 0.1s ease-in-out; + text-align: center; +} + +.toolbar .metainfo { + font-size: 1.2em; + top: 0.5em; +} + +.metainfo span { + margin: 0 0.5em; +} + +.mobile .metainfo .book-title, +.mobile .metainfo .title-separator { + display: none; +} + +body:not(.mobile) .toolbar:hover, +.mobile .toolbar, +.toolbar.open { + opacity: 1; +} + +.toolbar div { + display: inline-block; + position: relative; +} + +.toolbar .separator { + /* border: solid 1px; */ + height: 1em; + opacity: 0.5; +} + +.toolbar button, .sidebar button { + color: white; + border: none; + background-color: transparent; + padding: 0; +} + +.toolbar div > button, +.sidebar div > button { + font-size: 1.5em; + padding: 0.5em; + margin: 0; +} + +.mobile .toolbar div > button, +.mobile .sidebar div > button { + padding: 0.5em; + margin: 0; +} + +body:not(.mobile) .toolbar div > button:hover, +body:not(.mobile) .sidebar div > button:hover { + color: #8CC746; +} + +body:not(.mobile) .toolbar button[data-action=close]:hover { + color: #FF6464; +} + +.hide { + display: none !important; +} + +/* sidebar */ +.sidebar.open { + box-shadow: 3px 0px 3px 0px rgba(0, 0, 0, 0.4); + display: block; +} + +.sidebar { + background: #6b6b6b; + position: fixed; + top: 0; + min-width: 25em; + height: 100%; + overflow: hidden; + display: none; + z-index: 100; +} + +.sidebar.wide { + width: 20%; +} + +.panels { + overflow: hidden; +} + +.panels .open { + background-color: #6B6B6B; +} + +.view.open { + display: block !important; +} + +#toc-populate.open { + display: inline-block !important; + background-color: #4e4e4e; +} + +.view { + overflow-y: scroll; + display: none !important; + width: 100%; + position: absolute; + top: 3em; + bottom: 0; + text-align: center; +} + +.toc-view li { + margin: 1em; + font-family: Georgia, "Times New Roman", Times, serif; +} + +.toc-view img, .placeholder { + width: 100%; + position: relative; + background-color: #999; +} + +.toc-view span { + position: absolute; + transform: translate3d(-3em, 3em, 0); + font-size: 5em; + font-weight: bold; + color: #F8F8F8; + text-shadow: 0.05em 0.05em 0.02em rgba(70, 70, 70, 0.8); + -webkit-text-stroke: 2px black; + background-color: rgba(255,255,255,0.7); + border-radius: 1em; + box-shadow: 0 0 0.3em rgba(255,255,255,1); + padding: 0.5em; +} + +.settings-container { + text-align: left; + display: inline-block; + width: 95%; + font-size: 1em; + background: #F8F8F8; + color: #111; + padding-top: 1em; + padding-bottom: 1em; + margin-top: 1em; + border-radius: 4px; + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.settings-container label { + margin-right: 1em; +} + +.settings-container > label { + font-weight: bold; + width: 100%; + display: inline-block; + margin-bottom: 1em; + text-align: center; +} + +.view .control-group input[type=range] { + width: 80%; + float: right; + margin: 0; +} + +.view .control-group { + padding: 1em; +} + +.view .sliders { + font-size: 1.5em; +} + +.view .control-group span { + float: left; + margin: 0 2px; + clear: both; +} + +.view .control-group input[type=reset] { + float: right; +} + +.metadata { + padding: 1em; + margin: 1em; +} + +.metadata table { + font-size: 1.2em; + color: #F8F8F8; +} + +.metadata td:nth-child(1) { + font-weight: bold; + padding-right: 1em; +} + +/* END sidebar */ + +/* progressbar (loading/unarchiving) */ + +.progress, .bar { + width: 100%; + height: 0.3em; + margin: 0; + padding: 0; + display: inline-block; +} +.progress { + border: none; +} +.bar { + width: 0; + background-color: red; + box-shadow: 0px 1px 3px rgba(0,0,0,.6); +} + +.message { + margin: 3em; +} + +.message-icons { + font-size: 3em; + color: lightgrey; + vertical-align: middle; +} + +.message-text { + font-size: 1.5em; + color: black; +} + +.active { + color:black; +} + +.ok { + color:green; +} + +.error { + color:red; +} + +/* END progressbar */ diff --git a/vendor/cbrjs/css/idevice.css b/vendor/cbrjs/css/idevice.css new file mode 100644 index 0000000..e8da954 --- /dev/null +++ b/vendor/cbrjs/css/idevice.css @@ -0,0 +1,4 @@ +.view { + overflow-y: scroll; + -webkit-overflow-scrolling: touch; +} diff --git a/vendor/cbrjs/img/loading.gif b/vendor/cbrjs/img/loading.gif new file mode 100644 index 0000000..95350aa Binary files /dev/null and b/vendor/cbrjs/img/loading.gif differ diff --git a/vendor/epubjs/css/annotations.css b/vendor/epubjs/css/annotations.css new file mode 100644 index 0000000..ae74f91 --- /dev/null +++ b/vendor/epubjs/css/annotations.css @@ -0,0 +1,8 @@ +.ui-loader { + display: none; +} + +.annotator-hl { + box-shadow: none !important; + cursor: pointer !important; +} \ No newline at end of file diff --git a/vendor/epubjs/css/font/fontello.eot b/vendor/epubjs/css/font/fontello.eot new file mode 100644 index 0000000..f63ffa0 Binary files /dev/null and b/vendor/epubjs/css/font/fontello.eot differ diff --git a/vendor/epubjs/css/font/fontello.svg b/vendor/epubjs/css/font/fontello.svg new file mode 100644 index 0000000..2db1398 --- /dev/null +++ b/vendor/epubjs/css/font/fontello.svg @@ -0,0 +1,33 @@ + + + +Copyright (C) 2013 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/epubjs/css/font/fontello.ttf b/vendor/epubjs/css/font/fontello.ttf new file mode 100644 index 0000000..95715f8 Binary files /dev/null and b/vendor/epubjs/css/font/fontello.ttf differ diff --git a/vendor/epubjs/css/font/fontello.woff b/vendor/epubjs/css/font/fontello.woff new file mode 100644 index 0000000..084f0c5 Binary files /dev/null and b/vendor/epubjs/css/font/fontello.woff differ diff --git a/vendor/epubjs/css/idevice.css b/vendor/epubjs/css/idevice.css new file mode 100644 index 0000000..680c7f8 --- /dev/null +++ b/vendor/epubjs/css/idevice.css @@ -0,0 +1,14 @@ +.notes, +.search-results, +.view { + overflow-y: scroll; + -webkit-overflow-scrolling: touch; +} + +.search-results { + height: calc(100% - 5em); +} + +.notes { + height: calc(100% - 9em); +} diff --git a/vendor/epubjs/css/main.css b/vendor/epubjs/css/main.css new file mode 100644 index 0000000..eae2183 --- /dev/null +++ b/vendor/epubjs/css/main.css @@ -0,0 +1,347 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +fieldset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} + +body { + background: #4e4e4e; + overflow: hidden; + font-style: +} + +#main { + position: absolute; + width: 100%; + height: 100%; + right: 0; + border-radius: 5px; + background: #fff; + overflow: hidden; + -webkit-transition: -webkit-transform .4s, width .2s; + -moz-transition: -webkit-transform .4s, width .2s; + + -moz-box-shadow: inset 0 0 50px rgba(0,0,0,.1); + -webkit-box-shadow: inset 0 0 50px rgba(0,0,0,.1); + box-shadow: inset 0 0 50px rgba(0,0,0,.1); +} + +#titlebar { + padding: 0.5em; + color: #4f4f4f; + font-weight: 100; + font-family: Georgia, "Times New Roman", Times, serif; + opacity: .5; + text-align: center; + -webkit-transition: opacity .5s; + -moz-transition: opacity .5s; + z-index: 10; + position: fixed; + width: 100%; +} + +#titlebar:hover { + opacity: 1; +} + +#titlebar a { + width: 1em; + height: 1em; + overflow: hidden; + display: inline-block; + opacity: .5; + padding: 0.2em; + border-radius: 0.2em; + border: 1px rgba(0,0,0,0) solid; +} + +#titlebar a::before { + visibility: visible; +} + +#titlebar a:hover { + opacity: .8; + border: 1px rgba(0,0,0,.2) solid; +} + +#titlebar a:active { + opacity: 1; + color: rgba(0,0,0,.6); + box-shadow: inset 0 0 6px rgba(155,155,155,.8); +} + +#book-title { + font-weight: 600; +} + +#title-separator { + display: none; +} + +#title-controls, +#opener { + margin-left: 0.3em; + margin-right: 0.3em; + position: fixed; + top: 0.3em; +} + +#opener { + left: 0; +} + +#metainfo { + position: fixed; + /* width: 80%; + left: 10%; */ + width: 50%; + width: calc(100% - 7.5em); + left: 1.5em; + top: 0.3em; + padding: 0.2em; + height: 1em; + overflow: hidden; + text-align: center; +} + +#title-controls { + right: 0; +} + +#viewer { + width: 80%; + height: 80%; + top: 10%; + margin: auto; + max-width: 72em; + z-index: 2; + position: relative; + overflow: hidden; +} + +#viewer iframe { + border: none; +} + +#prev, #next { + position: absolute; + top: 10%; + height: 80%; + margin: 0; +} + +.touch_nav { + width: 35%; +} + +.arrow div { + display: table-cell; + vertical-align: middle; +} + + +#prev { + left: 0; + padding-left: 2em; +} + +#next { + right: 0; + padding-right: 2em; + text-align: right; +} + +.arrow { + font-size: 64px; + /* color: #E2E2E2; */ + font-family: arial, sans-serif; + font-weight: bold; + cursor: pointer; + display: table; + z-index: 3; + opacity: 0.2; +} + +.arrow:hover { + /* color: #777; */ + opacity: 0.5; +} + +.arrow:active, +.arrow.active { + color: #000; +} + +#main.closed { + transform: translate(25em, 0); +} + +#main.single { + width: calc(100% - 25em); +} + +#divider { + position: absolute; + width: 1px; + border-right: 1px #000 solid; + height: 80%; + z-index: 100; + left: 50%; + margin-left: -1px; + top: 10%; + opacity: .15; + box-shadow: -2px 0 15px rgba(0, 0, 0, 1); + display: none; +} + +#divider.show { + display: block; +} + +#loader { + position: absolute; + z-index: 10; + left: 50%; + top: 50%; + margin: -33px 0 0 -33px; +} + +.pull-left { + float: left; +} + +.pull-right { + float: right; +} + +.highlight { + background-color: yellow; +} + +.overlay { + position: fixed; + width: 100%; + height: 100%; + visibility: hidden; + top: 0; + left: 0; + z-index: 1000; + opacity: 0; + background: rgba(255,255,255,0.8); + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; +} + +.hide { + display: none !important; +} + +.translucent { + opacity: 0 !important; +} + +@media only screen and (max-width: 1040px) { +/* + #viewer{ + width: 50%; + margin-left: 25%; + } + */ + #divider, + #divider.show { + display: none; + } +} + +@media only screen and (max-width: 900px) { +/* + #viewer{ + width: 60%; + margin-left: 20%; + } +*/ + #prev { + padding-left: 20px; + } + + #next { + padding-right: 20px; + } +} + +@media only screen and (max-width: 550px) { +/* + #viewer{ + width: 80%; + margin-left: 10%; + } + */ + #viewer { + width: 95%; + } + + #prev { + padding-left: 0; + } + + #next { + padding-right: 0; + } + + .arrow div { + text-indent: 100%; + white-space: nowrap; + overflow: hidden; + } + + /* + #main { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -webkit-transition: -webkit-transform .3s; + -moz-transition: -moz-transform .3s; + } + + #main.closed { + -webkit-transform: translate(260px, 0); + -moz-transform: translate(260px, 0); + } + */ + #titlebar { + /* font-size: 16px; */ + /* margin: 0 50px 0 50px; */ + } + + #metainfo span { + font-size: 0.5em; + } + /* + #tocView { + width: 260px; + } + + #tocView li { + font-size: 12px; + } + + #tocView > ul{ + padding-left: 10px; + -webkit-padding-start:; + } + */ +} + diff --git a/vendor/epubjs/css/normalize.css b/vendor/epubjs/css/normalize.css new file mode 100644 index 0000000..c3e014d --- /dev/null +++ b/vendor/epubjs/css/normalize.css @@ -0,0 +1,505 @@ +/*! normalize.css v1.0.1 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/* + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/* + * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3, + * and Safari 4. + * Known issue: no IE 6 support. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* + * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using + * `em` units. + * 2. Prevents iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-size: 100%; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -ms-text-size-adjust: 100%; /* 2 */ +} + +/* + * Addresses `font-family` inconsistency between `textarea` and other form + * elements. + */ + +html, +button, +input, +select, +textarea { + font-family: sans-serif; +} + +/* + * Addresses margins handled incorrectly in IE 6/7. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/* + * Addresses `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/* + * Addresses font sizes and margins set differently in IE 6/7. + * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5, + * and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.75em; + margin: 2.33em 0; +} + +/* + * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/* + * Addresses styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE 6/7/8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/* + * Addresses margins set differently in IE 6/7. + */ + +p, +pre { + margin: 1em 0; +} + +/* + * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * Addresses CSS quotes not supported in IE 6/7. + */ + +q { + quotes: none; +} + +/* + * Addresses `quotes` property not supported in Safari 4. + */ + +q:before, +q:after { + content: ''; + content: none; +} + +/* + * Addresses inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/* + * Prevents `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Lists + ========================================================================== */ + +/* + * Addresses margins set differently in IE 6/7. + */ + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +/* + * Addresses paddings set differently in IE 6/7. + */ + +menu, +ol, +ul { + padding: 0 0 0 40px; +} + +/* + * Corrects list images handled incorrectly in IE 7. + */ + +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/* + * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3. + * 2. Improves image quality when scaled in IE 7. + */ + +img { + border: 0; /* 1 */ + -ms-interpolation-mode: bicubic; /* 2 */ +} + +/* + * Corrects overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/* + * Corrects margin displayed oddly in IE 6/7. + */ + +form { + margin: 0; +} + +/* + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE 6/7/8/9. + * 2. Corrects text not wrapping in Firefox 3. + * 3. Corrects alignment displayed oddly in IE 6/7. + */ + +legend { + border: 0; /* 1 */ + padding: 0; + white-space: normal; /* 2 */ + *margin-left: -7px; /* 3 */ +} + +/* + * 1. Corrects font size not being inherited in all browsers. + * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, + * and Chrome. + * 3. Improves appearance and consistency in all browsers. + */ + +button, +input, +select, +textarea { + font-size: 100%; /* 1 */ + margin: 0; /* 2 */ + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ +} + +/* + * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/* + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Corrects inability to style clickable `input` types in iOS. + * 3. Improves usability and consistency of cursor style between image-type + * `input` and others. + * 4. Removes inner spacing in IE 7 without affecting normal text inputs. + * Known issue: inner spacing remains in IE 6. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ + *overflow: visible; /* 4 */ +} + +/* + * Re-set default cursor for disabled elements. + */ + +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to content-box in IE 8/9. + * 2. Removes excess padding in IE 8/9. + * 3. Removes excess padding in IE 7. + * Known issue: excess padding remains in IE 6. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + *height: 13px; /* 3 */ + *width: 13px; /* 3 */ +} + +/* + * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ +/* +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +*/ + +/* + * Removes inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +/* input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} */ + +/* + * Removes inner padding and border in Firefox 3+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE 6/7/8/9. + * 2. Improves readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/vendor/epubjs/css/popup.css b/vendor/epubjs/css/popup.css new file mode 100644 index 0000000..65c2f9b --- /dev/null +++ b/vendor/epubjs/css/popup.css @@ -0,0 +1,109 @@ +/* http://davidwalsh.name/css-tooltips */ +/* base CSS element */ +.popup { + background: #eee; + border: 1px solid #ccc; + padding: 10px; + border-radius: 8px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + position: fixed; + max-width: 300px; + font-size: 12px; + + display: none; + margin-left: 2px; + + margin-top: 30px; +} + +.popup.above { + margin-top: -10px; +} + +.popup.left { + margin-left: -20px; +} + +.popup.right { + margin-left: 40px; +} + +.pop_content { + max-height: 225px; + overflow-y: auto; +} + +.pop_content > p { + margin-top: 0; +} + +/* below */ +.popup:before { + position: absolute; + display: inline-block; + border-bottom: 10px solid #eee; + border-right: 10px solid transparent; + border-left: 10px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + left: 50%; + top: -10px; + margin-left: -6px; + content: ''; +} + +.popup:after { + position: absolute; + display: inline-block; + border-bottom: 9px solid #eee; + border-right: 9px solid transparent; + border-left: 9px solid transparent; + left: 50%; + top: -9px; + margin-left: -5px; + content: ''; +} + +/* above */ +.popup.above:before { + border-bottom: none; + border-top: 10px solid #eee; + border-top-color: rgba(0, 0, 0, 0.2); + top: 100%; +} + +.popup.above:after { + border-bottom: none; + border-top: 9px solid #eee; + top: 100%; +} + +.popup.left:before, +.popup.left:after +{ + left: 20px; +} + +.popup.right:before, +.popup.right:after +{ + left: auto; + right: 20px; +} + + +.popup.show, .popup.on { + display: block; +} + +.note-marker, +.EPUBJS-CFI-MARKER, +.EPUBJS-CFI-SPLIT { + vertical-align: super; + font-size: 0.75em; + line-height: 1em; + padding: 0.1em; + background-color: #fffa96; + border-radius: 5px; + cursor: pointer; +} + diff --git a/vendor/epubjs/css/sidebar.css b/vendor/epubjs/css/sidebar.css new file mode 100644 index 0000000..2fad6c0 --- /dev/null +++ b/vendor/epubjs/css/sidebar.css @@ -0,0 +1,494 @@ +/* sidebar */ + +.sidebar.open { + box-shadow: 3px 0px 3px 0px rgba(0, 0, 0, 0.4); + display: block; +} + +.sidebar { + background: #6b6b6b; + position: fixed; + top: 0; + min-width: 25em; + height: 100%; + overflow: hidden; + display: none; + z-index: 100; +} + +.sidebar.wide { + width: 20%; +} + +.toolbar, +.panels { + position: absolute; + width: 100%; + height: 2em; + background: #4e4e4e; + /* above titlebar controls */ + z-index: 3; +} + +.toolbar { + text-align: center; +} + +.toolbar .metainfo { + font-size: 1.2em; + top: 0.5em; +} + +.toolbar div { + display: inline-block; + position: relative; +} + +.toolbar .separator { + /* border: solid 1px; */ + height: 1em; + opacity: 0.5; +} + +.toolbar button, .sidebar button { + color: white; + border: none; + background-color: transparent; + padding: 0; +} + +.sidebar div > button { + /* font-size: 1.5em; */ + /* font-size: 1em; */ + padding: 0.4em; + margin: 0; +} + +.mobile .sidebar div > button { + padding: 0.5em; + margin: 0; +} + +.sidebar div > button:hover { + color: #8CC746; +} + +.panels .open { + background-color: #6B6B6B; +} + +.view.open { + display: block !important; +} + +.view { + overflow-x: hidden; + display: none !important; + width: 100%; + position: absolute; + top: 2em; + bottom: 0; +} + +.list_item a { + color: #AAA; + text-decoration: none; +} + +.list_item a.chapter { + font-size: 1em; +} + +.list_item a.section { + font-size: .8em; +} + +.list_item.currentChapter > a, +.list_item a:hover { + color: #f1f1f1 +} + +.list_item a:hover { + color: #E2E2E2; +} + +.list_item ul { + display: none; +} + +.list_item.currentChapter > ul, +.list_item.openChapter > ul { + display: block; +} + +legend { + margin-left: 1em; + padding: 0.5em; + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); + } + +.settings-container { + text-align: left; + margin: 1em; + background: #F8F8F8; + color: #111; + border-radius: 4px; + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.settings-container > legend { + position: relative; + top: 1em; + margin-bottom: 1em; + text-align: center; + font-weight: 600; + box-shadow: none; +} + +.settings-container label { + margin-right: 1em; +} + +.center-box { + text-align: center; + margin: 1em; +} + +.font_example { + margin: 1em; + text-align: center; + box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.font_example div { + padding: 1em; +} + +.view .control-group input[type=range] { + width: 90%; + float: right; + margin: 0; +} + +.view .control-group { + padding: 1em; +} + +.view .sliders { + font-size: 1.5em; +} + +.view .control-group span { + float: left; + margin: 0 2px; + clear: both; +} + +.view .control-group input[type=reset] { + float: right; +} +.metadata { + padding: 1em; + margin: 1em; +} + +.metadata table { + font-size: 1.2em; + color: #F8F8F8; +} + +.metadata td:nth-child(1) { + font-weight: bold; + padding-right: 1em; +} + +/* panels */ +.panels a { + visibility: hidden; + overflow: hidden; + display: inline-block; + color: #ccc; +} + +.panels a::before { + visibility: visible; +} + +.panels a:hover { + color: #AAA; +} + +.panels a:active { + color: #AAA; + margin: 1px 0 -1px 6px; +} + +.panels a.active, +.panels a.active:hover { + color: #AAA; +} + +/* END panels */ + +/* TOC (and search, and bookmarks) */ +.toc_toggle { + display: inline-block; + width: 14px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.toc_toggle:before { + content: '▸'; + color: #fff; + margin-right: -4px; +} + +.currentChapter > .toc_toggle:before, +.openChapter > .toc_toggle:before { + content: '▾'; +} + +#toc-populate.open { + display: inline-block !important; + background-color: #4e4e4e; +} + +.toc-view li, +.bookmarks-view li, +.search-view li { + margin: 1em; + font-family: Georgia, "Times New Roman", Times, serif; + list-style: none; +} + +.toc-view li { + text-transform: capitalize; +} + +.toc-vew.hidden { + display: none; +} + +.bookmark_link_text { + float: right; + font-size: 75%; + margin: 1em; + color: #AAA; +} + +/* END TOC */ + + +/* search */ + +.search-view, +.notes-view { + overflow: hidden; +} + +.search-results { + overflow-y: scroll; + height: 95%; + height: calc(100vh - 5em); +} + +.search-input { + padding-top: 1em; + padding-bottom: 1em; + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.searchbox { + width: 80%; + float: left; + margin-left: 1em; +} + +.searchbox + span { + position: relative; + left: -1em; + visibility: hidden; + font-weight: bold; + font-family: sans-serif; + cursor: pointer; +} + +.searchbox + span:hover { + color: red; +} + +#clear_search { + padding: 0; + padding-right: 0.5em; +} + +/* END search */ + +/* notes */ + +.notes-input { + padding-top: 1em; + padding-bottom: 2em; + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.notes { + overflow-y: scroll; + height: 95%; + height: calc(100vh - 11em); +} + +.notes li, +.bookmarks li { + margin: 1em 1em 1em 0.5em; + padding: 0.25em 0 0 0.5em; + font-family: Georgia, "Times New Roman", Times, serif; + /* border-top: 1px #eee dotted; */ + color: #ccc; + padding-bottom: 0.5em; + border-left: 0.5em solid transparent; +} + +.notes li:hover, +.bookmarks li:hover { + border-left: 0.5em solid #eee; +} + +.notes li:hover span, +.notes li:hover a, +.bookmarks li:hover span, +.bookmarks li:hover a { + color: #fff; +} + +.item-date { + text-align: right; + font-size: 0.8em; + padding-top: 0.5em; + color: #bbb; +} + +.item-delete, +.item-edit, +.item-save, +.item-cancel, +.note-link { + color: #999; + display: inline-block; + text-decoration: none; + margin-right: 0.5em; + margin-top: 0.5em; + cursor: pointer; +} + +.item-save, +.item-cancel { + background-color: #ccc; + padding: 0.2em 0.4em; + border: 1px solid #ccc; + border-radius: 0.2em; +} + +.item-save:hover, +.item-cancel:hover { + background-color: #eee; + border: 1px solid white; +} + +.item-save { + color: green !important; +} + +.item-cancel { + color: red !important; +} + +.note-link { + float: right; + margin-right: 0; +} + +.note-delete, +.note-edit { + float: left; +} + +div.editable { + background-color: #EEE; + color: #111; + /* border: 1px dotted white; */ + border-radius: 0.2em; + padding: 0.25em; + box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.4); +} + +.notes li a:hover, +.bookmarks li a:hover { + text-decoration: underline; +} + +.notes li img { +} + +.note-text { + text-align: left; + display: block; + width: calc(100% - 2em); + height: 6em; + margin-left: 1em; + margin-right: 1em; + margin-bottom: 1em; + border-radius: 4px; +} + +.note-text[disabled], #note-text[disabled="disabled"]{ + opacity: .5; +} + +.note-anchor { + font-size: 1.5em; + padding: 0 !important; + margin-right: 0.5em !important; +} + +.note-marker { + vertical-align: super; + font-size: 0.75em; + line-height: 1em; + padding: 0.1em; + background-color: #fffa96; + border-radius: 5px; + cursor: pointer; +} + +/* END notes */ + +/* margins */ + +.margin-icon { + float: none !important; + font-size: 3em; + vertical-align: middle; +} + +/* media-specific rules */ + +@media only screen and (max-width: 25em) { + + .sidebar { + min-width: 10em; + width: 80%; + } + + #main.single { + width: 100% !important; + } +} + + +/* END sidebar */ + + diff --git a/vendor/epubjs/epub.main.css b/vendor/epubjs/epub.main.css new file mode 100644 index 0000000..3ce2b1a --- /dev/null +++ b/vendor/epubjs/epub.main.css @@ -0,0 +1,778 @@ +@font-face { + font-family: 'fontello'; + src: url('font/fontello.eot?60518104'); + src: url('font/fontello.eot?60518104#iefix') format('embedded-opentype'), + url('font/fontello.woff?60518104') format('woff'), + url('font/fontello.ttf?60518104') format('truetype'), + url('font/fontello.svg?60518104#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} + +body { + background: #4e4e4e; + overflow: hidden; +} + +#main { + /* height: 500px; */ + position: absolute; + width: 100%; + height: 100%; + right: 0; + /* left: 40px; */ +/* -webkit-transform: translate(40px, 0); + -moz-transform: translate(40px, 0); */ + + /* border-radius: 5px 0px 0px 5px; */ + border-radius: 5px; + background: #fff; + overflow: hidden; + -webkit-transition: -webkit-transform .4s, width .2s; + -moz-transition: -webkit-transform .4s, width .2s; + + -moz-box-shadow: inset 0 0 50px rgba(0,0,0,.1); + -webkit-box-shadow: inset 0 0 50px rgba(0,0,0,.1); + box-shadow: inset 0 0 50px rgba(0,0,0,.1); +} + + +#titlebar { + height: 8%; + min-height: 20px; + padding: 10px; + /* margin: 0 50px 0 50px; */ + position: relative; + color: #4f4f4f; + font-weight: 100; + font-family: Georgia, "Times New Roman", Times, serif; + opacity: .5; + text-align: center; + -webkit-transition: opacity .5s; + -moz-transition: opacity .5s; + z-index: 10; +} + +#titlebar:hover { + opacity: 1; +} + +#titlebar a { + width: 18px; + height: 19px; + line-height: 20px; + overflow: hidden; + display: inline-block; + opacity: .5; + padding: 4px; + border-radius: 4px; +} + +#titlebar a::before { + visibility: visible; +} + +#titlebar a:hover { + opacity: .8; + border: 1px rgba(0,0,0,.2) solid; + padding: 3px; +} + +#titlebar a:active { + opacity: 1; + color: rgba(0,0,0,.6); + /* margin: 1px -1px -1px 1px; */ + -moz-box-shadow: inset 0 0 6px rgba(155,155,155,.8); + -webkit-box-shadow: inset 0 0 6px rgba(155,155,155,.8); + box-shadow: inset 0 0 6px rgba(155,155,155,.8); +} + +#book-title { + font-weight: 600; +} + +#title-seperator { + display: none; +} + +#viewer { + width: 80%; + height: 80%; + /* margin-left: 10%; */ + margin: 0 auto; + max-width: 1250px; + z-index: 2; + position: relative; + overflow: hidden; +} + +#viewer iframe { + border: none; +} + +#prev, #next { + position: absolute; + top: 10%; + height: 85%; + margin: 0; +} + +.touch_nav { + width: 35%; +} + +.arrow div { + display: table-cell; + vertical-align: middle; +} + + +#prev { + left: 0; + padding-left: 40px; +} + +#next { + right: 0; + padding-right: 40px; + text-align: right; +} + +.arrow { + /* position: relative; + top: 50%; + margin-top: -32px; */ + font-size: 64px; + color: #E2E2E2; + font-family: arial, sans-serif; + font-weight: bold; + cursor: pointer; + display: table; + z-index: 3; +} + +.arrow:hover { + color: #777; +} + +.arrow:active, +.arrow.active { + color: #000; +} + +#sidebar { + background: #6b6b6b; + position: absolute; + /* left: -260px; */ + /* -webkit-transform: translate(-260px, 0); + -moz-transform: translate(-260px, 0); */ + top: 0; + min-width: 300px; + width: 25%; + height: 100%; + -webkit-transition: -webkit-transform .5s; + -moz-transition: -moz-transform .5s; + + overflow: hidden; +} + +#sidebar.open { + /* left: 0; */ + /* -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); */ +} + +#main.closed { + /* left: 300px; */ + -webkit-transform: translate(300px, 0); + -moz-transform: translate(300px, 0); +} + +#main.single { + width: 75%; +} + +#main.single #viewer { + /* width: 60%; + margin-left: 20%; */ +} + +#panels { + background: #4e4e4e; + position: absolute; + left: 0; + top: 0; + width: 100%; + padding: 13px 0; + height: 14px; + -moz-box-shadow: 0px 1px 3px rgba(0,0,0,.6); + -webkit-box-shadow: 0px 1px 3px rgba(0,0,0,.6); + box-shadow: 0px 1px 3px rgba(0,0,0,.6); +} + +#opener { + /* padding: 10px 10px; */ + float: left; +} + +/* #opener #slider { + width: 25px; +} */ + +#metainfo { + display: inline-block; + text-align: center; + max-width: 80%; +} + +#title-controls { + float: right; +} + +#panels a { + visibility: hidden; + width: 18px; + height: 20px; + overflow: hidden; + display: inline-block; + color: #ccc; + margin-left: 6px; +} + +#panels a::before { + visibility: visible; +} + +#panels a:hover { + color: #AAA; +} + +#panels a:active { + color: #AAA; + margin: 1px 0 -1px 6px; +} + +#panels a.active, +#panels a.active:hover { + color: #AAA; +} + +.searchbox { + position: inline-block; + width: 50%; + float: left; + margin-left: 1em; +} + +.searchbox + span { + position: relative; + left: -1em; + visibility: hidden; + font-weight: bold; +} + +input::-webkit-input-placeholder { + color: #454545; +} +input:-moz-placeholder { + color: #454545; +} + +#divider { + position: absolute; + width: 1px; + border-right: 1px #000 solid; + height: 80%; + z-index: 1; + left: 50%; + margin-left: -1px; + top: 10%; + opacity: .15; + box-shadow: -2px 0 15px rgba(0, 0, 0, 1); + display: none; +} + +#divider.show { + display: block; +} + +#loader { + position: absolute; + z-index: 10; + left: 50%; + top: 50%; + margin: -33px 0 0 -33px; +} + +#tocView, +#bookmarksView { + overflow-x: hidden; + overflow-y: hidden; + min-width: 300px; + width: 25%; + height: 100%; + visibility: hidden; + -webkit-transition: visibility 0 ease .5s; + -moz-transition: visibility 0 ease .5s; +} + + + +#sidebar.open #tocView, +#sidebar.open #bookmarksView { + overflow-y: auto; + visibility: visible; + -webkit-transition: visibility 0 ease 0; + -moz-transition: visibility 0 ease 0; +} + +#sidebar.open #tocView { + display: block; +} + +#tocView > ul, +#bookmarksView > ul { + margin-top: 15px; + margin-bottom: 50px; + padding-left: 20px; + display: block; +} + +#tocView li, +#bookmarksView li { + margin-bottom:10px; + width: 225px; + font-family: Georgia, "Times New Roman", Times, serif; + list-style: none; + text-transform: capitalize; +} + +#tocView li:active, +#tocView li.currentChapter +{ + list-style: none; +} + +.list_item a { + color: #AAA; + text-decoration: none; +} + +.list_item a.chapter { + font-size: 1em; +} + +.list_item a.section { + font-size: .8em; +} + +.list_item.currentChapter > a, +.list_item a:hover { + color: #f1f1f1 +} + +/* #tocView li.openChapter > a, */ +.list_item a:hover { + color: #E2E2E2; +} + +.list_item ul { + padding-left:10px; + margin-top: 8px; + display: none; +} + +.list_item.currentChapter > ul, +.list_item.openChapter > ul { + display: block; +} + +#tocView.hidden { + display: none; +} + +.toc_toggle { + display: inline-block; + width: 14px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.toc_toggle:before { + content: '▸'; + color: #fff; + margin-right: -4px; +} + +.currentChapter > .toc_toggle:before, +.openChapter > .toc_toggle:before { + content: '▾'; +} + +.view { + width: 300px; + height: 100%; + display: none; + padding-top: 50px; + overflow-y: auto; +} + +#searchResults { + margin-bottom: 50px; + padding-left: 20px; + display: block; +} + +#searchResults li { + margin-bottom:10px; + width: 225px; + font-family: Georgia, "Times New Roman", Times, serif; + list-style: none; +} + +#searchResults a { + color: #AAA; + text-decoration: none; +} + +#searchResults p { + text-decoration: none; + font-size: 12px; + line-height: 16px; +} + +#searchResults p .match { + background: #ccc; + color: #000; +} + +#searchResults li > p { + color: #AAA; +} + +#searchResults li a:hover { + color: #E2E2E2; +} + +#searchView.shown { + display: block; + overflow-y: scroll; +} + +#notes { + padding: 0 0 0 34px; +} + +#notes li { + color: #eee; + font-size: 12px; + width: 240px; + border-top: 1px #fff solid; + padding-top: 6px; + margin-bottom: 6px; +} + +#notes li a { + color: #fff; + display: inline-block; + margin-left: 6px; +} + +#notes li a:hover { + text-decoration: underline; +} + +#notes li img { + max-width: 240px; +} + +#note-text { + display: block; + width: 260px; + height: 80px; + margin: 0 auto; + padding: 5px; + border-radius: 5px; +} + +#note-text[disabled], #note-text[disabled="disabled"]{ + opacity: .5; +} + +#note-anchor { + margin-left: 218px; + margin-top: 5px; +} + +#settingsPanel { + display:none; +} + +#settingsPanel h3 { + color:#f1f1f1; + font-family:Georgia, "Times New Roman", Times, serif; + margin-bottom:10px; +} + +#settingsPanel ul { + margin-top:60px; + list-style-type:none; +} + +#settingsPanel li { + font-size:1em; + color:#f1f1f1; +} + +#settingsPanel .xsmall { font-size:x-small; } +#settingsPanel .small { font-size:small; } +#settingsPanel .medium { font-size:medium; } +#settingsPanel .large { font-size:large; } +#settingsPanel .xlarge { font-size:x-large; } + +.highlight { background-color: yellow } + +.modal { + position: fixed; + top: 50%; + left: 50%; + width: 50%; + width: 630px; + + height: auto; + z-index: 2000; + visibility: hidden; + margin-left: -320px; + margin-top: -160px; + +} + +.overlay { + position: fixed; + width: 100%; + height: 100%; + visibility: hidden; + top: 0; + left: 0; + z-index: 1000; + opacity: 0; + background: rgba(255,255,255,0.8); + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; +} + +.md-show { + visibility: visible; +} + +.md-show ~ .overlay { + opacity: 1; + visibility: visible; +} + +/* Content styles */ +.md-content { + color: #fff; + background: #6b6b6b; + position: relative; + border-radius: 3px; + margin: 0 auto; + height: 320px; +} + +.md-content h3 { + margin: 0; + padding: 6px; + text-align: center; + font-size: 22px; + font-weight: 300; + opacity: 0.8; + background: rgba(0,0,0,0.1); + border-radius: 3px 3px 0 0; +} + +.md-content > div { + padding: 15px 40px 30px; + margin: 0; + font-weight: 300; + font-size: 14px; +} + +.md-content > div p { + margin: 0; + padding: 10px 0; +} + +.md-content > div ul { + margin: 0; + padding: 0 0 30px 20px; +} + +.md-content > div ul li { + padding: 5px 0; +} + +.md-content button { + display: block; + margin: 0 auto; + font-size: 0.8em; +} + +/* Effect 1: Fade in and scale up */ +.md-effect-1 .md-content { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + opacity: 0; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; +} + +.md-show.md-effect-1 .md-content { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; +} + +.md-content > .closer { + font-size: 18px; + position: absolute; + right: 0; + top: 0; + font-size: 24px; + padding: 4px; +} + +@media only screen and (max-width: 1040px) { + #viewer{ + width: 50%; + margin-left: 25%; + } + + #divider, + #divider.show { + display: none; + } +} + +@media only screen and (max-width: 900px) { + #viewer{ + width: 60%; + margin-left: 20%; + } + + #prev { + padding-left: 20px; + } + + #next { + padding-right: 20px; + } +} + +@media only screen and (max-width: 550px) { + #viewer{ + width: 80%; + margin-left: 10%; + } + + #prev { + padding-left: 0; + } + + #next { + padding-right: 0; + } + + .arrow div { + text-indent: 100%; + white-space: nowrap; + overflow: hidden; + } + + #main { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -webkit-transition: -webkit-transform .3s; + -moz-transition: -moz-transform .3s; + } + + #main.closed { + -webkit-transform: translate(260px, 0); + -moz-transform: translate(260px, 0); + } + + #titlebar { + /* font-size: 16px; */ + /* margin: 0 50px 0 50px; */ + } + + #metainfo { + font-size: 10px; + } + + #tocView { + width: 260px; + } + + #tocView li { + font-size: 12px; + } + + #tocView > ul{ + padding-left: 10px; + webkit-padding-start:; + } +} + +[class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* you can be more comfortable with increased icons size */ + font-size: 112%; +} + + +.icon-search:before { content: '\e807'; } /* '' */ +.icon-resize-full-1:before { content: '\e804'; } /* '' */ +.icon-cancel-circled2:before { content: '\e80f'; } /* '' */ +.icon-link:before { content: '\e80d'; } /* '' */ +.icon-bookmark:before { content: '\e805'; } /* '' */ +.icon-bookmark-empty:before { content: '\e806'; } /* '' */ +.icon-download-cloud:before { content: '\e811'; } /* '' */ +.icon-edit:before { content: '\e814'; } /* '' */ +.icon-menu:before { content: '\e802'; } /* '' */ +.icon-cog:before { content: '\e813'; } /* '' */ +.icon-resize-full:before { content: '\e812'; } /* '' */ +.icon-cancel-circled:before { content: '\e80e'; } /* '' */ +.icon-up-dir:before { content: '\e80c'; } /* '' */ +.icon-right-dir:before { content: '\e80b'; } /* '' */ +.icon-angle-right:before { content: '\e809'; } /* '' */ +.icon-angle-down:before { content: '\e80a'; } /* '' */ +.icon-right:before { content: '\e815'; } /* '' */ +.icon-list-1:before { content: '\e803'; } /* '' */ +.icon-list-numbered:before { content: '\e801'; } /* '' */ +.icon-columns:before { content: '\e810'; } /* '' */ +.icon-list:before { content: '\e800'; } /* '' */ +.icon-resize-small:before { content: '\e808'; } /* '' */ diff --git a/vendor/epubjs/epub.min.js b/vendor/epubjs/epub.min.js new file mode 100644 index 0000000..b482450 --- /dev/null +++ b/vendor/epubjs/epub.min.js @@ -0,0 +1,11 @@ +/*! + * @overview RSVP - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2016 Yehuda Katz, Tom Dale, Stefan Penner and contributors + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE + * @version 3.3.3 + */ +"use strict";!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):b(a.RSVP=a.RSVP||{})}(this,function(a){function b(a,b){for(var c=0,d=a.length;c1)throw new Error("Second argument not supported");if("object"!=typeof a)throw new TypeError("Argument must be an object");return h.prototype=a,new h},Aa=[],Ba=void 0,Ca=1,Da=2,Ea=new y,Fa=new y;E.prototype._validateInput=function(a){return xa(a)},E.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},E.prototype._init=function(){this._result=new Array(this.length)},E.prototype._enumerate=function(){for(var a=this.length,b=this.promise,c=this._input,d=0;b._state===Ba&&d=i)l.resolve();else{if(c&&c.cancelled)return e.remove(),this.element.removeChild(f),void l.reject(new Error("User cancelled"));h=g,b=new EPUBJS.Chapter(this.spine[h],this.store),e.displayChapter(b,this.globalLayoutProperties).then(function(a){e.pageMap.forEach(function(a){j+=1,d.push({cfi:a.start,page:j})}),e.pageMap.length%2>0&&e.spreads&&(j+=1,d.push({cfi:e.pageMap[e.pageMap.length-1].end,page:j})),setTimeout(function(){k(l)},1)})}return l.promise}.bind(this);k().then(function(){e.remove(),this.element.removeChild(f),g.resolve(d)}.bind(this),function(a){g.reject(a)});return g.promise},EPUBJS.Book.prototype.generatePagination=function(a,b,c){var d=this,e=new RSVP.defer;return this.ready.spine.promise.then(function(){d.generatePageList(a,b,c).then(function(a){d.pageList=d.contents.pageList=a,d.pagination.process(a),d.ready.pageList.resolve(d.pageList),e.resolve(d.pageList)},function(a){e.reject(a)})}),e.promise},EPUBJS.Book.prototype.loadPagination=function(a){var b;return b="string"==typeof a?JSON.parse(a):a,b&&b.length&&(this.pageList=b,this.pagination.process(this.pageList),this.ready.pageList.resolve(this.pageList)),this.pageList},EPUBJS.Book.prototype.getPageList=function(){return this.ready.pageList.promise},EPUBJS.Book.prototype.getMetadata=function(){return this.ready.metadata.promise},EPUBJS.Book.prototype.getToc=function(){return this.ready.toc.promise},EPUBJS.Book.prototype.networkListeners=function(){var a=this;window.addEventListener("offline",function(b){a.online=!1,a.settings.storage&&a.fromStorage(!0),a.trigger("book:offline")},!1),window.addEventListener("online",function(b){a.online=!0,a.settings.storage&&a.fromStorage(!1),a.trigger("book:online")},!1)},EPUBJS.Book.prototype.listenToRenderer=function(a){var b=this;a.Events.forEach(function(c){a.on(c,function(a){b.trigger(c,a)})}),a.on("renderer:visibleRangeChanged",function(a){var b,c,d,e=[];this.pageList.length>0&&(b=this.pagination.pageFromCfi(a.start),d=this.pagination.percentageFromPage(b),e.push(b),a.end&&(c=this.pagination.pageFromCfi(a.end),e.push(c)),this.trigger("book:pageChanged",{anchorPage:b,percentage:d,pageRange:e}))}.bind(this)),a.on("render:loaded",this.loadChange.bind(this))},EPUBJS.Book.prototype.loadChange=function(a){var b,c,d=EPUBJS.core.uri(a),e=EPUBJS.core.uri(this.currentChapter.absolute);d.path!=e.path?(console.warn("Miss Match",d.path,this.currentChapter.absolute),b=this.spineIndexByURL[d.filename],c=new EPUBJS.Chapter(this.spine[b],this.store),this.currentChapter=c,this.renderer.currentChapter=c,this.renderer.afterLoad(this.renderer.render.docEl),this.renderer.beforeDisplay(function(){this.renderer.afterDisplay()}.bind(this))):this._rendering||this.renderer.reformat()},EPUBJS.Book.prototype.unlistenToRenderer=function(a){a.Events.forEach(function(b){a.off(b)})},EPUBJS.Book.prototype.coverUrl=function(){var a=this.ready.cover.promise.then(function(a){return this.settings.fromStorage?this.store.getUrl(this.contents.cover):this.settings.contained?this.zip.getUrl(this.contents.cover):this.contents.cover}.bind(this));return a.then(function(a){this.cover=a}.bind(this)),a},EPUBJS.Book.prototype.loadXml=function(a){return this.settings.fromStorage?this.store.getXml(a,this.settings.encoding):this.settings.contained?this.zip.getXml(a,this.settings.encoding):EPUBJS.core.request(a,"xml",this.settings.withCredentials)},EPUBJS.Book.prototype.urlFrom=function(a){var b,c=EPUBJS.core.uri(a),d=c.protocol,e="/"==c.path[0],f=window.location,g=f.origin||f.protocol+"//"+f.host,h=document.getElementsByTagName("base");return h.length&&(b=h[0].href),c.protocol?c.origin+c.path:!d&&e?(b||g)+c.path:d||e?void 0:EPUBJS.core.resolveUrl(b||f.pathname,c.path)},EPUBJS.Book.prototype.unarchive=function(a){return this.zip=new EPUBJS.Unarchiver,this.store=this.zip,this.zip.open(a)},EPUBJS.Book.prototype.isContained=function(a){if(a instanceof ArrayBuffer)return!0;var b=EPUBJS.core.uri(a);return!(!b.extension||"epub"!=b.extension&&"zip"!=b.extension)},EPUBJS.Book.prototype.isSaved=function(a){var b;return!!localStorage&&(b=localStorage.getItem(a),!(!localStorage||null===b))},EPUBJS.Book.prototype.generateBookKey=function(a){return"epubjs:"+EPUBJS.VERSION+":"+window.location.host+":"+a},EPUBJS.Book.prototype.saveContents=function(){return!!localStorage&&void localStorage.setItem(this.settings.bookKey,JSON.stringify(this.contents))},EPUBJS.Book.prototype.removeSavedContents=function(){return!!localStorage&&void localStorage.removeItem(this.settings.bookKey)},EPUBJS.Book.prototype.renderTo=function(a){var b,c=this;if(EPUBJS.core.isElement(a))this.element=a;else{if("string"!=typeof a)return void console.error("Not an Element");this.element=EPUBJS.core.getEl(a)}return b=this.opened.then(function(){return c.renderer.initialize(c.element,c.settings.width,c.settings.height),c.metadata.direction&&c.renderer.setDirection(c.metadata.direction),c._rendered(),c.startDisplay()})},EPUBJS.Book.prototype.startDisplay=function(){var a;return a=this.settings.goto?this.goto(this.settings.goto):this.settings.previousLocationCfi?this.gotoCfi(this.settings.previousLocationCfi):this.displayChapter(this.spinePos,this.settings.displayLastPage)},EPUBJS.Book.prototype.restore=function(a){var b,c=this,d=["manifest","spine","metadata","cover","toc","spineNodeIndex","spineIndexByURL","globalLayoutProperties"],e=!1,f=this.generateBookKey(a),g=localStorage.getItem(f),h=d.length;if(this.settings.clearSaved&&(e=!0),!e&&"undefined"!=g&&null!==g)for(c.contents=JSON.parse(g),b=0;b=this.spine.length)&&(console.warn("Not A Valid Location"),f=0,b=!1,e=!1),g=new EPUBJS.Chapter(this.spine[f],this.store),this._rendering=!0,this._needsAssetReplacement()&&g.registerHook("beforeChapterRender",[EPUBJS.replace.head,EPUBJS.replace.resources,EPUBJS.replace.svg],!0),h.currentChapter=g,d=h.renderer.displayChapter(g,this.globalLayoutProperties),e?h.renderer.gotoCfi(e):b&&h.renderer.lastPage(),d.then(function(a){h.spinePos=f,i.resolve(h.renderer),h.settings.fromStorage===!1&&h.settings.contained===!1&&h.preloadNextChapter(),h._rendering=!1,h._displayQ.dequeue(),0===h._displayQ.length()&&h._gotoQ.dequeue()},function(a){console.error("Could not load Chapter: "+g.absolute,a),h.trigger("book:chapterLoadFailed",g.absolute),h._rendering=!1,i.reject(a)}),i.promise):(this._q.enqueue("displayChapter",arguments),i.reject({message:"Rendering",stack:(new Error).stack}),i.promise)},EPUBJS.Book.prototype.nextPage=function(a){var a=a||new RSVP.defer;if(!this.isRendered)return this._q.enqueue("nextPage",[a]),a.promise;var b=this.renderer.nextPage();return b?(a.resolve(!0),a.promise):this.nextChapter(a)},EPUBJS.Book.prototype.prevPage=function(a){var a=a||new RSVP.defer;if(!this.isRendered)return this._q.enqueue("prevPage",[a]),a.promise;var b=this.renderer.prevPage();return b?(a.resolve(!0),a.promise):this.prevChapter(a)},EPUBJS.Book.prototype.nextChapter=function(a){var a=a||new RSVP.defer;if(this.spinePos0){for(var b=this.spinePos-1;this.spine[b]&&this.spine[b].linear&&"no"==this.spine[b].linear;)b--;if(b>=0)return this.displayChapter(b,!0,a)}return this.trigger("book:atStart"),a.resolve(!0),a.promise},EPUBJS.Book.prototype.getCurrentLocationCfi=function(){return!!this.isRendered&&this.renderer.currentLocationCfi},EPUBJS.Book.prototype.goto=function(a){return 0===a.indexOf("epubcfi(")?this.gotoCfi(a):a.indexOf("%")===a.length-1?this.gotoPercentage(parseInt(a.substring(0,a.length-1))/100):"number"==typeof a||isNaN(a)===!1?this.gotoPage(a):this.gotoHref(a)},EPUBJS.Book.prototype.gotoCfi=function(a,b){var c,d,e,f,g,h=b||new RSVP.defer;return this.isRendered?this._moving||this._rendering?(console.warn("Renderer is moving"),this._gotoQ.enqueue("gotoCfi",[a,h]),!1):(c=new EPUBJS.EpubCFI(a),d=c.spinePos,d!=-1&&(e=this.spine[d],f=h.promise,this._moving=!0,this.currentChapter&&this.spinePos===d?(this.renderer.gotoCfi(c),this._moving=!1,h.resolve(this.renderer.currentLocationCfi)):(e&&d!=-1||(d=0,e=this.spine[d]),g=this.displayChapter(a),g.then(function(a){this._moving=!1,h.resolve(a.currentLocationCfi)}.bind(this),function(){this._moving=!1}.bind(this))),f.then(function(){this._gotoQ.dequeue()}.bind(this)),f)):(console.warn("Not yet Rendered"),this.settings.previousLocationCfi=a,!1)},EPUBJS.Book.prototype.gotoHref=function(a,b){var c,d,e,f,g,h=b||new RSVP.defer;return this.isRendered?this._moving||this._rendering?(this._gotoQ.enqueue("gotoHref",[a,h]),!1):(c=a.split("#"),d=c[0],e=c[1]||!1,f=d.search("://")==-1?d.replace(EPUBJS.core.uri(this.settings.contentsPath).path,""):d.replace(this.settings.contentsPath,""),g=this.spineIndexByURL[f],d||(g=this.currentChapter?this.currentChapter.spinePos:0),"number"==typeof g&&(this.currentChapter&&g==this.currentChapter.spinePos?(e?this.renderer.section(e):this.renderer.firstPage(),h.resolve(this.renderer.currentLocationCfi),h.promise.then(function(){this._gotoQ.dequeue()}.bind(this)),h.promise):this.displayChapter(g).then(function(){e&&this.renderer.section(e),h.resolve(this.renderer.currentLocationCfi)}.bind(this)))):(this.settings.goto=a,!1)},EPUBJS.Book.prototype.gotoPage=function(a){var b=this.pagination.cfiFromPage(a);return this.gotoCfi(b)},EPUBJS.Book.prototype.gotoPercentage=function(a){var b=this.pagination.pageFromPercentage(a);return this.gotoPage(b)},EPUBJS.Book.prototype.preloadNextChapter=function(){var a,b=this.spinePos+1;return!(b>=this.spine.length)&&(a=new EPUBJS.Chapter(this.spine[b]),void(a&&EPUBJS.core.request(a.absolute)))},EPUBJS.Book.prototype.storeOffline=function(){var a=this,b=EPUBJS.core.values(this.manifest);return this.store.put(b).then(function(){a.settings.stored=!0,a.trigger("book:stored")})},EPUBJS.Book.prototype.availableOffline=function(){return this.settings.stored>0},EPUBJS.Book.prototype.toStorage=function(){var a=this.settings.bookKey;this.store.isStored(a).then(function(b){return b===!0?(this.settings.stored=!0,!0):this.storeOffline().then(function(){this.store.token(a,!0)}.bind(this))}.bind(this))},EPUBJS.Book.prototype.fromStorage=function(a){[EPUBJS.replace.head,EPUBJS.replace.resources,EPUBJS.replace.svg];this.contained||this.settings.contained||(this.online&&this.opened.then(this.toStorage.bind(this)),this.store&&this.settings.fromStorage&&a===!1?(this.settings.fromStorage=!1,this.store.off("offline"),this.store=!1):this.settings.fromStorage||(this.store=new EPUBJS.Storage(this.settings.credentials),this.store.on("offline",function(a){a?(this.offline=!0,this.settings.fromStorage=!0,this.trigger("book:offline")):(this.offline=!1,this.settings.fromStorage=!1,this.trigger("book:online"))}.bind(this))))},EPUBJS.Book.prototype.setStyle=function(a,b,c){var d=["color","background","background-color"];return this.isRendered?(this.settings.styles[a]=b,this.renderer.setStyle(a,b,c),void(d.indexOf(a)===-1&&this.renderer.reformat())):this._q.enqueue("setStyle",arguments)},EPUBJS.Book.prototype.removeStyle=function(a){return this.isRendered?(this.renderer.removeStyle(a),this.renderer.reformat(),void delete this.settings.styles[a]):this._q.enqueue("removeStyle",arguments)},EPUBJS.Book.prototype.resetClasses=function(a){return this.isRendered?(a.constructor===String&&(a=[a]),this.settings.classes=a,this.renderer.setClasses(this.settings.classes),void this.renderer.reformat()):this._q.enqueue("setClasses",arguments)}, +EPUBJS.Book.prototype.addClass=function(a){return this.isRendered?(this.settings.classes.indexOf(a)==-1&&this.settings.classes.push(a),this.renderer.setClasses(this.settings.classes),void this.renderer.reformat()):this._q.enqueue("addClass",arguments)},EPUBJS.Book.prototype.removeClass=function(a){if(!this.isRendered)return this._q.enqueue("removeClass",arguments);var b=this.settings.classes.indexOf(a);b!=-1&&(delete this.settings.classes[b],this.renderer.setClasses(this.settings.classes),this.renderer.reformat())},EPUBJS.Book.prototype.addHeadTag=function(a,b){return this.isRendered?void(this.settings.headTags[a]=b):this._q.enqueue("addHeadTag",arguments)},EPUBJS.Book.prototype.useSpreads=function(a){console.warn("useSpreads is deprecated, use forceSingle or set a layoutOveride instead"),a===!1?this.forceSingle(!0):this.forceSingle(!1)},EPUBJS.Book.prototype.forceSingle=function(a){var b="undefined"==typeof a||a;this.renderer.forceSingle(b),this.settings.forceSingle=b,this.isRendered&&this.renderer.reformat()},EPUBJS.Book.prototype.setMinSpreadWidth=function(a){this.settings.minSpreadWidth=a,this.isRendered&&(this.renderer.setMinSpreadWidth(this.settings.minSpreadWidth),this.renderer.reformat())},EPUBJS.Book.prototype.setGap=function(a){this.settings.gap=a,this.isRendered&&(this.renderer.setGap(this.settings.gap),this.renderer.reformat())},EPUBJS.Book.prototype.chapter=function(a){var b,c,d=this.spineIndexByURL[a];return d&&(b=this.spine[d],c=new EPUBJS.Chapter(b,this.store,this.settings.withCredentials),c.load()),c},EPUBJS.Book.prototype.unload=function(){this.settings.restore&&localStorage&&this.saveContents(),this.unlistenToRenderer(this.renderer),this.trigger("book:unload")},EPUBJS.Book.prototype.destroy=function(){window.removeEventListener("beforeunload",this.unload),this.currentChapter&&this.currentChapter.unload(),this.unload(),this.renderer&&this.renderer.remove()},EPUBJS.Book.prototype._ready=function(){this.trigger("book:ready")},EPUBJS.Book.prototype._rendered=function(a){this.isRendered=!0,this.trigger("book:rendered"),this._q.flush()},EPUBJS.Book.prototype.applyStyles=function(a,b){a.applyStyles(this.settings.styles),b()},EPUBJS.Book.prototype.applyClasses=function(a,b){a.setClasses(this.settings.classes),b()},EPUBJS.Book.prototype.applyHeadTags=function(a,b){a.applyHeadTags(this.settings.headTags),b()},EPUBJS.Book.prototype._registerReplacements=function(a){a.registerHook("beforeChapterDisplay",this.applyStyles.bind(this,a),!0),a.registerHook("beforeChapterDisplay",this.applyHeadTags.bind(this,a),!0),a.registerHook("beforeChapterDisplay",this.applyClasses.bind(this,a),!0),a.registerHook("beforeChapterDisplay",EPUBJS.replace.hrefs.bind(this),!0)},EPUBJS.Book.prototype._needsAssetReplacement=function(){return!!this.settings.fromStorage||!!this.settings.contained},EPUBJS.Book.prototype.parseLayoutProperties=function(a){var b=this.settings.layoutOveride&&this.settings.layoutOveride.layout||a.layout||"reflowable",c=this.settings.layoutOveride&&this.settings.layoutOveride.spread||a.spread||"auto",d=this.settings.layoutOveride&&this.settings.layoutOveride.orientation||a.orientation||"auto";return{layout:b,spread:c,orientation:d}},RSVP.EventTarget.mixin(EPUBJS.Book.prototype),RSVP.on("error",function(a){console.error(a)}),EPUBJS.Chapter=function(a,b,c){this.href=a.href,this.absolute=a.url,this.id=a.id,this.spinePos=a.index,this.cfiBase=a.cfiBase,this.properties=a.properties,this.manifestProperties=a.manifestProperties,this.linear=a.linear,this.pages=1,this.store=b,this.credentials=c,this.epubcfi=new EPUBJS.EpubCFI,this.deferred=new RSVP.defer,this.loaded=this.deferred.promise,EPUBJS.Hooks.mixin(this),this.getHooks("beforeChapterRender"),this.caches={}},EPUBJS.Chapter.prototype.load=function(a,b){var c,d=a||this.store,e=b||this.credentials;return c=d?d.getXml(this.absolute):EPUBJS.core.request(this.absolute,!1,e),c.then(function(a){try{this.setDocument(a),this.deferred.resolve(this)}catch(a){this.deferred.reject({message:this.absolute+" -> "+a.message,stack:(new Error).stack})}}.bind(this)),c},EPUBJS.Chapter.prototype.render=function(a){return this.load().then(function(a){var b=a.querySelector("head"),c=a.createElement("base");return c.setAttribute("href",this.absolute),b.insertBefore(c,b.firstChild),this.contents=a,new RSVP.Promise(function(b,c){this.triggerHooks("beforeChapterRender",function(){b(a)}.bind(this),this)}.bind(this))}.bind(this)).then(function(a){var b=new XMLSerializer,c=b.serializeToString(a);return c}.bind(this))},EPUBJS.Chapter.prototype.url=function(a){var b,c=new RSVP.defer,d=a||this.store,e=this;return d?this.tempUrl?(b=this.tempUrl,c.resolve(b)):d.getUrl(this.absolute).then(function(a){e.tempUrl=a,c.resolve(a)}):(b=this.absolute,c.resolve(b)),c.promise},EPUBJS.Chapter.prototype.setPages=function(a){this.pages=a},EPUBJS.Chapter.prototype.getPages=function(a){return this.pages},EPUBJS.Chapter.prototype.getID=function(){return this.ID},EPUBJS.Chapter.prototype.unload=function(a){this.document=null,this.tempUrl&&a&&(a.revokeUrl(this.tempUrl),this.tempUrl=!1)},EPUBJS.Chapter.prototype.setDocument=function(a){this.document=a,this.contents=a.documentElement,!this.document.evaluate&&document.evaluate&&(this.document.evaluate=document.evaluate)},EPUBJS.Chapter.prototype.cfiFromRange=function(a){var b,c,d,e,f,g,h;if(this.document){if("undefined"!=typeof document.evaluate){if(c=EPUBJS.core.getElementXPath(a.startContainer),d=EPUBJS.core.getElementXPath(a.endContainer),e=this.document.evaluate(c,this.document,EPUBJS.core.nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue,a.collapsed||(f=this.document.evaluate(d,this.document,EPUBJS.core.nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue),b=this.document.createRange(),e)try{b.setStart(e,a.startOffset),!a.collapsed&&f&&b.setEnd(f,a.endOffset)}catch(a){console.log("missed"),e=!1}e||(console.log("not found, try fuzzy match"),g=EPUBJS.core.cleanStringForXpath(a.startContainer.textContent),c="//text()[contains(.,"+g+")]",e=this.document.evaluate(c,this.document,EPUBJS.core.nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue,e&&(b.setStart(e,a.startOffset),a.collapsed||(h=EPUBJS.core.cleanStringForXpath(a.endContainer.textContent),d="//text()[contains(.,"+h+")]",f=this.document.evaluate(d,this.document,EPUBJS.core.nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue,f&&b.setEnd(f,a.endOffset))))}else b=a;return this.epubcfi.generateCfiFromRange(b,this.cfiBase)}},EPUBJS.Chapter.prototype.find=function(a){var b=this,c=[],d=a.toLowerCase(),e=function(a){for(var e,f,g,h=a.textContent.toLowerCase(),i=b.document.createRange(),j=-1,k=150;f!=-1;)f=h.indexOf(d,j+1),f!=-1&&(i=b.document.createRange(),i.setStart(a,f),i.setEnd(a,f+d.length),e=b.cfiFromRange(i),a.textContent.lengthb?1:a0?i:i+1:0===f?i:f===-1?EPUBJS.core.locationOf(a,b,c,i,h):EPUBJS.core.locationOf(a,b,c,g,i))},EPUBJS.core.indexOfSorted=function(a,b,c,d,e){var f,g=d||0,h=e||b.length,i=parseInt(g+(h-g)/2);return c||(c=function(a,b){return a>b?1:a0;){if(c=d.shift(),"text"===c.type?(e=g.childNodes[c.index],g=e.parentNode||g):g=c.id?f.getElementById(c.id):h[c.index],!g||"undefined"==typeof g)return console.error("No Element For",c,a.str),!1;h=Array.prototype.slice.call(g.children)}return g},EPUBJS.EpubCFI.prototype.compare=function(a,b){if("string"==typeof a&&(a=new EPUBJS.EpubCFI(a)),"string"==typeof b&&(b=new EPUBJS.EpubCFI(b)),a.spinePos>b.spinePos)return 1;if(a.spinePosb.steps[c].index)return 1;if(a.steps[c].indexb.characterOffset?1:a.characterOffset")},EPUBJS.EpubCFI.prototype.generateRangeFromCfi=function(a,b){var c,d,e,f,g,h,i=b||document,j=i.createRange();return"string"==typeof a&&(a=this.parse(a)),a.spinePos!==-1&&(c=a.steps[a.steps.length-1],"undefined"!=typeof document.evaluate?(d=this.generateXpathFromSteps(a.steps),e=i.evaluate(d,i,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue):(g=this.generateQueryFromSteps(a.steps),h=i.querySelector(g),h&&"text"==c.type&&(e=h.childNodes[c.index])),e?(e&&a.characterOffset>=0?(f=e.length,a.characterOffset-1&&this.hooks[a].splice(c,1)):Array.isArray(b)&&b.forEach(function(b){c=this.hooks[a].indexOf(b),c>-1&&this.hooks[a].splice(c,1)},this))},a.prototype.triggerHooks=function(a,b,c){function d(){f--,f<=0&&b&&b()}var e,f;return"undefined"!=typeof this.hooks[a]&&(e=this.hooks[a],f=e.length,0===f&&b&&b(),void e.forEach(function(a){a(d,c)}))},{register:function(a){if(void 0===EPUBJS.hooks[a]&&(EPUBJS.hooks[a]={}),"object"!=typeof EPUBJS.hooks[a])throw"Already registered: "+a;return EPUBJS.hooks[a]},mixin:function(b){for(var c in a.prototype)b[c]=a.prototype[c]}}}(),EPUBJS.Layout=EPUBJS.Layout||{},EPUBJS.Layout.isFixedLayout=function(a){var b=a.querySelector("[name=viewport]");if(!b||!b.hasAttribute("content"))return!1;var c=b.getAttribute("content");return/,/.test(c)},EPUBJS.Layout.Reflowable=function(){this.documentElement=null,this.spreadWidth=null},EPUBJS.Layout.Reflowable.prototype.format=function(a,b,c,d){var e=EPUBJS.core.prefixed("columnAxis"),f=EPUBJS.core.prefixed("columnGap"),g=EPUBJS.core.prefixed("columnWidth"),h=EPUBJS.core.prefixed("columnFill"),i=Math.floor(b),j=Math.floor(i/8),k=d>=0?d:j%2===0?j:j-1;return this.documentElement=a,this.spreadWidth=i+k,a.style.overflow="hidden",a.style.width=i+"px",a.style.height=c+"px",a.style[e]="horizontal",a.style[h]="auto",a.style[g]=i+"px",a.style[f]=k+"px",this.colWidth=i,this.gap=k,{pageWidth:this.spreadWidth,pageHeight:c}},EPUBJS.Layout.Reflowable.prototype.calculatePages=function(){var a,b;return this.documentElement.style.width="auto",a=this.documentElement.scrollWidth,b=Math.ceil(a/this.spreadWidth),{displayedPages:b,pageCount:b}},EPUBJS.Layout.ReflowableSpreads=function(){this.documentElement=null,this.spreadWidth=null},EPUBJS.Layout.ReflowableSpreads.prototype.format=function(a,b,c,d){var e=EPUBJS.core.prefixed("columnAxis"),f=EPUBJS.core.prefixed("columnGap"),g=EPUBJS.core.prefixed("columnWidth"),h=EPUBJS.core.prefixed("columnFill"),i=2,j=Math.floor(b),k=j%2===0?j:j-1,l=Math.floor(k/8),m=d>=0?d:l%2===0?l:l-1,n=Math.floor((k-m)/i);return this.documentElement=a,this.spreadWidth=(n+m)*i,a.style.overflow="hidden",a.style.width=k+"px",a.style.height=c+"px",a.style[e]="horizontal",a.style[h]="auto",a.style[f]=m+"px",a.style[g]=n+"px",this.colWidth=n,this.gap=m,{pageWidth:this.spreadWidth,pageHeight:c}},EPUBJS.Layout.ReflowableSpreads.prototype.calculatePages=function(){var a=this.documentElement.scrollWidth,b=Math.ceil(a/this.spreadWidth);return this.documentElement.style.width=b*this.spreadWidth-this.gap+"px",{displayedPages:b,pageCount:2*b}},EPUBJS.Layout.Fixed=function(){this.documentElement=null},EPUBJS.Layout.Fixed.prototype.format=function(a,b,c,d){var e,f,g,h,i=EPUBJS.core.prefixed("columnWidth"),j=EPUBJS.core.prefixed("transform"),k=EPUBJS.core.prefixed("transformOrigin"),l=a.querySelector("[name=viewport]");this.documentElement=a,l&&l.hasAttribute("content")&&(e=l.getAttribute("content"),f=e.split(","),f[0]&&(g=f[0].replace("width=","")),f[1]&&(h=f[1].replace("height=","")));var m=b/g,n=c/h,o=m=e?g.resolve():(d=c,b=new EPUBJS.Chapter(this.spine[d],this.store,this.credentials),this.process(b).then(function(){setTimeout(function(){f(g)},1)})),g.promise}.bind(this);return"number"==typeof a&&(this.break=a),b=f().then(function(){this.total=this._locations.length-1,this._currentCfi&&(this.currentLocation=this._currentCfi),c.resolve(this._locations)}.bind(this)),c.promise},EPUBJS.Locations.prototype.process=function(a){return a.load().then(function(b){var c,d,e,f=b,g=f.documentElement.querySelector("body"),h=0;this.sprint(g,function(b){var g,i=b.length,j=0;for(0===h&&(c=f.createRange(),c.setStart(b,0)),g=this.break-h,g>i&&(h+=i,j=i);j=i?h=i-(j-this.break):(c.setEnd(b,j),e=a.cfiFromRange(c),this._locations.push(e),h=0,j+=1,c=f.createRange(),c.setStart(b,j));d=b}.bind(this)),c&&(c.setEnd(d,d.length),e=a.cfiFromRange(c),this._locations.push(e),h=0)}.bind(this))},EPUBJS.Locations.prototype.sprint=function(a,b){for(var c,d=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,null,!1);c=d.nextNode();)b(c)},EPUBJS.Locations.prototype.locationFromCfi=function(a){return 0===this._locations.length?-1:EPUBJS.core.locationOf(a,this._locations,this.epubcfi.compare)},EPUBJS.Locations.prototype.percentageFromCfi=function(a){var b=this.locationFromCfi(a);return this.percentageFromLocation(b)},EPUBJS.Locations.prototype.percentageFromLocation=function(a){return a&&this.total?a/this.total:0},EPUBJS.Locations.prototype.cfiFromLocation=function(a){var b=-1;return"number"!=typeof a&&(a=parseInt(a)),a>=0&&a1?a/100:a,c=Math.ceil(this.total*b);return this.cfiFromLocation(c)},EPUBJS.Locations.prototype.load=function(a){return this._locations=JSON.parse(a),this.total=this._locations.length-1,this._locations},EPUBJS.Locations.prototype.save=function(a){return JSON.stringify(this._locations)},EPUBJS.Locations.prototype.getCurrent=function(a){return this._current},EPUBJS.Locations.prototype.setCurrent=function(a){var b;if("string"==typeof a)this._currentCfi=a;else{if("number"!=typeof a)return;this._current=a}0!==this._locations.length&&("string"==typeof a?(b=this.locationFromCfi(a),this._current=b):b=a,this.trigger("changed",{percentage:this.percentageFromLocation(b)}))},Object.defineProperty(EPUBJS.Locations.prototype,"currentLocation",{get:function(){return this._current},set:function(a){this.setCurrent(a)}}),RSVP.EventTarget.mixin(EPUBJS.Locations.prototype),EPUBJS.Pagination=function(a){this.pages=[],this.locations=[],this.epubcfi=new EPUBJS.EpubCFI,a&&a.length&&this.process(a)},EPUBJS.Pagination.prototype.process=function(a){a.forEach(function(a){this.pages.push(a.page),this.locations.push(a.cfi)},this),this.pageList=a,this.firstPage=parseInt(this.pages[0]),this.lastPage=parseInt(this.pages[this.pages.length-1]),this.totalPages=this.lastPage-this.firstPage},EPUBJS.Pagination.prototype.pageFromCfi=function(a){var b=-1;if(0===this.locations.length)return-1;var c=EPUBJS.core.indexOfSorted(a,this.locations,this.epubcfi.compare);return c!=-1?b=this.pages[c]:(c=EPUBJS.core.locationOf(a,this.locations,this.epubcfi.compare),b=c-1>=0?this.pages[c-1]:this.pages[0],void 0!==b||(b=-1)),b},EPUBJS.Pagination.prototype.cfiFromPage=function(a){var b=-1;"number"!=typeof a&&(a=parseInt(a));var c=this.pages.indexOf(a);return c!=-1&&(b=this.locations[c]),b},EPUBJS.Pagination.prototype.pageFromPercentage=function(a){var b=Math.round(this.totalPages*a);return b},EPUBJS.Pagination.prototype.percentageFromPage=function(a){var b=(a-this.firstPage)/this.totalPages;return Math.round(1e3*b)/1e3},EPUBJS.Pagination.prototype.percentageFromCfi=function(a){var b=this.pageFromCfi(a),c=this.percentageFromPage(b);return c},EPUBJS.Parser=function(a){this.baseUrl=a||""},EPUBJS.Parser.prototype.container=function(a){var b,c,d,e;return a?(b=a.querySelector("rootfile"))?(c=b.getAttribute("full-path"), +d=EPUBJS.core.uri(c).directory,e=a.xmlEncoding,{packagePath:c,basePath:d,encoding:e}):void console.error("No RootFile Found"):void console.error("Container File Not Found")},EPUBJS.Parser.prototype.identifier=function(a){var b;return a?(b=a.querySelector("metadata"),b?this.getElementText(b,"identifier"):void console.error("No Metadata Found")):void console.error("Package File Not Found")},EPUBJS.Parser.prototype.packageContents=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=this;return b&&(this.baseUrl=b),a?(c=a.querySelector("metadata"))?(d=a.querySelector("manifest"))?(e=a.querySelector("spine"))?(f=n.manifest(d),g=n.findNavPath(d),h=n.findTocPath(d,e),i=n.findCoverPath(a),j=Array.prototype.indexOf.call(e.parentNode.childNodes,e),k=n.spine(e,f),l={},k.forEach(function(a){l[a.href]=a.index}),m=n.metadata(c),m.direction=e.getAttribute("page-progression-direction"),{metadata:m,spine:k,manifest:f,navPath:g,tocPath:h,coverPath:i,spineNodeIndex:j,spineIndexByURL:l}):void console.error("No Spine Found"):void console.error("No Manifest Found"):void console.error("No Metadata Found"):void console.error("Package File Not Found")},EPUBJS.Parser.prototype.findNavPath=function(a){var b=a.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");return!!b&&b.getAttribute("href")},EPUBJS.Parser.prototype.findTocPath=function(a,b){var c,d=a.querySelector("item[media-type='application/x-dtbncx+xml']");return d||(c=b.getAttribute("toc"),c&&(d=a.querySelector("item[id='"+c+"']"))),!!d&&d.getAttribute("href")},EPUBJS.Parser.prototype.metadata=function(a){var b={},c=this;return b.bookTitle=c.getElementText(a,"title"),b.creator=c.getElementText(a,"creator"),b.description=c.getElementText(a,"description"),b.pubdate=c.getElementText(a,"date"),b.publisher=c.getElementText(a,"publisher"),b.identifier=c.getElementText(a,"identifier"),b.language=c.getElementText(a,"language"),b.rights=c.getElementText(a,"rights"),b.modified_date=c.querySelectorText(a,"meta[property='dcterms:modified']"),b.layout=c.querySelectorText(a,"meta[property='rendition:layout']"),b.orientation=c.querySelectorText(a,"meta[property='rendition:orientation']"),b.spread=c.querySelectorText(a,"meta[property='rendition:spread']"),b},EPUBJS.Parser.prototype.findCoverPath=function(a){var b=a.querySelector("package").getAttribute("version");if("2.0"===b){var c=a.querySelector('meta[name="cover"]');if(c){var d=c.getAttribute("content"),e=a.querySelector("item[id='"+d+"']");return!!e&&e.getAttribute("href")}return!1}var f=a.querySelector("item[properties='cover-image']");return!!f&&f.getAttribute("href")},EPUBJS.Parser.prototype.getElementText=function(a,b){var c,d=a.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/",b);return d&&0!==d.length?(c=d[0],c.childNodes.length?c.childNodes[0].nodeValue:""):""},EPUBJS.Parser.prototype.querySelectorText=function(a,b){var c=a.querySelector(b);return c&&c.childNodes.length?c.childNodes[0].nodeValue:""},EPUBJS.Parser.prototype.manifest=function(a){var b=this.baseUrl,c={},d=a.querySelectorAll("item"),e=Array.prototype.slice.call(d);return e.forEach(function(a){var d=a.getAttribute("id"),e=a.getAttribute("href")||"",f=a.getAttribute("media-type")||"",g=a.getAttribute("properties")||"";c[d]={href:e,url:b+e,type:f,properties:g}}),c},EPUBJS.Parser.prototype.spine=function(a,b){var c=a.getElementsByTagName("itemref"),d=Array.prototype.slice.call(c),e=Array.prototype.indexOf.call(a.parentNode.childNodes,a),f=new EPUBJS.EpubCFI;return d.map(function(a,c){var d=a.getAttribute("idref"),g=f.generateChapterComponent(e,c,d),h=a.getAttribute("properties")||"",i=h.length?h.split(" "):[],j=b[d].properties,k=j.length?j.split(" "):[];return{id:d,linear:a.getAttribute("linear")||"",properties:i,manifestProperties:k,href:b[d].href,url:b[d].url,index:c,cfiBase:g,cfi:"epubcfi("+g+")"}})},EPUBJS.Parser.prototype.querySelectorByType=function(a,b,c){var d=a.querySelector(b+'[*|type="'+c+'"]');if(null!==d&&0!==d.length)return d;d=a.querySelectorAll(b);for(var e=0;e1&&d[1],{cfi:f,href:h,packageUrl:e,page:j}):{href:h,page:j}},EPUBJS.Render.Iframe=function(){this.iframe=null,this.document=null,this.window=null,this.docEl=null,this.bodyEl=null,this.headEl=null,this.leftPos=0,this.pageWidth=0,this.id=EPUBJS.core.uuid()},EPUBJS.Render.Iframe.prototype.create=function(){return this.element=document.createElement("div"),this.element.id="epubjs-view:"+this.id,this.isMobile=navigator.userAgent.match(/(iPad|iPhone|iPod|Mobile|Android)/g),this.transform=EPUBJS.core.prefixed("transform"),this.element},EPUBJS.Render.Iframe.prototype.addIframe=function(){return this.iframe=document.createElement("iframe"),this.iframe.id="epubjs-iframe:"+this.id,this.iframe.scrolling=this.scrolling||"no",this.iframe.seamless="seamless",this.iframe.style.border="none",this.iframe.addEventListener("load",this.loaded.bind(this),!1),(this._width||this._height)&&(this.iframe.height=this._height,this.iframe.width=this._width),this.iframe},EPUBJS.Render.Iframe.prototype.load=function(a,b){var c=this,d=new RSVP.defer;return this.window&&this.unload(),this.iframe&&this.element.removeChild(this.iframe),this.iframe=this.addIframe(),this.element.appendChild(this.iframe),this.iframe.onload=function(a){c.document=c.iframe.contentDocument,c.docEl=c.document.documentElement,c.headEl=c.document.head,c.bodyEl=c.document.body||c.document.querySelector("body"),c.window=c.iframe.contentWindow,c.window.addEventListener("resize",c.resized.bind(c),!1),c.leftPos=0,c.setLeft(0),c.bodyEl&&(c.bodyEl.style.margin="0"),d.resolve(c.docEl)},this.iframe.onerror=function(a){d.reject({message:"Error Loading Contents: "+a,stack:(new Error).stack})},this.document=this.iframe.contentDocument,this.document?(this.iframe.contentDocument.open(),this.iframe.contentDocument.write(a),this.iframe.contentDocument.close(),d.promise):(d.reject(new Error("No Document Available")),d.promise)},EPUBJS.Render.Iframe.prototype.loaded=function(a){var b,c,d=this.iframe.contentWindow.location.href;this.document=this.iframe.contentDocument,this.docEl=this.document.documentElement,this.headEl=this.document.head,this.bodyEl=this.document.body||this.document.querySelector("body"),this.window=this.iframe.contentWindow,this.window.focus(),"about:blank"!=d&&(b=this.iframe.contentDocument.querySelector("base"),c=b.getAttribute("href"),this.trigger("render:loaded",c))},EPUBJS.Render.Iframe.prototype.resize=function(a,b){this.element&&(this.element.style.height=b,isNaN(a)||a%2===0||(a+=1),this.element.style.width=a,this.iframe&&(this.iframe.height=b,this.iframe.width=a),this._height=b,this._width=a,this.width=parseInt(window.getComputedStyle(document.getElementById("viewer")).width)||a,this.height=parseInt(window.getComputedStyle(document.getElementById("viewer")).height)||b)},EPUBJS.Render.Iframe.prototype.resized=function(a){this.width=parseInt(window.getComputedStyle(document.getElementById("viewer")).width),this.height=parseInt(window.getComputedStyle(document.getElementById("viewer")).height)},EPUBJS.Render.Iframe.prototype.totalWidth=function(){return this.docEl.scrollWidth},EPUBJS.Render.Iframe.prototype.totalHeight=function(){return this.docEl.scrollHeight},EPUBJS.Render.Iframe.prototype.setPageDimensions=function(a,b){this.pageWidth=a,this.pageHeight=b},EPUBJS.Render.Iframe.prototype.setDirection=function(a){this.direction=a,this.docEl&&"rtl"==this.docEl.dir&&(this.docEl.dir="rtl","pre-paginated"!==this.layout&&(this.docEl.style.position="static",this.docEl.style.right="auto"))},EPUBJS.Render.Iframe.prototype.setLeft=function(a){this.isMobile?this.docEl.style[this.transform]="translate("+-a+"px, 0)":this.document.defaultView.scrollTo(a,0)},EPUBJS.Render.Iframe.prototype.setLayout=function(a){this.layout=a},EPUBJS.Render.Iframe.prototype.setStyle=function(a,b,c){c&&(a=EPUBJS.core.prefixed(a)),this.headEl&&(this.headEl.style[a]=b)},EPUBJS.Render.Iframe.prototype.removeStyle=function(a){this.headEl&&(this.headEl.style[a]="")},EPUBJS.Render.Iframe.prototype.setClasses=function(a){this.bodyEl&&(this.bodyEl.className=a.join(" "))},EPUBJS.Render.Iframe.prototype.addHeadTag=function(a,b,c){var d=c||this.document,e=d.createElement(a),f=d.head;for(var g in b)e.setAttribute(g,b[g]);f&&f.insertBefore(e,f.firstChild)},EPUBJS.Render.Iframe.prototype.page=function(a){this.leftPos=this.pageWidth*(a-1),"rtl"===this.direction&&(this.leftPos=this.leftPos*-1),this.setLeft(this.leftPos)},EPUBJS.Render.Iframe.prototype.getPageNumberByElement=function(a){var b,c;if(a)return b=this.leftPos+a.getBoundingClientRect().left,c=Math.floor(b/this.pageWidth)+1},EPUBJS.Render.Iframe.prototype.getPageNumberByRect=function(a){var b,c;return b=this.leftPos+a.left,c=Math.floor(b/this.pageWidth)+1},EPUBJS.Render.Iframe.prototype.getBaseElement=function(){return this.bodyEl},EPUBJS.Render.Iframe.prototype.getDocumentElement=function(){return this.docEl},EPUBJS.Render.Iframe.prototype.isElementVisible=function(a){var b,c;return!!(a&&"function"==typeof a.getBoundingClientRect&&(b=a.getBoundingClientRect(),c=b.left,0!==b.width&&0!==b.height&&c>=0&&c=1&&a<=this.displayedPages&&(this.chapterPos=a,this.render.page(a),this.visibleRangeCfi=this.getVisibleRangeCfi(),this.currentLocationCfi=this.visibleRangeCfi.start,this.trigger("renderer:locationChanged",this.currentLocationCfi),this.trigger("renderer:visibleRangeChanged",this.visibleRangeCfi),!0):(console.warn("pageMap not set, queuing"),this._q.enqueue("page",arguments),!0)},EPUBJS.Renderer.prototype.nextPage=function(){return this.page(this.chapterPos+1)},EPUBJS.Renderer.prototype.prevPage=function(){return this.page(this.chapterPos-1)},EPUBJS.Renderer.prototype.pageByElement=function(a){var b;a&&(b=this.render.getPageNumberByElement(a),this.page(b))},EPUBJS.Renderer.prototype.lastPage=function(){return this._moving?this._q.enqueue("lastPage",arguments):void this.page(this.displayedPages)},EPUBJS.Renderer.prototype.firstPage=function(){return this._moving?this._q.enqueue("firstPage",arguments):void this.page(1)},EPUBJS.Renderer.prototype.section=function(a){var b=this.doc.getElementById(a);b&&this.pageByElement(b)},EPUBJS.Renderer.prototype.firstElementisTextNode=function(a){var b=a.childNodes,c=b.length;return!!(c&&b[0]&&3===b[0].nodeType&&b[0].textContent.trim().length)},EPUBJS.Renderer.prototype.isGoodNode=function(a){var b=["audio","canvas","embed","iframe","img","math","object","svg","video"];return b.indexOf(a.tagName.toLowerCase())!==-1||this.firstElementisTextNode(a)},EPUBJS.Renderer.prototype.walk=function(a,b,c){for(var d,e,f,g,h=a,i=[h],j=1e4,k=0;!d&&i.length;){if(a=i.shift(),this.containsPoint(a,b,c)&&this.isGoodNode(a)&&(d=a),!d&&a&&a.childElementCount>0){if(e=a.children,!e||!e.length)return d;f=e.length?e.length:0;for(var l=f-1;l>=0;l--)e[l]!=g&&i.unshift(e[l])}if(!d&&0===i.length&&h&&null!==h.parentNode&&(i.push(h.parentNode),g=h,h=h.parentNode),k++,k>j){console.error("ENDLESS LOOP");break}}return d},EPUBJS.Renderer.prototype.containsPoint=function(a,b,c){var d;return!!(a&&"function"==typeof a.getBoundingClientRect&&(d=a.getBoundingClientRect(),0!==d.width&&0!==d.height&&d.left>=b&&b<=d.left+d.width))},EPUBJS.Renderer.prototype.textSprint=function(a,b){var c,d,e=function(a){return/^\s*$/.test(a.data)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT};try{for(c=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,{acceptNode:e},!1);d=c.nextNode();)b(d)}catch(f){for(c=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,e,!1);d=c.nextNode();)b(d)}},EPUBJS.Renderer.prototype.sprint=function(a,b){for(var c,d=document.createTreeWalker(a,NodeFilter.SHOW_ELEMENT,null,!1);c=d.nextNode();)b(c)},EPUBJS.Renderer.prototype.mapPage=function(){var a,b,c,d,e,f,g,h,i=this,j=[],k=this.render.getBaseElement(),l=1,m=this.layout.colWidth+this.layout.gap,n=this.formated.pageWidth*(this.chapterPos-1),o=m*l-n,p=0,q=function(b){var c,e,f;if(b.nodeType==Node.TEXT_NODE){if(e=document.createRange(),e.selectNodeContents(b),c=e.getBoundingClientRect(),!c||0===c.width&&0===c.height)return;c.left>p&&(f=r(b)),c.right>p&&(f=r(b)),d=b,f&&(a=null)}},r=function(e){var f,g=i.splitTextNodeIntoWordsRanges(e);return g.forEach(function(e){var g=e.getBoundingClientRect();!g||0===g.width&&0===g.height||(g.left+g.width0&&(b&&(b.setEnd(a,e),c.push(b)),b=this.doc.createRange(),b.setStart(a,e+1));return b&&(b.setEnd(a,d.length),c.push(b)),c},EPUBJS.Renderer.prototype.rangePosition=function(a){var b,c;return c=a.getClientRects(),c.length?b=c[0]:null},EPUBJS.Renderer.prototype.getPageCfi=function(){var a=2*this.chapterPos-1;return this.pageMap[a].start},EPUBJS.Renderer.prototype.getRange=function(a,b,c){var d,e=this.doc.createRange();return c=!0,"undefined"==typeof document.caretPositionFromPoint||c?"undefined"==typeof document.caretRangeFromPoint||c?(this.visibileEl=this.findElementAfter(a,b),e.setStart(this.visibileEl,1)):e=this.doc.caretRangeFromPoint(a,b):(d=this.doc.caretPositionFromPoint(a,b),e.setStart(d.offsetNode,d.offset)),e},EPUBJS.Renderer.prototype.pagesInCurrentChapter=function(){var a;return this.pageMap?a=this.pageMap.length:(console.warn("page map not loaded"),!1)},EPUBJS.Renderer.prototype.currentRenderedPage=function(){var a;return this.pageMap?a=this.spreads&&this.pageMap.length>1?2*this.chapterPos-1:this.chapterPos:(console.warn("page map not loaded"),!1)},EPUBJS.Renderer.prototype.getRenderedPagesLeft=function(){var a,b,c;return this.pageMap?(b=this.pageMap.length,a=this.spreads?2*this.chapterPos-1:this.chapterPos,c=b-a):(console.warn("page map not loaded"),!1)},EPUBJS.Renderer.prototype.getVisibleRangeCfi=function(){var a,b,c;return this.pageMap?(this.spreads?(a=2*this.chapterPos,b=this.pageMap[a-2],c=b,this.pageMap.length>1&&this.pageMap.length>a-1&&(c=this.pageMap[a-1])):(a=this.chapterPos,b=this.pageMap[a-1],c=b),b||(console.warn("page range miss:",a,this.pageMap),b=this.pageMap[this.pageMap.length-1],c=b),{start:b.start,end:c.end}):(console.warn("page map not loaded"),!1)},EPUBJS.Renderer.prototype.gotoCfi=function(a){var b,c,d;if(this._moving)return this._q.enqueue("gotoCfi",arguments);if(EPUBJS.core.isString(a)&&(a=this.epubcfi.parse(a)),"undefined"==typeof document.evaluate)c=this.epubcfi.addMarker(a,this.doc),c&&(b=this.render.getPageNumberByElement(c),this.epubcfi.removeMarker(c,this.doc),this.page(b));else if(d=this.epubcfi.generateRangeFromCfi(a,this.doc)){var e=d.getBoundingClientRect();b=e?this.render.getPageNumberByRect(e):1,this.page(b),this.currentLocationCfi=a.str}else this.page(1)},EPUBJS.Renderer.prototype.findFirstVisible=function(a){var b,c=a||this.render.getBaseElement();return b=this.walk(c,0,0),b?b:a},EPUBJS.Renderer.prototype.findElementAfter=function(a,b,c){var d,e=c||this.render.getBaseElement();return d=this.walk(e,a,b),d?d:e},EPUBJS.Renderer.prototype.resize=function(a,b,c){this.width=a,this.height=b,c!==!1&&this.render.resize(this.width,this.height),this.contents&&this.reformat(),this.trigger("renderer:resized",{width:this.width,height:this.height})},EPUBJS.Renderer.prototype.onResized=function(a){this.trigger("renderer:beforeResize");var b=parseInt(window.getComputedStyle(document.getElementById("viewer")).width),c=parseInt(window.getComputedStyle(document.getElementById("viewer")).height);this.resize(b,c,!1)},EPUBJS.Renderer.prototype.addEventListeners=function(){this.render.document&&this.listenedEvents.forEach(function(a){this.render.document.addEventListener(a,this.triggerEvent.bind(this),!1)},this)},EPUBJS.Renderer.prototype.removeEventListeners=function(){this.render.document&&this.listenedEvents.forEach(function(a){this.render.document.removeEventListener(a,this.triggerEvent,!1)},this)},EPUBJS.Renderer.prototype.triggerEvent=function(a){this.trigger("renderer:"+a.type,a)},EPUBJS.Renderer.prototype.addSelectionListeners=function(){this.render.document.addEventListener("selectionchange",this.onSelectionChange.bind(this),!1)},EPUBJS.Renderer.prototype.removeSelectionListeners=function(){this.render.document&&this.doc.removeEventListener("selectionchange",this.onSelectionChange,!1)},EPUBJS.Renderer.prototype.onSelectionChange=function(a){this.selectionEndTimeout&&clearTimeout(this.selectionEndTimeout),this.selectionEndTimeout=setTimeout(function(){this.selectedRange=this.render.window.getSelection(),this.trigger("renderer:selected",this.selectedRange)}.bind(this),500)},EPUBJS.Renderer.prototype.setMinSpreadWidth=function(a){this.minSpreadWidth=a,this.spreads=this.determineSpreads(a)},EPUBJS.Renderer.prototype.determineSpreads=function(a){return!(this.isForcedSingle||!a||this.width=d?h.resolve():(c=a[e].url,g=window.encodeURIComponent(c),EPUBJS.core.request(c,"binary").then(function(a){return localforage.setItem(g,a)}).then(function(a){e++,setTimeout(function(){f(h)},1)})),h.promise}.bind(this);return Array.isArray(a)||(a=[a]),f().then(function(){c.resolve()}.bind(this)),c.promise},EPUBJS.Storage.prototype.token=function(a,b){var c=window.encodeURIComponent(a);return localforage.setItem(c,b).then(function(a){return null!==a})},EPUBJS.Storage.prototype.isStored=function(a){var b=window.encodeURIComponent(a);return localforage.getItem(b).then(function(a){return null!==a})},EPUBJS.Storage.prototype.getText=function(a){var b=window.encodeURIComponent(a);return EPUBJS.core.request(a,"arraybuffer",this.withCredentials).then(function(a){return this.offline&&(this.offline=!1,this.trigger("offline",!1)),localforage.setItem(b,a),a}.bind(this)).then(function(b){var c=new RSVP.defer,d=EPUBJS.core.getMimeType(a),e=new Blob([b],{type:d}),f=new FileReader;return f.addEventListener("loadend",function(){c.resolve(f.result)}),f.readAsText(e,d),c.promise}).catch(function(){var c=new RSVP.defer,d=localforage.getItem(b);return this.offline||(this.offline=!0,this.trigger("offline",!0)),d?(d.then(function(b){var d=EPUBJS.core.getMimeType(a),e=new Blob([b],{type:d}),f=new FileReader;f.addEventListener("loadend",function(){c.resolve(f.result)}),f.readAsText(e,d)}),c.promise):(c.reject({message:"File not found in the storage: "+a,stack:(new Error).stack}),c.promise)}.bind(this))},EPUBJS.Storage.prototype.getUrl=function(a){var b=window.encodeURIComponent(a);return EPUBJS.core.request(a,"arraybuffer",this.withCredentials).then(function(c){ +return this.offline&&(this.offline=!1,this.trigger("offline",!1)),localforage.setItem(b,c),a}.bind(this)).catch(function(){var c,d,e=new RSVP.defer,f=window.URL||window.webkitURL||window.mozURL;return this.offline||(this.offline=!0,this.trigger("offline",!0)),b in this.urlCache?(e.resolve(this.urlCache[b]),e.promise):(c=localforage.getItem(b))?(c.then(function(c){var g=new Blob([c],{type:EPUBJS.core.getMimeType(a)});d=f.createObjectURL(g),e.resolve(d),this.urlCache[b]=d}.bind(this)),e.promise):(e.reject({message:"File not found in the storage: "+a,stack:(new Error).stack}),e.promise)}.bind(this))},EPUBJS.Storage.prototype.getXml=function(a){var b=window.encodeURIComponent(a);return EPUBJS.core.request(a,"arraybuffer",this.withCredentials).then(function(a){return this.offline&&(this.offline=!1,this.trigger("offline",!1)),localforage.setItem(b,a),a}.bind(this)).then(function(b){var c=new RSVP.defer,d=EPUBJS.core.getMimeType(a),e=new Blob([b],{type:d}),f=new FileReader;return f.addEventListener("loadend",function(){var a=new DOMParser,b=a.parseFromString(f.result,"text/xml");c.resolve(b)}),f.readAsText(e,d),c.promise}).catch(function(){var c=new RSVP.defer,d=localforage.getItem(b);return this.offline||(this.offline=!0,this.trigger("offline",!0)),d?(d.then(function(b){var d=EPUBJS.core.getMimeType(a),e=new Blob([b],{type:d}),f=new FileReader;f.addEventListener("loadend",function(){var a=new DOMParser,b=a.parseFromString(f.result,"text/xml");c.resolve(b)}),f.readAsText(e,d)}),c.promise):(c.reject({message:"File not found in the storage: "+a,stack:(new Error).stack}),c.promise)}.bind(this))},EPUBJS.Storage.prototype.revokeUrl=function(a){var b=window.URL||window.webkitURL||window.mozURL,c=this.urlCache[a];c&&b.revokeObjectURL(c)},EPUBJS.Storage.prototype.failed=function(a){console.error(a)},RSVP.EventTarget.mixin(EPUBJS.Storage.prototype),EPUBJS.Unarchiver=function(a){this.checkRequirements(),this.urlCache={}},EPUBJS.Unarchiver.prototype.checkRequirements=function(a){"undefined"==typeof JSZip&&console.error("JSZip lib not loaded")},EPUBJS.Unarchiver.prototype.open=function(a,b){if(a instanceof ArrayBuffer){this.zip=new JSZip(a);var c=new RSVP.defer;return c.resolve(),c.promise}return EPUBJS.core.request(a,"binary").then(function(a){this.zip=new JSZip(a)}.bind(this))},EPUBJS.Unarchiver.prototype.getXml=function(a,b){var c=window.decodeURIComponent(a);return this.getText(c,b).then(function(b){var c=new DOMParser,d=EPUBJS.core.getMimeType(a);return c.parseFromString(b,d)})},EPUBJS.Unarchiver.prototype.getUrl=function(a,b){var c,d,e=this,f=new RSVP.defer,g=window.decodeURIComponent(a),h=this.zip.file(g),i=window.URL||window.webkitURL||window.mozURL;return h?a in this.urlCache?(f.resolve(this.urlCache[a]),f.promise):(d=new Blob([h.asUint8Array()],{type:EPUBJS.core.getMimeType(h.name)}),c=i.createObjectURL(d),f.resolve(c),e.urlCache[a]=c,f.promise):(f.reject({message:"File not found in the epub: "+a,stack:(new Error).stack}),f.promise)},EPUBJS.Unarchiver.prototype.getText=function(a,b){var c,d=new RSVP.defer,e=window.decodeURIComponent(a),f=this.zip.file(e);return f?(c=f.asText(),d.resolve(c),d.promise):(d.reject({message:"File not found in the epub: "+a,stack:(new Error).stack}),d.promise)},EPUBJS.Unarchiver.prototype.revokeUrl=function(a){var b=window.URL||window.webkitURL||window.mozURL,c=this.urlCache[a];c&&b.revokeObjectURL(c)},EPUBJS.Unarchiver.prototype.failed=function(a){console.error(a)},EPUBJS.Unarchiver.prototype.afterSaved=function(a){this.callback()},EPUBJS.Unarchiver.prototype.toStorage=function(a){function b(){f--,0===f&&e.afterSaved()}var c=0,d=20,e=this,f=a.length;a.forEach(function(a){setTimeout(function(a){e.saveEntryFileToStorage(a,b)},c,a),c+=d}),console.log("time",c)},function(){var a={application:{ecmascript:["es","ecma"],javascript:"js",ogg:"ogx",pdf:"pdf",postscript:["ps","ai","eps","epsi","epsf","eps2","eps3"],"rdf+xml":"rdf",smil:["smi","smil"],"xhtml+xml":["xhtml","xht"],xml:["xml","xsl","xsd","opf","ncx"],zip:"zip","x-httpd-eruby":"rhtml","x-latex":"latex","x-maker":["frm","maker","frame","fm","fb","book","fbdoc"],"x-object":"o","x-shockwave-flash":["swf","swfl"],"x-silverlight":"scr","epub+zip":"epub","font-tdpfr":"pfr","inkml+xml":["ink","inkml"],json:"json","jsonml+json":"jsonml","mathml+xml":"mathml","metalink+xml":"metalink",mp4:"mp4s","omdoc+xml":"omdoc",oxps:"oxps","vnd.amazon.ebook":"azw",widget:"wgt","x-dtbook+xml":"dtb","x-dtbresource+xml":"res","x-font-bdf":"bdf","x-font-ghostscript":"gsf","x-font-linux-psf":"psf","x-font-otf":"otf","x-font-pcf":"pcf","x-font-snf":"snf","x-font-ttf":["ttf","ttc"],"x-font-type1":["pfa","pfb","pfm","afm"],"x-font-woff":"woff","x-mobipocket-ebook":["prc","mobi"],"x-mspublisher":"pub","x-nzb":"nzb","x-tgif":"obj","xaml+xml":"xaml","xml-dtd":"dtd","xproc+xml":"xpl","xslt+xml":"xslt","internet-property-stream":"acx","x-compress":"z","x-compressed":"tgz","x-gzip":"gz"},audio:{flac:"flac",midi:["mid","midi","kar","rmi"],mpeg:["mpga","mpega","mp2","mp3","m4a","mp2a","m2a","m3a"],mpegurl:"m3u",ogg:["oga","ogg","spx"],"x-aiff":["aif","aiff","aifc"],"x-ms-wma":"wma","x-wav":"wav",adpcm:"adp",mp4:"mp4a",webm:"weba","x-aac":"aac","x-caf":"caf","x-matroska":"mka","x-pn-realaudio-plugin":"rmp",xm:"xm",mid:["mid","rmi"]},image:{gif:"gif",ief:"ief",jpeg:["jpeg","jpg","jpe"],pcx:"pcx",png:"png","svg+xml":["svg","svgz"],tiff:["tiff","tif"],"x-icon":"ico",bmp:"bmp",webp:"webp","x-pict":["pic","pct"],"x-tga":"tga","cis-cod":"cod"},message:{rfc822:["eml","mime","mht","mhtml","nws"]},text:{"cache-manifest":["manifest","appcache"],calendar:["ics","icz","ifb"],css:"css",csv:"csv",h323:"323",html:["html","htm","shtml","stm"],iuls:"uls",mathml:"mml",plain:["txt","text","brf","conf","def","list","log","in","bas"],richtext:"rtx","tab-separated-values":"tsv","x-bibtex":"bib","x-dsrc":"d","x-diff":["diff","patch"],"x-haskell":"hs","x-java":"java","x-literate-haskell":"lhs","x-moc":"moc","x-pascal":["p","pas"],"x-pcs-gcd":"gcd","x-perl":["pl","pm"],"x-python":"py","x-scala":"scala","x-setext":"etx","x-tcl":["tcl","tk"],"x-tex":["tex","ltx","sty","cls"],"x-vcard":"vcf",sgml:["sgml","sgm"],"x-c":["c","cc","cxx","cpp","h","hh","dic"],"x-fortran":["f","for","f77","f90"],"x-opml":"opml","x-nfo":"nfo","x-sfv":"sfv","x-uuencode":"uu",webviewhtml:"htt"},video:{mpeg:["mpeg","mpg","mpe","m1v","m2v","mp2","mpa","mpv2"],mp4:["mp4","mp4v","mpg4"],quicktime:["qt","mov"],ogg:"ogv","vnd.mpegurl":["mxu","m4u"],"x-flv":"flv","x-la-asf":["lsf","lsx"],"x-mng":"mng","x-ms-asf":["asf","asx","asr"],"x-ms-wm":"wm","x-ms-wmv":"wmv","x-ms-wmx":"wmx","x-ms-wvx":"wvx","x-msvideo":"avi","x-sgi-movie":"movie","x-matroska":["mpv","mkv","mk3d","mks"],"3gpp2":"3g2",h261:"h261",h263:"h263",h264:"h264",jpeg:"jpgv",jpm:["jpm","jpgm"],mj2:["mj2","mjp2"],"vnd.ms-playready.media.pyv":"pyv","vnd.uvvu.mp4":["uvu","uvvu"],"vnd.vivo":"viv",webm:"webm","x-f4v":"f4v","x-m4v":"m4v","x-ms-vob":"vob","x-smv":"smv"}},b=function(){var b,c,d,e,f={};for(b in a)if(a.hasOwnProperty(b))for(c in a[b])if(a[b].hasOwnProperty(c))if(d=a[b][c],"string"==typeof d)f[d]=b+"/"+c;else for(e=0;ef/2.5&&(o=f/2.5,pop_content.style.maxHeight=o+"px"),popRect.height+m>=f-25?(c.style.top=m-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),l-popRect.width<=0?(c.style.left=l+"px",c.classList.add("left")):c.classList.remove("left"),l+popRect.width/2>=j?(c.style.left=l-300+"px",popRect=c.getBoundingClientRect(),c.style.left=l-popRect.width+"px",popRect.height+m>=f-25?(c.style.top=m-popRect.height+"px",c.classList.add("above")):c.classList.remove("above"),c.classList.add("right")):c.classList.remove("right")}function d(){h[id].classList.add("on")}function g(){h[id].classList.remove("on")}function i(){setTimeout(function(){h[id].classList.remove("show")},100)}var j,k,l,m,n,o=a.getAttribute(e);o==f&&(j=a.getAttribute("href"),id=j.replace("#",""),k=b.render.document.getElementById(id),a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",i,!1))}),a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").mathml=function(a,b){if(b.currentChapter.manifestProperties.indexOf("mathml")!==-1){b.render.iframe.contentWindow.mathmlCallback=a;var c=document.createElement("script");c.type="text/x-mathjax-config",c.innerHTML=' MathJax.Hub.Register.StartupHook("End",function () { window.mathmlCallback(); }); MathJax.Hub.Config({jax: ["input/TeX","input/MathML","output/SVG"],extensions: ["tex2jax.js","mml2jax.js","MathEvents.js"],TeX: {extensions: ["noErrors.js","noUndefined.js","autoload-all.js"]},MathMenu: {showRenderer: false},menuSettings: {zoom: "Click"},messageStyle: "none"}); ',b.doc.body.appendChild(c),EPUBJS.core.addScript("http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML",null,b.doc.head)}else a&&a()},EPUBJS.Hooks.register("beforeChapterDisplay").smartimages=function(a,b){var c=b.contents.querySelectorAll("img"),d=Array.prototype.slice.call(c),e=b.height;return"reflowable"!=b.layoutSettings.layout?void a():(d.forEach(function(a){var c=function(){var c,d=a.getBoundingClientRect(),f=d.height,g=d.top,h=a.getAttribute("data-height"),i=h||f,j=Number(getComputedStyle(a,"").fontSize.match(/(\d*(\.\d*)?)px/)[1]),k=j?j/2:0;e=b.contents.clientHeight,g<0&&(g=0),i+g>=e?(ge&&(a.style.maxHeight=e+"px",a.style.width="auto",d=a.getBoundingClientRect(),i=d.height),a.style.display="block",a.style.WebkitColumnBreakBefore="always",a.style.breakBefore="column"),a.setAttribute("data-height",c)):(a.style.removeProperty("max-height"),a.style.removeProperty("margin-top"))},d=function(){b.off("renderer:resized",c),b.off("renderer:chapterUnload",this)};a.addEventListener("load",c,!1),b.on("renderer:resized",c),b.on("renderer:chapterUnload",d),c()}),void(a&&a()))},EPUBJS.Hooks.register("beforeChapterDisplay").transculsions=function(a,b){var c=b.contents.querySelectorAll("[transclusion]"),d=Array.prototype.slice.call(c);d.forEach(function(a){function c(){j=g,k=h,j>chapter.colWidth&&(d=chapter.colWidth/j,j=chapter.colWidth,k*=d),f.width=j,f.height=k}var d,e=a.getAttribute("ref"),f=document.createElement("iframe"),g=a.getAttribute("width"),h=a.getAttribute("height"),i=a.parentNode,j=g,k=h;c(),b.listenUntil("renderer:resized","renderer:chapterUnloaded",c),f.src=e,i.replaceChild(f,a)}),a&&a()}; \ No newline at end of file diff --git a/vendor/epubjs/hooks.min.map b/vendor/epubjs/hooks.min.map new file mode 100644 index 0000000..5da22be --- /dev/null +++ b/vendor/epubjs/hooks.min.map @@ -0,0 +1 @@ +{"version":3,"file":"hooks.min.js","sources":["../../hooks/default/endnotes.js","../../hooks/default/mathml.js","../../hooks/default/smartimages.js","../../hooks/default/transculsions.js"],"names":["EPUBJS","Hooks","register","endnotes","callback","renderer","notes","contents","querySelectorAll","items","Array","prototype","slice","call","attr","type","folder","core","location","pathname","popups","cssPath","addCss","render","document","head","forEach","item","showPop","pop","itemRect","iheight","height","iwidth","width","maxHeight","txt","el","cloneNode","querySelector","id","createElement","setAttribute","pop_content","appendChild","body","addEventListener","onPop","offPop","on","hidePop","this","getBoundingClientRect","left","top","classList","add","popRect","style","remove","setTimeout","href","epubType","getAttribute","replace","getElementById","mathml","currentChapter","manifestProperties","indexOf","iframe","contentWindow","mathmlCallback","s","innerHTML","doc","addScript","smartimages","images","layoutSettings","layout","size","newHeight","rectHeight","oHeight","fontSize","Number","getComputedStyle","match","fontAdjust","clientHeight","display","removeProperty","unloaded","off","transculsions","trans","orginal_width","orginal_height","chapter","colWidth","ratio","src","parent","parentNode","listenUntil","replaceChild"],"mappings":"AAAAA,OAAOC,MAAMC,SAAS,wBAAwBC,SAAW,SAASC,EAAUC,GAE1E,GAAIC,GAAQD,EAASE,SAASC,iBAAiB,WAC9CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKP,GACnCQ,EAAO,YACPC,EAAO,UACPC,EAAShB,OAAOiB,KAAKD,OAAOE,SAASC,UAErCC,GADWJ,EAAShB,OAAOqB,SAAYL,KAGxChB,QAAOiB,KAAKK,OAAOtB,OAAOqB,QAAU,aAAa,EAAOhB,EAASkB,OAAOC,SAASC,MAGjFhB,EAAMiB,QAAQ,SAASC,GAqBtB,QAASC,KACR,GAICC,GAEAC,EALAC,EAAU1B,EAAS2B,OACnBC,EAAS5B,EAAS6B,MAGlBC,EAAY,GAGTC,KACHP,EAAMQ,EAAGC,WAAU,GACnBF,EAAMP,EAAIU,cAAc,MAKrBnB,EAAOoB,KACVpB,EAAOoB,GAAMhB,SAASiB,cAAc,OACpCrB,EAAOoB,GAAIE,aAAa,QAAS,SAEjCC,YAAcnB,SAASiB,cAAc,OAErCrB,EAAOoB,GAAII,YAAYD,aAEvBA,YAAYC,YAAYR,GACxBO,YAAYD,aAAa,QAAS,eAElCrC,EAASkB,OAAOC,SAASqB,KAAKD,YAAYxB,EAAOoB,IAGjDpB,EAAOoB,GAAIM,iBAAiB,YAAaC,GAAO,GAChD3B,EAAOoB,GAAIM,iBAAiB,WAAYE,GAAQ,GAKhD3C,EAAS4C,GAAG,uBAAwBC,EAASC,MAC7C9C,EAAS4C,GAAG,uBAAwBD,EAAQG,OAI7CtB,EAAMT,EAAOoB,GAIbV,EAAWH,EAAKyB,wBAChBC,EAAOvB,EAASuB,KAChBC,EAAMxB,EAASwB,IAGfzB,EAAI0B,UAAUC,IAAI,QAGlBC,QAAU5B,EAAIuB,wBAGdvB,EAAI6B,MAAML,KAAOA,EAAOI,QAAQvB,MAAQ,EAAI,KAC5CL,EAAI6B,MAAMJ,IAAMA,EAAM,KAInBnB,EAAYJ,EAAU,MACxBI,EAAYJ,EAAU,IACtBY,YAAYe,MAAMvB,UAAYA,EAAY,MAIxCsB,QAAQzB,OAASsB,GAAOvB,EAAU,IACpCF,EAAI6B,MAAMJ,IAAMA,EAAMG,QAAQzB,OAAU,KACxCH,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAInBN,EAAOI,QAAQvB,OAAS,GAC1BL,EAAI6B,MAAML,KAAOA,EAAO,KACxBxB,EAAI0B,UAAUC,IAAI,SAElB3B,EAAI0B,UAAUI,OAAO,QAInBN,EAAOI,QAAQvB,MAAQ,GAAKD,GAE9BJ,EAAI6B,MAAML,KAAOA,EAAO,IAAM,KAE9BI,QAAU5B,EAAIuB,wBACdvB,EAAI6B,MAAML,KAAOA,EAAOI,QAAQvB,MAAQ,KAErCuB,QAAQzB,OAASsB,GAAOvB,EAAU,IACpCF,EAAI6B,MAAMJ,IAAMA,EAAMG,QAAQzB,OAAU,KACxCH,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAGtB9B,EAAI0B,UAAUC,IAAI,UAElB3B,EAAI0B,UAAUI,OAAO,SAMvB,QAASZ,KACR3B,EAAOoB,GAAIe,UAAUC,IAAI,MAG1B,QAASR,KACR5B,EAAOoB,GAAIe,UAAUI,OAAO,MAG7B,QAAST,KACRU,WAAW,WACVxC,EAAOoB,GAAIe,UAAUI,OAAO,SAC1B,KAxIJ,GACCE,GACArB,EACAH,EAGAgB,EACAC,EACAlB,EARG0B,EAAWnC,EAAKoC,aAAajD,EAU9BgD,IAAY/C,IAEf8C,EAAOlC,EAAKoC,aAAa,QACzBvB,EAAKqB,EAAKG,QAAQ,IAAK,IACvB3B,EAAKhC,EAASkB,OAAOC,SAASyC,eAAezB,GAG7Cb,EAAKmB,iBAAiB,YAAalB,GAAS,GAC5CD,EAAKmB,iBAAiB,WAAYI,GAAS,MA4HzC9C,GAAUA,KC5JfJ,OAAOC,MAAMC,SAAS,wBAAwBgE,OAAS,SAAS9D,EAAUC,GAGtE,GAAoE,KAAjEA,EAAS8D,eAAeC,mBAAmBC,QAAQ,UAAkB,CAGpEhE,EAASkB,OAAO+C,OAAOC,cAAcC,eAAiBpE,CAGtD,IAAIqE,GAAIjD,SAASiB,cAAc,SAC/BgC,GAAE1D,KAAO,wBACT0D,EAAEC,UAAY,6ZAMdrE,EAASsE,IAAI9B,KAAKD,YAAY6B,GAE9BzE,OAAOiB,KAAK2D,UAAU,gFAAiF,KAAMvE,EAASsE,IAAIlD,UAGvHrB,IAAUA,KCtBrBJ,OAAOC,MAAMC,SAAS,wBAAwB2E,YAAc,SAASzE,EAAUC,GAC7E,GAAIyE,GAASzE,EAASE,SAASC,iBAAiB,OAC/CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKiE,GACnC/C,EAAU1B,EAAS2B,MAGpB,OAAqC,cAAlC3B,EAAS0E,eAAeC,WAC1B5E,MAIDK,EAAMiB,QAAQ,SAASC,GAEtB,GAAIsD,GAAO,WACV,GAKCC,GALGpD,EAAWH,EAAKyB,wBACnB+B,EAAarD,EAASE,OACtBsB,EAAMxB,EAASwB,IACf8B,EAAUzD,EAAKoC,aAAa,eAC5B/B,EAASoD,GAAWD,EAEpBE,EAAWC,OAAOC,iBAAiB5D,EAAM,IAAI0D,SAASG,MAAM,mBAAmB,IAC/EC,EAAaJ,EAAWA,EAAW,EAAI,CAExCtD,GAAU1B,EAASE,SAASmF,aACnB,EAANpC,IAASA,EAAM,GAEftB,EAASsB,GAAOvB,GAETA,EAAQ,EAAduB,GAEF4B,EAAYnD,EAAUuB,EAAMmC,EAC5B9D,EAAK+B,MAAMvB,UAAY+C,EAAY,KACnCvD,EAAK+B,MAAMxB,MAAO,SAEfF,EAASD,IACXJ,EAAK+B,MAAMvB,UAAYJ,EAAU,KACjCJ,EAAK+B,MAAMxB,MAAO,OAClBJ,EAAWH,EAAKyB,wBAChBpB,EAASF,EAASE,QAEnBL,EAAK+B,MAAMiC,QAAU,QACrBhE,EAAK+B,MAA+B,wBAAI,SACxC/B,EAAK+B,MAAmB,YAAI,UAI7B/B,EAAKe,aAAa,cAAewC,KAGjCvD,EAAK+B,MAAMkC,eAAe,cAC1BjE,EAAK+B,MAAMkC,eAAe,gBAIxBC,EAAW,WAEdxF,EAASyF,IAAI,mBAAoBb,GACjC5E,EAASyF,IAAI,yBAA0B3C,MAGxCxB,GAAKmB,iBAAiB,OAAQmC,GAAM,GAEpC5E,EAAS4C,GAAG,mBAAoBgC,GAEhC5E,EAAS4C,GAAG,yBAA0B4C,GAEtCZ,WAIE7E,GAAUA,OCtEfJ,OAAOC,MAAMC,SAAS,wBAAwB6F,cAAgB,SAAS3F,EAAUC,GAO/E,GAAI2F,GAAQ3F,EAASE,SAASC,iBAAiB,kBAC7CC,EAAQC,MAAMC,UAAUC,MAAMC,KAAKmF,EAErCvF,GAAMiB,QAAQ,SAASC,GAWtB,QAASsD,KACR/C,EAAQ+D,EACRjE,EAASkE,EAENhE,EAAQiE,QAAQC,WAClBC,EAAQF,QAAQC,SAAWlE,EAE3BA,EAAQiE,QAAQC,SAChBpE,GAAkBqE,GAGnB/B,EAAOpC,MAAQA,EACfoC,EAAOtC,OAASA,EAtBjB,GAOCqE,GAPGC,EAAM3E,EAAKoC,aAAa,OAC3BO,EAAS9C,SAASiB,cAAc,UAChCwD,EAAgBtE,EAAKoC,aAAa,SAClCmC,EAAiBvE,EAAKoC,aAAa,UACnCwC,EAAS5E,EAAK6E,WACdtE,EAAQ+D,EACRjE,EAASkE,CAoBVjB,KAKA5E,EAASoG,YAAY,mBAAoB,2BAA4BxB,GAErEX,EAAOgC,IAAMA,EAGbC,EAAOG,aAAapC,EAAQ3C,KAQ1BvB,GAAUA"} \ No newline at end of file diff --git a/vendor/epubjs/hooks/default/smartimages.js b/vendor/epubjs/hooks/default/smartimages.js new file mode 100644 index 0000000..8c1ec5f --- /dev/null +++ b/vendor/epubjs/hooks/default/smartimages.js @@ -0,0 +1,73 @@ +EPUBJS.Hooks.register("beforeChapterDisplay").smartimages = function(callback, renderer){ + var images = renderer.contents.querySelectorAll('img'), + items = Array.prototype.slice.call(images), + iheight = renderer.height,//chapter.bodyEl.clientHeight,//chapter.doc.body.getBoundingClientRect().height, + oheight; + + if(renderer.layoutSettings.layout != "reflowable") { + callback(); + return; //-- Only adjust images for reflowable text + } + + items.forEach(function(item){ + + var size = function() { + var itemRect = item.getBoundingClientRect(), + rectHeight = itemRect.height, + top = itemRect.top, + oHeight = item.getAttribute('data-height'), + height = oHeight || rectHeight, + newHeight, + fontSize = Number(getComputedStyle(item, "").fontSize.match(/(\d*(\.\d*)?)px/)[1]), + fontAdjust = fontSize ? fontSize / 2 : 0; + + iheight = renderer.contents.clientHeight; + if(top < 0) top = 0; + + if(height + top >= iheight) { + + if(top < iheight/2) { + // Remove top and half font-size from height to keep container from overflowing + newHeight = iheight - top - fontAdjust; + item.style.maxHeight = newHeight + "px"; + item.style.width= "auto"; + }else{ + if(height > iheight) { + item.style.maxHeight = iheight + "px"; + item.style.width= "auto"; + itemRect = item.getBoundingClientRect(); + height = itemRect.height; + } + item.style.display = "block"; + item.style["WebkitColumnBreakBefore"] = "always"; + item.style["breakBefore"] = "column"; + + } + + item.setAttribute('data-height', newHeight); + + }else{ + item.style.removeProperty('max-height'); + item.style.removeProperty('margin-top'); + } + } + + var unloaded = function(){ + // item.removeEventListener('load', size); // crashes in IE + renderer.off("renderer:resized", size); + renderer.off("renderer:chapterUnload", this); + }; + + item.addEventListener('load', size, false); + + renderer.on("renderer:resized", size); + + renderer.on("renderer:chapterUnload", unloaded); + + size(); + + }); + + if(callback) callback(); + +} diff --git a/vendor/epubjs/hooks/extensions/highlight.js b/vendor/epubjs/hooks/extensions/highlight.js new file mode 100644 index 0000000..1dd1c67 --- /dev/null +++ b/vendor/epubjs/hooks/extensions/highlight.js @@ -0,0 +1,14 @@ +EPUBJS.Hooks.register("beforeChapterDisplay").highlight = function(callback, renderer){ + + // EPUBJS.core.addScript("js/libs/jquery.highlight.js", null, renderer.doc.head); + + var s = document.createElement("style"); + s.innerHTML =".highlight { background: yellow; font-weight: normal; }"; + + renderer.render.document.head.appendChild(s); + + if(callback) callback(); + +} + + diff --git a/vendor/epubjs/libs/jquery.min.js b/vendor/epubjs/libs/jquery.min.js new file mode 100644 index 0000000..4024b66 --- /dev/null +++ b/vendor/epubjs/libs/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("