From: Radek Czajka Date: Fri, 6 May 2022 11:51:02 +0000 (+0200) Subject: New covers. X-Git-Tag: 2.4 X-Git-Url: https://git.mdrn.pl/librarian.git/commitdiff_plain/8495b2ce8e9aebe778db74217b60fb68c0b5f9f2 New covers. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index e953fb4..13da76c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ This document records all notable changes to Librarian. +## 2.4 (2022-05-06) + +### Added +- New 'marquise' cover style. +- 'Factory' cover style with a QR code. + ## 2.3.5 (2022-04-07) diff --git a/setup.py b/setup.py index 957652f..7bb2477 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def whole_tree(prefix, path): setup( name='librarian', - version='2.3.5', + version='2.4', description='Converter from WolneLektury.pl XML-based language to XHTML, TXT and other formats', author="Marek Stępniowski", author_email='marek@stepniowski.com', @@ -44,6 +44,7 @@ setup( 'ebooklib', 'aeneas', 'mutagen', + 'qrcode', ], entry_points = { "console_scripts": [ diff --git a/src/librarian/cover.py b/src/librarian/cover.py index dfb0b76..b60ec34 100644 --- a/src/librarian/cover.py +++ b/src/librarian/cover.py @@ -152,10 +152,14 @@ class Cover(object): } def __init__(self, book_info, format=None, width=None, height=None, cover_logo=None): + self.book_info = book_info self.authors = [auth.readable() for auth in book_info.authors] self.title = book_info.title if format is not None: self.format = format + self.set_size(width, height) + + def set_size(self, width, height): if width and height: self.height = int(round(height * self.width / width)) scale = max(float(width or 0) / self.width, @@ -167,7 +171,7 @@ class Cover(object): def pretty_authors(self): """Allows for decorating authors' names.""" - return [self.authors] + return self.authors def pretty_title(self): """Allows for decorating title.""" @@ -315,6 +319,8 @@ class WLCover(Cover): bleed=0, cover_logo=None): super(WLCover, self).__init__(book_info, format=format, width=width, height=height) + + self.slug = book_info.url.slug # Set box position. self.box_position = book_info.cover_box_position or \ self.kind_box_position.get(book_info.kind, self.box_position) @@ -336,10 +342,20 @@ class WLCover(Cover): if book_info.cover_url: url = book_info.cover_url bg_src = None - if bg_src is None: - bg_src = URLOpener().open(url) - self.background_img = BytesIO(bg_src.read()) - bg_src.close() + while True: + try: + if bg_src is None: + import requests + bg_src = requests.get(url, timeout=5) + self.background_img = BytesIO(bg_src.content) + bg_src.close() + except Exception as e: + bg_src = None + print(e) + import time + time.sleep(1) + else: + break def get_variable_color(self, book_info): return self.epoch_colors.get(book_info.epoch, None) @@ -354,7 +370,7 @@ class WLCover(Cover): metr = Metric(self, self.scale) # Write author name. - box = TextBox(metr.title_box_width, metr.height, + box = TextBox(metr.title_box_width - 2 * self.bleed, metr.height, padding_y=metr.box_padding_y, padding_x=metr.box_padding_x, bar_width=metr.box_bar_width, @@ -399,7 +415,7 @@ class WLCover(Cover): box_top = (metr.height - box_img.size[1]) // 2 box_left = metr.bar_width + ( - metr.width - metr.bar_width - box_img.size[0] + metr.width - metr.bar_width - box_img.size[0] - self.bleed ) // 2 # Draw the white box. @@ -528,6 +544,8 @@ class LogoWLCover(WLCover): logos_right = True gradient_logo_centering = False + qrcode = None + def __init__(self, book_info, *args, cover_logo=None, **kwargs): super(LogoWLCover, self).__init__(book_info, *args, **kwargs) @@ -546,7 +564,7 @@ class LogoWLCover(WLCover): @property def has_gradient_logos(self): - return self.gradient_logos or self.additional_cover_logos or self.end_cover_logos or self.annotation + return self.gradient_logos or self.additional_cover_logos or self.end_cover_logos or self.annotation or self.qrcode is not None def add_gradient_logos(self, img, metr): gradient = Image.new( @@ -583,7 +601,7 @@ class LogoWLCover(WLCover): logo_top = int( metr.height - metr.gradient_height / 2 - - metr.gradient_logo_height / 2 - metr.bleed / 2 + - metr.gradient_logo_height / 2 - metr.bleed ) logos = [ @@ -593,19 +611,23 @@ class LogoWLCover(WLCover): logos = self.additional_cover_logos + logos + self.end_cover_logos - if self.logos_right: - logos.reverse() - logos = [ Image.open(logo_bytes).convert('RGBA') for logo_bytes in logos ] + if self.qrcode is not None: + import qrcode + logos.append(qrcode.make(f'https://wolnelektury.pl/katalog/lektura/{self.slug}/?{self.qrcode}')) + + if self.logos_right: + logos.reverse() + # See if logos fit into the gradient. If not, scale down accordingly. space_for_logos = ( metr.width - metr.bar_width - - 2 * metr.gradient_logo_margin_right + - 2 * metr.gradient_logo_margin_right + self.bleed ) widths = [ min( @@ -933,6 +955,39 @@ class BNCover(LogoWLCover): # annotation = 'Zadanie „Udostępnienie publikacji w formatach cyfrowych” w ramach Narodowego Programu Rozwoju Czytelnictwa. Dofinansowano ze środków Ministra Kultury, Dziedzictwa Narodowego i Sportu.' +class FactoryCover(LogoWLCover): + gradient_logos = [ + 'res/factory.jpg', + 'res/wl-logo-white.png', + ] + qrcode = 'pk_campaign=factory22' + format = 'PNG' + + def __init__(self, *args, **kwargs): + kwargs.setdefault('bleed', 10) + return super().__init__(*args, **kwargs) + + def ext(self): + return 'pdf' + + def output_file(self, *args, **kwargs): + imgfile = super().output_file(*args, **kwargs) + import subprocess + import tempfile + with tempfile.TemporaryDirectory(prefix='factory-', dir='.') as d: + import os + import shutil + with open(d + '/cover.png', 'wb') as f: + f.write(imgfile.get_bytes()) + shutil.copy(get_resource('res/factory-cover.svg'), d) + subprocess.call(['inkscape', f'--export-filename={d}/cover.pdf', d + '/factory-cover.svg']) + with open(d + '/cover.pdf', 'rb') as f: + return OutputFile.from_bytes(f.read()) + + + +from librarian.covers.marquise import MarquiseCover, LabelMarquiseCover + COVER_CLASSES = { 'default': LogoWLCover, 'kmlu': KMLUCover, @@ -942,6 +997,9 @@ COVER_CLASSES = { 'legimi': LegimiCover, 'legimi-corner': LegimiCornerCover, 'legimi-audiobook': LegimiAudiobookCover, + 'factory': FactoryCover, + 'm': MarquiseCover, + 'm-label': LabelMarquiseCover, } diff --git a/src/librarian/covers/__init__.py b/src/librarian/covers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/librarian/covers/marquise.py b/src/librarian/covers/marquise.py new file mode 100644 index 0000000..283a2be --- /dev/null +++ b/src/librarian/covers/marquise.py @@ -0,0 +1,172 @@ +import PIL.Image +from librarian.cover import Cover, Metric +from .utils.color import algo_contrast_or_hue, luminance, is_very_bright +from .utils.textbox import DoesNotFit +from .widgets.author import AuthorBox +from .widgets.background import Background +from .widgets.image import WLLogo, Label +from .widgets.marquise import Marquise +from .widgets.title import TitleBox + + +class MarquiseCover(Cover): + additional_logos = [] + square_variant = False + + width = 2100 + height = 2970 + margin = 100 + logo_h = 177 + title_box_top = 262 + + color_schemes = [ + { + 'rgb': (0xc3, 0x27, 0x21), + 'text': '#000', + }, + { + 'rgb': (0xa0, 0xbf, 0x38), + 'text': '#000', + }, + { + 'rgb': (0xed, 0xc0, 0x16), + 'text': '#000', + }, + { + 'rgb': (0x47, 0x66, 0x75), + 'text': '#fff', + } + ] + for c in color_schemes: + c['luminance'] = luminance(c['rgb']) + cim = PIL.Image.new('RGB', (1, 1)) + cim.putpixel((0, 0), c['rgb']) + cim.convert('HSV') + c['hsv'] = cim.getpixel((0, 0)) + + + def set_size(self, final_width, final_height): + if final_width is None: + self.m = Metric(self, 1) + else: + if final_width > self.width: + self.m = Metric(self, final_width / self.width) + else: + self.m = Metric(self, 1) + self.scale_after = final_width / self.width + + if final_height is not None: + self.height = round(final_height / self.m._scale) + + self.square_variant = self.height / self.width < 250 / 210 + + marquise_square_small = int(self.width / 2) - 300 + marquise_square_big = int(self.width / 2) - 100 + marquise_a4_small = 2970 - self.width + marquise_a4_big = marquise_a4_small + 100 + + self.marquise_small = int(round(marquise_square_small + (marquise_a4_small - marquise_square_small) * (self.height - self.width) / (2970 - 2100))) + self.marquise_big = int(round(marquise_square_big + (marquise_a4_big - marquise_square_big) * (self.height - self.width) / (2970 - 2100))) + self.marquise_xl = self.marquise_big + 200 + + if self.marquise_small > self.marquise_big: + self.marquise_small = self.marquise_big + + def set_color_scheme_from(self, img): + self.color_scheme = algo_contrast_or_hue(img, self.color_schemes) + self.is_very_bright = is_very_bright(img) + + def image(self): + img = PIL.Image.new('RGB', (self.m.width, self.m.height), self.background_color) + + bg = Background(self) + + if self.square_variant: + layout_options = [ + (self.m.marquise_small, 1), + (self.m.marquise_big, 2), + (self.m.marquise_big, 3), + (self.m.marquise_big, None), + ] + else: + layout_options = [ + (self.m.marquise_small, 2), + (self.m.marquise_small, 1), + (self.m.marquise_big, 3), + (self.m.marquise_xl, 4), + (self.m.marquise_xl, None), + ] + + for marquise_h, lines in layout_options: + title_box_height = marquise_h - self.m.title_box_top - self.m.margin + try: + title_box = TitleBox( + self, + self.m.width - 2 * self.m.margin, + title_box_height, + lines, + force=lines is None + ) + except DoesNotFit: + continue + else: + break + + self.marquise_height = marquise_h + marquise = Marquise(self, marquise_h) + + bg.apply( + img, + 0, marquise.edge_top, + self.m.width, self.m.height - marquise.edge_top + ) + self.set_color_scheme_from( + img.crop(( + 0, marquise.edge_top, + self.m.width, marquise.edge_top + ( + self.m.height - marquise.edge_top + ) / 4 + )) + ) + + marquise.apply( + img, 0, 0, self.m.width + ) + title_box.apply( + img, + marquise.title_box_position[0], + marquise.title_box_position[1], + ) + + AuthorBox(self, self.m.width - self.m.margin).apply( + img, 0, self.m.margin + ) + WLLogo(self).apply(img, self.m.margin, self.m.margin, None, self.m.logo_h) + + + for logo in self.additional_logos: + LogoSticker(self, logo).apply(img, 0, 0) + + + return img + + + +class LabelMarquiseCover(MarquiseCover): + label_left = 95 + label_top = 105 + label_w = 710 + label_h = 710 + + def image(self): + img = super().image() + + Label(self).apply( + img, + self.m.label_left, + self.marquise_height - self.m.label_top, + self.m.label_w, + self.m.label_h + ) + + return img diff --git a/src/librarian/covers/utils/__init__.py b/src/librarian/covers/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/librarian/covers/utils/color.py b/src/librarian/covers/utils/color.py new file mode 100644 index 0000000..afcf38e --- /dev/null +++ b/src/librarian/covers/utils/color.py @@ -0,0 +1,39 @@ +def luminance(rgb): + rgb = [ + c / 255 + for c in rgb + ] + rgb = [ + c / 12.92 if c < .03928 else ((c + .055) / 1.055) ** 2.4 + for c in rgb + ] + return .2126 * rgb[0] + .7152 * rgb[1] + .0722 * rgb[2] + + +def cdist(a, b): + d = abs(a-b) + if d > 128: + d = 256 - d + return d + + +def algo_contrast_or_hue(img, colors): + rgb = img.convert('RGB').resize((1, 1)).getpixel((0, 0)) + lumi = luminance(rgb) + + if lumi > .9: + return colors[3] + elif lumi < .1: + return colors[2] + + hue = img.convert('HSV').resize((1, 1)).getpixel((0, 0))[0] + return max( + colors[:3], + key=lambda c: cdist(hue, c['hsv'][0]) ** 2 + (lumi - c['luminance']) ** 2 + ) + + +def is_very_bright(img): + rgb = img.convert('RGB').getpixel((0, 0)) + lumi = luminance(rgb) + return lumi > .95 diff --git a/src/librarian/covers/utils/textbox.py b/src/librarian/covers/utils/textbox.py new file mode 100644 index 0000000..a5097ed --- /dev/null +++ b/src/librarian/covers/utils/textbox.py @@ -0,0 +1,114 @@ +import PIL.Image +import PIL.ImageDraw + + +def split_words(text): + words = [] + conj = False + for word in text.split(): + if conj: + words[-1] += ' ' + word + else: + words.append(word) + conj = len(word.lstrip('(').lstrip('[')) == 1 + return words + + + +def text_with_tracking(draw, tracking, pos, text, fill=None, font=None): + x, y = pos + for c in text: + width = font.getsize(c)[0] + draw.text((x, y), c, fill=fill, font=font) + x += width + tracking + + +class DoesNotFit(Exception): + pass + + +class TextBox: + def __init__(self, width, height, texts, + font, lines, leading, tracking, + align_h, align_v): + self.width = width + self.height = height + self.texts = texts + self.font = font + self.lines = lines + self.leading = leading + self.tracking = tracking + self.align_h = align_h + self.align_v = align_v + + self.margin_top = self.font.getbbox('l')[1] + + self.glue = self.get_length(' ') + + groups = [ + (self.get_length(word), word) + for word in self.texts + ] + + self.grouping = self.find_grouping(groups, self.lines, self.glue) + if self.grouping is None: + raise DoesNotFit() + + def get_length(self, text): + return self.font.getlength(text) + self.tracking * len(text) + + def as_pil_image(self, color): + img = PIL.Image.new('RGBA', (self.width, self.height + 2 * self.margin_top)) + draw = PIL.ImageDraw.ImageDraw(img) + + font_letter_height = self.font.getmetrics()[0] - self.margin_top + + y = self.align_v * (self.height - ((self.lines - 1) * self.leading + font_letter_height)) + for group in self.grouping: + x = (self.width - group[0] + self.tracking) * self.align_h + self.align_h * - group[0] / 2 + for s, w in group[1]: + text_with_tracking( + draw, self.tracking, (x, y), + w, fill=color, font=self.font + ) + x += s + self.glue + y += self.leading + + return img + + def find_grouping(self, groups, ngroups, glue): + best = None + best_var = None + + mean = sum(g[0] for g in groups) + (len(groups) - ngroups) * glue + if mean > self.width * ngroups: + return None + + for grouping in self.all_groupings(groups, ngroups, glue): + if max(g[0] for g in grouping) > self.width: + continue + var = sum((g[0] - mean) ** 2 for g in grouping) + if best is None or best_var > var: + best, best_var = grouping, var + + return best + + def all_groupings(self, groups, ngroups, glue): + if len(groups) == 1: + if ngroups == 1: + yield [(groups[0][0], groups)] + return + next_groups = groups[1:] + for grouping in self.all_groupings(next_groups, ngroups, glue): + yield [ + ( + groups[0][0] + glue + grouping[0][0], + [groups[0]] + grouping[0][1] + ) + ] + grouping[1:] + if ngroups > 1: + for grouping in self.all_groupings(next_groups, ngroups - 1, glue): + yield [ + (groups[0][0], [groups[0]]) + ] + grouping diff --git a/src/librarian/covers/widgets/__init__.py b/src/librarian/covers/widgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/librarian/covers/widgets/author.py b/src/librarian/covers/widgets/author.py new file mode 100644 index 0000000..786204c --- /dev/null +++ b/src/librarian/covers/widgets/author.py @@ -0,0 +1,107 @@ +import PIL.ImageFont +from librarian import get_resource +from librarian.cover import Metric +from ..utils.textbox import TextBox, split_words +from .base import Widget + + +class AuthorBox(Widget): + font_size = 75 + leading = 92 + + def __init__(self, cover, width): + self.width = width + self.m = Metric(self, cover.m._scale) + super().__init__(cover) + + def setup(self): + author_font = PIL.ImageFont.truetype( + get_resource('fonts/SourceSans3VF-Roman.ttf'), + self.m.font_size, + layout_engine=PIL.ImageFont.LAYOUT_BASIC + ) + author_font.set_variation_by_axes([600]) + + translator_font = PIL.ImageFont.truetype( + get_resource('fonts/SourceSans3VF-Roman.ttf'), + self.m.font_size, + layout_engine=PIL.ImageFont.LAYOUT_BASIC + ) + translator_font.set_variation_by_axes([400]) + + authors = [a.readable() for a in self.cover.book_info.authors] + translators = [a.readable() for a in self.cover.book_info.translators] + if authors and translators: + author_str = ', '.join(authors) + translator_str = '(tłum. ' + ', '.join(translators) + ')' + # just print + parts = [author_str, translator_str] + + self.textboxes = [ + TextBox( + self.width, + self.m.leading * 2, + [author_str], + author_font, + 1, + self.m.leading, + 0, + 1, 0 + ), + TextBox( + self.width, + self.m.leading * 2, + [translator_str], + translator_font, + 1, + self.m.leading, + 0, + 1, 0 + ) + ] + + else: + assert authors + if len(authors) == 2: + parts = authors + elif len(authors) > 2: + parts = [author + ',' for author in authors[:-1]] + [authors[-1]] + else: + parts = split_words(authors[0]) + + try: + self.textboxes = [ + TextBox( + self.width, + self.m.leading * 2, + parts, + author_font, + 2, + self.m.leading, + 0, + 1, 0 + ) + ] + except: + self.textboxes = [ + TextBox( + self.width, + self.m.leading * 2, + parts, + author_font, + 1, + self.m.leading, + 0, + 1, 0 + ) + ] + self.margin_top = self.textboxes[0].margin_top + + def build(self, w, h): + img = PIL.Image.new('RGBA', (self.width, self.m.leading * 2)) + offset = 0 + for i, tb in enumerate(self.textboxes): + sub_img = tb.as_pil_image(self.cover.color_scheme['text']) + img.paste(sub_img, (0, self.m.leading * i), sub_img) + + return img diff --git a/src/librarian/covers/widgets/background.py b/src/librarian/covers/widgets/background.py new file mode 100644 index 0000000..a6c7205 --- /dev/null +++ b/src/librarian/covers/widgets/background.py @@ -0,0 +1,45 @@ +import io +from urllib.request import urlopen +import PIL.Image +from .base import Widget + + +class Background(Widget): + transparency = False + + def setup(self): + if self.cover.book_info.cover_url: + while True: + try: + data = io.BytesIO(urlopen(self.cover.book_info.cover_url, timeout=3).read()) + except: + time.sleep(2) + else: + break + + img = PIL.Image.open(data) + + # crop top square. + if img.size[1] > img.size[0]: + img = img.crop((0, 0, img.size[0], img.size[0])) + else: + left = round((img.size[0] - img.size[1])/2) + img = img.crop(( + left, + 0, + left + img.size[1], + img.size[1] + )) + self.img = img + + def build(self, w, h): + kwadrat = round(max(w, h)) + img = self.img + img = self.img.resize((kwadrat, kwadrat)) + img = img.crop(( + int((img.size[0] - w) / 2), + 0, + w + int((img.size[0] - w) / 2), + h)) + + return img diff --git a/src/librarian/covers/widgets/base.py b/src/librarian/covers/widgets/base.py new file mode 100644 index 0000000..15e4462 --- /dev/null +++ b/src/librarian/covers/widgets/base.py @@ -0,0 +1,21 @@ +class Widget: + transparency = True + margin_top = 0 + + def __init__(self, cover): + self.cover = cover + self.setup() + + def setup(self): + pass + + def build(self, w, h): + raise NotImplementedError() + + def apply(self, img, x, y, w=None, h=None): + my_img = self.build(w, h) + img.paste( + my_img, + (round(x), round(y - self.margin_top)), + my_img if self.transparency else None + ) diff --git a/src/librarian/covers/widgets/image.py b/src/librarian/covers/widgets/image.py new file mode 100644 index 0000000..39879d4 --- /dev/null +++ b/src/librarian/covers/widgets/image.py @@ -0,0 +1,29 @@ +import PIL.Image +from librarian import get_resource +from .base import Widget + + +class ImageWidget(Widget): + def build(self, w, h): + img = PIL.Image.open(self.image_path) + img = img.resize((round(img.size[0] / img.size[1] * h), h)) + return img + + +class WLLogo(ImageWidget): + @property + def image_path(self): + if self.cover.color_scheme['text'] == '#fff': + return get_resource('res/cover/logo_WL_invert.png') + else: + return get_resource('res/cover/logo_WL.png') + + +class Label(ImageWidget): + @property + def image_path(self): + if self.cover.is_very_bright: + return get_resource('res/cover/label_WLpolecaja.szary.png') + else: + return get_resource('res/cover/label_WLpolecaja.png') + diff --git a/src/librarian/covers/widgets/marquise.py b/src/librarian/covers/widgets/marquise.py new file mode 100644 index 0000000..81ccf5e --- /dev/null +++ b/src/librarian/covers/widgets/marquise.py @@ -0,0 +1,45 @@ +import PIL.Image +from .base import Widget + + +class Marquise(Widget): + segments = 4 + + def __init__(self, cover, edge_top): + self.edge_top = edge_top + super().__init__(cover) + + def setup(self): + self.slope_w = self.cover.m.width / self.segments / 2 + self.segment_h = self.cover.m.margin + self.title_box_position = ( + self.cover.m.margin, + self.cover.m.title_box_top + ) + + def get_points(self, w): + tip_y = self.edge_top + self.segment_h + points = [ + (0, 0), + (w, 0), + (w, tip_y), + ] + for i in range(self.segments - 1, 0, -1): + points.extend([ + ((2 * i + 1) * self.slope_w, self.edge_top), + (2 * i * self.slope_w, tip_y) + ]) + points.extend([ + (self.slope_w, self.edge_top), + (0, tip_y) + ]) + return points + + def build(self, w, h): + img = PIL.Image.new('RGBA', ( + round(w), round(self.edge_top + self.segment_h) + )) + draw = PIL.ImageDraw.ImageDraw(img) + draw.polygon( + self.get_points(w), fill=self.cover.color_scheme['rgb']) + return img diff --git a/src/librarian/covers/widgets/title.py b/src/librarian/covers/widgets/title.py new file mode 100644 index 0000000..a5c6522 --- /dev/null +++ b/src/librarian/covers/widgets/title.py @@ -0,0 +1,59 @@ +import PIL.ImageFont +from librarian import get_resource +from librarian.cover import Metric +from ..utils.textbox import TextBox, split_words +from .base import Widget + + +class TitleBox(Widget): + font_size = 159 # 45pt + leading = 176 # 50pt + tracking = 2.385 + + def __init__(self, cover, width, height, lines, force=False): + self.width = width + self.height = height + self.lines = lines + self.force = force + self.m = Metric(self, cover.m._scale) + super().__init__(cover) + + def setup(self): + m = self.m + while True: + try: + self.build_box() + except: + if self.force: + self.m = Metric(self, self.m._scale * .99) + print('lower to', self.m.font_size) + else: + raise + else: + break + + def build_box(self): + title_font = PIL.ImageFont.truetype( + get_resource('fonts/SourceSans3VF-Roman.ttf'), + self.m.font_size, + layout_engine=PIL.ImageFont.LAYOUT_BASIC + ) + title_font.set_variation_by_axes([800]) + + lines = self.lines or (int(self.height * (176/200) / self.m.leading) - 0) + + self.tb = TextBox( + self.width, + self.height, + split_words(self.cover.title), + title_font, + lines, + self.m.leading, + self.m.tracking, + .5, .5 + ) + self.margin_top = self.tb.margin_top + + def build(self, w, h): + return self.tb.as_pil_image(self.cover.color_scheme['text']) + diff --git a/src/librarian/fonts/SourceSans3VF-Roman.ttf b/src/librarian/fonts/SourceSans3VF-Roman.ttf new file mode 100644 index 0000000..093d01d Binary files /dev/null and b/src/librarian/fonts/SourceSans3VF-Roman.ttf differ diff --git a/src/librarian/res/cover/label_WLpolecaja.png b/src/librarian/res/cover/label_WLpolecaja.png new file mode 100644 index 0000000..fd6231c Binary files /dev/null and b/src/librarian/res/cover/label_WLpolecaja.png differ diff --git a/src/librarian/res/cover/label_WLpolecaja.svg b/src/librarian/res/cover/label_WLpolecaja.svg new file mode 100644 index 0000000..0f9f9dd --- /dev/null +++ b/src/librarian/res/cover/label_WLpolecaja.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/librarian/res/cover/label_WLpolecaja.szary.png b/src/librarian/res/cover/label_WLpolecaja.szary.png new file mode 100644 index 0000000..3b7c9cf Binary files /dev/null and b/src/librarian/res/cover/label_WLpolecaja.szary.png differ diff --git a/src/librarian/res/cover/label_WLpolecaja.szary.svg b/src/librarian/res/cover/label_WLpolecaja.szary.svg new file mode 100644 index 0000000..30555e0 --- /dev/null +++ b/src/librarian/res/cover/label_WLpolecaja.szary.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/librarian/res/cover/logo_WL.png b/src/librarian/res/cover/logo_WL.png new file mode 100644 index 0000000..4229cac Binary files /dev/null and b/src/librarian/res/cover/logo_WL.png differ diff --git a/src/librarian/res/cover/logo_WL.svg b/src/librarian/res/cover/logo_WL.svg new file mode 100644 index 0000000..ac91c2f --- /dev/null +++ b/src/librarian/res/cover/logo_WL.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/librarian/res/cover/logo_WL_invert.png b/src/librarian/res/cover/logo_WL_invert.png new file mode 100644 index 0000000..cc33408 Binary files /dev/null and b/src/librarian/res/cover/logo_WL_invert.png differ diff --git a/src/librarian/res/factory-cover.svg b/src/librarian/res/factory-cover.svg new file mode 100644 index 0000000..aa9bc3f --- /dev/null +++ b/src/librarian/res/factory-cover.svg @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/src/librarian/res/factory.jpg b/src/librarian/res/factory.jpg new file mode 100644 index 0000000..5843dde Binary files /dev/null and b/src/librarian/res/factory.jpg differ