Compare commits

...
Sign in to create a new pull request.

45 commits

Author SHA1 Message Date
Bala Clark
57627ed2c2 Update README.md 2015-07-21 00:17:35 +02:00
Bala Clark
8e9a61abbe Update README.md 2015-07-20 23:59:13 +02:00
Bala Clark
5f179b978d update readme 2015-07-20 23:57:44 +02:00
Bala Clark
19f74b5912 allow preload to start from a given image 2015-07-20 23:49:44 +02:00
Bala Clark
108e696d6b allow the last page in double page mode to be blank 2015-07-20 22:54:45 +02:00
Bala Clark
226a772452 only render in double page mode once both pages have been preloaded 2015-07-20 22:39:31 +02:00
Bala Clark
f441053e7f Merge branch 'release/1.0.0' of github.com:balaclark/HTML5-Comic-Book-Reader into release/1.0.0 2015-07-19 20:07:49 +02:00
Bala Clark
83ea19cc21 add watchify 2015-07-19 17:51:40 +02:00
Bala Clark
648890a2c0 remove shrinkwrap for now 2015-07-19 17:26:47 +02:00
Bala Clark
26e6d148f3 remove shrinkwrap for now 2015-07-19 17:24:52 +02:00
Bala Clark
3c064e8b04 phantomjs optional 2015-07-19 17:17:59 +02:00
Bala Clark
18de4c4526 move phantomjs to peer dependecies 2015-07-19 17:16:02 +02:00
Bala Clark
b2c5349113 re-draw comic when page is resized 2015-07-19 15:00:38 +02:00
Bala Clark
0995ea3ca3 make sure that the requested page has been loaded before drawing it 2015-07-19 14:47:53 +02:00
Bala Clark
ee877d0f1b double page mode, sinon.spy instead of spy, pass opts directly to drawImage 2015-07-19 14:12:08 +02:00
Bala Clark
8ab949583f adding draw page methods, preparing double page mode 2015-07-19 10:07:11 +02:00
Bala Clark
78e5fcd049 many improvements
* draw single images
* integrate load overlay properly
* preload:ready event
2015-07-19 00:16:22 +02:00
Bala Clark
d8cbdc709d copy css images when building css 2015-07-19 00:14:25 +02:00
Bala Clark
33c57f75dc progress bar 2015-07-18 20:20:02 +02:00
Bala Clark
7ddb94ad77 skip failing comic draw test for now 2015-07-18 17:59:47 +02:00
Bala Clark
cb9b7e7c16 use arrow functions 2015-07-18 17:59:21 +02:00
Bala Clark
0238164789 use browserify-runtime on npm test 2015-07-18 17:59:05 +02:00
Bala Clark
f63ac2e286 remove jquery dep, use native dom api instead 2015-07-18 17:16:49 +02:00
Bala Clark
155edf4935 port drawPage function 2015-07-18 16:58:53 +02:00
Bala Clark
a1766f5e54 improve canvas imagediff tests 2015-07-17 10:34:59 +02:00
Bala Clark
c6551102f8 starting to port canvas drawing code 2015-07-16 23:33:46 +02:00
Bala Clark
e6d46b593b starting canvas module 2015-07-16 10:29:33 +02:00
Bala Clark
7ccbdac37b use arrow function syntax where possible 2015-07-15 11:07:02 +02:00
Bala Clark
7772805c25 show the load indicator when beginning to preload 2015-07-15 09:47:28 +02:00
Bala Clark
a422c90982 add default value to image srcs 2015-07-15 09:14:24 +02:00
Bala Clark
0bfbe97344 basic image preloading 2015-07-15 09:10:09 +02:00
Bala Clark
8c6a76a61a remove chai as it doesn't work in strict mode 2015-07-15 05:03:23 +02:00
Bala Clark
5d69f23ff2 adding npm shrinkwrap 2015-07-14 23:21:31 +02:00
Bala Clark
5d8791dba7 upgrade jquery 2015-07-14 23:20:36 +02:00
Bala Clark
dd71c9cdd9 remove stricify for now as it doesn't play nice with handlebars 2015-07-14 23:19:36 +02:00
Bala Clark
8f2ab74683 minify js 2015-07-14 23:07:03 +02:00
Bala Clark
d642df8e46 build css 2015-07-14 22:42:37 +02:00
Bala Clark
0b29b1d1db update travis 2015-07-14 10:36:41 +02:00
Bala Clark
75b3d0adc9 load indicator module 2015-07-14 10:14:57 +02:00
Bala Clark
cbe68047f5 use mochify instead of mocha-phantomjs 2015-07-13 23:00:38 +02:00
Bala Clark
58c59de4e2 setting up test runner 2015-07-13 22:38:53 +02:00
Bala Clark
5bac5a1b8d starting new build 2015-07-13 20:51:17 +02:00
Bala Clark
b1a95d1e7a cleaning house before rewrite 2015-07-13 19:17:59 +02:00
Bala Clark
94c507be15 minify css, upgrade uglify-js 2015-07-13 18:16:09 +02:00
Bala Clark
f363959bc0 use standard instead of jshint 2015-07-13 10:57:39 +02:00
125 changed files with 4896 additions and 9284 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.DS_Store .DS_Store
.project .project
.settings .settings
tags
lib/tests/pid.txt lib/tests/pid.txt
comicbook comicbook

View file

@ -1,5 +1,4 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.12"
before_script: sudo: false
- make

View file

@ -1,52 +0,0 @@
#
# build package & update examples
#
build:
@echo "Running jshint..."
@./node_modules/.bin/jshint lib/ComicBook.js --config lib/.jshintrc
@echo "Compiling Handlebars templates..."
@./node_modules/.bin/handlebars templates/*.handlebars -f lib/templates.js
@echo "Compiling and minifying javascript..."
@mkdir -p comicbook/js/pixastic
@cat lib/vendor/pixastic/pixastic.js lib/vendor/pixastic/pixastic.effects.js lib/vendor/pixastic/pixastic.worker.js lib/vendor/handlebars.runtime-1.0.rc.1.min.js lib/templates.js lib/ComicBook.js > comicbook/js/comicbook.js
@cp lib/vendor/pixastic/pixastic.js comicbook/js/pixastic
@cp lib/vendor/pixastic/pixastic.effects.js comicbook/js/pixastic
@cp lib/vendor/pixastic/pixastic.worker.js comicbook/js/pixastic
@cp lib/vendor/pixastic/pixastic.worker.control.js comicbook/js/pixastic
@cp lib/vendor/pixastic/license-gpl-3.0.txt comicbook/js/pixastic
@cp lib/vendor/pixastic/license-mpl.txt comicbook/js/pixastic
@./node_modules/.bin/uglifyjs -nc comicbook/js/comicbook.js > comicbook/js/comicbook.min.js
@echo "Compiling CSS..."
@cat fonts/icomoon-toolbar/style.css css/reset.css css/styles.css css/toolbar.css > comicbook/comicbook.css
@echo "Copying assets..."
@cp -r css/img comicbook/img
@cp -r icons/1_Desktop_Icons/icon_128.png comicbook/img
@cp -r icons/1_Desktop_Icons/icon_196.png comicbook/img
@cp -r fonts/icomoon-toolbar/fonts comicbook
@cp -r fonts/icomoon-toolbar/license.txt comicbook/fonts
@echo "Updating examples"
@cp -r comicbook examples
@echo "Done"
#
# run jshint & quint tests
#
test:
@./node_modules/.bin/jshint lib/ComicBook.js --config lib/.jshintrc
@./node_modules/.bin/jshint lib/tests/unit/*.js --config lib/.jshintrc
@node lib/tests/server.js &
@./node_modules/.bin/phantomjs lib/tests/phantom.js "http://localhost:3000/lib/tests"
@kill -9 `cat lib/tests/pid.txt`
@rm lib/tests/pid.txt
#
# remove prior builds
#
clean:
@rm -r comicbook
@rm -r examples/comicbook
@rm lib/templates.js

View file

@ -2,6 +2,7 @@ Comic Book Reader
================= =================
[![Build Status](https://api.travis-ci.org/balaclark/HTML5-Comic-Book-Reader.png)](https://travis-ci.org/balaclark/HTML5-Comic-Book-Reader) [![Build Status](https://api.travis-ci.org/balaclark/HTML5-Comic-Book-Reader.png)](https://travis-ci.org/balaclark/HTML5-Comic-Book-Reader)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
A canvas based web application for reading comics. You can also see an implementation A canvas based web application for reading comics. You can also see an implementation
of this as an offline Chrome packaged application CBZ / CBR comic book reader at: of this as an offline Chrome packaged application CBZ / CBR comic book reader at:
@ -17,14 +18,40 @@ Development Install
Builds require nodejs and npm. Installs have been tested with nodejs 0.10.0, older Builds require nodejs and npm. Installs have been tested with nodejs 0.10.0, older
versions may or may not work. versions may or may not work.
npm install ```sh
make npm install
make test npm run build
npm test
```
In order to run the test suite you will need phantomjs installed, if you don't
have it already installed globally:
```sh
npm install phantomjs@1.9
npm test
```
Contribute
----------
Contributions are welcome, use the standard code style and [ES6](https://github.com/lukehoban/es6features#readme). Code is split into modules using browserify.
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
This project aims to have an absolute minimum of external dependencies (dev dependencies are more acceptable).
To auto build js and auto run tests use the following commands in seperate terminals.
```sh
npm run buildjs-watch
npm run test-watch
```
Copyright and License Copyright and License
--------------------- ---------------------
Copyright 2010 Bala Clark Copyright 2010-2015 Bala Clark
Licensed under the Apache License, Version 2.0 (the "License"); you may not use Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this work except in compliance with the License. You may obtain a copy of the this work except in compliance with the License. You may obtain a copy of the

21
TODO
View file

@ -1,21 +0,0 @@
Fo sho:
- remember reading position / applied effects (per comic)
- offline mode via cache manifest
- firefox marketplace
- create onclose callback, show close button only if set
- trigger preload if requesting valid but not loaded images (can happen if network was interupted)
- loading and generally hackiness of pointer is buggy, fix.
- check for html5 feature support where used: diveintohtml5.org/everything.html or www.modernizr.com
Nice 2 have:
- remove image inhancement lag when navigating by pre-applying enhancements to other pages
- jump to page?
- make page draggable with the cursor
- enable menu items via config, allow for custom items
- split out classes into seperate files
- thumbnail browser
- refactor so we are not using all these loose shared variables and other nastyness
- use custom event emitters instead of hacky code
- properly bind 'this' so we don't have to keep using 'self'
- allow toolbar to be hidden on mobile (maybe show a small translucent button that opens the toolbar when clicked)
- enhancement progress bar

154
app/comic-book.js Normal file
View file

@ -0,0 +1,154 @@
let EventEmitter = require('events').EventEmitter
let Canvas = require('./view/canvas')
let LoadIndicator = require('./view/load-indicator')
let ProgressBar = require('./view/progress-bar')
class ComicBook extends EventEmitter {
constructor (srcs = [], options) {
super()
this.options = Object.assign({
// manga mode
rtl: false,
doublePage: false
}, options)
// requested image srcs
this.srcs = srcs
// loaded image objects
this.pages = new Map()
this.preloadBuffer = 4
// TODO move this logic into the router
this.currentPageIndex = 0
this.canvas = new Canvas()
this.loadIndicator = new LoadIndicator()
this.progressBar = new ProgressBar()
this.addEventListeners()
}
addEventListeners () {
this.on('preload:start', this.loadIndicator.show.bind(this.loadIndicator))
this.on('preload:start', this.progressBar.show.bind(this.progressBar))
this.on('preload:image', this.updateProgressBar.bind(this))
this.on('preload:ready', this.loadIndicator.hide.bind(this.loadIndicator))
this.on('preload:ready', this.drawPage.bind(this))
this.on('preload:finish', this.progressBar.hide.bind(this.progressBar))
}
render () {
this.pageRendered = false
this.el = document.createElement('div')
this.el.appendChild(this.canvas.canvas)
this.el.appendChild(this.progressBar.el)
this.el.appendChild(this.loadIndicator.el)
this.drawPage()
return this
}
// TODO use a queue, only allow x concurrent downloads at a time
// TODO preload in both directions
// TODO fire ready on forward direction only
preload (startIndex) {
this.emit('preload:start')
if (startIndex == null || startIndex >= this.srcs.length) {
startIndex = this.currentPageIndex
}
// reorder srcs to start from the requested index
let _srcs = this.srcs.slice()
let srcs = _srcs.splice(startIndex).concat(_srcs)
this.currentPageIndex = startIndex
srcs.forEach((src, pageIndex) => {
// allow preload to be run multiple times without duplicating requests
if (this.pages.has(pageIndex)) return
let image = new window.Image()
image.src = src
image.onload = setImage.bind(this, image, pageIndex)
function setImage (image, index) {
this.pages.set(index, image)
this.emit('preload:image', image)
if (this.pages.size >= this.preloadBuffer && !this.pageRendered) {
this.emit('preload:ready')
}
if (this.pages.size === this.srcs.length) {
this.emit('preload:finish')
}
}
})
}
updateProgressBar () {
let percentage = Math.floor((this.pages.size / this.srcs.length) * 100)
this.progressBar.update(percentage)
}
drawPage (pageIndex) {
if (typeof pageIndex !== 'number') pageIndex = this.currentPageIndex
let page = this.pages.get(pageIndex)
// if the requested image hasn't been loaded yet, force another preload run
if (!page) return this.preload(pageIndex)
let args = [ page ]
if (this.options.doublePage) {
let page2Index = pageIndex + 1
let page2 = this.pages.get(page2Index)
if (page2Index <= (this.pages.size - 1) && !page2) {
return this.preload(page2Index)
}
args.push(page2)
if (this.options.rtl) {
args.reverse()
}
}
args.push(this.options)
try {
this.canvas.drawImage.apply(this.canvas, args)
this.currentPageIndex = pageIndex
this.pageRendered = true
} catch (e) {
if (e.message !== 'Invalid image') throw e
}
}
drawNextPage () {
let increment = this.options.doublePage ? 2 : 1
let index = this.currentPageIndex + increment
if (index >= this.pages.size) {
index = this.pages.size - 1
}
this.drawPage(index)
}
drawPreviousPage () {
let increment = this.options.doublePage ? 2 : 1
let index = this.currentPageIndex - increment
if (index < 0) index = 0
this.drawPage(index)
}
}
module.exports = ComicBook

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

@ -49,6 +49,11 @@ body:not(.mobile) .navigate:hover {
opacity: 0.8; opacity: 0.8;
background: #000 url("img/loading.gif") no-repeat center; background: #000 url("img/loading.gif") no-repeat center;
box-shadow: none; box-shadow: none;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
} }
#cb-status { #cb-status {

25
app/index.js Normal file
View file

@ -0,0 +1,25 @@
let ComicBook = window.ComicBook = require('./comic-book')
let debounce = require('lodash.debounce')
let srcs = [
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_00.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_01.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_02.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_03.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_04.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_05.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_06.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_07.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_08.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_09.jpg',
'https://raw.githubusercontent.com/balaclark/HTML5-Comic-Book-Reader/master/examples/goldenboy/goldenboy_10.jpg'
]
let comic = window.comic = new ComicBook(srcs, { doublePage: true })
comic.render().drawPage(5)
window.addEventListener('resize', debounce(comic.drawPage.bind(comic), 100))
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(comic.el)
}, false)

9
app/lib/make-image.js Normal file
View file

@ -0,0 +1,9 @@
module.exports = function makeImage (src, cb) {
let image = new window.Image()
image.onload = () => {
cb(image)
}
image.src = src
}

137
app/view/canvas.js Normal file
View file

@ -0,0 +1,137 @@
let EventEmitter = require('events').EventEmitter
// TODO replace
function windowWidth () {
return window.innerWidth
}
class Canvas extends EventEmitter {
constructor (options) {
super()
this.canvas = document.createElement('canvas')
this.context = this.canvas.getContext('2d')
}
drawImage (page, page2, opts = {}) {
this.emit('draw:start')
if (page2 === null || !(page2 instanceof window.Image)) {
opts = page2 || opts
}
let options = Object.assign({
doublePage: false,
zoomMode: 'fitWidth'
}, opts)
if (!(page instanceof window.Image) || (options.doublePage && page2 === null)) {
throw new Error('Invalid image')
}
let zoomScale
let offsetW = 0
let offsetH = 0
let width = page.width
let height = page.height
let doublePageMode = options.doublePage
let canvasWidth
let canvasHeight
let pageWidth
let pageHeight
// reset the canvas to stop duplicate pages showing
this.canvas.width = 0
this.canvas.height = 0
// show double page spreads on a single page
let isDoublePageSpread = (
page2 &&
(page.width > page.height || page2.width > page2.height) &&
doublePageMode
)
if (isDoublePageSpread) doublePageMode = false
if (doublePageMode) {
// for double page spreads, factor in the width of both pages
if (page2 instanceof window.Image) {
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 this.scale if a non manual mode has been chosen
switch (options.zoomMode) {
case 'manual':
document.body.style.overflowX = 'auto'
zoomScale = (doublePageMode) ? this.scale * 2 : this.scale
break
case 'fitWidth':
document.body.style.overflowX = 'hidden'
// this.scale up if the window is wider than the page, scale down if the window
// is narrower than the page
zoomScale = (windowWidth() > width) ? ((windowWidth() - width) / windowWidth()) + 1 : windowWidth() / width
this.scale = zoomScale
break
case 'fitWindow':
document.body.style.overflowX = 'hidden'
let widthScale = (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
let windowHeight = window.innerHeight
let heightScale = (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
zoomScale = (widthScale > heightScale) ? heightScale : widthScale
this.scale = zoomScale
break
}
canvasWidth = page.width * zoomScale
canvasHeight = page.height * zoomScale
pageWidth = (options.zoomMode === 'manual') ? page.width * this.scale : canvasWidth
pageHeight = (options.zoomMode === 'manual') ? page.height * this.scale : canvasHeight
canvasHeight = pageHeight
// make sure the canvas is always at least full screen, even if the page is narrower than the screen
this.canvas.width = (canvasWidth < windowWidth()) ? windowWidth() : canvasWidth
this.canvas.height = (canvasHeight < window.innerHeight) ? window.innerHeight : canvasHeight
// always keep pages centered
if (options.zoomMode === 'manual' || options.zoomMode === 'fitWindow') {
// work out a horizontal position
if (canvasWidth < windowWidth()) {
offsetW = (windowWidth() - pageWidth) / 2
if (options.doublePage) { offsetW = offsetW - pageWidth / 2 }
}
// work out a vertical position
if (canvasHeight < window.innerHeight) {
offsetH = (window.innerHeight - pageHeight) / 2
}
}
// draw the page(s)
this.context.drawImage(page, offsetW, offsetH, pageWidth, pageHeight)
if (options.doublePage && typeof page2 === 'object') {
this.context.drawImage(page2, pageWidth + offsetW, offsetH, pageWidth, pageHeight)
}
this.emit('draw:finish')
}
}
module.exports = Canvas

View file

@ -0,0 +1,27 @@
let EventEmitter = require('events').EventEmitter
class LoadIndicator extends EventEmitter {
constructor () {
super()
this.render().hide()
}
render () {
this.el = document.createElement('div')
this.el.id = 'cb-loading-overlay'
return this
}
show () {
this.el.style.display = 'block'
this.emit('show', this)
}
hide () {
this.el.style.display = 'none'
this.emit('hide', this)
}
}
module.exports = LoadIndicator

29
app/view/progress-bar.js Normal file
View file

@ -0,0 +1,29 @@
let template = require('./template/progress-bar.handlebars')
class ProgressBar {
constructor () {
this.createElements()
this.hide()
}
createElements () {
let el = document.createElement('div')
el.innerHTML = template()
this.el = el.firstChild
this.progressEl = this.el.querySelector('.progressbar-value')
}
update (percentage) {
this.progressEl.style.width = `${percentage}%`
}
show () {
this.el.style.display = 'block'
}
hide () {
this.el.style.display = 'none'
}
}
module.exports = ProgressBar

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 646 B

After

Width:  |  Height:  |  Size: 646 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

398
dist/comicbook.css vendored Normal file
View file

@ -0,0 +1,398 @@
@font-face {
font-family: 'toolbar';
src:url('fonts/toolbar.eot');
src:url('fonts/toolbar.eot?#iefix') format('embedded-opentype'),
url('fonts/toolbar.woff') format('woff'),
url('fonts/toolbar.ttf') format('truetype'),
url('fonts/toolbar.svg#toolbar') format('svg');
font-weight: normal;
font-style: normal;
}
/* Use the following CSS code if you want to use data attributes for inserting your icons */
[data-icon]:before {
font-family: 'toolbar';
content: attr(data-icon);
speak: none;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
/* Use the following CSS code if you want to have a class per icon */
/*
Instead of a list of all class selectors,
you can use the generic selector below, but it's slower:
[class*="icon-"] {
*/
.icon-file, .icon-image, .icon-zoom-out, .icon-zoom-in, .icon-expand, .icon-expand-2, .icon-folder-open, .icon-folder, .icon-cog, .icon-menu, .icon-wrench, .icon-settings, .icon-loop, .icon-pin, .icon-first, .icon-last, .icon-arrow-left, .icon-arrow-right, .icon-arrow-left-2, .icon-arrow-right-2, .icon-arrow-left-3, .icon-arrow-right-3, .icon-previous, .icon-next, .icon-droplet, .icon-adjust, .icon-sun, .icon-remove-sign, .icon-remove, .icon-copy {
font-family: 'toolbar';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
.icon-file:before {
content: "\e000";
}
.icon-image:before {
content: "\e001";
}
.icon-zoom-out:before {
content: "\e002";
}
.icon-zoom-in:before {
content: "\e003";
}
.icon-expand:before {
content: "\e004";
}
.icon-expand-2:before {
content: "\e005";
}
.icon-folder-open:before {
content: "\e006";
}
.icon-folder:before {
content: "\e007";
}
.icon-cog:before {
content: "\e008";
}
.icon-menu:before {
content: "\e009";
}
.icon-wrench:before {
content: "\e00a";
}
.icon-settings:before {
content: "\e00b";
}
.icon-loop:before {
content: "\e00c";
}
.icon-pin:before {
content: "\e00d";
}
.icon-first:before {
content: "\e00e";
}
.icon-last:before {
content: "\e00f";
}
.icon-arrow-left:before {
content: "\e011";
}
.icon-arrow-right:before {
content: "\e010";
}
.icon-arrow-left-2:before {
content: "\e012";
}
.icon-arrow-right-2:before {
content: "\e013";
}
.icon-arrow-left-3:before {
content: "\e014";
}
.icon-arrow-right-3:before {
content: "\e015";
}
.icon-previous:before {
content: "\e016";
}
.icon-next:before {
content: "\e017";
}
.icon-droplet:before {
content: "\e01a";
}
.icon-adjust:before {
content: "\f042";
}
.icon-sun:before {
content: "\e018";
}
.icon-remove-sign:before {
content: "\f057";
}
.icon-remove:before {
content: "\f00d";
}
.icon-copy:before {
content: "\e037";
}
/* v1.0 | 20080212 */
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;
}
.cb-control {
font-family: helvetica, arial, sans-serif;
font-size: 12px;
}
.cb-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: 128px;
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: 35%;
position: relative;
}
body:not(.mobile) .navigate:hover {
opacity: 1;
}
.navigate-left {
left: 0;
}
.navigate-right {
right: 0;
}
#cb-loading-overlay {
z-index: 100;
opacity: 0.8;
background: #000 url("img/loading.gif") no-repeat center;
box-shadow: none;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
#cb-status {
z-index: 101;
font-size: 12px;
right: 0;
bottom: 0;
margin: 8px;
border-radius: 4px;
}
#cb-progress-bar {
width: 200px;
}
#cb-progress-bar,
#cb-progress-bar .progressbar-value {
height: 3px;
}
#cb-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 {
color: white;
background-color: black;
background-image: linear-gradient(to bottom, rgb(80, 80, 80), rgb(17, 17, 17));
overflow: visible;
padding: 0.75em;
position: fixed;
left: 0;
right: 0;
z-index: 99;
margin-bottom: 0;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.mobile .toolbar {
opacity: 1;
}
.toolbar:hover {
opacity: 1;
}
.toolbar li {
display: inline-block;
position: relative;
}
.toolbar .separator {
border: solid 1px;
height: 1em;
}
.toolbar button {
color: white;
border: none;
background-color: transparent;
padding: 0;
}
.toolbar li > button {
font-size: 1.5em;
padding: 0 12px;
}
.mobile .toolbar li > button {
padding: 0 9px;
}
.toolbar li > button:hover {
color: #8CC746;
}
.toolbar button[data-action=close]:hover {
color: #FF6464;
}
.toolbar .dropdown {
font-size: 1em;
position: absolute;
width: 212px;
background-color: white;
color: #111;
border-radius: 4px;
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
top: 2em;
padding: 4px 0;
display: none;
}
body:not(.mobile) .toolbar li:hover > .dropdown {
display: block;
}
/* dropdown arrow code taken from Twitter Bootstrap 2.3.1 */
.toolbar .dropdown:after {
position: absolute;
top: -4px;
left: 15px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
border-left: 6px solid transparent;
content: '';
}
.toolbar .close {
display: none;
}
.dropdown .control-group {
padding: 8px;
}
.dropdown .sliders {
font-size: 1.5em;
}
.dropdown .control-group span {
float: left;
margin: 0 2px;
clear: both;
}
.dropdown .control-group input[type=range] {
width: 171px;
float: right;
margin: 0;
}

2855
dist/comicbook.js vendored Normal file

File diff suppressed because it is too large Load diff

139
dist/comicbook.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/comicbook.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3
dist/comicbook.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/comicbook.min.js.map vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/img/loading.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

67
examples/basic.html Executable file → Normal file
View file

@ -1,62 +1,11 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf8"> <title>Basic Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/> <link rel="stylesheet" href="../dist/comicbook.css">
<meta name="mobile-web-app-capable" content="yes"> <script src='../dist/comicbook.js'></script>
<meta name="apple-mobile-web-app-capable" content="yes"> </head>
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <body>
<title>Comic Book Reader</title> </body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="comicbook/js/comicbook.min.js"></script>
<link rel="stylesheet" href="comicbook/comicbook.css">
<link rel="shortcut icon" sizes="196x196" href="../icons/1_Desktop_icons/icon_196.png">
<link rel="apple-touch-icon" sizes="128x128" href="../icons/1_Desktop_icons/con_128.png">
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="../icons/1_Desktop_icons/con_128.png">
</head>
<body>
<canvas id="comic"></canvas>
<script>
var book = new ComicBook('comic', [
'goldenboy/goldenboy_00.jpg',
'goldenboy/goldenboy_01.jpg',
'goldenboy/goldenboy_02.jpg',
'goldenboy/goldenboy_03.jpg',
'goldenboy/goldenboy_04.jpg',
'goldenboy/goldenboy_05.jpg',
'goldenboy/goldenboy_06.jpg',
'goldenboy/goldenboy_07.jpg',
'goldenboy/goldenboy_08.jpg',
'goldenboy/goldenboy_09.jpg',
'goldenboy/goldenboy_10.jpg',
'goldenboy/goldenboy_11.jpg',
'goldenboy/goldenboy_12.jpg',
'goldenboy/goldenboy_13.jpg',
'goldenboy/goldenboy_14.jpg',
'goldenboy/goldenboy_15.jpg',
'goldenboy/goldenboy_16.jpg',
'goldenboy/goldenboy_17.jpg',
'goldenboy/goldenboy_18.jpg',
'goldenboy/goldenboy_19.jpg',
'goldenboy/goldenboy_20.jpg',
'goldenboy/goldenboy_21.jpg',
'goldenboy/goldenboy_22.jpg',
'goldenboy/goldenboy_23.jpg',
'goldenboy/goldenboy_24.jpg',
'goldenboy/goldenboy_25.jpg'
], {
libPath: "/HTML5-Comic-Book-Reader/examples/comicbook/js/"
});
book.draw();
$(window).on('resize', function(event) {
book.draw();
});
</script>
</body>
</html> </html>

View file

@ -1 +0,0 @@
bitjs: Binary Tools for JavaScript

View file

@ -1,340 +0,0 @@
/**
* archive.js
*
* Provides base functionality for unarchiving.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*/
var bitjs = bitjs || {};
bitjs.archive = bitjs.archive || {};
(function() {
// ===========================================================================
// Stolen from Closure because it's the best way to do Java-like inheritance.
bitjs.base = function(me, opt_methodName, var_args) {
var caller = arguments.callee.caller;
if (caller.superClass_) {
// This is a constructor. Call the superclass constructor.
return caller.superClass_.constructor.apply(
me, Array.prototype.slice.call(arguments, 1));
}
var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = false;
for (var ctor = me.constructor;
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
if (ctor.prototype[opt_methodName] === caller) {
foundCaller = true;
} else if (foundCaller) {
return ctor.prototype[opt_methodName].apply(me, args);
}
}
// If we did not find the caller in the prototype chain,
// then one of two things happened:
// 1) The caller is an instance method.
// 2) This method was not called by the right caller.
if (me[opt_methodName] === caller) {
return me.constructor.prototype[opt_methodName].apply(me, args);
} else {
throw Error(
'goog.base called from a method of one name ' +
'to a method of a different name');
}
};
bitjs.inherits = function(childCtor, parentCtor) {
/** @constructor */
function tempCtor() {};
tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor;
};
// ===========================================================================
/**
* An unarchive event.
*
* @param {string} type The event type.
* @constructor
*/
bitjs.archive.UnarchiveEvent = function(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).
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveInfoEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
/**
* An unrecoverable error has occured.
*
* @param {string} msg The error message.
*/
bitjs.archive.UnarchiveErrorEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
/**
* Start event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveStartEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
};
bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
/**
* Finish event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveFinishEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
};
bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
/**
* Progress event.
*/
bitjs.archive.UnarchiveProgressEvent = function(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
this.currentFilename = currentFilename;
this.currentFileNumber = currentFileNumber;
this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
this.totalFilesInArchive = totalFilesInArchive;
this.currentBytesUnarchived = currentBytesUnarchived;
this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
};
bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
/**
* All extracted files returned by an Unarchiver will implement
* the following interface:
*
* interface UnarchivedFile {
* string filename
* TypedArray fileData
* }
*
*/
/**
* Extract event.
*/
bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/**
* @type {UnarchivedFile}
*/
this.unarchivedFile = unarchivedFile;
};
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
/**
* Base class for all Unarchivers.
*
* @param {ArrayBuffer} arrayBuffer The Array Buffer.
* @param {string} opt_pathToBitJS Optional string for where the BitJS files are located.
* @constructor
*/
bitjs.archive.Unarchiver = function(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.<string, Array>}
*/
this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
}
};
/**
* Private web worker initialized during start().
* @type {Worker}
* @private
*/
bitjs.archive.Unarchiver.prototype.worker_ = null;
/**
* This method must be overridden by the subclass to return the script filename.
* @return {string} The script filename.
* @protected.
*/
bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()';
};
/**
* Adds an event listener for UnarchiveEvents.
*
* @param {string} Event type.
* @param {function} An event handler function.
*/
bitjs.archive.Unarchiver.prototype.addEventListener = function(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.
*/
bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) {
if (type in this.listeners_) {
var 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
*/
bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) {
if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
this.listeners_[e.type] instanceof Array) {
this.listeners_[e.type].forEach(function (listener) { listener(e) });
} else {
console.log(e);
}
};
/**
* Starts the unarchive in a separate Web Worker thread and returns immediately.
*/
bitjs.archive.Unarchiver.prototype.start = function() {
var me = this;
var 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});
}
}
/**
* Unzipper
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unzipper = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
bitjs.archive.Unzipper.prototype.getScriptFileName = function() { return 'unzip.js' };
/**
* Unrarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unrarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { return 'unrar.js' };
/**
* Untarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Untarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
bitjs.archive.Untarrer.prototype.getScriptFileName = function() { return 'untar.js' };
})();

View file

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html><head>
<script src="io.js"></script>
<script src="archive.js"></script>
</head>
<body>
<input id='filechooser' type='file'></input>
</body>
<script>
function assertTrue(a) {
if (!a) {
alert("'" + a + "' not true");
}
}
function assertEquals(a,b) {
if (a != b) {
alert("'" + a + "' != '" + b + "'")
}
}
function testAddRemoveEventListeners() {
var progress = function(e) { alert('progress') };
var progress2 = function(e) { alert('progress2') };
var ua = new bitjs.archive.Unarchiver(null);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress2);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress2);
assertEquals(2, ua.listeners_[bitjs.archive.UnarchiveEvent.Type.PROGRESS].length);
ua.removeEventListener(bitjs.archive.UnarchiveEvent.Type.START, progress);
assertEquals(2, ua.listeners_[bitjs.archive.UnarchiveEvent.Type.PROGRESS].length);
ua.removeEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, progress);
assertEquals(1, ua.listeners_[bitjs.archive.UnarchiveEvent.Type.PROGRESS].length);
}
function testAbstractUnarchiveThrows() {
var ua = new bitjs.archive.Unarchiver(null);
try {
ua.start();
alert("unarchive() didn't throw");
} catch(e) {}
}
// unit tests
// testAddRemoveEventListeners();
// testAbstractUnarchiveThrows();
// var uz = new bitjs.archive.Unzipper(null);
document.body.querySelector("#filechooser").addEventListener("change",
function(evt) {
var inp = evt.target;
var filelist = inp.files;
if (filelist.length == 1) {
var blob = filelist[0];
var fr = new FileReader();
fr.onload = function() {
var ua = new bitjs.archive.Unzipper(fr.result);
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.INFO, function(e) {
console.log("info: " + e.msg);
})
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS, function(e) {
console.log("progress: " + e.msg);
})
ua.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT, function(e) {
console.log("extract: " + e.unarchiveFile.filename);
})
ua.start();
};
fr.readAsArrayBuffer(blob);
}
}, false);
</script>
</html>

View file

@ -1,310 +0,0 @@
/*
* io.js
*
* Provides readers for bit/byte streams (reading) and a byte buffer (writing).
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
// mask for getting the Nth bit (zero-based)
bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000];
// mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
/**
* This bit stream peeks and consumes bits out of a binary stream.
*
* {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false).
* {Number} opt_offset The offset into the ArrayBuffer
* {Number} opt_length The length of this BitStream
*/
bitjs.io.BitStream = function(ab, rtl, opt_offset, opt_length) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
}
var offset = opt_offset || 0;
var 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.
bitjs.io.BitStream.prototype.peekBits_ltr = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bitsIn = 0,
bytes = this.bytes;
// 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;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
var mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++;
bitPtr = 0;
bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte;
}
else {
var mask = (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.
bitjs.io.BitStream.prototype.peekBits_rtl = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var movePointers = movePointers || false,
bytePtr = this.bytePtr,
bitPtr = this.bitPtr,
result = 0,
bytes = this.bytes;
// 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;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++;
bitPtr = 0;
n -= numBitsLeftInThisByte;
}
else {
result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
//some voodoo magic
bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr+1] & 0xff) << 8) +
((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff);
};
bitjs.io.BitStream.prototype.readBits = function(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.
bitjs.io.BitStream.prototype.peekBytes = function(n, 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);
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr,
bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) {
this.bytePtr += n;
}
return result;
};
bitjs.io.BitStream.prototype.readBytes = function( n ) {
return this.peekBytes(n, true);
};
/**
* This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer.
*
* This object is much easier to write than the above BitStream since
* everything is byte-aligned.
*
* {ArrayBuffer} ab The ArrayBuffer object.
* {Number} opt_offset The offset into the ArrayBuffer
* {Number} opt_length The length of this BitStream
*/
bitjs.io.ByteStream = function(ab, opt_offset, opt_length) {
var offset = opt_offset || 0;
var 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?
bitjs.io.ByteStream.prototype.peekNumber = function( n ) {
// TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n != typeof 1)
return -1;
var result = 0;
// read from last byte to first byte and roll them in
var 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
bitjs.io.ByteStream.prototype.readNumber = function( n ) {
var num = this.peekNumber( n );
this.ptr += n;
return num;
};
// This returns n bytes as a sub-array, advancing the pointer if movePointers
// is true.
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) {
this.ptr += n;
}
return result;
};
bitjs.io.ByteStream.prototype.readBytes = function( n ) {
return this.peekBytes(n, true);
};
// peeks at the next n bytes as a string but does not advance the pointer
bitjs.io.ByteStream.prototype.peekString = function( n ) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]);
}
return result;
};
// returns the next n bytes as a string
// and advances the stream pointer n bytes
bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n);
this.ptr += n;
return strToReturn;
};
/**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
*/
bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes != typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
}
this.data = new Uint8Array(numBytes);
this.ptr = 0;
};
bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid?
this.data[this.ptr++] = b;
};
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr);
this.ptr += bytes.length;
};
})();

View file

@ -1,906 +0,0 @@
/**
* unrar.js
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*
* Reference Documentation:
*
* http://kthoom.googlecode.com/hg/docs/unrar.html
*/
// This file expects to be invoked as a Worker (see onmessage below).
importScripts('io.js');
importScripts('archive.js');
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var 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));
};
// shows a byte value as its hex representation
var nibble = "0123456789ABCDEF";
var byteValueToHexString = function(num) {
return nibble[num>>4] + nibble[num&0xF];
};
var twoByteValueToHexString = function(num) {
return nibble[(num>>12)&0xF] + nibble[(num>>8)&0xF] + nibble[(num>>4)&0xF] + nibble[num&0xF];
};
// Volume Types
var MARK_HEAD = 0x72,
MAIN_HEAD = 0x73,
FILE_HEAD = 0x74,
COMM_HEAD = 0x75,
AV_HEAD = 0x76,
SUB_HEAD = 0x77,
PROTECT_HEAD = 0x78,
SIGN_HEAD = 0x79,
NEWSUB_HEAD = 0x7a,
ENDARC_HEAD = 0x7b;
// bstream is a bit stream
var RarVolumeHeader = function(bstream) {
var 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);
for (var _i = 0, _s = ''; _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
var extTimeFlags = bstream.readBits(16);
// this is adapted straight out of arcread.cpp, Archive::ReadHeader()
for (var I = 0; I < 4; ++I) {
var rmode = extTimeFlags >> ((3-I)*4);
if ((rmode & 8)==0)
continue;
if (I!=0)
bstream.readBits(16);
var count = (rmode&3);
for (var 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;
}
};
var BLOCK_LZ = 0,
BLOCK_PPM = 1;
var 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],
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],
rDBitLengthCounts = [4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12],
rSDDecode = [0,4,8,16,32,64,128,192],
rSDBits = [2,2,3, 4, 5, 6, 6, 6];
var 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];
var 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];
var rLOW_DIST_REP_COUNT = 16;
var rNC = 299,
rDC = 60,
rLDC = 17,
rRC = 28,
rBC = 20,
rHUFF_TABLE_SIZE = (rNC+rDC+rRC+rLDC);
var UnpBlockType = BLOCK_LZ;
var UnpOldTable = new Array(rHUFF_TABLE_SIZE);
var BD = { //bitdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rBC)
};
var LD = { //litdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rNC)
};
var DD = { //distdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rDC)
};
var LDD = { //low dist decode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rLDC)
};
var RD = { //rep decode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rRC)
};
var rBuffer;
// read in Huffman tables for RAR
function RarReadTables(bstream) {
var BitLength = new Array(rBC),
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 (var i = UnpOldTable.length; i--;) UnpOldTable[i] = 0;
}
// read in bit lengths
for (var I = 0; I < rBC; ++I) {
var Length = bstream.readBits(4);
if (Length == 15) {
var 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);
var TableSize = rHUFF_TABLE_SIZE;
//console.log(DecodeLen, DecodePos, DecodeNum);
for (var i = 0; i < TableSize;) {
var num = RarDecodeNumber(bstream, BD);
if (num < 16) {
Table[i] = (num + UnpOldTable[i]) & 0xf;
i++;
} else if(num < 18) {
var N = (num == 16) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11);
while (N-- > 0 && i < TableSize) {
Table[i] = Table[i - 1];
i++;
}
} else {
var 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 (var i = UnpOldTable.length; i--;) {
UnpOldTable[i] = Table[i];
}
return true;
}
function RarDecodeNumber(bstream, dec) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum;
var bitField = bstream.getBits() & 0xfffe;
//some sort of rolled out binary search
var 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);
var N = DecodePos[bits] + ((bitField - DecodeLen[bits -1]) >>> (16 - bits));
return DecodeNum[N];
}
function RarMakeDecodeTables(BitLength, offset, dec, size) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum;
var LenCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
TmpPos = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
N = 0, M = 0;
for (var i = DecodeNum.length; i--;) DecodeNum[i] = 0;
for (var i = 0; i < size; i++) {
LenCount[BitLength[i + offset] & 0xF]++;
}
LenCount[0] = 0;
TmpPos[0] = 0;
DecodePos[0] = 0;
DecodeLen[0] = 0;
for (var 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 (I = 0; I < size; ++I)
if (BitLength[I + offset] != 0)
DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I;
}
// TODO: implement
function Unpack15(bstream, Solid) {
info("ERROR! RAR 1.5 compression not supported");
}
function Unpack20(bstream, Solid) {
var destUnpSize = rBuffer.data.length;
var oldDistPtr = 0;
RarReadTables20(bstream);
while (destUnpSize > rBuffer.ptr) {
var num = RarDecodeNumber(bstream, LD);
if (num < 256) {
rBuffer.insertByte(num);
continue;
}
if (num > 269) {
var Length = rLDecode[num -= 270] + 3;
if ((Bits = rLBits[num]) > 0) {
Length += bstream.readBits(Bits);
}
var DistNumber = RarDecodeNumber(bstream, DD);
var 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) {
var Distance = rOldDist[(oldDistPtr - (num - 256)) & 3];
var LengthNumber = RarDecodeNumber(bstream, RD);
var 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) {
var 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() {
var change = rBuffer.ptr - currentBytesUnarchivedInFile;
currentBytesUnarchivedInFile = rBuffer.ptr;
currentBytesUnarchived += change;
postProgress();
}
var rNC20 = 298,
rDC20 = 48,
rRC20 = 28,
rBC20 = 19,
rMC20 = 257;
var UnpOldTable20 = new Array(rMC20 * 4);
function RarReadTables20(bstream) {
var BitLength = new Array(rBC20);
var Table = new Array(rMC20 * 4);
var TableSize, N, I;
var AudioBlock = bstream.readBits(1);
if (!bstream.readBits(1))
for (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = 0;
TableSize = rNC20 + rDC20 + rRC20;
for (var I = 0; I < rBC20; I++)
BitLength[I] = bstream.readBits(4);
RarMakeDecodeTables(BitLength, 0, BD, rBC20);
I = 0;
while (I < TableSize) {
var 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 (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i];
}
var lowDistRepCount = 0, prevLowDist = 0;
var rOldDist = [0,0,0,0];
var lastDist;
var lastLength;
function Unpack29(bstream, Solid) {
// lazy initialize rDDecode and rDBits
var DDecode = new Array(rDC);
var DBits = new Array(rDC);
var Dist=0,BitLength=0,Slot=0;
for (var I = 0; I < rDBitLengthCounts.length; I++,BitLength++) {
for (var J = 0; J < rDBitLengthCounts[I]; J++,Slot++,Dist+=(1<<BitLength)) {
DDecode[Slot]=Dist;
DBits[Slot]=BitLength;
}
}
var Bits;
//tablesRead = false;
rOldDist = [0,0,0,0]
lastDist = 0;
lastLength = 0;
for (var i = UnpOldTable.length; i--;) UnpOldTable[i] = 0;
// read in Huffman tables
RarReadTables(bstream);
while (true) {
var num = RarDecodeNumber(bstream, LD);
if (num < 256) {
rBuffer.insertByte(num);
continue;
}
if (num >= 271) {
var Length = rLDecode[num -= 271] + 3;
if ((Bits = rLBits[num]) > 0) {
Length += bstream.readBits(Bits);
}
var DistNumber = RarDecodeNumber(bstream, DD);
var 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 {
var 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) {
//console.log("READVMCODE");
if (!RarReadVMCode(bstream)) break;
continue;
}
if (num == 258) {
if (lastLength != 0) {
RarCopyString(lastLength, lastDist);
}
continue;
}
if (num < 263) {
var DistNum = num - 259;
var Distance = rOldDist[DistNum];
for (var I = DistNum; I > 0; I--) {
rOldDist[I] = rOldDist[I-1];
}
rOldDist[0] = Distance;
var LengthNumber = RarDecodeNumber(bstream, RD);
var Length = rLDecode[LengthNumber] + 2;
if ((Bits = rLBits[LengthNumber]) > 0) {
Length += bstream.readBits(Bits);
}
RarInsertLastMatch(Length, Distance);
RarCopyString(Length, Distance);
continue;
}
if (num < 272) {
var Distance = rSDDecode[num -= 263] + 1;
if ((Bits = rSDBits[num]) > 0) {
Distance += bstream.readBits(Bits);
}
RarInsertOldDist(Distance);
RarInsertLastMatch(2, Distance);
RarCopyString(2, Distance);
continue;
}
}
RarUpdateProgress()
}
function RarReadEndOfBlock(bstream) {
RarUpdateProgress()
var NewTable = false, NewFile = false;
if (bstream.readBits(1)) {
NewTable = true;
} else {
NewFile = true;
NewTable = !!bstream.readBits(1);
}
//tablesRead = !NewTable;
return !(NewFile || NewTable && !RarReadTables(bstream));
}
function RarReadVMCode(bstream) {
var FirstByte = bstream.readBits(8);
var Length = (FirstByte & 7) + 1;
if (Length == 7) {
Length = bstream.readBits(8) + 7;
} else if(Length == 8) {
Length = bstream.readBits(16);
}
var vmCode = [];
for(var I = 0; I < Length; I++) {
//do something here with cheking readbuf
vmCode.push(bstream.readBits(8));
}
return RarAddVMCode(FirstByte, vmCode, Length);
}
function RarAddVMCode(firstByte, vmCode, length) {
//console.log(vmCode);
if (vmCode.length > 0) {
info("Error! RarVM not supported yet!");
}
return true;
}
function RarInsertLastMatch(length, distance) {
lastDist = distance;
lastLength = length;
}
function RarInsertOldDist(distance) {
rOldDist.splice(3,1);
rOldDist.splice(0,0,distance);
}
//this is the real function, the other one is for debugging
function RarCopyString(length, distance) {
var destPtr = rBuffer.ptr - distance;
if(destPtr < 0){
var l = rOldBuffers.length;
while(destPtr < 0){
destPtr = rOldBuffers[--l].data.length + destPtr
}
//TODO: lets hope that it never needs to read beyond file boundaries
while(length--) rBuffer.insertByte(rOldBuffers[l].data[destPtr++]);
}
if (length > distance) {
while(length--) rBuffer.insertByte(rBuffer.data[destPtr++]);
} else {
rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length));
}
}
var rOldBuffers = []
// v must be a valid RarVolume
function unpack(v) {
// TODO: implement what happens when unpVer is < 15
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer,
Solid = v.header.LHD_SOLID,
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
Unpack29(bstream, Solid);
break;
} // switch(method)
rOldBuffers.push(rBuffer);
//TODO: clear these old buffers when there's over 4MB of history
return rBuffer.data;
}
// bstream is a bit stream
var RarLocalFile = function(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;
}
}
};
RarLocalFile.prototype.unrar = function() {
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;
} else {
this.isValid = true;
this.fileData = unpack(this);
}
}
}
var unrar = function(arrayBuffer) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
postMessage(new bitjs.archive.UnarchiveStartEvent());
var bstream = new bitjs.io.BitStream(arrayBuffer, false /* rtl */);
var header = new RarVolumeHeader(bstream);
if (header.crc == 0x6152 &&
header.headType == 0x72 &&
header.flags.value == 0x1A21 &&
header.headSize == 7) {
info("Found RAR signature");
var mhead = new RarVolumeHeader(bstream);
if (mhead.headType != MAIN_HEAD) {
info("Error! RAR did not include a MAIN_HEAD header");
}
else {
var localFiles = [],
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
// TODO: unpack
localFiles = localFiles.sort(function(a,b) {
// extract the number at the end of both filenames
var aname = a.filename;
var bname = b.filename;
return aname > bname ? 1 : -1;
/*
var aindex = aname.length, bindex = bname.length;
// Find the last number character from the back of the filename.
while (aname[aindex-1] < '0' || aname[aindex-1] > '9') --aindex;
while (bname[bindex-1] < '0' || bname[bindex-1] > '9') --bindex;
// Find the first number character from the back of the filename
while (aname[aindex-1] >= '0' && aname[aindex-1] <= '9') --aindex;
while (bname[bindex-1] >= '0' && bname[bindex-1] <= '9') --bindex;
// parse them into numbers and return comparison
var anum = parseInt(aname.substr(aindex), 10),
bnum = parseInt(bname.substr(bindex), 10);
return bnum - anum;*/
});
info(localFiles.map(function(a){return a.filename}).join(', '));
for (var i = 0; i < localFiles.length; ++i) {
var 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) {
var ab = event.data.file;
unrar(ab, true);
};

View file

@ -1,182 +0,0 @@
/**
* untar.js
*
* 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');
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var 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));
};
// 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;
};
// takes a ByteStream and parses out the local file information
var TarLocalFile = function(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.");
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;
}
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.")
}
};
// Takes an ArrayBuffer of a tar file in
// returns null on error
// returns an array of DecompressedFile objects on success
var untar = function(arrayBuffer) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
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) {
// extract the number at the end of both filenames
var aname = a.filename;
var bname = b.filename;
var aindex = aname.length, bindex = bname.length;
// Find the last number character from the back of the filename.
while (aname[aindex-1] < '0' || aname[aindex-1] > '9') --aindex;
while (bname[bindex-1] < '0' || bname[bindex-1] > '9') --bindex;
// Find the first number character from the back of the filename
while (aname[aindex-1] >= '0' && aname[aindex-1] <= '9') --aindex;
while (bname[bindex-1] >= '0' && bname[bindex-1] <= '9') --bindex;
// parse them into numbers and return comparison
var anum = parseInt(aname.substr(aindex), 10),
bnum = parseInt(bname.substr(bindex), 10);
return anum - bnum;
});
// 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();
}
postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent());
};
// event.data.file has the ArrayBuffer.
onmessage = function(event) {
var ab = event.data.file;
untar(ab);
};

View file

@ -1,631 +0,0 @@
/**
* unzip.js
*
* 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).
importScripts('io.js');
importScripts('archive.js');
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var 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));
};
var zLocalFileHeaderSignature = 0x04034b50;
var zArchiveExtraDataSignature = 0x08064b50;
var zCentralFileHeaderSignature = 0x02014b50;
var zDigitalSignatureSignature = 0x05054b50;
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(){}) {
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.bytes.buffer, bstream.ptr, this.compressedSize);
bstream.ptr += 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 & bitjs.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
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)");
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
var unzip = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent());
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
currentBytesUnarchived = 0;
var bstream = new bitjs.io.ByteStream(arrayBuffer);
// detect local file header signature or return null
if (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
var localFiles = [];
// loop until we don't see any more local files
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
var 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(function(a,b) {
// extract the number at the end of both filenames
var aname = a.filename;
var bname = b.filename;
var aindex = aname.length, bindex = bname.length;
// Find the last number character from the back of the filename.
while (aname[aindex-1] < '0' || aname[aindex-1] > '9') --aindex;
while (bname[bindex-1] < '0' || bname[bindex-1] > '9') --bindex;
// Find the first number character from the back of the filename
while (aname[aindex-1] >= '0' && aname[aindex-1] <= '9') --aindex;
while (bname[bindex-1] >= '0' && bname[bindex-1] <= '9') --bindex;
// parse them into numbers and return comparison
var anum = parseInt(aname.substr(aindex), 10),
bnum = parseInt(bname.substr(bindex), 10);
return anum - bnum;
});
// archive extra data record
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) {
info(" Found an Archive Extra Data Signature");
// skipping this record for now
bstream.readNumber(4);
var 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
var fileNameLength = bstream.readNumber(2); // file name length
var extraFieldLength = bstream.readNumber(2); // extra field length
var 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);
var 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 (var i = 0; i < localFiles.length; ++i) {
var 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
var numLengths = bitLengths.length,
bl_count = [],
MAX_BITS = 1;
// Step 1: count up how many codes of each length we have
for (var i = 0; i < numLengths; ++i) {
var 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
var next_code = [],
code = 0;
for (var bits = 1; bits <= MAX_BITS; ++bits) {
var 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
var table = {}, tableLength = 0;
for (var n = 0; n < numLengths; ++n) {
var 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
var fixedHCtoLiteral = null;
var fixedHCtoDistance = null;
function getFixedLiteralTable() {
// create once
if (!fixedHCtoLiteral) {
var bitlengths = new Array(288);
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;
// get huffman code table
fixedHCtoLiteral = getHuffmanCodes(bitlengths);
}
return fixedHCtoLiteral;
}
function getFixedDistanceTable() {
// create once
if (!fixedHCtoDistance) {
var bitlengths = new Array(32);
for (var 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) {
var code = 0, len = 0;
var match = false;
// loop until we match
for (;;) {
// read in next bit
var 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;
}
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
*/
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]
];
/*
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
*/
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]
];
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.
*/
var numSymbols = 0, blockSize = 0;
for (;;) {
var 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 {
var lengthLookup = LengthLookupTable[symbol-257],
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]),
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)],
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 <length = 5, distance = 2>
// adds X,Y,X,Y,X to the output stream."
//
// loop for each character
var ch = buffer.ptr - distance;
blockSize += length;
if(length > distance) {
var 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.
var bstream = new bitjs.io.BitStream(compressedData.buffer,
false /* rtl */,
compressedData.byteOffset,
compressedData.byteLength);
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
var numBlocks = 0, blockSize = 0;
// block format: http://tools.ietf.org/html/rfc1951#page-9
do {
var bFinal = bstream.readBits(1),
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);
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));
blockSize = len;
}
// fixed Huffman codes
else if(bType == 1) {
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
}
// dynamic Huffman codes
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];
for (var i = 0; i < numCodeLengthCodes; ++i) {
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
}
// get the Huffman Codes for the code lengths
var 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
var literalCodeLengths = [];
var prevCodeLength = 0;
while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
var symbol = decodeSymbol(bstream, codeLengthsCodes);
if (symbol <= 15) {
literalCodeLengths.push(symbol);
prevCodeLength = symbol;
}
else if (symbol == 16) {
var repeat = bstream.readBits(2) + 3;
while (repeat--) {
literalCodeLengths.push(prevCodeLength);
}
}
else if (symbol == 17) {
var repeat = bstream.readBits(3) + 3;
while (repeat--) {
literalCodeLengths.push(0);
}
}
else if (symbol == 18) {
var repeat = bstream.readBits(7) + 11;
while (repeat--) {
literalCodeLengths.push(0);
}
}
}
// now split the distance code lengths out of the literal code array
var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
// now generate the true Huffman Code tables using these code lengths
var hcLiteralTable = getHuffmanCodes(literalCodeLengths),
hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
}
// error
else {
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);
};

View file

@ -1,71 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>Comic Book Reader (Dev)</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="../lib/vendor/pixastic/pixastic.js"></script>
<script src="../lib/vendor/pixastic/pixastic.effects.js"></script>
<script src="../lib/vendor/pixastic/pixastic.worker.js"></script>
<script src="../lib/vendor/handlebars.runtime-1.0.rc.1.min.js"></script>
<script src="../lib/templates.js"></script>
<script src="../lib/ComicBook.js"></script>
<link rel="stylesheet" href="../css/reset.css">
<link rel="stylesheet" href="../fonts/icomoon-toolbar/style.css">
<link rel="stylesheet" href="../css/styles.css">
<link rel="stylesheet" href="../css/toolbar.css">
<link rel="shortcut icon" sizes="196x196" href="../icons/1_Desktop_icons/icon_196.png">
<link rel="apple-touch-icon" sizes="196x196" href="../icons/1_Desktop_icons/icon_196.png">
<link rel="apple-touch-icon-precomposed" sizes="196x196" href="../icons/1_Desktop_icons/icon_196.png">
</head>
<body>
<canvas id="comic"></canvas>
<script>
var book = new ComicBook('comic', [
'goldenboy/goldenboy_00.jpg',
'goldenboy/goldenboy_01.jpg',
'goldenboy/goldenboy_02.jpg',
'goldenboy/goldenboy_03.jpg',
'goldenboy/goldenboy_04.jpg',
'goldenboy/goldenboy_05.jpg',
'goldenboy/goldenboy_06.jpg',
'goldenboy/goldenboy_07.jpg',
'goldenboy/goldenboy_08.jpg',
'goldenboy/goldenboy_09.jpg',
'goldenboy/goldenboy_10.jpg',
'goldenboy/goldenboy_11.jpg',
'goldenboy/goldenboy_12.jpg',
'goldenboy/goldenboy_13.jpg',
'goldenboy/goldenboy_14.jpg',
'goldenboy/goldenboy_15.jpg',
'goldenboy/goldenboy_16.jpg',
'goldenboy/goldenboy_17.jpg',
'goldenboy/goldenboy_18.jpg',
'goldenboy/goldenboy_19.jpg',
'goldenboy/goldenboy_20.jpg',
'goldenboy/goldenboy_21.jpg',
'goldenboy/goldenboy_22.jpg',
'goldenboy/goldenboy_23.jpg',
'goldenboy/goldenboy_24.jpg',
'goldenboy/goldenboy_25.jpg'
], {
libPath: "/HTML5-Comic-Book-Reader/examples/comicbook/js/"
});
book.draw();
$(window).on('resize', function () {
book.draw();
});
</script>
</body>
</html>

View file

@ -1,211 +0,0 @@
<!--
TODO:
· Update ComicBook to allow pages to be added after the object is initialised.
This would allow us to start rendering the comic immediately instead of
having to wait for the complete archive to be extracted before rendering.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>Comic Book Reader</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="bitjs/io.js"></script>
<script src="bitjs/archive.js"></script>
<script src="comicbook/js/comicbook.min.js"></script>
<link rel="stylesheet" href="comicbook/comicbook.css">
<link rel="shortcut icon" sizes="196x196" href="../icons/1_Desktop_icons/icon_196.png">
<link rel="apple-touch-icon" sizes="128x128" href="../icons/1_Desktop_icons/con_128.png">
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="../icons/1_Desktop_icons/con_128.png">
<style>
#filepicker {
margin: 10px;
}
.progress, .bar {
width: 200px;
height: 1em;
display: inline-block;
}
.progress {
border: solid 1px;
}
.bar {
width: 0;
background-color: gray;
}
#droptarget {
width: 100%;
height: 400px;
border: dashed 1px gray;
}
#droptarget.active {
border-style: solid;
}
</style>
</head>
<body>
<div id="filepicker">
<input type="file" id="open">
<div id="droptarget"></div>
<div id="progressbar" style="display:none">
opening <span id="filename"></span>...
<br>
<span class="progress"><span class="bar"></span></span>
</div>
</div>
<canvas id="comic" style="display:none"></canvas>
<script>
(function () {
'use strict';
var $droptarget = $('#droptarget');
var $progressbar = $('.bar');
if (!$.isFunction(window.FileReader)) {
$('#filepicker').html("Your browser doesn't support the HTML5 FileReader API. <a href='http://browsehappy.com'>http://browsehappy.com</a>.");
return false;
}
function extractImages(files, opts) {
var images = [];
var fr = new FileReader();
var file = files[0];
var re_file_ext = new RegExp(/\.([a-z]+)$/);
var archive_class = ({ cbz: 'Unzipper', cbr: 'Unrarrer' })[file.name.toLowerCase().match(re_file_ext)[1]];
var 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;
}
options.start(file);
fr.onload = function () {
var done = false;
var ua = new bitjs.archive[archive_class](this.result, '/HTML5-Comic-Book-Reader/examples/bitjs/');
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.start();
};
fr.readAsArrayBuffer(file);
}
function openComicArchive(e) {
var title;
var files = e.target.files || e.originalEvent.dataTransfer.files;
e.stopPropagation();
e.preventDefault();
extractImages(files, {
start: function (file) {
this.file = file;
$droptarget.hide();
$('#open').hide();
$('#filename').text(file.name);
$('#progressbar').show();
},
extract: function (url, blob) {
// $('body').append($('<img>').attr('src', url).css('width', '10px'));
// console.log(url, Math.floor(blob.size / 1024));
},
progress: function (percent_complete) {
$progressbar.css('width', percent_complete + '%');
},
finish: function (pages) {
var name = this.file.name.replace(/\.[a-z]+$/, '');
var id = encodeURIComponent(name.toLowerCase());
var book = new ComicBook('comic', pages, { libPath: '/HTML5-Comic-Book-Reader/examples/comicbook/js/'});
document.title = name;
$('#filepicker').hide();
$('#comic').show();
book.draw();
$(window).on('resize', function () {
book.draw();
});
}
});
}
$droptarget.on('dragover', function (e) {
e.stopPropagation();
e.preventDefault();
$(this).addClass('active');
});
$droptarget.on('dragleave', function (e) {
e.stopPropagation();
e.preventDefault();
$(this).removeClass('active');
});
$droptarget.on('drop', openComicArchive);
$('#open').on('change', openComicArchive);
}());
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

View file

@ -1,21 +0,0 @@
{
"predef": [
"jQuery",
"Handlebars",
"Pixastic"
],
"forin": true,
"noarg": true,
"noempty": true,
"eqeqeq": true,
"bitwise": true,
"strict": true,
"undef": true,
"unused": true,
"curly": true,
"browser": true,
"maxerr": 50,
"quotmark": "single",
"trailing": false,
"indent": 2
}

View file

@ -1,879 +0,0 @@
/* exported ComicBook */
var 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: 'double', // single / double
zoomMode: 'fitWindow', // manual / fitWidth / fitWindow
manga: false, // true / false
enhance: {},
keyboard: {
next: 78,
previous: 80,
toolbar: 84,
toggleLayout: 76
},
libPath: '/lib/',
forward_buffer: 3
};
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');
defaults.displayMode = 'single';
window.addEventListener('load', function () {
setTimeout(function () {
window.scrollTo(0, 1);
}, 0);
});
}
var options = merge(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 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', 'cb-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 !== pointer && loaded.indexOf(hash) > -1) {
pointer = 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();
// the current page, can pass a default as a url hash
var pointer = (hash < srcs.length) ? hash : 0;
/**
* Setup the canvas element for use throughout the class.
*
* @see #ComicBook.prototype.draw
* @see #ComicBook.prototype.enhance
*/
function init() {
// setup canvas
canvas = document.getElementById(canvas_id);
context = canvas.getContext('2d');
// render user controls
if (controlsRendered === false) {
self.renderControls();
controlsRendered = true;
}
// add page controls
window.addEventListener('keydown', self.navigation, false);
window.addEventListener('hashchange', checkHash, false);
}
window.addEventListener('touchstart', function (e) {
var $el = $(e.target);
if ($el.attr('id') === 'comic') {
self.toggleToolbar();
}
if ($el.data('toggle') === 'dropdown' ) {
$el.siblings('.dropdown').toggle();
}
}, false);
/**
* Render Handlebars templates. Templates with data-trigger & data-action will
* have the specified events bound.
*/
ComicBook.prototype.renderControls = function () {
var controls = {}, $toolbar;
$.each(Handlebars.templates, function (name, template) {
var $template = $(template().trim());
controls[name] = $template;
// add event listeners to controls that specify callbacks
$template.find('*').andSelf().filter('[data-action][data-trigger]').each(function () {
var $this = $(this);
var trigger = $this.data('trigger');
var action = $this.data('action');
// trigger a direct method if exists
if (typeof self[$this.data('action')] === 'function') {
$this.on(trigger, self[action]);
}
// throw an event to be caught outside if the app code
$this.on(trigger, function (e) {
$(self).trigger(trigger, e);
});
});
$(canvas).before($template);
});
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();
};
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() {
var $toolbar = self.getControl('toolbar');
var displayMode = (options.displayMode === 'single') ? 'double' : 'single';
options.displayMode = displayMode;
$toolbar.find('.layout').hide().end().find('.layout-' + options.displayMode).show();
self.drawPage();
};
/**
* 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);
$('#cb-loading-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(pointer) === '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 = pointer; // 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;
loaded.push(i);
$('#cb-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' && pointer < srcs.length-1) ? 1 : 0;
// start rendering the comic when the requested page is ready
if ((rendered === false && ($.inArray(pointer + 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 pointer
if (typeof page_requested === 'number') {
pointer = page_requested-1;
page_requested = false;
}
self.drawPage();
self.hideControl('loadingOverlay');
rendered = true;
}
if (queue.length) {
loadImage(queue[0]);
queue.splice(0,1);
} else {
$('#cb-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) {
pointer = page_no-1;
if (!this.pageLoaded(page_no)) {
this.showControl('loadingOverlay');
return;
}
}
if (pointer < 0) { pointer = 0; }
var zoom_scale;
var offsetW = 0, offsetH = 0;
var page = self.getPage(pointer);
var page2 = false;
if (options.displayMode === 'double' && pointer < srcs.length-1) {
page2 = self.getPage(pointer + 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.libPath + 'pixastic/');
// apply any image enhancements previously defined
$.each(options.enhance, function(action, options) {
self.enhance[action](options);
});
var current_page =
(options.displayMode === 'double' &&
pointer + 2 <= srcs.length) ? (pointer + 1) + '-' + (pointer + 2) : pointer + 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.cb-fit-width').attr('disabled', (options.zoomMode === 'fitWidth'));
$('button.cb-fit-window').attr('disabled', (options.zoomMode === 'fitWindow'));
// disable prev/next buttons if not needed
$('.navigate').show();
if (pointer === 0) {
if (options.manga) {
$('.navigate-left').show();
$('.navigate-right').hide();
} else {
$('.navigate-left').hide();
$('.navigate-right').show();
}
}
if (pointer === srcs.length-1 || (typeof page2 === 'object' && pointer === srcs.length-2)) {
if (options.manga) {
$('.navigate-left').hide();
$('.navigate-right').show();
} else {
$('.navigate-left').show();
$('.navigate-right').hide();
}
}
if (pointer !== getHash()){
$(this).trigger('navigate');
}
// update hash location
if (getHash() !== pointer) {
setHash(pointer + 1);
}
};
/**
* Increment the counter and draw the page in the canvas
*
* @see #drawPage
*/
ComicBook.prototype.drawNextPage = function () {
var page;
try {
page = self.getPage(pointer+1);
} catch (e) {}
if (!page) { return false; }
if (pointer + 1 < pages.length) {
pointer += (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(pointer-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 (pointer > 0) {
pointer -= (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);
};
ComicBook.prototype.brightness = function () {
self.enhance.brightness({ brightness: $(this).val() });
};
ComicBook.prototype.contrast = function () {
self.enhance.brightness({ contrast: $(this).val() });
};
ComicBook.prototype.sharpen = function () {
self.enhance.sharpen({ strength: $(this).val() });
};
ComicBook.prototype.desaturate = function () {
if ($(this).is(':checked')) {
self.enhance.desaturate();
} else {
self.enhance.resaturate();
}
};
ComicBook.prototype.resetEnhancements = function () {
self.enhance.reset();
};
/**
* Apply image enhancements to the canvas.
*
* Powered by the awesome Pixastic: http://www.pixastic.com/
*
* TODO: reset & apply all image enhancements each time before applying new one
* TODO: abstract this into an 'Enhance' object, separate from ComicBook?
*/
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);
// remember options for later
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);
}
};
ComicBook.prototype.navigation = function (e) {
// disable navigation when the overlay is showing
if ($('#cb-loading-overlay').is(':visible')) { return false; }
var side = false;
switch (e.type) {
case 'click':
side = e.currentTarget.getAttribute('data-navigate-side');
break;
case 'keydown':
// navigation
if (e.keyCode === options.keyboard.previous) { side = 'left'; }
if (e.keyCode === options.keyboard.next) { side = 'right'; }
// display controls
if (e.keyCode === options.keyboard.toolbar) {
self.toggleToolbar();
}
if (e.keyCode === options.keyboard.toggleLayout) {
self.toggleLayout();
}
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;
}
};
ComicBook.prototype.toggleReadingMode = function () {
options.manga = !options.manga;
self.getControl('toolbar')
.find('.manga-' + options.manga).show().end()
.find('.manga-' + !options.manga).hide();
};
ComicBook.prototype.toggleToolbar = function () {
self.toggleControl('toolbar');
};
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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,27 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Comic Book Reader Test Suite</title>
<script src="../vendor/jquery-2.0.0.min.js"></script>
<link rel="stylesheet" href="vendor/qunit-1.11.0.css" type="text/css" media="screen" />
<script src="vendor/qunit-1.11.0.js"></script>
<script src="unit/logger.js"></script>
<script src="../vendor/pixastic/pixastic.js"></script>
<script src="../vendor/pixastic/pixastic.effects.js"></script>
<script src="../vendor/pixastic/pixastic.worker.js"></script>
<script src="../vendor/handlebars.runtime-1.0.rc.1.min.js"></script>
<script src="../templates.js"></script>
<script src="../ComicBook.js"></script>
<script src="unit/ComicBook.js"></script>
</head>
<body>
<div>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</div>
</body>
</html>

View file

@ -1,63 +0,0 @@
// Simple phantom.js integration script
// Taken from Twitter Bootstrap
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 5001 //< Default Max Timout is 5s
, start = new Date().getTime()
, condition = false
, interval = setInterval(function () {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()) //< defensive code
} else {
if (!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout")
phantom.exit(1)
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof(onReady) === "string" ? eval(onReady) : onReady() //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval) //< Stop this interval
}
}
}, 100) //< repeat check every 100ms
}
if (phantom.args.length === 0 || phantom.args.length > 2) {
console.log('Usage: phantom.js URL')
phantom.exit()
}
var page = new WebPage()
// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function(msg) {
console.log(msg)
};
page.open(phantom.args[0], function(status){
if (status !== "success") {
console.log("Unable to access network")
phantom.exit()
} else {
waitFor(function(){
return page.evaluate(function(){
var el = document.getElementById('qunit-testresult')
if (el && el.innerText.match('completed')) {
return true
}
return false
})
}, function(){
var failedNum = page.evaluate(function(){
var el = document.getElementById('qunit-testresult')
try {
return el.getElementsByClassName('failed')[0].innerHTML
} catch (e) { }
return 10000
});
phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0)
})
}
})

View file

@ -1,25 +0,0 @@
/*
* Simple connect server for phantom.js
* Adapted from Twitter Bootstrap
*/
var connect = require('connect'),
http = require('http'),
fs = require('fs'),
app = connect(),
pid_path = __dirname + '/pid.txt';
// clean up after failed test runs
if (fs.existsSync(pid_path)) {
try {
var pid = fs.readFileSync(pid_path, { encoding: 'utf-8' });
process.kill(pid, 'SIGHUP');
} catch (e) {}
fs.unlinkSync(pid_path);
}
app.use(connect.static(__dirname + '/../../'));
http.createServer(app).listen(3000);
fs.writeFileSync(pid_path, process.pid, 'utf-8');

View file

@ -1,62 +0,0 @@
/* global $: false, module: false, test: false, equal: false, ComicBook: false console: false */
$(function () {
'use strict';
var $fixture;
var book;
function initBook() {
$fixture = $('#qunit-fixture');
$fixture.append('<canvas id="comic"></canvas>');
book = new ComicBook(
'comic',
['img/1.png','img/2.png','img/3.png','img/4.png','img/5.png','img/6.png'],
{ libPath: '../vendor/' }
);
}
module('not yet rendered comic', {
setup: initBook
});
test('controls shouldn\'t be renderd yet', function () {
equal($('.cb-control, .toolbar').length, 0, 'book not drawn yet, nothing should be rendered');
});
module('rendered comic', {
setup: function () {
initBook();
book.draw();
},
teardown: function () {
}
});
test('all controls should be rendered', function () {
equal($('.cb-control, .toolbar').length, 5, 'All toolbar elements should have rendered after book.draw');
});
// navigate on keyboard
// don't navigate if nothing left
// show current page
// customise keyboard control
// dropdown menus
// apply effects
// maximise
// minimise
// fit width
// single page / double page
// single page should allow double page spreads
// preloading
// update hash
// resume based on hash
// load from middle of page
// emit custom events based on data-attributes
// destroy
});

View file

@ -1,24 +0,0 @@
/* jshint strict: false */
/* global console: false, QUnit: false */
// Logging setup for phantom integration
// Taken from Twitter Bootstrap
QUnit.begin = function () {
console.log('Starting test suite');
console.log('================================================\n');
};
QUnit.moduleDone = function (opts) {
if (opts.failed === 0) {
console.log('\u2714 All tests passed in "' + opts.name + '" module');
} else {
console.log('\u2716 ' + opts.failed + ' tests failed in "' + opts.name + '" module');
}
};
QUnit.done = function (opts) {
console.log('\n================================================');
console.log('Tests completed in ' + opts.runtime + ' milliseconds');
console.log(opts.passed + ' tests of ' + opts.total + ' passed, ' + opts.failed + ' failed.');
};

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