4

I need to get outline paths for certain text in MetaPost in LuaLaTeX to use them for some further manipulations, similar to what you can do in ConTeXt:

\starttext
    \startMPcode
        picture p;
        p := outlinetext("TEST") scaled 10;
        for i within p:
            j := j + 1;
            drawarrow pathpart i;
        endfor;
    \stopMPcode
\stoptext

enter image description here

outlinetext from ConTeXt doesn't seem to work under LuaLaTeX, and examples like this I couldn't get to work either. Is there a way to achieve this?

3
  • 2
    I would say glyph works as advertised. It is no different in LuaLaTeX than pdfLaTeX or LaTeX (or the TeX equivalents). To say it doesn't work under LuaLaTeX just seems misleading. Having read one of your answers, I think you rather want it to work without needing to run mpost, but that was far from obvious when reading your question here. I learnt something playing with glyph, but I would never have posted it!
    – cfr
    Commented Dec 13, 2023 at 5:59
  • Sorry, I was wrong about glyph not working in LuaLaTeX, now that I check the example from MP manual: glyph "Dcaron" of "ec-lmr10" scaled .2, it works, I just couldn't get any of the examples where the glyph comes from a picture (e.g. btex $\sum_{x=0}^{10}x^2$ etex) to work and wrongly assumed that the problem is in glyph. Commented Dec 13, 2023 at 9:57
  • Changed the question to make it clearer what I want to achieve and what I tried. Commented Dec 13, 2023 at 10:01

2 Answers 2

8

My first approach to solve this would be simply to turn on the metafun format provided by luamplib, like this:

\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\mplibsetformat{metafun} 
\begin{mplibcode}
beginfig(1);
     picture p;
        p := outlinetext("BEST") scaled 10;
        for i within p:
            j := j + 1;
            drawarrow pathpart i;
        endfor;

endfig;
\end{mplibcode}
\end{document}

The metafun format provides an outlinetext macro, but unfortunately in my copy of TexLive 2023 it does not work. If I compile the above document with lualatex, I get these error messages (and no output):

mplib warning: error in script: [string "mp.mf _outline_text(1,[===[BEST]===],[===[]===..."]:1: attempt to call a nil value (field 'mf_outline_text')
mplib warning: error in script: [string "mp.mf_get_outline_text(1)"]:1: attempt to call a nil value (field 'mf_get_outline_text')

So a second approach would be to define my own outlinetext macro. The Context implementation is rather complex and deals properly with kerning and vertical movement and so on, but it is not too hard to make a version that ignores all these complexities and works ok for simple cases.

\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\begin{mplibcode}
vardef outlinetext(expr s) = 
  save c, g, x; string c; picture g; numeric x;
  image(
    for i = 1 upto length s:
      c := substring (i-1, i) of s;
      g := glyph ASCII c of defaultfont scaled 1/64;
      x := xpart lrcorner currentpicture;
      for item within g:
        draw pathpart item shifted (x, 0);
      endfor
    endfor
  )
enddef;
beginfig(1);
  % defaultfont := "pplri8r";  % <- uncomment to try another font
  ahangle := 30;
  picture p; p = outlinetext("BESTA&?") scaled 10;
  for item within p:
    drawarrow pathpart item;
  endfor;
endfig;
\end{mplibcode}
\end{document}

Compile this with lualatex to get a PDF that looks like this:

enter image description here

3
  • Thank you! It does work, but it's not quite the same as outlinetext, since it doesn't respect current font, in addition to being limited to only simple words with no kerning, and also it doesn't seem to accept Unicode in a straightforward manner (at least it didn't work with Cyrillic for me). None of this was explicitly mentioned in the question though. I had some luck with this code tex.stackexchange.com/a/451951/99551 which, after minor modifications, made it possible to use the same font as the rest of the document as well as Unicode characters, but still no coherent text layout. Commented Dec 14, 2023 at 0:52
  • You are right, my outlinetext is much simpler than the one provided as part of context. It might be a good idea to raise a bug with the luamplib team to get them to look at fixing those error messages. You could develop my example a bit if you wanted. For example you could change "cmr10" to the normal font variable defaultfont.
    – Thruston
    Commented Dec 14, 2023 at 9:42
  • As far as I understand, it's not a luamplib's bug, but it has more to do with luaotfload (see tex.stackexchange.com/questions/524099/… ), which has some of ConTeXt's font support, but lacks at the very least .metapost.paths method required to convert glyph outlines to MetaPost paths and, presumably, something else required to convert whole boxes of text to MP. It might be a reasonable feature request, given that the implementation already exists, but as of now I only have some understanding of how .metapost.paths thing works. Commented Dec 14, 2023 at 21:22
4

It's fairly easy to trick LaTeX into loading the ConTeXt support code, so we can make a pretty close replica of the ConTeXt outlinetext function:

\documentclass{article}
\pagestyle{empty}

\usepackage{luamplib}

{\catcode`\%=12
\directlua{
    do --[[ Define some variables that ConTeXt expects to be defined ]]
        nodes.rulecodes = {}
        nodes.nuts = node.direct
        nodes.nuts.isglyph = node.direct.is_glyph
        nodes.nuts.effectiveglue = node.direct.effective_glue
        function nodes.nuts.getreplace(n)
            local _, _, h, _, _, t = node.direct.getdisc(n,true)
            return h, t
        end

        --[[ Load the ConTeXt Lua module ]]
        require("font-mps")
    end

    --[[ Expose the Lua function to Metapost ]]
    luamplib.everymplib[""] = luamplib.everymplib[""] .. [[
        def textoutline (text t) =
            runscript("textoutline('" & t & "')")
        enddef;
    ]]

    function textoutline(text)
        --[[ Create a box holding the provided text ]]
        local box = tonumber(
            luamplib.maketext(text):match("mplibtexboxid=(%d+)")
        )

        --[[ Convert the box to a MetaPost string ]]
        local mp_str = fonts.metapost.boxtomp(box)

        --[[ Clean up the MetaPost code so that it works with LaTeX ]]
        mp_str = mp_str:gsub("mfun_do_outline_text_flush%([^,]-,[^,]-,([%d.-]+),([%d.-]+),[^)]-%)(%b());", "draw %3 shifted (%1,%2);")
                       :gsub("checkbounds%b();", "")

        mp_str = "image(" .. mp_str .. ")"

        --[[ Insert the MetaPost code into the document ]]
        mp.print(mp_str)
    end
}}

\usepackage{fontspec}
\setmainfont{texgyrechorus-mediumitalic}

\begin{document}
    \begin{mplibcode}
        beginfig(1);
            picture t; t := textoutline("\\TeX \\textsf{\\TeX}") scaled 10;
            for item within t:
                fill pathpart item withcolor .9white;
                draw pathpart item dashed evenly withcolor red;
            endfor
        endfig;
    \end{mplibcode}
\end{document}

output

The example given in the MetaPost manual also works unmodified, with the condition that you can only use old-school Type 1 fonts:

\documentclass{article}
\pagestyle{empty}

\usepackage{luamplib}

\begin{document}
    \begin{mplibcode}
        beginfig(1);
            picture q; path p;
            interim ahlength := 12bp;
            interim ahangle := 25;

            q := glyph "Dcaron" of "ec-lmr10" scaled .2;

            for item within q:
                p := pathpart item;

                drawarrow p withcolor (.6,.9,.6)
                    withpen pencircle scaled 1.5;

                for j=0 upto length p:
                    pickup pencircle scaled .7;
                    draw (point j of p -- precontrol j of p)
                        dashed evenly withcolor blue;

                    draw (point j of p -- postcontrol j of p)
                        dashed evenly withcolor blue;

                    pickup pencircle scaled 3;
                    draw precontrol j of p withcolor red;
                    draw postcontrol j of p withcolor red;

                    pickup pencircle scaled 2;
                    draw point j of p withcolor black;
                endfor
            endfor
        endfig;
    \end{mplibcode}
\end{document}

output

Thruston's answer shows how to convert the above glyph-drawing function into the desired string-drawing function.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .