diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py
index 8c9332cd..af3b064e 100644
--- a/bookwyrm/apps.py
+++ b/bookwyrm/apps.py
@@ -1,8 +1,34 @@
+"""Do further startup configuration and initialization"""
+import os
+import urllib
+import logging
+
from django.apps import AppConfig
+
from bookwyrm import settings
+logger = logging.getLogger(__name__)
+
+
+def download_file(url, destination):
+ """Downloads a file to the given path"""
+ try:
+ # Ensure our destination directory exists
+ os.makedirs(os.path.dirname(destination))
+ with urllib.request.urlopen(url) as stream:
+ with open(destination, "b+w") as outfile:
+ outfile.write(stream.read())
+ except (urllib.error.HTTPError, urllib.error.URLError):
+ logger.error("Failed to download file %s", url)
+ except OSError:
+ logger.error("Couldn't open font file %s for writing", destination)
+ except: # pylint: disable=bare-except
+ logger.exception("Unknown error in file download")
+
class BookwyrmConfig(AppConfig):
+ """Handles additional configuration"""
+
name = "bookwyrm"
verbose_name = "BookWyrm"
@@ -11,3 +37,15 @@ class BookwyrmConfig(AppConfig):
from bookwyrm.telemetry import open_telemetry
open_telemetry.instrumentDjango()
+
+ if settings.ENABLE_PREVIEW_IMAGES and settings.FONTS:
+ # Download any fonts that we don't have yet
+ logger.debug("Downloading fonts..")
+ for name, config in settings.FONTS.items():
+ font_path = os.path.join(
+ settings.FONT_DIR, config["directory"], config["filename"]
+ )
+
+ if "url" in config and not os.path.exists(font_path):
+ logger.info("Just a sec, downloading %s", name)
+ download_file(config["url"], font_path)
diff --git a/bookwyrm/preview_images.py b/bookwyrm/preview_images.py
index a97ae2d5..891c8b6d 100644
--- a/bookwyrm/preview_images.py
+++ b/bookwyrm/preview_images.py
@@ -4,6 +4,7 @@ import os
import textwrap
from io import BytesIO
from uuid import uuid4
+import logging
import colorsys
from colorthief import ColorThief
@@ -17,34 +18,49 @@ from django.db.models import Avg
from bookwyrm import models, settings
from bookwyrm.tasks import app
+logger = logging.getLogger(__name__)
IMG_WIDTH = settings.PREVIEW_IMG_WIDTH
IMG_HEIGHT = settings.PREVIEW_IMG_HEIGHT
BG_COLOR = settings.PREVIEW_BG_COLOR
TEXT_COLOR = settings.PREVIEW_TEXT_COLOR
DEFAULT_COVER_COLOR = settings.PREVIEW_DEFAULT_COVER_COLOR
+DEFAULT_FONT = settings.PREVIEW_DEFAULT_FONT
TRANSPARENT_COLOR = (0, 0, 0, 0)
margin = math.floor(IMG_HEIGHT / 10)
gutter = math.floor(margin / 2)
inner_img_height = math.floor(IMG_HEIGHT * 0.8)
inner_img_width = math.floor(inner_img_height * 0.7)
-font_dir = os.path.join(settings.STATIC_ROOT, "fonts/public_sans")
-def get_font(font_name, size=28):
- """Loads custom font"""
- if font_name == "light":
- font_path = os.path.join(font_dir, "PublicSans-Light.ttf")
- if font_name == "regular":
- font_path = os.path.join(font_dir, "PublicSans-Regular.ttf")
- elif font_name == "bold":
- font_path = os.path.join(font_dir, "PublicSans-Bold.ttf")
+def get_imagefont(name, size):
+ """Loads an ImageFont based on config"""
+ try:
+ config = settings.FONTS[name]
+ path = os.path.join(settings.FONT_DIR, config["directory"], config["filename"])
+ return ImageFont.truetype(path, size)
+ except KeyError:
+ logger.error("Font %s not found in config", name)
+ except OSError:
+ logger.error("Could not load font %s from file", name)
+
+ return ImageFont.load_default()
+
+
+def get_font(weight, size=28):
+ """Gets a custom font with the given weight and size"""
+ font = get_imagefont(DEFAULT_FONT, size)
try:
- font = ImageFont.truetype(font_path, size)
- except OSError:
- font = ImageFont.load_default()
+ if weight == "light":
+ font.set_variation_by_name("Light")
+ if weight == "bold":
+ font.set_variation_by_name("Bold")
+ if weight == "regular":
+ font.set_variation_by_name("Regular")
+ except AttributeError:
+ pass
return font
diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py
index de790a9a..e9f3d058 100644
--- a/bookwyrm/settings.py
+++ b/bookwyrm/settings.py
@@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
env = Env()
env.read_env()
DOMAIN = env("DOMAIN")
-VERSION = "0.2.0"
+VERSION = "0.2.1"
PAGE_LENGTH = env("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
@@ -35,6 +35,9 @@ LOCALE_PATHS = [
]
LANGUAGE_COOKIE_NAME = env.str("LANGUAGE_COOKIE_NAME", "django_language")
+STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
+MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
+
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Preview image
@@ -44,6 +47,17 @@ PREVIEW_TEXT_COLOR = env.str("PREVIEW_TEXT_COLOR", "#363636")
PREVIEW_IMG_WIDTH = env.int("PREVIEW_IMG_WIDTH", 1200)
PREVIEW_IMG_HEIGHT = env.int("PREVIEW_IMG_HEIGHT", 630)
PREVIEW_DEFAULT_COVER_COLOR = env.str("PREVIEW_DEFAULT_COVER_COLOR", "#002549")
+PREVIEW_DEFAULT_FONT = env.str("PREVIEW_DEFAULT_FONT", "Source Han Sans")
+
+FONTS = {
+ # pylint: disable=line-too-long
+ "Source Han Sans": {
+ "directory": "source_han_sans",
+ "filename": "SourceHanSans-VF.ttf.ttc",
+ "url": "https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSans-VF.ttf.ttc",
+ }
+}
+FONT_DIR = os.path.join(STATIC_ROOT, "fonts")
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
@@ -150,6 +164,9 @@ LOGGING = {
"handlers": ["console", "mail_admins"],
"level": LOG_LEVEL,
},
+ "django.utils.autoreload": {
+ "level": "INFO",
+ },
# Add a bookwyrm-specific logger
"bookwyrm": {
"handlers": ["console"],
@@ -311,16 +328,11 @@ if USE_S3:
MEDIA_FULL_URL = MEDIA_URL
STATIC_FULL_URL = STATIC_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.ImagesStorage"
- # I don't know if it's used, but the site crashes without it
- STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
- MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
else:
STATIC_URL = "/static/"
- STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_URL = "/images/"
MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}"
STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}"
- MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT", None)
OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS", None)
diff --git a/bookwyrm/static/fonts/source_han_sans/LICENSE.txt b/bookwyrm/static/fonts/source_han_sans/LICENSE.txt
new file mode 100644
index 00000000..ddf7b7e9
--- /dev/null
+++ b/bookwyrm/static/fonts/source_han_sans/LICENSE.txt
@@ -0,0 +1,96 @@
+Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font
+Name 'Source'. Source is a trademark of Adobe in the United States
+and/or other countries.
+
+This Font Software is licensed under the SIL Open Font License,
+Version 1.1.
+
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font
+creation efforts of academic and linguistic communities, and to
+provide a free and open framework in which fonts may be shared and
+improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply to
+any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software
+components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to,
+deleting, or substituting -- in part or in whole -- any of the
+components of the Original Version, by changing formats or by porting
+the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed,
+modify, redistribute, and sell modified and unmodified copies of the
+Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the
+corresponding Copyright Holder. This restriction only applies to the
+primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created using
+the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/bookwyrm/static/fonts/source_han_sans/README.txt b/bookwyrm/static/fonts/source_han_sans/README.txt
new file mode 100644
index 00000000..53cfa9b8
--- /dev/null
+++ b/bookwyrm/static/fonts/source_han_sans/README.txt
@@ -0,0 +1,9 @@
+The font file itself is not included in the Git repository to avoid putting
+large files in the repo history. The Docker image should download the correct
+font into this folder automatically.
+
+In case something goes wrong, the font used is the Variable OTC TTF, available
+as of this writing from the Adobe Fonts GitHub repository:
+https://github.com/adobe-fonts/source-han-sans/tree/release#user-content-variable-otcs
+
+BookWyrm expects the file to be in this folder, named SourceHanSans-VF.ttf.ttc
diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html
index 8e35416f..a4c12f46 100644
--- a/bookwyrm/templates/lists/list.html
+++ b/bookwyrm/templates/lists/list.html
@@ -97,7 +97,7 @@
- {% include "lists/edit_item_form.html" %}
+ {% include "lists/edit_item_form.html" with book=item.book %}
{% endif %}
@@ -112,7 +112,7 @@
- {% include "lists/edit_item_form.html" %}
+ {% include "lists/edit_item_form.html" with book=item.book %}
{% endif %}
diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html
index 3a565221..cc4bb143 100644
--- a/bookwyrm/templates/shelf/shelf.html
+++ b/bookwyrm/templates/shelf/shelf.html
@@ -45,7 +45,7 @@
href="{{ shelf_tab.local_path }}"
{% if shelf_tab.identifier == shelf.identifier %} aria-current="page"{% endif %}
>
- {% include 'user/books_header.html' with shelf=shelf_tab %}
+ {% include "snippets/translated_shelf_name.html" with shelf=shelf_tab %}
{% endfor %}
diff --git a/bookwyrm/views/list/list_item.py b/bookwyrm/views/list/list_item.py
index 29cfbee8..5fd65938 100644
--- a/bookwyrm/views/list/list_item.py
+++ b/bookwyrm/views/list/list_item.py
@@ -19,4 +19,6 @@ class ListItem(View):
form = forms.ListItemForm(request.POST, instance=list_item)
if form.is_valid():
form.save()
+ else:
+ raise Exception(form.errors)
return redirect("list", list_item.book_list.id)
diff --git a/requirements.txt b/requirements.txt
index e2063c25..c1a35d39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,7 @@ environs==9.3.4
flower==1.0.0
gunicorn==20.0.4
Markdown==3.3.3
-Pillow>=8.2.0
+Pillow>=9.0.0
psycopg2==2.8.4
pycryptodome==3.9.4
python-dateutil==2.8.1