"
+ ].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..624f92d
--- /dev/null
+++ b/vendor/cbrjs/cbr.js
@@ -0,0 +1,1478 @@
+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, session, opts) {
+
+ var images = [],
+ xhr = new XMLHttpRequest(),
+ filename = session.filename,
+ format = session.format,
+ re_file_ext = new RegExp(/\.([a-z]+)$/),
+ 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, options.session, {
+ 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('*').addBack().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..d850691
--- /dev/null
+++ b/vendor/cbrjs/css/cbr.css
@@ -0,0 +1,448 @@
+/* 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;
+ background: #000 url("img/loading.gif") no-repeat center;
+}
+
+.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: #666;
+}
+
+.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 @@
+
+
+
\ 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,"