% !TEX TS-program = lualatex
\documentclass[11pt]{article}
\usepackage[letterpaper, margin=0.75in, top=0.75in, bottom=0.85in]{geometry}
\usepackage{fontspec}
\usepackage{luacode}
\usepackage{longtable, array, xcolor, needspace}
\usepackage[hidelinks]{hyperref}
\setmainfont{MFBOldstyle}[
    Extension      = .otf,
    UprightFont    = *-Regular,
    ItalicFont     = *-Italic,
    BoldFont       = *-Bold,
    BoldItalicFont = *-BoldItalic]
\newfontface\MFBglyphR{MFBOldstyle-Regular.otf}[Scale=1.6]
\newfontface\MFBglyphI{MFBOldstyle-Italic.otf}[Scale=1.6]
\newfontface\MFBglyphB{MFBOldstyle-Bold.otf}[Scale=1.6]
\newfontface\MFBglyphBI{MFBOldstyle-BoldItalic.otf}[Scale=1.6]

\setlength{\parindent}{0pt}
\setlength{\tabcolsep}{4pt}
\renewcommand{\arraystretch}{1.1}
\newcolumntype{G}{>{\centering\arraybackslash}m{0.55in}}
\newcommand{\cmapnone}{{\color{gray}\normalsize ---}}
\newsavebox{\cmapsavebox}
\newcommand{\cmapbox}[1]{\sbox{\cmapsavebox}{#1}\textcolor{gray}{%
    \vrule height 0.3em depth 0pt width 0.3pt%
    \vrule height 0.3pt depth 0pt width \wd\cmapsavebox
    \vrule height 0.3em depth 0pt width 0.3pt}}
\newcommand{\cmappart}[1]{%
    \needspace{3in}%
    \section*{#1}\addcontentsline{toc}{section}{#1}}
\newcommand{\cmaptitle}[2]{%
    \multicolumn{7}{@{}p{\textwidth}@{}}{\rule{0pt}{4ex}\phantomsection
        \addcontentsline{toc}{subsection}{#1}%
        {\large\bfseries #1}\hfill{\normalfont\small\ttfamily #2}}\\[1ex]}
\newcommand{\cmapcolumns}[1]{%
    \multicolumn{1}{l}{\footnotesize\itshape #1} &
    \multicolumn{1}{l}{\footnotesize\itshape Glyph} &
    \footnotesize\itshape Regular & \footnotesize\itshape Italic & \footnotesize\itshape Bold &
    \footnotesize\itshape Bold Italic & \multicolumn{1}{l}{\footnotesize\itshape Name}\\}
\newcommand{\cmaphead}[3]{%
    \cmaptitle{#2}{#3}\cmapcolumns{#1}\endfirsthead
    \multicolumn{7}{l}{\footnotesize\itshape #2 (continued)}\\
    \cmapcolumns{#1}\endhead}

\begin{luacode*}
mfbmap = {}

-- Unicode names and general categories
local names, cats = {}, {}
for line in io.lines(kpse.find_file("UnicodeData.txt")) do
    local f = {}
    for field in (line .. ";"):gmatch("([^;]*);") do f[#f + 1] = field end
    local cp = tonumber(f[1], 16)
    local name = f[2]
    if name == "<control>" and f[11] ~= "" then name = f[11] .. " (control)" end
    names[cp], cats[cp] = name, f[3]
end

-- the blocks a Latin text font draws from; anything else is listed by its 256-character row
local blocks = {
    {0x0000, 0x007F, "Basic Latin"}, {0x0080, 0x00FF, "Latin-1 Supplement"},
    {0x0100, 0x017F, "Latin Extended-A"}, {0x0180, 0x024F, "Latin Extended-B"},
    {0x0250, 0x02AF, "IPA Extensions"}, {0x02B0, 0x02FF, "Spacing Modifier Letters"},
    {0x0300, 0x036F, "Combining Diacritical Marks"}, {0x0370, 0x03FF, "Greek and Coptic"},
    {0x0400, 0x04FF, "Cyrillic"}, {0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended"},
    {0x1D00, 0x1D7F, "Phonetic Extensions"}, {0x1D80, 0x1DBF, "Phonetic Extensions Supplement"},
    {0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement"},
    {0x1E00, 0x1EFF, "Latin Extended Additional"}, {0x2000, 0x206F, "General Punctuation"},
    {0x2070, 0x209F, "Superscripts and Subscripts"}, {0x20A0, 0x20CF, "Currency Symbols"},
    {0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols"}, {0x2100, 0x214F, "Letterlike Symbols"},
    {0x2150, 0x218F, "Number Forms"}, {0x2190, 0x21FF, "Arrows"},
    {0x2200, 0x22FF, "Mathematical Operators"}, {0x2300, 0x23FF, "Miscellaneous Technical"},
    {0x2500, 0x257F, "Box Drawing"}, {0x2580, 0x259F, "Block Elements"},
    {0x25A0, 0x25FF, "Geometric Shapes"}, {0x2600, 0x26FF, "Miscellaneous Symbols"},
    {0x2700, 0x27BF, "Dingbats"}, {0x2C60, 0x2C7F, "Latin Extended-C"},
    {0x2E00, 0x2E7F, "Supplemental Punctuation"}, {0xA700, 0xA71F, "Modifier Tone Letters"},
    {0xA720, 0xA7FF, "Latin Extended-D"}, {0xAB30, 0xAB6F, "Latin Extended-E"},
    {0xE000, 0xF8FF, "Private Use Area"}, {0xFB00, 0xFB4F, "Alphabetic Presentation Forms"},
    {0xFE00, 0xFE0F, "Variation Selectors"}, {0xFE20, 0xFE2F, "Combining Half Marks"},
    {0xFFF0, 0xFFFF, "Specials"},
}
local LIGATURE_BLOCK = 0xFB00     -- its characters (fi, fl) lead the ligatures

local function block_of(cp)
    for _, b in ipairs(blocks) do
        if cp >= b[1] and cp <= b[2] then return b[1], b[2], b[3] end
    end
    local first = cp - cp % 256
    return first, first + 255, string.format("Characters U+%04X\\textendash U+%04X", first, first + 255)
end

-- the fonts: each style's glyphs by name (luaotfload gives the glyphs that have no Unicode
-- code point of their own private ones, from U+F0000 on)
local styles = {"R", "I", "B", "BI"}
local codes, classes, order = {}, {}, {}
local PRIVATE = 0xF0000
function mfbmap.register(style, id)
    local raw = font.getfont(id).shared.rawdata.descriptions
    local byname = {}
    for cp, d in pairs(raw) do
        if type(cp) == "number" and d.name then
            byname[d.name] = cp
            if style == "R" then classes[d.name], order[d.name] = d.class, d.index end
        end
    end
    codes[style] = byname
end

local function escape(s)
    return (s:gsub("[\\{}%$&#_%^~%%]", function(c) return "\\char" .. string.byte(c) .. " " end))
end

-- a glyph in one style: marks after a no-break space, as designed; glyphs without ink as their width
local function cell(style, name)
    local cp = codes[style][name]
    if not cp then return "\\cmapnone" end
    local f = "\\MFBglyph" .. style .. " "
    local ch = string.format("\\char\"%X ", cp)
    local cat = cp < PRIVATE and cats[cp] or ""
    if classes[name] == "mark" or cat == "Mn" or cat == "Me" then
        return f .. "\\char\"A0 " .. ch
    elseif cat == "Zs" or cat == "Cf" or cat == "Cc" or cat == "Zl" or cat == "Zp" then
        return "\\cmapbox{" .. f .. ch .. "}"
    end
    return f .. ch
end

local function row(code, name, description)
    -- a glyph name may break after _ and .
    local shown = escape(name):gsub("\\char95 ", "\\char95 \\allowbreak{}"):gsub("%.", ".\\allowbreak{}")
    return string.format("{\\ttfamily\\small %s} & {\\ttfamily\\scriptsize %s} & %s & %s & %s & %s & {\\footnotesize\\textsc{%s}}\\\\",
        code, shown, cell("R", name), cell("I", name), cell("B", name), cell("BI", name), escape(description:lower()))
end

-- a block: its heading in the table's first head, the first two rows tied to it
local TABLE = "\\begin{longtable}{@{}>{\\raggedright\\arraybackslash}p{0.55in} >{\\raggedright\\arraybackslash}p{1.25in} G G G G >{\\raggedright\\arraybackslash}p{2.15in}@{}}"
local function block(put, column, title, range, rows)
    put("\\needspace{1in}")
    put(TABLE)
    put(string.format("\\cmaphead{%s}{%s}{%s}", column, title, range))
    for i, r in ipairs(rows) do
        put(i <= 2 and r:gsub("\\\\$", "\\\\*") or r)
    end
    put("\\end{longtable}")
end

-- what a glyph without a code point of its own is, from its name
local unicode_of = {}         -- glyph name -> code point (the encoded glyphs)
local function uname(name)
    local cp = unicode_of[name] or (name:match("^uni(%x%x%x%x)$") and tonumber(name:sub(4), 16))
        or (name:match("^u(%x%x%x%x%x?)$") and tonumber(name:sub(2), 16))
    return cp and names[cp] or name
end
local function components(base)
    local parts = {}
    if base:match("^uni%x+$") and #base > 7 and (#base - 3) % 4 == 0 then
        for h in base:sub(4):gmatch("%x%x%x%x") do parts[#parts + 1] = names[tonumber(h, 16)] or h end
    else
        for p in base:gmatch("[^_]+") do parts[#parts + 1] = uname(p) end
    end
    return parts
end
local FIGURES = {lf = {"pnum", "proportional lining figure"}, tf = {"tnum", "tabular lining figure"},
                 osf = {"onum pnum", "proportional old-style figure"}, tosf = {"onum", "tabular old-style figure"},
                 numr = {"numr", "numerator"}, dnom = {"dnom", "denominator"}}
local LOCL = {MAH = "Marshallese", TRK = "Turkish"}
local SECTIONS = {"Ligatures", "Small capitals", "Figures", "Accented sequences", "Localized forms", "Other glyphs"}

local function describe(name)
    -- its section, the feature that reaches it, and what it is
    local base, rest = name:match("^([^.]*)%.?(.*)$")
    local locl = name:match("%.locl(%u%u%u)$")
    if locl then
        local inner = name:gsub("%.locl%u%u%u$", "")
        local what = inner:match("%.sc$") and ("small capital of " .. uname((inner:gsub("%.sc$", "")))) or uname(inner)
        return "Localized forms", "locl", (LOCL[locl] or locl) .. " form of " .. what
    elseif rest == "liga" or rest == "dlig" then
        local letters = {}
        for p in base:gmatch("[^_]+") do letters[#letters + 1] = p end
        return "Ligatures", rest, (rest == "dlig" and "discretionary ligature of " or "ligature of ") .. table.concat(letters, " + ")
    elseif base:find("_") or (base:match("^uni%x+$") and #base > 7) then
        local what = table.concat(components(base), " + ")
        if rest == "sc" then return "Accented sequences", "smcp", "small capital of " .. what end
        return "Accented sequences", "ccmp", what
    elseif rest == "sc" then
        return "Small capitals", "smcp", "small capital of " .. uname(base)
    elseif FIGURES[rest] then
        return "Figures", FIGURES[rest][1], FIGURES[rest][2] .. ": " .. uname(base)
    elseif name == ".notdef" then
        return "Other glyphs", "", "shown for characters the fonts lack"
    elseif rest == "alt" then
        return "Other glyphs", "", "alternate of " .. uname(base) .. " (in composites)"
    end
    return "Other glyphs", "", rest ~= "" and (rest .. " form of " .. uname(base)) or uname(base)
end

function mfbmap.map()
    local out = {}
    local function put(s) out[#out + 1] = s end
    -- the Unicode characters, by block
    local cps, glyph_of, seen = {}, {}, {}
    for _, s in ipairs(styles) do
        for name, cp in pairs(codes[s]) do
            if cp < PRIVATE then
                unicode_of[name] = cp
                if not seen[cp] then seen[cp] = true; cps[#cps + 1] = cp; glyph_of[cp] = name end
            end
        end
    end
    table.sort(cps)
    put("\\cmappart{Unicode characters}")
    local rows, current, presentation = {}, nil, {}
    for _, cp in ipairs(cps) do
        local first, last, name = block_of(cp)
        if first == LIGATURE_BLOCK then
            presentation[#presentation + 1] = cp
        else
            if first ~= (current and current[1]) then
                if current then block(put, "Code", current[3], current[4], rows) end
                current, rows = {first, last, name, string.format("U+%04X\\textendash U+%04X", first, last)}, {}
            end
            rows[#rows + 1] = row(string.format("U+%04X", cp), glyph_of[cp], names[cp] or "(unassigned)")
        end
    end
    if current then block(put, "Code", current[3], current[4], rows) end
    -- the glyphs without code points of their own, by kind, in the fonts' glyph order
    local groups, private = {}, {}
    for name, cp in pairs(codes.R) do
        if cp >= PRIVATE then private[#private + 1] = name end
    end
    table.sort(private, function(a, b) return (order[a] or 0) < (order[b] or 0) end)
    for _, name in ipairs(private) do
        local section, feature, description = describe(name)
        groups[section] = groups[section] or {}
        table.insert(groups[section], row(feature ~= "" and feature or "---", name, description))
    end
    -- the presentation forms (fi, fl) lead the ligatures
    local ligatures = {}
    for _, cp in ipairs(presentation) do
        ligatures[#ligatures + 1] = row(string.format("U+%04X", cp), glyph_of[cp], names[cp] or "(unassigned)")
    end
    for _, r in ipairs(groups["Ligatures"] or {}) do ligatures[#ligatures + 1] = r end
    groups["Ligatures"] = ligatures
    put("\\cmappart{Ligatures and glyph variants}")
    for _, section in ipairs(SECTIONS) do
        if groups[section] and #groups[section] > 0 then
            block(put, "Feature", section, string.format("%d glyphs", #groups[section]), groups[section])
        end
    end
    tex.print(out)
end
\end{luacode*}

% the fonts' glyphs
\newcommand{\cmapregister}[2]{\setbox0=\hbox{#2\directlua{mfbmap.register("#1", font.current())}}}
\cmapregister{R}{\MFBglyphR}
\cmapregister{I}{\MFBglyphI}
\cmapregister{B}{\MFBglyphB}
\cmapregister{BI}{\MFBglyphBI}

\begin{document}
\begin{center}
    {\LARGE\itshape MFB Oldstyle}\par\medskip
    {\large Character Map}\par\medskip
\end{center}
\bigskip
\tableofcontents
\clearpage

\directlua{mfbmap.map()}
\end{document}
