Skip to content

Instantly share code, notes, and snippets.

@hmenke
Last active January 28, 2023 01:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hmenke/6e8ff7c90a5e5df3c4895f60059a2ef7 to your computer and use it in GitHub Desktop.
Save hmenke/6e8ff7c90a5e5df3c4895f60059a2ef7 to your computer and use it in GitHub Desktop.
Emulate XeTeX font primitives in LuaTeX
\ifdefined\directlua
\input XeTeXemulate.sty
\input luaotfload.sty
\XeTeXemulate
\fi
\font\1="[lmroman10-italic.otf]" at 10pt\1
\font\2=cmr10 at 10pt
\edef\x{\the\XeTeXfonttype\1}“\x”
\edef\x{\the\XeTeXfonttype\2}“\x”
\edef\x{\the\XeTeXversion}“\x”
\edef\x{\XeTeXrevision}“\x”
\edef\x{\char\XeTeXfirstfontchar\1}“\x”
\edef\x{\char\XeTeXfirstfontchar\2}“\x”
\edef\x{\char\XeTeXlastfontchar\1}“\x”
\edef\x{\char\XeTeXlastfontchar\2}“\x”
\edef\x{\XeTeXglyph150}“\x”
\edef\x{\the\XeTeXcountglyphs\1}“\x”
\edef\x{\the\XeTeXcountglyphs\2}“\x”
\edef\x{\XeTeXglyphname\1 150}“\x”
\edef\x{\the\XeTeXglyphindex"acircumflexgrave"\relax}“\x”
\edef\x{\the\XeTeXglyphindex"acircumflexgrave" }“\x”
\edef\x{\the\XeTeXcharglyph"00A5}“\x”
\edef\x{\the\XeTeXglyphbounds 1 \XeTeXcharglyph`f}“\x”
\edef\x{\the\XeTeXglyphbounds 2 \XeTeXcharglyph`f}“\x”
\edef\x{\the\XeTeXglyphbounds 3 \XeTeXcharglyph`f}“\x”
\edef\x{\the\XeTeXglyphbounds 4 \XeTeXcharglyph`f}“\x”
\edef\x{[\the\XeTeXglyphindex yen ] x}“\x”
\edef\x{[\the\XeTeXglyphindex "yen" ] x}“\x”
\edef\x{[\the\XeTeXglyphindex"yen"\relax] x }“\x”
\edef\x{[\the\XeTeXglyphindex"yen"] x }“\x”
\edef\x{[\the\XeTeXglyphindex "yen" *2]}“\x”
\edef\x{[\the\XeTeXglyphindex "yen"\relax*2]}“\x”
\edef\x{[\XeTeXglyphindex"yen"] }“\meaning\x”
\edef\x{[\XeTeXglyphindex] }“\meaning\x”
\bye
\ifcsname ProvidesPackage\endcsname
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{XeTeXemulate}[2018/09/03 v0.0 Emulate the XeTeX font primitives in LuaTeX]
\fi
\def\XeTeXemulate{%
\chardef\XeTeXversion=0
\def\XeTeXrevision{.99999}%
% Circumvent \outer error
\csname newcount\endcsname\XeTeXtracingfonts \XeTeXtracingfonts=0
\csname newcount\endcsname\XeTeXuseglyphmetrics \XeTeXuseglyphmetrics=1
%
\let\XeTeXmathcode\Umathcode
%
\protected\def\XeTeXfonttype{%
\directlua{
local fid = token.scan_int()
local tfmdata = font.getfont(fid)
local fonttype = tfmdata.format == "opentype" and 2 or 0
tex.print([[\numexpr]] .. fonttype .. [[\relax]])
}\fontid
}%
%
\protected\def\XeTeXfirstfontchar{%
\directlua{
local fid = token.scan_int()
local tfmdata = font.getfont(fid)
local min
for slot in pairs(tfmdata.characters) do
min = min and (slot < min and slot or min) or slot
end
tex.print([[\numexpr]] .. min .. [[\relax]])
}\fontid
}%
%
\protected\def\XeTeXlastfontchar{%
\directlua{
local fid = token.scan_int()
local tfmdata = font.getfont(fid)
local max
for slot in pairs(tfmdata.characters) do
max = max and (slot > max and slot < 2^16 and slot or max) or slot
end
tex.print([[\numexpr]] .. max .. [[\relax]])
}\fontid
}%
%
\def\XeTeXglyph{%
\directlua{
local index = token.scan_int()
local tfmdata = font.getfont(font.current())
if tfmdata.format \string~= "opentype" then
tex.error([[Cannot use \string\XeTeXglyph]] .. [[ with ]] ..
tfmdata.name .. [[; not a native platform font]])
end
for slot, char in pairs(tfmdata.characters) do
if char.index == index then
tex.print(utf.char(char.unicode))
return
end
end
}}%
%
\protected\def\XeTeXcountglyphs{%
\directlua{
local fid = token.scan_int()
local tfmdata = font.getfont(fid)
local count = 0
if tfmdata.format == "opentype" then
for _ in pairs(tfmdata.characters) do
count = count + 1
end
end
tex.print([[\numexpr]] .. count .. [[\relax]])
}\fontid
}%
%
\def\XeTeXglyphname{%
\directlua{
local fid = token.scan_int()
local index = token.scan_int()
local tfmdata = font.getfont(fid)
if tfmdata.format \string~= "opentype" then
tex.error([[Cannot use \string\XeTeXglyphname]] .. [[ with ]] ..
tfmdata.name .. [[; not a native platform font]])
end
local count = 0
for slot, char in pairs(tfmdata.shared.rawdata.descriptions) do
if char.index == index then
tex.print(char.name)
return
end
end
}\fontid
}%
%
\protected\def\XeTeXglyphindex{%
\directlua{
local k = string.gsub(token.scan_string(),'"','')
token.scan_keyword(' ') % remove optional space
local tfmdata = font.getfont(font.current())
if tfmdata.format \string~= "opentype" then
tex.error([[Cannot use \string\XeTeXglyphindex]] .. [[ with ]] ..
tfmdata.name .. [[; not a native platform font]])
end
local index = 0
for slot, char in pairs(tfmdata.shared.rawdata.descriptions) do
if char.name == k then
index = char.index
break
end
end
tex.print([[\numexpr]] .. index .. [[\relax]])
}}%
%
\protected\def\XeTeXcharglyph{%
\directlua{
local id = token.scan_int()
local tfmdata = font.getfont(font.current())
if tfmdata.format \string~= "opentype" then
tex.error([[Cannot use \string\XeTeXcharglyph]] .. [[ with ]] ..
tfmdata.name .. [[; not a native platform font]])
end
local index = tfmdata.characters[id].index
tex.print([[\numexpr]] .. index .. [[\relax]])
}}%
%
\protected\def\XeTeXglyphbounds{%
\directlua{
local edge = token.scan_int()
local slot = token.scan_int()
local tfmdata = font.getfont(font.current())
if tfmdata.format \string~= "opentype" then
tex.error([[Cannot use \string\XeTeXglyphname]] .. [[ with ]] ..
tfmdata.name .. [[; not a native platform font]])
end
for _,char in pairs(tfmdata.shared.rawdata.descriptions) do
if char and char.index == slot then
local bbox = char.boundingbox
local dimen = 0
if edge == 1 then
dimen = bbox and bbox[1] / 100 * 2^16 or 0
elseif edge == 2 then
dimen = tfmdata.characters[char.unicode].height
elseif edge == 3 then
dimen = bbox and (char.width - bbox[3]) / 100 * 2^16 or 0
elseif edge == 4 then
dimen = tfmdata.characters[char.unicode].depth
end
tex.print([[\dimexpr]] .. tex.round(dimen) .. [[sp\relax]])
break
end
end
}}%
}
\endinput
@davidcarlisle
Copy link

currently

\typeout{YEN: \the\XeTeXglyphindex"yen" }

produces

YEN: 638\ignorespaces

in luatex as \ignorespaces isn't expandable, also it modifies the value of \count@ which may cause some surprises.

May I suggest


\documentclass{article}

\makeatletter
\ifx\directlua\@undefined\else

  \protected\def\XeTeXglyphindex"#1"{%
    \directlua{
      local tfmdata = font.getfont(font.current())
      if tfmdata.format \string~= "opentype" then
          tex.error([[Cannot use \string\XeTeXglyphindex]] .. [[ with ]] ..
                    tfmdata.name .. [[; not a native platform font]])
      end
      local index = 0
      for slot, char in pairs(tfmdata.shared.rawdata.descriptions) do
          if char.name == "\luaescapestring{#1}" then
              index = char.index
              break
          end
      end
      tex.print([[\numexpr(]] .. index .. ')')
    }}%
%

\fi

\begin{document}

\typeout{YEN 1: [\the\XeTeXglyphindex"yen" ] x}
\typeout{YEN 2: [\the\XeTeXglyphindex"yen"\relax] x }% a bit weird (so emulation drops \relax)
\typeout{YEN 3: [\the\XeTeXglyphindex"yen"] x }% too weird to contemplate emulating
\typeout{YEN 4: [\XeTeXglyphindex"yen"] }
\typeout{YEN 5: [\XeTeXglyphindex] }

\end{document}

@davidcarlisle
Copy link

davidcarlisle commented Sep 3, 2018

It may be better to add the \relax in the tex.print to prevent following tokens being takes as the numexpr, I added (..) but that would still gobble *2 etc. Also the " in xetex are actually optional, this works

\typeout{YEN 1b: [\the\XeTeXglyphindex yen ] x}

@davidcarlisle
Copy link

better emulation using token scanner

\documentclass{article}

\makeatletter
\ifx\directlua\@undefined\else

  \protected\def\XeTeXglyphindex"#1"{%
    \directlua{
      local tfmdata = font.getfont(font.current())
      if tfmdata.format \string~= "opentype" then
          tex.error([[Cannot use \string\XeTeXglyphindex]] .. [[ with ]] ..
                    tfmdata.name .. [[; not a native platform font]])
      end
      local index = 0
      for slot, char in pairs(tfmdata.shared.rawdata.descriptions) do
          if char.name == "\luaescapestring{#1}" then
              index = char.index
              break
          end
      end
      tex.print([[\numexpr(]] .. index .. ')')
    }}%
%
  \protected\def\XeTeXglyphindex{%
    \directlua{
      local k = string.gsub(token.scan_string(),'"','')
      local sp = token.scan_keyword(' ')
      local tfmdata = font.getfont(font.current())
      if tfmdata.format \string~= "opentype" then
          tex.error([[Cannot use \string\XeTeXglyphindex]] .. [[ with ]] ..
                    tfmdata.name .. [[; not a native platform font]])
      end
      local index = 0
      for slot, char in pairs(tfmdata.shared.rawdata.descriptions) do
          if char.name == k then
              index = char.index
              break
          end
      end
      tex.print([[\numexpr]] .. index .. [[\relax]])
    }}%
%

\fi

\begin{document}

\typeout{YEN 0: [\the\XeTeXglyphindex yen ] x}
\typeout{YEN 1: [\the\XeTeXglyphindex "yen" ] x}
\typeout{YEN 2: [\the\XeTeXglyphindex"yen"\relax] x }
\typeout{YEN 3: [\the\XeTeXglyphindex"yen"] x }
\typeout{YEN 4: [\XeTeXglyphindex"yen"] }
\typeout{YEN 5: [\XeTeXglyphindex] }
\typeout{YEN 6: [\the\XeTeXglyphindex "yen" *2]}
\typeout{YEN 7: [\the\XeTeXglyphindex "yen"\relax*2]}
\end{document}

with xelatex


YEN 0: [638] x
YEN 1: [638] x
YEN 2: [638\relax ] x 
YEN 3: [0x 
YEN 4: [\XeTeXglyphindex "yen"] 
YEN 5: [\XeTeXglyphindex ] 
YEN 6: [638*2]
YEN 7: [638\relax *2]

with lualatex

YEN 0: [638] x
YEN 1: [638] x
YEN 2: [638\relax ] x 
YEN 3: [0x 
YEN 4: [\XeTeXglyphindex "yen"] 
YEN 5: [\XeTeXglyphindex ] 
YEN 6: [638*2]
YEN 7: [638\relax *2]

@kberry
Copy link

kberry commented Jan 3, 2023

IMHO, it would be great to have this released ...

@kberry
Copy link

kberry commented Jan 8, 2023

  1. it seems it would be good to check for truetype as well as opentype in the tfmdata.format \string. at least, once I made that change, it all worked for me with a ttf.
  2. it is possible to use ttf/otf which are not platform fonts, so the error message is misleading. also, it's helpful to show the actual string being tested.

I ended up with thefor \XeTeXglyph, used to typeset the visible glyph (a la Unicode font charts) for U+FE00 (variation selector-1). Using Base rendering mode was also necessary to stop the normal Unicode interpretation. I'll append my whole test document for the heck of it, but the relevant thing for your code is just the tweaks to the Lua condition and error string.

\documentclass{article}
\pagestyle{empty}
\usepackage{fontspec}

\ifx\undefined\XeTeXglyph
  \def\XeTeXglyph{%
    \directlua{
      local index = token.scan_int()
      local tfmdata = font.getfont(font.current())
      if tfmdata.format \string ~= "opentype"
         and tfmdata.format \string ~= "truetype" then
          tex.error([[Cannot use \string\XeTeXglyph]] .. [[ with ]] ..
                    tfmdata.name .. tfmdata.format \string ..
                    [[; not opentype or truetype]])
      end
      for slot, char in pairs(tfmdata.characters) do
          if char.index == index then
              tex.print(utf.char(char.unicode))
              return
          end
      end
    }}%
  %
\newfontfamily{\NSM}[Renderer=Base]{NotoSansManichaean-Regular.ttf}
\newcommand{\VSone}{{\NSM\XeTeXglyph58}} % FE00
\else
\newcommand{\VSone}{{\fontspec{NotoSansManichaean-Regular.ttf}\XeTeXglyph58}}
\fi
\begin{document}
\VSone
\end{document}

@hmenke
Copy link
Author

hmenke commented Jan 8, 2023

I'd actually rather reimplement the entire thing in pure Lua and expose the commands via token.set_lua. That makes them feel a lot more like actual primitives since they expand in a single step. That will need some expandafter magic for \fontid, but other than that should be straight-forward.

@hmenke
Copy link
Author

hmenke commented Jan 26, 2023

@kberry
Copy link

kberry commented Jan 28, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment