1

In LaTeX, it is possible to produce the greek expression Ἑλληνικὴ γλώσσα with command

\textgreek{<Ellhnik`h gl'wssa}

i.e., using only latin characters and without having to use any greek letters in the TeX-file.

This particular command DOES NOT work in XeLaTeX.

Is there a way to turn latin characters into greek characters in XeLaTeX?

I am not talking about having to write something like

 $\gamma\lambda\'\omega\sigma\sigma\alpha$.
2

2 Answers 2

2

Since this problem is related to XeTeX, I assume that your document can be migrated to LuaTeX without too much trouble. First of all, I have to say that I know nothing about Greek, but at least I understand a little bit about what needs to be done based on the files shared by Davislor in the comments. The solution I come up with is shown below.

  1. For now, \textgreek only supports text, which means almost no LaTeX commands will work correctly inside.
  2. I only defined the character mappings for L2G_VARIANT_GRAVE, L2G_VARIANT_ROUGH and L2G_VARIANT_ACUTE. You have to fill in new mappings inside _l2g_variant_indicator table.
\documentclass{exam}
\usepackage{fontspec}
\usepackage{luacode}
\usepackage{expl3}

\setmainfont{DejaVu Serif}

\begin{luacode*}
L2G_VARIANT_ACUTE = 0x00000001
L2G_VARIANT_CIRCUM = 0x00000002
L2G_VARIANT_DIA = 0x00000004
L2G_VARIANT_DIACIRCUM = 0x00000008
L2G_VARIANT_DIAGRAVE = 0x00000010
L2G_VARIANT_GRAVE = 0x00000020
L2G_VARIANT_IOTASUB = 0x00000040
L2G_VARIANT_ROUGH = 0x00000080
L2G_VARIANT_SMOOTH = 0x00000100

l2g_variants = {
  "acute", "circum", "dia", "diacircum", "diagrave", "grave", "iotasub", "rough", "smooth"
}

l2g_char_mapping = {
  [97]=945,[98]=946,[99]=958,[100]=948,[101]=949,[102]=966,
  [103]=947,[104]=951,[105]=953,[107]=954,[108]=955,[109]=956,
  [110]=957,[111]=959,[112]=960,[113]=952,[114]=961,[116]=964,
  [117]=965,[118]=989,[119]=969,[120]=958,[121]=968,[122]=950,
  [65]=913,[66]=914,[67]=926,[68]=916,[69]=917,[70]=934,
  [71]=915,[72]=919,[73]=921,[75]=922,[76]=923,[77]=924,
  [78]=925,[79]=927,[80]=928,[81]=920,[82]=929,[84]=932,
  [85]=933,[86]=988,[87]=937,[88]=926,[89]=936,[90]=918
}

_l2g_variant_indicator = {
  ["`"] = L2G_VARIANT_GRAVE,
  ["<"] = L2G_VARIANT_ROUGH,
  ["'"] = L2G_VARIANT_ACUTE
}

-- an integer version of _l2g_variant_indicator will be generated right away
l2g_variant_indicator = {}
for key, val in pairs(_l2g_variant_indicator) do
  local ind = key:byte(1)
  l2g_variant_indicator[ind] = val
end

l2g_variant_mapping = {
  [97]={[256]=7936,[288]=7938,[352]=8066,[128]=7937,[160]=7939,[224]=8067,[257]=7940,[321]=8068,[129]=7941,[193]=8069,[258]=7942,[322]=8070,[130]=7943,[194]=8071,[320]=8064,[192]=8065,[32]=8048,[96]=8114,[1]=940,[65]=8116,[2]=8118,[66]=8119,[64]=8115},
  [65]={[256]=7944,[288]=7946,[352]=8074,[128]=7945,[160]=7947,[224]=8075,[257]=7948,[321]=8076,[129]=7949,[193]=8077,[258]=7950,[322]=8078,[130]=7951,[194]=8079,[320]=8072,[192]=8073,[1]=902,[32]=8122,[64]=8124},
  [104]={[256]=7968,[288]=7970,[352]=8082,[128]=7969,[160]=7971,[224]=8083,[257]=7972,[321]=8084,[129]=7973,[193]=8085,[258]=7974,[322]=8086,[320]=8080,[192]=8081,[1]=942,[65]=8132,[2]=8134,[66]=8135,[32]=8052,[64]=8131},
  [72]={[256]=7976,[288]=7978,[352]=8090,[128]=7977,[160]=7979,[224]=8091,[257]=7980,[321]=8092,[129]=7981,[193]=8093,[258]=7982,[322]=8094,[320]=8088,[192]=8089,[1]=905,[32]=8138,[64]=8140},
  [119]={[256]=8032,[288]=8034,[352]=8098,[128]=8097,[160]=8035,[224]=8099,[257]=8036,[321]=8100,[129]=8037,[193]=8101,[258]=8038,[322]=8102,[130]=8039,[194]=8103,[320]=8096,[192]=8097,[32]=8060,[96]=8178,[1]=974,[65]=8180,[2]=8182,[66]=8183,[64]=8179},
  [87]={[256]=8040,[288]=8042,[352]=8106,[128]=8041,[160]=8043,[224]=8107,[257]=8044,[321]=8108,[129]=8045,[193]=8109,[258]=8046,[322]=8110,[130]=8047,[194]=8111,[320]=8104,[192]=8105,[1]=911,[32]=8186,[64]=8188},
  [105]={[4]=970,[5]=912,[256]=7984,[288]=7986,[128]=7985,[160]=7987,[257]=7988,[129]=7989,[258]=7990,[130]=7991,[36]=8146,[16]=8146,[6]=8151,[8]=8151,[1]=943,[32]=8054,[2]=8150},
  [117]={[4]=971,[5]=944,[256]=8016,[288]=8018,[128]=8017,[160]=8019,[257]=8020,[129]=8021,[258]=8022,[130]=8023,[36]=8162,[16]=8162,[6]=8167,[8]=8167,[1]=973,[32]=8058,[2]=8166},
  [101]={[256]=7952,[288]=7954,[128]=7953,[160]=7955,[257]=7956,[129]=7957,[1]=941,[32]=8050},
  [69]={[256]=7960,[288]=7962,[128]=7961,[160]=7963,[257]=7964,[129]=7965,[1]=904,[32]=8136},
  [73]={[256]=7992,[288]=7994,[128]=7993,[160]=7995,[257]=7996,[129]=7997,[258]=7998,[130]=7999,[1]=906,[4]=938,[32]=8154},
  [111]={[256]=8000,[288]=8002,[128]=8001,[160]=8003,[257]=8004,[129]=8005,[1]=972,[32]=8056},
  [79]={[256]=8008,[288]=8010,[128]=8009,[160]=8011,[257]=8012,[129]=8013,[1]=908,[32]=8184},
  [85]={[128]=8017,[160]=8027,[129]=8029,[130]=8031,[1]=910,[4]=939,[32]=8170},
  [114]={[256]=8164,[128]=8165},
  [82]={[128]=8172}
}

-- assume Lua 5.3 is used, otherwise may need to use bit32 library
-- https://stackoverflow.com/questions/5977654/how-do-i-use-the-bitwise-operator-xor-in-lua
function l2g_bitwise_or(a, b)
  return a | b
end

function l2g_print_warning(s)
  tex.print(string.format([[\PackageWarning{l2g}{%s}]], s))
end

function l2g_code_to_char(c)
  return utf8.char(c)
end

l2gstate = {
  ["variant_flag"] = 0,
  ["variant_storage"] = -1,
  ["result"] = -1
}


function l2gstate:new()
  local obj = {}
  setmetatable(obj, self)
  self.__index = self
  self.result = {}
  self.variant_storage = {}
  return obj
end

function l2gstate:get_last_variant()
  local len = #self.variant_storage
  if len == 0 then
    return -1
  end
  local last_char = self.variant_storage[len]
  return utf8.codepoint(last_char)
end

function l2gstate:register_variant(var_ccode)
  local var_flag = l2g_variant_indicator[var_ccode]
  self.variant_flag = l2g_bitwise_or(self.variant_flag, var_flag)
  table.insert(self.variant_storage, l2g_code_to_char(var_ccode))
end

function l2gstate:discard_variant()
  self.variant_flag = 0
  self.variant_storage = {}
end

function l2gstate:add_variant_char()
  for _, val in ipairs(self.variant_storage) do
    table.insert(self.result, val)
  end
end

function l2gstate:add_char(code)
  table.insert(self.result, l2g_code_to_char(code))
end


function l2g(s)
  local pos = 1
  local cur_byte = -1
  local cur_char = nil
  
  local variant_state = l2gstate:new()
  local char_find = nil
  local inner_find = nil
  local inner_char_find = nil
  
  while pos <= utf8.len(s) do
    cur_byte = utf8.codepoint(s, pos)
    cur_char = utf8.char(cur_byte)
    
    variant_find = l2g_variant_indicator[cur_byte]
    if variant_find ~= nil then
      if cur_byte == variant_state:get_last_variant() then
        -- escaping current sequence
        variant_state:add_char(cur_byte)
        variant_state:discard_variant()
      else
        variant_state:register_variant(cur_byte)
      end
      goto continue
    end
    
    char_find = l2g_char_mapping[cur_byte]
    if char_find ~= nil then
      -- try to resolve variants
      if variant_state.variant_flag > 0 then
        inner_find = l2g_variant_mapping[cur_byte]
        if inner_find ~= nil then
          inner_char_find = inner_find[variant_state.variant_flag]
          if inner_char_find ~= nil then
            variant_state:add_char(inner_char_find)
          else
            l2g_print_warning(string.format("letter '%s' does not have variant type %d", cur_char, variant_state.variant_flag))
            variant_state:add_variant_char()
            variant_state:add_char(cur_byte)
          end
        else
          l2g_print_warning(string.format("letter '%s' does not have any variant", cur_char))
          variant_state:add_variant_char()
          variant_state:add_char(cur_byte)
        end
        variant_state:discard_variant()
      else
        -- no variant set for this character
        variant_state:add_char(char_find)
      end
    else
      -- unable to find this character in mapping
      variant_state:add_variant_char()
      variant_state:add_char(cur_byte)
      variant_state:discard_variant()
    end
    
    ::continue::
    pos = pos + 1
  end

  if variant_state:get_last_variant() ~= -1 then
    variant_state:add_variant_char()
    variant_state:discard_variant()
  end

  return table.concat(variant_state.result, "")
end
\end{luacode*}

\newcommand{\textgreek}[1]{
  \directlua{
    local res = l2g("\luaescapestring{#1}")
    tex.print(res)
  }
}


\begin{document}
\par \textgreek{<Ellhnik`h gl'wssa}
\par \textgreek{<Ellhnik<`h gl<'wssa}
\par \textgreek{<<Ellhnik``h gl''wssa}

\end{document}

The result is:

Ἑλληνικὴ γλώssα
Ἑλληνικἣ γλὥssα
<Ελληνικ‘η γλ’ωssα
0
1

Although you don’t provide a complete MWE, it appears that you’re using the teubner package, directly or indirectly through babel. This is different from beta code or a direct mapping to LGR.

Preferably, Convert Your Source

My first suggestion would be, if feasible, to convert the input to Unicode, which you can search, copy, paste, spell-check, edit with whatever input method you normally use for Greek, etc. This is especially the case if it’s polytonic Greek, where the mapping of the unaccented Greek alphabet to its Latin transliteration breaks down.

How difficult it would be to parse your LaTeX source with an external script and generate a new source file in UTF-8 depends on how complex the \textgreek expressions are. If they’re all like your example, it could be done with regular expressions. If they can contain only well-behaved, non-nested commands such as \textbf{<Ellhnik`h}, it’s still possible to do mechanically without writing a more complex parser. If your input can contain arbitrarily-nested commands, you would need to write at least a parser that can match braces, and if commands can do complex things like change the language, you would need to scan for that. If you pass programmatically-generated strings to \textgreek, you’d need to rewrite that by hand.

Be aware that XeTeX and LuaTeX only support precomposed (NFC) Greek characters as of 2020, not combining Greek accents. I’m told by the package maintainers that the babel language files are likely to get a fix for this before the Unicode hyphenation algorithm does.

In XeTeX

It would be possible to write a .tec mapping file that converts Teubner codes to Unicode, similar to the one I wrote for beta code. You would compile it with teckit_compile, and activate it with the Mapping= font feature of fontspec.

Among other differences, Teubner codes can contain LICR macros such as \~{e}, which would generate combining accents, and these must be normalized to NFC form to work correctly.

In LuaTeX

Alan Xiang has proposed an answer using Lua, which I haven’t tested.

As a Fallback

As a last resort, it is possible to load LGR fonts with luainputenc. It might be possible to continue to load teubner with those 8-bit fonts, but without knowing your requirements, I can’t tell you if this would work for you.

You must log in to answer this question.

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