Skip to content

Commit

Permalink
Add support for xypic diagrams
Browse files Browse the repository at this point in the history
We allow for xypic figures to be created with the same syntax as in
LaTeX, using the same approach as for Tikz diagrams. Indeed, we
implement this by tweaking the previous Tikz-specific API to handle
both cases.
  • Loading branch information
Richard Williamson committed Feb 1, 2019
1 parent 4f98614 commit 494c311
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/python3.7

import argparse
import enum
import errno
import find_block
import glob
Expand All @@ -15,7 +16,7 @@
"""
Initialises logging. Logs to
tikz_diagrams.log
diagrams.log
"""
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Expand All @@ -24,7 +25,7 @@
"%(asctime)s %(levelname)s %(name)s %(message)s")
log_directory = os.environ["NLAB_LOG_DIRECTORY"]
logging_file_handler = logging.FileHandler(
os.path.join(log_directory, "tikz_diagrams.log"))
os.path.join(log_directory, "diagrams.log"))
logging_file_handler.setFormatter(logging_formatter)
logger.addHandler(logging_file_handler)

Expand All @@ -36,8 +37,12 @@ class SvgRenderingException(Exception):
def __init__(self, message):
super().__init__(message)

class DiagramType(enum.Enum):
TIKZ = "tikz"
XYPIC = "xypic"

def create_diagram_source_directory_if_needed():
diagram_source_directory = os.environ["NLAB_TIKZ_DIAGRAM_SOURCE_DIRECTORY"]
diagram_source_directory = os.environ["NLAB_DIAGRAM_SOURCE_DIRECTORY"]
try:
os.mkdir(diagram_source_directory)
except OSError as osError:
Expand Down Expand Up @@ -75,24 +80,42 @@ def extract_error(output):
break
return "\n".join(error_description)

def create_pdf(
diagram_source_directory,
diagram_id,
tikz_diagram,
is_commutative_diagram):
def tikz_tex_source(diagram, is_commutative_diagram):
tikz_diagram_template_path = os.environ["NLAB_TIKZ_DIAGRAM_TEMPLATE"]
with open(tikz_diagram_template_path, "r") as tikz_diagram_template_file:
tikz_diagram_template = tikz_diagram_template_file.read()
if not is_commutative_diagram:
libraries, tikz_diagram_without_tikz_libraries = extract_tikz_libraries(
tikz_diagram)
tex_source = string.Template(tikz_diagram_template).substitute(
diagram)
return string.Template(tikz_diagram_template).substitute(
tikz_libraries = libraries,
tikz_diagram = tikz_diagram_without_tikz_libraries)
else:
tex_source = string.Template(tikz_diagram_template).substitute(
return string.Template(tikz_diagram_template).substitute(
tikz_libraries = "\\usetikzlibrary{cd}",
tikz_diagram = tikz_diagram)
tikz_diagram = diagram)

def xypic_tex_source(diagram):
xypic_diagram_template_path = os.environ["NLAB_XYPIC_DIAGRAM_TEMPLATE"]
with open(xypic_diagram_template_path, "r") as xypic_diagram_template_file:
xypic_diagram_template = xypic_diagram_template_file.read()
return string.Template(xypic_diagram_template).substitute(
xypic_diagram = diagram)

def create_pdf(
diagram_source_directory,
diagram_id,
diagram,
diagram_type,
is_commutative_diagram = True):
if diagram_type == DiagramType.TIKZ:
tex_source = tikz_tex_source(diagram, is_commutative_diagram)
elif diagram_type == DiagramType.XYPIC:
tex_source = xypic_tex_source(diagram)
else:
raise ValueError(
"Following diagram type not handled: " +
diagram_type.value)
tex_path = os.path.join(diagram_source_directory, diagram_id + ".tex")
with open(tex_path, "w") as tex_source_file:
tex_source_file.write(tex_source)
Expand Down Expand Up @@ -128,8 +151,15 @@ def remove_diagram_files(diagram_source_directory, diagram_id):
def argument_parser():
parser = argparse.ArgumentParser(
description = (
"Creates SVG source from tikz diagram code passed into stdin"))
parser.add_argument(
"Creates SVG source from diagram code passed into stdin"))
subparsers = parser.add_subparsers(dest = "subcommand")
parser_tikz = subparsers.add_parser(
"tikz",
help = "Tikz diagrams")
parser_xypic = subparsers.add_parser(
"xypic",
help = "XYPic diagrams")
parser_tikz.add_argument(
"-c",
"--commutative_diagram",
action = "store_true",
Expand All @@ -139,22 +169,30 @@ def argument_parser():
def main():
parser = argument_parser()
arguments = parser.parse_args()
is_commutative_diagram = arguments.commutative_diagram
tikz_diagram = sys.stdin.read().strip()
diagram_type = DiagramType(arguments.subcommand)
diagram = sys.stdin.read().strip()
diagram_source_directory = create_diagram_source_directory_if_needed()
diagram_id = str(random.randint(10**8, (10**9) - 1))
try:
create_pdf(
diagram_source_directory,
diagram_id,
tikz_diagram,
is_commutative_diagram)
svg_diagram = create_svg(diagram_source_directory, diagram_id)
if diagram_type == DiagramType.TIKZ:
create_pdf(
diagram_source_directory,
diagram_id,
diagram,
diagram_type,
arguments.commutative_diagram)
else:
create_pdf(
diagram_source_directory,
diagram_id,
diagram,
diagram_type)
svg_diagram = create_svg(diagram_source_directory, diagram_id)
except PdfRenderingException as pdfRenderingException:
message = (
"An error occurred when running pdflatex on the following " +
"diagram. \n" +
tikz_diagram +
diagram +
"\nThe error was: " +
str(pdfRenderingException))
logger.warning(message)
Expand All @@ -163,17 +201,18 @@ def main():
message = (
"An error occurred when creating an SVG from a PDF for the " +
"following diagram. \n" +
tikz_diagram)
diagram)
logger.warning(
message +
"\nThe error was: " +
str(svgRenderingException))
sys.exit(message)
except Exception as exception:
raise exception
message = (
"An unexpected error occurred when creating an SVG from a PDF " +
"for the following diagram. \n" +
tikz_diagram)
diagram)
logger.warning(
message +
"\nThe error was: " +
Expand All @@ -183,7 +222,7 @@ def main():
remove_diagram_files(diagram_source_directory, diagram_id)
logger.info(
"Successfully created an SVG for the following diagram. \n" +
tikz_diagram)
diagram)
print(svg_diagram)

if __name__ == "__main__":
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions script/src/diagrams/xypic_diagram_template
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
\documentclass[12pt]{standalone}

\usepackage[all]{xy}

\begin{document}

$xypic_diagram

\end{document}
6 changes: 5 additions & 1 deletion script/src/renderer/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import theorem_environment_blocks
import tikz_diagram_block
import time
import xypic_diagram_block

"""
Initialises logging. Logs to
Expand Down Expand Up @@ -239,7 +240,8 @@ def _write_to_file_for_maruku(page_id, content_for_rendering):
"""
def render(page_id, page_content):
page_content = centre_block.handle_initial_centring(page_content)
page_content = tikz_diagram_block.handle_commutative_diagrams(page_content)
page_content = tikz_diagram_block.handle_tikz_diagrams(page_content)
page_content = xypic_diagram_block.handle_xypic_diagrams(page_content)
pages_to_re_render_and_expire = []
pages_to_render_to_include = []
references_before_rendering = _references_before_rendering(page_id)
Expand Down Expand Up @@ -344,6 +346,8 @@ def initial_rendering(page_id, page_content):
error_message = str(invalid_tex_exception)
except tikz_diagram_block.TikzDiagramException as tikz_diagram_exception:
error_message = str(tikz_diagram_exception)
except xypic_diagram_block.XyPicDiagramException as xypic_diagram_exception:
error_message = str(xypic_diagram_exception)
except Exception as exception:
error_message = "An unexpected error occurred when rendering the page"
error = exception
Expand Down
10 changes: 5 additions & 5 deletions script/src/renderer/tikz_diagram_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def tikz_diagram_processor(tikz_diagram):
"\\begin{tikzpicture}" +
tikz_diagram +
"\\end{tikzpicture}")
tikz_diagram_api_path = os.environ["NLAB_TIKZ_DIAGRAM_API_PATH"]
diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"]
completed_tikz_diagram_process = subprocess.run(
[ tikz_diagram_api_path ],
[ diagram_api_path, "tikz" ],
input = tikz_diagram,
text = True,
capture_output = True)
Expand All @@ -40,9 +40,9 @@ def tikz_commutative_diagram_processor(tikz_diagram):
"[row sep=huge, column sep=huge, transform shape, nodes = {scale=1.25}]" +
tikz_diagram +
"\\end{tikzcd}")
tikz_diagram_api_path = os.environ["NLAB_TIKZ_DIAGRAM_API_PATH"]
diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"]
completed_tikz_diagram_process = subprocess.run(
[ tikz_diagram_api_path, "-c" ],
[ diagram_api_path, "tikz", "-c" ],
input = tikz_diagram,
text = True,
capture_output = True)
Expand All @@ -65,7 +65,7 @@ def define_tikz_commutative_diagram():
tikz_commutative_diagram_processor,
True)

def handle_commutative_diagrams(content):
def handle_tikz_diagrams(content):
processor = find_block.Processor(
[ define_tikz(), define_tikz_commutative_diagram() ])
return processor.process(content)
Expand Down
50 changes: 50 additions & 0 deletions script/src/renderer/xypic_diagram_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/python3

"""
For creating SVG code from a tikz diagram
"""

import find_block
import os
import subprocess

class XyPicDiagramException(Exception):
def __init__(self, message):
super().__init__(message)

def xypic_diagram_processor(xypic_diagram):
xypic_diagram = (
"\\xymatrix@" +
xypic_diagram +
"}")
diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"]
completed_xypic_diagram_process = subprocess.run(
[ diagram_api_path, "xypic" ],
input = xypic_diagram,
text = True,
capture_output = True)
if completed_xypic_diagram_process.returncode != 0:
raise XyPicDiagramException(
completed_xypic_diagram_process.stderr)
return completed_xypic_diagram_process.stdout

def define_xymatrix_default_size():
return find_block.Block(
"\\xymatrix{",
"}",
lambda diagram: xypic_diagram_processor(
"=5em{" + diagram),
True)

def define_xymatrix():
return find_block.Block(
"\\xymatrix@",
"}",
xypic_diagram_processor,
True)

def handle_xypic_diagrams(content):
processor = find_block.Processor(
[ define_xymatrix(), define_xymatrix_default_size() ])
return processor.process(content)

0 comments on commit 494c311

Please sign in to comment.