Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image2latex #751

Merged
merged 12 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Enhancements
#. Better handling of comparisons with finite precision numbers.
#. Improved implementation for ``Precision``.
#. Infix operators, like ``->`` render with their Unicode symbol when ``$CharacterEncoding`` is not "ASCII".
#. ``Grid`` compatibility with WMA was improved. Now it supports non-uniform list of lists and lists with general elements.
#. Support for BigEndian Big TIFF

5.0.2
Expand Down
13 changes: 6 additions & 7 deletions mathics/builtin/atomic/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,19 +342,18 @@ def rhs(expr):
),
)
)
if grid:
if lines:
if lines:
if grid:
return Expression(
SymbolGrid,
ListExpression(*(ListExpression(line) for line in lines)),
Expression(SymbolRule, Symbol("ColumnAlignments"), Symbol("Left")),
)
else:
return SymbolNull
else:
for line in lines:
evaluation.print_out(Expression(SymbolInputForm, line))
return SymbolNull
for line in lines:
evaluation.print_out(Expression(SymbolInputForm, line))

return SymbolNull

def format_definition_input(self, symbol, evaluation):
"InputForm: Definition[symbol_]"
Expand Down
13 changes: 12 additions & 1 deletion mathics/builtin/box/image.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-

import base64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without a docstring at the top, this doesn't appear in any printed documentation or in Django docs. If that is intended, then the homegrown tagging such as ImageBox (on line 26) is not used.

We should decide way we want to go and either add a docstring at the top or remove the homegrown tagging.

import tempfile

from mathics.builtin.box.expression import BoxExpression


Expand Down Expand Up @@ -27,4 +30,12 @@ def boxes_to_mathml(self, elements=None, **options):
)

def boxes_to_tex(self, elements=None, **options):
return "-Image-"
fp = tempfile.NamedTemporaryFile(delete=True, suffix=".png")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using pillow routines for writing PNG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conversion was done when Image is converted in ImageBox. imageBox stores a B64 encoded version of the PNG file. Here we just decode it and store it in a file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ImageBox can keep the pillow structure, that may be a win. A more general problem we have is that in digesting things for M-Expressions we lose the efficient and sometimes more flexible properties of whatever the object was before. And we spend a lot of time in conversion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done now.

path = fp.name
fp.close()
base64data = self.elements[0].value.encode("utf-8")
data = base64.decodebytes(data[22:])
with open(path, "wb") as imgfile:
imgfile.write(data)

return "\\includegraphics[width=3cm]{" + path + "}"
21 changes: 17 additions & 4 deletions mathics/builtin/box/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,29 @@ class GridBox(BoxExpression):
# elements in its evaluated form.

def get_array(self, elements, evaluation):
options = self.get_option_values(elements=elements[1:], evaluation=evaluation)
if not elements:
raise BoxConstructError

options = self.get_option_values(elements=elements[1:], evaluation=evaluation)
expr = elements[0]
if not expr.has_form("List", None):
if not all(element.has_form("List", None) for element in expr.elements):
raise BoxConstructError
items = [element.elements for element in expr.elements]
if not is_constant_list([len(row) for row in items]):
raise BoxConstructError
items = [
element.elements if element.has_form("List", None) else element
for element in expr.elements
]
if not is_constant_list([len(row) for row in items if isinstance(row, tuple)]):
max_len = max(len(items) for item in items)
empty_string = String("")

def complete_rows(row):
if isinstance(row, tuple):
return row + (max_len - len(row)) * (empty_string,)
return row

items = [complete_rows(row) for row in items]

return items, options


Expand Down
57 changes: 48 additions & 9 deletions mathics/builtin/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,62 @@ class Grid(Builtin):
= a b
.
. c d

For shallow lists, elements are shown as a column
>> Grid[{a, b, c}]
= a
.
. b
.
. c

If the sublists have different sizes, the grid has the number of columns of the \
largest one. Incomplete rows are completed with empty strings:

>> Grid[{{"first", "second", "third"},{a},{1, 2, 3}}]
= first second third
.
. a
.
. 1 2 3

If the list is a mixture of lists and other expressions, the non-list expressions are
shown as rows:

>> Grid[{"This is a long title", {"first", "second", "third"},{a},{1, 2, 3}}]
= This is a long title
.
. first second third
.
. a
.
. 1 2 3

"""

options = GridBox.options
summary_text = " 2D layout containing arbitrary objects"

def eval_makeboxes(self, array, f, evaluation: Evaluation, options) -> Expression:
"""MakeBoxes[Grid[array_?MatrixQ, OptionsPattern[Grid]],
"""MakeBoxes[Grid[array_List, OptionsPattern[Grid]],
f:StandardForm|TraditionalForm|OutputForm]"""

elements = array.elements

rows = (
element.elements if element.has_form("List", None) else element
for element in elements
)

def format_row(row):
if isinstance(row, tuple):
return ListExpression(
*(format_element(item, evaluation, f) for item in row),
)
return format_element(row, evaluation, f)

return GridBox(
ListExpression(
*(
ListExpression(
*(format_element(item, evaluation, f) for item in row.elements),
)
for row in array.elements
),
),
ListExpression(*(format_row(row) for row in rows)),
*options_to_rules(options),
)

Expand Down
Binary file removed mathics/doc/latex/logo.pdf
Binary file not shown.
36 changes: 33 additions & 3 deletions mathics/format/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
Symbols exist.
"""

import base64
import re
import tempfile

from mathics.builtin.box.graphics import GraphicsBox
from mathics.builtin.box.graphics3d import Graphics3DBox
from mathics.builtin.box.image import ImageBox
from mathics.builtin.box.layout import (
FractionBox,
GridBox,
Expand Down Expand Up @@ -163,6 +166,7 @@ def boxes_to_tex(box, **options):
elements = self._elements
evaluation = box_options.get("evaluation")
items, options = self.get_array(elements, evaluation)

new_box_options = box_options.copy()
new_box_options["inside_list"] = True
column_alignments = options["System`ColumnAlignments"].get_name()
Expand All @@ -175,12 +179,21 @@ def boxes_to_tex(box, **options):
except KeyError:
# invalid column alignment
raise BoxConstructError
column_count = 0
column_count = 1
for row in items:
column_count = max(column_count, len(row))
if isinstance(row, tuple):
column_count = max(column_count, len(row))

result = r"\begin{array}{%s} " % (column_alignments * column_count)
for index, row in enumerate(items):
result += " & ".join(boxes_to_tex(item, **new_box_options) for item in row)
if isinstance(row, tuple):
result += " & ".join(boxes_to_tex(item, **new_box_options) for item in row)
else:
result += r"\multicolumn{%s}{%s}{%s}" % (
str(column_count),
column_alignments,
boxes_to_tex(row, **new_box_options),
)
if index != len(items) - 1:
result += "\\\\ "
result += r"\end{array}"
Expand Down Expand Up @@ -563,3 +576,20 @@ def graphics3dbox(self, elements=None, **options) -> str:


add_conversion_fn(Graphics3DBox, graphics3dbox)


def imagebox(self, elements=None, **options) -> str:
if elements is None:
elements = self.elements
fp = tempfile.NamedTemporaryFile(delete=True, suffix=".png")
path = fp.name
fp.close()
base64data = elements[0].value.encode("utf-8")[22:]
data = base64.decodebytes(base64data)
with open(path, "wb") as imgfile:
imgfile.write(data)

return "\\includegraphics[width=2.5cm]{" + path + "}"


add_conversion_fn(ImageBox, imagebox)
11 changes: 7 additions & 4 deletions mathics/format/mathml.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def boxes_to_mathml(box, **options):
elements = self._elements
evaluation = box_options.get("evaluation")
items, options = self.get_array(elements, evaluation)
num_fields = max(len(item) if isinstance(item, tuple) else 1 for item in items)

attrs = {}
column_alignments = options["System`ColumnAlignments"].get_name()
try:
Expand All @@ -148,10 +150,11 @@ def boxes_to_mathml(box, **options):
new_box_options["inside_list"] = True
for row in items:
result += "<mtr>"
for item in row:
result += (
f"<mtd {joined_attrs}>{boxes_to_mathml(item, **new_box_options)}</mtd>"
)
if isinstance(row, tuple):
for item in row:
result += f"<mtd {joined_attrs}>{boxes_to_mathml(item, **new_box_options)}</mtd>"
else:
result += f"<mtd {joined_attrs} columnspan={num_fields}>{boxes_to_mathml(row, **new_box_options)}</mtd>"
result += "</mtr>\n"
result += "</mtable>"
# print(f"gridbox: {result}")
Expand Down
34 changes: 26 additions & 8 deletions mathics/format/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,37 @@ def gridbox(self, elements=None, **box_options) -> str:
result = ""
if not items:
return ""
widths = [0] * len(items[0])
try:
widths = [0] * max(1, max(len(row) for row in items if isinstance(row, tuple)))
except ValueError:
widths = [0]

cells = [
[
# TODO: check if this evaluation is necesary.
boxes_to_text(item, **box_options).splitlines()
for item in row
]
if isinstance(row, tuple)
else [boxes_to_text(row, **box_options).splitlines()]
for row in items
]
for row in cells:

# compute widths
full_width = 0
for i, row in enumerate(cells):
for index, cell in enumerate(row):
if index >= len(widths):
raise BoxConstructError
for line in cell:
widths[index] = max(widths[index], len(line))
if not isinstance(items[i], tuple):
for line in cell:
full_width = max(full_width, len(line))
else:
for line in cell:
widths[index] = max(widths[index], len(line))

full_width = max(sum(widths), full_width)

for row_index, row in enumerate(cells):
if row_index > 0:
result += "\n"
Expand All @@ -95,10 +111,12 @@ def gridbox(self, elements=None, **box_options) -> str:
else:
text = ""
line += text
if cell_index < len(row) - 1:
line += " " * (widths[cell_index] - len(text))
# if cell_index < len(row) - 1:
line += " "
if isinstance(items[row_index], tuple):
if cell_index < len(row) - 1:
line += " " * (widths[cell_index] - len(text))
# if cell_index < len(row) - 1:
line += " "

if line_exists:
result += line + "\n"
else:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"Mathics-Scanner >= 1.3.0.dev0",
# Pillow 9.1.0 supports BigTIFF with big-endian byte order.
# ExampleData image hedy.tif is in this format.
"pillow >= 9.1.0",
"pillow == 9.2.0",
mmatera marked this conversation as resolved.
Show resolved Hide resolved
]

# Ensure user has the correct Python version
Expand Down