From 065b09a002e04a542625e64a9e62f223e204265b Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 11:55:01 -0700 Subject: [PATCH 01/10] Fix LaTeX math mode in adjustbox - wrap formula in $...$ --- backend/api/latex_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/api/latex_utils.py b/backend/api/latex_utils.py index 80614f5..bcaa961 100644 --- a/backend/api/latex_utils.py +++ b/backend/api/latex_utils.py @@ -150,7 +150,7 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar # Escape special LaTeX characters in the formula name escaped_name = name.replace("\\", "\\textbackslash ").replace("&", "\\&").replace("%", "\\%").replace("#", "\\#").replace("_", "\\_").replace("^", "\\textasciicircum ").replace("{", "\\{").replace("}", "\\}") body_lines.append("\\textbf{" + escaped_name + "}") - body_lines.append("\\[ \\adjustbox{max width=\\linewidth}{\\displaystyle " + latex + "} \\]") + body_lines.append("\\[ \\adjustbox{max width=\\linewidth}{$" + latex + "$} \\]") body_lines.append(f"\\\\[{formula_gap}]") if in_flushleft: From 961dcb8d9b52aad0cba7c2db2bff8ef4326ed622 Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 13:35:05 -0700 Subject: [PATCH 02/10] Fix spacing settings: add baselineskip control to tighten line spacing The spacing setting only controlled section/subsection header spacing, not actual line spacing. This adds baselineskip control so 'tiny' actually reduces line spacing significantly. - Add baselineskip values to SPACING_MAP (6pt/8pt/11pt/14pt for tiny/small/medium/large) - Add \setlength{\baselineskip}{X} to dynamic header generation - Update formula_gap extraction to handle 6-element tuple --- backend/api/latex_utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/api/latex_utils.py b/backend/api/latex_utils.py index bcaa961..f410294 100644 --- a/backend/api/latex_utils.py +++ b/backend/api/latex_utils.py @@ -33,12 +33,12 @@ "12pt": "\\normalsize", } -# Spacing presets: (section_before, section_after, subsection_before, subsection_after, formula_spacing) +# Spacing presets: (section_before, section_after, subsection_before, subsection_after, formula_gap, baselineskip) SPACING_MAP = { - "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt"), - "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt"), - "medium": ("8pt", "4pt", "4pt", "2pt", "8pt"), - "large": ("16pt", "8pt", "8pt", "4pt", "16pt"), + "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt", "6pt"), + "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt", "8pt"), + "medium": ("8pt", "4pt", "4pt", "2pt", "8pt", "11pt"), + "large": ("16pt", "8pt", "8pt", "4pt", "16pt", "14pt"), } @@ -47,7 +47,7 @@ def build_dynamic_header(columns=2, font_size="10pt", margins="0.25in", spacing= Build a dynamic LaTeX header based on user-selected options. """ size_command = FONT_SIZE_MAP.get(font_size, "\\footnotesize") - sec_before, sec_after, subsec_before, subsec_after, _ = SPACING_MAP.get(spacing, SPACING_MAP["large"]) + sec_before, sec_after, subsec_before, subsec_after, _, baseline_skip = SPACING_MAP.get(spacing, SPACING_MAP["large"]) # The standard `article` class only supports 10pt/11pt/12pt. # Use `extarticle` (from the extsizes package) for 8pt and 9pt. @@ -70,6 +70,7 @@ def build_dynamic_header(columns=2, font_size="10pt", margins="0.25in", spacing= "\\titleformat{\\subsection}{\\normalfont\\scriptsize\\bfseries}{}{0pt}{}", f"\\titlespacing*{{\\section}}{{0pt}}{{{sec_before}}}{{{sec_after}}}", f"\\titlespacing*{{\\subsection}}{{0pt}}{{{subsec_before}}}{{{subsec_after}}}", + f"\\setlength{{\\baselineskip}}{{{baseline_skip}}}", "", "\\begin{document}", size_command, @@ -103,7 +104,7 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar """ header = build_dynamic_header(columns, font_size, margins, spacing) footer = build_dynamic_footer(columns) - _, _, _, _, formula_gap = SPACING_MAP.get(spacing, SPACING_MAP["large"]) + _, _, _, _, formula_gap, _ = SPACING_MAP.get(spacing, SPACING_MAP["large"]) if not selected_formulas: return header + footer From d1da61b2a923ce495d0a64486f40091653dbd130 Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 13:44:34 -0700 Subject: [PATCH 03/10] Enhance spacing, recompile, and add 4-column option - Make tiny spacing truly tiny (baselineskip 4pt vs 6pt) - Fix recompile to apply current layout options (columns, fontSize, spacing, margins) - Add 4-column option to dropdown and backend validation - Add proper React dependencies to handleCompileOnly --- .opencode/opencode.json | 6 +++++ backend/api/latex_utils.py | 6 ++--- backend/api/views.py | 2 +- frontend/src/components/CreateCheatSheet.jsx | 1 + frontend/src/hooks/latex.js | 27 ++++++++++++++++++-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 .opencode/opencode.json diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 0000000..30e8a0b --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + "oh-my-openagent" + ] +} \ No newline at end of file diff --git a/backend/api/latex_utils.py b/backend/api/latex_utils.py index f410294..f948923 100644 --- a/backend/api/latex_utils.py +++ b/backend/api/latex_utils.py @@ -35,9 +35,9 @@ # Spacing presets: (section_before, section_after, subsection_before, subsection_after, formula_gap, baselineskip) SPACING_MAP = { - "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt", "6pt"), - "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt", "8pt"), - "medium": ("8pt", "4pt", "4pt", "2pt", "8pt", "11pt"), + "tiny": ("0pt", "0pt", "0pt", "0pt", "0pt", "4pt"), + "small": ("2pt", "1pt", "1pt", "0.5pt", "2pt", "6pt"), + "medium": ("8pt", "4pt", "4pt", "2pt", "8pt", "10pt"), "large": ("16pt", "8pt", "8pt", "4pt", "16pt", "14pt"), } diff --git a/backend/api/views.py b/backend/api/views.py index 8181847..d268ee2 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -22,7 +22,7 @@ def validate_layout_params(columns, font_size, margins, spacing): try: - columns = max(1, min(3, int(columns))) + columns = max(1, min(4, int(columns))) except (TypeError, ValueError): columns = 2 diff --git a/frontend/src/components/CreateCheatSheet.jsx b/frontend/src/components/CreateCheatSheet.jsx index f110837..10f6deb 100644 --- a/frontend/src/components/CreateCheatSheet.jsx +++ b/frontend/src/components/CreateCheatSheet.jsx @@ -449,6 +449,7 @@ const LayoutOptions = ({ columns, setColumns, fontSize, setFontSize, spacing, se +
diff --git a/frontend/src/hooks/latex.js b/frontend/src/hooks/latex.js index f8f25e5..b4adbb0 100644 --- a/frontend/src/hooks/latex.js +++ b/frontend/src/hooks/latex.js @@ -131,11 +131,34 @@ export function useLatex(initialData) { isCompilingRef.current = true; setIsCompiling(true); setCompileError(null); + + let contentToCompile = content; + + try { + const response = await fetch('/api/generate-sheet/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + formulas: [], + columns: columns, + font_size: fontSize, + spacing: spacing, + margins: margins + }), + }); + if (response.ok) { + const data = await response.json(); + contentToCompile = data.tex_code; + } + } catch (e) { + console.log('Regenerate failed, using existing content:', e); + } + try { const response = await fetch('/api/compile/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content }), + body: JSON.stringify({ content: contentToCompile }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -163,7 +186,7 @@ export function useLatex(initialData) { setIsCompiling(false); isCompilingRef.current = false; } - }, [content]); + }, [content, columns, fontSize, spacing, margins]); const handlePreview = useCallback(async (latexContent = null, regenerateOptions = null) => { if (isCompilingRef.current) return; From 00e34e01df07ab5f5331b6e3c5300780c1c439fd Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 13:47:35 -0700 Subject: [PATCH 04/10] Fix recompile to apply layout options, add 4-column, move LayoutOptions to bottom - Backend: generate-sheet now returns valid LaTeX even with empty formulas (for recompile) - Frontend: recompile (middle circle button) now regenerates with current layout options - Add 4-column option to dropdown - Move LayoutOptions from top selection panel to bottom editor panel - Update tests for new empty formulas behavior --- backend/api/tests.py | 8 +++---- backend/api/views.py | 7 ++++--- frontend/src/components/CreateCheatSheet.jsx | 22 ++++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/backend/api/tests.py b/backend/api/tests.py index 84cf7db..ac3f6f0 100644 --- a/backend/api/tests.py +++ b/backend/api/tests.py @@ -242,13 +242,13 @@ def test_filter_problems_by_sheet(self, api_client, sample_problem, sample_sheet class TestGenerateSheetEndpoint: def test_generate_sheet_no_formulas(self, api_client): resp = api_client.post("/api/generate-sheet/", {"formulas": []}, format="json") - assert resp.status_code == 400 - assert "error" in resp.json() + assert resp.status_code == 200 + assert "tex_code" in resp.json() def test_generate_sheet_missing_formulas_key(self, api_client): resp = api_client.post("/api/generate-sheet/", {}, format="json") - assert resp.status_code == 400 - assert "error" in resp.json() + assert resp.status_code == 200 + assert "tex_code" in resp.json() def test_generate_sheet_valid_formula(self, api_client): resp = api_client.post( diff --git a/backend/api/views.py b/backend/api/views.py index d268ee2..23bd3be 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -72,11 +72,12 @@ def generate_sheet(request): margins = request.data.get("margins", "0.25in") spacing = request.data.get("spacing", "large") - if not selected: - return Response({"error": "No formulas selected"}, status=400) - columns, font_size, margins, spacing = validate_layout_params(columns, font_size, margins, spacing) + if not selected: + tex_code = build_latex_for_formulas([], columns, font_size, margins, spacing) + return Response({"tex_code": tex_code}) + formula_data = get_formula_data() selected_formulas = [] diff --git a/frontend/src/components/CreateCheatSheet.jsx b/frontend/src/components/CreateCheatSheet.jsx index 10f6deb..da2437d 100644 --- a/frontend/src/components/CreateCheatSheet.jsx +++ b/frontend/src/components/CreateCheatSheet.jsx @@ -602,17 +602,6 @@ const CreateCheatSheet = ({ onSave, initialData }) => { onRemoveClass={removeClassFromOrder} onRemoveFormula={removeSingleFormula} /> - -
{/* Box 2: Editor and preview */} @@ -660,6 +649,17 @@ const CreateCheatSheet = ({ onSave, initialData }) => { + + Date: Thu, 9 Apr 2026 13:48:48 -0700 Subject: [PATCH 05/10] Fix recompile to preserve document body while applying new layout Previously recompile replaced entire content with empty formula template, causing empty document error. Now it extracts the body from existing content and merges with new layout header. --- frontend/src/hooks/latex.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/latex.js b/frontend/src/hooks/latex.js index b4adbb0..3817f92 100644 --- a/frontend/src/hooks/latex.js +++ b/frontend/src/hooks/latex.js @@ -148,7 +148,17 @@ export function useLatex(initialData) { }); if (response.ok) { const data = await response.json(); - contentToCompile = data.tex_code; + const newLatex = data.tex_code; + + const oldBodyMatch = content.match(/\\begin\{document\}([\s\S]*)\\end\{document\}/); + const newBodyMatch = newLatex.match(/\\begin\{document\}([\s\S]*)\\end\{document\}/); + + if (oldBodyMatch && newBodyMatch) { + const newHeader = newLatex.split('\\begin{document}')[0]; + contentToCompile = newHeader + '\\begin{document}' + oldBodyMatch[1] + '\\end{document}'; + } else { + contentToCompile = newLatex; + } } } catch (e) { console.log('Regenerate failed, using existing content:', e); From f8f1534e9670d295c12490d803695812bd849d7a Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 13:53:13 -0700 Subject: [PATCH 06/10] Move LayoutOptions to top of editor panel, fix merge logic - LayoutOptions now appears at top of editor/preview panel (above LaTeX editor) - Simplify merge logic to extract and preserve document body - Fix missing \end{document} in merged output --- frontend/src/components/CreateCheatSheet.jsx | 22 ++++++++++---------- frontend/src/hooks/latex.js | 9 +++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/CreateCheatSheet.jsx b/frontend/src/components/CreateCheatSheet.jsx index da2437d..771eab6 100644 --- a/frontend/src/components/CreateCheatSheet.jsx +++ b/frontend/src/components/CreateCheatSheet.jsx @@ -606,6 +606,17 @@ const CreateCheatSheet = ({ onSave, initialData }) => { {/* Box 2: Editor and preview */}
+ +
{
- - Date: Thu, 9 Apr 2026 14:13:39 -0700 Subject: [PATCH 07/10] Fix recompile to properly apply layout changes The old merge logic kept the old multicols wrapper which overrode the new column count. Now it extracts just the formula content from the old body and inserts it into the new layout's multicols environment, preserving the new column/spacing settings. --- backend/test_merge.py | 54 +++++++++++++++++++++++++++++++++++++ backend/test_regex.py | 19 +++++++++++++ frontend/src/hooks/latex.js | 24 ++++++++++++++--- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 backend/test_merge.py create mode 100644 backend/test_regex.py diff --git a/backend/test_merge.py b/backend/test_merge.py new file mode 100644 index 0000000..3e01693 --- /dev/null +++ b/backend/test_merge.py @@ -0,0 +1,54 @@ +import re + +# Old content body would be extracted as everything between \begin{document} and \end{document} +old_body = r""" +\begin{multicols}{4} +\raggedcolumns +\section*{ALGEBRA I} + +\subsection*{Linear Equations} + +\begin{flushleft} +\textbf{Slope} +\[ \adjustbox{max width=\linewidth}{$m = \frac{a}{b}$} \] +\\[0pt] +\end{flushleft}\end{multicols} +""" + +# New header from API (with 3 columns, tiny spacing) +new_latex = r"""\documentclass[10pt,fleqn]{article} +\usepackage[margin=0.25in]{geometry} +\usepackage{amsmath, amssymb} +\usepackage{enumitem} +\usepackage{multicol} +\usepackage{titlesec} +\usepackage{adjustbox} + +\titleformat{\section}{\normalfont\footnotesize\bfseries}{}{0pt}{} +\titleformat{\subsection}{\normalfont\scriptsize\bfseries}{}{0pt}{} +\titlespacing*{\section}{0pt}{0pt}{0pt} +\titlespacing*{\subsection}{0pt}{0pt}{0pt} +\setlength{\baselineskip}{4pt} + +\begin{document} +\scriptsize +\begin{multicols}{3} +\raggedcolumns +\end{multicols} +\end{document}""" + +# Frontend merge: newHeader + \begin{document} + oldBody +new_header = new_latex.split(r'\begin{document}')[0] +merged = new_header + r'\begin{document}' + old_body + +# Add \end{document} if missing +if r'\end{document}' not in merged: + merged = merged + r'\end{document}' + +print("=== MERGED OUTPUT (first 1000 chars) ===") +print(merged[:1000]) +print() +print("=== KEY CHECKS ===") +print("Has multicols{3}:", r'multicols{3}' in merged) +print("Has multicols{4}:", r'multicols{4}' in merged) +print("Has baselineskip 4pt:", r'baselineskip}{4pt' in merged) \ No newline at end of file diff --git a/backend/test_regex.py b/backend/test_regex.py new file mode 100644 index 0000000..3f0adaf --- /dev/null +++ b/backend/test_regex.py @@ -0,0 +1,19 @@ +import re + +content = r"""\documentclass[10pt,fleqn]{article} +\usepackage[margin=0.25in]{geometry} +\begin{document} +\begin{multicols}{4} +\section*{ALGEBRA I} +\end{multicols} +\end{document}""" + +# JS regex: /\\begin\{document\}([\s\S]*)\\end\{document\}/ +# In JS, \\ in string becomes \ in the actual regex +pattern = r"\\begin\{document\}([\s\S]*)\\end\{document\}" +match = re.search(pattern, content) + +print("Pattern:", repr(pattern)) +print("Match found:", match is not None) +if match: + print("Body:", match.group(1)[:100]) \ No newline at end of file diff --git a/frontend/src/hooks/latex.js b/frontend/src/hooks/latex.js index 168a878..42cd49c 100644 --- a/frontend/src/hooks/latex.js +++ b/frontend/src/hooks/latex.js @@ -153,11 +153,27 @@ export function useLatex(initialData) { const oldBodyMatch = content.match(/\\begin\{document\}([\s\S]*)\\end\{document\}/); if (oldBodyMatch) { - const newHeader = newLatex.split('\\begin{document}')[0]; - contentToCompile = newHeader + '\\begin{document}' + oldBodyMatch[1]; + const oldBody = oldBodyMatch[1]; - if (!contentToCompile.includes('\\end{document}')) { - contentToCompile = contentToCompile + '\\end{document}'; + const multicolMatch = oldBody.match(/\\begin\{multicols\}\{(\d+)\}([\s\S]*?)\\end\{multicols\}/); + + let formulaContent = oldBody; + if (multicolMatch) { + formulaContent = multicolMatch[2]; + } + + const newParts = newLatex.split('\\begin{multicols}'); + if (newParts.length > 1) { + const afterMulticols = newParts[1].split('}'); + const columnCount = afterMulticols[0]; + const restOfDoc = afterMulticols.slice(1).join('}'); + + const beforeEnd = restOfDoc.split('\\end{multicols}')[0]; + const afterEnd = restOfDoc.split('\\end{multicols}').slice(1).join('\\end{multicols}'); + + contentToCompile = newParts[0] + '\\begin{multicols}' + columnCount + '}' + beforeEnd + formulaContent + '\\end{multicols}' + afterEnd; + } else { + contentToCompile = newLatex; } } else { contentToCompile = newLatex; From e57bb09feb3fd170533eef26ec5530fe9e7368b1 Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 14:25:21 -0700 Subject: [PATCH 08/10] Clean up test files --- backend/test_merge.py | 54 ------------------------------------------- backend/test_regex.py | 19 --------------- 2 files changed, 73 deletions(-) delete mode 100644 backend/test_merge.py delete mode 100644 backend/test_regex.py diff --git a/backend/test_merge.py b/backend/test_merge.py deleted file mode 100644 index 3e01693..0000000 --- a/backend/test_merge.py +++ /dev/null @@ -1,54 +0,0 @@ -import re - -# Old content body would be extracted as everything between \begin{document} and \end{document} -old_body = r""" -\begin{multicols}{4} -\raggedcolumns -\section*{ALGEBRA I} - -\subsection*{Linear Equations} - -\begin{flushleft} -\textbf{Slope} -\[ \adjustbox{max width=\linewidth}{$m = \frac{a}{b}$} \] -\\[0pt] -\end{flushleft}\end{multicols} -""" - -# New header from API (with 3 columns, tiny spacing) -new_latex = r"""\documentclass[10pt,fleqn]{article} -\usepackage[margin=0.25in]{geometry} -\usepackage{amsmath, amssymb} -\usepackage{enumitem} -\usepackage{multicol} -\usepackage{titlesec} -\usepackage{adjustbox} - -\titleformat{\section}{\normalfont\footnotesize\bfseries}{}{0pt}{} -\titleformat{\subsection}{\normalfont\scriptsize\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{0pt}{0pt} -\titlespacing*{\subsection}{0pt}{0pt}{0pt} -\setlength{\baselineskip}{4pt} - -\begin{document} -\scriptsize -\begin{multicols}{3} -\raggedcolumns -\end{multicols} -\end{document}""" - -# Frontend merge: newHeader + \begin{document} + oldBody -new_header = new_latex.split(r'\begin{document}')[0] -merged = new_header + r'\begin{document}' + old_body - -# Add \end{document} if missing -if r'\end{document}' not in merged: - merged = merged + r'\end{document}' - -print("=== MERGED OUTPUT (first 1000 chars) ===") -print(merged[:1000]) -print() -print("=== KEY CHECKS ===") -print("Has multicols{3}:", r'multicols{3}' in merged) -print("Has multicols{4}:", r'multicols{4}' in merged) -print("Has baselineskip 4pt:", r'baselineskip}{4pt' in merged) \ No newline at end of file diff --git a/backend/test_regex.py b/backend/test_regex.py deleted file mode 100644 index 3f0adaf..0000000 --- a/backend/test_regex.py +++ /dev/null @@ -1,19 +0,0 @@ -import re - -content = r"""\documentclass[10pt,fleqn]{article} -\usepackage[margin=0.25in]{geometry} -\begin{document} -\begin{multicols}{4} -\section*{ALGEBRA I} -\end{multicols} -\end{document}""" - -# JS regex: /\\begin\{document\}([\s\S]*)\\end\{document\}/ -# In JS, \\ in string becomes \ in the actual regex -pattern = r"\\begin\{document\}([\s\S]*)\\end\{document\}" -match = re.search(pattern, content) - -print("Pattern:", repr(pattern)) -print("Match found:", match is not None) -if match: - print("Body:", match.group(1)[:100]) \ No newline at end of file From f82b586cc37c14da847513b5ade965ddf0336195 Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 14:41:48 -0700 Subject: [PATCH 09/10] Fix regex parsing of new multicols in merge logic --- frontend/src/hooks/latex.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/src/hooks/latex.js b/frontend/src/hooks/latex.js index 42cd49c..9f7d03e 100644 --- a/frontend/src/hooks/latex.js +++ b/frontend/src/hooks/latex.js @@ -164,14 +164,20 @@ export function useLatex(initialData) { const newParts = newLatex.split('\\begin{multicols}'); if (newParts.length > 1) { - const afterMulticols = newParts[1].split('}'); - const columnCount = afterMulticols[0]; - const restOfDoc = afterMulticols.slice(1).join('}'); + const afterMulticols = newParts[1]; + const columnMatch = afterMulticols.match(/^\{(\d+)\}/); - const beforeEnd = restOfDoc.split('\\end{multicols}')[0]; - const afterEnd = restOfDoc.split('\\end{multicols}').slice(1).join('\\end{multicols}'); - - contentToCompile = newParts[0] + '\\begin{multicols}' + columnCount + '}' + beforeEnd + formulaContent + '\\end{multicols}' + afterEnd; + if (columnMatch) { + const columnCount = columnMatch[1]; + const afterColumn = afterMulticols.slice(columnMatch[0].length); + + const beforeEnd = afterColumn.split('\\end{multicols}')[0]; + const afterEnd = afterColumn.split('\\end{multicols}').slice(1).join('\\end{multicols}'); + + contentToCompile = newParts[0] + '\\begin{multicols}{' + columnCount + '}' + beforeEnd + formulaContent + '\\end{multicols}' + afterEnd; + } else { + contentToCompile = newLatex; + } } else { contentToCompile = newLatex; } From 96503f55a8c3156b0f17b47855b53a33085951d0 Mon Sep 17 00:00:00 2001 From: Kamisato Date: Thu, 9 Apr 2026 18:20:30 -0700 Subject: [PATCH 10/10] Fix spacing options not working - flushleft not closed, fix backslash escaping --- backend/api/latex_utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/api/latex_utils.py b/backend/api/latex_utils.py index f948923..2a48307 100644 --- a/backend/api/latex_utils.py +++ b/backend/api/latex_utils.py @@ -125,10 +125,10 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar body_lines.append(r"\end{flushleft}") in_flushleft = False if current_class is not None: - body_lines.append("") + body_lines.append("%") # Suppress paragraph break between sections escaped_class = class_name.replace("&", "\\&") body_lines.append("\\section*{" + escaped_class + "}") - body_lines.append("") + body_lines.append("%") # Suppress paragraph break after section current_class = class_name current_category = None @@ -140,7 +140,7 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar if not is_special: escaped_category = category.replace("&", "\\&") body_lines.append("\\subsection*{" + escaped_category + "}") - body_lines.append("") + body_lines.append("%") body_lines.append(r"\begin{flushleft}") in_flushleft = True current_category = category @@ -150,9 +150,10 @@ def build_latex_for_formulas(selected_formulas, columns=2, font_size="10pt", mar else: # Escape special LaTeX characters in the formula name escaped_name = name.replace("\\", "\\textbackslash ").replace("&", "\\&").replace("%", "\\%").replace("#", "\\#").replace("_", "\\_").replace("^", "\\textasciicircum ").replace("{", "\\{").replace("}", "\\}") - body_lines.append("\\textbf{" + escaped_name + "}") - body_lines.append("\\[ \\adjustbox{max width=\\linewidth}{$" + latex + "$} \\]") - body_lines.append(f"\\\\[{formula_gap}]") + body_lines.append(r"\textbf{" + escaped_name + "}") + body_lines.append(r"\[" + r" \adjustbox{max width=\linewidth}{$" + latex + r"$} " + r"\]") + if formula_gap != "0pt": + body_lines.append(r"\vspace{" + formula_gap + "}") if in_flushleft: body_lines.append(r"\end{flushleft}")