1
0
Fork 0
mirror of https://git.lynn.is/Gwen/python-layout.git synced 2024-01-13 01:31:55 +01:00
This commit is contained in:
Gwendolyn 2023-02-04 15:36:54 +01:00
parent cd4c111c78
commit 4d4dc38fe9
9 changed files with 369 additions and 365 deletions

74
box.py
View file

@ -19,82 +19,82 @@ class Box(Layout):
**kwargs
):
super().__init__(**kwargs)
self._title = title
self._title_font = title_font
self._title_color = title_color
self._title_anchor = title_anchor
self._title_position = title_position
self._title_padding = title_padding
self._content = content
self.title = title
self.title_font = title_font
self.title_color = title_color
self.title_anchor = title_anchor
self.title_position = title_position
self.title_padding = title_padding
self.content = content
def _children(self):
if self._content is not None:
return [self._content]
def children(self):
if self.content is not None:
return [self.content]
else:
return []
def _get_title_font(self):
if self._title_font is not None:
return self._title_font
def get_title_font(self):
if self.title_font is not None:
return self.title_font
else:
return self.get_font()
def _get_title_color(self):
if self._title_color is not None:
return self._title_color
def get_title_color(self):
if self.title_color is not None:
return self.title_color
else:
return self.get_fg_color()
def get_min_inner_width(self, max_height=None):
if self._content is None:
if self.content is None:
return 0
else:
return self._content.get_min_outer_width(max_height)
return self.content.get_min_outer_width(max_height)
def get_min_inner_height(self, max_width=None):
if self._content is None:
if self.content is None:
return 0
else:
return self._content.get_min_outer_height(max_width)
return self.content.get_min_outer_height(max_width)
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
if self._content is None:
if self.content is None:
return 0, 0
else:
return self._content.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
return self.content.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
def render_content(self, rect):
if self._content is None:
if self.content is None:
return self.make_canvas()
else:
return self._content.render(rect)
return self.content.render(rect)
def _title_pos(self, rect):
left, top, right, bottom = self._get_title_font().getbbox(self._title)
def title_pos(self, rect):
left, top, right, bottom = self.get_title_font().getbbox(self.title)
height = bottom - top
width = right - left
border_radius = self.border_radius()
border_radius = self.get_border_radius()
available_width = rect[2] - rect[0] + 1 - border_radius[1] - border_radius[0]
pos_x = rect[0] + border_radius[0] + self._title_position
if self._title_anchor == BoxTitleAnchor.LEFT:
pos_x = rect[0] + border_radius[0] + self.title_position
if self.title_anchor == BoxTitleAnchor.LEFT:
pos_x += 0
elif self._title_anchor == BoxTitleAnchor.CENTER:
elif self.title_anchor == BoxTitleAnchor.CENTER:
pos_x += (available_width - width) / 2
elif self._title_anchor == BoxTitleAnchor.RIGHT:
elif self.title_anchor == BoxTitleAnchor.RIGHT:
pos_x += available_width - width
pos_y = rect[1] - height / 2
return (pos_x, pos_y), (left - self._title_padding, top, right + self._title_padding, bottom)
return (pos_x, pos_y), (left - self.title_padding, top, right + self.title_padding, bottom)
def modify_border_mask(self, border_mask, rect):
if self._title is None:
if self.title is None:
return
(x, y), (left, top, right, bottom) = self._title_pos(rect)
(x, y), (left, top, right, bottom) = self.title_pos(rect)
d = ImageDraw.Draw(border_mask)
d.rectangle((x + left, y + top, x + right, y + bottom), fill=0)
def render_after_border(self, image, rect):
if self._title is None:
if self.title is None:
return
(x, y), _ = self._title_pos(rect)
(x, y), _ = self.title_pos(rect)
d = ImageDraw.Draw(image)
d.text((x, y), self._title, font=self._get_title_font(), fill=self._get_title_color())
d.text((x, y), self.title, font=self.get_title_font(), fill=self.get_title_color())

View file

@ -17,20 +17,20 @@ class Container(Layout):
contents = []
self._contents = contents
def _children(self):
def children(self):
return self._contents
def get_min_inner_width(self, max_height=None):
min_width = 0
for c in self._contents:
width = c.get_min_outer_width(max_height)
if c._left is not None:
width += c._left
if c._right is not None:
width += c._right
if c.left is not None:
width += c.left
if c.right is not None:
width += c.right
min_width = max(min_width, width)
if self._min_width is not None:
min_width = max(min_width, self._min_width)
if self.min_width is not None:
min_width = max(min_width, self.min_width)
return min_width
def get_min_inner_height(self, max_width=None):
@ -38,17 +38,17 @@ class Container(Layout):
min_height_absolute = 0
for c in self._contents:
height = c.get_min_outer_height(max_width)
if c._top is None and c._bottom is None and c._left is None and c._right is None:
if c.top is None and c.bottom is None and c.left is None and c.right is None:
min_height_automatic += height
else:
if c._top is not None:
height += c._top
if c._bottom is not None:
height += c._bottom
if c.top is not None:
height += c.top
if c.bottom is not None:
height += c.bottom
min_height_absolute = max(min_height_absolute, height)
min_height = max(min_height_automatic, min_height_absolute)
if self._min_height is not None:
min_height = max(min_height, self._min_height)
if self.min_height is not None:
min_height = max(min_height, self.min_height)
return min_height
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
@ -56,24 +56,24 @@ class Container(Layout):
for c in self._contents:
w, h = c.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
if c._left is not None:
w += c._left
if c._right is not None:
w += c._right
if c._top is not None:
h += c._top
if c._bottom is not None:
h += c._bottom
if c.left is not None:
w += c.left
if c.right is not None:
w += c.right
if c.top is not None:
h += c.top
if c.bottom is not None:
h += c.bottom
width = max(width, w)
if c._top is None and c._bottom is None and c._left is None and c._right is None:
if c.top is None and c.bottom is None and c.left is None and c.right is None:
height_automatic += h
else:
height_absolute = max(height_absolute, h)
height = max(height_automatic, height_absolute)
if self._width is not None:
width = self._width
if self._height is not None:
height = self._height
if self.width is not None:
width = self.width
if self.height is not None:
height = self.height
return width, height
def render_content(self, rect):
@ -83,29 +83,29 @@ class Container(Layout):
for c in self._contents:
soft_max_width, soft_max_height = x2 - x1 + 1, y2 - y1 + 1
is_absolute = not (c._top is None and c._bottom is None and c._left is None and c._right is None)
is_absolute = not (c.top is None and c.bottom is None and c.left is None and c.right is None)
if c._left is not None:
soft_max_width -= c._left
if c._right is not None:
soft_max_width -= c._right
if c._top is not None:
soft_max_height -= c._top
if c._bottom is not None:
soft_max_height -= c._bottom
if c.left is not None:
soft_max_width -= c.left
if c.right is not None:
soft_max_width -= c.right
if c.top is not None:
soft_max_height -= c.top
if c.bottom is not None:
soft_max_height -= c.bottom
hard_max_width, hard_max_height = c._max_width, c._max_height
if c._left is not None and c._right is not None:
hard_max_width = min_with_none(hard_max_width, x2 - x1 + 1 - c._left - c._right)
if c._top is not None and c._bottom is not None:
hard_max_height = min_with_none(hard_max_height, y2 - y1 + 1 - c._top - c._bottom)
hard_max_width, hard_max_height = c.max_width, c.max_height
if c.left is not None and c.right is not None:
hard_max_width = min_with_none(hard_max_width, x2 - x1 + 1 - c.left - c.right)
if c.top is not None and c.bottom is not None:
hard_max_height = min_with_none(hard_max_height, y2 - y1 + 1 - c.top - c.bottom)
width, height = c.get_ideal_outer_dimensions(available_width=soft_max_width, available_height=soft_max_height)
if c._left is not None and c._right is not None:
width = (x2 - c._right) - (x1 + c._left) + 1
if c._top is not None and c._bottom is not None:
height = (y2 - c._bottom) - (y1 + c._top) + 1
if c.left is not None and c.right is not None:
width = (x2 - c.right) - (x1 + c.left) + 1
if c.top is not None and c.bottom is not None:
height = (y2 - c.bottom) - (y1 + c.top) + 1
min_width, min_height = c.get_min_outer_width(hard_max_height), c.get_min_outer_height(hard_max_width)
width = max_with_none(width, min_width)
@ -115,26 +115,26 @@ class Container(Layout):
height = min_with_none(height, hard_max_height)
if is_absolute:
if c._left is None:
if c._right is None:
if c.left is None:
if c.right is None:
cx1 = x1
cx2 = cx1 + width - 1
else:
cx2 = x2 - c._right
cx2 = x2 - c.right
cx1 = cx2 - width + 1
else:
cx1 = x1 + c._left
cx1 = x1 + c.left
cx2 = cx1 + width - 1
if c._top is None:
if c._bottom is None:
if c.top is None:
if c.bottom is None:
cy1 = y1
cy2 = cy1 + height - 1
else:
cy2 = y2 - c._bottom
cy2 = y2 - c.bottom
cy1 = cy2 - height + 1
else:
cy1 = y1 + c._top
cy1 = y1 + c.top
cy2 = cy1 + height - 1
else:
cx1 = x1

View file

@ -14,67 +14,67 @@ class Document(Layout):
**kwargs
):
super().__init__(**kwargs)
if self._overflow is None:
self._overflow = True
if self._font is None:
self._font = ImageFont.load_default()
self._content = content
self._actual_size = None
if self.overflow is None:
self.overflow = True
if self.font is None:
self.font = ImageFont.load_default()
self.content = content
self.actual_size = None
self.complete_init(None)
def _children(self):
if self._content is not None:
return [self._content]
def children(self):
if self.content is not None:
return [self.content]
else:
return []
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
if self._content is None:
if self.content is None:
return 0,0
else:
return self._content.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
return self.content.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
def get_min_inner_height(self, max_width=None):
if self._content is None:
if self.content is None:
return 0
else:
return self._content.get_min_outer_height(max_width)
return self.content.get_min_outer_height(max_width)
def get_min_inner_width(self, max_height=None):
if self._content is None:
if self.content is None:
return 0
else:
return self._content.get_min_outer_width(max_height)
return self.content.get_min_outer_width(max_height)
def render_content(self, rect):
image = self.make_canvas()
if self._content is not None:
content_image = self._content.render(rect)
if self.content is not None:
content_image = self.content.render(rect)
image.alpha_composite(content_image)
return image
def get_image(self):
min_width = max_with_none(self._width, self._min_width)
min_height = max_with_none(self._height, self._min_height)
max_width = min_with_none(self._width, self._max_width)
max_height = min_with_none(self._height, self._max_height)
min_width = max_with_none(self.width, self.min_width)
min_height = max_with_none(self.height, self.min_height)
max_width = min_with_none(self.width, self.max_width)
max_height = min_with_none(self.height, self.max_height)
width, height = self.get_ideal_outer_dimensions(min_width=min_width,
min_height=min_height,
available_width=max_width,
available_height=max_height)
if self._width is not None:
width = self._width
if self.width is not None:
width = self.width
else:
width = min_with_none(max_with_none(width, self._min_width), self._max_width)
if self._height is not None:
height = self._height
width = min_with_none(max_with_none(width, self.min_width), self.max_width)
if self.height is not None:
height = self.height
else:
height = min_with_none(max_with_none(height, self._min_height), self._max_height)
height = min_with_none(max_with_none(height, self.min_height), self.max_height)
self._actual_size = (width, height)
self.actual_size = (width, height)
background = Image.new('RGBA', (width, height), self._bg_color)
background = Image.new('RGBA', (width, height), self.bg_color)
content = self.render((0, 0, width - 1, height - 1))
return Image.alpha_composite(background, content)

44
flex.py
View file

@ -21,43 +21,43 @@ class Flex(Layout):
super().__init__(**kwargs)
if contents is None:
contents = []
self._direction = direction
self._wrap = wrap
self._justify = justify
self._align_items = align_items
self._align_content = align_content
self._gap = gap
self._contents = contents
self._flex_layouter = FlexLayouter(contents, gap, direction, wrap, justify, align_content, align_items)
self.direction = direction
self.wrap = wrap
self.justify = justify
self.align_items = align_items
self.align_content = align_content
self.gap = gap
self.contents = contents
self.flex_layouter = FlexLayouter(contents, gap, direction, wrap, justify, align_content, align_items)
def _children(self):
return self._contents
def children(self):
return self.contents
def get_min_inner_width(self, max_height=None):
max_width = min_with_none(self._max_width, 0)
width, _ = self._flex_layouter.get_dimensions(self._min_width, self._min_height, max_width,
min_with_none(self._max_height, max_height))
max_width = min_with_none(self.max_width, 0)
width, _ = self.flex_layouter.get_dimensions(self.min_width, self.min_height, max_width,
min_with_none(self.max_height, max_height))
return width
def get_min_inner_height(self, max_width=None):
max_height = min_with_none(self._min_height, 0)
_, height = self._flex_layouter.get_dimensions(self._min_width, self._min_height,
min_with_none(self._max_width, max_width), max_height)
max_height = min_with_none(self.min_height, 0)
_, height = self.flex_layouter.get_dimensions(self.min_width, self.min_height,
min_with_none(self.max_width, max_width), max_height)
return height
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
min_width = max_with_none(min_width, self._min_width)
min_height = max_with_none(min_height, self._min_height)
available_width = min_with_none(available_width, self._max_width, self._width)
available_height = min_with_none(available_height, self._max_height, self._height)
return self._flex_layouter.get_dimensions(min_width, min_height, available_width, available_height)
min_width = max_with_none(min_width, self.min_width)
min_height = max_with_none(min_height, self.min_height)
available_width = min_with_none(available_width, self.max_width, self.width)
available_height = min_with_none(available_height, self.max_height, self.height)
return self.flex_layouter.get_dimensions(min_width, min_height, available_width, available_height)
def render_content(self, rect):
image = self.make_canvas()
x1, y1, x2, y2 = rect
width = x2 - x1 + 1
height = y2 - y1 + 1
contents = self._flex_layouter.arrange(self._min_width, self._min_height, width, height)
contents = self.flex_layouter.arrange(self.min_width, self.min_height, width, height)
for (cx1, cy1, cx2, cy2), content in contents:
content_image = content.render((cx1 + x1, cy1 + y1, cx2 + x1, cy2 + y1))
image.alpha_composite(content_image)

202
image.py
View file

@ -19,69 +19,69 @@ class Image(Layout):
**kwargs
):
super().__init__(**kwargs)
self._image = image
self._mode = mode
self._scale = scale
self._position = position
self._offset_x = offset_x
self._offset_y = offset_y
self.image = image
self.mode = mode
self.scale = scale
self.position = position
self.offset_x = offset_x
self.offset_y = offset_y
def scale(self):
if isinstance(self._scale, (tuple, list)):
if len(self._scale) == 0:
def get_scale(self):
if isinstance(self.scale, (tuple, list)):
if len(self.scale) == 0:
return 1, 1
elif len(self._scale) == 1:
return self._scale[0], self._scale[0]
elif len(self.scale) == 1:
return self.scale[0], self.scale[0]
else:
return self._scale[0], self._scale[1]
elif isinstance(self._scale, (int, float)):
return self._scale, self._scale
return self.scale[0], self.scale[1]
elif isinstance(self.scale, (int, float)):
return self.scale, self.scale
else:
return 1, 1
def _get_image_width(self):
if self._image is None:
def get_image_width(self):
if self.image is None:
return 0
return self._image.size[0]
return self.image.size[0]
def _get_image_height(self):
if self._image is None:
def get_image_height(self):
if self.image is None:
return 0
return self._image.size[1]
return self.image.size[1]
def get_min_inner_width(self, max_height=None):
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self._get_image_width()
if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self.get_image_width()
else:
width = 0
return max_with_none(width, self._min_width)
return max_with_none(width, self.min_width)
def get_min_inner_height(self, max_width=None):
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self._get_image_height()
if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self.get_image_height()
else:
height = 0
return max_with_none(height, self._min_height)
return max_with_none(height, self.min_height)
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
width, height = 0, 0
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self._get_image_width()
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self._get_image_height()
if self._width is not None:
width = self._width
if self._height is not None:
height = self._height
width = min_with_none(width, available_width, self._max_width)
height = min_with_none(height, available_height, self._max_height)
width = max_with_none(width, min_width, self._min_width)
height = max_with_none(height, min_height, self._min_height)
if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self.get_image_width()
if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self.get_image_height()
if self.width is not None:
width = self.width
if self.height is not None:
height = self.height
width = min_with_none(width, available_width, self.max_width)
height = min_with_none(height, available_height, self.max_height)
width = max_with_none(width, min_width, self.min_width)
height = max_with_none(height, min_height, self.min_height)
return width, height
def render_content(self, rect):
image = self.make_canvas()
if self._image is None:
if self.image is None:
return image
x1, y1, x2, y2 = rect
@ -91,24 +91,24 @@ class Image(Layout):
if width == 0 or height == 0:
return image
image_width = int(self._image.size[0] * self.scale()[0])
image_height = int(self._image.size[1] * self.scale()[1])
image_width = int(self.image.size[0] * self.get_scale()[0])
image_height = int(self.image.size[1] * self.get_scale()[1])
offset_x = self._offset_x
offset_y = self._offset_y
offset_x = self.offset_x
offset_y = self.offset_y
if self._mode == ImageMode.ORIGINAL:
if image_width != self._image.size[0] or image_height != self._image.size[1]:
img = self._image.resize((image_width, image_height), reducing_gap=3.0)
if self.mode == ImageMode.ORIGINAL:
if image_width != self.image.size[0] or image_height != self.image.size[1]:
img = self.image.resize((image_width, image_height), reducing_gap=3.0)
else:
img = self._image
elif self._mode == ImageMode.STRETCH:
img = self._image.resize((width, height), reducing_gap=3.0)
elif self._mode == ImageMode.STRETCH_X:
img = self._image.resize((width, image_height), reducing_gap=3.0)
elif self._mode == ImageMode.STRETCH_Y:
img = self._image.resize((image_width, height), reducing_gap=3.0)
elif self._mode == ImageMode.CONTAIN:
img = self.image
elif self.mode == ImageMode.STRETCH:
img = self.image.resize((width, height), reducing_gap=3.0)
elif self.mode == ImageMode.STRETCH_X:
img = self.image.resize((width, image_height), reducing_gap=3.0)
elif self.mode == ImageMode.STRETCH_Y:
img = self.image.resize((image_width, height), reducing_gap=3.0)
elif self.mode == ImageMode.CONTAIN:
ratio_width = width / image_width
ratio_height = height / image_height
if ratio_width < ratio_height:
@ -120,8 +120,8 @@ class Image(Layout):
else:
w = width
h = height
img = self._image.resize((w, h), reducing_gap=3.0)
elif self._mode == ImageMode.COVER:
img = self.image.resize((w, h), reducing_gap=3.0)
elif self.mode == ImageMode.COVER:
ratio_width = width / image_width
ratio_height = height / image_height
if ratio_width > ratio_height:
@ -133,42 +133,42 @@ class Image(Layout):
else:
w = width
h = height
img = self._image.resize((w, h), reducing_gap=3.0)
img = self.image.resize((w, h), reducing_gap=3.0)
if self._mode == ImageMode.REPEAT_X_FILL:
if self.mode == ImageMode.REPEAT_X_FILL:
repeat_width = int(image_width * height / image_height)
repeat_height = height
elif self._mode == ImageMode.REPEAT_Y_FILL:
elif self.mode == ImageMode.REPEAT_Y_FILL:
repeat_width = width
repeat_height = int(image_height * width / image_width)
else:
repeat_width = image_width
repeat_height = image_height
if self._mode in (ImageMode.REPEAT, ImageMode.REPEAT_X, ImageMode.REPEAT_X_FILL, ImageMode.REPEAT_X_STRETCH):
if self._position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT):
if self.mode in (ImageMode.REPEAT, ImageMode.REPEAT_X, ImageMode.REPEAT_X_FILL, ImageMode.REPEAT_X_STRETCH):
if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT):
repeat_offset_x = offset_x
elif self._position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER):
elif self.position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER):
repeat_offset_x = (width // 2) - repeat_width // 2 + offset_x
elif self._position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT):
elif self.position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT):
repeat_offset_x = width - repeat_width + offset_x
else:
raise Exception('invalid image position')
if self._mode in (ImageMode.REPEAT, ImageMode.REPEAT_Y, ImageMode.REPEAT_Y_FILL, ImageMode.REPEAT_Y_STRETCH):
if self._position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT):
if self.mode in (ImageMode.REPEAT, ImageMode.REPEAT_Y, ImageMode.REPEAT_Y_FILL, ImageMode.REPEAT_Y_STRETCH):
if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT):
repeat_offset_y = offset_y
elif self._position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT):
elif self.position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT):
repeat_offset_y = (height // 2) - repeat_height // 2 + offset_y
elif self._position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT):
elif self.position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT):
repeat_offset_y = height - repeat_height + offset_y
else:
raise Exception('invalid image position')
if self._mode == ImageMode.REPEAT:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((repeat_width, repeat_height), reducing_gap=3.0)
if self.mode == ImageMode.REPEAT:
if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1
num_y = height // repeat_height + 1
@ -179,66 +179,66 @@ class Image(Layout):
img.paste(img_part, (i * repeat_width + ox, j * repeat_height + oy))
offset_x = 0
offset_y = 0
elif self._mode == ImageMode.REPEAT_X:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((repeat_width, repeat_height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_X:
if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, repeat_height), (0,0,0,0))
num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width)
for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0
elif self._mode == ImageMode.REPEAT_Y:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((repeat_width, repeat_height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_Y:
if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (repeat_width, height), (0,0,0,0))
num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height)
for i in range(-1, num_y):
img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0
elif self._mode == ImageMode.REPEAT_X_STRETCH:
if repeat_width != self._image.size[0] or height != self._image.size[1]:
img_part = self._image.resize((repeat_width, height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_X_STRETCH:
if repeat_width != self.image.size[0] or height != self.image.size[1]:
img_part = self.image.resize((repeat_width, height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width)
for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0
elif self._mode == ImageMode.REPEAT_Y_STRETCH:
if width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((width, repeat_height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_Y_STRETCH:
if width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height)
for i in range(-1, num_y):
img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0
elif self._mode == ImageMode.REPEAT_X_FILL:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((repeat_width, repeat_height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_X_FILL:
if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -w)
for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0
elif self._mode == ImageMode.REPEAT_Y_FILL:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]:
img_part = self._image.resize((repeat_width, repeat_height), reducing_gap=3.0)
elif self.mode == ImageMode.REPEAT_Y_FILL:
if repeat_width != self.image.size[0] or repeat_height != self.image.size[1]:
img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else:
img_part = self._image
img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height)
@ -246,19 +246,19 @@ class Image(Layout):
img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0
if self._position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT):
if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT):
x = x1 + offset_x
elif self._position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER):
elif self.position in (ImageAnchor.TOP_CENTER, ImageAnchor.MIDDLE_CENTER, ImageAnchor.BOTTOM_CENTER):
x = x1 + (width // 2) - img.size[0] // 2 + offset_x
elif self._position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT):
elif self.position in (ImageAnchor.TOP_RIGHT, ImageAnchor.MIDDLE_RIGHT, ImageAnchor.BOTTOM_RIGHT):
x = x1 + width - img.size[0] + offset_x
else:
raise Exception('invalid image position')
if self._position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT):
if self.position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT):
y = y1 + offset_y
elif self._position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT):
elif self.position in (ImageAnchor.MIDDLE_LEFT, ImageAnchor.MIDDLE_CENTER, ImageAnchor.MIDDLE_RIGHT):
y = y1 + (height // 2) - img.size[1] // 2 + offset_y
elif self._position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT):
elif self.position in (ImageAnchor.BOTTOM_LEFT, ImageAnchor.BOTTOM_CENTER, ImageAnchor.BOTTOM_RIGHT):
y = y1 + height - img.size[1] + offset_y
else:
raise Exception('invalid image position')

View file

@ -2,7 +2,7 @@ from ..enums import FlexDirection, FlexWrap, FlexJustify, FlexAlignItems, FlexAl
from .helpers import min_with_none, max_with_none
class FlexLineItem():
class FlexLineItem:
def __init__(self, content, max_width, max_height, direction):
self.__content = content
self.__max_width = max_width
@ -20,14 +20,14 @@ class FlexLineItem():
def base_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
if self.__content._width is not None:
return self.__content._width
if self.__content.width is not None:
return self.__content.width
else:
return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width,
available_height=self.__max_height)[0]
else:
if self.__content._height is not None:
return self.__content._height
if self.__content.height is not None:
return self.__content.height
else:
return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width,
available_height=self.__max_height)[1]
@ -46,15 +46,15 @@ class FlexLineItem():
def max_main_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
return self.__content._max_width
return self.__content.max_width
else:
return self.__content._max_height
return self.__content.max_height
def max_cross_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
return self.__content._max_height
return self.__content.max_height
else:
return self.__content._max_width
return self.__content.max_width
def hypothetical_main_size(self):
return min_with_none(self.max_main_size(), max_with_none(self.min_main_size(), self.base_size()))
@ -81,10 +81,10 @@ class FlexLineItem():
return self.__cross_pos
def grow_factor(self):
return self.__content._flex_grow
return self.__content.flex_grow
def shrink_factor(self):
return self.__content._flex_shrink
return self.__content.flex_shrink
def scaled_shrink_factor(self):
return self.shrink_factor() * self.base_size()

124
layout.py
View file

@ -32,68 +32,68 @@ class Layout:
flex_shrink=0,
debug_layout=False,
):
self._width = width
self._height = height
self._min_width = min_width
self._min_height = min_height
self._max_width = max_width
self._max_height = max_height
self._fg_color = fg_color
self._bg_color = bg_color
self._border_color = border_color
self._border_width = border_width
self._border_radius = border_radius
self._padding = padding
self._font = font
self._overflow = overflow
self._left = left
self._top = top
self._right = right
self._bottom = bottom
self._flex_grow = flex_grow
self._flex_shrink = flex_shrink
self._debug_layout = debug_layout
self._container = None
self.width = width
self.height = height
self.min_width = min_width
self.min_height = min_height
self.max_width = max_width
self.max_height = max_height
self.fg_color = fg_color
self.bg_color = bg_color
self.border_color = border_color
self.border_width = border_width
self.border_radius = border_radius
self.padding = padding
self.font = font
self.overflow = overflow
self.left = left
self.top = top
self.right = right
self.bottom = bottom
self.flex_grow = flex_grow
self.flex_shrink = flex_shrink
self.debug_layout = debug_layout
self.container = None
def complete_init(self, container):
self._container = container
for c in self._children():
self.container = container
for c in self.children():
c.complete_init(self)
def _children(self):
def children(self):
return []
def get_document_size(self):
if isinstance(self, layout.Document):
return self._actual_size
return self.actual_size
else:
return self._container.get_document_size()
return self.container.get_document_size()
def get_fg_color(self):
if self._fg_color is not None:
return self._fg_color
elif self._container is not None:
return self._container.get_fg_color()
if self.fg_color is not None:
return self.fg_color
elif self.container is not None:
return self.container.get_fg_color()
else:
return None
def get_font(self):
if self._font is not None:
return self._font
elif self._container is not None:
return self._container.get_font()
if self.font is not None:
return self.font
elif self.container is not None:
return self.container.get_font()
else:
raise Exception('no font defined in {0}'.format(self.__class__))
def get_overflow(self):
if self._overflow is not None:
return self._overflow
elif self._container is not None:
return self._container.get_overflow()
if self.overflow is not None:
return self.overflow
elif self.container is not None:
return self.container.get_overflow()
else:
return None
def _get_tuple_property(self, name, default, allow_tuple=True):
def get_tuple_property(self, name, default, allow_tuple=True):
value = getattr(self, name)
if isinstance(value, list) or (allow_tuple and isinstance(value, tuple)):
if len(value) == 0:
@ -111,33 +111,33 @@ class Layout:
else:
return value, value, value, value
def padding(self):
return self._get_tuple_property('_padding', 0)
def get_padding(self):
return self.get_tuple_property('padding', 0)
def border_width(self):
return self._get_tuple_property('_border_width', 0)
def get_border_width(self):
return self.get_tuple_property('border_width', 0)
def border_color(self):
return self._get_tuple_property('_border_color', None, False)
def get_border_color(self):
return self.get_tuple_property('border_color', None, False)
def border_radius(self):
return self._get_tuple_property('_border_radius', None)
def get_border_radius(self):
return self.get_tuple_property('border_radius', None)
def get_min_inner_width(self, max_height=None):
return self._min_width
return self.min_width
def get_min_inner_height(self, max_width=None):
return self._min_height
return self.min_height
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
return self._width, self._height
return self.width, self.height
def get_x_padding_border_size(self):
border, padding = self.border_width(), self.padding()
border, padding = self.get_border_width(), self.get_padding()
return border[0] + border[2] + padding[0] + padding[2]
def get_y_padding_border_size(self):
border, padding = self.border_width(), self.padding()
border, padding = self.get_border_width(), self.get_padding()
return border[1] + border[3] + padding[1] + padding[3]
def get_min_outer_width(self, max_outer_height=None):
@ -208,7 +208,7 @@ class Layout:
def make_border_inside_mask(self, rect, border_radii):
mask = Image.new('1', self.get_document_size(), 0)
x1, y1, x2, y2 = rect
width_left, width_top, width_right, width_bottom = self.border_width()
width_left, width_top, width_right, width_bottom = self.get_border_width()
radius_top_left, radius_top_right, radius_bottom_right, radius_bottom_left = border_radii
inner_rect = (x1 + width_left, y1 + width_top, x2 - width_right, y2 - width_bottom)
@ -236,8 +236,8 @@ class Layout:
def make_border_color_image(self, rect, border_radii):
x1, y1, x2, y2 = rect
color_left, color_top, color_right, color_bottom = self.border_color()
width_left, width_top, width_right, width_bottom = self.border_width()
color_left, color_top, color_right, color_bottom = self.get_border_color()
width_left, width_top, width_right, width_bottom = self.get_border_width()
radius_top_left, radius_top_right, radius_bottom_right, radius_bottom_left = border_radii
image = self.make_canvas()
@ -297,7 +297,7 @@ class Layout:
x1, y1, x2, y2 = rect
width, height = x2 - x1 + 1, y2 - y1 + 1
border_radii = self.border_radius()
border_radii = self.get_border_radius()
radius_sum_top = border_radii[0] + border_radii[1]
radius_sum_right = border_radii[1] + border_radii[2]
radius_sum_bottom = border_radii[2] + border_radii[3]
@ -354,8 +354,8 @@ class Layout:
self.modify_inside_mask(inside_mask, rect)
self.modify_border_mask(border_mask, rect)
if self._bg_color is not None:
image.paste(self._bg_color, mask=outside_mask)
if self.bg_color is not None:
image.paste(self.bg_color, mask=outside_mask)
self.render_after_background(image, rect)
border_color = self.make_border_color_image(rect, border_radii)
@ -365,8 +365,8 @@ class Layout:
image.alpha_composite(border_image)
self.render_after_border(image, rect)
border_width = self.border_width()
padding = self.padding()
border_width = self.get_border_width()
padding = self.get_padding()
content_x1 = rect[0] + border_width[0] + padding[0]
content_y1 = rect[1] + border_width[1] + padding[1]
content_x2 = rect[2] - border_width[2] - padding[2]
@ -380,7 +380,7 @@ class Layout:
image.alpha_composite(content_image)
self.render_after_content(image, rect)
if self._debug_layout:
if self.debug_layout:
dc = Image.new('RGBA', image.size, (0, 0, 0, 0))
d = ImageDraw.Draw(dc)

View file

@ -485,7 +485,11 @@ Where the image is anchored for positioning with `offset_x` and `offset_y`.
#### ImageAnchor.BOTTOM_RIGHT
####
# custom layouts
A layout should extend the `Layout` class and implement the following methods:
# possible future features

98
text.py
View file

@ -18,65 +18,65 @@ class Text(Layout):
**kwargs
):
super().__init__(**kwargs)
self._text_layouter = None
self._text = text
self._text_align = text_align
self._vertical_text_align = vertical_text_align
self._text_wrap = text_wrap
self._line_spacing = line_spacing
self.text_layouter = None
self.text = text
self.text_align = text_align
self.vertical_text_align = vertical_text_align
self.text_wrap = text_wrap
self.line_spacing = line_spacing
def complete_init(self, container):
super().complete_init(container)
if self._is_empty():
if self.is_empty():
lines = []
elif isinstance(self._text, list):
lines = self._text
elif isinstance(self.text, list):
lines = self.text
else:
lines = self._text.splitlines()
self._text_layouter = TextLayouter(lines=lines,
align=self._text_align,
vertical_align=self._vertical_text_align,
wrap=self._text_wrap,
font=self.get_font(),
line_height=self._get_line_height_with_spacing())
lines = self.text.splitlines()
self.text_layouter = TextLayouter(lines=lines,
align=self.text_align,
vertical_align=self.vertical_text_align,
wrap=self.text_wrap,
font=self.get_font(),
line_height=self.get_line_height_with_spacing())
def _is_empty(self):
return self._text is None or self._text == '' or (isinstance(self._text, list)
and len(self._text) == 0)
def is_empty(self):
return self.text is None or self.text == '' or (isinstance(self.text, list)
and len(self.text) == 0)
def get_min_inner_width(self, max_height=None):
if self._is_empty():
if self.is_empty():
width = 0
else:
if max_height is not None:
max_height += self._line_spacing
width = self._text_layouter.get_dimensions(0, max_height)[0]
return max_with_none(width, self._min_width)
max_height += self.line_spacing
width = self.text_layouter.get_dimensions(0, max_height)[0]
return max_with_none(width, self.min_width)
def get_min_inner_height(self, max_width=None):
if self._is_empty():
if self.is_empty():
height = 0
else:
# no spacing after the last line
height = self._text_layouter.get_dimensions(max_width, 0)[1] - self._line_spacing
return max_with_none(height, self._min_height)
height = self.text_layouter.get_dimensions(max_width, 0)[1] - self.line_spacing
return max_with_none(height, self.min_height)
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
available_width = min_with_none(available_width, self._width)
available_height = min_with_none(available_height, self._height)
available_width = min_with_none(available_width, self.width)
available_height = min_with_none(available_height, self.height)
width, height = self._text_layouter.get_dimensions(available_width, available_height)
height -= self._line_spacing # no spacing after the last line
width, height = self.text_layouter.get_dimensions(available_width, available_height)
height -= self.line_spacing # no spacing after the last line
if self._width is not None:
width = self._width
if self._height is not None:
height = self._height
if self.width is not None:
width = self.width
if self.height is not None:
height = self.height
width = max_with_none(width, self._min_width)
height = max_with_none(height, self._min_height)
width = min_with_none(width, self._max_width)
height = min_with_none(height, self._max_height)
width = max_with_none(width, self.min_width)
height = max_with_none(height, self.min_height)
width = min_with_none(width, self.max_width)
height = min_with_none(height, self.max_height)
return width, height
@ -86,21 +86,21 @@ class Text(Layout):
x1, y1, x2, y2 = rect
width, height = x2 - x1 + 1, y2 - y1 + 1
if self._max_width is not None:
width = min(width, self._max_width)
if self._min_width is not None:
width = max(width, self._min_width)
if self._max_height is not None:
height = min(height, self._max_height)
if self._min_height is not None:
height = max(height, self._min_height)
if self.max_width is not None:
width = min(width, self.max_width)
if self.min_width is not None:
width = max(width, self.min_width)
if self.max_height is not None:
height = min(height, self.max_height)
if self.min_height is not None:
height = max(height, self.min_height)
lines = self._text_layouter.get_lines(width, height)
lines = self.text_layouter.get_lines(width, height)
color = self.get_fg_color()
font = self.get_font()
for posx, posy, text in lines:
d.text((x1 + posx, y1 + posy), text, font=font, fill=color)
return image
def _get_line_height_with_spacing(self):
return get_line_height(self.get_font()) + self._line_spacing
def get_line_height_with_spacing(self):
return get_line_height(self.get_font()) + self.line_spacing