diff --git a/README.md b/README.md
index f72c498..6e21b8a 100644
--- a/README.md
+++ b/README.md
@@ -2,32 +2,43 @@

-Melpomene is a a simple, easy to integrate webcomic reading utility .
+Melpomene is a a simple, HTML/CSS/JS webcomic reading utility .
It allows people to look through your webcomic like they would in real life : panel by panel. It displays your pages through a series of zooms you defines beforehand.
-Melpomene uses SVGs as input to defines the zooms. It allows you to use any SVG editor as a "What You See Is What You Get" editor.
+To avoid writing the zooms informations by hand, Melpomene utility scripts uses SVGs as input to defines the zooms. It allows you to use any SVG editor as a "What You See Is What You Get" editor.
-This repository host a small demo you can open in your browser : the [episode 35 of pepper and carrot](https://www.peppercarrot.com/en/webcomic/ep35_The-Reflection.html) (see Credits). You can open `demos/pepper_and_carrot_e35_lowres.html` and `demos/pepper_and_carrot_e35_highres.html` to see Mepomene in action!
+This repository host a small demo you can open in your browser : the [episode 35 of pepper and carrot](https://www.peppercarrot.com/en/webcomic/ep35_The-Reflection.html) (see Credits). You can open the HTML files in the `demos` folder to see Mepomene in action!
Melpomene main repository is [https://git.aribaud.net/caribaud/melpomene](https://git.aribaud.net/caribaud/melpomene)
# How Melpomene works
-It works with :
-+ HTML, CSS and Javascript snipets you can put in your web page to add the webcomic reader
-+ A Javascript file describing the various informations required by the reader
-+ A Python script, used to generate the above Javascript configuration file
-
```mermaid
flowchart TB
page([Your comic pages]) -- use in --> svgedit[SVG editor e.g. Inkscape, etc...]
svgedit -- export --> svg([SVGs with zooms defined])
- svg -- use in --> confgen[Melpomene config generator]
- confgen --> conf([json config])
- conf & html([HTML snipet]) & js([JS snipet]) & css([CSS snipet]) -- use / import into --> webpage([Your web page])
+ svg -- use in --> gen[Melpomene config generator]
+ gen -- generate --> html([Melpomene HTML snipet])
+ html -- copy into --> webpage([Your web page])
+ js([melpomene.js]) & css([melpomene.css]) -- include into --> webpage
```
+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.css`
+
+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 :
++ `data-pages-width` must be set to your comic pages width in px
++ `data-pages-height` must be set to your comic pages height in px
++ You must duplicate the `img` tag for each of you comic page and :
+ + set `url` to the actual URL of your page
+ + set `data-zooms` with the zooms information, like so : `, , , ; ...`
+ + example : ``
+
+Because doing this by hand would be very tedious, Melpomene comes with an helper script that allows generating that HTML for you from SVGs.
+
## Limitations
The following limitations are known and will be improved upon :
@@ -53,42 +64,25 @@ To create the zooms for a comic page, what you need to do is :
3. Once you are done, save the SGV
-## Generating the configuration 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.
You need to open a terminal and then run :
-+ `python zooms_generator.py zooms_data.json` on windows
-+ `python3 zooms_generator.py zooms_data.json` on linux
++ `python zooms_generator.py html -p -e `
+ + 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 html -p /static/comic/ -e jpg`
+ + It will generate the following `img` tag : ``
The pages need to be in alphabetical order! It assumes the first page is page 1, the next one is page 2, etc..
-The script will then generate the file `zooms_data.json`.
+The script will then generate the file `melpomene_data.html`.
+
+If you wish to run a custom generation process, this generator can output a JSON or a JS file as well, run `python zooms_generator.py -h` for help
You are now ready to integrate Melpomene in your website!
-## Integrating Melpomene
-
-Let's assume your site is `https://my.website/`.
-
-You now need :
-1. Host the Melpomene Javascript and CSS files in your website, e.g. at `https://my.website/melptomene.js` and `https://my.website/melptomene.css`
-2. Host the zooms data on your website, e.g. at `https://my.website/zooms_data.js`
-2. Include the various parts of the HTML snipets :
- 1. `melpomene_head.html` goes in the `head` tag of your page
- * You may need to change the `href` values to where you uploaded the CSS file.
- 2. `melpomene_reader.html` goes where you want somewhere within the `body` tag of your page
- * Change `data-pages-width` and `data-pages-height` with the actual width and height of your pages
- * Change `https://link.to.my/comic/page.jpg` to the actual URL of your comic pages and duplicate the `img`
- tag for each of them
- 3. `melpomene_js.html` goes right after the `body` tag of your page.
- * You may need to change the `href` values to where you uploaded the JS files.
-
-You can now open your page and should see the results.
-
-If you are having troubles, you can open the demo files to see how it was done.
-
# 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).
\ No newline at end of file
diff --git a/demos/demo_data.js b/demos/demo_data.js
index 07797b4..dffd7ed 100644
--- a/demos/demo_data.js
+++ b/demos/demo_data.js
@@ -1,4 +1,4 @@
-zooms = [
+PAGES_ZOOMS = [
[1, 2481.0, 1327.1057, 0.0, 0.0],
[1, 593.15338, 1076.4635, 0.0, 1364.053],
[1, 890.72864, 491.29874, 830.81415, 1751.5],
diff --git a/demos/pepper_and_carrot_e35_highres.html b/demos/pepper_and_carrot_e35_highres.html
index 10899ff..d2b5845 100644
--- a/demos/pepper_and_carrot_e35_highres.html
+++ b/demos/pepper_and_carrot_e35_highres.html
@@ -11,13 +11,10 @@
-
-
diff --git a/melpomene.js b/melpomene.js
index ece00db..327dc25 100644
--- a/melpomene.js
+++ b/melpomene.js
@@ -1,4 +1,5 @@
-/* Melpomene CSS */
+/* Melpomene webcomic reader JS */
+/* Version 1.0.0 - UNSTABLE */
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
//============
@@ -34,6 +35,15 @@ PROGRESS_BAR_CONTAINER = document.getElementById("reader-progress-container")
PROGRESS_BAR = document.getElementById("reader-progress-bar")
PROGRESS_BAR_PAGES = document.getElementById("reader-progress-pages")
+//===========================
+// STATES GLOBAL VARIABLES
+//===========================
+
+// The variable ZOOMS can either be defined by another JS file or contructed at init
+if (typeof PAGES_ZOOMS == 'undefined') {
+ PAGES_ZOOMS = null
+}
+
CURRENT_ZOOM = 0
CURRENT_PAGE = 1
CURRENT_WIDTH = 0
@@ -51,10 +61,39 @@ MOUSEWHELL_WAIT = false
// Zooms utilites
// --------------
+function loadZoomsFromImgTagsIfRequired(){
+
+ // Zooms may be defined by another JS file
+ if (PAGES_ZOOMS == null){
+
+ PAGES_ZOOMS = []
+
+ // parse the data-zooms of each img and and the page number info
+ for (var i = 0; i < READER_PAGES.children.length; i++) {
+
+ zooms_raw_data = READER_PAGES.children[i].dataset.zooms
+
+ // ';' separates zooms data, ',' separates values
+ // We add the page number (adding 1 because of indexing)
+ zooms = zooms_raw_data.split(";").map(
+ zoom => [i + 1].concat(
+ zoom.split(',').map(
+ value => parseFloat(value)
+ )
+ )
+ )
+
+ PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms)
+ }
+
+ }
+
+}
+
function getFirstZoomOfPage(pageNumber){
- for (var zoom_idx = 0; zoom_idx < zooms.length; zoom_idx++){
- if (zooms[zoom_idx][0] == pageNumber) {
+ for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
+ if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
return zoom_idx
}
}
@@ -63,12 +102,12 @@ function getFirstZoomOfPage(pageNumber){
function getLastZoomOfPage(pageNumber){
let res = null
- for (var zoom_idx = 0; zoom_idx < zooms.length; zoom_idx++){
- if (zooms[zoom_idx][0] == pageNumber) {
+ for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
+ if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
res = zoom_idx
}
- if (res != null && zooms[zoom_idx][0] != pageNumber) {
+ if (res != null && PAGES_ZOOMS[zoom_idx][0] != pageNumber) {
break
}
}
@@ -77,11 +116,11 @@ function getLastZoomOfPage(pageNumber){
}
function getZoomCountForPage(pageNumber) {
- return zooms.filter(zoom => zoom[0] == pageNumber).length
+ return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
}
function getCurrentZoomIndexForPage() {
- previousZoomsCount = zooms.filter(zoom => zoom[0] < CURRENT_PAGE).length
+ previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length
return CURRENT_ZOOM - previousZoomsCount + 1
}
@@ -148,6 +187,7 @@ function totalPagesWidth() {
// =========
function initReader(){
+ loadZoomsFromImgTagsIfRequired()
moveReaderDisplayToZoom(0)
// Smoothly show pictures when they intersect with the viewport
@@ -232,7 +272,7 @@ function moveReaderDisplayToPage(pageNumber) {
function moveReaderDisplayToZoom(index) {
- moveReaderDisplayToArea(zooms[index][0], zooms[index][1], zooms[index][2], zooms[index][3], 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
}
@@ -278,7 +318,7 @@ function moveReader(to_next) {
} else {
- if (to_next && CURRENT_ZOOM < zooms.length - 1) {
+ if (to_next && CURRENT_ZOOM < PAGES_ZOOMS.length - 1) {
moveReaderDisplayToZoom(CURRENT_ZOOM + 1)
}
diff --git a/melpomene_head.html b/melpomene_head.html
deleted file mode 100644
index b5a6539..0000000
--- a/melpomene_head.html
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/melpomene_js.html b/melpomene_js.html
deleted file mode 100644
index 19a608a..0000000
--- a/melpomene_js.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/zooms_generator.py b/zooms_generator.py
index 0a2cc41..4c321c0 100644
--- a/zooms_generator.py
+++ b/zooms_generator.py
@@ -1,48 +1,182 @@
-# Melpomene comic reader
+# Melpomene webcomic reader JSON/JS/HTML generator
+# Version 1.0.0 - UNSTABLE
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
import sys
import re
import xml.etree.ElementTree as ET
+import argparse
from pathlib import Path
-def extract_zooms(src_folder, dest_file):
+HTML_START_CONSTANT = """\
+
+
+
+
+"""
+
+HTML_END_CONSTANT = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
←
/ scroll up / clic : previous
+
→
/ scroll down / clic : next
+
-----------------------
+
F
: Toggle fullscreen
+
P
: Toggle progress bar
+
V
: Toggle panel / page viewing mode
+
+
+
+
+
+
+
+
+
+"""
+
+
+def extract_zooms(src_folder):
folder = Path(src_folder)
zooms = {}
+
+ max_width = 0
+ max_height = 0
+
+ idx = 0
for svg_path in folder.glob("*.svg"):
- print(svg_path.name)
- match = re.search("P(\d+)", svg_path.name)
- if match:
- page_idx = int(match.group(1))
+ idx += 1
+
+ print(f"page {idx} : {svg_path.name}")
+
+ zooms[idx] = {
+ "name": svg_path.stem,
+ "zooms": [],
+ }
+
+ tree = ET.parse(svg_path)
+ root = tree.getroot()
- zooms[page_idx] = []
+ for svg in root.findall('.//{*}svg'):
+ if area.get("width") > max_width:
+ max_width = area.get("width")
+ if area.get("height") > max_width:
+ max_width = area.get("height")
- tree = ET.parse(svg_path)
- root = tree.getroot()
-
- for area in root.findall('.//{*}rect'):
- zooms[page_idx].append([
- float(area.get("width")),
- float(area.get("height")),
- float(area.get("x")),
- float(area.get("y")),
- ])
+ for area in root.findall('.//{*}rect'):
+ zooms[idx]["zooms"].append([
+ float(area.get("width")),
+ float(area.get("height")),
+ float(area.get("x")),
+ float(area.get("y")),
+ ])
+
+ return zooms, max_width, max_height
+
+
+def write_json_or_js(zooms, dest_file, is_js):
with open(dest_file, "w") as data_file:
- data_file.write("zooms = [\n")
+
+ if is_js:
+ data_file.write("PAGES_ZOOMS = ")
+ data_file.write("[\n")
+ first_coma_skiped = False
for page_idx in sorted(zooms.keys()):
- for zoom in zooms[page_idx]:
+ for zoom in zooms[page_idx]["zooms"]:
if zoom[2] < 0 or zoom[3] < 0 :
print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)")
- data_file.write(f" {[page_idx] + zoom},\n")
- data_file.write("]\n")
+ if first_coma_skiped:
+ data_file.write(",\n")
+ else:
+ first_coma_skiped = True
+ data_file.write(f" {[page_idx] + zoom}")
+ data_file.write("\n]\n")
+
+
+def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention):
+
+ with open(dest_file, "w") as data_file:
+
+ data_file.write(HTML_START_CONSTANT)
+
+ data_file.write(f'
\n')
+
+ for page_idx in sorted(zooms.keys()):
+ img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
+ zoom_html_data = [','.join([str(zoom) for zoom in page_zooms]) for page_zooms in zooms[page_idx]["zooms"]]
+ zoom_html_str = ';'.join(zoom_html_data)
+ data_file.write(f' \n')
+
+ data_file.write(f'
\n')
+
+ data_file.write(HTML_END_CONSTANT)
+
+def generate_argparse():
+ """ Generate Melpomene's generator input parser"""
+
+ parser = argparse.ArgumentParser(
+ description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader"
+ )
+
+ parser.add_argument("output_format", choices=["html", "json", "js"], help="The type of output to generate")
+ parser.add_argument("svg_folders", help="Path of the folder containing the SVGs")
+ parser.add_argument("-o", metavar="dest_file", help="Where to write the generator output to")
+ parser.add_argument("-p", default="", metavar="img_url_prefix", help="What to prefix the URL of the images when using HTML format.")
+ parser.add_argument("-e", default="png", metavar="img_ext", help="What extention to use in the URL of the images when using HTML format.")
+
+ return parser
+
if __name__ == "__main__":
- extract_zooms(sys.argv[1], sys.argv[2])
+
+ args = generate_argparse().parse_args()
+
+
+ # Get the final outout name
+ output = None
+
+ if not args.o:
+ output = "melpomene_data"
+ else:
+ output = args.o
+
+ if args.output_format == "html" and not output.endswith(".html"):
+ output += ".html"
+
+ elif args.output_format == "json" and not output.endswith(".json"):
+ output += ".json"
+
+ elif args.output_format == "js" and not output.endswith(".js"):
+ output += ".js"
+
+ zooms, max_width, max_height = extract_zooms(args.svg_folders)
+
+ if args.output_format == "html":
+ write_html(zooms, output, max_width, max_height, args.p, args.e)
+
+ elif args.output_format == "json":
+ write_json_or_js(zooms, output, False)
+
+ elif args.output_format == "js":
+ write_json_or_js(zooms, output, True)
\ No newline at end of file