Initial commit.

main
Gijs 2 years ago
commit 27ad7da676

3
.gitignore vendored

@ -0,0 +1,3 @@
__pycache__
*.pyc
node_modules

@ -0,0 +1,35 @@
#
The boilerplate uses the python framework flask to run the webinterface and to generate the HTML of the book. To generate the PDF it uses the javascript library pagedjs together with the chromium webbrowser in headless mode.
# Installation
First [download](gitlaburl) it on gitlab.
Or clone the repository:
`git clone `
This boilerplate uses Python 3 and the framework [Flask](https://palletsprojects.com/p/flask/).
Find information on how to [install python here](https://www.python.org/downloads/)
To install the requirements, move to the directory this readme is located, and then run:
`pip install -r requirements.txt`
Then install [pagedjs-cli](https://gitlab.coko.foundation/pagedjs/pagedjs-cli), with the command:
`npm install -g pagedjs-cli`
Finally edit a copy of `settings.example.py` and save it as `settings.py`.
It is important to set the `PAGEDJS_BINARY_PATH`, you should be able to find it
by running: `whereis pagedjs-cli`.
Now you should be able to start the interface with: `./run.sh`.
# Deployment
Todo
Note: this boilerplate does not make any effort at rate-limiting.

@ -0,0 +1,84 @@
from flask import Flask, render_template, Response, abort, url_for
# from weasyprint import HTML
import os
from pagedjs import make_pdf_from_url
from settings import BASEURL, DEBUG, HTML_TMP_DIR, SITEURL
import tempfile
from werkzeug.security import safe_join
app = Flask(__name__)
"""
Shows the index page of the application
"""
@app.route('{}/'.format(BASEURL))
def index():
context = {}
return render_template('index.html', **context)
"""
This is where the book generation happens.
Possibly adjust the allowed methods.
"""
@app.route('{}/generate'.format(BASEURL), methods=['GET', 'POST'])
def generate ():
context = {
'DEBUG': DEBUG
}
html = render_template('book.html', **context)
if (DEBUG):
return html
else:
pdf = make_pdf(html)
print(pdf)
r = Response(pdf, mimetype='application/pdf')
r.headers.extend({
'Content-Disposition': 'attachment; filename="Title of the.pdf"'
})
return r
"""
Proxy(?) for pagedjs, it stores the provided html in a separate file.
Now pagedjs can connect to the application through HTTP.
This way static files, like fonts, images and css can be served in a regular way.
"""
def make_pdf (html):
if not os.path.exists(HTML_TMP_DIR):
os.mkdir(HTML_TMP_DIR)
with tempfile.NamedTemporaryFile(mode='w', prefix='book_', dir=HTML_TMP_DIR, delete=False) as tmpfile:
tmpfile.write(html)
tmpfile.flush()
bookname = os.path.basename(tmpfile.name)
url = SITEURL + url_for('show_book', bookname=bookname)
tmpfile.close()
# Generate the pdf with pagedjs
return make_pdf_from_url(url)
"""
View for pagedjs. It loads the generated HTML from the tmp dir and returns it.
"""
@app.route('{}/book/<string:bookname>'.format(BASEURL))
def show_book (bookname):
bookpath = safe_join(HTML_TMP_DIR, bookname)
if os.path.exists(bookpath):
with open(bookpath, 'r') as h:
html = h.read()
return html
abort(404)

@ -0,0 +1,112 @@
import subprocess
import tempfile
import os.path
from settings import PAGEDJS_BINARY_PATH
basepath = os.path.abspath(os.path.dirname(__file__))
"""
Calls the pagedjs binary.
path_html path to the html sources to be read by pagedjs
path_pdf output path of the generated pdf
cwd path to be used as current working directory for the subprocess
extra_scripts array of paths to additional javascript
"""
def run_pagedjs (path_html, path_pdf, cwd=None, extra_scripts=[]):
args = [
PAGEDJS_BINARY_PATH
]
for script in extra_scripts:
args.extend([
'--additional-script',
script
])
args.extend([
'-o', path_pdf,
path_html
])
print(' '.join(args))
try:
return subprocess.check_output(args, cwd=cwd, stderr=subprocess.STDOUT).decode()
except subprocess.CalledProcessError as e:
return 'Error:\n{}'.format(e.output.decode())
"""
Generates a PDF based on provided HTML using pagedjs and returns the generated PDF.
If optional path_out is provided the PDF is written there and the function returns the path.
Optional extra_scripts is a list of strings with javascript.
Scripts are sent in the same order to paged.js
"""
def make_pdf_from_string (html, path_out=None, extra_scripts=[]):
with tempfile.TemporaryDirectory(prefix='algoliterary_publishing_house_') as tempdir:
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.html', delete=False) as temphtml:
# Store html in a temporary file
temphtml.write(html)
temphtml.close()
name_in = temphtml.name
extra_scripts_tmp = []
for script in extra_scripts:
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.js', delete=False) as tempjs:
tempjs.write(script)
tempjs.close()
extra_scripts_tmp.append(tempjs.name)
# Make a temporary file for the generated PDF
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.pdf', delete=False) as temppdf:
temppdf.close()
name_out = temppdf.name
# Make the pdf
run_pagedjs(name_in, name_out, cwd=basepath, extra_scripts=extra_scripts_tmp)
if path_out:
import shutil
shutil.copy(name_out, path_out)
return path_out
else:
with open(name_out, 'rb') as generated_pdf:
return generated_pdf.read()
"""
Generates a PDF based on provided HTML using pagedjs and returns the generated PDF.
If optional path_out is provided the PDF is written there and the function returns the path.
Optional extra_scripts is a list of strings with javascript.
Scripts are sent in the same order to paged.js
"""
def make_pdf_from_url (url, path_out=None, extra_scripts=[]):
with tempfile.TemporaryDirectory(prefix='algoliterary_publishing_house_') as tempdir:
extra_scripts_tmp = []
for script in extra_scripts:
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.js', delete=False) as tempjs:
tempjs.write(script)
tempjs.close()
extra_scripts_tmp.append(tempjs.name)
# Make a temporary file for the generated PDF
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.pdf', delete=False) as temppdf:
temppdf.close()
name_out = temppdf.name
# Make the pdf
run_pagedjs(url, name_out, cwd=basepath, extra_scripts=extra_scripts_tmp)
if path_out:
import shutil
shutil.copy(name_out, path_out)
return path_out
else:
with open(name_out, 'rb') as generated_pdf:
return generated_pdf.read()

@ -0,0 +1,4 @@
#!/bin/bash
export FLASK_APP=app.py
export FLASK_ENV=development # Disable line on deployment
flask run

@ -0,0 +1,11 @@
PAGEDJS_BINARY_PATH = ''
# Path to the pagedjs-cli executable
# Find it with `whereis pagedjs-cli`
DEBUG = False
# When set to true, the application doesn't generate a PDF,
# but uses the pagedjs-polyfill to show a preview of the document
SITEURL = 'http://localhost:5000'
# The url pagedjs-cli tries to connect to, this url is the
# default of the Flask development server
BASEURL = ''
HTML_TMP_DIR = '/tmp/publishing_house/'

File diff suppressed because it is too large Load Diff

@ -0,0 +1,180 @@
/* CSS for Paged.js interface v0.3 */
/* Change the look */
:root {
--color-background: whitesmoke;
--color-pageSheet: #cfcfcf;
--color-pageBox: violet;
--color-paper: white;
--color-marginBox: transparent;
--pagedjs-crop-color: black;
--pagedjs-crop-shadow: white;
--pagedjs-crop-stroke: 1px;
}
/* To define how the book look on the screen: */
@media screen, pagedjs-ignore {
body {
background-color: var(--color-background);
}
.pagedjs_pages {
display: flex;
width: calc(var(--pagedjs-width) * 2);
flex: 0;
flex-wrap: wrap;
margin: 0 auto;
}
.pagedjs_page {
background-color: var(--color-paper);
box-shadow: 0 0 0 1px var(--color-pageSheet);
margin: 0;
flex-shrink: 0;
flex-grow: 0;
margin-top: 10mm;
}
.pagedjs_first_page {
margin-left: var(--pagedjs-width);
}
.pagedjs_page:last-of-type {
margin-bottom: 10mm;
}
.pagedjs_pagebox{
box-shadow: 0 0 0 1px var(--color-pageBox);
}
.pagedjs_left_page{
z-index: 20;
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop {
border-color: transparent;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: 0;
}
.pagedjs_right_page{
z-index: 10;
position: relative;
left: calc(var(--pagedjs-bleed-left)*-1);
}
/* show the margin-box */
.pagedjs_margin-top-left-corner-holder,
.pagedjs_margin-top,
.pagedjs_margin-top-left,
.pagedjs_margin-top-center,
.pagedjs_margin-top-right,
.pagedjs_margin-top-right-corner-holder,
.pagedjs_margin-bottom-left-corner-holder,
.pagedjs_margin-bottom,
.pagedjs_margin-bottom-left,
.pagedjs_margin-bottom-center,
.pagedjs_margin-bottom-right,
.pagedjs_margin-bottom-right-corner-holder,
.pagedjs_margin-right,
.pagedjs_margin-right-top,
.pagedjs_margin-right-middle,
.pagedjs_margin-right-bottom,
.pagedjs_margin-left,
.pagedjs_margin-left-top,
.pagedjs_margin-left-middle,
.pagedjs_margin-left-bottom {
box-shadow: 0 0 0 1px inset var(--color-marginBox);
}
/* uncomment this part for recto/verso book : ------------------------------------ */
/*
.pagedjs_pages {
flex-direction: column;
width: 100%;
}
.pagedjs_first_page {
margin-left: 0;
}
.pagedjs_page {
margin: 0 auto;
margin-top: 10mm;
}
.pagedjs_left_page{
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width) + var(--pagedjs-bleed-left))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop{
border-color: var(--pagedjs-crop-color);
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: var(--pagedjs-cross-size)!important;
}
.pagedjs_right_page{
left: 0;
}
*/
/*--------------------------------------------------------------------------------------*/
/* uncomment this par to see the baseline : -------------------------------------------*/
/* .pagedjs_pagebox {
--pagedjs-baseline: 22px;
--pagedjs-baseline-position: 5px;
--pagedjs-baseline-color: cyan;
background: linear-gradient(transparent 0%, transparent calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) var(--pagedjs-baseline)), transparent;
background-size: 100% var(--pagedjs-baseline);
background-repeat: repeat-y;
background-position-y: var(--pagedjs-baseline-position);
} */
/*--------------------------------------------------------------------------------------*/
}
/* Marks (to delete when merge in paged.js) */
.pagedjs_marks-crop{
z-index: 999999999999;
}
.pagedjs_bleed-top .pagedjs_marks-crop,
.pagedjs_bleed-bottom .pagedjs_marks-crop{
box-shadow: 1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-top .pagedjs_marks-crop:last-child,
.pagedjs_bleed-bottom .pagedjs_marks-crop:last-child{
box-shadow: -1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop,
.pagedjs_bleed-right .pagedjs_marks-crop{
box-shadow: 0px 1px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop:last-child,
.pagedjs_bleed-right .pagedjs_marks-crop:last-child{
box-shadow: 0px -1px 0px 0px var(--pagedjs-crop-shadow);
}

@ -0,0 +1,4 @@
h1 {
text-decoration: underline;
break-after: always;
}

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
{% if DEBUG %}
<link href="{{ url_for('static', filename='pagedjs.interface.css') }}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/paged.polyfill.js') }}"></script>
<!-- <script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script> -->
{% endif %}
<title>Generated book</title>
</head>
<body>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
</body>
</html>

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
<title>Generative book</title>
</head>
<body>
<a href="{{ url_for('generate') }}">Generate</a>
<!-- Or to generate the book with a post request, uncomment the block below. -->
<!-- <form method="post" action="{{ url_for('generate') }}">
<button>Generate</button>
</form> -->
</body>
</html>

@ -0,0 +1,27 @@
import os
from fcntl import lockf, LOCK_EX, LOCK_UN
def get_edition_count(counter_path):
# Try to open a file descriptor to the given path
fd = os.open(counter_path, os.O_RDWR|os.O_CREAT)
# Lock the file so it can't be read in other processes
lockf(fd, LOCK_EX)
# Open the file and read
fo = os.fdopen(fd, 'r+', encoding='utf-8')
content = fo.read()
if not content:
edition_count = 0
else:
edition_count = int(content.strip())
edition_count += 1
# Clear file, write incremented value, unlock file and close
fo.seek(0)
fo.truncate()
fo.write(str(edition_count))
fo.flush()
lockf(fd, LOCK_UN)
os.close(fd)
return edition_count
Loading…
Cancel
Save