diff --git a/src/engine/layout.jl b/src/engine/layout.jl index 4eafb07..ff835a9 100644 --- a/src/engine/layout.jl +++ b/src/engine/layout.jl @@ -219,6 +219,38 @@ function tex_layout(expr, state) ], [1, shrink, shrink] ) + elseif head == :env && args[1] == "matrix" + entries = permutedims(tex_layout.(args[2], state)) + entry_wdts = inkwidth.(entries) + entry_hgts = inkheight.(entries) + + wdts = vec(maximum(entry_wdts, dims=2)) + hgts = vec(maximum(entry_hgts, dims=1)) + + hor_sep = 2*xheight(font_family) + vert_sep = 0.5*xheight(font_family) + + padded_wdts = wdts .+ hor_sep + padded_hgts = hgts .+ vert_sep + + row_offsets = reverse(cumsum(reverse([padded_hgts..., 0]))) + column_offsets = cumsum([0, padded_wdts...]) + + linear_entries = TeXElement[] + offsets = Point2f[] + for I in eachindex(IndexCartesian(), entries) + offset = (column_offsets[I[1]], row_offsets[I[2]]) + + push!(linear_entries, entries[I]) + push!(offsets, offset) + + # Uncomment to see vertical extent of each matrix entry + push!(linear_entries, HLine(inkwidth(entries[I]), thickness(font_family))) + push!(offsets, offset) + push!(linear_entries, HLine(inkwidth(entries[I]), thickness(font_family))) + push!(offsets, offset .+ (0, inkheight(entries[I]))) + end + return Group(linear_entries, offsets) end catch # TODO Better error diff --git a/src/parser/commands_data.jl b/src/parser/commands_data.jl index a03364f..157c31a 100644 --- a/src/parser/commands_data.jl +++ b/src/parser/commands_data.jl @@ -110,5 +110,12 @@ punctuation_symbols = split(raw", ; . !") delimiter_symbols = split(raw"| / ( ) [ ] < >") font_names = split(raw"rm cal it tt sf bf default bb frak scr regular") +## +## Env +## + +supported_env = split("matrix pmatrix bmatrix") + + # TODO Add to the parser what come below, if needed wide_accent_commands = split(raw"\widehat \widetilde \widebar") \ No newline at end of file diff --git a/src/parser/commands_registration.jl b/src/parser/commands_registration.jl index 93e3f89..fc8cc98 100644 --- a/src/parser/commands_registration.jl +++ b/src/parser/commands_registration.jl @@ -51,6 +51,8 @@ function get_symbol_char(command) return first(latex_symbols[command]) end +is_supported(env) = env in supported_env + # Numbers for char in join(0:9) symbol_to_canonical[char] = TeXExpr(:digit, char) @@ -66,6 +68,10 @@ command_to_canonical[raw"\overline"] = TeXExpr(:argument_gatherer, [:overline, 1 command_to_canonical[raw"\{"] = TeXExpr(:delimiter, '{') command_to_canonical[raw"\}"] = TeXExpr(:delimiter, '}') +# Commands for env +command_to_canonical[raw"\begin"] = TeXExpr(:argument_gatherer, [:begin_env, 1]) +command_to_canonical[raw"\end"] = TeXExpr(:argument_gatherer, [:end_env, 1]) + ## ## Commands from the commands_data.jl file ## diff --git a/src/parser/parser.jl b/src/parser/parser.jl index 0062b04..09fd32a 100644 --- a/src/parser/parser.jl +++ b/src/parser/parser.jl @@ -16,7 +16,7 @@ function show_state(io::IO, stack, position, data) if position > lastindex(data) println(io, "after the end of the data") println(io, data) - else + elseif position > 0 # Compute the index of the char from the byte index position_to_id = zeros(Int, lastindex(data)) id = 0 @@ -135,7 +135,7 @@ function _end_group!(stack, p, data) current_head(stack) == :skip_char && return current_head(stack) != :group && throw( - TeXParseError("Unexpected '}' at position $(p-1)", stack, p, data)) + TeXParseError("unexpected '}'", stack, p, data)) group = pop!(stack) # Remplace empty groups by a zero-width space @@ -146,9 +146,9 @@ function _end_group!(stack, p, data) group = first(group.args) end - if requirement(stack) == :argument - push_to_current!(stack, group) + push_to_current!(stack, group) + if requirement(stack) == :argument command_builder = current(stack) head, required_n_args = command_builder.args[1:2] args = command_builder.args[3:end] @@ -156,19 +156,69 @@ function _end_group!(stack, p, data) # Check if the argument gatherer got all the needed arguments if required_n_args == length(args) pop!(stack) - command = TeXExpr(head, args) - push_to_current!(stack, command) + if head == :begin_env + # Transform the content of the argument back to a single string + env_name = String(Char.(first(args).args)) + !is_supported(env_name) && throw( + TeXParseError( + "env '$env_name' is not supported", + stack, p, data)) + push!(stack, TeXExpr(:env, Any[env_name])) + push!(stack, TeXExpr(:env_row)) + push!(stack, TeXExpr(:env_cell)) + elseif head == :end_env + env_name = String(Char.(args[1].args)) + current(stack).head != :env_cell && throw( + TeXParseError( + "unexpected end of environnement '$env_name'", + stack, p, data)) + + cell = pop!(stack) + push_to_current!(stack, cell) + row = pop!(stack) + push_to_current!(stack, row) + open_env_name = current(stack).args[1] + env_name != open_env_name && throw( + TeXParseError( + "found an end for environnement '$env_name', but it is not matching the currently open env", + stack, p, data)) + + # Make a matrix of the arguments + env_data = pop!(stack) + rows = env_data.args[2:end] + n_cols = maximum([length(row.args) for row in rows]) + matrix = fill(TeXExpr(:space, 0), length(rows), n_cols) + for (i, row) in enumerate(rows) + for (j, cell) in enumerate(row.args) + # Remove the :env_cell wrapper + if length(cell.args) == 1 + matrix[i, j] = first(cell.args) + else + matrix[i, j] = TeXExpr(:group, cell.args) + end + end + end + + env = TeXExpr(:env, [env_name, matrix]) + push_to_current!(stack, env) + else + command = TeXExpr(head, args) + push_to_current!(stack, command) + end end - else - push_to_current!(stack, group) end end function _push_char!(stack, p, data) - if current_head(stack) == :skip_char - pop!(stack) - elseif isvalid(data, p-1) - char = data[prevind(data, p)] + current_head(stack) == :skip_char && return pop!(stack) + !isvalid(data, p-1) && return + + char = data[prevind(data, p)] + + if current_head(stack) == :env_cell && char == '&' + push_to_current!(stack, pop!(stack)) + push!(stack, TeXExpr(:env_cell)) + else push_to_current!(stack, canonical_expr(char)) end end @@ -203,7 +253,10 @@ function _setup_decorated!(stack, p, data) end end -_begin_command_builder!(stack, p, data) = push!(stack, TeXExpr(:command_builder)) +function _begin_command_builder!(stack, p, data) + current_head(stack) == :skip_char && return pop!(stack) + push!(stack, TeXExpr(:command_builder)) +end function _end_command_builder!(stack, p, data) if current_head(stack) == :command_builder @@ -231,6 +284,16 @@ function _end_command_builder!(stack, p, data) TeXParseError("unexpected '\\right' at position $(p-1)", stack, p, data)) push!(stack, TeXExpr(:right_delimiter)) + elseif command_name == "\\" + current(stack).head != :env_cell && throw( + TeXParseError("'\\' for newline is only supported inside env", + stack, p, data)) + current_cell = pop!(stack) + push_to_current!(stack, current_cell) + current_row = pop!(stack) + push_to_current!(stack, current_row) + push!(stack, TeXExpr(:env_row)) + push!(stack, TeXExpr(:env_cell)) elseif haskey(command_to_canonical, command) expr = command_to_canonical[command] @@ -246,7 +309,7 @@ function _end_command_builder!(stack, p, data) stack, p, data)) end - if skip_char + if skip_char push!(stack, TeXExpr(:skip_char)) end end diff --git a/src/parser/texexpr.jl b/src/parser/texexpr.jl index c41093d..483efb2 100644 --- a/src/parser/texexpr.jl +++ b/src/parser/texexpr.jl @@ -116,8 +116,53 @@ function AbstractTrees.printnode(io::IO, texexpr::TeXExpr) end end -function Base.show(io::IO, texexpr::TeXExpr) - print_tree(io, texexpr, maxdepth=10) +to_latex(::Nothing) = nothing + +function to_latex(texexpr::TeXExpr) + head = texexpr.head + args = texexpr.args + + head in [:char, :digit, :punctuation, :symbol] && return string(first(args)) + head == :function && return "\\$(first(args))" + head == :frac && return "\\frac{$(to_latex(args[1]))}{$(to_latex(args[2]))}" + head == :group && return join(to_latex.(args), " ") + head == :sqrt && return "\\sqrt{$(to_latex(first(args)))}" + head == :spaced && return string(first(first(args).args)) + head == :space && return "" + + if head in [:decorated, :integral, :underover] + core, sub, sup = to_latex.(args) + + if !isnothing(sub) + if length(sub) == 1 + core *= "_$sub" + else + core *= "_{$sub}" + end + end + + if !isnothing(sup) + if length(sup) == 1 + core *= "^$sup" + else + core *= "^{$sup}" + end + end + return core + end + + # TODO :accent, :space properly + + # Fallback + return "to_latex($texexpr)" +end + +function Base.show(io::IO, ::MIME"text/plain", texexpr::TeXExpr) + if haskey(io, :compact) && io[:compact] + print(io, "TeX\"$(to_latex(texexpr))\"") + else + return print_tree(io, texexpr, maxdepth=10) + end end function Base.:(==)(tex1::TeXExpr, tex2::TeXExpr)