papyru 2.10.4.dev77__tar.gz → 2.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {papyru-2.10.4.dev77/papyru.egg-info → papyru-2.11.0}/PKG-INFO +1 -1
- papyru-2.11.0/papyru/create_variant/component.py +35 -0
- papyru-2.11.0/papyru/create_variant/constants.py +42 -0
- papyru-2.11.0/papyru/create_variant/edit_template/__init__.py +0 -0
- papyru-2.11.0/papyru/create_variant/edit_template/ean.py +64 -0
- papyru-2.11.0/papyru/create_variant/edit_template/manufacturer_address.py +67 -0
- papyru-2.11.0/papyru/create_variant/edit_template/motive.py +14 -0
- papyru-2.11.0/papyru/create_variant/edit_template/render_text.py +107 -0
- papyru-2.11.0/papyru/create_variant/edit_template/title.py +59 -0
- papyru-2.11.0/papyru/create_variant/edit_template/xml.py +126 -0
- papyru-2.11.0/papyru/create_variant/resi.py +25 -0
- papyru-2.11.0/papyru/static/__init__.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0/papyru.egg-info}/PKG-INFO +1 -1
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru.egg-info/SOURCES.txt +11 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/setup.py +3 -1
- {papyru-2.10.4.dev77 → papyru-2.11.0}/LICENSE.txt +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/README.md +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/bin/generate_jsonschema.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/__init__.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/auth.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/context.py +0 -0
- {papyru-2.10.4.dev77/papyru/static → papyru-2.11.0/papyru/create_variant}/__init__.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/logger.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/logger_stdout.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/logger_types.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/middleware.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/problem.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/profiler.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/resource.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/serializer.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/stack_context.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/static/apps.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/static/urls.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/static/views.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/utils.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/validation.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/__init__.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/assets/Job.json +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/assets/JobStatusHistory.json +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/command/__init__.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/command/config.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/command/types.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/serializers.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/varspool/types.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru/xml_helper.py +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru.egg-info/dependency_links.txt +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru.egg-info/requires.txt +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/papyru.egg-info/top_level.txt +0 -0
- {papyru-2.10.4.dev77 → papyru-2.11.0}/setup.cfg +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from papyru import XMLHelper
|
|
2
|
+
|
|
3
|
+
from .edit_template.ean import insert_ean
|
|
4
|
+
from .edit_template.manufacturer_address import insert_manufacturer_address
|
|
5
|
+
from .edit_template.motive import insert_motive
|
|
6
|
+
from .edit_template.title import insert_title
|
|
7
|
+
from .edit_template.xml import ImageInfo
|
|
8
|
+
from .resi import import_into_resi
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_component(template: str,
|
|
12
|
+
motive_image_info: ImageInfo,
|
|
13
|
+
resi_instance: object,
|
|
14
|
+
resi_url: str,
|
|
15
|
+
disposition_component: str,
|
|
16
|
+
disposition_manufacturer_address: str,
|
|
17
|
+
disposition_title=None,
|
|
18
|
+
motive_title=None,
|
|
19
|
+
disposition_ean=None,
|
|
20
|
+
ean=None,
|
|
21
|
+
properties=None,
|
|
22
|
+
) -> str:
|
|
23
|
+
|
|
24
|
+
doc = XMLHelper(template)
|
|
25
|
+
insert_motive(doc, motive_image_info)
|
|
26
|
+
insert_title(doc, motive_title, disposition_title, resi_instance, resi_url)
|
|
27
|
+
insert_ean(doc, ean, disposition_ean, resi_instance, resi_url)
|
|
28
|
+
insert_manufacturer_address(doc, disposition_manufacturer_address,
|
|
29
|
+
resi_instance, resi_url)
|
|
30
|
+
|
|
31
|
+
return import_into_resi(doc.serialize(doc.root),
|
|
32
|
+
resi_instance,
|
|
33
|
+
resi_url,
|
|
34
|
+
properties,
|
|
35
|
+
disposition_component)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for specific templates
|
|
3
|
+
|
|
4
|
+
These constants are used in specific templates to ensure that SVG
|
|
5
|
+
components are generated correctly, e.g.:
|
|
6
|
+
|
|
7
|
+
- https://masa.productdesignerd.puzzleandplay.de/
|
|
8
|
+
templates/single-image-4-3-landscape/structure/original
|
|
9
|
+
- https://masa.productdesignerd.puzzleandplay.de/
|
|
10
|
+
templates/box_small_500_landscape_grey/structure/original
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .edit_template.render_text import (Alignment, HorizontalAlignment,
|
|
14
|
+
RGBAColor, VerticalAlignment)
|
|
15
|
+
|
|
16
|
+
# Preview types can be overwritten by ShopAttributes in Masa.
|
|
17
|
+
PREVIEW_TYPE_BOX = 'box'
|
|
18
|
+
PREVIEW_TYPE_PARTS = 'pieces'
|
|
19
|
+
|
|
20
|
+
PREVIEW_IMAGE_FORMAT = 'jpeg'
|
|
21
|
+
PREVIEW_MIME_TYPE = 'image/%s' % PREVIEW_IMAGE_FORMAT
|
|
22
|
+
PREVIEW_QUALITY_LEVEL = 90
|
|
23
|
+
DEFAULT_PREVIEW_SIZE = 1500
|
|
24
|
+
|
|
25
|
+
MOTIVE_PLACEHOLDER_ID = 'img-main'
|
|
26
|
+
MOTIVE_USE_ID = 'img-main-use'
|
|
27
|
+
|
|
28
|
+
TITLE_PLACEHOLDER_ID = 'txt-main'
|
|
29
|
+
TITLE_ALIGNMENT = Alignment(
|
|
30
|
+
vertical=VerticalAlignment.Top, horizontal=HorizontalAlignment.Center)
|
|
31
|
+
TITLE_FONT = 'FreeSerif'
|
|
32
|
+
TITLE_COLOR_FOREGROUND = RGBAColor(255, 255, 255, 1.0)
|
|
33
|
+
TITLE_COLOR_BACKGROUND = RGBAColor(0, 0, 0, 0.0)
|
|
34
|
+
|
|
35
|
+
EAN_PLACEHOLDER_ID = 'img-ean'
|
|
36
|
+
|
|
37
|
+
MANUFACTURER_ADDRESS_PLACEHOLDER_ID = 'manufacturer-address'
|
|
38
|
+
MANUFACTURER_ADDRESS_ALIGNMENT = Alignment(
|
|
39
|
+
vertical=VerticalAlignment.Center, horizontal=HorizontalAlignment.Center)
|
|
40
|
+
MANUFACTURER_ADDRESS_FONT = 'Open Sans'
|
|
41
|
+
MANUFACTURER_ADDRESS_COLOR_FOREGROUND = RGBAColor(0, 0, 0, 1.0)
|
|
42
|
+
MANUFACTURER_ADDRESS_COLOR_BACKGROUND = RGBAColor(0, 0, 0, 0.0)
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from os import getpid
|
|
2
|
+
from subprocess import PIPE, Popen
|
|
3
|
+
|
|
4
|
+
from papyru import XMLHelper
|
|
5
|
+
from papyru.create_variant.constants import EAN_PLACEHOLDER_ID
|
|
6
|
+
from papyru.create_variant.resi import import_into_resi
|
|
7
|
+
|
|
8
|
+
from .xml import ImageInfo, find_placeholder, fit_image
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def insert_ean(doc: XMLHelper,
|
|
12
|
+
ean: str,
|
|
13
|
+
disposition: str,
|
|
14
|
+
resi_instance: object,
|
|
15
|
+
resi_url: str,
|
|
16
|
+
) -> XMLHelper:
|
|
17
|
+
ph = find_placeholder(doc, 'image', EAN_PLACEHOLDER_ID)
|
|
18
|
+
|
|
19
|
+
if ph is None:
|
|
20
|
+
return doc
|
|
21
|
+
|
|
22
|
+
width = float(doc.get_attribute(ph.wrapper, 'width'))
|
|
23
|
+
height = float(doc.get_attribute(ph.wrapper, 'height'))
|
|
24
|
+
|
|
25
|
+
ean_location = _create_ean(ean, width, height, disposition,
|
|
26
|
+
resi_instance, resi_url)
|
|
27
|
+
|
|
28
|
+
return fit_image(doc,
|
|
29
|
+
EAN_PLACEHOLDER_ID,
|
|
30
|
+
ImageInfo(location=ean_location,
|
|
31
|
+
width=width,
|
|
32
|
+
height=height))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _create_ean(ean: str,
|
|
36
|
+
width: int,
|
|
37
|
+
height: int,
|
|
38
|
+
disposition: str,
|
|
39
|
+
resi_instance: any,
|
|
40
|
+
resi_url: str) -> str:
|
|
41
|
+
_, ean_bytes = resi_instance.provide_version_bytes(
|
|
42
|
+
generate_barcode_svg(ean).encode(),
|
|
43
|
+
{
|
|
44
|
+
"convert": "image/png",
|
|
45
|
+
"stretch": {
|
|
46
|
+
"width": int(width),
|
|
47
|
+
"height": int(height),
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
track=str(getpid()))
|
|
51
|
+
|
|
52
|
+
return import_into_resi(
|
|
53
|
+
ean_bytes, resi_instance, resi_url, None, disposition)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def generate_barcode_svg(text: str) -> str:
|
|
57
|
+
call = ['barcode', '-e', 'EAN', '-S']
|
|
58
|
+
|
|
59
|
+
with Popen(call, stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True) as proc:
|
|
60
|
+
out, err = proc.communicate(input=text)
|
|
61
|
+
if err != '':
|
|
62
|
+
raise Exception("call barcode failed: %s" % err)
|
|
63
|
+
|
|
64
|
+
return out.replace('Helvetica', 'Open Sans')
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
|
|
3
|
+
from papyru import XMLHelper
|
|
4
|
+
from papyru.create_variant.constants import (
|
|
5
|
+
MANUFACTURER_ADDRESS_ALIGNMENT, MANUFACTURER_ADDRESS_COLOR_BACKGROUND,
|
|
6
|
+
MANUFACTURER_ADDRESS_COLOR_FOREGROUND, MANUFACTURER_ADDRESS_FONT,
|
|
7
|
+
MANUFACTURER_ADDRESS_PLACEHOLDER_ID)
|
|
8
|
+
from papyru.create_variant.resi import import_into_resi
|
|
9
|
+
|
|
10
|
+
from .render_text import TextConfig, render_text
|
|
11
|
+
from .xml import (ImageInfo, find_placeholder, fit_image,
|
|
12
|
+
replace_text_with_image)
|
|
13
|
+
|
|
14
|
+
log = getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def insert_manufacturer_address(doc: XMLHelper,
|
|
18
|
+
disposition: str,
|
|
19
|
+
resi_instance: object,
|
|
20
|
+
resi_url: str,
|
|
21
|
+
) -> XMLHelper:
|
|
22
|
+
ph = find_placeholder(doc, 'text', MANUFACTURER_ADDRESS_PLACEHOLDER_ID)
|
|
23
|
+
if ph is None:
|
|
24
|
+
log.warning('no manufacturer address found')
|
|
25
|
+
return doc
|
|
26
|
+
|
|
27
|
+
width = float(doc.get_attribute(ph.wrapper, 'width'))
|
|
28
|
+
height = float(doc.get_attribute(ph.wrapper, 'height'))
|
|
29
|
+
|
|
30
|
+
address = '\n'.join(map(
|
|
31
|
+
lambda tspan: tspan.text,
|
|
32
|
+
ph.placeholder.findall(
|
|
33
|
+
'.//{%s}tspan' % doc.default_namespace)))
|
|
34
|
+
location = _create_text_manufacturer_address(
|
|
35
|
+
address, width, height, disposition, resi_instance, resi_url)
|
|
36
|
+
|
|
37
|
+
return fit_image(
|
|
38
|
+
replace_text_with_image(doc, MANUFACTURER_ADDRESS_PLACEHOLDER_ID),
|
|
39
|
+
MANUFACTURER_ADDRESS_PLACEHOLDER_ID,
|
|
40
|
+
ImageInfo(location=location,
|
|
41
|
+
width=width,
|
|
42
|
+
height=height))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _create_text_manufacturer_address(address: str,
|
|
46
|
+
width: int,
|
|
47
|
+
height: int,
|
|
48
|
+
disposition: str,
|
|
49
|
+
resi_instance: any,
|
|
50
|
+
resi_url: str) -> str:
|
|
51
|
+
text_bytes = render_text(
|
|
52
|
+
TextConfig(
|
|
53
|
+
text=address,
|
|
54
|
+
width=int(width),
|
|
55
|
+
height=int(height),
|
|
56
|
+
alignment=MANUFACTURER_ADDRESS_ALIGNMENT,
|
|
57
|
+
font_name=MANUFACTURER_ADDRESS_FONT,
|
|
58
|
+
fg_color=MANUFACTURER_ADDRESS_COLOR_FOREGROUND,
|
|
59
|
+
bg_color=MANUFACTURER_ADDRESS_COLOR_BACKGROUND),
|
|
60
|
+
resi_instance)
|
|
61
|
+
|
|
62
|
+
return import_into_resi(
|
|
63
|
+
text_bytes,
|
|
64
|
+
resi_instance,
|
|
65
|
+
resi_url,
|
|
66
|
+
None,
|
|
67
|
+
disposition)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
|
|
3
|
+
from papyru import XMLHelper
|
|
4
|
+
from papyru.create_variant.constants import (MOTIVE_PLACEHOLDER_ID,
|
|
5
|
+
MOTIVE_USE_ID)
|
|
6
|
+
|
|
7
|
+
from .xml import ImageInfo, fit_image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def insert_motive(doc: XMLHelper, motive_info: ImageInfo) -> XMLHelper:
|
|
11
|
+
return reduce(lambda acc, el: fit_image(acc, el, motive_info),
|
|
12
|
+
[MOTIVE_PLACEHOLDER_ID,
|
|
13
|
+
MOTIVE_USE_ID],
|
|
14
|
+
doc)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from os import path
|
|
4
|
+
from subprocess import run
|
|
5
|
+
from tempfile import TemporaryDirectory
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class RGBAColor:
|
|
11
|
+
red: int
|
|
12
|
+
green: int
|
|
13
|
+
blue: int
|
|
14
|
+
alpha: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VerticalAlignment(Enum):
|
|
18
|
+
Center = 0x01
|
|
19
|
+
Top = 0x02
|
|
20
|
+
Bottom = 0x03
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HorizontalAlignment(Enum):
|
|
24
|
+
Center = 0x01
|
|
25
|
+
Left = 0x02
|
|
26
|
+
Right = 0x03
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class Alignment:
|
|
31
|
+
vertical: VerticalAlignment
|
|
32
|
+
horizontal: HorizontalAlignment
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class TextConfig:
|
|
37
|
+
text: str
|
|
38
|
+
fg_color: RGBAColor
|
|
39
|
+
bg_color: RGBAColor
|
|
40
|
+
font_name: str
|
|
41
|
+
alignment: Alignment
|
|
42
|
+
width: int
|
|
43
|
+
height: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_text(config: TextConfig, resi_instance: object) -> bytes:
|
|
47
|
+
# see `convert -list gravity`
|
|
48
|
+
def _get_gravity(alignment: Alignment) -> str:
|
|
49
|
+
h = ''
|
|
50
|
+
v = ''
|
|
51
|
+
|
|
52
|
+
if alignment.horizontal == HorizontalAlignment.Left:
|
|
53
|
+
h = 'West'
|
|
54
|
+
elif alignment.horizontal == HorizontalAlignment.Right:
|
|
55
|
+
h = 'East'
|
|
56
|
+
|
|
57
|
+
if alignment.vertical == VerticalAlignment.Top:
|
|
58
|
+
v = 'North'
|
|
59
|
+
elif alignment.vertical == VerticalAlignment.Bottom:
|
|
60
|
+
v = 'South'
|
|
61
|
+
|
|
62
|
+
if h == '' and v == '':
|
|
63
|
+
return 'Center'
|
|
64
|
+
|
|
65
|
+
return '%s%s' % (v, h)
|
|
66
|
+
|
|
67
|
+
def _get_font(font_name, resi_instance):
|
|
68
|
+
fonts_dir = resi_instance.fonts_directory()
|
|
69
|
+
|
|
70
|
+
if fonts_dir is None or not path.exists(fonts_dir):
|
|
71
|
+
raise Exception('missing fonts directory')
|
|
72
|
+
|
|
73
|
+
return path.join(fonts_dir, font_name)
|
|
74
|
+
|
|
75
|
+
with TemporaryDirectory() as temp_dir:
|
|
76
|
+
out_filename = path.join(temp_dir, '%s.png' % uuid4())
|
|
77
|
+
|
|
78
|
+
text = config.text
|
|
79
|
+
draw_operators = (
|
|
80
|
+
('-fill', _imagemagick_color_string(config.fg_color),
|
|
81
|
+
'-font', '%s' % _get_font(config.font_name, resi_instance),
|
|
82
|
+
'-gravity', _get_gravity(config.alignment),
|
|
83
|
+
'label:%s' % text)
|
|
84
|
+
if text != ''
|
|
85
|
+
else ('xc:none',))
|
|
86
|
+
|
|
87
|
+
resp = run([
|
|
88
|
+
'magick',
|
|
89
|
+
'-colorspace', 'RGB',
|
|
90
|
+
'-size', '%sx%s' % (config.width, config.height),
|
|
91
|
+
'-background', _imagemagick_color_string(config.bg_color),
|
|
92
|
+
*draw_operators,
|
|
93
|
+
'PNG32:%s' % out_filename
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
if resp.returncode != 0:
|
|
97
|
+
raise Exception('could not render text "%s"' % config.text)
|
|
98
|
+
|
|
99
|
+
with open(out_filename, 'rb') as f:
|
|
100
|
+
return f.read()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _imagemagick_color_string(color: RGBAColor) -> str:
|
|
104
|
+
return 'rgba(%d, %d, %d, %0.2f)' % (color.red,
|
|
105
|
+
color.green,
|
|
106
|
+
color.blue,
|
|
107
|
+
color.alpha)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from papyru import XMLHelper
|
|
2
|
+
from papyru.create_variant.constants import (TITLE_ALIGNMENT,
|
|
3
|
+
TITLE_COLOR_BACKGROUND,
|
|
4
|
+
TITLE_COLOR_FOREGROUND,
|
|
5
|
+
TITLE_FONT, TITLE_PLACEHOLDER_ID)
|
|
6
|
+
from papyru.create_variant.resi import import_into_resi
|
|
7
|
+
|
|
8
|
+
from .render_text import TextConfig, render_text
|
|
9
|
+
from .xml import (ImageInfo, find_placeholder, fit_image,
|
|
10
|
+
replace_text_with_image)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def insert_title(doc: XMLHelper,
|
|
14
|
+
title: str,
|
|
15
|
+
disposition: str,
|
|
16
|
+
resi_instance: object,
|
|
17
|
+
resi_url: any
|
|
18
|
+
) -> XMLHelper:
|
|
19
|
+
if title is None:
|
|
20
|
+
return doc
|
|
21
|
+
|
|
22
|
+
ph = find_placeholder(doc, 'text', TITLE_PLACEHOLDER_ID)
|
|
23
|
+
|
|
24
|
+
if ph is None:
|
|
25
|
+
return doc
|
|
26
|
+
|
|
27
|
+
width = float(doc.get_attribute(ph.wrapper, 'width'))
|
|
28
|
+
height = float(doc.get_attribute(ph.wrapper, 'height'))
|
|
29
|
+
|
|
30
|
+
title_location = _create_title_image(title, width, height, disposition,
|
|
31
|
+
resi_instance, resi_url)
|
|
32
|
+
|
|
33
|
+
return fit_image(
|
|
34
|
+
replace_text_with_image(doc, TITLE_PLACEHOLDER_ID),
|
|
35
|
+
TITLE_PLACEHOLDER_ID,
|
|
36
|
+
ImageInfo(location=title_location,
|
|
37
|
+
width=width,
|
|
38
|
+
height=height))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _create_title_image(title: str,
|
|
42
|
+
width: int,
|
|
43
|
+
height: int,
|
|
44
|
+
disposition: str,
|
|
45
|
+
resi_instance: any,
|
|
46
|
+
resi_url: str
|
|
47
|
+
) -> str:
|
|
48
|
+
text_bytes = render_text(
|
|
49
|
+
TextConfig(text=title,
|
|
50
|
+
width=int(width),
|
|
51
|
+
height=int(height),
|
|
52
|
+
alignment=TITLE_ALIGNMENT,
|
|
53
|
+
font_name=TITLE_FONT,
|
|
54
|
+
fg_color=TITLE_COLOR_FOREGROUND,
|
|
55
|
+
bg_color=TITLE_COLOR_BACKGROUND),
|
|
56
|
+
resi_instance)
|
|
57
|
+
|
|
58
|
+
return import_into_resi(
|
|
59
|
+
text_bytes, resi_instance, resi_url, None, disposition)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from xml.etree.ElementTree import Element, SubElement
|
|
3
|
+
|
|
4
|
+
from papyru import XMLHelper
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class PlaceholderInfo:
|
|
9
|
+
wrapper: Element
|
|
10
|
+
placeholder: Element
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ImageInfo:
|
|
15
|
+
location: str
|
|
16
|
+
width: int
|
|
17
|
+
height: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_placeholder(doc: XMLHelper,
|
|
21
|
+
placeholder_tag: str,
|
|
22
|
+
placeholder_id: str) -> PlaceholderInfo:
|
|
23
|
+
|
|
24
|
+
wrapper = doc.root.find('.//{%s}svg/{%s}%s[@id="%s"]/..'
|
|
25
|
+
% (doc.default_namespace,
|
|
26
|
+
doc.default_namespace,
|
|
27
|
+
placeholder_tag,
|
|
28
|
+
placeholder_id))
|
|
29
|
+
|
|
30
|
+
if wrapper is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
placeholder = wrapper.find('./{%s}%s[@id="%s"]'
|
|
34
|
+
% (doc.default_namespace,
|
|
35
|
+
placeholder_tag,
|
|
36
|
+
placeholder_id))
|
|
37
|
+
|
|
38
|
+
if placeholder is None:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
return PlaceholderInfo(wrapper=wrapper,
|
|
42
|
+
placeholder=placeholder)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def replace_text_with_image(doc: XMLHelper,
|
|
46
|
+
placeholder_id: str) -> XMLHelper:
|
|
47
|
+
ph = find_placeholder(doc, 'text', placeholder_id)
|
|
48
|
+
|
|
49
|
+
if ph is None:
|
|
50
|
+
return doc
|
|
51
|
+
|
|
52
|
+
ph.wrapper.remove(ph.placeholder)
|
|
53
|
+
|
|
54
|
+
replacement = SubElement(ph.wrapper,
|
|
55
|
+
'{%s}image' % doc.default_namespace)
|
|
56
|
+
|
|
57
|
+
doc.set_attribute(
|
|
58
|
+
replacement, 'id', doc.get_attribute(ph.placeholder, 'id'))
|
|
59
|
+
|
|
60
|
+
doc.set_attribute(replacement, 'x', '0')
|
|
61
|
+
doc.set_attribute(replacement, 'y', '0')
|
|
62
|
+
|
|
63
|
+
doc.set_attribute(
|
|
64
|
+
replacement, 'width', doc.get_attribute(ph.wrapper, 'width'))
|
|
65
|
+
|
|
66
|
+
doc.set_attribute(
|
|
67
|
+
replacement, 'height', doc.get_attribute(ph.wrapper, 'height'))
|
|
68
|
+
|
|
69
|
+
return doc
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def fit_image(doc: XMLHelper,
|
|
73
|
+
placeholder_id: str,
|
|
74
|
+
image_info: ImageInfo) -> XMLHelper:
|
|
75
|
+
ph = find_placeholder(doc, 'image', placeholder_id)
|
|
76
|
+
|
|
77
|
+
if ph is None:
|
|
78
|
+
return doc
|
|
79
|
+
|
|
80
|
+
wrapper_dims = (float(doc.get_attribute(ph.wrapper, 'width')),
|
|
81
|
+
float(doc.get_attribute(ph.wrapper, 'height')))
|
|
82
|
+
|
|
83
|
+
image_dims = (float(image_info.width),
|
|
84
|
+
float(image_info.height))
|
|
85
|
+
|
|
86
|
+
viewbox = _fit_dimensions(wrapper_dims, image_dims)
|
|
87
|
+
|
|
88
|
+
doc.set_attribute(ph.wrapper,
|
|
89
|
+
'viewBox',
|
|
90
|
+
'%s %s %s %s' % viewbox)
|
|
91
|
+
|
|
92
|
+
has_xlink = doc.namespace('xlink') is not None
|
|
93
|
+
doc.set_attribute(ph.placeholder,
|
|
94
|
+
'href', image_info.location,
|
|
95
|
+
'xlink' if has_xlink else None)
|
|
96
|
+
|
|
97
|
+
doc.set_attribute(ph.placeholder, 'x', '0.0')
|
|
98
|
+
doc.set_attribute(ph.placeholder, 'y', '0.0')
|
|
99
|
+
doc.set_attribute(ph.placeholder, 'width', '%s' % image_dims[0])
|
|
100
|
+
doc.set_attribute(ph.placeholder, 'height', '%s' % image_dims[1])
|
|
101
|
+
|
|
102
|
+
_set_changed(doc, ph.placeholder)
|
|
103
|
+
|
|
104
|
+
return doc
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _set_changed(document: XMLHelper, element: Element):
|
|
108
|
+
if document.has_attribute(element, 'is-unchanged', 'pap'):
|
|
109
|
+
document.set_attribute(element, 'is-unchanged', 'false', 'pap')
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _fit_dimensions(container_dims: (float, float),
|
|
113
|
+
element_dims: (float, float)
|
|
114
|
+
) -> (float, float, float, float):
|
|
115
|
+
aspect_container = container_dims[0] / container_dims[1]
|
|
116
|
+
aspect_element = element_dims[0] / element_dims[1]
|
|
117
|
+
|
|
118
|
+
e_w = element_dims[0]
|
|
119
|
+
e_h = element_dims[1]
|
|
120
|
+
|
|
121
|
+
if aspect_container > aspect_element:
|
|
122
|
+
h = element_dims[0] / aspect_container
|
|
123
|
+
return (0.0, (e_h - h) / 2, e_w, h)
|
|
124
|
+
else:
|
|
125
|
+
w = element_dims[1] * aspect_container
|
|
126
|
+
return ((e_w - w) / 2, 0.0, w, e_h)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from os import getpid
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import resi
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def import_into_resi(data: bytes,
|
|
8
|
+
resi_instance: resi.Instance,
|
|
9
|
+
resi_url: str,
|
|
10
|
+
properties: Optional[resi.VersionProperties] = None,
|
|
11
|
+
disposition: Optional[str] = None
|
|
12
|
+
) -> str:
|
|
13
|
+
|
|
14
|
+
if properties is not None:
|
|
15
|
+
properties.is_derived = False
|
|
16
|
+
|
|
17
|
+
with resi_instance.import_image_binary(data,
|
|
18
|
+
properties=properties,
|
|
19
|
+
track=str(getpid())) as img:
|
|
20
|
+
if disposition is not None:
|
|
21
|
+
img.set_properties(disposition=disposition)
|
|
22
|
+
|
|
23
|
+
location = '%s/images/%s' % (resi_url, img.ref_number)
|
|
24
|
+
|
|
25
|
+
return location
|
|
File without changes
|
|
@@ -22,6 +22,17 @@ papyru.egg-info/SOURCES.txt
|
|
|
22
22
|
papyru.egg-info/dependency_links.txt
|
|
23
23
|
papyru.egg-info/requires.txt
|
|
24
24
|
papyru.egg-info/top_level.txt
|
|
25
|
+
papyru/create_variant/__init__.py
|
|
26
|
+
papyru/create_variant/component.py
|
|
27
|
+
papyru/create_variant/constants.py
|
|
28
|
+
papyru/create_variant/resi.py
|
|
29
|
+
papyru/create_variant/edit_template/__init__.py
|
|
30
|
+
papyru/create_variant/edit_template/ean.py
|
|
31
|
+
papyru/create_variant/edit_template/manufacturer_address.py
|
|
32
|
+
papyru/create_variant/edit_template/motive.py
|
|
33
|
+
papyru/create_variant/edit_template/render_text.py
|
|
34
|
+
papyru/create_variant/edit_template/title.py
|
|
35
|
+
papyru/create_variant/edit_template/xml.py
|
|
25
36
|
papyru/static/__init__.py
|
|
26
37
|
papyru/static/apps.py
|
|
27
38
|
papyru/static/urls.py
|
|
@@ -2,7 +2,7 @@ from setuptools import setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='papyru',
|
|
5
|
-
version='2.
|
|
5
|
+
version='2.11.0',
|
|
6
6
|
description=(
|
|
7
7
|
'minimal REST library with OpenAPI-based validation for django'),
|
|
8
8
|
author='puzzleYOU GmbH',
|
|
@@ -12,6 +12,8 @@ setup(
|
|
|
12
12
|
platforms=['any'],
|
|
13
13
|
packages=[
|
|
14
14
|
'papyru',
|
|
15
|
+
'papyru.create_variant',
|
|
16
|
+
'papyru.create_variant.edit_template',
|
|
15
17
|
'papyru.static',
|
|
16
18
|
'papyru.varspool',
|
|
17
19
|
'papyru.varspool.command'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|