Compare commits

..

No commits in common. "c8451c07aa75f5ec56c92d9bbb559ee78f3e7760" and "5f8d8873b311348ced29a0d4119fb3b9c361128d" have entirely different histories.

5 changed files with 370 additions and 543 deletions

4
.gitignore vendored
View File

@ -1,6 +1,2 @@
# ESLINT report
eslint_report.html
# Demo sources
demos/src_highres demos/src_highres
demos/src_lowres demos/src_lowres

View File

@ -25,19 +25,16 @@ flowchart TB
``` ```
Melpomene is mainly one JS file and one CSS file. You only need to include them in you web page. They are : Melpomene is mainly one JS file and one CSS file. You only need to include them in you web page. They are :
+ `melpomene.js`
+ `melpomene.js` + `melpomene.css`
+ `melpomene.css`
The JS files expect you to write some very specific HTML tags to be able to work. The JS files expect you to write some very specific HTML tags to be able to work.
To simplify things, you only need to copy-paste the content of `melpomene.html` into your own page and change a few things : To simplify things, you only need to copy-paste the content of `melpomene.html` into your own page and change a few things :
+ You must duplicate the `img` tag for each of you comic page and : + You must duplicate the `img` tag for each of you comic page and :
+ set `url` to the actual URL of your page + set `url` to the actual URL of your page
+ set `height` and `width` to the actual image sizes + set `height` and `width` to the actual image sizes
+ set `data-zooms` with the zooms information, like so : `<zoom 1 width>, <zoom 1 height>, <zoom 1 x offset>, <zoom 1 y offset>; <zoom 2 width> ...` + set `data-zooms` with the zooms information, like so : `<zoom 1 width>, <zoom 1 height>, <zoom 1 x offset>, <zoom 1 y offset>; <zoom 2 width> ...`
+ example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.4470    2,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>` + example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>`
Because doing this by hand would be very tedious, Melpomene comes with an helper script that allows generating that HTML for you from SVGs. Because doing this by hand would be very tedious, Melpomene comes with an helper script that allows generating that HTML for you from SVGs.
@ -48,7 +45,7 @@ The following limitations are known and will be improved upon :
+ Mobile support is currently limited + Mobile support is currently limited
+ There are some performences issues + There are some performences issues
# How to use Melpomene ? # How to setup Melpomene ?
## Defining the zooms ## Defining the zooms
@ -58,21 +55,21 @@ To do so, you can use your favorite SVG editor. If you don't have one, [Inkscape
To create the zooms for a comic page, what you need to do is : To create the zooms for a comic page, what you need to do is :
1. Open your comic page in your editor (make sure to keep the image's original size when importing!) 1. Open your comic page in your editor (make sure to keep the image's original size when importing!)
* If your software ask, there is no need to actually import the image, you only need to link it. * If your software ask, there is no need to actually import the image, you only need to link it.
2. Create a simple rectangle over each zoom you want, in the order you want them to show up 2. Create a simple rectangle over each zoom you want, in the order you want them to show up
* You can set the rectangle's color to be translucent so you can still see the page underneath them! * You can set the rectangle's color to be translucent so you can still see the page underneath them!
* If you want to change the zoom order, you can change their order in the layer view of your SVG tool if they support it * If you want to change the zoom order, you can change their order in the layer view of your SVG tool if they support it
3. Once you are done, save the SGV 3. Once you are done, save the SGV
## Generating the HTML files ## Generating the HTML files
Once the SVG for your pages are done, put them in one folder. Then, you can run the zoom generator. Once the SVG for your pages are done, put them in one folder. Then, you can run the zoom generator.
You need to open a terminal and then run : You need to open a terminal and then run :
+ `python zooms_generator.py <path to the SVG folder> html -p <prefix of the img's url> -e <extension of the img's url>` + `python zooms_generator.py <path to the SVG folder> html -p <prefix of the img's url> -e <extension of the img's url>`
+ For example, if your comic pages are hosted at `static/comic/page_x.jpg`, the svg must be named `page_x.svg` + For example, if your comic pages are hosted at `static/comic/page_x.jpg`, the svg must be named `page_x.svg`
and you must run `python zooms_generator.py <path to the SVG folder> html -p /static/comic/ -e jpg` and you must run `python zooms_generator.py <path to the SVG folder> html -p /static/comic/ -e jpg`
+ It will generate the following `img` tag : `<img loading="lazy" src="static/comic/page_x.jpg" data-zooms="..."/>` + It will generate the following `img` tag : `<img loading="lazy" src="static/comic/page_x.jpg" data-zooms="..."/>`
The pages need to be in alphabetical order! It assumes the first page is page 1, the next one is page 2, etc.. The pages need to be in alphabetical order! It assumes the first page is page 1, the next one is page 2, etc..
@ -92,24 +89,6 @@ If you need to do some global scaling / offset of all zooms in HTML (if for exam
+ If they become greater than the page size, they get clamped to the page size and width / height get reduced to compensate + If they become greater than the page size, they get clamped to the page size and width / height get reduced to compensate
+ `data-global-zoom-scale="<float value>"` : scale all positions / sizes by this factor + `data-global-zoom-scale="<float value>"` : scale all positions / sizes by this factor
# Developpement
## Setting up quality checking for JS
Regarding JS, quality checking is done using [eslint](https://eslint.org/).
The configuration file is `eslint/eslintrc.json`.
To setup eslint, you can either install it on your system reading it's documentation, or use the provided Dockerfile to run it. This requires [docker](https://www.docker.com/).
To do so, assuming you are using linux, after installing docker, you can run from this repository root:
+ `docker build -t melpomene-eslint eslint/`, once
+ `docker run -v .:/melpomene:rw -w /melpomene --user $(id -u):$(id -g) melpomene-eslint`, every time you want to run the analysis
You can now open `eslint_report.html` to see the result.
# Credits # Credits
Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html).  Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html).

View File

@ -1,11 +0,0 @@
FROM debian:latest
RUN apt-get update && apt-get install -y --no-install-recommends npm
RUN npm install eslint --global
COPY eslintrc.json /eslintrc.json
ENTRYPOINT ["eslint", "--no-eslintrc"]
CMD ["--help"]

View File

@ -1,34 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:all"
],
"rules": {
"semi": "warn",
"spaced-comment": "warn",
"padded-blocks": ["warn", "never"],
"camelcase": "warn",
"multiline-comment-style": "off",
"max-len": "warn",
"prefer-template": "warn",
"object-curly-spacing" : ["warn", "always"],
"func-style": ["warn", "declaration"],
"key-spacing": "warn",
"one-var": ["warn", "never"],
"quotes": "warn",
"space-before-function-paren": ["warn", "never"],
"no-undefined": "off",
"strict": ["error", "global"],
"keyword-spacing": "warn",
"function-call-argument-newline": "warn",
"operator-assignment": "off",
"space-before-blocks": "warn",
"brace-style": ["warn", "allman", {"allowSingleLine": true}],
"function-call-argument-newline" : ["warn", "consistent"],
"function-paren-newline": ["warn", "consistent"],
"no-magic-numbers": ["error", {"ignore": [0,1,2,100]}]
}
}

View File

@ -2,63 +2,60 @@
/* Version 1.0.0_RC1 */ /* Version 1.0.0_RC1 */
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */ /* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
"use strict"; //============
// CONTROLS
//============
// ============ MOVE_NEXT = "ArrowRight"
// CONTROLS MOVE_BACK = "ArrowLeft"
// ============ TOGGLE_FULLSCREEN = "F"
TOGGLE_PROGRESSBAR = "P"
const MOVE_NEXT = "ArrowRight"; TOGGLE_VIEW_MODE = "V"
const MOVE_BACK = "ArrowLeft";
const TOGGLE_FULLSCREEN = "F";
const TOGGLE_PROGRESSBAR = "P";
const TOGGLE_VIEW_MODE = "V";
// ======================== //========================
// NAVIGATION CONSTANTS // NAVIGATION CONSTANTS
// ======================== //========================
const MOUSEWHELL_MIN_DELAY = 50; PAGE_TRANSITION_SPEED = "1.5s"
const DELAY_BEFORE_HIDDING_CONTROLS = 4000; MOUSEWHELL_MIN_DELAY = 50
DELAY_BEFORE_HIDDING_CONTROLS = 4000;
// ==================== //====================
// STATES CONSTANTS // STATES CONSTANTS
// ==================== //====================
const MELPOMENE_VERSION = "1.0.0_UNSTABLE"; MELPOMENE_VERSION = "1.0.0_RC1"
const READER_FRAME = document.getElementById("melpomene"); READER_FRAME = document.getElementById("melpomene")
const READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame"); READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame")
const READER_PAGES = document.getElementById("melpomene-pages"); READER_PAGES = document.getElementById("melpomene-pages")
const FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus"); FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus")
const FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col"); FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col")
const HELP_CONTROLS = document.getElementById("melpomene-help-menu"); HELP_CONTROLS = document.getElementById("melpomene-help-menu")
const PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container"); PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container")
const PROGRESS_BAR = document.getElementById("melpomene-progress-bar"); PROGRESS_BAR = document.getElementById("melpomene-progress-bar")
const PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections"); PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections")
const VERSION_DISPLAY = document.getElementById("melpomene-version"); VERSION_DISPLAY = document.getElementById("melpomene-version")
// =========================== //===========================
// STATES GLOBAL VARIABLES // STATES GLOBAL VARIABLES
// =========================== //===========================
var PAGES_ZOOMS;
// The variable ZOOMS can either be defined by another JS file or contructed at init // The variable ZOOMS can either be defined by another JS file or contructed at init
if (typeof PAGES_ZOOMS === 'undefined') if (typeof PAGES_ZOOMS == 'undefined') {
{ PAGES_ZOOMS = null
PAGES_ZOOMS = null;
} }
var CURRENT_ZOOM = 0; CURRENT_ZOOM = 0
var CURRENT_PAGE = 1; CURRENT_PAGE = 1
var CURRENT_WIDTH = 0; CURRENT_WIDTH = 0
var CURRENT_HEIGHT = 0; CURRENT_HEIGHT = 0
var CURRENT_X = 0; CURRENT_X = 0
var CURRENT_Y = 0; CURRENT_Y = 0
var IS_PAGE_MODE = false; IS_PAGE_MODE = false
var MOUSEWHELL_WAIT = false; MOUSEWHELL_WAIT = false
// ============= // =============
// UTILITIES // UTILITIES
@ -67,433 +64,360 @@ var MOUSEWHELL_WAIT = false;
// Zooms utilites // Zooms utilites
// -------------- // --------------
function globalZoomScale() function globalZoomScale(){
{
if (READER_PAGES.dataset.globalZoomScale !== undefined) if (READER_PAGES.dataset.globalZoomScale != undefined){
{ return parseFloat(READER_PAGES.dataset.globalZoomScale)
return parseFloat(READER_PAGES.dataset.globalZoomScale);
} }
return 1.0; return 1.0
} }
function globalZoomOffsetX() function globalZoomOffsetX(){
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined) if (READER_PAGES.dataset.globalZoomOffset != undefined){
{ return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[0])
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[0]);
} }
return 0.0; return 0.0
} }
function globalZoomOffsetY() function globalZoomOffsetY(){
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined) if (READER_PAGES.dataset.globalZoomOffset != undefined){
{ return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[1])
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(',')[1]);
} }
return 0.0; return 0.0
} }
function loadZoomsFromImgTagsIfRequired() function loadZoomsFromImgTagsIfRequired(){
{
// Zooms may be defined by another JS file // Zooms may be defined by another JS file
if (PAGES_ZOOMS === null) if (PAGES_ZOOMS == null){
{
PAGES_ZOOMS = []; PAGES_ZOOMS = []
// parse the data-zooms of each img and and the page number info // parse the data-zooms of each img and and the page number info
for (let idx = 0; idx < READER_PAGES.children.length; idx += 1) for (var i = 0; i < READER_PAGES.children.length; i++) {
{
const zoomsRawData = READER_PAGES.children[idx].dataset.zooms; zooms_raw_data = READER_PAGES.children[i].dataset.zooms
// ';' separates zooms data, ',' separates values // ';' separates zooms data, ',' separates values
// We add the page number (adding 1 because of indexing) // We add the page number (adding 1 because of indexing)
const zooms = zoomsRawData.split(";").map( zooms = zooms_raw_data.split(";").map(
zoom => [idx + 1].concat( zoom => [i + 1].concat(
zoom.split(',').map( zoom.split(',').map(
value => parseFloat(value) value => parseFloat(value)
) )
) )
); )
PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms)
}
}
}
PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms); function getFirstZoomOfPage(pageNumber){
for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
return zoom_idx
} }
} }
} }
function getFirstZoomOfPage(pageNumber) function getLastZoomOfPage(pageNumber){
{ let res = null
for (let zoomIdx = 0; zoomIdx < PAGES_ZOOMS.length; zoomIdx += 1)
{ for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
if (PAGES_ZOOMS[zoomIdx][0] === pageNumber) if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
{ res = zoom_idx
return zoomIdx; }
if (res != null && PAGES_ZOOMS[zoom_idx][0] != pageNumber) {
break
} }
} }
return res
} }
function getLastZoomOfPage(pageNumber) function getZoomCountForPage(pageNumber) {
{ return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
let res = null; }
for (let zoomIdx = 0; zoomIdx < PAGES_ZOOMS.length; zoomIdx += 1) function getCurrentZoomIndexForPage() {
{ previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length
if (PAGES_ZOOMS[zoomIdx][0] === pageNumber) return CURRENT_ZOOM - previousZoomsCount + 1
{ }
res = zoomIdx;
}
if (res !== null && PAGES_ZOOMS[zoomIdx][0] !== pageNumber) function getReadingProgressPercent() {
{ progressPerPage = 1 / getPagesCount()
break;
} if (IS_PAGE_MODE){
return 100 * progressPerPage * CURRENT_PAGE
} }
return res; progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE)
readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom
return 100 * readingProgress
} }
function getZoomCountForPage(pageNumber) function updateProgressBar(){
{ PROGRESS_BAR.style.width = getReadingProgressPercent() + "%"
return PAGES_ZOOMS.filter(zoom => zoom[0] === pageNumber).length;
}
function getCurrentZoomIndexForPage()
{
const previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length;
return CURRENT_ZOOM - previousZoomsCount + 1;
}
function getReadingProgressPercent()
{
const progressPerPage = 1 / getPagesCount();
if (IS_PAGE_MODE)
{
return 100 * progressPerPage * CURRENT_PAGE;
}
const progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE);
const readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom;
return 100 * readingProgress;
}
function updateProgressBar()
{
PROGRESS_BAR.style.width = getReadingProgressPercent() + "%";
} }
// Dimensions utilites // Dimensions utilites
// ------------------- // -------------------
function getPagesCount() function getPagesCount() {
{ return READER_PAGES.childElementCount
return READER_PAGES.childElementCount;
} }
function pageOriginalHeight(pageNumber) function pageOriginalHeight(pageNumber) {
{ return READER_PAGES.children[pageNumber - 1].naturalHeight
return READER_PAGES.children[pageNumber - 1].naturalHeight;
} }
function pageOriginalWidth(pageNumber) function pageOriginalWidth(pageNumber) {
{ return READER_PAGES.children[pageNumber - 1].naturalWidth
return READER_PAGES.children[pageNumber - 1].naturalWidth;
} }
function readerFrameRatio() function readerFrameRatio() {
{ return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight
return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight;
} }
function pageRatio(pageNumber) function pageRatio(pageNumber) {
{ return READER_PAGES.children[pageNumber - 1].naturalWidth / READER_PAGES.children[pageNumber - 1].naturalHeight
return READER_PAGES.children[pageNumber - 1].naturalWidth / READER_PAGES.children[pageNumber - 1].naturalHeight;
} }
function pageMaxHeight() function pageMaxHeight(){
{ let max_height = 0
let maxHeight = 0;
for (var i = 0; i < READER_PAGES.children.length; i++) {
for (let i = 0; i < READER_PAGES.children.length; i += 1) if(READER_PAGES.children[i].naturalHeight > max_height){
{ max_height = READER_PAGES.children[i].naturalHeight
if(READER_PAGES.children[i].naturalHeight > maxHeight)
{
maxHeight = READER_PAGES.children[i].naturalHeight;
} }
} }
return maxHeight; return max_height
} }
function pageVerticalOffset(pageNumber) function pageVerticalOffset(pageNumber) {
{ return ( pageMaxHeight() - pageOriginalHeight(pageNumber) ) / 2
return ( pageMaxHeight() - pageOriginalHeight(pageNumber) ) / 2;
} }
function previousPagesWidth(pageNumber) function previousPagesWidth(pageNumber) {
{
// The width of all previous pages relative to the provided index // The width of all previous pages relative to the provided index
let totalWidth = 0; let totalWidth = 0
for (let idx = 0; idx < pageNumber - 1; idx += 1) for (let idx = 0; idx < pageNumber - 1; idx++){
{ totalWidth = totalWidth + READER_PAGES.children[idx].naturalWidth
totalWidth += READER_PAGES.children[idx].naturalWidth;
} }
return totalWidth; return totalWidth
} }
// ========= // =========
// ACTIONS // ACTIONS
// ========= // =========
function initReader() function initReader(){
{ loadZoomsFromImgTagsIfRequired()
VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION); moveReaderDisplayToZoom(0)
loadZoomsFromImgTagsIfRequired();
moveReaderDisplayToZoom(0);
// Smoothly show pictures when they intersect with the viewport // Smoothly show pictures when they intersect with the viewport
const visibilityObserver = new IntersectionObserver( let visibilityObserver = new IntersectionObserver((entries, observer) => {
(entries, observer) => entries.forEach((entry) => {
{ if (entry.isIntersecting) {
entries.forEach((entry) => entry.target.style.opacity = 1
{ entry.target.style.visibility = "visible"
if (entry.isIntersecting) } else {
{ entry.target.style.opacity = 0
entry.target.style.opacity = 1; entry.target.style.visibility = "hidden"
entry.target.style.visibility = "visible"; }
} });
}, { root: READER_CONTENT_FRAME, rootMargin: "-10px" });
else
{ for (var i = 0; i < READER_PAGES.children.length; i++) {
entry.target.style.opacity = 0; let img = READER_PAGES.children[i];
entry.target.style.visibility = "hidden"; visibilityObserver.observe(img)
}
}); PROGRESS_BAR_PAGES.appendChild(document.createElement("div"))
},
{ root: READER_CONTENT_FRAME, rootMargin: "-10px" }
);
for (let i = 0; i < READER_PAGES.children.length; i += 1)
{
const img = READER_PAGES.children[i];
visibilityObserver.observe(img);
PROGRESS_BAR_PAGES.appendChild(document.createElement("div"));
} }
READER_PAGES.style.display = "flex"; READER_PAGES.style.display = "flex"
setTimeout( setTimeout(() => {
() => { READER_PAGES.hidden = false }, READER_PAGES.hidden = false
"300" }, "300")
);
setTimeout(() => {
setTimeout( HELP_CONTROLS.style.opacity = null;
() => HELP_CONTROLS.style.transform = null;
{ }, DELAY_BEFORE_HIDDING_CONTROLS)
HELP_CONTROLS.style.opacity = null;
HELP_CONTROLS.style.transform = null;
},
DELAY_BEFORE_HIDDING_CONTROLS
);
} }
function moveReaderDisplayToArea(pageNumber, oWidth, oHeight, oPosx, oPosy) function moveReaderDisplayToArea(pageNumber, width, height, posx, posy){
{
// Keep original values for registering // Keep original values for registering
let width = oWidth; o_width = width
let height = oHeight; o_height = height
let posx = oPosx; o_posx = posx
let posy = oPosy; o_posy = posy
// Apply global offsets before scales if we are displaying a zoom // Apply global offsets before scales if we are displaying a zoom
// Pages display uses width & height = 0 // Pages display uses width & height = 0
if (width !== 0 || height !== 0) if (width != 0 || height != 0){
{ width = width * globalZoomScale()
width = width * globalZoomScale(); height = height * globalZoomScale()
height = height * globalZoomScale(); posx = (posx + globalZoomOffsetX()) * globalZoomScale()
posx = (posx + globalZoomOffsetX()) * globalZoomScale(); posy = (posy + globalZoomOffsetY()) * globalZoomScale()
posy = (posy + globalZoomOffsetY()) * globalZoomScale();
} }
// reduce width if offset sent us outside of page // reduce width if offset sent us outside of page
if (posx < 0) if (posx < 0) {
{ width = width + posx
width = width + posx; posx = 0
posx = 0; }
if ((posx + width) > pageOriginalWidth(pageNumber)) {
width = pageOriginalWidth(pageNumber) - posx
} }
if ((posx + width) > pageOriginalWidth(pageNumber))
{
width = pageOriginalWidth(pageNumber) - posx;
}
// reduce height if offset sent us outside of page // reduce height if offset sent us outside of page
if (posy < 0) if (posy < 0) {
{ height = height + posy
height = height + posy; posy = 0
posy = 0; }
if ((posy + height) > pageOriginalHeight(pageNumber)) {
height = pageOriginalHeight(pageNumber) - posy
} }
if ((posy + height) > pageOriginalHeight(pageNumber))
{
height = pageOriginalHeight(pageNumber) - posy;
}
// Align the top-left corner of the frame with the page // Align the top-left corner of the frame with the page
READER_PAGES.style.transform = "translate(-" + previousPagesWidth(pageNumber) + "px, -" + pageVerticalOffset(pageNumber) + "px )"; READER_PAGES.style.transform = "translate(-" + previousPagesWidth(pageNumber) + "px, -" + pageVerticalOffset(pageNumber) + "px )"
// Then move so the top-left point of the zoom match the frame top-left // Then move so the top-left point of the zoom match the frame top-left
READER_PAGES.style.transform = "translate(" + (- posx) + "px, " + (-posy) + "px )" + READER_PAGES.style.transform; READER_PAGES.style.transform = "translate(" + (- posx) + "px, " + (-posy) + "px )" + READER_PAGES.style.transform
// Then, scale so the zoom would fit the frame, and center the zoom // Then, scale so the zoom would fit the frame, and center the zoom
if (width === 0) if (width == 0){
{ width = pageOriginalWidth(pageNumber)
width = pageOriginalWidth(pageNumber);
} }
if (height === 0) if (height == 0){
{ height = pageOriginalHeight(pageNumber)
height = pageOriginalHeight(pageNumber);
} }
const zoomRatio = width / height; zoomRatio = width / height
if (readerFrameRatio() > zoomRatio) if (readerFrameRatio() > zoomRatio) {
{
// Frame wider than zoom => scale so heights are the same, offset on x // Frame wider than zoom => scale so heights are the same, offset on x
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height; var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform; READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
const scaledWidth = width * zoomToFrameScaleFactor; var scaledWidth = width * zoomToFrameScaleFactor
const offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2; var offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2
READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform; READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform
updateFocusByWidth(scaledWidth); updateFocusByWidth(scaledWidth)
} } else {
else
{
// Frame narower than zoom => scale so left/right match, offset on y // Frame narower than zoom => scale so left/right match, offset on y
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width; var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform; READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
const scaledHeight = height * zoomToFrameScaleFactor; var scaledHeight = height * zoomToFrameScaleFactor
const offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2; var offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2
READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform; READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform
updateFocusByHeight(scaledHeight); updateFocusByHeight(scaledHeight)
} }
// Use values before global offset / scale // Use values before global offset / scale
CURRENT_PAGE = pageNumber; CURRENT_PAGE = pageNumber
CURRENT_WIDTH = oWidth; CURRENT_WIDTH = o_width
CURRENT_HEIGHT = oHeight; CURRENT_HEIGHT = o_height
CURRENT_X = oPosx; CURRENT_X = o_posx
CURRENT_Y = oPosy; CURRENT_Y = o_posy
} }
function refreshReaderDisplay() function refreshReaderDisplay() {
{ moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y)
moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y);
} }
function moveReaderDisplayToPage(pageNumber) function moveReaderDisplayToPage(pageNumber) {
{ moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0)
moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0);
} }
function moveReaderDisplayToZoom(index) function moveReaderDisplayToZoom(index) {
{
moveReaderDisplayToArea(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_ZOOMS[index][4]); moveReaderDisplayToArea(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_ZOOMS[index][4])
CURRENT_ZOOM = index; CURRENT_ZOOM = index
} }
function updateFocusByWidth(width) function updateFocusByWidth(width){
{ FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%"
FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%"; FOCUS_OVERLAY_HEIGHT.style.height = "100%"
FOCUS_OVERLAY_HEIGHT.style.height = "100%";
} }
function updateFocusByHeight(height) function updateFocusByHeight(height){
{ FOCUS_OVERLAY_WIDTH.style.width = "100%"
FOCUS_OVERLAY_WIDTH.style.width = "100%"; FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%"
FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%";
} }
function toggleViewMode() function toggleViewMode() {
{ if (IS_PAGE_MODE){
if (IS_PAGE_MODE) if (CURRENT_ZOOM != null){
{ moveReaderDisplayToZoom(CURRENT_ZOOM)
if (CURRENT_ZOOM !== null) } else {
{ moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE))
moveReaderDisplayToZoom(CURRENT_ZOOM);
} }
IS_PAGE_MODE = false
else } else {
{ moveReaderDisplayToPage(CURRENT_PAGE)
moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE)); IS_PAGE_MODE = true
}
IS_PAGE_MODE = false;
} }
else updateProgressBar()
{
moveReaderDisplayToPage(CURRENT_PAGE);
IS_PAGE_MODE = true;
}
updateProgressBar();
} }
function moveReader(toNext) function moveReader(to_next) {
{
if (IS_PAGE_MODE) if (IS_PAGE_MODE){
{ if (to_next && CURRENT_PAGE < getPagesCount()) {
if (toNext && CURRENT_PAGE < getPagesCount()) moveReaderDisplayToPage(CURRENT_PAGE + 1)
{ CURRENT_ZOOM = null
moveReaderDisplayToPage(CURRENT_PAGE + 1);
CURRENT_ZOOM = null;
} }
else if (!toNext && CURRENT_PAGE > 1) else if (!to_next && CURRENT_PAGE > 1) {
{ moveReaderDisplayToPage(CURRENT_PAGE - 1)
moveReaderDisplayToPage(CURRENT_PAGE - 1); CURRENT_ZOOM = null
CURRENT_ZOOM = null; }
} else {
if (to_next && CURRENT_ZOOM < PAGES_ZOOMS.length - 1) {
moveReaderDisplayToZoom(CURRENT_ZOOM + 1)
}
else if (!to_next && CURRENT_ZOOM > 0) {
moveReaderDisplayToZoom(CURRENT_ZOOM - 1)
} }
} }
else updateProgressBar()
{
if (toNext && CURRENT_ZOOM < PAGES_ZOOMS.length - 1)
{
moveReaderDisplayToZoom(CURRENT_ZOOM + 1);
}
else if (!toNext && CURRENT_ZOOM > 0)
{
moveReaderDisplayToZoom(CURRENT_ZOOM - 1);
}
}
updateProgressBar();
} }
@ -501,89 +425,69 @@ function moveReader(toNext)
// CALLBACKS // CALLBACKS
// ============= // =============
function handleKeyPress(key) function handleKeyPress(key){
{
if (key === MOVE_NEXT) if (key == MOVE_NEXT) {
{ moveReader(true)
moveReader(true);
}
else if (key === MOVE_BACK)
{
moveReader(false);
}
else if (key.toUpperCase() === TOGGLE_FULLSCREEN)
{
if (document.fullscreenElement === null)
{
READER_FRAME.requestFullscreen();
}
else
{
document.exitFullscreen();
}
}
else if (key.toUpperCase() === TOGGLE_PROGRESSBAR)
{
if (PROGRESS_BAR_CONTAINER.hidden === true)
{
PROGRESS_BAR_CONTAINER.hidden = false;
}
else
{
PROGRESS_BAR_CONTAINER.hidden = true;
}
refreshReaderDisplay();
}
else if (key.toUpperCase() === TOGGLE_VIEW_MODE)
{
toggleViewMode();
}
}
function handleMouseWhell(event)
{
// Only handle scroll event if the target is the nav controls
// to avoid preventing page scrolling.
// Do disable page scrolling when we do prev/next, though
if (! READER_FRAME.contains(event.target))
{
return;
}
event.preventDefault();
event.stopPropagation();
if (MOUSEWHELL_WAIT)
{
return;
} }
else else if (key == MOVE_BACK) {
{ moveReader(false)
MOUSEWHELL_WAIT = true; }
setTimeout(
() => { MOUSEWHELL_WAIT = false; }, else if (key.toUpperCase() == TOGGLE_FULLSCREEN){
MOUSEWHELL_MIN_DELAY if (document.fullscreenElement == null){
); READER_FRAME.requestFullscreen();
} else {
document.exitFullscreen();
}
}
else if (key.toUpperCase() == TOGGLE_PROGRESSBAR){
if (PROGRESS_BAR_CONTAINER.hidden == true) {
PROGRESS_BAR_CONTAINER.hidden = false
} else {
PROGRESS_BAR_CONTAINER.hidden = true
}
refreshReaderDisplay();
}
else if (key.toUpperCase() == TOGGLE_VIEW_MODE) {
toggleViewMode()
} }
if (event.deltaY > 0) }
{
moveReader(true, false);
}
else function handleMouseWhell(event){
{
moveReader(false, false); // Only handle scroll event if the target is the nav controls
// to avoid preventing page scrolling.
// Do disable page scrolling when we do prev/next, though
if (! READER_FRAME.contains(event.target)){
return
}
event.preventDefault()
event.stopPropagation()
if (MOUSEWHELL_WAIT){
return
} else {
MOUSEWHELL_WAIT = true
setTimeout(() => {
MOUSEWHELL_WAIT = false
}, MOUSEWHELL_MIN_DELAY)
}
if (event.deltaY > 0) {
moveReader(true, false)
}
else {
moveReader(false, false)
} }
} }
@ -591,26 +495,19 @@ function handleMouseWhell(event)
// INIT // INIT
// ====== // ======
window.addEventListener( window.addEventListener("load", (event) => {
"load", VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION)
(event) => { initReader() } initReader()
); });
addEventListener( addEventListener("resize", (event) => {
"resize", refreshReaderDisplay();
(event) => { refreshReaderDisplay() } });
);
addEventListener( addEventListener("keydown", (event) => {
"keydown", handleKeyPress(event.key, event.shiftKey)
(event) => });
{
handleKeyPress(event.key, event.shiftKey)
}
);
addEventListener( addEventListener("wheel", (event) => {
"wheel", handleMouseWhell(event)
(event) => { handleMouseWhell(event) }, }, { passive:false });
{ passive:false }
);