Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM pandoc/typst:3.7.0
ARG ENVIRONMENT=production

RUN apk add --no-cache python3 py3-pip
RUN apk add --no-cache poppler-utils
RUN apk add --no-cache font-noto-cjk font-noto-all font-noto-emoji ttf-dejavu fontconfig \
&& fc-cache -f

Expand Down
38 changes: 30 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Literal
from typing import Literal, cast

import tempfile
import asyncio
Expand Down Expand Up @@ -26,8 +26,9 @@
}

class DocConversionTask:
def __init__(self, doc_type: Literal['docx', 'pdf', 'tex'], temp_dir_name: str, env: dict[str, str] | None):
def __init__(self, doc_type: Literal['docx', 'pdf', 'tex'], paper_size: Literal['letter', 'a4'], temp_dir_name: str, env: dict[str, str] | None):
self.doc_type = doc_type
self.paper_size: Literal['letter', 'a4'] = paper_size
self.temp_dir_name = temp_dir_name
self.env = env
self.future = asyncio.get_running_loop().create_future()
Expand All @@ -38,12 +39,26 @@ async def convert(self):

match self.doc_type:
case "pdf":
paper_size_variable = "us-letter" if self.paper_size == "letter" else "a4"
cmd = f"pandoc --from markdown --to pdf --standalone --embed-resources --no-highlight \
--pdf-engine=typst {MD_FILE_NAME} -o {output_file_name}"
--pdf-engine=typst {MD_FILE_NAME} \
-V papersize={paper_size_variable} \
-o {output_file_name}"
case "tex":
cmd = f"pandoc --from markdown --to latex --standalone --embed-resources --no-highlight {MD_FILE_NAME} -o {output_file_name}"
cmd = f"pandoc --from markdown --to latex --standalone --embed-resources --no-highlight \
-V papersize={self.paper_size} \
{MD_FILE_NAME} -o {output_file_name}"
case "docx":
if self.paper_size == "letter":
reference_doc = "/code/app/reference_docs/reference_letter.docx"
else:
reference_doc = "/code/app/reference_docs/reference_a4.docx"

cmd = f"pandoc --from markdown --to {self.doc_type} --standalone --embed-resources --no-highlight \
--reference-doc={reference_doc} \
{MD_FILE_NAME} -o {output_file_name}"
case _:
cmd = f"pandoc --from markdown --to {self.doc_type} --standalone --embed-resources --no-highlight {MD_FILE_NAME} -o {output_file_name}"
raise HTTPException(status_code=405, detail="Unsupported document type")

try:
if (time.monotonic() - self.wait_start_time) > MAX_QUEUE_WAIT_TIME:
Expand Down Expand Up @@ -122,9 +137,16 @@ async def delete_temp_directory(temp_dir_name):
await asyncio.to_thread(shutil.rmtree, temp_dir_name, ignore_errors=True)


@app.post("/docgen/{doc_type}")
async def convert_markdown_file(request_file: UploadFile, doc_type: Literal['docx', 'pdf', 'tex'],
@app.post("/docgen/{raw_doc_type}")
async def convert_markdown_file(request_file: UploadFile, raw_doc_type: Literal['docx', 'docx_a4', 'pdf', 'pdf_a4', 'tex', 'tex_a4'],
background_tasks: BackgroundTasks):
if raw_doc_type.endswith('_a4'):
paper_size = 'a4'
else:
paper_size = 'letter'

doc_type = cast(Literal['docx', 'pdf', 'tex'], raw_doc_type.removesuffix('_a4'))

temp_dir_name = None

try:
Expand Down Expand Up @@ -154,7 +176,7 @@ async def convert_markdown_file(request_file: UploadFile, doc_type: Literal['doc
md_input_file.write(("Created with [EngineeringPaper.xyz](https://engineeringpaper.xyz)\n\n").encode('utf-8'))

# add to task queue
doc_conversion_task = DocConversionTask(doc_type, temp_dir_name, env)
doc_conversion_task = DocConversionTask(doc_type, paper_size, temp_dir_name, env)
await queue.put(doc_conversion_task) # finishes when task is inserted into queue
output_file_name = await doc_conversion_task.future # finishes when doc conversion is completed or raises

Expand Down
Binary file added app/reference_docs/reference_a4.docx
Binary file not shown.
Binary file added app/reference_docs/reference_letter.docx
Binary file not shown.
Binary file modified tests/output_reference.docx
Binary file not shown.
1 change: 1 addition & 0 deletions tests/output_reference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
\PassOptionsToPackage{unicode}{hyperref}
\PassOptionsToPackage{hyphens}{url}
\documentclass[
letterpaper,
]{article}
\usepackage{xcolor}
\usepackage{amsmath,amssymb}
Expand Down
Binary file modified tests/output_reference_0.docx
Binary file not shown.
Binary file modified tests/output_reference_1.docx
Binary file not shown.
Binary file modified tests/output_reference_10.docx
Binary file not shown.
Binary file modified tests/output_reference_11.docx
Binary file not shown.
Binary file modified tests/output_reference_12.docx
Binary file not shown.
Binary file modified tests/output_reference_13.docx
Binary file not shown.
Binary file modified tests/output_reference_14.docx
Binary file not shown.
Binary file modified tests/output_reference_15.docx
Binary file not shown.
Binary file modified tests/output_reference_16.docx
Binary file not shown.
Binary file modified tests/output_reference_17.docx
Binary file not shown.
Binary file modified tests/output_reference_18.docx
Binary file not shown.
Binary file modified tests/output_reference_19.docx
Binary file not shown.
Binary file modified tests/output_reference_2.docx
Binary file not shown.
Binary file modified tests/output_reference_3.docx
Binary file not shown.
Binary file modified tests/output_reference_4.docx
Binary file not shown.
Binary file modified tests/output_reference_5.docx
Binary file not shown.
Binary file modified tests/output_reference_6.docx
Binary file not shown.
Binary file modified tests/output_reference_7.docx
Binary file not shown.
Binary file modified tests/output_reference_8.docx
Binary file not shown.
Binary file modified tests/output_reference_9.docx
Binary file not shown.
Binary file added tests/output_reference_a4.docx
Binary file not shown.
138 changes: 138 additions & 0 deletions tests/output_reference_a4.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
% Options for packages loaded elsewhere
\PassOptionsToPackage{unicode}{hyperref}
\PassOptionsToPackage{hyphens}{url}
\documentclass[
a4paper,
]{article}
\usepackage{xcolor}
\usepackage{amsmath,amssymb}
\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
\usepackage{iftex}
\ifPDFTeX
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{textcomp} % provide euro and other symbols
\else % if luatex or xetex
\usepackage{unicode-math} % this also loads fontspec
\defaultfontfeatures{Scale=MatchLowercase}
\defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
\fi
\usepackage{lmodern}
\ifPDFTeX\else
% xetex/luatex font selection
\fi
% Use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\IfFileExists{microtype.sty}{% use microtype if available
\usepackage[]{microtype}
\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
}{}
\makeatletter
\@ifundefined{KOMAClassName}{% if non-KOMA class
\IfFileExists{parskip.sty}{%
\usepackage{parskip}
}{% else
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}}
}{% if KOMA class
\KOMAoptions{parskip=half}}
\makeatother
\setlength{\emergencystretch}{3em} % prevent overfull lines
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\usepackage{bookmark}
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
\urlstyle{same}
\hypersetup{
hidelinks,
pdfcreator={LaTeX via pandoc}}

\author{}
\date{}

\begin{document}

\section{Database Consistency Test}\label{database-consistency-test}

\section{Introduction}\label{introduction}

This EngineeringPaper.xyz sheet shows how to calculated the maximum
stress and displacement for a cantilever beam loaded by a vertical force
at its end. The geometry of the beam is shown in the figure below. The
beam has a rectangular cross section with height h and width b. The
maximum stress in a cantilever with length much larger than its height
is due to bending stress that occurs at the base of the beam. For a
downward force, the maximum stress will be a tensile force at the top of
the beam and a compressive stress at the bottom of the beam. For beam
sections that are symmetric top to bottom, the maximum tensile and
compressive stresses will have equal magnitude. \# \# \# Select Beam
Material

\section{Select Beam Section}\label{select-beam-section}

Source for beam section equations and images:
\url{https://en.wikipedia.org/wiki/List_of_second_moments_of_area}

\section{Define the Other Beam
Parameters}\label{define-the-other-beam-parameters}

\[ l=500\left[mm\right] \]

\[ F=500\left[N\right] \]

\section{Define the Deflection and Stress
Equations}\label{define-the-deflection-and-stress-equations}

The maximum displacement of a cantilever beam loaded at its end is
defined by {[}1{]}:

\[ y_{max}=-\frac{F\cdot l^{3}}{3\cdot E\cdot I_{x}} \]

where Ix is the area moment of inertia of the cross section about the
x-axis as selected in the table above. The maximum displacement can now
be queried:

\[\boxed{ y_{max}=\left[mm\right] =-0.165297794307557 \left[mm\right] }\]

The maximum bending stress in a beam section is given by:

\[ \sigma_{max}=\frac{M\cdot c}{I_{x}} \]

where M is the bending moment at the section of interest and c is
distance from the neutral axis of the beam to the top or bottom of the
beam. The maximum moment occurs at the base of the beam and is defined
by:

\[ M=l\cdot F \]

where l is the length of the beam. Now that everything is defined, the
maximum stress can be queried and converted to {[}MPa{]} units:

\[\boxed{ \sigma_{max}=\left[MPa\right] =5.33333333333333 \left[MPa\right] }\]

These results can be compared to the results obtained from a finite
element analysis using SolidWorks. The beam has the same dimensions,
loading, and material as the rectangular cross-section and the aluminum
table options above. The displacement plot is shown below. The maximum
displacement magnitude obtained using finite element analysis is .175
{[}mm{]}, which is close to the .165 {[}mm{]} obtained using the
equations above.

Similarly, the maximum stress calculation can be compared to the
SolidWorks results. The maximum stress obtained from SolidWorks is 6.5
{[}MPa{]} as shown below. Note that this is higher than the 5.3
{[}MPa{]} obtained using the beam equation. This is the result of the
stress concentration that occurs at the based of the cantilever where it
meets the fixed boundary condition. A littler further from the fixed
boundary condition, the finite element values are close to the
calculated values.

{[}1{]} Norton, R. L.
``\href{https://www.pearson.com/us/higher-education/program/Norton-Machine-Design-5th-Edition/PGM275676.html}{Machine
design. A integrated approach, 5th Editi.}'' (2013).

\[ y=\frac{F}{6\cdot E\cdot I_{x}}\cdot\left(x^{3}-3\cdot l\cdot x^{2}\right) \]

Created with \href{https://engineeringpaper.xyz}{EngineeringPaper.xyz}

\end{document}
60 changes: 59 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ def test_md_to_docx(client):

assert filecmp.cmp('./tests/output/output.docx', './tests/output_reference.docx', shallow=False)

def test_md_to_docx_a4(client):
files = {'request_file': ("input.md", open('./tests/input.md', 'rb'), "text/markdown")}
response = client.post("/docgen/docx_a4", files=files)
assert response.status_code == 200

# save docx file as artifact
with open('./tests/output/output_a4.docx', 'wb') as output_docx:
output_docx.write(response.content)

assert filecmp.cmp('./tests/output/output_a4.docx', './tests/output_reference_a4.docx', shallow=False)

def test_md_to_pdf(client):
files = {'request_file': ("input.md", open('./tests/input_with_unicode.md', 'rb'), "text/markdown")}
response = client.post("/docgen/pdf", files=files)
Expand All @@ -50,17 +61,64 @@ def test_md_to_pdf(client):
# file comparison not working (change identifier metadata?) so will just check size
assert len(response.content) > 200000

# Check paper size using pdfinfo
pdfinfo_result = subprocess.run(['pdfinfo', './tests/output/output.pdf'], capture_output=True, text=True)
assert pdfinfo_result.returncode == 0

letter_size = False
for line in pdfinfo_result.stdout.splitlines():
if "letter" in line:
letter_size = True
break

assert(letter_size)

def test_md_to_pdf_a4(client):
files = {'request_file': ("input.md", open('./tests/input_with_unicode.md', 'rb'), "text/markdown")}
response = client.post("/docgen/pdf_a4", files=files)
assert response.status_code == 200

# save pdf file as artifact
with open('./tests/output/output.pdf', 'wb') as output_pdf:
output_pdf.write(response.content)

# file comparison not working (change identifier metadata?) so will just check size
assert len(response.content) > 200000

# Check paper size using pdfinfo
pdfinfo_result = subprocess.run(['pdfinfo', './tests/output/output.pdf'], capture_output=True, text=True)
assert pdfinfo_result.returncode == 0

a4_size = False
for line in pdfinfo_result.stdout.splitlines():
if "A4" in line:
a4_size = True
break

assert(a4_size)

def test_md_to_latex(client):
files = {'request_file': ("input.md", open('./tests/input.md', 'rb'), "text/markdown")}
response = client.post("/docgen/tex", files=files)
assert response.status_code == 200

# save pdf file as artifact
# save md file as artifact
with open('./tests/output/output.tex', 'wb') as output_pdf:
output_pdf.write(response.content)

assert filecmp.cmp('./tests/output/output.tex', './tests/output_reference.tex', shallow=False)

def test_md_to_latex_a4(client):
files = {'request_file': ("input.md", open('./tests/input.md', 'rb'), "text/markdown")}
response = client.post("/docgen/tex_a4", files=files)
assert response.status_code == 200

# save md file as artifact
with open('./tests/output/output_a4.tex', 'wb') as output_pdf:
output_pdf.write(response.content)

assert filecmp.cmp('./tests/output/output_a4.tex', './tests/output_reference_a4.tex', shallow=False)

def test_error_on_binary_input(client):
files = {'request_file': ("input.md", open('./tests/output_reference.docx', 'rb'), "text/markdown")}
response = client.post("/docgen/docx", files=files)
Expand Down