--
-- This is file `lua-list-hyphen.lua',
-- generated with the docstrip utility.
--
-- The original source files were:
--
-- lua-list-hyphen.dtx  (with options: `lua')
-- 
-- This is a generated file.
-- 
-- Copyright (C) 2026 Alan J. Cain
-- 
-- This file may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either
-- version 1.3c of this license or (at your option) any later
-- version. The latest version of this license is in:
-- 
-- http://www.latex-project.org/lppl.txt
-- 
-- and version 1.3c or later is part of all distributions of
-- LaTeX version 2008-05-04 or later.
-- 
local function debug(s)
end

if tex.count['l__lualisthyphen_debug_int'] ~= 0 then
  debug = function(s)
    print('lua-list-hyphen DEBUG: ' .. s)
  end
end
local KEY_WORD = 'word'
local KEY_LANG = 'lang'
local KEY_DIVISION = 'division'
local KEY_INDEX = 'index'
local NODE_ID_HLIST = node.id('hlist')
local NODE_ID_DISC = node.id('disc')
local NODE_ID_GLUE = node.id('glue')
local NODE_ID_KERN = node.id('kern')
local NODE_ID_MARGIN_KERN = node.id('margin_kern')
local NODE_ID_GLYPH = node.id('glyph')
local NODE_KERN_SUBTYPE_FONTKERN
for k,v in pairs(node.subtypes('kern')) do
  if v == 'fontkern' then
    NODE_KERN_SUBTYPE_FONTKERN = k
    break
  end
end
local function list_filter(t, f)
  local j = 1
  local n = #t

  for i=1,n do
    if (f(t[i])) then
      if (i ~= j) then
        t[j] = t[i]
        t[i] = nil
      end
      j = j + 1
    else
      t[i] = nil
    end
  end

end
local function list_uniq(t, f)
  local j = 1
  local n = #t

  for i=2,n do
    if (f(t[i],t[j])) then
      t[i] = nil
    else
      j = i
    end
  end

  list_filter(
    t,
    function(a) return a end
  )
end
local LIGATURE_COMPONENTS = {
  [0xfb00] = {'f','f'},
  [0xfb01] = {'f','i'},
  [0xfb02] = {'f','l'},
  [0xfb03] = {'f','f','i'},
  [0xfb04] = {'f','f','l'},
}
local get_node_text
local get_nodelist_text
get_node_text = function(n)

  if n.id == NODE_ID_GLYPH then

    if LIGATURE_COMPONENTS[n.char] ~= nil then
      local text = ''

      for _,c in ipairs(LIGATURE_COMPONENTS[n.char]) do
        text = text .. c
      end
      return text

    elseif n.components then
      return get_nodelist_text(n.components)
    else
      -- See [https://tug.org/pipermail/luatex/2018-March/006786.html]
      local u = fonts.hashes.identifiers[n.font].characters[n.char].tounicode
      return utf8.char(tonumber(u,16))
    end
  elseif n.id == NODE_ID_DISC then
    if n.replace then
      return get_nodelist_text(n.replace)
    else
      return ''
    end
  else
    return ''
  end

end
get_nodelist_text = function (head,last)

  local text = ''

  for item in node.traverse(head) do

    text = text .. get_node_text(item)

    if item == last then
      break
    end
  end

  return text

end
local function is_possible_word_node(n)

  return (
    n.id == NODE_ID_GLYPH
    or
    n.id == NODE_ID_DISC
    or
    (n.id == NODE_ID_KERN and n.subtype == NODE_KERN_SUBTYPE_FONTKERN)
    or
    n.id == NODE_ID_MARGIN_KERN
  )

end
local function trim_nonletters_both(s)

  return unicode.utf8.match(s,'^%A*(.-)%A*$')

end
local function trim_nonletters_start(s)

  return unicode.utf8.match(s,'^%A*(.-)$')

end
local function trim_nonletters_end(s)

  return unicode.utf8.match(s,'^(.-)%A*$')

end
local hyphen_attr = luatexbase.new_attribute('hyphen_attr')
local hlist_hyphenatable_word_list = {}
  -- local function get_first_glyph_lang(n)

  --   item = n
  --   while item and is_possible_word_node(item) do
  --     if item.id == NODE_ID_GLYPH then
  --       return item.lang
  --     end
  --     item = item.next
  --   end

  --   return nil

  -- end
local function get_disc_lang(n)

  -- lang = get_first_glyph_lang(n.replace)
  -- if lang then
  --   print(lang)
  --   return lang
  -- end

  -- lang = get_first_glyph_lang(n.pre)
  -- if lang then
  --   print(lang)
  --   return lang
  -- end

  -- lang = get_first_glyph_lang(n.post)
  -- if lang then
  --   return lang
  -- end

  local item

  item = n
  while item and is_possible_word_node(item) do
    if item.id == NODE_ID_GLYPH then
      return item.lang
    end
    item = item.prev
  end

  item = n
  while item and is_possible_word_node(item) do
    if item.id == NODE_ID_GLYPH then
      return item.lang
    end
    item = item.next
  end

  return 0

end
local function pre_linebreak(hlist_head,groupcode)
  local word_start_node = nil
  local hyphenatable_index = nil
  local hyphenatable_count = 0
  local lang = nil

  debug('Pre-linebreak processing start')

  for item in node.traverse(hlist_head) do
    if item.id == NODE_ID_GLYPH and not word_start_node then
      word_start_node = item
    end
    if item.id == NODE_ID_DISC then
      if not hyphenatable_index then
        hyphenatable_count = hyphenatable_count + 1
        hyphenatable_index = hyphenatable_count
        lang = get_disc_lang(item)
      end
      node.set_attribute(item,hyphen_attr,hyphenatable_index)
    end
    if not is_possible_word_node(item) then
      if word_start_node and hyphenatable_index then
        local hyphenatable_word = trim_nonletters_both(
          get_nodelist_text(word_start_node,item.prev)
        )
        if hyphenatable_word ~= '' then
          debug(
            '  Hyphenatable word (index ' .. hyphenatable_index .. ') "'
            .. hyphenatable_word .. '"'
          )
          hlist_hyphenatable_word_list[hyphenatable_index] = {
            [KEY_WORD] = hyphenatable_word,
            [KEY_LANG] = lang,
          }
        end
      end
      word_start_node = nil
      hyphenatable_index = nil
    end

  end

  debug('Pre-linebreak processing finish')

  return true
end
local function get_used_disc(hlist_head)

  local item = node.tail(hlist_head)

  while item and item.id ~= NODE_ID_GLYPH do
    if item.id == NODE_ID_DISC then
      return item
    end
    item = item.prev
  end

  return nil

end
local function get_disc_word_start(hlist_head,n)

  local item = n

  while item do
    local prev = item.prev

    if not (prev and is_possible_word_node(prev)) then
      return item
    end

    item = prev
  end

  return nil
end
local function get_next_hlist(n)

  item = n.next

  while item do
    if item.id == NODE_ID_HLIST then
      return item
    end
    item = item.next
  end

  return nil

end
local function get_line_first_word(hlist_head)
  local word_start_node = nil

  for item in node.traverse(hlist_head) do

    if item.id == NODE_ID_GLYPH then
      if not word_start_node then
        word_start_node = item
      end
    end

    if not is_possible_word_node(item) then
      if word_start_node then
        return get_nodelist_text(word_start_node,item.prev)
      end
    end

  end
  if word_start_node then
    return get_nodelist_text(word_start_node,node.tail(hlist_head))
  else
    return nil
  end
end
local hyphenation_table = {}
local function check_line_hyphenation(hlist)
  local last_disc = get_used_disc(hlist.head)
  if not last_disc then
    debug('  No disc node found at end of line')
    return
  end
  local hyphenation_index = node.has_attribute(last_disc,hyphen_attr)
  local t = hlist_hyphenatable_word_list[hyphenation_index]
  if not t then
    debug('  Disc node not associated to a stored word')
    return
  end
  local word = t[KEY_WORD]
  local lang = t[KEY_LANG]
  local next_line = get_next_hlist(hlist)

  if not next_line then
    debug('  No following line found (which should not happen)')
    return
  end
  lang_hyphenation_list = hyphenation_table[lang]
  if not lang_hyphenation_list then
    hyphenation_table[lang] = {}
    lang_hyphenation_list = hyphenation_table[lang]
  end
  local pre = get_nodelist_text(get_disc_word_start(hlist.head,last_disc))
  local pre_temp = trim_nonletters_start(pre)
  if pre_temp ~= '' then
    pre = pre_temp
  end
  local post = trim_nonletters_end(get_line_first_word(next_line.head))

  debug(
    '  Hyphenated word found: "' .. word .. '" -> "' .. pre .. '<>' .. post .. '"'
  )
  table.insert(
    lang_hyphenation_list,
    {
      [KEY_WORD] = word,
      [KEY_DIVISION] = pre .. post,
      [KEY_INDEX] = #lang_hyphenation_list,
    }
  )

end
local function post_linebreak(vlist_head,groupcode)

  debug('Post-linebreak processing start')

  local line_no = 0

  for item in node.traverse(vlist_head) do

    if item.id == NODE_ID_HLIST then
      line_no = line_no + 1
      debug('  Line no.' .. line_no)
      check_line_hyphenation(item)
    end

  end

  hlist_hyphenatable_word_list = {}

  debug('Post-linebreak processing end')

  return true

end
local LUA_LIST_HYPHEN_PRE_LINEBREAK = 'LUA_LIST_HYPHEN_PRE_LINEBREAK'
luatexbase.add_to_callback(
  'pre_linebreak_filter',
  pre_linebreak,
  LUA_LIST_HYPHEN_PRE_LINEBREAK
)
local LUA_LIST_HYPHEN_POST_LINEBREAK = 'LUA_LIST_HYPHEN_POST_LINEBREAK'
luatexbase.add_to_callback(
  'post_linebreak_filter',
  post_linebreak,
  LUA_LIST_HYPHEN_POST_LINEBREAK
)
local language_table = {}
local function babel_save_language_name(lang_id,name)

  if not language_table[lang_id] then
    language_table[lang_id] = name
  end

end
local function polyglossia_get_language_names()

  if not polyglossia then
    return
  end

  for name,language in pairs(polyglossia.newloader_loaded_languages) do
    language_table[lang.id(language)] = name
  end

end
local function equal_hyphenation_case_sensitive(a,b)
  return (
    a[KEY_WORD] == b[KEY_WORD]
    and
    a[KEY_DIVISION] == b[KEY_DIVISION]
  )
end
local function equal_hyphenation_case_insensitive(a,b)
  return (
    unicode.utf8.lower(a[KEY_WORD]) == unicode.utf8.lower(b[KEY_WORD])
    and
    unicode.utf8.lower(a[KEY_DIVISION]) == unicode.utf8.lower(b[KEY_DIVISION])
  )
end
local function lessthan_hyphenation_case_sensitive(a,b)
  return (
    a[KEY_WORD] < b[KEY_WORD]
    or
    (
      a[KEY_WORD] == b[KEY_WORD]
      and
      a[KEY_DIVISION] < b[KEY_DIVISION]
    )
    or
    (
      a[KEY_WORD] == b[KEY_WORD]
      and
      a[KEY_DIVISION] == b[KEY_DIVISION]
      and
      a[KEY_INDEX] == b[KEY_INDEX]
    )
  )
end
local function lessthan_hyphenation_case_insensitive(a,b)
  return (
    unicode.utf8.lower(a[KEY_WORD]) < unicode.utf8.lower(b[KEY_WORD])
    or
    (
      unicode.utf8.lower(a[KEY_WORD]) == unicode.utf8.lower(b[KEY_WORD])
      and
      unicode.utf8.lower(a[KEY_DIVISION]) < unicode.utf8.lower(b[KEY_DIVISION])
    )
    or
    (
      unicode.utf8.lower(a[KEY_WORD]) == unicode.utf8.lower(b[KEY_WORD])
      and
      unicode.utf8.lower(a[KEY_DIVISION]) < unicode.utf8.lower(b[KEY_DIVISION])
      and
      a[KEY_INDEX] == b[KEY_INDEX]
    )
  )
end
local function sort_lang_hyphenation_list_none(hyphenation_list)
  table.sort(
    hyphenation_list,
    function(a,b)
      return a[KEY_INDEX] < b[KEY_INDEX]
    end
  )
end
local function sort_lang_hyphenation_list_case_sensitive(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_sensitive
  )
end
local function sort_lang_hyphenation_list_case_insensitive(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_insensitive
  )
end
local sort_lang_hyphenation_list
if tex.count['l__lualisthyphen_sort_int'] == 1 then
  sort_lang_hyphenation_list = sort_lang_hyphenation_list_case_sensitive
elseif tex.count['l__lualisthyphen_sort_int'] == 2 then
  sort_lang_hyphenation_list = sort_lang_hyphenation_list_case_insensitive
else
  sort_lang_hyphenation_list = sort_lang_hyphenation_list_none
end
local function deduplicate_lang_hyphenation_list_none(hyphenation_list)
end
local function deduplicate_lang_hyphenation_list_case_sensitive(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_sensitive
  )
  list_uniq(
    hyphenation_list,
    equal_hyphenation_case_sensitive
  )
end
local function deduplicate_lang_hyphenation_list_case_insensitive(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_insensitive
  )
  list_uniq(
    hyphenation_list,
    equal_hyphenation_case_insensitive
  )
end
local deduplicate_lang_hyphenation_list
if tex.count['l__lualisthyphen_unique_int'] == 1 then
  deduplicate_lang_hyphenation_list = deduplicate_lang_hyphenation_list_case_sensitive
elseif tex.count['l__lualisthyphen_unique_int'] == 2 then
  deduplicate_lang_hyphenation_list = deduplicate_lang_hyphenation_list_case_insensitive
else
  deduplicate_lang_hyphenation_list = deduplicate_lang_hyphenation_list_none
end
local function process_lang_hyphenation_list(hyphenation_list)
  deduplicate_lang_hyphenation_list(hyphenation_list)
  sort_lang_hyphenation_list(hyphenation_list)
end
local function write_lang_hyphenation_list_standard(f,hyphenation_list)

  for i,v in ipairs(hyphenation_list) do

    if v then
      f:write(v[KEY_DIVISION] .. '\n')
    end

  end

end
local function write_lang_hyphenation_list_verbose(f,hyphenation_list)

  for i,v in ipairs(hyphenation_list) do

    if v then
      f:write(v[KEY_WORD] .. ' -> ' .. v[KEY_DIVISION] .. '\n')
    end

  end

end
local write_lang_hyphenation_list
if tex.count['l__lualisthyphen_verbose_int'] == 0 then
  write_lang_hyphenation_list = write_lang_hyphenation_list_standard
else
  write_lang_hyphenation_list = write_lang_hyphenation_list_verbose
end
local function get_hyphenation_file_path(prefix,extension,lang_id)

  local lang_name = language_table[lang_id]
  if not lang_name then
    lang_name = lang_id
  end

  local hyphenation_file_path = prefix .. tostring(lang_name) .. extension

  if not status.output_directory then
    return hyphenation_file_path
  end

  if string.sub(status.output_directory,-1,-1) == '/' then
    hyphenation_file_path = status.output_directory .. hyphenation_file_path
  else
    hyphenation_file_path = status.output_directory .. '/' .. hyphenation_file_path
  end

  return hyphenation_file_path

end
local function process_write_lang_hyphenation_list(
    prefix,extension,lang_id,hyphenation_list
)

  process_lang_hyphenation_list(hyphenation_list)

  local f = io.open(get_hyphenation_file_path(prefix,extension,lang_id),'w')
  write_lang_hyphenation_list(f,hyphenation_list)
  f:close()

end
local function process_write_hyphenation_lists(prefix,extension)

  polyglossia_get_language_names()

  for k,v in pairs(hyphenation_table) do
    process_write_lang_hyphenation_list(prefix,extension,k,v)
  end

end
return {
  process_write_hyphenation_lists = process_write_hyphenation_lists,
  babel_save_language_name = babel_save_language_name,
}

