[Question also asked on the ConTeXt mailing list, I'll post a solution from either in the other] The following minimal example illustrates my problem. I need to typeset a generic string (which is read from an XML file using lua) from METAPOST (as label using textext()). Running the below file with ConTeXt produces a result which illustrates that the curly braces are lost when going from Lua to METAPOST, but escaping them doesn't work. I am trying to prevent the use of \type{} as the label in the end needs to be a small paragraph, so during typesetting a TeX \vbox.

If it is not possible (which I guess might be the case given how TeX works), I need to split the text from the XML up in whitespace separated elements and put each of these in a type{} and then combine these in a paragraph. That will be complex and messy.



function warn( ... )
  texio.write_nl("-----> " .. string.format(...))

local function mpLabelString( xmlLabelString)
  -- Returns a string where each " is replaced by a METAPOST compatible result, except for outer double quotes"
  rep = {
      [1] = { "\"", "\"&ditto&\""   },
      -- DOESN'T WORK: [2] = { "\\", "\\\\" },
  local tmpString = string.formatters( "%!tex!", xmlLabelString)
  warn( "STRING.FORMAT XML \"%s\"", xmlLabelString)
  warn( "STRING.FORMAT TeX-ed \"%s\"", tmpString)
  warn( "STRING.FORMAT Replaced \"%s\"", lpeg.replacer(rep):match(tmpString))
  return lpeg.replacer(rep):match(tmpString)

function warnAndConTeXt( ...)
  warn( ...)
  context( ...)

function moduledata.test( filename)
  local labelString
  context( "The string to typeset is:\\par\\type-{Label} \"a\" [Text]!-")
  context( "\\par The attempts are:")
  context( "\\par1. \\type-Label Text-")
  context( "\\par2. \\type-Label [Text]!-")
  context( "\\par3. \\type-Label \"a\" [Text]!-")
  context( "\\par4. \\type-{Label} [Text]!-")
  context( "\\par5. \\type-{Label} \"a\" [Text]!-")
  context.startMPpage { instance = "doublefun" }
  context( "picture pic;")
  labelString = "1. Label Text OK"
  warnAndConTeXt( "pic := Foo( 0, 0, 150, 50, \"%s\");", mpLabelString( labelString))
  labelString = "2. Label [Text]! OK"
  warnAndConTeXt( "pic := Foo( 0, -75, 150, 50, \"%s\");", mpLabelString( labelString))
  labelString = "3. Label \"a\" [Text]! OK"
  warnAndConTeXt( "pic := Foo( 0, -150, 150, 50, \"%s\");", mpLabelString( labelString))
  labelString = "4. {Label} [Text]! MISSING curly braces"
  warnAndConTeXt( "pic := Foo( 0, -225, 150, 50, \"%s\");", mpLabelString( labelString))
  labelString = "5. {Label} \"a\" [Text]! MISSING curly braces"
  warnAndConTeXt( "pic := Foo( 0, -300, 150, 50, \"%s\");", mpLabelString( labelString))
  context( "drawdot (0,0) withpen pencircle scaled 4 withcolor red;")






vardef makeTeXLabel( expr w, h, name) =
  show "NAME makeTeXLabel:", name;
  save p; picture p ;
  save s; string s;
  s := "\framed{" & name & "}"; % Curly braces will be missing. I need this to work.
  % s := "\type-" & name & "-"; % Curly braces are displayed, but this must become a vbox in the end, so can't use it
  show "SCAN:", s;
  p := textext( s);

vardef Foo( expr xpos, ypos, width, height, str) =
  show "NAME Foo:", str; % Backslashes are already gone here
  save pic; picture pic;
  pic := makeTeXLabel( width, height, str) shifted (xpos, ypos);
  draw pic;




2 Answers 2


Your “minimal” example is pretty long, so I hope I understood the question correctly. So what you want to do is take a Lua string, pass that through TeX to MetaPost from where it will be again passed to TeX to be typeset as a label.

The problem in your code (as far as I can tell) is that you are missing the last step. You assume that MetaPost typesets the text but that is not true. So on top of escaping all the " you also have to escape all the special TeX characters. Luckily there is a builtin function for that context.escape.


local function MPescape(str)
    str = context.escape(str)
    str = str:gsub("\"", "\" & ditto & \"")
    return str

local labelString = "5. $ ^ _ % {Label} \"a\" [Text]!"

context.verbatim("draw textext(\"%s\");", MPescape(labelString))


enter image description here

  • Thanks for trying to help. I am not assuming METAPOST typesets the text (though with the label() command it minimally can). I know that by using textext() from METAPOST it gets passed on to TeX again. Taco came up with a better idea for me which I will replicate in another answer. One reason it was better was because I also want to be able to print backslashes in the labels.
    – gctwnl
    Apr 4, 2020 at 8:33

Taco Hoekwater told me what a better approach was. I am now using the following function:

function doubleQuotableEscapedConTeXtString( str)
  warn( "STRING.ESCAPED in: \"%s\"", str)
  warn( "STRING.CONTEXTESCAPED: \"%s\"", context.escape(str))
  local rep = {
      [1] =  { '{', '{\\textbraceleft}' },
      [2] =  { '}', '{\\textbraceright}' },
      [3] =  { '#', '{\\texthash}' },
      [4] =  { '$', '{\\textdollar}' },
      [5] =  { '&', '{\\textampersand}' },
      [6] =  { '%', '{\\textpercent}' },
      [7] =  { '\\','{\\textbackslash}' },
      [8] =  { '|', '{\\textbar}' },
      [9] =  { '_', '{\\textunderscore}' },
      [10] = { '~', '{\\textasciitilde}' },
      [11] = { '^', '{\\textasciicircum}' },
      [12] = { '"', "\"&ditto&\"" },
  warn( "STRING.ESCAPED out: \"%s\"", lpeg.replacer(rep):match(str))
  return lpeg.replacer(rep):match(str)

(The warnings are just for debugging). The advantage of this is that I do not use verbatim anywhere and it is very robust.

-----> STRING.ESCAPED in: "5. {Label} \DIR "a" [Text]!"
-----> STRING.CONTEXTESCAPED: "5. \{Label\} \\DIR "a" [Text]!"
-----> STRING.ESCAPED out: "5. {\textbraceleft}Label{\textbraceright} {\textbackslash}DIR "&ditto&"a"&ditto&" [Text]!"

I could actually replace the dittos by TeX commands for the " characters and get rid of the dittos altogether.

You must log in to answer this question.

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