diff --git a/elsie/box.py b/elsie/box.py index fb5224b5..7fb13803 100644 --- a/elsie/box.py +++ b/elsie/box.py @@ -1,8 +1,11 @@ import logging import lxml.etree as et +import base64 +from PIL import Image +import io -from .draw import draw_text +from .draw import draw_text, draw_bitmap from .geom import Rect from .highlight import highlight_code from .image import get_image_steps, create_image_data @@ -45,6 +48,19 @@ def set_paint_style(xml, color, bg_color, stroke_width, stroke_dasharray): xml.set("style", ";".join(styles)) +def scaler(rect, image_width, image_height): + scale_x = rect.width / image_width + scale_y = rect.height / image_height + + if rect.width and rect.height: + return min(scale_x, scale_y) + elif rect.width: + return scale_x + elif rect.height: + return scale_y + return None + + class Box: def __init__(self, @@ -268,6 +284,46 @@ def _render_svg(self, ctx, x, y, scale, data): ctx.xml.close() def image(self, filename, scale=None, fragments=True, show_begin=1): + """ Draw an svg/png/jpeg image, detect by extension """ + if filename.endswith("svg"): + self.image_svg(filename, scale, fragments, show_begin) + elif any(filename.endswith(ext) for ext in [".png", ".jpeg", ".jpg"]): + self._image_bitmap(filename, scale) + else: + raise Exception("Unkown image extension") + return self + + def _image_bitmap(self, filename, scale): + + with open(filename, "rb") as f: + data = f.read() + + img = Image.open(io.BytesIO(data)) + mime = Image.MIME[img.format] + image_width, image_height = img.size + del img + + data = base64.b64encode(data).decode("ascii") + + def draw(ctx, rect): + if scale is None: + s = scaler(rect, image_width, image_height) + if s is None: + logging.warning( + "Scale of image {} is 0, set scale explicitly or set at least one " + "dimension for the parent box".format(filename)) + else: + s = scale + + w = image_width * s + h = image_height * s + x = rect.x + (rect.width - w) / 2 + y = rect.y + (rect.height - h) / 2 + draw_bitmap(ctx.xml, x, y, w, h, mime, data) + + self.add_child(draw) + + def _image_svg(self, filename, scale=None, fragments=True, show_begin=1): """ Draw an svg image """ root = et.parse(filename).getroot() @@ -298,17 +354,8 @@ def draw(ctx, rect): data = image_data if scale is None: - scale_x = rect.width / image_width - scale_y = rect.height / image_height - - s = 0 - if rect.width and rect.height: - s = min(scale_x, scale_y) - elif rect.width: - s = scale_x - elif rect.height: - s = scale_y - else: + s = scaler(rect, image_width, image_height) + if s is None: logging.warning( "Scale of image {} is 0, set scale explicitly or set at least one " "dimension for the parent box".format(filename)) @@ -323,8 +370,6 @@ def draw(ctx, rect): self.add_child(draw) - return self - def code(self, language, text, tabsize=4, line_numbers=False, style="code"): """ Draw a code with syntax highlighting """ diff --git a/elsie/draw.py b/elsie/draw.py index 3e5c8076..c1614687 100644 --- a/elsie/draw.py +++ b/elsie/draw.py @@ -67,3 +67,13 @@ def draw_text(xml, x, y, parsed_text, style, styles, id=None): for s in active_styles: xml.close("tspan") xml.close("text") + + +def draw_bitmap(xml, x, y, width, height, mime, data): + xml.element("image") + xml.set("x", x) + xml.set("y", y) + xml.set("width", width) + xml.set("height", height) + xml.set("xlink:href", "data:{};base64,{}".format(mime, data), escape=False) + xml.close("image") \ No newline at end of file diff --git a/elsie/image.py b/elsie/image.py index a6910a1d..1d25bb69 100644 --- a/elsie/image.py +++ b/elsie/image.py @@ -36,4 +36,4 @@ def find_hidden_elements(element): find_hidden_elements(root) for element, child in hidden: element.remove(child) - return et.tostring(root).decode() + return et.tostring(root).decode() \ No newline at end of file diff --git a/elsie/sxml.py b/elsie/sxml.py index 2e9b8ab6..995c438f 100644 --- a/elsie/sxml.py +++ b/elsie/sxml.py @@ -23,9 +23,10 @@ def element(self, name): self.stack.append(name) self.is_open = True - def set(self, name, value): + def set(self, name, value, escape=True): assert self.is_open - value = str(value).replace("'", "\\'") + if escape: + value = str(value).replace("'", "\\'") self.chunks.append(" {}='{}'".format(name, escape_text(value))) def text(self, text): diff --git a/tests/data/imgs/test.jpeg b/tests/data/imgs/test.jpeg new file mode 100644 index 00000000..6096e86d Binary files /dev/null and b/tests/data/imgs/test.jpeg differ diff --git a/tests/data/imgs/test.png b/tests/data/imgs/test.png new file mode 100644 index 00000000..854d7637 Binary files /dev/null and b/tests/data/imgs/test.png differ diff --git a/tests/test_image.py b/tests/test_image.py index c042c795..9de6a631 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -65,3 +65,22 @@ def test_image_scale_width_height(test_env): def test_image_scale_no_dimensions(test_env): test_env.slide.box().image(test_env.data_path("scale.svg")) test_env.check("scale-no-dimensions") + +def test_image_bitmap(test_env): + slide = test_env.slide + img_png = test_env.data_path("imgs/test.png") + img_jpg = test_env.data_path("imgs/test.jpeg") + + b = slide.fbox(height=140) + b.rect(bg_color="green") + b.image(img_png) + + b = slide.fbox(height=300) + b.rect(bg_color="blue") + b.image(img_png) + + b = slide.fbox(height=300) + b.rect(bg_color="green") + b.image(img_jpg) + + test_env.check("image-bitmap", 1)