Compare commits

..

1 Commits

Author SHA1 Message Date
caribaud c35e426838 Fix licence button URL 2023-05-22 01:12:37 +02:00
15 changed files with 847 additions and 1604 deletions

7
.gitignore vendored
View File

@ -1,9 +1,2 @@
# ESLINT report
eslint_report.html
# Demo sources
demos/src_highres demos/src_highres
demos/src_lowres demos/src_lowres
# Local dev environment settings
.vscode

13
Pipfile
View File

@ -1,13 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
black = "*"
prospector = {extras = ["with_mypy"], version = "*"}
[requires]
python_version = "3.11"

509
Pipfile.lock generated
View File

@ -1,509 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "6bf84a05ccc0682985bf11d77db01419dde589c614d97f59ca2bac4c4b43c80f"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {
"astroid": {
"hashes": [
"sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324",
"sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.15.5"
},
"black": {
"hashes": [
"sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
"sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
"sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
"sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
"sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
"sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
"sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
"sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
"sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
"sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
"sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
"sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
"sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
"sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
"sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
"sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
"sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
"sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
"sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
"sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
"sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
"sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
"sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
"sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
"sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
],
"index": "pypi",
"version": "==23.3.0"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.6"
},
"dill": {
"hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
],
"markers": "python_version >= '3.11'",
"version": "==0.3.6"
},
"dodgy": {
"hashes": [
"sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
"sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"
],
"version": "==0.2.1"
},
"flake8": {
"hashes": [
"sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
"sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==5.0.4"
},
"flake8-polyfill": {
"hashes": [
"sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9",
"sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"
],
"version": "==1.0.2"
},
"gitdb": {
"hashes": [
"sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a",
"sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"
],
"markers": "python_version >= '3.7'",
"version": "==4.0.10"
},
"gitpython": {
"hashes": [
"sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573",
"sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.31"
},
"isort": {
"hashes": [
"sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
"sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==5.12.0"
},
"lazy-object-proxy": {
"hashes": [
"sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382",
"sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82",
"sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9",
"sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494",
"sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46",
"sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30",
"sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63",
"sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4",
"sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae",
"sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be",
"sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701",
"sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd",
"sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006",
"sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a",
"sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586",
"sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8",
"sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821",
"sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07",
"sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b",
"sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171",
"sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b",
"sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2",
"sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7",
"sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4",
"sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8",
"sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e",
"sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f",
"sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda",
"sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4",
"sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e",
"sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671",
"sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11",
"sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455",
"sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734",
"sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb",
"sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"
],
"markers": "python_version >= '3.7'",
"version": "==1.9.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"mypy": {
"hashes": [
"sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd",
"sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d",
"sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2",
"sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965",
"sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e",
"sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758",
"sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981",
"sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135",
"sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42",
"sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5",
"sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd",
"sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff",
"sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9",
"sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352",
"sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8",
"sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d",
"sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf",
"sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f",
"sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05",
"sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840",
"sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de",
"sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b",
"sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042",
"sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d",
"sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af",
"sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"
],
"version": "==1.4.0"
},
"mypy-extensions": {
"hashes": [
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"packaging": {
"hashes": [
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.1"
},
"pathspec": {
"hashes": [
"sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
"sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.1"
},
"pep8-naming": {
"hashes": [
"sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164",
"sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"
],
"version": "==0.10.0"
},
"platformdirs": {
"hashes": [
"sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc",
"sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
},
"prospector": {
"extras": [
"with_mypy"
],
"hashes": [
"sha256:3bfe103c28bb821cca84926ca31357fbfd32405e4bf8c34ca2e55885684557e4",
"sha256:cc8f09e79bdd32247edddf05b666940e88ad96338a84f5717b1e8c0678337821"
],
"index": "pypi",
"version": "==1.10.2"
},
"pycodestyle": {
"hashes": [
"sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785",
"sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"
],
"markers": "python_version >= '3.6'",
"version": "==2.9.1"
},
"pydocstyle": {
"hashes": [
"sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019",
"sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"
],
"markers": "python_version >= '3.6'",
"version": "==6.3.0"
},
"pyflakes": {
"hashes": [
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
],
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
"pylint": {
"hashes": [
"sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1",
"sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.17.4"
},
"pylint-celery": {
"hashes": [
"sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"
],
"version": "==0.3"
},
"pylint-django": {
"hashes": [
"sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591",
"sha256:56b12b6adf56d548412445bd35483034394a1a94901c3f8571980a13882299d5"
],
"version": "==2.5.3"
},
"pylint-flask": {
"hashes": [
"sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"
],
"version": "==0.6"
},
"pylint-plugin-utils": {
"hashes": [
"sha256:b3d43e85ab74c4f48bb46ae4ce771e39c3a20f8b3d56982ab17aa73b4f98d535",
"sha256:ce48bc0516ae9415dd5c752c940dfe601b18fe0f48aa249f2386adfa95a004dd"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==0.7"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"markers": "python_version >= '3.6'",
"version": "==6.0"
},
"requirements-detector": {
"hashes": [
"sha256:3642cd7a5b261d79536c36bb7ecacf2adabd902d2e0e42bfb2ba82515da10501",
"sha256:d7c60493bf166da3dd59de0e6cb25765e0e32a1931aeae92614034e5786d0bd0"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==1.2.2"
},
"semver": {
"hashes": [
"sha256:2a23844ba1647362c7490fe3995a86e097bb590d16f0f32dfc383008f19e4cdf",
"sha256:9ec78c5447883c67b97f98c3b6212796708191d22e4ad30f4570f840171cbce1"
],
"markers": "python_version >= '3.7'",
"version": "==3.0.1"
},
"setoptconf-tmp": {
"hashes": [
"sha256:76035d5cd1593d38b9056ae12d460eca3aaa34ad05c315b69145e138ba80a745",
"sha256:e0480addd11347ba52f762f3c4d8afa3e10ad0affbc53e3ffddc0ca5f27d5778"
],
"version": "==0.3.1"
},
"smmap": {
"hashes": [
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.0"
},
"snowballstemmer": {
"hashes": [
"sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
"sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
],
"version": "==2.2.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"tomlkit": {
"hashes": [
"sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171",
"sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.8"
},
"typing-extensions": {
"hashes": [
"sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26",
"sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"
],
"markers": "python_version >= '3.7'",
"version": "==4.6.3"
},
"wrapt": {
"hashes": [
"sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
"sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
"sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
"sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
"sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
"sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
"sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
"sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
"sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
"sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
"sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
"sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
"sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
"sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
"sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
"sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
"sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
"sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
"sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
"sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
"sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
"sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
"sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
"sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
"sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
"sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
"sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
"sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
"sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
"sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
"sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
"sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
"sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
"sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
"sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
"sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
"sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
"sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
"sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
"sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
"sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
"sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
"sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
"sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
"sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
"sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
"sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
"sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
"sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
"sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
"sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
"sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
"sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
"sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
"sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
"sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
"sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
"sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
"sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
"sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
"sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
"sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
"sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
"sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
"sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
"sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
"sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
"sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
"sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
"sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
"sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
"sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
"sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
"sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
"sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
],
"markers": "python_version >= '3.11'",
"version": "==1.15.0"
}
}
}

105
README.md
View File

@ -1,6 +1,6 @@
# Melpomene # Melpomene
![](https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png) ![](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)
Melpomene is a a simple, HTML/CSS/JS webcomic reading utility . Melpomene is a a simple, HTML/CSS/JS webcomic reading utility .
@ -25,19 +25,17 @@ 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 :
+ `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 : + 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 `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.
@ -47,8 +45,9 @@ 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
+ Your comic pages need to be the same size
# How to use Melpomene ? # How to setup Melpomene ?
## Defining the zooms ## Defining the zooms
@ -58,21 +57,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..
@ -83,83 +82,7 @@ If you wish to run a custom generation process, this generator can output a JSON
You are now ready to integrate Melpomene in your website! You are now ready to integrate Melpomene in your website!
## Advanced usage
If you need to do some global scaling / offset of all zooms in HTML (if for example you reuse zooms data for multiple resolutions), you can add the following attributes to the `<div id="reader-pages" ...>` tag :
+ `data-global-zoom-offset="<x offset float value>,<y offset float value>"` : offset all positions by the provided x / y values
+ If they become negative, they get clamped to 0 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
# 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.
## Setting up quality checking for Python
Regarding Python, quality checking is done using [prospector](https://prospector.landscape.io/en/master/) using [mypy](https://mypy.readthedocs.io/en/stable/) as an additional checker.
Auto-formating is done using [Black](https://pypi.org/project/black/)
Dependencies are managed not using `pip` but [`pipenv`](https://pipenv.pypa.io/en/latest/).
To setup prospector, you need to run:
+ Only once, in melpomene's root folder:
+ Install pipenv: `pip install pipenv`
+ Install melpomene's root folder: `pipenv install --dev`
+ Every time to run the quality checking:
+ `pipenv shell`
+ `prospector -s veryhigh -w mypy --max-line-length 88 .`
+ `black .`
For VSCode users, you can run those automatically in your IDE. Here is what your .vscode/settings.json file should include :
```json
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.pyc": true,
"**/__pycache__": true
},
// Replace by your own venv python path
"python.defaultInterpreterPath": "~\\.virtualenvs\\melpomene-ajDJjHHp\\Scripts\\python.exe",
"python.formatting.provider": "black",
"[python]": {
"editor.formatOnType": true,
},
"python.linting.enabled": true,
"python.linting.prospectorEnabled": true,
"python.linting.prospectorArgs": [
"-s",
"veryhigh",
"-w",
"mypy",
"--max-line-length",
"88"
],
"editor.rulers": [
88
],
```
# 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).

83
demos/demo_data.js Normal file
View File

@ -0,0 +1,83 @@
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],
[1, 2481.0, 1078.4192, 0.0, 1364.053],
[1, 562.77032, 909.44702, 102.48115, 2491.6567],
[1, 920.74463, 909.44702, 698.55927, 2491.6567],
[1, 728.776, 909.44702, 1652.3695, 2491.6567],
[2, 1459.9161, 960.62878, 99.857468, 103.85177],
[2, 788.87384, 960.62878, 1593.7252, 103.85177],
[2, 455.35007, 305.56384, 389.44412, 1376.0359],
[2, 2282.7415, 760.914, 99.857475, 1114.4093],
[2, 1069.9728, 496.29166, 101.85461, 1928.2478],
[2, 1209.7733, 496.29166, 1172.3267, 1928.2478],
[2, 788.87402, 926.67731, 101.85462, 2474.468],
[2, 415.40707, 926.67731, 924.68018, 2474.468],
[2, 1008.5604, 926.67731, 1372.0416, 2474.468],
[3, 2278.4634, 1424.0264, 103.9937, 103.9937],
[3, 457.3472, 589.15906, 816.83411, 469.33011],
[3, 618.69055, 832.04285, 104.47829, 1578.7172],
[3, 1170.7311, 832.04285, 756.71887, 1578.7172],
[3, 418.14838, 832.04285, 1962.6545, 1578.7172],
[3, 2280.6614, 940.57422, 100.82664, 2461.1147],
[4, 1128.3893, 547.21899, 100.85604, 101.85461],
[4, 1125.3937, 505.27878, 101.85462, 700.99945],
[4, 659.59674, 1104.1381, 1237.9126, 100.46677],
[4, 474.09705, 1104.7645, 1908.0801, 103.04887],
[4, 991.58466, 641.08496, 365.47833, 1256.2069],
[4, 2276.7502, 1268.1899, 101.85461, 1256.2069],
[4, 669.66565, 827.24689, 100.95136, 2574.8799],
[4, 1574.1992, 825.24969, 806.56573, 2576.877],
[5, 2270.7588, 1348.0758, 107.84606, 109.84322],
[5, 828.80096, 1879.4762, 104.70337, 1519.7256],
[5, 606.50098, 1877.479, 975.78717, 1521.7228],
[5, 755.02411, 1139.5406, 1623.401, 1521.7228],
[5, 757.02124, 680.4198, 1621.4038, 2716.7849],
[6, 2273.6387, 1248.3829, 104.50265, 107.32705],
[6, 624.19147, 440.60574, 1259.6805, 771.06006],
[6, 830.37231, 940.5238, 110.15143, 1415.0222],
[6, 1395.2515, 1313.3441, 985.71411, 1409.3734],
[6, 1321.8173, 796.47961, 112.97583, 2417.6829],
[6, 974.4165, 754.11365, 1403.7247, 2646.4587],
[7, 828.81702, 497.29019, 443.36716, 531.2417],
[7, 2275.0127, 1188.0789, 105.30553, 109.78442],
[7, 2275.0127, 804.28339, 105.30553, 1356.3481],
[7, 847.87823, 1182.9146, 105.30553, 2210.6436],
[7, 497.6918, 1185.7389, 994.37964, 2213.468],
[7, 854.35431, 1185.7389, 1528.7885, 2213.468],
[8, 2272.7561, 1228.2468, 101.85461, 107.84607],
[8, 497.29019, 756.91962, 994.58044, 569.18756],
[8, 2276.7502, 1052.4977, 101.85461, 1336.0929],
[8, 2278.7473, 958.63165, 101.85462, 2444.5107],
[9, 1131.1705, 453.31552, 101.67825, 103.09045],
[9, 1148.1168, 453.31552, 1232.8488, 103.09045],
[9, 1481.6407, 453.31552, 501.89209, 103.09045],
[9, 2282.1118, 855.79193, 98.853851, 604.42072],
[9, 1776.5449, 881.21149, 290.91275, 1355.71],
[9, 703.27454, 398.23981, 762.58685, 2863.9373],
[9, 2270.8142, 1132.5826, 107.32704, 2270.8142],
[10, 2278.7473, 1240.2297, 101.85461, 101.85461],
[10, 667.04791, 1016.549, 99.857468, 1390.016],
[10, 1583.7395, 1014.5519, 798.85974, 1390.016],
[10, 2280.7446, 944.65161, 99.857468, 2456.4937],
[11, 830.81415, 1194.2953, 97.860321, 101.85461],
[11, 774.89398, 1198.2897, 960.62885, 99.857468],
[11, 609.13055, 1192.2982, 1769.4744, 101.85461],
[11, 363.4812, 868.75995, 99.857468, 1348.0758],
[11, 1495.8649, 870.75714, 493.2959, 1346.0787],
[11, 357.48975, 870.75714, 2023.1123, 1348.0758],
[11, 529.24457, 499.28735, 103.85177, 2280.7446],
[11, 531.24176, 687.01941, 99.857468, 2716.123],
[11, 1022.5405, 1120.4008, 669.04504, 2280.7446],
[11, 657.06213, 1120.4008, 1725.5371, 2280.7446],
[12, 704.99371, 341.51254, 619.11633, 281.59805],
[12, 393.43842, 551.21326, 1459.9161, 159.77196],
[12, 2280.7446, 691.01367, 99.857468, 101.85461],
[12, 718.97375, 1198.2897, 99.857468, 840.79987],
[12, 712.9823, 1196.2925, 850.78564, 842.79706],
[12, 780.88538, 1192.2982, 1599.7167, 842.79706],
[12, 2366.6221, 1445.9362, 61.911629, 2049.0752],
[12, 922.68298, 551.21326, 984.59467, 2378.605],
[12, 631.09918, 211.69783, 1851.3574, 3289.3049],
]

View File

@ -11,66 +11,59 @@
<body> <body>
<div id="melpomene"> <!-- Melpomene comic reader -->
<!-- Melpomene comic reader --> <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
<!-- Version 1.0.0_RC1 --> <div id="reader-frame">
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ --> <div id="reader-content-frame">
<div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
<div id="melpomene-content-frame"> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg"/>
<div id="melpomene-pages" hidden> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg"/>
<img loading="lazy" height="3503" width="2481" 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"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/>
<img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/>
</div> </div>
<div id="focus-overlay" class="flex-col fill">
<div id="melpomene-focus-container"> <div class="grow obscured animated"></div>
<div></div> <div id="focus-overlay-height" class="flex animated" style="height:100%">
<div id="melpomene-focus-col"> <div class="grow obscured animated"></div>
<div></div> <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
<div id="melpomene-focus"></div> <div class="grow obscured animated"></div>
<div></div>
</div> </div>
<div></div> <div class="grow obscured animated"></div>
</div> </div>
<div id="nav-controls" class="fill">
<div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);"> <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
<div><div class="melpomene-key">&larr;</div>/ scroll up / clic : previous</div> <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
<div><div class="melpomene-key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
<div><div class="melpomene-key">P</div>: Toggle progress bar</div>
<div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
<div>-----------------------</div>
<div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
<div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
</div> </div>
<div id="help-menu">
<div id="melpomene-nav-controls"> <div id="help-controls" style="opacity:1; transform: translate(0,0);">
<div onclick="moveReader(false,false)"></div> <div><div class="key">&larr;</div>/ scroll up / clic : previous</div>
<div onclick="moveReader(true,false)"></div> <div><div class="key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="key">F</div>: Toggle fullscreen</div>
<div><div class="key">P</div>: Toggle progress bar</div>
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
</div>
</div> </div>
</div> </div>
<div id="reader-progress-container">
<div id="melpomene-progress-container"> <div id="reader-progress-bar"></div>
<div id="melpomene-progress-bar"></div> <div id="reader-progress-pages"></div>
<div id="melpomene-progress-sections"></div>
</div> </div>
</div> </div>
<!-- End of Melpomene comic reader -->
</body> </body>
<!-- melpomene_js.html import --> <!-- melpomene_js.html import -->
<script src="demo_data.js"></script>
<script src="../melpomene.js"></script> <script src="../melpomene.js"></script>
</html> </html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="demo.css">
<!-- melpomene_head.html import -->
<link rel="stylesheet" href="../melpomene.css">
</head>
<body>
<!-- Melpomene comic reader -->
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
<div id="reader-frame">
<div id="reader-content-frame">
<div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
<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"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/>
<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/>
</div>
<div id="focus-overlay" class="flex-col fill">
<div class="grow obscured animated"></div>
<div id="focus-overlay-height" class="flex animated" style="height:100%">
<div class="grow obscured animated"></div>
<div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
<div class="grow obscured animated"></div>
</div>
<div class="grow obscured animated"></div>
</div>
<div id="nav-controls" class="fill">
<div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
<div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
</div>
<div id="help-menu">
<div id="help-controls" style="opacity:1; transform: translate(0,0);">
<div><div class="key">&larr;</div>/ scroll up / clic : previous</div>
<div><div class="key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="key">F</div>: Toggle fullscreen</div>
<div><div class="key">P</div>: Toggle progress bar</div>
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
</div>
</div>
</div>
<div id="reader-progress-container">
<div id="reader-progress-bar"></div>
<div id="reader-progress-pages"></div>
</div>
</div>
<!-- End of Melpomene comic reader -->
</body>
<!-- melpomene_js.html import -->
<script src="../melpomene.js"></script>
</html>

View File

@ -11,66 +11,67 @@
<body> <body>
<div id="melpomene"> <!-- melpomene_reader.html import -->
<!-- Melpomene comic reader -->
<!-- Version 1.0.0_RC1 --> <!-- Melpomene comic reader -->
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ --> <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
<div id="reader-frame">
<div id="melpomene-content-frame"> <div id="reader-content-frame">
<div id="melpomene-pages" data-global-zoom-scale="0.4836759371221282" data-global-zoom-offset="0,-70" hidden> <div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-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"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg"/>
<img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/> <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg"/>
</div> </div>
<div id="melpomene-focus-container"> <div id="focus-overlay" class="flex-col fill">
<div></div> <div class="grow obscured animated"></div>
<div id="melpomene-focus-col"> <div id="focus-overlay-height" class="flex animated" style="height:100%">
<div></div> <div class="grow obscured animated"></div>
<div id="melpomene-focus"></div> <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
<div></div> <div class="grow obscured animated"></div>
</div> </div>
<div></div> <div class="grow obscured animated"></div>
</div> </div>
<div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);"> <div id="nav-controls" class="fill">
<div><div class="melpomene-key">&larr;</div>/ scroll up / clic : previous</div> <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
<div><div class="melpomene-key">&rarr;</div>/ scroll down / clic : next</div> <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
<div>-----------------------</div>
<div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
<div><div class="melpomene-key">P</div>: Toggle progress bar</div>
<div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
<div>-----------------------</div>
<div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
<div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
</div> </div>
<div id="melpomene-nav-controls"> <div id="help-menu">
<div onclick="moveReader(false,false)"></div> <div id="help-controls" style="opacity:1; transform: translate(0,0);">
<div onclick="moveReader(true,false)"></div> <div><div class="key">&larr;</div>/ scroll up / clic : previous</div>
<div><div class="key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="key">F</div>: Toggle fullscreen</div>
<div><div class="key">P</div>: Toggle progress bar</div>
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
</div>
</div> </div>
</div> </div>
<div id="melpomene-progress-container"> <div id="reader-progress-container">
<div id="melpomene-progress-bar"></div> <div id="reader-progress-bar"></div>
<div id="melpomene-progress-sections"></div> <div id="reader-progress-pages"></div>
</div> </div>
</div> </div>
<!-- End of Melpomene comic reader -->
</body> </body>
<!-- melpomene_js.html import --> <!-- melpomene_js.html import -->
<script src="demo_data.js"></script>
<script src="../melpomene.js"></script> <script src="../melpomene.js"></script>
</html> </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
WORKDIR melpomene
CMD ["eslint", "--no-eslintrc", "-c", "eslint/eslintrc.json", "-f", "html", "-o", "eslint_report.html", "melpomene.js"]

View File

@ -1,40 +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]}],
"quote-props": ["warn", "as-needed"],
"no-unused-vars": ["error", {"argsIgnorePattern": "^_"}],
"max-len" : ["warn", {"code": 88}],
"capitalized-comments": "off",
"operator-linebreak": ["warn", "before"],
"max-lines": "off"
}
}

View File

@ -1,44 +0,0 @@
import re
from pathlib import Path
version_input = input("New version = ").strip()
js_file = Path("melpomene.js")
css_file = Path("melpomene.css")
JS_CSS_MATCH = r"(?<=/\* Version ).*(?= \*/)"
JS_VARIABLE_MATCH = r"(?<=const MELPOMENE_VERSION = \").*(?=\";)"
html_file = Path("melpomene.html")
demo_high_res_file = Path("demos/pepper_and_carrot_e35_highres.html")
demo_low_res_file = Path("demos/pepper_and_carrot_e35_lowres.html")
HTML_MATCH = r"(?<=<!-- Version ).*(?= -->)"
zoom_generator_file = Path("zooms_generator.py")
ZOOM_GENERATOR_MATCH = r"(?<=# Version ).*(?=$)"
def replace_version(version, file_path, matcher):
with open(file=file_path, mode="r", encoding="UTF8") as file:
file_data = file.read()
file_data = re.sub(matcher, version, file_data, flags=re.MULTILINE)
with open(file=file_path, mode="w", encoding="UTF8") as file:
file.write(file_data)
if re.match(r"[.a-zA-Z\ \-0-9]{1,30}", version_input):
replace_version(version_input, js_file, JS_CSS_MATCH)
replace_version(version_input, js_file, JS_VARIABLE_MATCH)
replace_version(version_input, css_file, JS_CSS_MATCH)
replace_version(version_input, html_file, HTML_MATCH)
replace_version(version_input, demo_high_res_file, HTML_MATCH)
replace_version(version_input, demo_low_res_file, HTML_MATCH)
replace_version(version_input, zoom_generator_file, ZOOM_GENERATOR_MATCH)
print("Done")
else:
print("Input is not valid : only use letters, numbers, spaces, dots and dashes")

View File

@ -1,8 +1,43 @@
/* Melpomene webcomic reader CSS */ /* Melpomene webcomic reader CSS */
/* Version 1.0.0_RC1 */ /* Version 1.0.0 - UNSTABLE */
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */ /* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
#melpomene-focus-container, #melpomene-nav-controls, #melpomene-progress-sections { :root {
--reader-progressbar-height: 0.3em;
}
.animated {
transition: all 1.5s ease;
}
#reader-frame {
display: flex;
flex-direction: column;
}
#reader-content-frame {
flex: 1;
position: relative;
overflow: hidden;
}
#reader-pages {
position: absolute;
flex-direction: row;
left: 0px;
right: 0px;
transform-origin: top left;
transform: scale(1) translate(0);
}
#reader-pages > img {
display: inline-block;
opacity: 0;
transition: all 1s cubic-bezier(.9,.03,.69,.22); /* ease-in quartic */
flex-shrink: 0;
}
.fill {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -10,100 +45,82 @@
height: 100%; height: 100%;
} }
#melpomene, #melpomene-focus-container, #melpomene-pages, #melpomene-nav-controls, #melpomene-focus-col { .obscured {
background-color: rgba(0, 0, 0, 0.85);
}
.flex {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#melpomene-pages, #melpomene-focus-container * { .flex-col {
transition: all 1.5s ease; display: flex;
}
#melpomene, #melpomene-focus-col {
flex-direction: column; flex-direction: column;
} }
#melpomene { .grow {
height: 100%; flex: 1
width: 100%;
border: 2px solid;
box-sizing: border-box;
background-color: black;
} }
/* Reset images style to avoid external CSS interfering */ #nav-controls {
#melpomene img { display: grid;
all: initial;
}
#melpomene-content-frame {
position: relative;
flex: 1;
overflow: hidden;
}
#melpomene-pages {
transform-origin: top left;
transform: scale(1) translate(0);
align-items: center;
width: fit-content;
height: fit-content;
}
#melpomene-pages > img {
display: inline-block;
opacity: 0;
transition: all 1s cubic-bezier(.9,.03,.69,.22); /* ease-in quartic */
}
#melpomene-focus-container * {
background-color: rgba(0, 0, 0, 0.85);
flex-grow: 1;
}
#melpomene-focus-col, #melpomene-focus {
background-color: initial;
flex-grow: 0;
}
#melpomene-focus {
box-shadow: inset 0px 0px 5px 5px rgba(0, 0, 0, 0.85);
}
#melpomene-nav-controls {
display: flex;
grid-template-areas: grid-template-areas:
"left top right" "left top right"
"left center right" "left center right"
"left bottom right"; "left bottom right";
}
#nav-controls {
grid-template-columns: 33% 0 1fr; grid-template-columns: 33% 0 1fr;
grid-template-rows: auto; grid-template-rows: auto;
} }
#melpomene-nav-controls > div { #nav-controls > div {
flex-grow: 1;
cursor: pointer; cursor: pointer;
} }
#melpomene-nav-controls > div:first-child { .top {
width: 35%; grid-area: top;
flex-grow: 0
} }
#melpomene-help-menu { .left {
grid-area: left;
}
position: absolute; .right {
bottom: 0; grid-area: right;
right: 0; }
.top {
grid-area: top;
}
.bottom {
grid-area: bottom;
}
.focus {
box-shadow: inset 0px 0px 5px 5px rgba(0, 0, 0, 0.85);
}
#help-menu{
font-size: 120%; font-size: 120%;
display: flex;
flex-direction: column;
color: white; color: white;
background-color: rgba(0,0,0,0.8); background-color: rgba(0,0,0,0.8)
border: 0.1em solid black;
height: 100%;
justify-content: end;
box-sizing: border-box;
align-items: end;
}
#help-menu > #help-controls {
padding: 0 1em 1em 1em; padding: 0 1em 1em 1em;
background-color: rgba(0,0,0,0.5);
z-index: 1; z-index: 1;
opacity: 0; opacity: 0;
transition: all 1.7s cubic-bezier(.76,.05,.86,.06); transition: all 1.7s cubic-bezier(.76,.05,.86,.06);
@ -113,28 +130,18 @@
transform: translate(0, calc(100% - 2em)); transform: translate(0, calc(100% - 2em));
} }
#melpomene-help-menu:hover { #help-menu > #help-controls:hover {
opacity: 1; opacity: 1;
transition: all 0.3s linear; transition: all 0.3s linear;
transform: translate(0, 0); transform: translate(0, 0);
} }
#melpomene-help-menu * { #help-menu > #help-controls > div {
margin-top: 1em; margin-top: 1em;
text-align: center; text-align: center;
} }
#melpomene-help-menu .melpomene-credits { .key {
margin-top: 0.5em;
font-size: 70%;
text-align: right;
}
#melpomene-help-menu .melpomene-credits a {
color: white;
}
.melpomene-key {
display: inline; display: inline;
margin: 0 0.5em; margin: 0 0.5em;
border: 1px white solid; border: 1px white solid;
@ -142,25 +149,30 @@
border-radius: 0.2em; border-radius: 0.2em;
} }
#melpomene-progress-container { #reader-progress-container {
background-color: dimgray; background-color: dimgray;
position: relative; position: relative;
} }
#melpomene-progress-bar{ #reader-progress-bar{
height: 0.3em; height: var(--reader-progressbar-height);
background-color: whitesmoke; background-color: whitesmoke;
transition: all 0.5s ease-in-out;
width: 0%; width: 0%;
transition: all 0.5s ease-in-out;
} }
#melpomene-progress-sections { #reader-progress-pages {
display: flex; display: flex;
height: 100%;
position: absolute;
width: 100%;
top: 0;
left: 0;
} }
#melpomene-progress-sections > * { #reader-progress-pages > * {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
border-right: 0.3em black solid; border-right: 0.3em black solid;
flex: 1; flex: 1;
} }

View File

@ -1,45 +1,39 @@
<div id="melpomene"> <!-- Melpomene comic reader -->
<!-- Melpomene comic reader --> <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
<!-- Version 1.0.0_RC1 --> <div id="reader-frame">
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ --> <div id="reader-content-frame">
<!-- replace all 'xxxx' with the actual value -->
<div id="melpomene-content-frame"> <div id="reader-pages" class="animated" data-pages-width="xxxx" data-pages-height="xxxx" hidden>
<img loading="lazy" src="xxxx" data-zooms="xxxx"/>
<div id="melpomene-pages" hidden> <!-- duplicate img for each comic page -->
<!-- your img tags here, see documentation -->
</div> </div>
<div id="focus-overlay" class="flex-col fill">
<div id="melpomene-focus-container"> <div class="grow obscured animated"></div>
<div></div> <div id="focus-overlay-height" class="flex animated" style="height:100%">
<div id="melpomene-focus-col"> <div class="grow obscured animated"></div>
<div></div> <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
<div id="melpomene-focus"></div> <div class="grow obscured animated"></div>
<div></div>
</div> </div>
<div></div> <div class="grow obscured animated"></div>
</div> </div>
<div id="nav-controls" class="fill">
<div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);"> <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
<div><div class="melpomene-key">&larr;</div>/ scroll up / clic : previous</div> <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
<div><div class="melpomene-key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
<div><div class="melpomene-key">P</div>: Toggle progress bar</div>
<div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
<div>-----------------------</div>
<div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
<div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
</div> </div>
<div id="help-menu">
<div id="melpomene-nav-controls"> <div id="help-controls" style="opacity:1; transform: translate(0,0);">
<div onclick="moveReader(false,false)"></div> <div><div class="key">&larr;</div>/ scroll up / clic : previous</div>
<div onclick="moveReader(true,false)"></div> <div><div class="key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="key">F</div>: Toggle fullscreen</div>
<div><div class="key">P</div>: Toggle progress bar</div>
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
</div>
</div> </div>
</div> </div>
<div id="reader-progress-container">
<div id="melpomene-progress-container"> <div id="reader-progress-bar"></div>
<div id="melpomene-progress-bar"></div> <div id="reader-progress-pages"></div>
<div id="melpomene-progress-sections"></div>
</div> </div>
</div> </div>
<!-- End of Melpomene comic reader -->

View File

@ -1,505 +1,351 @@
/* Melpomene webcomic reader JS */ /* Melpomene webcomic reader JS */
/* Version 1.0.0_RC1 */ /* Version 1.0.0 - UNSTABLE */
/* 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"; READER_FRAME = document.getElementById("reader-frame")
READER_CONTENT_FRAME = document.getElementById("reader-content-frame")
READER_PAGES = document.getElementById("reader-pages")
FOCUS_OVERLAY_HEIGHT = document.getElementById("focus-overlay-height")
FOCUS_OVERLAY_WIDTH = document.getElementById("focus-overlay-width")
HELP_CONTROLS = document.getElementById("help-controls")
PROGRESS_BAR_CONTAINER = document.getElementById("reader-progress-container")
PROGRESS_BAR = document.getElementById("reader-progress-bar")
PROGRESS_BAR_PAGES = document.getElementById("reader-progress-pages")
const READER_FRAME = document.getElementById("melpomene"); //===========================
const READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame"); // STATES GLOBAL VARIABLES
const READER_PAGES = document.getElementById("melpomene-pages"); //===========================
const FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus");
const FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col");
const HELP_CONTROLS = document.getElementById("melpomene-help-menu");
const PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container");
const PROGRESS_BAR = document.getElementById("melpomene-progress-bar");
const PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections");
const VERSION_DISPLAY = document.getElementById("melpomene-version");
// ====================
// INDEX CONSTANTS
// ====================
const ZOOM_PAGE_INDEX = 0;
const ZOOM_WIDTH_INDEX = 1;
const ZOOM_HEIGHT_INDEX = 2;
const ZOOM_X_INDEX = 3;
const ZOOM_Y_INDEX = 4;
// ===========================
// STATES GLOBAL VARIABLES
// ===========================
let 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;
} }
let CURRENT_ZOOM = 0; CURRENT_ZOOM = 0
let CURRENT_PAGE = 1; CURRENT_PAGE = 1
let CURRENT_WIDTH = 0; CURRENT_WIDTH = 0
let CURRENT_HEIGHT = 0; CURRENT_HEIGHT = 0
let CURRENT_X = 0; CURRENT_X = 0
let CURRENT_Y = 0; CURRENT_Y = 0
let IS_PAGE_MODE = false; IS_PAGE_MODE = false
let MOUSEWHELL_WAIT = false; MOUSEWHELL_WAIT = false
// ============= // =============
// UTILITIES // UTILITIES
// ============= // =============
// Dimensions utilites
// -------------------
function getPagesCount()
{
return READER_PAGES.childElementCount;
}
function pageOriginalHeight(pageNumber)
{
return READER_PAGES.children[pageNumber - 1].naturalHeight;
}
function pageOriginalWidth(pageNumber)
{
return READER_PAGES.children[pageNumber - 1].naturalWidth;
}
function readerFrameRatio()
{
return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight;
}
function pageMaxHeight()
{
let maxHeight = 0;
for (let pageIdx = 0; pageIdx < READER_PAGES.children.length; pageIdx += 1)
{
if (READER_PAGES.children[pageIdx].naturalHeight > maxHeight)
{
maxHeight = READER_PAGES.children[pageIdx].naturalHeight;
}
}
return maxHeight;
}
function pageVerticalOffset(pageNumber)
{
return (pageMaxHeight() - pageOriginalHeight(pageNumber)) / 2;
}
function previousPagesWidth(pageNumber)
{
// The width of all previous pages relative to the provided index
let totalWidth = 0;
for (let idx = 0; idx < pageNumber - 1; idx += 1)
{
totalWidth += READER_PAGES.children[idx].naturalWidth;
}
return totalWidth;
}
// Zooms utilites // Zooms utilites
// -------------- // --------------
function globalZoomScale() function loadZoomsFromImgTagsIfRequired(){
{
if (READER_PAGES.dataset.globalZoomScale !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomScale);
}
return 1.0;
}
function globalZoomOffsetX()
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[0]);
}
return 0.0;
}
function globalZoomOffsetY()
{
if (READER_PAGES.dataset.globalZoomOffset !== undefined)
{
return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[1]);
}
return 0.0;
}
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 undefined; return res
} }
function getZoomCountForPage(pageNumber) function getZoomCountForPage(pageNumber) {
{ return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
return PAGES_ZOOMS.filter((zoom) => zoom[0] === pageNumber).length;
} }
function getCurrentZoomIndexForPage() function getCurrentZoomIndexForPage() {
{ previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length
const previousZoomsCount = PAGES_ZOOMS.filter((zoom) => zoom[0] < CURRENT_PAGE).length; return CURRENT_ZOOM - previousZoomsCount + 1
return CURRENT_ZOOM - previousZoomsCount + 1;
} }
function getReadingProgressPercent() function getReadingProgressPercent() {
{ progressPerPage = 1 / getPagesCount()
const progressPerPage = 1 / getPagesCount();
if (IS_PAGE_MODE){
if (IS_PAGE_MODE) return 100 * progressPerPage * CURRENT_PAGE
{
return 100 * progressPerPage * CURRENT_PAGE;
} }
const progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE); progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE)
readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom
let readingProgress = (CURRENT_PAGE - 1) * progressPerPage;
readingProgress += getCurrentZoomIndexForPage() * progressPerZoom; return 100 * readingProgress
return 100 * readingProgress;
} }
function updateProgressBar() function updateProgressBar(){
{ PROGRESS_BAR.style.width = getReadingProgressPercent() + "%"
PROGRESS_BAR.style.width = `${getReadingProgressPercent()}%`; }
// Dimensions utilites
// -------------------
function getPagesCount() {
return READER_PAGES.childElementCount
}
function pageOriginalHeight() {
return parseInt(READER_PAGES.dataset.pagesHeight)
}
function pageOriginalWidth() {
return parseInt(READER_PAGES.dataset.pagesWidth)
}
function readerFrameRatio() {
return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight
}
function pageRatio() {
return READER_PAGES.dataset.pagesWidth / READER_PAGES.dataset.pagesHeight
}
function isFrameRatioWiderThanPage(){
return readerFrameRatio() > pageRatio()
}
function pageToFrameScaleFactor(useHeight){
// The scale factor to apply to a page so it exactly fit in the reader frame
if (useHeight) {
return READER_CONTENT_FRAME.clientHeight / pageOriginalHeight()
}
return READER_CONTENT_FRAME.clientWidth / pageOriginalWidth()
}
function totalPagesWidth() {
// The width of all cumuled pages with scale factor applied
return pageOriginalWidth() * getPagesCount()
} }
// ========= // =========
// ACTIONS // ACTIONS
// ========= // =========
function updateFocusByWidth(width) function initReader(){
{ loadZoomsFromImgTagsIfRequired()
FOCUS_OVERLAY_WIDTH.style.width = `${width / READER_CONTENT_FRAME.clientWidth * 100}%`; moveReaderDisplayToZoom(0)
FOCUS_OVERLAY_HEIGHT.style.height = "100%";
}
function updateFocusByHeight(height)
{
FOCUS_OVERLAY_WIDTH.style.width = "100%";
FOCUS_OVERLAY_HEIGHT.style.height = `${height / READER_CONTENT_FRAME.clientHeight * 100}%`;
}
function moveReaderDisplayToArea(pageNumber, oWidth, oHeight, oPosx, oPosy)
{
// Keep original values for registering
let width = oWidth;
let height = oHeight;
let posx = oPosx;
let posy = oPosy;
// Apply global offsets before scales if we are displaying a zoom
// Pages display uses width & height = 0
if (width !== 0 || height !== 0)
{
width = width * globalZoomScale();
height = height * globalZoomScale();
posx = (posx + globalZoomOffsetX()) * globalZoomScale();
posy = (posy + globalZoomOffsetY()) * globalZoomScale();
}
// reduce width if offset sent us outside of page
if (posx < 0)
{
width = width + posx;
posx = 0;
}
if (posx + width > pageOriginalWidth(pageNumber))
{
width = pageOriginalWidth(pageNumber) - posx;
}
// reduce height if offset sent us outside of page
if (posy < 0)
{
height = height + posy;
posy = 0;
}
if (posy + height > pageOriginalHeight(pageNumber))
{
height = pageOriginalHeight(pageNumber) - posy;
}
// Align the top-left corner of the frame with the page
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
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
if (width === 0)
{
width = pageOriginalWidth(pageNumber);
}
if (height === 0)
{
height = pageOriginalHeight(pageNumber);
}
const zoomRatio = width / height;
if (readerFrameRatio() > zoomRatio)
{
// Frame wider than zoom => scale so heights are the same, offset on x
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height;
READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
const scaledWidth = width * zoomToFrameScaleFactor;
const offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2;
READER_PAGES.style.transform = `translateX(${offset}px) ${READER_PAGES.style.transform}`;
updateFocusByWidth(scaledWidth);
}
else
{
// Frame narower than zoom => scale so left/right match, offset on y
const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width;
READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
const scaledHeight = height * zoomToFrameScaleFactor;
const offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2;
READER_PAGES.style.transform = `translateY(${offset}px) ${READER_PAGES.style.transform}"`;
updateFocusByHeight(scaledHeight);
}
// Use values before global offset / scale
CURRENT_PAGE = pageNumber;
CURRENT_WIDTH = oWidth;
CURRENT_HEIGHT = oHeight;
CURRENT_X = oPosx;
CURRENT_Y = oPosy;
}
function refreshReaderDisplay()
{
moveReaderDisplayToArea(
CURRENT_PAGE,
CURRENT_WIDTH,
CURRENT_HEIGHT,
CURRENT_X,
CURRENT_Y
);
}
function moveReaderDisplayToPage(pageNumber)
{
moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0);
}
function moveReaderDisplayToZoom(zoomIdx)
{
moveReaderDisplayToArea(
PAGES_ZOOMS[zoomIdx][ZOOM_PAGE_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_WIDTH_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_HEIGHT_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_X_INDEX],
PAGES_ZOOMS[zoomIdx][ZOOM_Y_INDEX]
);
CURRENT_ZOOM = zoomIdx;
}
function toggleViewMode()
{
if (IS_PAGE_MODE)
{
if (CURRENT_ZOOM === null)
{
moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE));
}
else
{
moveReaderDisplayToZoom(CURRENT_ZOOM);
}
IS_PAGE_MODE = false;
}
else
{
moveReaderDisplayToPage(CURRENT_PAGE);
IS_PAGE_MODE = true;
}
updateProgressBar();
}
function moveReader(toNext)
{
if (IS_PAGE_MODE)
{
if (toNext && CURRENT_PAGE < getPagesCount())
{
moveReaderDisplayToPage(CURRENT_PAGE + 1);
CURRENT_ZOOM = null;
}
else if (!toNext && CURRENT_PAGE > 1)
{
moveReaderDisplayToPage(CURRENT_PAGE - 1);
CURRENT_ZOOM = null;
}
updateProgressBar();
}
// Zoom mode
else
{
if (toNext && CURRENT_ZOOM < PAGES_ZOOMS.length - 1)
{
moveReaderDisplayToZoom(CURRENT_ZOOM + 1);
}
else if (!toNext && CURRENT_ZOOM > 0)
{
moveReaderDisplayToZoom(CURRENT_ZOOM - 1);
}
updateProgressBar();
}
}
function initReader()
{
VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION);
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"; img.style.width = 100 / getPagesCount() + "%"
} visibilityObserver.observe(img)
});
}, PROGRESS_BAR_PAGES.appendChild(document.createElement("div"))
{
root: READER_CONTENT_FRAME,
rootMargin: "-10px"
}
);
for (let pageIndex = 0; pageIndex < READER_PAGES.children.length; pageIndex += 1)
{
const img = READER_PAGES.children[pageIndex];
visibilityObserver.observe(img);
PROGRESS_BAR_PAGES.appendChild(document.createElement("div"));
} }
READER_PAGES.style.display = "flex"
setTimeout(() => {
READER_PAGES.hidden = false
}, "300")
setTimeout(() => {
HELP_CONTROLS.style.opacity = null;
HELP_CONTROLS.style.transform = null;
}, DELAY_BEFORE_HIDDING_CONTROLS)
}
READER_PAGES.style.display = "flex";
setTimeout( function moveReaderDisplayToArea(pageNumber, width, height, posx, posy){
() => { READER_PAGES.hidden = false; },
"300" // First, scale so the page is at scale 1 compared to the frame
); READER_PAGES.style.transform = "scale(" + totalPagesWidth() / READER_CONTENT_FRAME.clientWidth + ")"
// Then, move to the correct page
READER_PAGES.style.transform = "translateX(" + (- pageOriginalWidth() * (pageNumber - 1)) + "px )" + READER_PAGES.style.transform
// 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
// Then, scale so the zoom would fit the frame, and center the zoom
if (width == 0){
width = pageOriginalWidth()
}
if (height == 0){
height = pageOriginalHeight()
}
zoomRatio = width / height
if (readerFrameRatio() > zoomRatio) {
// Frame wider than zoom => scale so heights are the same, offset on x
var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
var scaledWidth = width * zoomToFrameScaleFactor
var offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2
READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform
updateFocusByWidth(scaledWidth)
} else {
// Frame narower than zoom => scale so left/right match, offset on y
var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width
READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
var scaledHeight = height * zoomToFrameScaleFactor
var offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2
READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform
updateFocusByHeight(scaledHeight)
}
CURRENT_PAGE = pageNumber
CURRENT_WIDTH = width
CURRENT_HEIGHT = height
CURRENT_X = posx
CURRENT_Y = posy
}
setTimeout( function refreshReaderDisplay() {
() => moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y)
{ }
HELP_CONTROLS.style.opacity = null;
HELP_CONTROLS.style.transform = null; function moveReaderDisplayToPage(pageNumber) {
}, moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0)
DELAY_BEFORE_HIDDING_CONTROLS }
);
function moveReaderDisplayToZoom(index) {
moveReaderDisplayToArea(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_ZOOMS[index][4])
CURRENT_ZOOM = index
}
function updateFocusByWidth(width){
FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%"
FOCUS_OVERLAY_HEIGHT.style.height = "100%"
}
function updateFocusByHeight(height){
FOCUS_OVERLAY_WIDTH.style.width = "100%"
FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%"
}
function toggleViewMode() {
if (IS_PAGE_MODE){
if (CURRENT_ZOOM != null){
moveReaderDisplayToZoom(CURRENT_ZOOM)
} else {
moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE))
}
IS_PAGE_MODE = false
} else {
moveReaderDisplayToPage(CURRENT_PAGE)
IS_PAGE_MODE = true
}
updateProgressBar()
}
function moveReader(to_next) {
if (IS_PAGE_MODE){
if (to_next && CURRENT_PAGE < getPagesCount()) {
moveReaderDisplayToPage(CURRENT_PAGE + 1)
CURRENT_ZOOM = null
}
else if (!to_next && CURRENT_PAGE > 1) {
moveReaderDisplayToPage(CURRENT_PAGE - 1)
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)
}
}
updateProgressBar()
} }
@ -507,86 +353,57 @@ function initReader()
// 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) else if (key == MOVE_BACK) {
{ moveReader(false)
moveReader(false);
} }
else if (key.toUpperCase() === TOGGLE_FULLSCREEN) else if (key.toUpperCase() == TOGGLE_FULLSCREEN){
{ if (document.fullscreenElement == null){
if (document.fullscreenElement === null)
{
READER_FRAME.requestFullscreen(); READER_FRAME.requestFullscreen();
} } else {
else
{
document.exitFullscreen(); document.exitFullscreen();
} }
} }
else if (key.toUpperCase() === TOGGLE_PROGRESSBAR) else if (key.toUpperCase() == TOGGLE_PROGRESSBAR){
{ if (PROGRESS_BAR_CONTAINER.hidden == true) {
if (PROGRESS_BAR_CONTAINER.hidden === true) PROGRESS_BAR_CONTAINER.hidden = false
{ } else {
PROGRESS_BAR_CONTAINER.hidden = false; PROGRESS_BAR_CONTAINER.hidden = true
} }
else
{
PROGRESS_BAR_CONTAINER.hidden = true;
}
refreshReaderDisplay(); refreshReaderDisplay();
} }
else if (key.toUpperCase() === TOGGLE_VIEW_MODE) else if (key.toUpperCase() == TOGGLE_VIEW_MODE) {
{ toggleViewMode()
toggleViewMode();
} }
} }
function handleMouseWhell(event) function handleMouseWhell(deltaY){
{
// Only handle scroll event if the target is the nav controls if (MOUSEWHELL_WAIT){
// to avoid preventing page scrolling. return
} else {
// Do disable page scrolling when we do prev/next, though MOUSEWHELL_WAIT = true
setTimeout(() => {
if (!READER_FRAME.contains(event.target)) MOUSEWHELL_WAIT = false
{ }, MOUSEWHELL_MIN_DELAY)
return;
} }
event.preventDefault(); if (deltaY > 0) {
event.stopPropagation(); moveReader(true, false)
if (MOUSEWHELL_WAIT)
{
return;
} }
MOUSEWHELL_WAIT = true; else {
setTimeout( moveReader(false, false)
() => { MOUSEWHELL_WAIT = false; },
MOUSEWHELL_MIN_DELAY
);
if (event.deltaY > 0)
{
moveReader(true, false);
}
else
{
moveReader(false, false);
} }
} }
@ -594,26 +411,18 @@ function handleMouseWhell(event)
// INIT // INIT
// ====== // ======
window.addEventListener( window.addEventListener("load", (event) => {
"load", initReader()
(_event) => { 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.deltaY)
(event) => { handleMouseWhell(event); }, });
{ passive: false }
);

View File

@ -1,98 +1,111 @@
# Melpomene webcomic reader JSON/JS/HTML generator # Melpomene webcomic reader JSON/JS/HTML generator
# Version 1.0.0_RC1 # Version 1.0.0 - UNSTABLE
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ # CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
import sys import sys
from argparse import ArgumentParser import re
from xml.etree import ElementTree import xml.etree.ElementTree as ET
from xml.etree.ElementTree import Element import argparse
from typing import Any
from pathlib import Path from pathlib import Path
HTML_TEMPLATE = Path(__file__).parent / "melpomene.html" HTML_START_CONSTANT = """\
HTML_TO_REPLACE = "<!-- your img tags here, see documentation -->" <!-- Melpomene comic reader -->
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
<div id="reader-frame">
<div id="reader-content-frame">
"""
HTML_END_CONSTANT = """\
<div id="focus-overlay" class="flex-col fill">
<div class="grow obscured animated"></div>
<div id="focus-overlay-height" class="flex animated" style="height:100%">
<div class="grow obscured animated"></div>
<div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
<div class="grow obscured animated"></div>
</div>
<div class="grow obscured animated"></div>
</div>
<div id="nav-controls" class="fill">
<div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
<div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
</div>
<div id="help-menu">
<div id="help-controls" style="opacity:1; transform: translate(0,0);">
<div><div class="key">&larr;</div>/ scroll up / clic : previous</div>
<div><div class="key">&rarr;</div>/ scroll down / clic : next</div>
<div>-----------------------</div>
<div><div class="key">F</div>: Toggle fullscreen</div>
<div><div class="key">P</div>: Toggle progress bar</div>
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
</div>
</div>
</div>
<div id="reader-progress-container">
<div id="reader-progress-bar"></div>
<div id="reader-progress-pages"></div>
</div>
</div>
<!-- End of Melpomene comic reader -->
"""
def get_val_has_str(elem: Element, attrib: str, filepath: str | Path) -> str: def extract_zooms(src_folder):
value = elem.get(attrib)
if value is None:
sys.exit(f"Attribute '{attrib}' is not valid in file {filepath}")
return str(value)
def extract_zooms(src_folder) -> dict[int, Any]:
folder = Path(src_folder) folder = Path(src_folder)
pages_zooms: dict[int, Any] = {} zooms = {}
max_width = 0
max_height = 0
idx = 0 idx = 0
for svg_path in folder.glob("*.svg"): for svg_path in folder.glob("*.svg"):
idx += 1 idx += 1
print(f"page {idx} : {svg_path.name}") print(f"page {idx} : {svg_path.name}")
# Setting up default values zooms[idx] = {
pages_zooms[idx] = {
"name": svg_path.stem, "name": svg_path.stem,
"width": 0,
"height": 0,
"zooms": [], "zooms": [],
} }
tree = ElementTree.parse(svg_path) tree = ET.parse(svg_path)
root = tree.getroot() root = tree.getroot()
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")
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")),
])
width = get_val_has_str(root, "width", svg_path) return zooms, max_width, max_height
height = get_val_has_str(root, "height", svg_path)
if "." in width:
print(
f"WARNING: file {svg_path} has a floating width, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["width"] = round(float(width))
if "." in height:
print(
f"WARNING: file {svg_path} has a floating height, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["height"] = round(float(height))
zooms = []
for area in root.findall(".//{*}rect"):
zooms.append(
[
float(get_val_has_str(area, "width", svg_path)),
float(get_val_has_str(area, "height", svg_path)),
float(get_val_has_str(area, "x", svg_path)),
float(get_val_has_str(area, "y", svg_path)),
]
)
pages_zooms[idx]["zooms"] = zooms
return pages_zooms
def write_json_or_js(zooms, dest_file, is_js) -> None: def write_json_or_js(zooms, dest_file, is_js):
with open(dest_file, "w", encoding="UTF-8") as data_file:
with open(dest_file, "w") as data_file:
if is_js: if is_js:
data_file.write("PAGES_ZOOMS = ") data_file.write("PAGES_ZOOMS = ")
data_file.write("[\n") data_file.write("[\n")
first_coma_skiped = False first_coma_skiped = False
for page_idx in sorted(zooms.keys()): for page_idx in sorted(zooms.keys()):
for zoom in zooms[page_idx]["zooms"]: for zoom in zooms[page_idx]["zooms"]:
if zoom[2] < 0 or zoom[3] < 0:
print( if zoom[2] < 0 or zoom[3] < 0 :
f"WARNING: negative pos x / pos y in page {page_idx} for " print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)")
f"zoom {zoom} (is the rectangle flipped?)"
)
if first_coma_skiped: if first_coma_skiped:
data_file.write(",\n") data_file.write(",\n")
else: else:
@ -101,99 +114,69 @@ def write_json_or_js(zooms, dest_file, is_js) -> None:
data_file.write("\n]\n") data_file.write("\n]\n")
def write_html(zooms, dest_file, prefix, extention) -> None: def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention):
img_tags = ""
for page_idx in sorted(zooms.keys()): with open(dest_file, "w") as data_file:
img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
zoom_html_data = [ data_file.write(HTML_START_CONSTANT)
",".join([str(zoom) for zoom in page_zooms])
for page_zooms in zooms[page_idx]["zooms"] data_file.write(f' <div id="reader-pages" class="animated" data-pages-width="{pages_width}" data-pages-height="{pages_height}" hidden>\n')
]
zoom_html_str = ";".join(zoom_html_data) for page_idx in sorted(zooms.keys()):
img_tags = ( img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
img_tags 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)
+ f'<img loading="lazy" height="{zooms[page_idx]["height"]}" ' data_file.write(f' <img loading="lazy" src="{img_url}" data-zooms="{zoom_html_str}"/>\n')
+ f'width="{zooms[page_idx]["width"]}" src="{img_url}" '
+ f'data-zooms="{zoom_html_str}"/>\n' data_file.write(f' </div>\n')
)
data_file.write(HTML_END_CONSTANT)
img_tags = img_tags.strip() def generate_argparse():
""" Generate Melpomene's generator input parser"""
with open(HTML_TEMPLATE, "r", encoding="UTF-8") as template_file, open(
dest_file, "w", encoding="UTF-8" parser = argparse.ArgumentParser(
) as data_file: description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader"
data = template_file.read().replace(HTML_TO_REPLACE, img_tags)
data_file.write(data)
def generate_argparse() -> ArgumentParser:
"""Generate Melpomene's generator input parser"""
parser = 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("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("svg_folders", help="Path of the folder containing the SVGs")
parser.add_argument( parser.add_argument("-o", metavar="dest_file", help="Where to write the generator output to")
"-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.")
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 return parser
def run(): if __name__ == "__main__":
args = generate_argparse().parse_args()
args = generate_argparse().parse_args()
# Get the final outout name # Get the final outout name
output = None output = None
if not args.o: if not args.o:
output = "melpomene_data" output = "melpomene_data"
else: else:
output = args.o output = args.o
if args.output_format == "html" and not output.endswith(".html"): if args.output_format == "html" and not output.endswith(".html"):
output += ".html" output += ".html"
elif args.output_format == "json" and not output.endswith(".json"): elif args.output_format == "json" and not output.endswith(".json"):
output += ".json" output += ".json"
elif args.output_format == "js" and not output.endswith(".js"): elif args.output_format == "js" and not output.endswith(".js"):
output += ".js" output += ".js"
zooms = extract_zooms(args.svg_folders) zooms, max_width, max_height = extract_zooms(args.svg_folders)
if args.output_format == "html": if args.output_format == "html":
write_html(zooms, output, args.p, args.e) write_html(zooms, output, max_width, max_height, args.p, args.e)
elif args.output_format == "json": elif args.output_format == "json":
write_json_or_js(zooms, output, False) write_json_or_js(zooms, output, False)
elif args.output_format == "js": elif args.output_format == "js":
write_json_or_js(zooms, output, True) write_json_or_js(zooms, output, True)
if __name__ == "__main__":
run()