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 **kwargs
): ):
super().__init__(**kwargs) super().__init__(**kwargs)
self._title = title self.title = title
self._title_font = title_font self.title_font = title_font
self._title_color = title_color self.title_color = title_color
self._title_anchor = title_anchor self.title_anchor = title_anchor
self._title_position = title_position self.title_position = title_position
self._title_padding = title_padding self.title_padding = title_padding
self._content = content self.content = content
def _children(self): def children(self):
if self._content is not None: if self.content is not None:
return [self._content] return [self.content]
else: else:
return [] return []
def _get_title_font(self): def get_title_font(self):
if self._title_font is not None: if self.title_font is not None:
return self._title_font return self.title_font
else: else:
return self.get_font() return self.get_font()
def _get_title_color(self): def get_title_color(self):
if self._title_color is not None: if self.title_color is not None:
return self._title_color return self.title_color
else: else:
return self.get_fg_color() return self.get_fg_color()
def get_min_inner_width(self, max_height=None): def get_min_inner_width(self, max_height=None):
if self._content is None: if self.content is None:
return 0 return 0
else: 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): def get_min_inner_height(self, max_width=None):
if self._content is None: if self.content is None:
return 0 return 0
else: 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): 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 return 0, 0
else: 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): def render_content(self, rect):
if self._content is None: if self.content is None:
return self.make_canvas() return self.make_canvas()
else: else:
return self._content.render(rect) return self.content.render(rect)
def _title_pos(self, rect): def title_pos(self, rect):
left, top, right, bottom = self._get_title_font().getbbox(self._title) left, top, right, bottom = self.get_title_font().getbbox(self.title)
height = bottom - top height = bottom - top
width = right - left 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] available_width = rect[2] - rect[0] + 1 - border_radius[1] - border_radius[0]
pos_x = rect[0] + border_radius[0] + self._title_position pos_x = rect[0] + border_radius[0] + self.title_position
if self._title_anchor == BoxTitleAnchor.LEFT: if self.title_anchor == BoxTitleAnchor.LEFT:
pos_x += 0 pos_x += 0
elif self._title_anchor == BoxTitleAnchor.CENTER: elif self.title_anchor == BoxTitleAnchor.CENTER:
pos_x += (available_width - width) / 2 pos_x += (available_width - width) / 2
elif self._title_anchor == BoxTitleAnchor.RIGHT: elif self.title_anchor == BoxTitleAnchor.RIGHT:
pos_x += available_width - width pos_x += available_width - width
pos_y = rect[1] - height / 2 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): def modify_border_mask(self, border_mask, rect):
if self._title is None: if self.title is None:
return 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 = ImageDraw.Draw(border_mask)
d.rectangle((x + left, y + top, x + right, y + bottom), fill=0) d.rectangle((x + left, y + top, x + right, y + bottom), fill=0)
def render_after_border(self, image, rect): def render_after_border(self, image, rect):
if self._title is None: if self.title is None:
return return
(x, y), _ = self._title_pos(rect) (x, y), _ = self.title_pos(rect)
d = ImageDraw.Draw(image) 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 = [] contents = []
self._contents = contents self._contents = contents
def _children(self): def children(self):
return self._contents return self._contents
def get_min_inner_width(self, max_height=None): def get_min_inner_width(self, max_height=None):
min_width = 0 min_width = 0
for c in self._contents: for c in self._contents:
width = c.get_min_outer_width(max_height) width = c.get_min_outer_width(max_height)
if c._left is not None: if c.left is not None:
width += c._left width += c.left
if c._right is not None: if c.right is not None:
width += c._right width += c.right
min_width = max(min_width, width) min_width = max(min_width, width)
if self._min_width is not None: if self.min_width is not None:
min_width = max(min_width, self._min_width) min_width = max(min_width, self.min_width)
return min_width return min_width
def get_min_inner_height(self, max_width=None): def get_min_inner_height(self, max_width=None):
@ -38,17 +38,17 @@ class Container(Layout):
min_height_absolute = 0 min_height_absolute = 0
for c in self._contents: for c in self._contents:
height = c.get_min_outer_height(max_width) 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 min_height_automatic += height
else: else:
if c._top is not None: if c.top is not None:
height += c._top height += c.top
if c._bottom is not None: if c.bottom is not None:
height += c._bottom height += c.bottom
min_height_absolute = max(min_height_absolute, height) min_height_absolute = max(min_height_absolute, height)
min_height = max(min_height_automatic, min_height_absolute) min_height = max(min_height_automatic, min_height_absolute)
if self._min_height is not None: if self.min_height is not None:
min_height = max(min_height, self._min_height) min_height = max(min_height, self.min_height)
return min_height return min_height
def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None): 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: for c in self._contents:
w, h = c.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height) w, h = c.get_ideal_outer_dimensions(min_width, min_height, available_width, available_height)
if c._left is not None: if c.left is not None:
w += c._left w += c.left
if c._right is not None: if c.right is not None:
w += c._right w += c.right
if c._top is not None: if c.top is not None:
h += c._top h += c.top
if c._bottom is not None: if c.bottom is not None:
h += c._bottom h += c.bottom
width = max(width, w) 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 height_automatic += h
else: else:
height_absolute = max(height_absolute, h) height_absolute = max(height_absolute, h)
height = max(height_automatic, height_absolute) height = max(height_automatic, height_absolute)
if self._width is not None: if self.width is not None:
width = self._width width = self.width
if self._height is not None: if self.height is not None:
height = self._height height = self.height
return width, height return width, height
def render_content(self, rect): def render_content(self, rect):
@ -83,29 +83,29 @@ class Container(Layout):
for c in self._contents: for c in self._contents:
soft_max_width, soft_max_height = x2 - x1 + 1, y2 - y1 + 1 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: if c.left is not None:
soft_max_width -= c._left soft_max_width -= c.left
if c._right is not None: if c.right is not None:
soft_max_width -= c._right soft_max_width -= c.right
if c._top is not None: if c.top is not None:
soft_max_height -= c._top soft_max_height -= c.top
if c._bottom is not None: if c.bottom is not None:
soft_max_height -= c._bottom soft_max_height -= c.bottom
hard_max_width, hard_max_height = c._max_width, c._max_height hard_max_width, hard_max_height = c.max_width, c.max_height
if c._left is not None and c._right is not None: 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) 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: 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_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) 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: if c.left is not None and c.right is not None:
width = (x2 - c._right) - (x1 + c._left) + 1 width = (x2 - c.right) - (x1 + c.left) + 1
if c._top is not None and c._bottom is not None: if c.top is not None and c.bottom is not None:
height = (y2 - c._bottom) - (y1 + c._top) + 1 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) 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) width = max_with_none(width, min_width)
@ -115,26 +115,26 @@ class Container(Layout):
height = min_with_none(height, hard_max_height) height = min_with_none(height, hard_max_height)
if is_absolute: if is_absolute:
if c._left is None: if c.left is None:
if c._right is None: if c.right is None:
cx1 = x1 cx1 = x1
cx2 = cx1 + width - 1 cx2 = cx1 + width - 1
else: else:
cx2 = x2 - c._right cx2 = x2 - c.right
cx1 = cx2 - width + 1 cx1 = cx2 - width + 1
else: else:
cx1 = x1 + c._left cx1 = x1 + c.left
cx2 = cx1 + width - 1 cx2 = cx1 + width - 1
if c._top is None: if c.top is None:
if c._bottom is None: if c.bottom is None:
cy1 = y1 cy1 = y1
cy2 = cy1 + height - 1 cy2 = cy1 + height - 1
else: else:
cy2 = y2 - c._bottom cy2 = y2 - c.bottom
cy1 = cy2 - height + 1 cy1 = cy2 - height + 1
else: else:
cy1 = y1 + c._top cy1 = y1 + c.top
cy2 = cy1 + height - 1 cy2 = cy1 + height - 1
else: else:
cx1 = x1 cx1 = x1

View file

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

44
flex.py
View file

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

202
image.py
View file

@ -19,69 +19,69 @@ class Image(Layout):
**kwargs **kwargs
): ):
super().__init__(**kwargs) super().__init__(**kwargs)
self._image = image self.image = image
self._mode = mode self.mode = mode
self._scale = scale self.scale = scale
self._position = position self.position = position
self._offset_x = offset_x self.offset_x = offset_x
self._offset_y = offset_y self.offset_y = offset_y
def scale(self): def get_scale(self):
if isinstance(self._scale, (tuple, list)): if isinstance(self.scale, (tuple, list)):
if len(self._scale) == 0: if len(self.scale) == 0:
return 1, 1 return 1, 1
elif len(self._scale) == 1: elif len(self.scale) == 1:
return self._scale[0], self._scale[0] return self.scale[0], self.scale[0]
else: else:
return self._scale[0], self._scale[1] return self.scale[0], self.scale[1]
elif isinstance(self._scale, (int, float)): elif isinstance(self.scale, (int, float)):
return self._scale, self._scale return self.scale, self.scale
else: else:
return 1, 1 return 1, 1
def _get_image_width(self): def get_image_width(self):
if self._image is None: if self.image is None:
return 0 return 0
return self._image.size[0] return self.image.size[0]
def _get_image_height(self): def get_image_height(self):
if self._image is None: if self.image is None:
return 0 return 0
return self._image.size[1] return self.image.size[1]
def get_min_inner_width(self, max_height=None): def get_min_inner_width(self, max_height=None):
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self._get_image_width() width = self.get_image_width()
else: else:
width = 0 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): def get_min_inner_height(self, max_width=None):
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self._get_image_height() height = self.get_image_height()
else: else:
height = 0 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): def get_ideal_inner_dimensions(self, min_width=None, min_height=None, available_width=None, available_height=None):
width, height = 0, 0 width, height = 0, 0
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_Y, ImageMode.STRETCH_Y):
width = self._get_image_width() width = self.get_image_width()
if self._mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X): if self.mode in (ImageMode.ORIGINAL, ImageMode.REPEAT_X, ImageMode.STRETCH_X):
height = self._get_image_height() height = self.get_image_height()
if self._width is not None: if self.width is not None:
width = self._width width = self.width
if self._height is not None: if self.height is not None:
height = self._height height = self.height
width = min_with_none(width, available_width, self._max_width) width = min_with_none(width, available_width, self.max_width)
height = min_with_none(height, available_height, self._max_height) height = min_with_none(height, available_height, self.max_height)
width = max_with_none(width, min_width, self._min_width) width = max_with_none(width, min_width, self.min_width)
height = max_with_none(height, min_height, self._min_height) height = max_with_none(height, min_height, self.min_height)
return width, height return width, height
def render_content(self, rect): def render_content(self, rect):
image = self.make_canvas() image = self.make_canvas()
if self._image is None: if self.image is None:
return image return image
x1, y1, x2, y2 = rect x1, y1, x2, y2 = rect
@ -91,24 +91,24 @@ class Image(Layout):
if width == 0 or height == 0: if width == 0 or height == 0:
return image return image
image_width = int(self._image.size[0] * self.scale()[0]) image_width = int(self.image.size[0] * self.get_scale()[0])
image_height = int(self._image.size[1] * self.scale()[1]) image_height = int(self.image.size[1] * self.get_scale()[1])
offset_x = self._offset_x offset_x = self.offset_x
offset_y = self._offset_y offset_y = self.offset_y
if self._mode == ImageMode.ORIGINAL: if self.mode == ImageMode.ORIGINAL:
if image_width != self._image.size[0] or image_height != self._image.size[1]: 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) img = self.image.resize((image_width, image_height), reducing_gap=3.0)
else: else:
img = self._image img = self.image
elif self._mode == ImageMode.STRETCH: elif self.mode == ImageMode.STRETCH:
img = self._image.resize((width, height), reducing_gap=3.0) img = self.image.resize((width, height), reducing_gap=3.0)
elif self._mode == ImageMode.STRETCH_X: elif self.mode == ImageMode.STRETCH_X:
img = self._image.resize((width, image_height), reducing_gap=3.0) img = self.image.resize((width, image_height), reducing_gap=3.0)
elif self._mode == ImageMode.STRETCH_Y: elif self.mode == ImageMode.STRETCH_Y:
img = self._image.resize((image_width, height), reducing_gap=3.0) img = self.image.resize((image_width, height), reducing_gap=3.0)
elif self._mode == ImageMode.CONTAIN: elif self.mode == ImageMode.CONTAIN:
ratio_width = width / image_width ratio_width = width / image_width
ratio_height = height / image_height ratio_height = height / image_height
if ratio_width < ratio_height: if ratio_width < ratio_height:
@ -120,8 +120,8 @@ class Image(Layout):
else: else:
w = width w = width
h = height h = height
img = self._image.resize((w, h), reducing_gap=3.0) img = self.image.resize((w, h), reducing_gap=3.0)
elif self._mode == ImageMode.COVER: elif self.mode == ImageMode.COVER:
ratio_width = width / image_width ratio_width = width / image_width
ratio_height = height / image_height ratio_height = height / image_height
if ratio_width > ratio_height: if ratio_width > ratio_height:
@ -133,42 +133,42 @@ class Image(Layout):
else: else:
w = width w = width
h = height 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_width = int(image_width * height / image_height)
repeat_height = height repeat_height = height
elif self._mode == ImageMode.REPEAT_Y_FILL: elif self.mode == ImageMode.REPEAT_Y_FILL:
repeat_width = width repeat_width = width
repeat_height = int(image_height * width / image_width) repeat_height = int(image_height * width / image_width)
else: else:
repeat_width = image_width repeat_width = image_width
repeat_height = image_height repeat_height = image_height
if self._mode in (ImageMode.REPEAT, ImageMode.REPEAT_X, ImageMode.REPEAT_X_FILL, ImageMode.REPEAT_X_STRETCH): 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.position in (ImageAnchor.TOP_LEFT, ImageAnchor.MIDDLE_LEFT, ImageAnchor.BOTTOM_LEFT):
repeat_offset_x = offset_x 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 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 repeat_offset_x = width - repeat_width + offset_x
else: else:
raise Exception('invalid image position') raise Exception('invalid image position')
if self._mode in (ImageMode.REPEAT, ImageMode.REPEAT_Y, ImageMode.REPEAT_Y_FILL, ImageMode.REPEAT_Y_STRETCH): 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.position in (ImageAnchor.TOP_LEFT, ImageAnchor.TOP_CENTER, ImageAnchor.TOP_RIGHT):
repeat_offset_y = offset_y 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 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 repeat_offset_y = height - repeat_height + offset_y
else: else:
raise Exception('invalid image position') raise Exception('invalid image position')
if self._mode == ImageMode.REPEAT: if self.mode == ImageMode.REPEAT:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0)) img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1 num_x = width // repeat_width + 1
num_y = height // repeat_height + 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)) img.paste(img_part, (i * repeat_width + ox, j * repeat_height + oy))
offset_x = 0 offset_x = 0
offset_y = 0 offset_y = 0
elif self._mode == ImageMode.REPEAT_X: elif self.mode == ImageMode.REPEAT_X:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, repeat_height), (0,0,0,0)) img = PilImage.new('RGBA', (width, repeat_height), (0,0,0,0))
num_x = width // repeat_width + 1 num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width) ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width)
for i in range(-1, num_x): for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0)) img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0 offset_x = 0
elif self._mode == ImageMode.REPEAT_Y: elif self.mode == ImageMode.REPEAT_Y:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (repeat_width, height), (0,0,0,0)) img = PilImage.new('RGBA', (repeat_width, height), (0,0,0,0))
num_y = height // repeat_height + 1 num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height)
for i in range(-1, num_y): for i in range(-1, num_y):
img.paste(img_part, (0, i * repeat_height + oy)) img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0 offset_y = 0
elif self._mode == ImageMode.REPEAT_X_STRETCH: elif self.mode == ImageMode.REPEAT_X_STRETCH:
if repeat_width != self._image.size[0] or height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0)) img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1 num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width) ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -repeat_width)
for i in range(-1, num_x): for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0)) img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0 offset_x = 0
elif self._mode == ImageMode.REPEAT_Y_STRETCH: elif self.mode == ImageMode.REPEAT_Y_STRETCH:
if width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0)) img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_y = height // repeat_height + 1 num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height)
for i in range(-1, num_y): for i in range(-1, num_y):
img.paste(img_part, (0, i * repeat_height + oy)) img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0 offset_y = 0
elif self._mode == ImageMode.REPEAT_X_FILL: elif self.mode == ImageMode.REPEAT_X_FILL:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0)) img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_x = width // repeat_width + 1 num_x = width // repeat_width + 1
ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -w) ox = repeat_offset_x % (repeat_width if repeat_offset_x >= 0 else -w)
for i in range(-1, num_x): for i in range(-1, num_x):
img.paste(img_part, (i * repeat_width + ox, 0)) img.paste(img_part, (i * repeat_width + ox, 0))
offset_x = 0 offset_x = 0
elif self._mode == ImageMode.REPEAT_Y_FILL: elif self.mode == ImageMode.REPEAT_Y_FILL:
if repeat_width != self._image.size[0] or repeat_height != self._image.size[1]: 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) img_part = self.image.resize((repeat_width, repeat_height), reducing_gap=3.0)
else: else:
img_part = self._image img_part = self.image
img = PilImage.new('RGBA', (width, height), (0,0,0,0)) img = PilImage.new('RGBA', (width, height), (0,0,0,0))
num_y = height // repeat_height + 1 num_y = height // repeat_height + 1
oy = repeat_offset_y % (repeat_height if repeat_offset_y >= 0 else -repeat_height) 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)) img.paste(img_part, (0, i * repeat_height + oy))
offset_y = 0 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 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 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 x = x1 + width - img.size[0] + offset_x
else: else:
raise Exception('invalid image position') 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 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 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 y = y1 + height - img.size[1] + offset_y
else: else:
raise Exception('invalid image position') 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 from .helpers import min_with_none, max_with_none
class FlexLineItem(): class FlexLineItem:
def __init__(self, content, max_width, max_height, direction): def __init__(self, content, max_width, max_height, direction):
self.__content = content self.__content = content
self.__max_width = max_width self.__max_width = max_width
@ -20,14 +20,14 @@ class FlexLineItem():
def base_size(self): def base_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE): if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
if self.__content._width is not None: if self.__content.width is not None:
return self.__content._width return self.__content.width
else: else:
return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width, return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width,
available_height=self.__max_height)[0] available_height=self.__max_height)[0]
else: else:
if self.__content._height is not None: if self.__content.height is not None:
return self.__content._height return self.__content.height
else: else:
return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width, return self.__content.get_ideal_outer_dimensions(available_width=self.__max_width,
available_height=self.__max_height)[1] available_height=self.__max_height)[1]
@ -46,15 +46,15 @@ class FlexLineItem():
def max_main_size(self): def max_main_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE): if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
return self.__content._max_width return self.__content.max_width
else: else:
return self.__content._max_height return self.__content.max_height
def max_cross_size(self): def max_cross_size(self):
if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE): if self.__direction in (FlexDirection.ROW, FlexDirection.ROW_REVERSE):
return self.__content._max_height return self.__content.max_height
else: else:
return self.__content._max_width return self.__content.max_width
def hypothetical_main_size(self): def hypothetical_main_size(self):
return min_with_none(self.max_main_size(), max_with_none(self.min_main_size(), self.base_size())) 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 return self.__cross_pos
def grow_factor(self): def grow_factor(self):
return self.__content._flex_grow return self.__content.flex_grow
def shrink_factor(self): def shrink_factor(self):
return self.__content._flex_shrink return self.__content.flex_shrink
def scaled_shrink_factor(self): def scaled_shrink_factor(self):
return self.shrink_factor() * self.base_size() return self.shrink_factor() * self.base_size()

124
layout.py
View file

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

98
text.py
View file

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