My Manga Viewer written in Javascript

Elsewhere I can be found:
icychkn@protonmail.com
github/icychkn
youtube/icychkn

Link to the Demo. It shows the first chapter of "My Wife is Forcing Herself to Smile", a sweet romantic comedy.

This does need to be hosted behind a web server to function, I use Python's builtin one for testing locally. python -m http.server 8080 works well enough for me.

The Readable Version

I took an hour and a half to see what I can do to rewrite the original program. I'd say it turned out very well.

<!DOCTYPE html> <html> <head></head> <body> <div id='image_list'></div> </body> <script> function add_image(data) { const blob = new Blob([data], {type: "image/png"}); image_element = document.createElement('img'); image_element.src = URL.createObjectURL(blob); image_list.append(image_element); } // Reader.read tends to only fill part of the buffer at a time, so I have // to repeatedly call it until I have enough of the data I wanted. Oh and // the buffer passed to it needs to be carefully setup so it reads data in // correctly. // (or so I think, the standard is difficult to parse, but it works) function read_data(src, how_many_bytes) { let how_many_so_far = 0; return src.read(new Uint8Array(how_many_bytes)) // a recursive function is easier to loop // when the next data is ready with .then() .then(function loop({done, value}) { how_many_so_far += value.byteLength; const how_many_left = how_many_bytes - how_many_so_far; if (done || how_many_so_far == how_many_bytes) { // this is the whole buffer, not the // restricted range of "value". return value.buffer; } else { // this creates a partial view of the buffer, so // reader.read doesn't clobber the old data const next = new Uint8Array(value.buffer, how_many_so_far, how_many_left); return src.read(next) .then(loop); } }); } function read_numbers(source, how_many) { return read_data(source, how_many * 4).then( data => { return new Uint32Array(data); } ); } async function read_number(source) { return (await read_numbers(source, 1))[0]; } async function load(source) { const number_of_images = await read_number(source); const sizes_of_images = await read_numbers(source, number_of_images); sizes_of_images.forEach( size => { read_data(source, size).then(add_image); } ); } const file_path = (new URLSearchParams(window.location.search)).get('f'); fetch(file_path).then(({ok, body}) => { if (ok) { load(body.getReader({mode: "byob"})); } }) </script> </html>

The Original, Outdated, but Newly Commented Code

It's not pretty, but it works.

<body> <div id='image_list'></div> </body> <script> function addImage(data) { let blob = new Blob([data], {type: "image/png"}); image_element = document.createElement('img'); image_element.src = URL.createObjectURL(blob); image_list.append(image_element); } // note, this originally used the tar archive format, but it proved a poor // fit for this usecase. // so instead I made a slim archive format, one that includes three things: // // first is 4 bytes which counts the number of images in the file. // second is a list of (4 * number of images) bytes representing the size // of each file in bytes. // last is every file concatenated together. // // this proved very useful for slower connections, each image could be // loaded and displayed one at a time. async function parse(stream) { // number of images in the file... let value = await stream.readBytes(4); // read in the list of sizes... let indices = new Uint32Array(await stream.readBytes( new Uint32Array(value)[0] * 4)); // then load each image one at a time... async function eachImage(index) { let data = new Uint8Array(await stream.readBytes(indices[index])); // once the data is loaded, show it on the screen... addImage(data); if (index < indices.length - 1) { return eachImage(index + 1); } } eachImage(0); } // I tried to clean this up, I failed async function extractTar(archive) { // callback hell return fetch(archive) .then(response => { if (response.ok) { let reader = response.body.getReader({mode: "byob"}); // readBytes is used to fetch a number of bytes and no less. // for some reason I couldn't find anything similar builtin // to the language itself, so I build it here as a stream // "object" to keep the rest of the code clean. // the closest I could find can give fewer bytes than // requested. return { readBytes(size) { let offset = 0; return reader.read(new Uint8Array(size)) .then(function recurse({done, value}) { offset += value.byteLength; if (done || size == offset) { return value.buffer; } return reader.read( new Uint8Array(value.buffer, offset, size - offset)) .then(recurse); })} } } throw new Error('failed to load file'); }) .then(parse); } let uri = (new URLSearchParams(window.location.search)).get('f'); extractTar(uri); </script>