Javis.jl icon indicating copy to clipboard operation
Javis.jl copied to clipboard

LaTeX without npm

Open davibarreira opened this issue 2 years ago • 15 comments

Hello Javis developers. This is actually a tip. I've been playing around with Luxor, and I've implemented LaTeX without the need to install LaTeX in the users computer. To do so, I've used the package MathTeXEngine.jl. This awesome package is the one used by Makie to write LaTeX. Here is my implementation for a package that I'm working on

"""
    latex_text_size(lstr::LaTeXString)
Returns the width and height of a latex string.
"""
function latex_text_size(lstr::LaTeXString)
    sentence = generate_tex_elements(lstr)
    els = filter(x -> x[1] isa TeXChar, sentence)
    chars = [x[1].char for x in els]
    fonts = [x[1].font for x in els]
    pos_x = [x[2][1] for x in els]
    pos_y = [x[2][2] for x in els]
    scales = [x[3] for x in els]
    extents = [get_extent(f, c) for (f, c) in zip(fonts, chars)]

    textw = []
    texth= []
    for i in 1:length(extents)
        textw = push!(textw,height_insensitive_boundingbox(extents[i], fonts[i]).widths[1]*scales[i]+pos_x[i])
        texth = push!(texth,height_insensitive_boundingbox(extents[i], fonts[i]).widths[2]*scales[i]-pos_y[i])
    end
    return (maximum(textw), maximum(texth))
end


"""
    Luxor.text(lstr::LaTeXString, valign=:baseline, halign=:left; kwargs...)
Draws LaTeX string using `MathTexEngine.jl`. Hence, uses ModernCMU as font family.
Note that `valign` is not perfect.
This function assumes that the axis are in the standard Luxor directions,
i.e. ↓→.
"""
function Luxor.text(lstr::LaTeXString, pt::Point; valign=:baseline, halign=:left, kwargs...)

    # Function from MathTexEngine
    sentence = generate_tex_elements(lstr)

    # Get current font size.
    textsize = get_fontsize()

    textw, texth = latex_text_size(lstr)
    
    if halign === :left
        translate_x = 0
    elseif halign === :right
        translate_x = -textw * textsize
    elseif halign === :center
        translate_x = -textw/2 * textsize
    end
    
    if valign === :baseline
        translate_y = 0
    elseif valign === :top
        translate_y = -textsize
    elseif valign === :bottom
        translate_y = texth * textsize
    elseif valign === :middle
        translate_y = textsize/2
    end

    # Writes text using ModernCMU font.
    for text in sentence
        if text[1] isa TeXChar
            @layer begin
                translate(translate_x,translate_y)
                fontface(text[1].font.family_name)
                fontsize(textsize*text[3])
                Luxor.text(string(text[1].char), pt + Point(text[2]...)*textsize*(1,-1))
            end
        elseif text[1] isa HLine
            @layer begin
                translate(translate_x,translate_y)
                pointstart = pt+Point(text[2]...)*textsize*(1,-1)
                pointend = pointstart + Point(text[1].width,0)*textsize
                setline(1*text[1].thickness*textsize)
                line(pointstart, pointend,:stroke)
            end
        end
    end
end

davibarreira avatar Dec 13 '21 11:12 davibarreira

Awesome. Thanks a lot! Hopefully this will fix also our #212 issue 😅 I'll have a look at integrating this into Javis

Wikunia avatar Dec 13 '21 11:12 Wikunia

No problem! I suggested adding the LaTeX support directly to Luxor, but since it requires adding MathTeXEngine as a dependency, it was reject. I do understand though. So hopefully those of use who need LaTeX can add this small chunk of code and move on.

BTW, this is not a perfect implementation. There is some small issues with aligning. Feel free to improve on this code, so I can steal it back :grin:

davibarreira avatar Dec 13 '21 11:12 davibarreira

Haha sounds good. Yeah we would have the ability to have an extra JavisLatex package otherwise but I think for us it makes sense to directly integrate it.

Wikunia avatar Dec 13 '21 11:12 Wikunia

Out of curiosity, how does this work? Does it use the artifact system to package it within the Julia ecosystem? I've been looking into Tectonic so was curious if this was used under the hood or what was. Regardless, thanks @davibarreira and great to cross paths again! Hope all has been well!

TheCedarPrince avatar Dec 13 '21 14:12 TheCedarPrince

Hey @TheCedarPrince ! Yeah, I still owe you the answers for Chapter 2. Finished reading it, but haven't implemented. End of semester, things are chaotic again. About MathTeXEngine. Not an expert at all, but from what I understood, the package uses the CMU font and maps the LaTeX commands to the respective unicode characters. In other words, it's just a smart way to turning latex into unicode. Here is a more detailed account: MathTeXEngine parses your LaTeXString from left to right. Each latex symbol is transformed in either a unicode, a vertical line or a horizontal line, and also returns the position of the object. Take the following example:

L"\frac{\mu}{x}"

Now, MathTexEngine will recognize that the "\frac{}{}" will generate a horizontal line in a position, say (0,5), meaning that it's at the start of the string and at the middle in terms of vertical alignment. Next, it will parse to you that "\mu" is actually the unicode for \mu, at position (0,6). Finally, "x" is just "x" at position (0,-6).

This is what it returns

horizontal_line at (0,5)
\mu unicode at (0, 6)
x at (0,-6)

Now you do you :) I mean, it's your job to draw this. This is where my functions come from. I use Luxor to draw this with SVG. Hope it's clear.

davibarreira avatar Dec 13 '21 14:12 davibarreira

LaTeX support directly to Luxor, but since it requires adding MathTeXEngine as a dependency, it was reject.

It's an open issue (https://github.com/JuliaGraphics/Luxor.jl/issues/124) and not rejected. 😂

cormullion avatar Dec 26 '21 22:12 cormullion

Hey @Wikunia , I extensively tested @davibarreira 's suggestion and I can confirm, using his solution is completely feasible. I think the approach will actually be soon added into the issue @cormullion referenced, which, if so, only means that a user would have to install Lyx fonts onto their computer and not all of LaTeX nor fiddle with npm. A much easier solution I'd imagine!

TheCedarPrince avatar Dec 28 '21 02:12 TheCedarPrince

Great thanks for testing @TheCedarPrince will it be integrated into Luxor or another package or do we have to do it in Javis directly?

Wikunia avatar Dec 28 '21 08:12 Wikunia

It should be integrated into Luxor.jl with Requires.jl.

davibarreira avatar Dec 29 '21 20:12 davibarreira

It should be integrated into Luxor.jl with Requires.jl.

The idea is to extend Luxor.text to be used on LaTeXStrings. So things should work almost out of the box. The only requirements will probably be that the user will need to install CMU font, and MathTeXEngine.jl needs to be loaded, since the dependency will not be hard coded. If you wish Javis users to be able to use it without loading MathTeXEngine, you could make it a dependency in you package and load it.

davibarreira avatar Dec 29 '21 20:12 davibarreira

Hey @Wikunia, @cormullion, and @davibarreira ! After some tinkering thanks to Davi's awesome work on this, here is so far what I have found being able to be rendered using MathTeXEngine: https://github.com/Kolaru/MathTeXEngine.jl#supported-constructions

A more visual example that I made is here:

image

However, it would appear that MathTeXEngine cannot yet fully subsume all parts of LaTeX just yet. One crucial aspect is the lack of rendering matrices as seen here: https://github.com/Kolaru/MathTeXEngine.jl/issues/48 That unfortunately makes it so we cannot replace the LaTeX parsing system in Javis yet as we would be unable to support morphing.

However, as you were suggesting earlier, actually, @Wikunia , I would rather advocate to create a new package in the JuliaAnimators called like JavisTeX or JavisLaTeX to move some of this code from out of core Javis to other modules. Just thinking it might make hacking on things easier (especially for new contributors) and more accessible to figure out LaTeX support in Javis. What do you think?

TheCedarPrince avatar Jan 03 '22 04:01 TheCedarPrince

@TheCedarPrince , perhaps improving MathTeXEngine is a better route. It doesn't seem like supporting matrices would be hard.

davibarreira avatar Jan 03 '22 13:01 davibarreira

Oh, I just saw that you opened an issue. Nice! I myself am thinking of contributing to MathTeXEngine. So I might try to implement the matrix notation.

davibarreira avatar Jan 03 '22 13:01 davibarreira

I'll follow-up with you over at the issue here @davibarreira : https://github.com/Kolaru/MathTeXEngine.jl/issues/48

But, I was thinking about having a Javis TeX package for at least a staging ground for experimentation and development of Javis specific TeX handling. Plus with JSoC coming up, might be a good area for folks to contribute to potentially. :)

TheCedarPrince avatar Jan 03 '22 16:01 TheCedarPrince

From #483 which I opened too soon, another alternative is to: Use an approach similar to the old one from MakieTeX.jl (latexstring/latex document -> compiled latex as pdf -> SVG through dvisvgm). There is a lightweight TeX compiler one can use which is called tectonic, which also ships as a JLL.

The reason I have used this complicated pipeline as opposed to MathJax is because it allows the user to use arbitrary LaTeX packages (like amsmath, physics, tikz, lstlistings, et cetera. Arbitrary custom commands are also supported as a consequence, so one can paste sections of preamble from their paper and have identical LaTeX in their figures.

I have switched MakieTeX to use Poppler to render direct from PDF now, but you can see the old implementation in v0.0.4.

asinghvi17 avatar May 22 '22 22:05 asinghvi17