mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
reorganized slates a little bit
This commit is contained in:
parent
4a8ab6fac8
commit
6ef5853b65
12 changed files with 1520 additions and 1902 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"*": {
|
||||
"font-family": "arial",
|
||||
"font-size": 26,
|
||||
"font-color": "#ffffff",
|
||||
"font-bold": false,
|
||||
"font-italic": false,
|
||||
"bg-color": "#777777",
|
||||
"bg-alter-color": "#666666",
|
||||
"__alignment-vertical__values": ["left", "center", "right"],
|
||||
"alignment-vertical": "left",
|
||||
"__alignment-horizontal__values": ["top", "center", "bottom"],
|
||||
"alignment-horizontal": "top",
|
||||
"__padding__variants": [
|
||||
"padding-top", "padding-right", "padding-bottom", "padding-left"
|
||||
],
|
||||
"padding": 0,
|
||||
"__margin__variants": [
|
||||
"margin-top", "margin-right", "margin-bottom", "margin-left"
|
||||
],
|
||||
"margin": 0,
|
||||
},
|
||||
"layer": {
|
||||
|
||||
},
|
||||
"image": {
|
||||
|
||||
},
|
||||
"text": {
|
||||
|
||||
},
|
||||
"table": {
|
||||
|
||||
},
|
||||
"table-item": {
|
||||
|
||||
},
|
||||
"table-item-col-0": {
|
||||
|
||||
},
|
||||
"#MyName": {
|
||||
|
||||
}
|
||||
}
|
||||
114
pype/scripts/slate/lib.py
Normal file
114
pype/scripts/slate/lib.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import os
|
||||
import json
|
||||
from queue import Queue
|
||||
|
||||
# --- Lines for debug purpose ---------------------------------
|
||||
import sys
|
||||
sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages")
|
||||
# -------------------------------------------------------------
|
||||
|
||||
from slate_base.main_frame import MainFrame
|
||||
from slate_base.layer import Layer
|
||||
from slate_base.items import (
|
||||
ItemTable, ItemImage, ItemRectangle, ItemPlaceHolder
|
||||
)
|
||||
|
||||
|
||||
def main(fill_data):
|
||||
cur_folder = os.path.dirname(os.path.abspath(__file__))
|
||||
input_json = os.path.join(cur_folder, "netflix_v03.json")
|
||||
with open(input_json) as json_file:
|
||||
slate_data = json.load(json_file)
|
||||
|
||||
|
||||
width = slate_data["width"]
|
||||
height = slate_data["height"]
|
||||
dst_path = slate_data["destination_path"]
|
||||
style = slate_data.get("style") or {}
|
||||
|
||||
main = MainFrame(width, height, dst_path, fill_data, style=style)
|
||||
|
||||
load_queue = Queue()
|
||||
for item in slate_data["items"]:
|
||||
load_queue.put((item, main))
|
||||
|
||||
while not load_queue.empty():
|
||||
item_data, parent = load_queue.get()
|
||||
|
||||
item_type = item_data["type"].lower()
|
||||
item_style = item_data.get("style", {})
|
||||
item_name = item_data.get("name")
|
||||
|
||||
pos_x = item_data.get("pos_x")
|
||||
pos_y = item_data.get("pos_y")
|
||||
if parent.obj_type != "main_frame":
|
||||
if pos_x or pos_y:
|
||||
# TODO logging
|
||||
self.log.warning((
|
||||
"You have specified `pos_x` and `pos_y` but won't be used."
|
||||
" Possible only if parent of an item is `main_frame`."
|
||||
))
|
||||
pos_x = None
|
||||
pos_y = None
|
||||
|
||||
kwargs = {
|
||||
"parent": parent,
|
||||
"style": item_style,
|
||||
"name": item_name,
|
||||
"pos_x": pos_x,
|
||||
"pos_y": pos_y
|
||||
}
|
||||
|
||||
if item_type == "layer":
|
||||
direction = item_data.get("direction", 0)
|
||||
item_obj = Layer(direction, **kwargs)
|
||||
for item in item_data.get("items", []):
|
||||
load_queue.put((item, item_obj))
|
||||
|
||||
elif item_type == "table":
|
||||
use_alternate_color = item_data.get("use_alternate_color", False)
|
||||
values = item_data.get("values") or []
|
||||
ItemTable(values, use_alternate_color, **kwargs)
|
||||
|
||||
elif item_type == "image":
|
||||
path = item_data["path"]
|
||||
ItemImage(path, **kwargs)
|
||||
|
||||
elif item_type == "rectangle":
|
||||
ItemRectangle(**kwargs)
|
||||
|
||||
elif item_type == "placeholder":
|
||||
path = item_data["path"]
|
||||
ItemPlaceHolder(path, **kwargs)
|
||||
|
||||
else:
|
||||
# TODO logging
|
||||
self.log.warning(
|
||||
"Slate item not implemented <{}> - skipping".format(item_type)
|
||||
)
|
||||
|
||||
main.draw()
|
||||
print("Slate creation finished")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fill_data = {
|
||||
"destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v03.png",
|
||||
"project": {
|
||||
"name": "Project name"
|
||||
},
|
||||
"intent": "WIP",
|
||||
"version_name": "mei_101_001_0020_slate_NFX_v001",
|
||||
"date": "2019-08-09",
|
||||
"shot_type": "2d comp",
|
||||
"submission_note": "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.",
|
||||
"thumbnail_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png",
|
||||
"color_bar_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg",
|
||||
"vendor": "DAZZLE",
|
||||
"shot_name": "SLATE_SIMPLE",
|
||||
"frame_start": 1001,
|
||||
"frame_end": 1004,
|
||||
"duration": 3
|
||||
}
|
||||
main(fill_data)
|
||||
# raise NotImplementedError("Slates don't have Implemented args running")
|
||||
|
|
@ -1,355 +0,0 @@
|
|||
import textwrap
|
||||
from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor
|
||||
|
||||
|
||||
class TableDraw:
|
||||
def __init__(
|
||||
self, image_width, image_height,
|
||||
rel_pos_x, rel_pos_y, rel_width,
|
||||
col_fonts=None, col_font_colors=None,
|
||||
default_font=None, default_font_color=None,
|
||||
col_alignments=None, rel_col_widths=None, bg_color=None,
|
||||
alter_bg_color=None, pad=20,
|
||||
pad_top=None, pad_bottom=None, pad_left=None, pad_right=None
|
||||
):
|
||||
self.image_width = image_width
|
||||
|
||||
pos_x = image_width * rel_pos_x
|
||||
pos_y = image_height * rel_pos_y
|
||||
width = image_width * rel_width
|
||||
|
||||
self.pos_x_start = pos_x
|
||||
self.pos_y_start = pos_y
|
||||
self.pos_x_end = pos_x + width
|
||||
self.width = width
|
||||
|
||||
self.rel_col_widths = list(rel_col_widths)
|
||||
self._col_widths = None
|
||||
self._col_alignments = col_alignments
|
||||
|
||||
if bg_color and isinstance(bg_color, str):
|
||||
bg_color = ImageColor.getrgb(bg_color)
|
||||
|
||||
if alter_bg_color and isinstance(alter_bg_color, str):
|
||||
alter_bg_color = ImageColor.getrgb(alter_bg_color)
|
||||
|
||||
self._bg_color = bg_color
|
||||
self._alter_bg_color = alter_bg_color
|
||||
|
||||
self.alter_use = False
|
||||
|
||||
if col_fonts:
|
||||
_col_fonts = []
|
||||
for col_font in col_fonts:
|
||||
font_name, font_size = col_font
|
||||
font = ImageFont.truetype(font_name, font_size)
|
||||
_col_fonts.append(font)
|
||||
col_fonts = _col_fonts
|
||||
|
||||
self._col_fonts = col_fonts
|
||||
|
||||
self._col_font_colors = col_font_colors
|
||||
|
||||
if not default_font:
|
||||
default_font = ImageFont.truetype("times", 26)
|
||||
self.default_font = default_font
|
||||
|
||||
if not default_font_color:
|
||||
default_font_color = "#ffffff"
|
||||
self.default_font_color = default_font_color
|
||||
|
||||
self.texts = []
|
||||
|
||||
if pad is None:
|
||||
pad = 5
|
||||
|
||||
_pad_top = pad
|
||||
if pad_top is not None:
|
||||
_pad_top = pad_top
|
||||
|
||||
_pad_bottom = pad
|
||||
if pad_bottom is not None:
|
||||
_pad_bottom = pad_bottom
|
||||
|
||||
_pad_left = pad
|
||||
if pad_left is not None:
|
||||
_pad_left = pad_left
|
||||
|
||||
_pad_right = pad
|
||||
if pad_right is not None:
|
||||
_pad_right = pad_right
|
||||
|
||||
self.pad_top = _pad_top
|
||||
self.pad_bottom = _pad_bottom
|
||||
self.pad_left = _pad_left
|
||||
self.pad_right = _pad_right
|
||||
|
||||
@property
|
||||
def col_widths(self):
|
||||
if self._col_widths is None:
|
||||
sum_width = 0
|
||||
for w in self.rel_col_widths:
|
||||
sum_width += w
|
||||
|
||||
one_piece = self.width / sum_width
|
||||
self._col_widths = []
|
||||
for w in self.rel_col_widths:
|
||||
self._col_widths.append(one_piece * w)
|
||||
|
||||
return self._col_widths
|
||||
|
||||
@property
|
||||
def col_fonts(self):
|
||||
if self._col_fonts is None:
|
||||
self._col_fonts = []
|
||||
for _ in range(len(self.col_widths)):
|
||||
self._col_fonts.append(self.default_font)
|
||||
|
||||
elif len(self._col_fonts) < len(self.col_widths):
|
||||
if isinstance(self._col_fonts, tuple):
|
||||
self._col_fonts = list(self._col_fonts)
|
||||
|
||||
while len(self._col_fonts) < len(self.col_widths):
|
||||
self._col_fonts.append(self.default_font)
|
||||
|
||||
return self._col_fonts
|
||||
|
||||
@property
|
||||
def col_font_colors(self):
|
||||
if self._col_font_colors is None:
|
||||
self._col_font_colors = []
|
||||
for _ in range(len(self.col_widths)):
|
||||
self._col_font_colors.append(self.default_font_color)
|
||||
|
||||
elif len(self._col_font_colors) < len(self.col_widths):
|
||||
if isinstance(self._col_font_colors, tuple):
|
||||
self._col_font_colors = list(self._col_font_colors)
|
||||
|
||||
while len(self._col_font_colors) < len(self.col_widths):
|
||||
self._col_font_colors.append(self.default_font_color)
|
||||
|
||||
return self._col_font_colors
|
||||
|
||||
@property
|
||||
def col_alignments(self):
|
||||
if self._col_alignments is None:
|
||||
self._col_alignments = []
|
||||
for _ in range(len(self.col_widths)):
|
||||
self._col_alignments.append("left")
|
||||
|
||||
elif len(self._col_alignments) < len(self.col_widths):
|
||||
if isinstance(self._col_alignments, tuple):
|
||||
self._col_alignments = list(self._col_alignments)
|
||||
|
||||
while len(self._col_alignments) < len(self.col_widths):
|
||||
self._col_alignments.append("left")
|
||||
|
||||
return self._col_alignments
|
||||
|
||||
@property
|
||||
def bg_color(self):
|
||||
if self.alter_use is True:
|
||||
value = self.alter_bg_color
|
||||
self.alter_use = False
|
||||
else:
|
||||
value = self._bg_color
|
||||
self.alter_use = True
|
||||
return value
|
||||
|
||||
@property
|
||||
def alter_bg_color(self):
|
||||
if self._alter_bg_color:
|
||||
return self._alter_bg_color
|
||||
return self.bg_color
|
||||
|
||||
def add_texts(self, texts):
|
||||
if isinstance(texts, str):
|
||||
texts = [texts]
|
||||
|
||||
for text in texts:
|
||||
if isinstance(text, str):
|
||||
text = [text]
|
||||
|
||||
if len(text) > len(self.rel_col_widths):
|
||||
for _ in (len(text) - len(self.rel_col_widths)):
|
||||
self.rel_col_widths.append(1)
|
||||
for _t in self.texts:
|
||||
_t.append("")
|
||||
|
||||
self.texts.append(text)
|
||||
|
||||
def draw(self, drawer):
|
||||
y_pos = self.pos_y_start
|
||||
for texts in self.texts:
|
||||
max_height = None
|
||||
cols_data = []
|
||||
for _idx, col in enumerate(texts):
|
||||
width = self.col_widths[_idx]
|
||||
font = self.col_fonts[_idx]
|
||||
lines, line_height = self.lines_height_by_width(
|
||||
drawer, col, width - self.pad_left - self.pad_right, font
|
||||
)
|
||||
row_height = line_height * len(lines)
|
||||
if max_height is None or row_height > max_height:
|
||||
max_height = row_height
|
||||
|
||||
cols_data.append({
|
||||
"lines": lines,
|
||||
"line_height": line_height
|
||||
})
|
||||
|
||||
drawer.rectangle(
|
||||
(
|
||||
(self.pos_x_start, y_pos),
|
||||
(
|
||||
self.pos_x_end,
|
||||
y_pos + max_height + self.pad_top + self.pad_bottom
|
||||
)
|
||||
),
|
||||
fill=self.bg_color
|
||||
)
|
||||
|
||||
pos_x_start = self.pos_x_start + self.pad_left
|
||||
for col, col_data in enumerate(cols_data):
|
||||
lines = col_data["lines"]
|
||||
line_height = col_data["line_height"]
|
||||
alignment = self.col_alignments[col]
|
||||
x_offset = self.col_widths[col]
|
||||
font = self.col_fonts[col]
|
||||
font_color = self.col_font_colors[col]
|
||||
for idx, line_data in enumerate(lines):
|
||||
line = line_data["text"]
|
||||
line_width = line_data["width"]
|
||||
if alignment == "left":
|
||||
x_start = pos_x_start + self.pad_left
|
||||
elif alignment == "right":
|
||||
x_start = (
|
||||
pos_x_start + x_offset - line_width -
|
||||
self.pad_right - self.pad_left
|
||||
)
|
||||
else:
|
||||
# TODO else
|
||||
x_start = pos_x_start + self.pad_left
|
||||
|
||||
drawer.text(
|
||||
(
|
||||
x_start,
|
||||
y_pos + (idx * line_height) + self.pad_top
|
||||
),
|
||||
line,
|
||||
font=font,
|
||||
fill=font_color
|
||||
)
|
||||
pos_x_start += x_offset
|
||||
|
||||
y_pos += max_height + self.pad_top + self.pad_bottom
|
||||
|
||||
def lines_height_by_width(self, drawer, text, width, font):
|
||||
lines = []
|
||||
lines.append([part for part in text.split() if part])
|
||||
|
||||
line = 0
|
||||
while True:
|
||||
thistext = lines[line]
|
||||
line = line + 1
|
||||
if not thistext:
|
||||
break
|
||||
newline = []
|
||||
|
||||
while True:
|
||||
_textwidth = drawer.textsize(" ".join(thistext), font)[0]
|
||||
if (
|
||||
_textwidth <= width
|
||||
):
|
||||
break
|
||||
elif _textwidth > width and len(thistext) == 1:
|
||||
# TODO raise error?
|
||||
break
|
||||
|
||||
val = thistext.pop(-1)
|
||||
|
||||
if not val:
|
||||
break
|
||||
newline.insert(0, val)
|
||||
|
||||
if len(newline) > 0:
|
||||
lines.append(newline)
|
||||
else:
|
||||
break
|
||||
|
||||
_lines = []
|
||||
height = None
|
||||
for line_items in lines:
|
||||
line = " ".join(line_items)
|
||||
(width, _height) = drawer.textsize(line, font)
|
||||
if height is None or height < _height:
|
||||
height = _height
|
||||
|
||||
_lines.append({
|
||||
"width": width,
|
||||
"text": line
|
||||
})
|
||||
|
||||
return (_lines, height)
|
||||
|
||||
|
||||
width = 1920
|
||||
height = 1080
|
||||
# width = 800
|
||||
# height = 600
|
||||
bg_color_hex = "#242424"
|
||||
bg_color = ImageColor.getrgb(bg_color_hex)
|
||||
|
||||
base = Image.new('RGB', (width, height), color=bg_color)
|
||||
|
||||
texts = [
|
||||
("Version name:", "mei_101_001_0020_slate_NFX_v001"),
|
||||
("Date:", "2019-08-09"),
|
||||
("Shot Types:", "2d comp"),
|
||||
# ("Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.")
|
||||
]
|
||||
text_widths_rel = (2, 8)
|
||||
col_alignments = ("right", "left")
|
||||
fonts = (("arial", 20), ("times", 26))
|
||||
font_colors = ("#999999", "#ffffff")
|
||||
|
||||
table_color_hex = "#212121"
|
||||
table_alter_color_hex = "#272727"
|
||||
|
||||
drawer = ImageDraw.Draw(base)
|
||||
table_d = TableDraw(
|
||||
width, height,
|
||||
0.1, 0.1, 0.5,
|
||||
col_fonts=fonts,
|
||||
col_font_colors=font_colors,
|
||||
rel_col_widths=text_widths_rel,
|
||||
col_alignments=col_alignments,
|
||||
bg_color=table_color_hex,
|
||||
alter_bg_color=table_alter_color_hex,
|
||||
pad_top=20, pad_bottom=20, pad_left=5, pad_right=5
|
||||
)
|
||||
|
||||
table_d.add_texts(texts)
|
||||
table_d.draw(drawer)
|
||||
|
||||
image_path = r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\image.jpg"
|
||||
image = Image.open(image_path)
|
||||
img_width, img_height = image.size
|
||||
|
||||
rel_image_width = 0.3
|
||||
rel_image_pos_x = 0.65
|
||||
rel_image_pos_y = 0.1
|
||||
image_pos_x = int(width * rel_image_pos_x)
|
||||
image_pos_y = int(width * rel_image_pos_y)
|
||||
|
||||
new_width = int(width * rel_image_width)
|
||||
new_height = int(new_width * img_height / img_width)
|
||||
image = image.resize((new_width, new_height), Image.ANTIALIAS)
|
||||
|
||||
# mask = Image.new("L", image.size, 255)
|
||||
|
||||
base.paste(image, (image_pos_x, image_pos_y))
|
||||
base.save(r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\test{}x{}.jpg".format(
|
||||
width, height
|
||||
))
|
||||
base.show()
|
||||
0
pype/scripts/slate/slate_base/__init__.py
Normal file
0
pype/scripts/slate/slate_base/__init__.py
Normal file
373
pype/scripts/slate/slate_base/base.py
Normal file
373
pype/scripts/slate/slate_base/base.py
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
import os
|
||||
import re
|
||||
import logging
|
||||
import copy
|
||||
import json
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
def load_default_style():
|
||||
cur_folder = os.path.dirname(os.path.abspath(__file__))
|
||||
default_json_path = os.path.join(cur_folder, "default_style.json")
|
||||
with open(default_json_path, "r") as _file:
|
||||
data = _file.read()
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
class BaseObj:
|
||||
"""Base Object for slates."""
|
||||
|
||||
obj_type = None
|
||||
available_parents = []
|
||||
all_style_keys = [
|
||||
"font-family", "font-size", "font-color", "font-bold", "font-italic",
|
||||
"bg-color", "bg-alter-color",
|
||||
"alignment-horizontal", "alignment-vertical",
|
||||
"padding", "padding-left", "padding-right",
|
||||
"padding-top", "padding-bottom",
|
||||
"margin", "margin-left", "margin-right",
|
||||
"margin-top", "margin-bottom", "width", "height",
|
||||
"fill", "word-wrap", "ellide", "max-lines"
|
||||
]
|
||||
fill_data_regex = r"{[^}]+}"
|
||||
|
||||
def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None):
|
||||
if not self.obj_type:
|
||||
raise NotImplementedError(
|
||||
"Class don't have set object type <{}>".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
parent_obj_type = None
|
||||
if parent:
|
||||
parent_obj_type = parent.obj_type
|
||||
|
||||
if parent_obj_type not in self.available_parents:
|
||||
expected_parents = ", ".join(self.available_parents)
|
||||
raise Exception((
|
||||
"Invalid parent <{}> for <{}>. Expected <{}>"
|
||||
).format(
|
||||
parent.__class__.__name__, self.obj_type, expected_parents
|
||||
))
|
||||
|
||||
self.parent = parent
|
||||
self._style = style
|
||||
|
||||
self.id = uuid4()
|
||||
self.name = name
|
||||
self.items = {}
|
||||
|
||||
self._pos_x = pos_x or 0
|
||||
self._pos_y = pos_y or 0
|
||||
|
||||
log_parts = []
|
||||
module = self.__class__.__module__
|
||||
if module and module != "__main__":
|
||||
log_parts.append(module)
|
||||
log_parts.append(self.__class__.__name__)
|
||||
self.log = logging.getLogger(".".join(log_parts))
|
||||
|
||||
if parent:
|
||||
parent.add_item(self)
|
||||
|
||||
def fill_data_format(self):
|
||||
return
|
||||
|
||||
@property
|
||||
def fill_data(self):
|
||||
return self.parent.fill_data
|
||||
|
||||
@property
|
||||
def main_style(self):
|
||||
return load_default_style()
|
||||
|
||||
def height(self):
|
||||
raise NotImplementedError(
|
||||
"Attribute `height` is not implemented for <{}>".format(
|
||||
self.__clas__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def width(self):
|
||||
raise NotImplementedError(
|
||||
"Attribute `width` is not implemented for <{}>".format(
|
||||
self.__clas__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def collect_data(self):
|
||||
return None
|
||||
|
||||
def find_item(self, obj_type=None, name=None):
|
||||
obj_type_fits = False
|
||||
name_fits = False
|
||||
if obj_type is None or self.obj_type == obj_type:
|
||||
obj_type_fits = True
|
||||
|
||||
if name is None or self.name != name:
|
||||
name_fits = True
|
||||
|
||||
output = []
|
||||
if obj_type_fits and name_fits:
|
||||
output.append(self)
|
||||
|
||||
if not self.items:
|
||||
return output
|
||||
|
||||
for item in self.items.values():
|
||||
output.extend(
|
||||
item.find_item(obj_type=obj_type, name=name)
|
||||
)
|
||||
return output
|
||||
|
||||
@property
|
||||
def full_style(self):
|
||||
if self.parent is not None:
|
||||
style = dict(val for val in self.parent.full_style.items())
|
||||
else:
|
||||
style = self.main_style
|
||||
|
||||
for key, value in self._style.items():
|
||||
if key in self.all_style_keys:
|
||||
# TODO which variant is right?
|
||||
style[self.obj_type][key] = value
|
||||
# style["*"][key] = value
|
||||
else:
|
||||
if key not in style:
|
||||
style[key] = {}
|
||||
|
||||
if isinstance(style[key], dict):
|
||||
style[key].update(value)
|
||||
else:
|
||||
style[key] = value
|
||||
|
||||
return style
|
||||
|
||||
def get_style_for_obj_type(self, obj_type, style=None):
|
||||
if not style:
|
||||
style = copy.deepcopy(self.full_style)
|
||||
|
||||
base = style.get("*") or {}
|
||||
obj_specific = style.get(obj_type) or {}
|
||||
name_specific = {}
|
||||
if self.name:
|
||||
name = str(self.name)
|
||||
if not name.startswith("#"):
|
||||
name = "#" + name
|
||||
name_specific = style.get(name) or {}
|
||||
|
||||
if obj_type == "table-item":
|
||||
col_regex = r"table-item-col\[([\d\-, ]+)*\]"
|
||||
row_regex = r"table-item-row\[([\d\-, ]+)*\]"
|
||||
field_regex = (
|
||||
r"table-item-field\[(([ ]+)?\d+([ ]+)?:([ ]+)?\d+([ ]+)?)*\]"
|
||||
)
|
||||
# STRICT field regex (not allowed spaces)
|
||||
# fild_regex = r"table-item-field\[(\d+:\d+)*\]"
|
||||
|
||||
def get_indexes_from_regex_match(result, field=False):
|
||||
group = result.group(1)
|
||||
indexes = []
|
||||
if field:
|
||||
return [
|
||||
int(part.strip()) for part in group.strip().split(":")
|
||||
]
|
||||
|
||||
parts = group.strip().split(",")
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if "-" not in part:
|
||||
indexes.append(int(part))
|
||||
continue
|
||||
|
||||
sub_parts = [
|
||||
int(sub.strip()) for sub in part.split("-")
|
||||
]
|
||||
if len(sub_parts) != 2:
|
||||
# TODO logging
|
||||
self.log.warning("Invalid range '{}'".format(part))
|
||||
continue
|
||||
|
||||
for idx in range(sub_parts[0], sub_parts[1]+1):
|
||||
indexes.append(idx)
|
||||
return indexes
|
||||
|
||||
for key, value in style.items():
|
||||
if not key.startswith(obj_type):
|
||||
continue
|
||||
|
||||
result = re.search(col_regex, key)
|
||||
if result:
|
||||
indexes = get_indexes_from_regex_match(result)
|
||||
if self.col_idx in indexes:
|
||||
obj_specific.update(value)
|
||||
continue
|
||||
|
||||
result = re.search(row_regex, key)
|
||||
if result:
|
||||
indexes = get_indexes_from_regex_match(result)
|
||||
if self.row_idx in indexes:
|
||||
obj_specific.update(value)
|
||||
continue
|
||||
|
||||
result = re.search(field_regex, key)
|
||||
if result:
|
||||
col_idx, row_idx = get_indexes_from_regex_match(
|
||||
result, True
|
||||
)
|
||||
if self.col_idx == col_idx and self.row_idx == row_idx:
|
||||
obj_specific.update(value)
|
||||
|
||||
output = {}
|
||||
output.update(base)
|
||||
output.update(obj_specific)
|
||||
output.update(name_specific)
|
||||
|
||||
return output
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
return self.get_style_for_obj_type(self.obj_type)
|
||||
|
||||
@property
|
||||
def item_pos_x(self):
|
||||
if self.parent.obj_type == "main_frame":
|
||||
return int(self._pos_x)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def item_pos_y(self):
|
||||
if self.parent.obj_type == "main_frame":
|
||||
return int(self._pos_y)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def content_pos_x(self):
|
||||
pos_x = self.item_pos_x
|
||||
margin = self.style["margin"]
|
||||
margin_left = self.style.get("margin-left") or margin
|
||||
|
||||
pos_x += margin_left
|
||||
|
||||
return pos_x
|
||||
|
||||
@property
|
||||
def content_pos_y(self):
|
||||
pos_y = self.item_pos_y
|
||||
margin = self.style["margin"]
|
||||
margin_top = self.style.get("margin-top") or margin
|
||||
return pos_y + margin_top
|
||||
|
||||
@property
|
||||
def value_pos_x(self):
|
||||
pos_x = int(self.content_pos_x)
|
||||
padding = self.style["padding"]
|
||||
padding_left = self.style.get("padding-left")
|
||||
if padding_left is None:
|
||||
padding_left = padding
|
||||
|
||||
pos_x += padding_left
|
||||
|
||||
return pos_x
|
||||
|
||||
@property
|
||||
def value_pos_y(self):
|
||||
pos_y = int(self.content_pos_y)
|
||||
padding = self.style["padding"]
|
||||
padding_top = self.style.get("padding-top")
|
||||
if padding_top is None:
|
||||
padding_top = padding
|
||||
|
||||
pos_y += padding_top
|
||||
|
||||
return pos_y
|
||||
|
||||
@property
|
||||
def value_pos_start(self):
|
||||
return (self.value_pos_x, self.value_pos_y)
|
||||
|
||||
@property
|
||||
def value_pos_end(self):
|
||||
pos_x, pos_y = self.value_pos_start
|
||||
pos_x += self.width()
|
||||
pos_y += self.height()
|
||||
return (pos_x, pos_y)
|
||||
|
||||
@property
|
||||
def content_pos_start(self):
|
||||
return (self.content_pos_x, self.content_pos_y)
|
||||
|
||||
@property
|
||||
def content_pos_end(self):
|
||||
pos_x, pos_y = self.content_pos_start
|
||||
pos_x += self.content_width()
|
||||
pos_y += self.content_height()
|
||||
return (pos_x, pos_y)
|
||||
|
||||
def value_width(self):
|
||||
raise NotImplementedError(
|
||||
"Attribute <content_width> is not implemented <{}>".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def value_height(self):
|
||||
raise NotImplementedError(
|
||||
"Attribute <content_width> is not implemented for <{}>".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def content_width(self):
|
||||
width = self.value_width()
|
||||
padding = self.style["padding"]
|
||||
padding_left = self.style.get("padding-left")
|
||||
if padding_left is None:
|
||||
padding_left = padding
|
||||
|
||||
padding_right = self.style.get("padding-right")
|
||||
if padding_right is None:
|
||||
padding_right = padding
|
||||
|
||||
return width + padding_left + padding_right
|
||||
|
||||
def content_height(self):
|
||||
height = self.value_height()
|
||||
padding = self.style["padding"]
|
||||
padding_top = self.style.get("padding-top")
|
||||
if padding_top is None:
|
||||
padding_top = padding
|
||||
|
||||
padding_bottom = self.style.get("padding-bottom")
|
||||
if padding_bottom is None:
|
||||
padding_bottom = padding
|
||||
|
||||
return height + padding_top + padding_bottom
|
||||
|
||||
def width(self):
|
||||
width = self.content_width()
|
||||
|
||||
margin = self.style["margin"]
|
||||
margin_left = self.style.get("margin-left") or margin
|
||||
margin_right = self.style.get("margin-right") or margin
|
||||
|
||||
return width + margin_left + margin_right
|
||||
|
||||
def height(self):
|
||||
height = self.content_height()
|
||||
|
||||
margin = self.style["margin"]
|
||||
margin_top = self.style.get("margin-top") or margin
|
||||
margin_bottom = self.style.get("margin-bottom") or margin
|
||||
|
||||
return height + margin_bottom + margin_top
|
||||
|
||||
def add_item(self, item):
|
||||
self.items[item.id] = item
|
||||
item.fill_data_format()
|
||||
|
||||
|
||||
def reset(self):
|
||||
for item in self.items.values():
|
||||
item.reset()
|
||||
58
pype/scripts/slate/slate_base/default_style.json
Normal file
58
pype/scripts/slate/slate_base/default_style.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"*": {
|
||||
"font-family": "arial",
|
||||
"font-size": 26,
|
||||
"font-color": "#ffffff",
|
||||
"font-bold": false,
|
||||
"font-italic": false,
|
||||
"bg-color": "#0077ff",
|
||||
"alignment-horizontal": "left",
|
||||
"alignment-vertical": "top",
|
||||
"word-wrap": true,
|
||||
"ellide": true,
|
||||
"max-lines": null
|
||||
},
|
||||
"layer": {
|
||||
"padding": 0,
|
||||
"margin": 0
|
||||
},
|
||||
"rectangle": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"fill": true
|
||||
},
|
||||
"image": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"fill": true
|
||||
},
|
||||
"placeholder": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"fill": true
|
||||
},
|
||||
"main_frame": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"bg-color": "#252525"
|
||||
},
|
||||
"table": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"bg-color": "transparent"
|
||||
},
|
||||
"table-item": {
|
||||
"padding": 0,
|
||||
"margin": 0,
|
||||
"bg-color": "#212121",
|
||||
"bg-alter-color": "#272727",
|
||||
"font-color": "#dcdcdc",
|
||||
"font-bold": false,
|
||||
"font-italic": false,
|
||||
"alignment-horizontal": "left",
|
||||
"alignment-vertical": "top",
|
||||
"word-wrap": false,
|
||||
"ellide": true,
|
||||
"max-lines": 1
|
||||
}
|
||||
}
|
||||
93
pype/scripts/slate/slate_base/font_factory.py
Normal file
93
pype/scripts/slate/slate_base/font_factory.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import os
|
||||
import sys
|
||||
import collections
|
||||
|
||||
from PIL import ImageFont
|
||||
|
||||
|
||||
class FontFactory:
|
||||
fonts = None
|
||||
default = None
|
||||
|
||||
@classmethod
|
||||
def get_font(cls, family, font_size=None, italic=False, bold=False):
|
||||
if cls.fonts is None:
|
||||
cls.load_fonts()
|
||||
|
||||
styles = []
|
||||
if bold:
|
||||
styles.append("Bold")
|
||||
|
||||
if italic:
|
||||
styles.append("Italic")
|
||||
|
||||
if not styles:
|
||||
styles.append("Regular")
|
||||
|
||||
style = " ".join(styles)
|
||||
family = family.lower()
|
||||
family_styles = cls.fonts.get(family)
|
||||
if not family_styles:
|
||||
return cls.default
|
||||
|
||||
font = family_styles.get(style)
|
||||
if font:
|
||||
if font_size:
|
||||
font = font.font_variant(size=font_size)
|
||||
return font
|
||||
|
||||
# Return first found
|
||||
for font in family_styles:
|
||||
if font_size:
|
||||
font = font.font_variant(size=font_size)
|
||||
return font
|
||||
|
||||
return cls.default
|
||||
|
||||
@classmethod
|
||||
def load_fonts(cls):
|
||||
|
||||
cls.default = ImageFont.load_default()
|
||||
|
||||
available_font_ext = [".ttf", ".ttc"]
|
||||
dirs = []
|
||||
if sys.platform == "win32":
|
||||
# check the windows font repository
|
||||
# NOTE: must use uppercase WINDIR, to work around bugs in
|
||||
# 1.5.2's os.environ.get()
|
||||
windir = os.environ.get("WINDIR")
|
||||
if windir:
|
||||
dirs.append(os.path.join(windir, "fonts"))
|
||||
|
||||
elif sys.platform in ("linux", "linux2"):
|
||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
||||
if not lindirs:
|
||||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||
# default to /usr/share
|
||||
lindirs = "/usr/share"
|
||||
dirs += [
|
||||
os.path.join(lindir, "fonts") for lindir in lindirs.split(":")
|
||||
]
|
||||
|
||||
elif sys.platform == "darwin":
|
||||
dirs += [
|
||||
"/Library/Fonts",
|
||||
"/System/Library/Fonts",
|
||||
os.path.expanduser("~/Library/Fonts")
|
||||
]
|
||||
|
||||
available_fonts = collections.defaultdict(dict)
|
||||
for directory in dirs:
|
||||
for walkroot, walkdir, walkfilenames in os.walk(directory):
|
||||
for walkfilename in walkfilenames:
|
||||
ext = os.path.splitext(walkfilename)[1]
|
||||
if ext.lower() not in available_font_ext:
|
||||
continue
|
||||
|
||||
fontpath = os.path.join(walkroot, walkfilename)
|
||||
font_obj = ImageFont.truetype(fontpath)
|
||||
family = font_obj.font.family.lower()
|
||||
style = font_obj.font.style
|
||||
available_fonts[family][style] = font_obj
|
||||
|
||||
cls.fonts = available_fonts
|
||||
666
pype/scripts/slate/slate_base/items.py
Normal file
666
pype/scripts/slate/slate_base/items.py
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
import re
|
||||
from PIL import Image
|
||||
|
||||
from .base import BaseObj
|
||||
from .font_factory import FontFactory
|
||||
|
||||
|
||||
class BaseItem(BaseObj):
|
||||
available_parents = ["main_frame", "layer"]
|
||||
|
||||
@property
|
||||
def item_pos_x(self):
|
||||
if self.parent.obj_type == "main_frame":
|
||||
return self._pos_x
|
||||
return self.parent.child_pos_x(self.id)
|
||||
|
||||
@property
|
||||
def item_pos_y(self):
|
||||
if self.parent.obj_type == "main_frame":
|
||||
return self._pos_y
|
||||
return self.parent.child_pos_y(self.id)
|
||||
|
||||
def add_item(self, *args, **kwargs):
|
||||
raise Exception("Can't add item to an item, use layers instead.")
|
||||
|
||||
def draw(self, image, drawer):
|
||||
raise NotImplementedError(
|
||||
"Method `draw` is not implemented for <{}>".format(
|
||||
self.__clas__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ItemImage(BaseItem):
|
||||
obj_type = "image"
|
||||
|
||||
def __init__(self, image_path, *args, **kwargs):
|
||||
super(ItemImage, self).__init__(*args, **kwargs)
|
||||
self.image_path = image_path
|
||||
|
||||
def fill_data_format(self):
|
||||
if re.match(self.fill_data_regex, self.image_path):
|
||||
self.image_path = self.image_path.format(**self.fill_data)
|
||||
|
||||
def draw(self, image, drawer):
|
||||
source_image = Image.open(os.path.normpath(self.image_path))
|
||||
paste_image = source_image.resize(
|
||||
(self.value_width(), self.value_height()),
|
||||
Image.ANTIALIAS
|
||||
)
|
||||
image.paste(
|
||||
paste_image,
|
||||
(self.value_pos_x, self.value_pos_y)
|
||||
)
|
||||
|
||||
def value_width(self):
|
||||
return int(self.style["width"])
|
||||
|
||||
def value_height(self):
|
||||
return int(self.style["height"])
|
||||
|
||||
|
||||
class ItemRectangle(BaseItem):
|
||||
obj_type = "rectangle"
|
||||
|
||||
def draw(self, image, drawer):
|
||||
bg_color = self.style["bg-color"]
|
||||
fill = self.style.get("fill", False)
|
||||
kwargs = {}
|
||||
if fill:
|
||||
kwargs["fill"] = bg_color
|
||||
else:
|
||||
kwargs["outline"] = bg_color
|
||||
|
||||
start_pos_x = self.value_pos_x
|
||||
start_pos_y = self.value_pos_y
|
||||
end_pos_x = start_pos_x + self.value_width()
|
||||
end_pos_y = start_pos_y + self.value_height()
|
||||
drawer.rectangle(
|
||||
(
|
||||
(start_pos_x, start_pos_y),
|
||||
(end_pos_x, end_pos_y)
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def value_width(self):
|
||||
return int(self.style["width"])
|
||||
|
||||
def value_height(self):
|
||||
return int(self.style["height"])
|
||||
|
||||
|
||||
class ItemPlaceHolder(BaseItem):
|
||||
obj_type = "placeholder"
|
||||
|
||||
def __init__(self, image_path, *args, **kwargs):
|
||||
self.image_path = image_path
|
||||
super(ItemPlaceHolder, self).__init__(*args, **kwargs)
|
||||
|
||||
def fill_data_format(self):
|
||||
if re.match(self.fill_data_regex, self.image_path):
|
||||
self.image_path = self.image_path.format(**self.fill_data)
|
||||
|
||||
def draw(self, image, drawer):
|
||||
bg_color = self.style["bg-color"]
|
||||
|
||||
kwargs = {}
|
||||
if bg_color != "tranparent":
|
||||
kwargs["fill"] = bg_color
|
||||
|
||||
start_pos_x = self.value_pos_x
|
||||
start_pos_y = self.value_pos_y
|
||||
end_pos_x = start_pos_x + self.value_width()
|
||||
end_pos_y = start_pos_y + self.value_height()
|
||||
|
||||
drawer.rectangle(
|
||||
(
|
||||
(start_pos_x, start_pos_y),
|
||||
(end_pos_x, end_pos_y)
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def value_width(self):
|
||||
return int(self.style["width"])
|
||||
|
||||
def value_height(self):
|
||||
return int(self.style["height"])
|
||||
|
||||
def collect_data(self):
|
||||
return {
|
||||
"pos_x": self.value_pos_x,
|
||||
"pos_y": self.value_pos_y,
|
||||
"width": self.value_width(),
|
||||
"height": self.value_height(),
|
||||
"path": self.image_path
|
||||
}
|
||||
|
||||
|
||||
class ItemText(BaseItem):
|
||||
obj_type = "text"
|
||||
|
||||
def __init__(self, value, *args, **kwargs):
|
||||
super(ItemText, self).__init__(*args, **kwargs)
|
||||
self.value = value
|
||||
|
||||
def draw(self, image, drawer):
|
||||
bg_color = self.style["bg-color"]
|
||||
if bg_color and bg_color.lower() != "transparent":
|
||||
# TODO border outline styles
|
||||
drawer.rectangle(
|
||||
(self.content_pos_start, self.content_pos_end),
|
||||
fill=bg_color,
|
||||
outline=None
|
||||
)
|
||||
|
||||
font_color = self.style["font-color"]
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
drawer.text(
|
||||
self.value_pos_start,
|
||||
self.value,
|
||||
font=font,
|
||||
fill=font_color
|
||||
)
|
||||
|
||||
def value_width(self):
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
width = font.getsize(self.value)[0]
|
||||
return int(width)
|
||||
|
||||
def value_height(self):
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
height = font.getsize(self.value)[1]
|
||||
return int(height)
|
||||
|
||||
|
||||
class ItemTable(BaseItem):
|
||||
|
||||
obj_type = "table"
|
||||
|
||||
def __init__(self, values, use_alternate_color=False, *args, **kwargs):
|
||||
|
||||
self.values_by_cords = None
|
||||
self.prepare_values(values)
|
||||
|
||||
super(ItemTable, self).__init__(*args, **kwargs)
|
||||
self.size_values = None
|
||||
self.calculate_sizes()
|
||||
|
||||
self.use_alternate_color = use_alternate_color
|
||||
|
||||
def add_item(self, item):
|
||||
if item.obj_type == "table-item":
|
||||
return
|
||||
super(ItemTable, self).add_item(item)
|
||||
|
||||
def fill_data_format(self):
|
||||
for item in self.values:
|
||||
item.fill_data_format()
|
||||
|
||||
def prepare_values(self, _values):
|
||||
values = []
|
||||
values_by_cords = []
|
||||
row_count = 0
|
||||
col_count = 0
|
||||
for row in _values:
|
||||
row_count += 1
|
||||
if len(row) > col_count:
|
||||
col_count = len(row)
|
||||
|
||||
for row_idx in range(row_count):
|
||||
values_by_cords.append([])
|
||||
for col_idx in range(col_count):
|
||||
values_by_cords[row_idx].append([])
|
||||
if col_idx <= len(_values[row_idx]) - 1:
|
||||
col = _values[row_idx][col_idx]
|
||||
else:
|
||||
col = ""
|
||||
|
||||
col_item = TableField(row_idx, col_idx, col, parent=self)
|
||||
values_by_cords[row_idx][col_idx] = col_item
|
||||
values.append(col_item)
|
||||
|
||||
self.values = values
|
||||
self.values_by_cords = values_by_cords
|
||||
|
||||
def calculate_sizes(self):
|
||||
row_heights = []
|
||||
col_widths = []
|
||||
for row_idx, row in enumerate(self.values_by_cords):
|
||||
row_heights.append(0)
|
||||
for col_idx, col_item in enumerate(row):
|
||||
if len(col_widths) < col_idx + 1:
|
||||
col_widths.append(0)
|
||||
|
||||
_width = col_widths[col_idx]
|
||||
item_width = col_item.width()
|
||||
if _width < item_width:
|
||||
col_widths[col_idx] = item_width
|
||||
|
||||
_height = row_heights[row_idx]
|
||||
item_height = col_item.height()
|
||||
if _height < item_height:
|
||||
row_heights[row_idx] = item_height
|
||||
|
||||
self.size_values = (row_heights, col_widths)
|
||||
|
||||
def draw(self, image, drawer):
|
||||
bg_color = self.style["bg-color"]
|
||||
if bg_color and bg_color.lower() != "transparent":
|
||||
# TODO border outline styles
|
||||
drawer.rectangle(
|
||||
(self.content_pos_start, self.content_pos_end),
|
||||
fill=bg_color,
|
||||
outline=None
|
||||
)
|
||||
|
||||
for value in self.values:
|
||||
value.draw(image, drawer)
|
||||
|
||||
def value_width(self):
|
||||
row_heights, col_widths = self.size_values
|
||||
width = 0
|
||||
for _width in col_widths:
|
||||
width += _width
|
||||
|
||||
if width != 0:
|
||||
width -= 1
|
||||
return width
|
||||
|
||||
def value_height(self):
|
||||
row_heights, col_widths = self.size_values
|
||||
height = 0
|
||||
for _height in row_heights:
|
||||
height += _height
|
||||
|
||||
if height != 0:
|
||||
height -= 1
|
||||
return height
|
||||
|
||||
def content_pos_info_by_cord(self, row_idx, col_idx):
|
||||
row_heights, col_widths = self.size_values
|
||||
pos_x = int(self.value_pos_x)
|
||||
pos_y = int(self.value_pos_y)
|
||||
width = 0
|
||||
height = 0
|
||||
for idx, value in enumerate(col_widths):
|
||||
if col_idx == idx:
|
||||
width = value
|
||||
break
|
||||
pos_x += value
|
||||
|
||||
for idx, value in enumerate(row_heights):
|
||||
if row_idx == idx:
|
||||
height = value
|
||||
break
|
||||
pos_y += value
|
||||
|
||||
return (pos_x, pos_y, width, height)
|
||||
|
||||
|
||||
class TableField(BaseItem):
|
||||
|
||||
obj_type = "table-item"
|
||||
available_parents = ["table"]
|
||||
ellide_text = "..."
|
||||
|
||||
def __init__(self, row_idx, col_idx, value, *args, **kwargs):
|
||||
super(TableField, self).__init__(*args, **kwargs)
|
||||
self.row_idx = row_idx
|
||||
self.col_idx = col_idx
|
||||
self.value = value
|
||||
|
||||
def recalculate_by_width(self, value, max_width):
|
||||
padding = self.style["padding"]
|
||||
padding_left = self.style.get("padding-left")
|
||||
if padding_left is None:
|
||||
padding_left = padding
|
||||
|
||||
padding_right = self.style.get("padding-right")
|
||||
if padding_right is None:
|
||||
padding_right = padding
|
||||
|
||||
max_width -= (padding_left + padding_right)
|
||||
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
word_wrap = self.style.get("word-wrap")
|
||||
ellide = self.style.get("ellide")
|
||||
max_lines = self.style.get("max-lines")
|
||||
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
val_width = font.getsize(value)[0]
|
||||
if val_width <= max_width:
|
||||
return value
|
||||
|
||||
if not ellide and not word_wrap:
|
||||
# TODO logging
|
||||
self.log.warning((
|
||||
"Can't draw text because is too long with"
|
||||
" `word-wrap` and `ellide` turned off <{}>"
|
||||
).format(value))
|
||||
return ""
|
||||
|
||||
elif ellide and not word_wrap:
|
||||
max_lines = 1
|
||||
|
||||
words = [word for word in value.split()]
|
||||
words_len = len(words)
|
||||
lines = []
|
||||
last_index = None
|
||||
while True:
|
||||
start_index = 0
|
||||
if last_index is not None:
|
||||
start_index = int(last_index) + 1
|
||||
|
||||
line = ""
|
||||
for idx in range(start_index, words_len):
|
||||
_word = words[idx]
|
||||
connector = " "
|
||||
if line == "":
|
||||
connector = ""
|
||||
|
||||
_line = connector.join([line, _word])
|
||||
_line_width = font.getsize(_line)[0]
|
||||
if _line_width > max_width:
|
||||
break
|
||||
line = _line
|
||||
last_index = idx
|
||||
|
||||
if line:
|
||||
lines.append(line)
|
||||
|
||||
if last_index == words_len - 1:
|
||||
break
|
||||
|
||||
elif last_index is None:
|
||||
add_message = ""
|
||||
if ellide:
|
||||
add_message = " String was shortened to `{}`."
|
||||
line = ""
|
||||
for idx, char in enumerate(words[idx]):
|
||||
_line = line + char + self.ellide_text
|
||||
_line_width = font.getsize(_line)[0]
|
||||
if _line_width > max_width:
|
||||
if idx == 0:
|
||||
line = _line
|
||||
break
|
||||
line = line + char
|
||||
|
||||
lines.append(line)
|
||||
# TODO logging
|
||||
self.log.warning((
|
||||
"Font size is too big.{} <{}>"
|
||||
).format(add_message, value))
|
||||
break
|
||||
|
||||
output = ""
|
||||
if not lines:
|
||||
return output
|
||||
|
||||
over_max_lines = (max_lines and len(lines) > max_lines)
|
||||
if not over_max_lines:
|
||||
return "\n".join([line for line in lines])
|
||||
|
||||
lines = [lines[idx] for idx in range(max_lines)]
|
||||
if not ellide:
|
||||
return "\n".join(lines)
|
||||
|
||||
last_line = lines[-1]
|
||||
last_line_width = font.getsize(last_line + self.ellide_text)[0]
|
||||
if last_line_width <= max_width:
|
||||
lines[-1] += self.ellide_text
|
||||
return "\n".join([line for line in lines])
|
||||
|
||||
last_line_words = last_line.split()
|
||||
if len(last_line_words) == 1:
|
||||
if max_lines > 1:
|
||||
# TODO try previous line?
|
||||
lines[-1] = self.ellide_text
|
||||
return "\n".join([line for line in lines])
|
||||
|
||||
line = ""
|
||||
for idx, word in enumerate(last_line_words):
|
||||
_line = line + word + self.ellide_text
|
||||
_line_width = font.getsize(_line)[0]
|
||||
if _line_width > max_width:
|
||||
if idx == 0:
|
||||
line = _line
|
||||
break
|
||||
line = _line
|
||||
lines[-1] = line
|
||||
|
||||
return "\n".join([line for line in lines])
|
||||
|
||||
line = ""
|
||||
for idx, _word in enumerate(last_line_words):
|
||||
connector = " "
|
||||
if line == "":
|
||||
connector = ""
|
||||
|
||||
_line = connector.join([line, _word + self.ellide_text])
|
||||
_line_width = font.getsize(_line)[0]
|
||||
|
||||
if _line_width <= max_width:
|
||||
line = connector.join([line, _word])
|
||||
continue
|
||||
|
||||
if idx != 0:
|
||||
line += self.ellide_text
|
||||
break
|
||||
|
||||
if max_lines != 1:
|
||||
# TODO try previous line?
|
||||
line = self.ellide_text
|
||||
break
|
||||
|
||||
for idx, char in enumerate(_word):
|
||||
_line = line + char + self.ellide_text
|
||||
_line_width = font.getsize(_line)[0]
|
||||
if _line_width > max_width:
|
||||
if idx == 0:
|
||||
line = _line
|
||||
break
|
||||
line = line + char
|
||||
break
|
||||
|
||||
lines[-1] = line
|
||||
|
||||
return "\n".join([line for line in lines])
|
||||
|
||||
def fill_data_format(self):
|
||||
value = self.value
|
||||
if re.match(self.fill_data_regex, value):
|
||||
value = value.format(**self.fill_data)
|
||||
|
||||
self.orig_value = value
|
||||
|
||||
max_width = self.style.get("max-width")
|
||||
max_width = self.style.get("width") or max_width
|
||||
if max_width:
|
||||
value = self.recalculate_by_width(value, max_width)
|
||||
|
||||
self.value = value
|
||||
|
||||
def content_width(self):
|
||||
width = self.style.get("width")
|
||||
if width:
|
||||
return int(width)
|
||||
return super(TableField, self).content_width()
|
||||
|
||||
def content_height(self):
|
||||
return super(TableField, self).content_height()
|
||||
|
||||
def value_width(self):
|
||||
if not self.value:
|
||||
return 0
|
||||
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
width = font.getsize_multiline(self.value)[0] + 1
|
||||
|
||||
min_width = self.style.get("min-height")
|
||||
if min_width and min_width > width:
|
||||
width = min_width
|
||||
|
||||
return int(width)
|
||||
|
||||
def value_height(self):
|
||||
if not self.value:
|
||||
return 0
|
||||
|
||||
height = self.style.get("height")
|
||||
if height:
|
||||
return int(height)
|
||||
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
height = font.getsize_multiline(self.value)[1] + 1
|
||||
|
||||
min_height = self.style.get("min-height")
|
||||
if min_height and min_height > height:
|
||||
height = min_height
|
||||
|
||||
return int(height)
|
||||
|
||||
@property
|
||||
def item_pos_x(self):
|
||||
pos_x, pos_y, width, height = (
|
||||
self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx)
|
||||
)
|
||||
return pos_x
|
||||
|
||||
@property
|
||||
def item_pos_y(self):
|
||||
pos_x, pos_y, width, height = (
|
||||
self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx)
|
||||
)
|
||||
return pos_y
|
||||
|
||||
@property
|
||||
def value_pos_x(self):
|
||||
pos_x, pos_y, width, height = (
|
||||
self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx)
|
||||
)
|
||||
alignment_hor = self.style["alignment-horizontal"].lower()
|
||||
if alignment_hor in ["center", "centre"]:
|
||||
pos_x += (width - self.value_width()) / 2
|
||||
|
||||
elif alignment_hor == "right":
|
||||
pos_x += width - self.value_width()
|
||||
|
||||
else:
|
||||
padding = self.style["padding"]
|
||||
padding_left = self.style.get("padding-left")
|
||||
if padding_left is None:
|
||||
padding_left = padding
|
||||
|
||||
pos_x += padding_left
|
||||
|
||||
return int(pos_x)
|
||||
|
||||
@property
|
||||
def value_pos_y(self):
|
||||
pos_x, pos_y, width, height = (
|
||||
self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx)
|
||||
)
|
||||
|
||||
alignment_ver = self.style["alignment-vertical"].lower()
|
||||
if alignment_ver in ["center", "centre"]:
|
||||
pos_y += (height - self.value_height()) / 2
|
||||
|
||||
elif alignment_ver == "bottom":
|
||||
pos_y += height - self.value_height()
|
||||
|
||||
else:
|
||||
padding = self.style["padding"]
|
||||
padding_top = self.style.get("padding-top")
|
||||
if padding_top is None:
|
||||
padding_top = padding
|
||||
|
||||
pos_y += padding_top
|
||||
|
||||
return int(pos_y)
|
||||
|
||||
def draw(self, image, drawer):
|
||||
pos_x, pos_y, width, height = (
|
||||
self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx)
|
||||
)
|
||||
pos_start = (pos_x, pos_y)
|
||||
pos_end = (pos_x + width, pos_y + height)
|
||||
bg_color = self.style["bg-color"]
|
||||
if self.parent.use_alternate_color and (self.row_idx % 2) == 1:
|
||||
bg_color = self.style["bg-alter-color"]
|
||||
|
||||
if bg_color and bg_color.lower() != "transparent":
|
||||
# TODO border outline styles
|
||||
drawer.rectangle(
|
||||
(pos_start, pos_end),
|
||||
fill=bg_color,
|
||||
outline=None
|
||||
)
|
||||
|
||||
font_color = self.style["font-color"]
|
||||
font_family = self.style["font-family"]
|
||||
font_size = self.style["font-size"]
|
||||
font_bold = self.style.get("font-bold", False)
|
||||
font_italic = self.style.get("font-italic", False)
|
||||
|
||||
font = FontFactory.get_font(
|
||||
font_family, font_size, font_italic, font_bold
|
||||
)
|
||||
|
||||
alignment_hor = self.style["alignment-horizontal"].lower()
|
||||
if alignment_hor == "centre":
|
||||
alignment_hor = "center"
|
||||
|
||||
drawer.multiline_text(
|
||||
self.value_pos_start,
|
||||
self.value,
|
||||
font=font,
|
||||
fill=font_color,
|
||||
align=alignment_hor
|
||||
)
|
||||
139
pype/scripts/slate/slate_base/layer.py
Normal file
139
pype/scripts/slate/slate_base/layer.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
from .base import BaseObj
|
||||
|
||||
|
||||
class Layer(BaseObj):
|
||||
obj_type = "layer"
|
||||
available_parents = ["main_frame", "layer"]
|
||||
|
||||
# Direction can be 0=vertical/ 1=horizontal
|
||||
def __init__(self, direction=0, *args, **kwargs):
|
||||
super(Layer, self).__init__(*args, **kwargs)
|
||||
self._direction = direction
|
||||
|
||||
@property
|
||||
def item_pos_x(self):
|
||||
if self.parent.obj_type == self.obj_type:
|
||||
pos_x = self.parent.child_pos_x(self.id)
|
||||
elif self.parent.obj_type == "main_frame":
|
||||
pos_x = self._pos_x
|
||||
else:
|
||||
pos_x = self.parent.value_pos_x
|
||||
return int(pos_x)
|
||||
|
||||
@property
|
||||
def item_pos_y(self):
|
||||
if self.parent.obj_type == self.obj_type:
|
||||
pos_y = self.parent.child_pos_y(self.id)
|
||||
elif self.parent.obj_type == "main_frame":
|
||||
pos_y = self._pos_y
|
||||
else:
|
||||
pos_y = self.parent.value_pos_y
|
||||
|
||||
return int(pos_y)
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
if self._direction not in (0, 1):
|
||||
self.log.warning((
|
||||
"Direction of Layer must be 0 or 1 "
|
||||
"(0 is horizontal / 1 is vertical)! Setting to 0."
|
||||
))
|
||||
return 0
|
||||
return self._direction
|
||||
|
||||
def child_pos_x(self, item_id):
|
||||
pos_x = self.value_pos_x
|
||||
alignment_hor = self.style["alignment-horizontal"].lower()
|
||||
|
||||
item = None
|
||||
for id, _item in self.items.items():
|
||||
if item_id == id:
|
||||
item = _item
|
||||
break
|
||||
|
||||
if self.direction == 1:
|
||||
for id, _item in self.items.items():
|
||||
if item_id == id:
|
||||
break
|
||||
|
||||
pos_x += _item.width()
|
||||
if _item.obj_type not in ["image", "placeholder"]:
|
||||
pos_x += 1
|
||||
|
||||
else:
|
||||
if alignment_hor in ["center", "centre"]:
|
||||
pos_x += (self.content_width() - item.content_width()) / 2
|
||||
|
||||
elif alignment_hor == "right":
|
||||
pos_x += self.content_width() - item.content_width()
|
||||
|
||||
else:
|
||||
margin = self.style["margin"]
|
||||
margin_left = self.style.get("margin-left") or margin
|
||||
pos_x += margin_left
|
||||
|
||||
return int(pos_x)
|
||||
|
||||
def child_pos_y(self, item_id):
|
||||
pos_y = self.value_pos_y
|
||||
alignment_ver = self.style["alignment-horizontal"].lower()
|
||||
|
||||
item = None
|
||||
for id, _item in self.items.items():
|
||||
if item_id == id:
|
||||
item = _item
|
||||
break
|
||||
|
||||
if self.direction != 1:
|
||||
for id, item in self.items.items():
|
||||
if item_id == id:
|
||||
break
|
||||
pos_y += item.height()
|
||||
if item.obj_type not in ["image", "placeholder"]:
|
||||
pos_y += 1
|
||||
|
||||
else:
|
||||
if alignment_ver in ["center", "centre"]:
|
||||
pos_y += (self.content_height() - item.content_height()) / 2
|
||||
|
||||
elif alignment_ver == "bottom":
|
||||
pos_y += self.content_height() - item.content_height()
|
||||
|
||||
return int(pos_y)
|
||||
|
||||
def value_height(self):
|
||||
height = 0
|
||||
for item in self.items.values():
|
||||
if self.direction == 1:
|
||||
if height > item.height():
|
||||
continue
|
||||
# times 1 because won't get object pointer but number
|
||||
height = item.height()
|
||||
else:
|
||||
height += item.height()
|
||||
|
||||
# TODO this is not right
|
||||
min_height = self.style.get("min-height")
|
||||
if min_height and min_height > height:
|
||||
return min_height
|
||||
return height
|
||||
|
||||
def value_width(self):
|
||||
width = 0
|
||||
for item in self.items.values():
|
||||
if self.direction == 0:
|
||||
if width > item.width():
|
||||
continue
|
||||
# times 1 because won't get object pointer but number
|
||||
width = item.width()
|
||||
else:
|
||||
width += item.width()
|
||||
|
||||
min_width = self.style.get("min-width")
|
||||
if min_width and min_width > width:
|
||||
return min_width
|
||||
return width
|
||||
|
||||
def draw(self, image, drawer):
|
||||
for item in self.items.values():
|
||||
item.draw(image, drawer)
|
||||
77
pype/scripts/slate/slate_base/main_frame.py
Normal file
77
pype/scripts/slate/slate_base/main_frame.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import os
|
||||
import re
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from .base import BaseObj
|
||||
|
||||
|
||||
class MainFrame(BaseObj):
|
||||
|
||||
obj_type = "main_frame"
|
||||
available_parents = [None]
|
||||
|
||||
def __init__(
|
||||
self, width, height, destination_path, fill_data={}, *args, **kwargs
|
||||
):
|
||||
kwargs["parent"] = None
|
||||
super(MainFrame, self).__init__(*args, **kwargs)
|
||||
self._width = width
|
||||
self._height = height
|
||||
self.dst_path = destination_path
|
||||
self._fill_data = fill_data
|
||||
self.fill_data_format()
|
||||
|
||||
def fill_data_format(self):
|
||||
if re.match(self.fill_data_regex, self.dst_path):
|
||||
self.dst_path = self.dst_path.format(**self.fill_data)
|
||||
|
||||
@property
|
||||
def fill_data(self):
|
||||
return self._fill_data
|
||||
|
||||
def value_width(self):
|
||||
width = 0
|
||||
for item in self.items.values():
|
||||
width += item.width()
|
||||
return width
|
||||
|
||||
def value_height(self):
|
||||
height = 0
|
||||
for item in self.items.values():
|
||||
height += item.height()
|
||||
return height
|
||||
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
def draw(self, path=None):
|
||||
dir_path = os.path.dirname(self.dst_path)
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
|
||||
bg_color = self.style["bg-color"]
|
||||
image = Image.new("RGB", (self.width(), self.height()), color=bg_color)
|
||||
drawer = ImageDraw.Draw(image)
|
||||
for item in self.items.values():
|
||||
item.draw(image, drawer)
|
||||
|
||||
image.save(self.dst_path)
|
||||
self.reset()
|
||||
|
||||
def collect_data(self):
|
||||
output = {}
|
||||
output["width"] = self.width()
|
||||
output["height"] = self.height()
|
||||
output["slate_path"] = self.dst_path
|
||||
|
||||
placeholders = self.find_item(obj_type="placeholder")
|
||||
placeholders_data = []
|
||||
for placeholder in placeholders:
|
||||
placeholders_data.append(placeholder.collect_data())
|
||||
|
||||
output["placeholders"] = placeholders_data
|
||||
|
||||
return output
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"alignment": {
|
||||
"description": "Alignment of item",
|
||||
"type": "string",
|
||||
"enum": ["left", "right", "center"],
|
||||
"example": "left"
|
||||
},
|
||||
"font-family": {
|
||||
"description": "Font type",
|
||||
"type": "string",
|
||||
"example": "Arial"
|
||||
},
|
||||
"font-size": {
|
||||
"description": "Font size",
|
||||
"type": "integer",
|
||||
"example": 26
|
||||
},
|
||||
"font-color": {
|
||||
"description": "Font color",
|
||||
"type": "string",
|
||||
"regex": (
|
||||
"^[#]{1}[a-fA-F0-9]{1}$"
|
||||
" | ^[#]{1}[a-fA-F0-9]{3}$"
|
||||
" | ^[#]{1}[a-fA-F0-9]{6}$"
|
||||
),
|
||||
"example": "#ffffff"
|
||||
},
|
||||
"font-bold": {
|
||||
"description": "Font boldness",
|
||||
"type": "boolean",
|
||||
"example": True
|
||||
},
|
||||
"bg-color": {
|
||||
"description": "Background color, None means transparent",
|
||||
"type": ["string", None],
|
||||
"regex": (
|
||||
"^[#]{1}[a-fA-F0-9]{1}$"
|
||||
" | ^[#]{1}[a-fA-F0-9]{3}$"
|
||||
" | ^[#]{1}[a-fA-F0-9]{6}$"
|
||||
),
|
||||
"example": "#333"
|
||||
},
|
||||
"bg-alter-color": None,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue