mirror of
https://github.com/janeczku/calibre-web
synced 2025-10-29 22:33:01 +00:00
Merge remote-tracking branch 'Comic/comic_server'
# Conflicts: # cps/helper.py # cps/static/js/archive.js # cps/static/js/io.js # cps/static/js/kthoom.js # cps/static/js/unrar.js # cps/static/js/untar.js # cps/static/js/unzip.js # cps/templates/config_edit.html # cps/templates/detail.html # cps/templates/readcbr.html # cps/templates/stats.html # cps/ub.py # cps/web.py # optional-requirements.txt
This commit is contained in:
308
cps/static/js/bytestream.js
Normal file
308
cps/static/js/bytestream.js
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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 a stream. More bytes can be pushed into the back of the stream via the
|
||||
* push() method.
|
||||
*/
|
||||
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) {
|
||||
if (!(ab instanceof ArrayBuffer)) {
|
||||
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
|
||||
}
|
||||
|
||||
const offset = opt_offset || 0;
|
||||
const length = opt_length || ab.byteLength;
|
||||
|
||||
/**
|
||||
* The current page of bytes in the stream.
|
||||
* @type {Uint8Array}
|
||||
* @private
|
||||
*/
|
||||
this.bytes = new Uint8Array(ab, offset, length);
|
||||
|
||||
/**
|
||||
* The next pages of bytes in the stream.
|
||||
* @type {Array<Uint8Array>}
|
||||
* @private
|
||||
*/
|
||||
this.pages_ = [];
|
||||
|
||||
/**
|
||||
* The byte in the current page that we will read next.
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.ptr = 0;
|
||||
|
||||
/**
|
||||
* An ever-increasing number.
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.bytesRead_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many bytes have been read in the stream since the beginning of time.
|
||||
*/
|
||||
getNumBytesRead() {
|
||||
return this.bytesRead_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many bytes are currently in the stream left to be read.
|
||||
*/
|
||||
getNumBytesLeft() {
|
||||
const bytesInCurrentPage = (this.bytes.byteLength - this.ptr);
|
||||
return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer ahead n bytes. If the pointer is at the end of the current array
|
||||
* of bytes and we have another page of bytes, point at the new page. This is a private
|
||||
* method, no validation is done.
|
||||
* @param {number} n Number of bytes to increment.
|
||||
* @private
|
||||
*/
|
||||
movePointer_(n) {
|
||||
this.ptr += n;
|
||||
this.bytesRead_ += n;
|
||||
while (this.ptr >= this.bytes.length && this.pages_.length > 0) {
|
||||
this.ptr -= this.bytes.length;
|
||||
this.bytes = this.pages_.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks at the next n bytes as an unsigned number but does not advance the
|
||||
* pointer.
|
||||
* @param {number} n The number of bytes to peek at. Must be a positive integer.
|
||||
* @return {number} The n bytes interpreted as an unsigned number.
|
||||
*/
|
||||
peekNumber(n) {
|
||||
const num = parseInt(n, 10);
|
||||
if (n !== num || num < 0) {
|
||||
throw 'Error! Called peekNumber() with a non-positive integer';
|
||||
} else if (num === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n > 4) {
|
||||
throw 'Error! Called peekNumber(' + n +
|
||||
') but this method can only reliably read numbers up to 4 bytes long';
|
||||
}
|
||||
|
||||
if (this.getNumBytesLeft() < num) {
|
||||
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
|
||||
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
|
||||
}
|
||||
|
||||
let result = 0;
|
||||
let curPage = this.bytes;
|
||||
let pageIndex = 0;
|
||||
let ptr = this.ptr;
|
||||
for (let i = 0; i < num; ++i) {
|
||||
result |= (curPage[ptr++] << (i * 8));
|
||||
|
||||
if (ptr >= curPage.length) {
|
||||
curPage = this.pages_[pageIndex++];
|
||||
ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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. Must be a positive integer.
|
||||
* @return {number} The n bytes interpreted as an unsigned number.
|
||||
*/
|
||||
readNumber(n) {
|
||||
const num = this.peekNumber(n);
|
||||
this.movePointer_(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. Must be a positive integer.
|
||||
* @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. Must be a positive integer.
|
||||
* @return {number} The bytes interpreted as a signed number.
|
||||
*/
|
||||
readSignedNumber(n) {
|
||||
const num = this.peekSignedNumber(n);
|
||||
this.movePointer_(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. Must be a positive integer.
|
||||
* @param {boolean} movePointers Whether to move the pointers.
|
||||
* @return {Uint8Array} The subarray.
|
||||
*/
|
||||
peekBytes(n, movePointers) {
|
||||
const num = parseInt(n, 10);
|
||||
if (n !== num || num < 0) {
|
||||
throw 'Error! Called peekBytes() with a non-positive integer';
|
||||
} else if (num === 0) {
|
||||
return new Uint8Array();
|
||||
}
|
||||
|
||||
const totalBytesLeft = this.getNumBytesLeft();
|
||||
if (num > totalBytesLeft) {
|
||||
throw 'Error! Overflowed the byte stream during peekBytes! n=' + num +
|
||||
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
|
||||
}
|
||||
|
||||
const result = new Uint8Array(num);
|
||||
let curPage = this.bytes;
|
||||
let ptr = this.ptr;
|
||||
let bytesLeftToCopy = num;
|
||||
let pageIndex = 0;
|
||||
while (bytesLeftToCopy > 0) {
|
||||
const bytesLeftInPage = curPage.length - ptr;
|
||||
const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage);
|
||||
|
||||
result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy);
|
||||
|
||||
ptr += sourceLength;
|
||||
if (ptr >= curPage.length) {
|
||||
curPage = this.pages_[pageIndex++];
|
||||
ptr = 0;
|
||||
}
|
||||
|
||||
bytesLeftToCopy -= sourceLength;
|
||||
}
|
||||
|
||||
if (movePointers) {
|
||||
this.movePointer_(num);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next n bytes as a sub-array.
|
||||
* @param {number} n The number of bytes to read. Must be a positive integer.
|
||||
* @return {Uint8Array} The subarray.
|
||||
*/
|
||||
readBytes(n) {
|
||||
return this.peekBytes(n, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks at the next n bytes as an ASCII string but does not advance the pointer.
|
||||
* @param {number} n The number of bytes to peek at. Must be a positive integer.
|
||||
* @return {string} The next n bytes as a string.
|
||||
*/
|
||||
peekString(n) {
|
||||
const num = parseInt(n, 10);
|
||||
if (n !== num || num < 0) {
|
||||
throw 'Error! Called peekString() with a non-positive integer';
|
||||
} else if (num === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const totalBytesLeft = this.getNumBytesLeft();
|
||||
if (num > totalBytesLeft) {
|
||||
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
|
||||
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
|
||||
}
|
||||
|
||||
let result = new Array(num);
|
||||
let curPage = this.bytes;
|
||||
let pageIndex = 0;
|
||||
let ptr = this.ptr;
|
||||
for (let i = 0; i < num; ++i) {
|
||||
result[i] = String.fromCharCode(curPage[ptr++]);
|
||||
if (ptr >= curPage.length) {
|
||||
curPage = this.pages_[pageIndex++];
|
||||
ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. Must be a positive integer.
|
||||
* @return {string} The next n bytes as a string.
|
||||
*/
|
||||
readString(n) {
|
||||
const strToReturn = this.peekString(n);
|
||||
this.movePointer_(n);
|
||||
return strToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds more bytes into the back of the stream.
|
||||
* @param {ArrayBuffer} ab
|
||||
*/
|
||||
push(ab) {
|
||||
if (!(ab instanceof ArrayBuffer)) {
|
||||
throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object';
|
||||
}
|
||||
|
||||
this.pages_.push(new Uint8Array(ab));
|
||||
// If the pointer is at the end of the current page of bytes, this will advance
|
||||
// to the next page.
|
||||
this.movePointer_(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ByteStream from this ByteStream that can be read / peeked.
|
||||
* @return {bitjs.io.ByteStream} A clone of this ByteStream.
|
||||
*/
|
||||
tee() {
|
||||
const clone = new bitjs.io.ByteStream(this.bytes.buffer);
|
||||
clone.bytes = this.bytes;
|
||||
clone.ptr = this.ptr;
|
||||
clone.pages_ = this.pages_.slice();
|
||||
clone.bytesRead_ = this.bytesRead_;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6
|
||||
|
||||
*/
|
||||
/* global bitjs */
|
||||
/* global screenfull, bitjs */
|
||||
|
||||
if (window.opera) {
|
||||
window.console.log = function(str) {
|
||||
@@ -35,62 +35,69 @@ function getElem(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
if (window.kthoom === undefined) {
|
||||
if (typeof window.kthoom === "undefined" ) {
|
||||
kthoom = {};
|
||||
}
|
||||
|
||||
// key codes
|
||||
kthoom.Key = {
|
||||
ESCAPE: 27,
|
||||
SPACE: 32,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77,
|
||||
DOWN: 40,
|
||||
A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77,
|
||||
N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90,
|
||||
QUESTION_MARK: 191,
|
||||
LEFT_SQUARE_BRACKET: 219,
|
||||
RIGHT_SQUARE_BRACKET: 221
|
||||
};
|
||||
|
||||
// The rotation orientation of the comic.
|
||||
kthoom.rotateTimes = 0;
|
||||
|
||||
// global variables
|
||||
var unarchiver = null;
|
||||
var currentImage = 0;
|
||||
var imageFiles = [];
|
||||
var imageFilenames = [];
|
||||
var totalImages = 0;
|
||||
var lastCompletion = 0;
|
||||
|
||||
var hflip = false, vflip = false, fitMode = kthoom.Key.B;
|
||||
var canKeyNext = true, canKeyPrev = true;
|
||||
var settings = {
|
||||
hflip: false,
|
||||
vflip: false,
|
||||
rotateTimes: 0,
|
||||
fitMode: kthoom.Key.B,
|
||||
theme: "light"
|
||||
};
|
||||
|
||||
kthoom.saveSettings = function() {
|
||||
localStorage.kthoomSettings = JSON.stringify({
|
||||
rotateTimes: kthoom.rotateTimes,
|
||||
hflip: hflip,
|
||||
vflip: vflip,
|
||||
fitMode: fitMode
|
||||
});
|
||||
localStorage.kthoomSettings = JSON.stringify(settings);
|
||||
};
|
||||
|
||||
kthoom.loadSettings = function() {
|
||||
try {
|
||||
if (localStorage.kthoomSettings.length < 10){
|
||||
if (!localStorage.kthoomSettings) {
|
||||
return;
|
||||
}
|
||||
var s = JSON.parse(localStorage.kthoomSettings);
|
||||
kthoom.rotateTimes = s.rotateTimes;
|
||||
hflip = s.hflip;
|
||||
vflip = s.vflip;
|
||||
fitMode = s.fitMode;
|
||||
|
||||
$.extend(settings, JSON.parse(localStorage.kthoomSettings));
|
||||
|
||||
kthoom.setSettings();
|
||||
} catch (err) {
|
||||
alert("Error load settings");
|
||||
}
|
||||
};
|
||||
|
||||
kthoom.setSettings = function() {
|
||||
// Set settings control values
|
||||
$.each(settings, function(key, value) {
|
||||
if (typeof value === "boolean") {
|
||||
$("input[name=" + key + "]").prop("checked", value);
|
||||
} else {
|
||||
$("input[name=" + key + "]").val([value]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var createURLFromArray = function(array, mimeType) {
|
||||
var offset = array.byteOffset, len = array.byteLength;
|
||||
var url;
|
||||
@@ -127,9 +134,6 @@ var createURLFromArray = function(array, mimeType) {
|
||||
|
||||
|
||||
// Stores an image filename and its data: URI.
|
||||
// TODO: investigate if we really need to store as base64 (leave off ;base64 and just
|
||||
// non-safe URL characters are encoded as %xx ?)
|
||||
// This would save 25% on memory since base64-encoded strings are 4/3 the size of the binary
|
||||
kthoom.ImageFile = function(file) {
|
||||
this.filename = file.filename;
|
||||
var fileExtension = file.filename.split(".").pop().toLowerCase();
|
||||
@@ -141,138 +145,13 @@ kthoom.ImageFile = function(file) {
|
||||
};
|
||||
|
||||
|
||||
kthoom.initProgressMeter = function() {
|
||||
var svgns = "http://www.w3.org/2000/svg";
|
||||
var pdiv = $("#progress")[0];
|
||||
var svg = document.createElementNS(svgns, "svg");
|
||||
svg.style.width = "100%";
|
||||
svg.style.height = "100%";
|
||||
|
||||
var defs = document.createElementNS(svgns, "defs");
|
||||
|
||||
var patt = document.createElementNS(svgns, "pattern");
|
||||
patt.id = "progress_pattern";
|
||||
patt.setAttribute("width", "30");
|
||||
patt.setAttribute("height", "20");
|
||||
patt.setAttribute("patternUnits", "userSpaceOnUse");
|
||||
|
||||
var rect = document.createElementNS(svgns, "rect");
|
||||
rect.setAttribute("width", "100%");
|
||||
rect.setAttribute("height", "100%");
|
||||
rect.setAttribute("fill", "#cc2929");
|
||||
|
||||
var poly = document.createElementNS(svgns, "polygon");
|
||||
poly.setAttribute("fill", "yellow");
|
||||
poly.setAttribute("points", "15,0 30,0 15,20 0,20");
|
||||
|
||||
patt.appendChild(rect);
|
||||
patt.appendChild(poly);
|
||||
defs.appendChild(patt);
|
||||
|
||||
svg.appendChild(defs);
|
||||
|
||||
var g = document.createElementNS(svgns, "g");
|
||||
|
||||
var outline = document.createElementNS(svgns, "rect");
|
||||
outline.setAttribute("y", "1");
|
||||
outline.setAttribute("width", "100%");
|
||||
outline.setAttribute("height", "15");
|
||||
outline.setAttribute("fill", "#777");
|
||||
outline.setAttribute("stroke", "white");
|
||||
outline.setAttribute("rx", "5");
|
||||
outline.setAttribute("ry", "5");
|
||||
g.appendChild(outline);
|
||||
|
||||
var title = document.createElementNS(svgns, "text");
|
||||
title.id = "progress_title";
|
||||
title.appendChild(document.createTextNode("0%"));
|
||||
title.setAttribute("y", "13");
|
||||
title.setAttribute("x", "99.5%");
|
||||
title.setAttribute("fill", "white");
|
||||
title.setAttribute("font-size", "12px");
|
||||
title.setAttribute("text-anchor", "end");
|
||||
g.appendChild(title);
|
||||
|
||||
var meter = document.createElementNS(svgns, "rect");
|
||||
meter.id = "meter";
|
||||
meter.setAttribute("width", "0%");
|
||||
meter.setAttribute("height", "17");
|
||||
meter.setAttribute("fill", "url(#progress_pattern)");
|
||||
meter.setAttribute("rx", "5");
|
||||
meter.setAttribute("ry", "5");
|
||||
|
||||
var meter2 = document.createElementNS(svgns, "rect");
|
||||
meter2.id = "meter2";
|
||||
meter2.setAttribute("width", "0%");
|
||||
meter2.setAttribute("height", "17");
|
||||
meter2.setAttribute("opacity", "0.8");
|
||||
meter2.setAttribute("fill", "#007fff");
|
||||
meter2.setAttribute("rx", "5");
|
||||
meter2.setAttribute("ry", "5");
|
||||
|
||||
g.appendChild(meter);
|
||||
g.appendChild(meter2);
|
||||
|
||||
var page = document.createElementNS(svgns, "text");
|
||||
page.id = "page";
|
||||
page.appendChild(document.createTextNode("0/0"));
|
||||
page.setAttribute("y", "13");
|
||||
page.setAttribute("x", "0.5%");
|
||||
page.setAttribute("fill", "white");
|
||||
page.setAttribute("font-size", "12px");
|
||||
g.appendChild(page);
|
||||
|
||||
|
||||
svg.appendChild(g);
|
||||
pdiv.appendChild(svg);
|
||||
var l;
|
||||
svg.onclick = function(e) {
|
||||
for (var x = pdiv, l = 0; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft;
|
||||
var page = Math.max(1, Math.ceil(((e.clientX - l) / pdiv.offsetWidth) * totalImages)) - 1;
|
||||
function initProgressClick() {
|
||||
$("#progress").click(function(e) {
|
||||
var page = Math.max(1, Math.ceil((e.offsetX / $(this).width()) * totalImages)) - 1;
|
||||
currentImage = page;
|
||||
updatePage();
|
||||
};
|
||||
}
|
||||
|
||||
kthoom.setProgressMeter = function(pct, optLabel) {
|
||||
pct = (pct * 100);
|
||||
var part = 1 / totalImages;
|
||||
var remain = ((pct - lastCompletion) / 100) / part;
|
||||
var fract = Math.min(1, remain);
|
||||
var smartpct = ((imageFiles.length / totalImages) + (fract * part)) * 100;
|
||||
if (totalImages === 0) smartpct = pct;
|
||||
|
||||
// + Math.min((pct - lastCompletion), 100/totalImages * 0.9 + (pct - lastCompletion - 100/totalImages)/2, 100/totalImages);
|
||||
var oldval = parseFloat(getElem("meter").getAttribute("width"));
|
||||
if (isNaN(oldval)) oldval = 0;
|
||||
var weight = 0.5;
|
||||
smartpct = ((weight * smartpct) + ((1 - weight) * oldval));
|
||||
if (pct === 100) smartpct = 100;
|
||||
|
||||
if (!isNaN(smartpct)) {
|
||||
getElem("meter").setAttribute("width", smartpct + "%");
|
||||
}
|
||||
var title = getElem("progress_title");
|
||||
while (title.firstChild) title.removeChild(title.firstChild);
|
||||
|
||||
var labelText = pct.toFixed(2) + "% " + imageFiles.length + "/" + totalImages + "";
|
||||
if (optLabel) {
|
||||
labelText = optLabel + " " + labelText;
|
||||
}
|
||||
title.appendChild(document.createTextNode(labelText));
|
||||
|
||||
getElem("meter2").setAttribute("width",
|
||||
100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%");
|
||||
|
||||
var titlePage = getElem("page");
|
||||
while (titlePage.firstChild) titlePage.removeChild(titlePage.firstChild);
|
||||
titlePage.appendChild(document.createTextNode( (currentImage + 1) + "/" + totalImages ));
|
||||
|
||||
if (pct > 0) {
|
||||
//getElem('nav').className = '';
|
||||
getElem("progress").className = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function loadFromArrayBuffer(ab) {
|
||||
var start = (new Date).getTime();
|
||||
@@ -291,8 +170,7 @@ function loadFromArrayBuffer(ab) {
|
||||
function(e) {
|
||||
var percentage = e.currentBytesUnarchived / e.totalUncompressedBytesInArchive;
|
||||
totalImages = e.totalFilesInArchive;
|
||||
kthoom.setProgressMeter(percentage, "Unzipping");
|
||||
// display nav
|
||||
updateProgress(percentage *100);
|
||||
lastCompletion = percentage * 100;
|
||||
});
|
||||
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
|
||||
@@ -304,11 +182,20 @@ function loadFromArrayBuffer(ab) {
|
||||
if (imageFilenames.indexOf(f.filename) === -1) {
|
||||
imageFilenames.push(f.filename);
|
||||
imageFiles.push(new kthoom.ImageFile(f));
|
||||
// add thumbnails to the TOC list
|
||||
$("#thumbnails").append(
|
||||
"<li>" +
|
||||
"<a data-page='" + imageFiles.length + "'>" +
|
||||
"<img src='" + imageFiles[imageFiles.length - 1].dataURI + "'/>" +
|
||||
"<span>" + imageFiles.length + "</span>" +
|
||||
"</a>" +
|
||||
"</li>"
|
||||
);
|
||||
}
|
||||
}
|
||||
// display first page if we haven't yet
|
||||
if (imageFiles.length === currentImage + 1) {
|
||||
updatePage();
|
||||
updatePage(lastCompletion);
|
||||
}
|
||||
});
|
||||
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.FINISH,
|
||||
@@ -322,19 +209,54 @@ function loadFromArrayBuffer(ab) {
|
||||
}
|
||||
}
|
||||
|
||||
function scrollTocToActive() {
|
||||
// Scroll to the thumbnail in the TOC on page change
|
||||
$('#tocView').stop().animate({
|
||||
scrollTop: $('#tocView a.active').position().top
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function updatePage() {
|
||||
var title = getElem("page");
|
||||
while (title.firstChild) title.removeChild(title.firstChild);
|
||||
title.appendChild(document.createTextNode( (currentImage + 1 ) + "/" + totalImages ));
|
||||
$('.page').text((currentImage + 1 ) + "/" + totalImages);
|
||||
|
||||
// Mark the current page in the TOC
|
||||
$('#tocView a[data-page]')
|
||||
// Remove the currently active thumbnail
|
||||
.removeClass('active')
|
||||
// Find the new one
|
||||
.filter('[data-page='+ (currentImage + 1) +']')
|
||||
// Set it to active
|
||||
.addClass('active');
|
||||
|
||||
scrollTocToActive();
|
||||
updateProgress();
|
||||
|
||||
getElem("meter2").setAttribute("width",
|
||||
100 * (totalImages === 0 ? 0 : ((currentImage + 1 ) / totalImages)) + "%");
|
||||
if (imageFiles[currentImage]) {
|
||||
setImage(imageFiles[currentImage].dataURI);
|
||||
} else {
|
||||
setImage("loading");
|
||||
}
|
||||
|
||||
$("body").toggleClass("dark-theme", settings.theme === "dark");
|
||||
|
||||
kthoom.setSettings();
|
||||
kthoom.saveSettings();
|
||||
}
|
||||
|
||||
function updateProgress(loadPercentage) {
|
||||
// Set the load/unzip progress if it's passed in
|
||||
if (loadPercentage) {
|
||||
$("#progress .bar-load").css({ width: loadPercentage + "%" });
|
||||
|
||||
if (loadPercentage === 100) {
|
||||
$("#progress")
|
||||
.removeClass('loading')
|
||||
.find(".load").text('');
|
||||
}
|
||||
}
|
||||
|
||||
// Set page progress bar
|
||||
$("#progress .bar-read").css({ width: totalImages === 0 ? 0 : Math.round((currentImage + 1) / totalImages * 100) + "%"});
|
||||
}
|
||||
|
||||
function setImage(url) {
|
||||
@@ -345,81 +267,92 @@ function setImage(url) {
|
||||
updateScale(true);
|
||||
canvas.width = innerWidth - 100;
|
||||
canvas.height = 200;
|
||||
x.fillStyle = "red";
|
||||
x.font = "50px sans-serif";
|
||||
x.fillStyle = "black";
|
||||
x.textAlign = "center";
|
||||
x.font = "24px sans-serif";
|
||||
x.strokeStyle = "black";
|
||||
x.fillText("Loading Page #" + (currentImage + 1), 100, 100);
|
||||
x.fillText("Loading Page #" + (currentImage + 1), innerWidth / 2, 100);
|
||||
} else {
|
||||
if ($("body").css("scrollHeight") / innerHeight > 1) {
|
||||
$("body").css("overflowY", "scroll");
|
||||
}
|
||||
|
||||
var img = new Image();
|
||||
img.onerror = function() {
|
||||
canvas.width = innerWidth - 100;
|
||||
canvas.height = 300;
|
||||
if (url === "error") {
|
||||
updateScale(true);
|
||||
x.fillStyle = "orange";
|
||||
x.font = "50px sans-serif";
|
||||
canvas.width = innerWidth - 100;
|
||||
canvas.height = 200;
|
||||
x.fillStyle = "black";
|
||||
x.textAlign = "center";
|
||||
x.font = "24px sans-serif";
|
||||
x.strokeStyle = "black";
|
||||
x.fillText("Page #" + (currentImage + 1) + " (" +
|
||||
imageFiles[currentImage].filename + ")", 100, 100);
|
||||
x.fillStyle = "red";
|
||||
x.fillText("Is corrupt or not an image", 100, 200);
|
||||
x.fillText("Unable to decompress image #" + (currentImage + 1), innerWidth / 2, 100);
|
||||
} else {
|
||||
if ($("body").css("scrollHeight") / innerHeight > 1) {
|
||||
$("body").css("overflowY", "scroll");
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (/(html|htm)$/.test(imageFiles[currentImage].filename)) {
|
||||
xhr.open("GET", url, true);
|
||||
xhr.onload = function() {
|
||||
//document.getElementById('mainText').style.display = '';
|
||||
$("#mainText").css("display", "");
|
||||
$("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>");
|
||||
var img = new Image();
|
||||
img.onerror = function() {
|
||||
canvas.width = innerWidth - 100;
|
||||
canvas.height = 300;
|
||||
updateScale(true);
|
||||
x.fillStyle = "black";
|
||||
x.font = "50px sans-serif";
|
||||
x.strokeStyle = "black";
|
||||
x.fillText("Page #" + (currentImage + 1) + " (" +
|
||||
imageFiles[currentImage].filename + ")", innerWidth / 2, 100);
|
||||
x.fillStyle = "black";
|
||||
x.fillText("Is corrupt or not an image", innerWidth / 2, 200);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (/(html|htm)$/.test(imageFiles[currentImage].filename)) {
|
||||
xhr.open("GET", url, true);
|
||||
xhr.onload = function() {
|
||||
$("#mainText").css("display", "");
|
||||
$("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>");
|
||||
}
|
||||
xhr.send(null);
|
||||
} else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) {
|
||||
xhr.open("GET", url, true);
|
||||
xhr.onload = function() {
|
||||
$("#mainText").css("display", "");
|
||||
$("#mainText").innerText(xhr.responseText);
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
xhr.send(null);
|
||||
} else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) {
|
||||
xhr.open("GET", url, true);
|
||||
xhr.onload = function() {
|
||||
$("#mainText").css("display", "");
|
||||
$("#mainText").innerText(xhr.responseText);
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
};
|
||||
img.onload = function() {
|
||||
var h = img.height,
|
||||
w = img.width,
|
||||
sw = w,
|
||||
sh = h;
|
||||
kthoom.rotateTimes = (4 + kthoom.rotateTimes) % 4;
|
||||
x.save();
|
||||
if (kthoom.rotateTimes % 2 === 1) {
|
||||
sh = w;
|
||||
sw = h;
|
||||
}
|
||||
canvas.height = sh;
|
||||
canvas.width = sw;
|
||||
x.translate(sw / 2, sh / 2);
|
||||
x.rotate(Math.PI / 2 * kthoom.rotateTimes);
|
||||
x.translate(-w / 2, -h / 2);
|
||||
if (vflip) {
|
||||
x.scale(1, -1);
|
||||
x.translate(0, -h);
|
||||
}
|
||||
if (hflip) {
|
||||
x.scale(-1, 1);
|
||||
x.translate(-w, 0);
|
||||
}
|
||||
canvas.style.display = "none";
|
||||
scrollTo(0, 0);
|
||||
x.drawImage(img, 0, 0);
|
||||
};
|
||||
img.onload = function() {
|
||||
var h = img.height,
|
||||
w = img.width,
|
||||
sw = w,
|
||||
sh = h;
|
||||
settings.rotateTimes = (4 + settings.rotateTimes) % 4;
|
||||
x.save();
|
||||
if (settings.rotateTimes % 2 === 1) {
|
||||
sh = w;
|
||||
sw = h;
|
||||
}
|
||||
canvas.height = sh;
|
||||
canvas.width = sw;
|
||||
x.translate(sw / 2, sh / 2);
|
||||
x.rotate(Math.PI / 2 * settings.rotateTimes);
|
||||
x.translate(-w / 2, -h / 2);
|
||||
if (settings.vflip) {
|
||||
x.scale(1, -1);
|
||||
x.translate(0, -h);
|
||||
}
|
||||
if (settings.hflip) {
|
||||
x.scale(-1, 1);
|
||||
x.translate(-w, 0);
|
||||
}
|
||||
canvas.style.display = "none";
|
||||
scrollTo(0, 0);
|
||||
x.drawImage(img, 0, 0);
|
||||
|
||||
updateScale();
|
||||
updateScale(false);
|
||||
|
||||
canvas.style.display = "";
|
||||
$("body").css("overflowY", "");
|
||||
x.restore();
|
||||
};
|
||||
img.src = url;
|
||||
canvas.style.display = "";
|
||||
$("body").css("overflowY", "");
|
||||
x.restore();
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,149 +382,254 @@ function updateScale(clear) {
|
||||
mainImageStyle.height = "";
|
||||
mainImageStyle.maxWidth = "";
|
||||
mainImageStyle.maxHeight = "";
|
||||
var maxheight = innerHeight - 15;
|
||||
if (!/main/.test(getElem("titlebar").className)) {
|
||||
maxheight -= 25;
|
||||
}
|
||||
if (clear || fitMode === kthoom.Key.N) {
|
||||
} else if (fitMode === kthoom.Key.B) {
|
||||
mainImageStyle.maxWidth = "100%";
|
||||
mainImageStyle.maxHeight = maxheight + "px";
|
||||
} else if (fitMode === kthoom.Key.H) {
|
||||
mainImageStyle.height = maxheight + "px";
|
||||
} else if (fitMode === kthoom.Key.W) {
|
||||
mainImageStyle.width = "100%";
|
||||
var maxheight = innerHeight - 50;
|
||||
|
||||
if (!clear) {
|
||||
switch (settings.fitMode) {
|
||||
case kthoom.Key.B:
|
||||
mainImageStyle.maxWidth = "100%";
|
||||
mainImageStyle.maxHeight = maxheight + "px";
|
||||
break;
|
||||
case kthoom.Key.H:
|
||||
mainImageStyle.height = maxheight + "px";
|
||||
break;
|
||||
case kthoom.Key.W:
|
||||
mainImageStyle.width = "100%";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
$("#mainContent").css({maxHeight: maxheight + 5});
|
||||
kthoom.setSettings();
|
||||
kthoom.saveSettings();
|
||||
}
|
||||
|
||||
function keyHandler(evt) {
|
||||
var code = evt.keyCode;
|
||||
|
||||
if ($("#progress").css("display") === "none"){
|
||||
return;
|
||||
}
|
||||
canKeyNext = (($("body").css("offsetWidth") + $("body").css("scrollLeft")) / $("body").css("scrollWidth")) >= 1;
|
||||
canKeyPrev = (scrollX <= 0);
|
||||
|
||||
if (evt.ctrlKey || evt.shiftKey || evt.metaKey) return;
|
||||
switch (code) {
|
||||
var hasModifier = evt.ctrlKey || evt.shiftKey || evt.metaKey;
|
||||
switch (evt.keyCode) {
|
||||
case kthoom.Key.LEFT:
|
||||
if (canKeyPrev) showPrevPage();
|
||||
if (hasModifier) break;
|
||||
showPrevPage();
|
||||
break;
|
||||
case kthoom.Key.RIGHT:
|
||||
if (canKeyNext) showNextPage();
|
||||
if (hasModifier) break;
|
||||
showNextPage();
|
||||
break;
|
||||
case kthoom.Key.L:
|
||||
kthoom.rotateTimes--;
|
||||
if (kthoom.rotateTimes < 0) {
|
||||
kthoom.rotateTimes = 3;
|
||||
if (hasModifier) break;
|
||||
settings.rotateTimes--;
|
||||
if (settings.rotateTimes < 0) {
|
||||
settings.rotateTimes = 3;
|
||||
}
|
||||
updatePage();
|
||||
break;
|
||||
case kthoom.Key.R:
|
||||
kthoom.rotateTimes++;
|
||||
if (kthoom.rotateTimes > 3) {
|
||||
kthoom.rotateTimes = 0;
|
||||
if (hasModifier) break;
|
||||
settings.rotateTimes++;
|
||||
if (settings.rotateTimes > 3) {
|
||||
settings.rotateTimes = 0;
|
||||
}
|
||||
updatePage();
|
||||
break;
|
||||
case kthoom.Key.F:
|
||||
if (!hflip && !vflip) {
|
||||
hflip = true;
|
||||
} else if (hflip === true) {
|
||||
vflip = true;
|
||||
hflip = false;
|
||||
} else if (vflip === true) {
|
||||
vflip = false;
|
||||
if (hasModifier) break;
|
||||
if (!settings.hflip && !settings.vflip) {
|
||||
settings.hflip = true;
|
||||
} else if (settings.hflip === true && settings.vflip === true) {
|
||||
settings.vflip = false;
|
||||
settings.hflip = false;
|
||||
} else if (settings.hflip === true) {
|
||||
settings.vflip = true;
|
||||
settings.hflip = false;
|
||||
} else if (settings.vflip === true) {
|
||||
settings.hflip = true;
|
||||
}
|
||||
updatePage();
|
||||
break;
|
||||
case kthoom.Key.W:
|
||||
fitMode = kthoom.Key.W;
|
||||
updateScale();
|
||||
if (hasModifier) break;
|
||||
settings.fitMode = kthoom.Key.W;
|
||||
updateScale(false);
|
||||
break;
|
||||
case kthoom.Key.H:
|
||||
fitMode = kthoom.Key.H;
|
||||
updateScale();
|
||||
if (hasModifier) break;
|
||||
settings.fitMode = kthoom.Key.H;
|
||||
updateScale(false);
|
||||
break;
|
||||
case kthoom.Key.B:
|
||||
fitMode = kthoom.Key.B;
|
||||
updateScale();
|
||||
if (hasModifier) break;
|
||||
settings.fitMode = kthoom.Key.B;
|
||||
updateScale(false);
|
||||
break;
|
||||
case kthoom.Key.N:
|
||||
fitMode = kthoom.Key.N;
|
||||
updateScale();
|
||||
if (hasModifier) break;
|
||||
settings.fitMode = kthoom.Key.N;
|
||||
updateScale(false);
|
||||
break;
|
||||
case kthoom.Key.SPACE:
|
||||
var container = $('#mainContent');
|
||||
var atTop = container.scrollTop() === 0;
|
||||
var atBottom = container.scrollTop() >= container[0].scrollHeight - container.height();
|
||||
|
||||
if (evt.shiftKey && atTop) {
|
||||
evt.preventDefault();
|
||||
// If it's Shift + Space and the container is at the top of the page
|
||||
showPrevPage();
|
||||
} else if (!evt.shiftKey && atBottom) {
|
||||
evt.preventDefault();
|
||||
// If you're at the bottom of the page and you only pressed space
|
||||
showNextPage();
|
||||
container.scrollTop(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//console.log('KeyCode = ' + code);
|
||||
//console.log('KeyCode', evt.keyCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function init(filename) {
|
||||
if (!window.FileReader) {
|
||||
alert("Sorry, kthoom will not work with your browser because it does not support the File API. Please try kthoom with Chrome 12+ or Firefox 7+");
|
||||
/*function ImageLoadCallback() {
|
||||
var jso = this.response;
|
||||
// Unable to decompress file, or no response from server
|
||||
if (jso === null) {
|
||||
setImage("error");
|
||||
} else {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", filename);
|
||||
request.responseType = "arraybuffer";
|
||||
request.setRequestHeader("X-Test", "test1");
|
||||
request.setRequestHeader("X-Test", "test2");
|
||||
request.addEventListener("load", function(event) {
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
loadFromArrayBuffer(request.response);
|
||||
} else {
|
||||
console.warn(request.statusText, request.responseText);
|
||||
}
|
||||
});
|
||||
request.send();
|
||||
kthoom.initProgressMeter();
|
||||
document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : "";
|
||||
updateScale(true);
|
||||
kthoom.loadSettings();
|
||||
$(document).keydown(keyHandler);
|
||||
// IE 11 sometimes sees the response as a string
|
||||
if (typeof jso !== "object") {
|
||||
jso = JSON.parse(jso);
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
var f = (screen.width - innerWidth < 4 && screen.height - innerHeight < 4);
|
||||
getElem("titlebar").className = f ? "main" : "";
|
||||
updateScale();
|
||||
});
|
||||
if (jso.page !== jso.last) {
|
||||
this.open("GET", this.fileid + "/" + (jso.page + 1));
|
||||
this.addEventListener("load", ImageLoadCallback);
|
||||
this.send();
|
||||
}
|
||||
|
||||
$("#mainImage").click(function(evt) {
|
||||
// Firefox does not support offsetX/Y so we have to manually calculate
|
||||
// where the user clicked in the image.
|
||||
var mainContentWidth = $("#mainContent").width();
|
||||
var mainContentHeight = $("#mainContent").height();
|
||||
var comicWidth = evt.target.clientWidth;
|
||||
var comicHeight = evt.target.clientHeight;
|
||||
var offsetX = (mainContentWidth - comicWidth) / 2;
|
||||
var offsetY = (mainContentHeight - comicHeight) / 2;
|
||||
var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX);
|
||||
var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY);
|
||||
|
||||
// Determine if the user clicked/tapped the left side or the
|
||||
// right side of the page.
|
||||
var clickedPrev = false;
|
||||
switch (kthoom.rotateTimes) {
|
||||
case 0:
|
||||
clickedPrev = clickX < (comicWidth / 2);
|
||||
break;
|
||||
case 1:
|
||||
clickedPrev = clickY < (comicHeight / 2);
|
||||
break;
|
||||
case 2:
|
||||
clickedPrev = clickX > (comicWidth / 2);
|
||||
break;
|
||||
case 3:
|
||||
clickedPrev = clickY > (comicHeight / 2);
|
||||
break;
|
||||
}
|
||||
if (clickedPrev) {
|
||||
showPrevPage();
|
||||
} else {
|
||||
showNextPage();
|
||||
}
|
||||
});
|
||||
loadFromArrayBuffer(jso);
|
||||
}
|
||||
}*/
|
||||
function init(filename) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", filename);
|
||||
request.responseType = "arraybuffer";
|
||||
request.setRequestHeader("X-Test", "test1");
|
||||
request.setRequestHeader("X-Test", "test2");
|
||||
request.addEventListener("load", function(event) {
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
loadFromArrayBuffer(request.response);
|
||||
} else {
|
||||
console.warn(request.statusText, request.responseText);
|
||||
}
|
||||
});
|
||||
request.send();
|
||||
initProgressClick();
|
||||
document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : "";
|
||||
kthoom.loadSettings();
|
||||
updateScale(true);
|
||||
|
||||
$(document).keydown(keyHandler);
|
||||
|
||||
$(window).resize(function() {
|
||||
updateScale(false);
|
||||
});
|
||||
|
||||
// Open TOC menu
|
||||
$("#slider").click(function() {
|
||||
$("#sidebar").toggleClass("open");
|
||||
$("#main").toggleClass("closed");
|
||||
$(this).toggleClass("icon-menu icon-right");
|
||||
|
||||
// We need this in a timeout because if we call it during the CSS transition, IE11 shakes the page ¯\_(ツ)_/¯
|
||||
setTimeout(function(){
|
||||
// Focus on the TOC or the main content area, depending on which is open
|
||||
$('#main:not(.closed) #mainContent, #sidebar.open #tocView').focus();
|
||||
scrollTocToActive();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Open Settings modal
|
||||
$("#setting").click(function() {
|
||||
$("#settings-modal").toggleClass("md-show");
|
||||
});
|
||||
|
||||
// On Settings input change
|
||||
$("#settings input").on("change", function() {
|
||||
// Get either the checked boolean or the assigned value
|
||||
var value = this.type === "checkbox" ? this.checked : this.value;
|
||||
|
||||
// If it's purely numeric, parse it to an integer
|
||||
value = /^\d+$/.test(value) ? parseInt(value) : value;
|
||||
|
||||
settings[this.name] = value;
|
||||
updatePage();
|
||||
updateScale(false);
|
||||
});
|
||||
|
||||
// Close modal
|
||||
$(".closer, .overlay").click(function() {
|
||||
$(".md-show").removeClass("md-show");
|
||||
});
|
||||
|
||||
// TOC thumbnail pagination
|
||||
$("#thumbnails").on("click", "a", function() {
|
||||
currentImage = $(this).data("page") - 1;
|
||||
updatePage();
|
||||
});
|
||||
|
||||
// Fullscreen mode
|
||||
if (typeof screenfull !== "undefined") {
|
||||
$("#fullscreen").click(function() {
|
||||
screenfull.toggle($("#container")[0]);
|
||||
});
|
||||
|
||||
if (screenfull.raw) {
|
||||
var $button = $("#fullscreen");
|
||||
document.addEventListener(screenfull.raw.fullscreenchange, function() {
|
||||
screenfull.isFullscreen
|
||||
? $button.addClass("icon-resize-small").removeClass("icon-resize-full")
|
||||
: $button.addClass("icon-resize-full").removeClass("icon-resize-small");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Focus the scrollable area so that keyboard scrolling work as expected
|
||||
$('#mainContent').focus();
|
||||
|
||||
$("#mainImage").click(function(evt) {
|
||||
// Firefox does not support offsetX/Y so we have to manually calculate
|
||||
// where the user clicked in the image.
|
||||
var mainContentWidth = $("#mainContent").width();
|
||||
var mainContentHeight = $("#mainContent").height();
|
||||
var comicWidth = evt.target.clientWidth;
|
||||
var comicHeight = evt.target.clientHeight;
|
||||
var offsetX = (mainContentWidth - comicWidth) / 2;
|
||||
var offsetY = (mainContentHeight - comicHeight) / 2;
|
||||
var clickX = evt.offsetX ? evt.offsetX : (evt.clientX - offsetX);
|
||||
var clickY = evt.offsetY ? evt.offsetY : (evt.clientY - offsetY);
|
||||
|
||||
// Determine if the user clicked/tapped the left side or the
|
||||
// right side of the page.
|
||||
var clickedPrev = false;
|
||||
switch (settings.rotateTimes) {
|
||||
case 0:
|
||||
clickedPrev = clickX < (comicWidth / 2);
|
||||
break;
|
||||
case 1:
|
||||
clickedPrev = clickY < (comicHeight / 2);
|
||||
break;
|
||||
case 2:
|
||||
clickedPrev = clickX > (comicWidth / 2);
|
||||
break;
|
||||
case 3:
|
||||
clickedPrev = clickY > (comicHeight / 2);
|
||||
break;
|
||||
}
|
||||
if (clickedPrev) {
|
||||
showPrevPage();
|
||||
} else {
|
||||
showNextPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,76 @@
|
||||
/**
|
||||
* untar.js
|
||||
*
|
||||
* Copyright(c) 2011 Google Inc.
|
||||
*
|
||||
* Reference Documentation:
|
||||
*
|
||||
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
|
||||
*/
|
||||
/* global bitjs, importScripts, Uint8Array */
|
||||
* 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.js");
|
||||
importScripts("archive.js");
|
||||
importScripts('bytestream.js');
|
||||
importScripts('archive.js');
|
||||
|
||||
const UnarchiveState = {
|
||||
NOT_STARTED: 0,
|
||||
UNARCHIVING: 1,
|
||||
WAITING: 2,
|
||||
FINISHED: 3,
|
||||
};
|
||||
|
||||
// State - consider putting these into a class.
|
||||
let unarchiveState = UnarchiveState.NOT_STARTED;
|
||||
let bytestream = null;
|
||||
let allLocalFiles = null;
|
||||
let logToConsole = false;
|
||||
|
||||
// Progress variables.
|
||||
var currentFilename = "";
|
||||
var currentFileNumber = 0;
|
||||
var currentBytesUnarchivedInFile = 0;
|
||||
var currentBytesUnarchived = 0;
|
||||
var totalUncompressedBytesInArchive = 0;
|
||||
var totalFilesInArchive = 0;
|
||||
let currentFilename = "";
|
||||
let currentFileNumber = 0;
|
||||
let currentBytesUnarchivedInFile = 0;
|
||||
let currentBytesUnarchived = 0;
|
||||
let totalUncompressedBytesInArchive = 0;
|
||||
let totalFilesInArchive = 0;
|
||||
|
||||
// Helper functions.
|
||||
var info = function(str) {
|
||||
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
|
||||
const info = function(str) {
|
||||
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
|
||||
};
|
||||
var err = function(str) {
|
||||
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
|
||||
};
|
||||
var postProgress = function() {
|
||||
postMessage(new bitjs.archive.UnarchiveProgressEvent(
|
||||
currentFilename,
|
||||
currentFileNumber,
|
||||
currentBytesUnarchivedInFile,
|
||||
currentBytesUnarchived,
|
||||
totalUncompressedBytesInArchive,
|
||||
totalFilesInArchive));
|
||||
const err = function(str) {
|
||||
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
|
||||
};
|
||||
|
||||
// Removes all characters from the first zero-byte in the string onwards.
|
||||
var readCleanString = function(bstr, numBytes) {
|
||||
var str = bstr.readString(numBytes);
|
||||
var zIndex = str.indexOf(String.fromCharCode(0));
|
||||
return zIndex !== -1 ? str.substr(0, zIndex) : str;
|
||||
var str = bstr.readString(numBytes);
|
||||
var zIndex = str.indexOf(String.fromCharCode(0));
|
||||
return zIndex != -1 ? str.substr(0, zIndex) : str;
|
||||
};
|
||||
|
||||
// takes a ByteStream and parses out the local file information
|
||||
var TarLocalFile = function(bstream) {
|
||||
|
||||
const postProgress = function() {
|
||||
postMessage(new bitjs.archive.UnarchiveProgressEvent(
|
||||
currentFilename,
|
||||
currentFileNumber,
|
||||
currentBytesUnarchivedInFile,
|
||||
currentBytesUnarchived,
|
||||
totalUncompressedBytesInArchive,
|
||||
totalFilesInArchive,
|
||||
bytestream.getNumBytesRead(),
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
class TarLocalFile {
|
||||
// takes a ByteStream and parses out the local file information
|
||||
constructor(bstream) {
|
||||
this.isValid = false;
|
||||
|
||||
let bytesRead = 0;
|
||||
|
||||
// Read in the header block
|
||||
this.name = readCleanString(bstream, 100);
|
||||
this.mode = readCleanString(bstream, 8);
|
||||
@@ -60,23 +82,25 @@ var TarLocalFile = function(bstream) {
|
||||
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.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
|
||||
if (this.prefix.length) {
|
||||
this.name = this.prefix + this.name;
|
||||
}
|
||||
bstream.readBytes(12); // 512 - 500in
|
||||
} else {
|
||||
bstream.readBytes(255); // 512 - 257
|
||||
bstream.readBytes(255); // 512 - 257
|
||||
}
|
||||
|
||||
bytesRead += 512;
|
||||
|
||||
// Done header, now rest of blocks are the file contents.
|
||||
this.filename = this.name;
|
||||
this.fileData = null;
|
||||
@@ -86,84 +110,100 @@ var TarLocalFile = function(bstream) {
|
||||
info(" typeflag = " + this.typeflag);
|
||||
|
||||
// A regular file.
|
||||
if (this.typeflag === 0) {
|
||||
info(" This is a regular file.");
|
||||
// var sizeInBytes = parseInt(this.size);
|
||||
this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.size);
|
||||
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
|
||||
this.isValid = true;
|
||||
}
|
||||
if (this.typeflag == 0) {
|
||||
info(" This is a regular file.");
|
||||
const sizeInBytes = parseInt(this.size);
|
||||
this.fileData = new Uint8Array(bstream.readBytes(sizeInBytes));
|
||||
bytesRead += 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.
|
||||
var remaining = 512 - this.size % 512;
|
||||
if (remaining > 0 && remaining < 512) {
|
||||
bstream.readBytes(remaining);
|
||||
}
|
||||
} else if (this.typeflag === 5) {
|
||||
info(" This is a directory.");
|
||||
// Round up to 512-byte blocks.
|
||||
const remaining = 512 - bytesRead % 512;
|
||||
if (remaining > 0 && remaining < 512) {
|
||||
bstream.readBytes(remaining);
|
||||
}
|
||||
} else if (this.typeflag == 5) {
|
||||
info(" This is a directory.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const untar = function() {
|
||||
let bstream = bytestream.tee();
|
||||
|
||||
// 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) {
|
||||
// If we make it to this point and haven't thrown an error, we have successfully
|
||||
// read in the data for a local file, so we can update the actual bytestream.
|
||||
bytestream = bstream.tee();
|
||||
|
||||
allLocalFiles.push(oneLocalFile);
|
||||
totalUncompressedBytesInArchive += oneLocalFile.size;
|
||||
|
||||
// update progress
|
||||
currentFilename = oneLocalFile.filename;
|
||||
currentFileNumber = totalFilesInArchive++;
|
||||
currentBytesUnarchivedInFile = oneLocalFile.size;
|
||||
currentBytesUnarchived += oneLocalFile.size;
|
||||
postMessage(new bitjs.archive.UnarchiveExtractEvent(oneLocalFile));
|
||||
postProgress();
|
||||
}
|
||||
}
|
||||
totalFilesInArchive = allLocalFiles.length;
|
||||
|
||||
postProgress();
|
||||
|
||||
bytestream = bstream.tee();
|
||||
};
|
||||
|
||||
// Takes an ArrayBuffer of a tar file in
|
||||
// returns null on error
|
||||
// returns an array of DecompressedFile objects on success
|
||||
var untar = function(arrayBuffer) {
|
||||
// event.data.file has the first ArrayBuffer.
|
||||
// event.data.bytes has all subsequent ArrayBuffers.
|
||||
onmessage = function(event) {
|
||||
const bytes = event.data.file || event.data.bytes;
|
||||
logToConsole = !!event.data.logToConsole;
|
||||
|
||||
// This is the very first time we have been called. Initialize the bytestream.
|
||||
if (!bytestream) {
|
||||
bytestream = new bitjs.io.ByteStream(bytes);
|
||||
} else {
|
||||
bytestream.push(bytes);
|
||||
}
|
||||
|
||||
if (unarchiveState === UnarchiveState.NOT_STARTED) {
|
||||
currentFilename = "";
|
||||
currentFileNumber = 0;
|
||||
currentBytesUnarchivedInFile = 0;
|
||||
currentBytesUnarchived = 0;
|
||||
totalUncompressedBytesInArchive = 0;
|
||||
totalFilesInArchive = 0;
|
||||
|
||||
allLocalFiles = [];
|
||||
|
||||
postMessage(new bitjs.archive.UnarchiveStartEvent());
|
||||
var bstream = new bitjs.io.ByteStream(arrayBuffer);
|
||||
var localFiles = [];
|
||||
|
||||
// While we don't encounter an empty block, keep making TarLocalFiles.
|
||||
while (bstream.peekNumber(4) !== 0) {
|
||||
var 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(function(a, b) {
|
||||
var aname = a.filename.toLowerCase();
|
||||
var bname = b.filename.toLowerCase();
|
||||
return aname > bname ? 1 : -1;
|
||||
});
|
||||
|
||||
// report # files and total length
|
||||
if (localFiles.length > 0) {
|
||||
postProgress();
|
||||
}
|
||||
|
||||
// now do the shipping of each file
|
||||
for (var i = 0; i < localFiles.length; ++i) {
|
||||
var 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();
|
||||
}
|
||||
unarchiveState = UnarchiveState.UNARCHIVING;
|
||||
|
||||
postProgress();
|
||||
}
|
||||
|
||||
postMessage(new bitjs.archive.UnarchiveFinishEvent());
|
||||
};
|
||||
|
||||
// event.data.file has the ArrayBuffer.
|
||||
onmessage = function(event) {
|
||||
var ab = event.data.file;
|
||||
untar(ab);
|
||||
if (unarchiveState === UnarchiveState.UNARCHIVING ||
|
||||
unarchiveState === UnarchiveState.WAITING) {
|
||||
try {
|
||||
untar();
|
||||
unarchiveState = UnarchiveState.FINISHED;
|
||||
postMessage(new bitjs.archive.UnarchiveFinishEvent());
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' && e.startsWith('Error! Overflowed')) {
|
||||
// Overrun the buffer.
|
||||
unarchiveState = UnarchiveState.WAITING;
|
||||
} else {
|
||||
console.error('Found an error while untarring');
|
||||
console.dir(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
||||
* DEFLATE format: http://tools.ietf.org/html/rfc1951
|
||||
*/
|
||||
/* global bitjs, importScripts, Uint8Array */
|
||||
/* global bitjs */
|
||||
|
||||
// This file expects to be invoked as a Worker (see onmessage below).
|
||||
importScripts("io.js");
|
||||
@@ -44,12 +44,12 @@ var zLocalFileHeaderSignature = 0x04034b50;
|
||||
var zArchiveExtraDataSignature = 0x08064b50;
|
||||
var zCentralFileHeaderSignature = 0x02014b50;
|
||||
var zDigitalSignatureSignature = 0x05054b50;
|
||||
//var zEndOfCentralDirSignature = 0x06064b50;
|
||||
//var zEndOfCentralDirLocatorSignature = 0x07064b50;
|
||||
var zEndOfCentralDirSignature = 0x06064b50;
|
||||
var zEndOfCentralDirLocatorSignature = 0x07064b50;
|
||||
|
||||
// takes a ByteStream and parses out the local file information
|
||||
var ZipLocalFile = function(bstream) {
|
||||
if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber !== typeof function() {} ) {
|
||||
if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ var ZipLocalFile = function(bstream) {
|
||||
// "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 & bitjs.BIT[3]) !== 0) {
|
||||
if ((this.generalPurpose & bitjs.BIT[3]) != 0) {
|
||||
this.crc32 = bstream.readNumber(4);
|
||||
this.compressedSize = bstream.readNumber(4);
|
||||
this.uncompressedSize = bstream.readNumber(4);
|
||||
@@ -111,15 +111,17 @@ var ZipLocalFile = function(bstream) {
|
||||
ZipLocalFile.prototype.unzip = function() {
|
||||
|
||||
// Zip Version 1.0, no compression (store only)
|
||||
if (this.compressionMethod === 0 ) {
|
||||
info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
if (this.compressionMethod == 0 ) {
|
||||
info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
currentBytesUnarchivedInFile = this.compressedSize;
|
||||
currentBytesUnarchived += this.compressedSize;
|
||||
} else if (this.compressionMethod === 8) {
|
||||
// version == 20, compression method == 8 (DEFLATE)
|
||||
}
|
||||
// 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 {
|
||||
}
|
||||
else {
|
||||
err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
|
||||
this.fileData = null;
|
||||
}
|
||||
@@ -142,10 +144,10 @@ var unzip = function(arrayBuffer) {
|
||||
|
||||
var bstream = new bitjs.io.ByteStream(arrayBuffer);
|
||||
// detect local file header signature or return null
|
||||
if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
|
||||
if (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
|
||||
var localFiles = [];
|
||||
// loop until we don't see any more local files
|
||||
while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
|
||||
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
|
||||
var oneLocalFile = new ZipLocalFile(bstream);
|
||||
// this should strip out directories/folders
|
||||
if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
|
||||
@@ -156,14 +158,14 @@ var unzip = function(arrayBuffer) {
|
||||
totalFilesInArchive = localFiles.length;
|
||||
|
||||
// got all local files, now sort them
|
||||
localFiles.sort(function(a, b) {
|
||||
localFiles.sort(function(a,b) {
|
||||
var aname = a.filename.toLowerCase();
|
||||
var bname = b.filename.toLowerCase();
|
||||
return aname > bname ? 1 : -1;
|
||||
});
|
||||
|
||||
// archive extra data record
|
||||
if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
|
||||
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) {
|
||||
info(" Found an Archive Extra Data Signature");
|
||||
|
||||
// skipping this record for now
|
||||
@@ -174,11 +176,11 @@ var unzip = function(arrayBuffer) {
|
||||
|
||||
// central directory structure
|
||||
// TODO: handle the rest of the structures (Zip64 stuff)
|
||||
if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
|
||||
if (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
|
||||
info(" Found a Central File Header");
|
||||
|
||||
// read all file headers
|
||||
while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
|
||||
while (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
|
||||
bstream.readNumber(4); // signature
|
||||
bstream.readNumber(2); // version made by
|
||||
bstream.readNumber(2); // version needed to extract
|
||||
@@ -204,7 +206,7 @@ var unzip = function(arrayBuffer) {
|
||||
}
|
||||
|
||||
// digital signature
|
||||
if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
|
||||
if (bstream.peekNumber(4) == zDigitalSignatureSignature) {
|
||||
info(" Found a Digital Signature");
|
||||
|
||||
bstream.readNumber(4);
|
||||
@@ -229,68 +231,66 @@ var unzip = function(arrayBuffer) {
|
||||
// actually do the unzipping
|
||||
localfile.unzip();
|
||||
|
||||
if (localfile.fileData !== null) {
|
||||
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) {
|
||||
var len;
|
||||
// ensure bitLengths is an array containing at least one element
|
||||
if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
|
||||
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
|
||||
var numLengths = bitLengths.length,
|
||||
blCount = [],
|
||||
bl_count = [],
|
||||
MAX_BITS = 1;
|
||||
|
||||
|
||||
// Step 1: count up how many codes of each length we have
|
||||
for (var i = 0; i < numLengths; ++i) {
|
||||
len = bitLengths[i];
|
||||
var length = bitLengths[i];
|
||||
// test to ensure each bit length is a positive, non-zero number
|
||||
if (typeof len !== typeof 1 || len < 0) {
|
||||
err("bitLengths contained an invalid number in getHuffmanCodes(): " + len + " of type " + (typeof len));
|
||||
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 (blCount[len] === undefined) blCount[len] = 0;
|
||||
if (bl_count[length] == undefined) bl_count[length] = 0;
|
||||
// a length of zero means this symbol is not participating in the huffman coding
|
||||
if (len > 0) blCount[len]++;
|
||||
if (length > 0) bl_count[length]++;
|
||||
|
||||
if (len > MAX_BITS) MAX_BITS = len;
|
||||
if (length > MAX_BITS) MAX_BITS = length;
|
||||
}
|
||||
|
||||
// Step 2: Find the numerical value of the smallest code for each code length
|
||||
var nextCode = [],
|
||||
var next_code = [],
|
||||
code = 0;
|
||||
for (var bits = 1; bits <= MAX_BITS; ++bits) {
|
||||
len = bits - 1;
|
||||
var length = bits-1;
|
||||
// ensure undefined lengths are zero
|
||||
if (blCount[len] === undefined) blCount[len] = 0;
|
||||
code = (code + blCount[bits - 1]) << 1;
|
||||
nextCode[bits] = code;
|
||||
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
|
||||
var table = {}, tableLength = 0;
|
||||
for (var n = 0; n < numLengths; ++n) {
|
||||
len = bitLengths[n];
|
||||
if (len !== 0) {
|
||||
table[nextCode[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode[len],len) };
|
||||
var len = bitLengths[n];
|
||||
if (len != 0) {
|
||||
table[next_code[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(next_code[len],len) };
|
||||
tableLength++;
|
||||
nextCode[len]++;
|
||||
next_code[len]++;
|
||||
}
|
||||
}
|
||||
table.maxLength = tableLength;
|
||||
@@ -318,11 +318,10 @@ function getHuffmanCodes(bitLengths) {
|
||||
var fixedHCtoLiteral = null;
|
||||
var fixedHCtoDistance = null;
|
||||
function getFixedLiteralTable() {
|
||||
var i;
|
||||
// create once
|
||||
if (!fixedHCtoLiteral) {
|
||||
var bitlengths = new Array(288);
|
||||
for (i = 0; i <= 143; ++i) bitlengths[i] = 8;
|
||||
for (var i = 0; i <= 143; ++i) bitlengths[i] = 8;
|
||||
for (i = 144; i <= 255; ++i) bitlengths[i] = 9;
|
||||
for (i = 256; i <= 279; ++i) bitlengths[i] = 7;
|
||||
for (i = 280; i <= 287; ++i) bitlengths[i] = 8;
|
||||
@@ -336,9 +335,7 @@ function getFixedDistanceTable() {
|
||||
// create once
|
||||
if (!fixedHCtoDistance) {
|
||||
var bitlengths = new Array(32);
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
bitlengths[i] = 5;
|
||||
}
|
||||
for (var i = 0; i < 32; ++i) { bitlengths[i] = 5; }
|
||||
|
||||
// get huffman code table
|
||||
fixedHCtoDistance = getHuffmanCodes(bitlengths);
|
||||
@@ -350,17 +347,17 @@ function getFixedDistanceTable() {
|
||||
// then return that symbol
|
||||
function decodeSymbol(bstream, hcTable) {
|
||||
var code = 0, len = 0;
|
||||
// var match = false;
|
||||
var match = false;
|
||||
|
||||
// loop until we match
|
||||
for (;;) {
|
||||
// read in next bit
|
||||
var bit = bstream.readBits(1);
|
||||
code = (code << 1) | bit;
|
||||
code = (code<<1) | bit;
|
||||
++len;
|
||||
|
||||
// check against Huffman Code table and break if found
|
||||
if (hcTable.hasOwnProperty(code) && hcTable[code].length === len) {
|
||||
if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) {
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -375,31 +372,31 @@ function decodeSymbol(bstream, hcTable) {
|
||||
|
||||
|
||||
var 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
|
||||
*/
|
||||
/*
|
||||
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
|
||||
|
||||
*/
|
||||
var 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]
|
||||
[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
|
||||
@@ -417,20 +414,20 @@ var LengthLookupTable = [
|
||||
9 3 25-32 19 8 769-1024 29 13 24577-32768
|
||||
*/
|
||||
var 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]
|
||||
[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) {
|
||||
@@ -449,10 +446,10 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
|
||||
stream, and copy length bytes from this
|
||||
position to the output stream.
|
||||
*/
|
||||
var blockSize = 0;
|
||||
var numSymbols = 0, blockSize = 0;
|
||||
for (;;) {
|
||||
var symbol = decodeSymbol(bstream, hcLiteralTable);
|
||||
// ++numSymbols;
|
||||
++numSymbols;
|
||||
if (symbol < 256) {
|
||||
// copy literal byte to output
|
||||
buffer.insertByte(symbol);
|
||||
@@ -460,11 +457,11 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
|
||||
}
|
||||
else {
|
||||
// end of block reached
|
||||
if (symbol === 256) {
|
||||
if (symbol == 256) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var lengthLookup = LengthLookupTable[symbol - 257],
|
||||
var lengthLookup = LengthLookupTable[symbol-257],
|
||||
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]),
|
||||
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)],
|
||||
distance = distLookup[1] + bstream.readBits(distLookup[0]);
|
||||
@@ -482,13 +479,13 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
|
||||
// loop for each character
|
||||
var ch = buffer.ptr - distance;
|
||||
blockSize += length;
|
||||
if (length > distance) {
|
||||
var data = buffer.data;
|
||||
while (length--) {
|
||||
buffer.insertByte(data[ch++]);
|
||||
}
|
||||
if(length > distance) {
|
||||
var data = buffer.data;
|
||||
while (length--) {
|
||||
buffer.insertByte(data[ch++]);
|
||||
}
|
||||
} else {
|
||||
buffer.insertBytes(buffer.data.subarray(ch, ch + length));
|
||||
buffer.insertBytes(buffer.data.subarray(ch, ch + length))
|
||||
}
|
||||
|
||||
} // length-distance pair
|
||||
@@ -507,38 +504,37 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
compressedData.byteOffset,
|
||||
compressedData.byteLength);
|
||||
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
|
||||
//var numBlocks = 0;
|
||||
var blockSize = 0;
|
||||
var bFinal;
|
||||
var numBlocks = 0, blockSize = 0;
|
||||
|
||||
// block format: http://tools.ietf.org/html/rfc1951#page-9
|
||||
do {
|
||||
bFinal = bstream.readBits(1);
|
||||
var bType = bstream.readBits(2);
|
||||
var bFinal = bstream.readBits(1),
|
||||
bType = bstream.readBits(2);
|
||||
blockSize = 0;
|
||||
// ++numBlocks;
|
||||
++numBlocks;
|
||||
// no compression
|
||||
if (bType === 0) {
|
||||
if (bType == 0) {
|
||||
// skip remaining bits in this byte
|
||||
while (bstream.bitPtr !== 0) bstream.readBits(1);
|
||||
var len = bstream.readBits(16);
|
||||
// nlen = bstream.readBits(16);
|
||||
while (bstream.bitPtr != 0) bstream.readBits(1);
|
||||
var len = bstream.readBits(16),
|
||||
nlen = bstream.readBits(16);
|
||||
// TODO: check if nlen is the ones-complement of len?
|
||||
|
||||
if (len > 0) buffer.insertBytes(bstream.readBytes(len));
|
||||
if(len > 0) buffer.insertBytes(bstream.readBytes(len));
|
||||
blockSize = len;
|
||||
}
|
||||
// fixed Huffman codes
|
||||
else if (bType === 1) {
|
||||
else if(bType == 1) {
|
||||
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
|
||||
}
|
||||
// dynamic Huffman codes
|
||||
else if (bType === 2) {
|
||||
else if(bType == 2) {
|
||||
var numLiteralLengthCodes = bstream.readBits(5) + 257;
|
||||
var numDistanceCodes = bstream.readBits(5) + 1,
|
||||
numCodeLengthCodes = bstream.readBits(4) + 4;
|
||||
|
||||
// populate the array of code length codes (first de-compaction)
|
||||
var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
var codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
for (var i = 0; i < numCodeLengthCodes; ++i) {
|
||||
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
|
||||
}
|
||||
@@ -566,25 +562,24 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
var prevCodeLength = 0;
|
||||
while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
|
||||
var symbol = decodeSymbol(bstream, codeLengthsCodes);
|
||||
var repeat;
|
||||
if (symbol <= 15) {
|
||||
literalCodeLengths.push(symbol);
|
||||
prevCodeLength = symbol;
|
||||
}
|
||||
else if (symbol === 16) {
|
||||
repeat = bstream.readBits(2) + 3;
|
||||
else if (symbol == 16) {
|
||||
var repeat = bstream.readBits(2) + 3;
|
||||
while (repeat--) {
|
||||
literalCodeLengths.push(prevCodeLength);
|
||||
}
|
||||
}
|
||||
else if (symbol === 17) {
|
||||
repeat = bstream.readBits(3) + 3;
|
||||
else if (symbol == 17) {
|
||||
var repeat = bstream.readBits(3) + 3;
|
||||
while (repeat--) {
|
||||
literalCodeLengths.push(0);
|
||||
}
|
||||
}
|
||||
else if (symbol === 18) {
|
||||
repeat = bstream.readBits(7) + 11;
|
||||
else if (symbol == 18) {
|
||||
var repeat = bstream.readBits(7) + 11;
|
||||
while (repeat--) {
|
||||
literalCodeLengths.push(0);
|
||||
}
|
||||
@@ -610,7 +605,7 @@ function inflate(compressedData, numDecompressedBytes) {
|
||||
currentBytesUnarchived += blockSize;
|
||||
postProgress();
|
||||
|
||||
} while (bFinal !== 1);
|
||||
} while (bFinal != 1);
|
||||
// we are done reading blocks if the bFinal bit was set for this block
|
||||
|
||||
// return the buffer data bytes
|
||||
|
||||
Reference in New Issue
Block a user