Skip to content

Commit

Permalink
Added support for passing <svg> as BytesIO to FPDF.image()
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Feb 7, 2022
1 parent 356c572 commit b983b3e
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 34 deletions.
18 changes: 15 additions & 3 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from functools import wraps
from pathlib import Path
from typing import Callable, NamedTuple, Optional, Union, List
from xml.etree.ElementTree import ParseError, XML

from PIL import Image

Expand Down Expand Up @@ -2726,12 +2727,15 @@ def image(
)
if str(name).endswith(".svg"):
# Insert it as a PDF path:
return self._vector_image(name, x, y, w, h, link, title, alt_text)
img = load_image(str(name))
return self._vector_image(img, x, y, w, h, link, title, alt_text)
if isinstance(name, str):
img = None
elif isinstance(name, Image.Image):
name, img = hashlib.md5(name.tobytes()).hexdigest(), name
elif isinstance(name, io.BytesIO):
if _is_xml(name):
return self._vector_image(name, x, y, w, h, link, title, alt_text)
name, img = hashlib.md5(name.getvalue()).hexdigest(), name
else:
name, img = str(name), name
Expand Down Expand Up @@ -2782,7 +2786,7 @@ def image(

def _vector_image(
self,
filepath,
img: io.BytesIO,
x=None,
y=None,
w=0,
Expand All @@ -2791,7 +2795,7 @@ def _vector_image(
title=None,
alt_text=None,
):
svg = SVGObject.from_file(filepath)
svg = SVGObject(img.getvalue())
if w == 0 and h == 0:
if not svg.width or not svg.height:
raise ValueError(
Expand Down Expand Up @@ -4178,6 +4182,14 @@ def _sizeof_fmt(num, suffix="B"):
return f"{num:.1f}Yi{suffix}"


def _is_xml(img: io.BytesIO):
try:
XML(img.getvalue())
return True
except ParseError:
return False


sys.modules[__name__].__class__ = WarnOnDeprecatedModuleAttributes


Expand Down
4 changes: 2 additions & 2 deletions fpdf/image_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def load_image(filename):
"""
This method is used to load external resources, such as images.
It is automatically called when resource added to document by `FPDF.image()`.
It always return a BytesIO buffer.
"""
# if a bytesio instance is passed in, use it as is.
if isinstance(filename, BytesIO):
Expand All @@ -33,8 +34,7 @@ def _decode_base64_image(base64Image):
"Decode the base 64 image string into an io byte stream."
imageData = base64Image.split("base64,")[1]
decodedData = base64.b64decode(imageData)
imageBytes = BytesIO(decodedData)
return imageBytes
return BytesIO(decodedData)


def get_img_info(img, image_filter="AUTO", dims=None):
Expand Down
39 changes: 17 additions & 22 deletions test/image/image_types/test_insert_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ def test_insert_jpg(tmp_path):
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
file_path = HERE / "insert_images_insert_jpg.jpg"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(HERE / "insert_images_insert_jpg.jpg", x=15, y=15, h=140)
if sys.platform in ("cygwin", "win32"):
# Pillow uses libjpeg-turbo on Windows and libjpeg elsewhere,
# leading to a slightly different image being parsed and included in the PDF:
Expand All @@ -34,8 +33,7 @@ def test_insert_jpg_jpxdecode(tmp_path):
pdf.compress = False
pdf.set_image_filter("JPXDecode")
pdf.add_page()
file_path = HERE / "insert_images_insert_jpg.jpg"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(HERE / "insert_images_insert_jpg.jpg", x=15, y=15, h=140)
assert_pdf_equal(pdf, HERE / "image_types_insert_jpg_jpxdecode.pdf", tmp_path)


Expand All @@ -44,8 +42,7 @@ def test_insert_jpg_flatedecode(tmp_path):
pdf.compress = False
pdf.set_image_filter("FlateDecode")
pdf.add_page()
file_path = HERE / "insert_images_insert_jpg.jpg"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(HERE / "insert_images_insert_jpg.jpg", x=15, y=15, h=140)
if sys.platform in ("cygwin", "win32"):
# Pillow uses libjpeg-turbo on Windows and libjpeg elsewhere,
# leading to a slightly different image being parsed and included in the PDF:
Expand All @@ -60,8 +57,7 @@ def test_insert_png(tmp_path):
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
file_path = HERE / "insert_images_insert_png.png"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(HERE / "insert_images_insert_png.png", x=15, y=15, h=140)
assert_pdf_equal(pdf, HERE / "image_types_insert_png.pdf", tmp_path)


Expand All @@ -71,8 +67,9 @@ def test_insert_png_alpha(tmp_path):
pdf.add_page()
pdf.set_font("Helvetica", size=30)
pdf.cell(w=pdf.epw, h=30, txt="BEHIND")
file_path = HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png"
pdf.image(file_path, x=25, y=0, h=40)
pdf.image(
HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png", x=25, y=0, h=40
)
assert_pdf_equal(pdf, HERE / "image_types_insert_png_alpha.pdf", tmp_path)


Expand All @@ -82,8 +79,9 @@ def test_insert_png_disallow_transparency(tmp_path):
pdf.add_page()
pdf.set_font("Helvetica", size=30)
pdf.cell(w=pdf.epw, h=30, txt="BEHIND")
file_path = HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png"
pdf.image(file_path, x=25, y=0, h=40)
pdf.image(
HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png", x=25, y=0, h=40
)
assert_pdf_equal(
pdf, HERE / "image_types_insert_png_disallow_transparency.pdf", tmp_path
)
Expand All @@ -94,8 +92,9 @@ def test_insert_png_alpha_dctdecode(tmp_path):
pdf.compress = False
pdf.set_image_filter("DCTDecode")
pdf.add_page()
file_path = HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(
HERE / "../png_images/ba2b2b6e72ca0e4683bb640e2d5572f8.png", x=15, y=15, h=140
)
if sys.platform in ("cygwin", "win32"):
# Pillow uses libjpeg-turbo on Windows and libjpeg elsewhere,
# leading to a slightly different image being parsed and included in the PDF:
Expand All @@ -112,25 +111,22 @@ def test_insert_bmp(tmp_path):
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
file_path = HERE / "circle.bmp"
pdf.image(file_path, x=15, y=15, h=140)
pdf.image(HERE / "circle.bmp", x=15, y=15, h=140)
assert_pdf_equal(pdf, HERE / "image_types_insert_bmp.pdf", tmp_path)


def test_insert_gif(tmp_path):
pdf = fpdf.FPDF()
pdf.compress = False
pdf.add_page()
file_path = HERE / "circle.gif"
pdf.image(file_path, x=15, y=15)
pdf.image(HERE / "circle.gif", x=15, y=15)
assert_pdf_equal(pdf, HERE / "image_types_insert_gif.pdf", tmp_path)


def test_insert_pillow(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
file_path = HERE / "insert_images_insert_png.png"
img = Image.open(file_path)
img = Image.open(HERE / "insert_images_insert_png.png")
pdf.image(img, x=15, y=15, h=140)
assert_pdf_equal(pdf, HERE / "image_types_insert_png.pdf", tmp_path)

Expand All @@ -150,8 +146,7 @@ def test_insert_pillow_issue_139(tmp_path):
def test_insert_bytesio(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
file_path = HERE / "insert_images_insert_png.png"
img = Image.open(file_path)
img = Image.open(HERE / "insert_images_insert_png.png")
img_bytes = io.BytesIO()
img.save(img_bytes, "PNG")
pdf.image(img_bytes, x=15, y=15, h=140)
Expand Down
Binary file added test/image/svg_image_from_bytesio.pdf
Binary file not shown.
7 changes: 0 additions & 7 deletions test/image/test_load_image.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import binascii
from io import BytesIO
from pathlib import Path

import pytest
Expand All @@ -12,12 +11,6 @@
HERE = Path(__file__).resolve().parent


def test_recognize_bytesIO():
s = BytesIO()
a = fpdf.image_parsing.load_image(s)
assert a == s


def test_load_text_file():
file = HERE / "__init__.py"
contents = '"""This package contains image tests"""\n'
Expand Down
12 changes: 12 additions & 0 deletions test/image/test_vector_image.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from io import BytesIO
from pathlib import Path

import pytest
Expand Down Expand Up @@ -42,3 +43,14 @@ def test_svg_image_fixed_dimensions(tmp_path):
pdf.add_page()
pdf.image(HERE / "../svg/svg_sources/SVG_logo_fixed_dimensions.svg")
assert_pdf_equal(pdf, HERE / "svg_image_fixed_dimensions.pdf", tmp_path)


def test_svg_image_from_bytesio(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
pdf.image(
BytesIO(
b'<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg"><rect x="60" y="60" width="60" height="60"/></svg>'
)
)
assert_pdf_equal(pdf, HERE / "svg_image_from_bytesio.pdf", tmp_path)

0 comments on commit b983b3e

Please sign in to comment.