Commit 9cd40d51 authored by Ingo Heimbach's avatar Ingo Heimbach

Added a LaTeX output mode for the `embedded-image` directive

SVG files can now be used in LaTeX documents with the `embedded-image`
directive. SVGs are converted to PDFs with `rsvg-convert`.
parent a26db80c
from .code_directive import CodeBlock
from .embedded_image_directive import EmbeddedImage
from .tikz_directive import Tikz, TikzFigure, set_output_mode
from .tikz_directive import Tikz, TikzFigure
from .output_mode import get_output_mode, set_output_mode
__all__ = ("CodeBlock", "EmbeddedImage", "Tikz", "TikzFigure", "set_output_mode")
__all__ = ("CodeBlock", "EmbeddedImage", "Tikz", "TikzFigure", "get_output_mode", "set_output_mode")
# -*- coding: utf-8 -*-
import base64
import requests
import os
import shutil
import subprocess
import tempfile
import urlparse
import sys
from io import BytesIO
......@@ -9,31 +15,82 @@ from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives, states
from docutils.nodes import fully_normalize_name, whitespace_normalize_name
from docutils.parsers.rst.roles import set_classes
from .output_mode import get_output_mode
TMP_IMAGE_FILENAME = "_downloaded_image{i:04d}.{ext}"
OUT_PDF_FILENAME = "_converted_image{i:04d}.pdf"
def is_url(url):
return urlparse.urlparse(url).scheme not in ("", "file")
def convert_image_to_pillow_image_and_html_base64(image_filepath_or_url):
def load_and_convert_image(image_filepath_or_url, convert_to_base64=True, convert_svg_to_pdf=False):
self = load_and_convert_image
if not hasattr(self, "image_number"):
self.image_number = 1
image = None
image_content = None
image_filepath = None
image_is_pdf = None
image_is_svg = image_filepath_or_url.lower().endswith(".svg")
if is_url(image_filepath_or_url):
image_is_remote = is_url(image_filepath_or_url)
if image_is_svg and convert_svg_to_pdf and (image_is_remote or convert_to_base64):
tmp_dir = tempfile.mkdtemp()
else:
tmp_dir = None
if image_is_remote:
image_url = image_filepath_or_url
response = requests.get(image_url)
response.raise_for_status()
image_content = response.content
if not convert_to_base64 or (image_is_svg and convert_svg_to_pdf):
image_filepath = TMP_IMAGE_FILENAME.format(i=self.image_number, ext=os.path.splitext(image_url)[1])
if tmp_dir is not None:
image_filepath = os.path.join(tmp_dir, image_filepath)
with open(image_filepath.encode(sys.getfilesystemencoding()), "wb") as image_file:
image_file.write(image_content)
else:
image_filepath = image_filepath_or_url
with open(image_filepath.encode(sys.getfilesystemencoding()), "rb") as image_file:
image_content = image_file.read()
if image_is_svg:
image = None
image_format = "svg+xml"
else:
try:
if image_is_svg and convert_svg_to_pdf:
image_content = None
out_pdf_filename = OUT_PDF_FILENAME.format(i=self.image_number)
if convert_to_base64:
out_pdf_filename = os.path.join(tmp_dir, out_pdf_filename)
with open(os.devnull, "w") as devnull:
subprocess.check_call(
["rsvg-convert", "-f", "pdf", "-o", out_pdf_filename, image_filepath],
stdout=devnull,
stderr=devnull,
)
image_filepath = out_pdf_filename
image_is_svg = False
image_is_pdf = True
if image_content is None:
with open(image_filepath.encode(sys.getfilesystemencoding()), "rb") as image_file:
image_content = image_file.read()
finally:
if tmp_dir is not None:
shutil.rmtree(tmp_dir)
tmp_dir = None
try:
image = Image.open(BytesIO(image_content))
image_format = image.format.lower()
encoded_string = "data:image/{format};base64,".format(format=image_format) + base64.b64encode(image_content)
return image, encoded_string
except IOError:
if image_is_svg:
image_format = "svg+xml"
elif image_is_pdf:
image_format = "pdf"
else:
image_format = "unknown"
if convert_to_base64:
image_reference = "data:image/{format};base64,".format(format=image_format) + base64.b64encode(image_content)
else:
image_reference = image_filepath
self.image_number += 1
return image, image_reference
class EmbeddedImage(Directive):
......@@ -61,9 +118,13 @@ class EmbeddedImage(Directive):
'the "align" option. Valid values for "align" are: "%s".'
% (self.name, self.options["align"], '", "'.join(self.align_values))
)
is_output_mode_html = get_output_mode() == "html"
messages = []
image, encoded_string = convert_image_to_pillow_image_and_html_base64(image_filepath)
reference = directives.uri(encoded_string)
image = None
image, image_reference = load_and_convert_image(
image_filepath, convert_to_base64=is_output_mode_html, convert_svg_to_pdf=not is_output_mode_html
)
reference = directives.uri(image_reference)
self.options["uri"] = reference
if not any(attr in self.options for attr in ("height", "width", "scale")) and image is not None:
self.options["width"] = "{}px".format(image.size[0])
......
# -*- coding: utf-8 -*-
VALID_OUTPUT_MODES = ("latex", "html")
_output_mode = "latex"
class InvalidOutputModeError(Exception):
pass
def get_output_mode():
return _output_mode
def set_output_mode(output_mode):
global _output_mode
if output_mode in VALID_OUTPUT_MODES:
_output_mode = output_mode
else:
raise InvalidOutputModeError("{} is not valid output mode".format(output_mode))
......@@ -14,6 +14,7 @@ from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives, states
from docutils.nodes import fully_normalize_name, whitespace_normalize_name
from docutils.parsers.rst.roles import set_classes
from .output_mode import get_output_mode
TEMPLATE_LATEX = u"""
......@@ -50,23 +51,6 @@ TMP_SVG_FILENAME = "tikz_picture.svg"
OUT_SVG_FILENAME = "_tikz_rendered{:04d}.svg"
VALID_OUTPUT_MODES = ("latex", "html")
_output_mode = "latex"
class InvalidOutputModeError(Exception):
pass
def set_output_mode(output_mode):
global _output_mode
if output_mode in VALID_OUTPUT_MODES:
_output_mode = output_mode
else:
raise InvalidOutputModeError("{} is not valid output mode".format(output_mode))
class TemporaryDirectory(object):
def __init__(self):
self.tmp_dir = tempfile.mkdtemp()
......@@ -199,7 +183,7 @@ class Tikz(Directive):
)
if "document_options" not in self.options:
self.options["document_options"] = "12pt, border=5pt"
is_output_mode_html = _output_mode == "html"
is_output_mode_html = get_output_mode() == "html"
messages = []
if read_from_file:
with codecs.open(tikz_source_filename, "r", "utf-8") as f:
......
......@@ -19,8 +19,7 @@ except:
from docutils.core import publish_cmdline
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives.images import Image
from ..directives import CodeBlock, Tikz, TikzFigure, set_output_mode
from ..directives import CodeBlock, EmbeddedImage, Tikz, TikzFigure, set_output_mode
description = (
......@@ -37,7 +36,7 @@ def main():
set_output_mode("latex")
for directive_name in ("code", "code-block"):
directives.register_directive(directive_name, CodeBlock)
directives.register_directive("embedded-image", Image)
directives.register_directive("embedded-image", EmbeddedImage)
directives.register_directive("tikz", Tikz)
directives.register_directive("tikz-figure", TikzFigure)
publish_cmdline(writer_name="latex", description=description)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment