% !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}
\makeatletter
\renewcommand{\@pnumwidth}{2em}
\renewcommand{\@tocrmarg}{3em}
\makeatother
% Prefer the accompanying exports to an older installed copy. A build may also
% supply \MFBFontPath explicitly (including its trailing slash).
\providecommand{\MFBFontPath}{}
\ifx\MFBFontPath\empty
  \IfFileExists{MFBOldstyle-Regular.otf}{}{%
    \IfFileExists{../opentype/MFBOldstyle-Regular.otf}
      {\renewcommand{\MFBFontPath}{../opentype/}}
      {\IfFileExists{../../../otf/MFBOldstyle-Regular.otf}
        {\renewcommand{\MFBFontPath}{../../../otf/}}
        {\IfFileExists{../../../MFBOldstyle-Regular.otf}
          {\renewcommand{\MFBFontPath}{../../../}}{}}}}
\fi
\setmainfont{MFBOldstyle}[
    Path           = \MFBFontPath,
    Extension      = .otf,
    UprightFont    = *-Regular,
    ItalicFont     = *-Italic,
    BoldFont       = *-Bold,
    BoldItalicFont = *-BoldItalic]
\newfontface\MFBglyphR{MFBOldstyle-Regular.otf}[Path=\MFBFontPath,Scale=1.6]
\newfontface\MFBglyphI{MFBOldstyle-Italic.otf}[Path=\MFBFontPath,Scale=1.6]
\newfontface\MFBglyphB{MFBOldstyle-Bold.otf}[Path=\MFBFontPath,Scale=1.6]
\newfontface\MFBglyphBI{MFBOldstyle-BoldItalic.otf}[Path=\MFBFontPath,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}}
% Use the actual ink bounds, with breathing room, when the font's TeX metrics
% underestimate a tall symbol. Starting horizontal mode also centers mark boxes.
\newcommand{\cmapink}[3]{\leavevmode\raisebox{0pt}[#1sp][#2sp]{#3}}
\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]{%
    {\footnotesize\itshape #1} & {\footnotesize\itshape Glyph} &
    {\footnotesize\itshape Regular} & {\footnotesize\itshape Italic} & {\footnotesize\itshape Bold} &
    {\footnotesize\itshape Bold Italic} & {\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

-- Named blocks used by the family; future blocks still get a code-range heading.
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"}, {0x1F00, 0x1FFF, "Greek Extended"},
    {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"},
    {0x10140, 0x1018F, "Ancient Greek Numbers"},
    {0x1D200, 0x1D24F, "Ancient Greek Musical Notation"},
    {0xF0000, 0xFFFFF, "Supplementary Private Use Area-A"},
    {0x100000, 0x10FFFF, "Supplementary Private Use Area-B"},
}
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 original cmap and glyph order come from fontloader. luaotfload's Unicode
-- descriptions also contain synthetic character slots and ToUnicode values;
-- neither is evidence that an unencoded glyph has an actual Unicode encoding.
local styles = {"R", "I", "B", "BI"}
local codes, info, encoded, order, unicode_of, semantic_of = {}, {}, {}, {}, {}, {}
local inventory = {}
local function record(...)
    local fields = {...}
    for i, value in ipairs(fields) do fields[i] = tostring(value) end
    inventory[#inventory + 1] = table.concat(fields, "\t")
end
function mfbmap.register(style, id)
    local loaded = font.getfont(id)
    local raw = loaded.shared.rawdata
    local filename = loaded.filename or raw.metadata.filename
    local handle = assert(fontloader.open(filename), "Cannot read " .. filename)
    local native = fontloader.to_table(handle)
    fontloader.close(handle)
    local slots = {}
    for cp, d in pairs(raw.descriptions) do
        if type(cp) == "number" and d.index then
            if not slots[d.index] or cp < slots[d.index] then slots[d.index] = cp end
        end
    end
    local byname, details, cmap, encoded_names = {}, {}, {}, {}
    record("FONT", style, filename)
    local limit = math.min(assert(native.map.enc.char_cnt), 0x110000)
    for cp, gid in pairs(native.map.map) do
        local g = native.glyphs[gid]
        if type(cp) == "number" and cp >= 0 and cp < limit
                and g and g.unicode and g.unicode >= 0 then
            cmap[cp], encoded_names[g.name] = g.name, true
            unicode_of[g.name] = math.min(unicode_of[g.name] or cp, cp)
            record("CHAR", string.format("%X", cp), style, g.name)
        end
    end
    for gid, g in pairs(native.glyphs) do
        local slot = assert(slots[gid], "No printable slot for " .. style .. ":" .. g.name)
        assert(raw.descriptions[slot].name == g.name, "Stale font cache for " .. style .. ":" .. g.name)
        byname[g.name] = slot
        local semantic = raw.descriptions[slot].unicode
        if type(semantic) == "number" and names[semantic] then semantic_of[g.name] = semantic end
        details[g.name] = {
            class = g.class or raw.descriptions[slot].class,
            box = g.boundingbox or raw.descriptions[slot].boundingbox,
            factor = loaded.parameters.hfactor or loaded.size / loaded.units_per_em,
            encoded = encoded_names[g.name] or false,
        }
        if not order[g.name] then
            local rank = 0
            for i, s in ipairs(styles) do if s == style then rank = i end end
            order[g.name] = rank * 100000 + gid
        end
        record("GLYPH", style, g.name, encoded_names[g.name] and "encoded" or "unencoded")
    end
    codes[style], info[style], encoded[style] = byname, details, cmap
end

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

-- A literal glyph in one style. Center marks on their ink bounds so zero-width
-- marks do not drift into the preceding column. Empty glyphs show their width.
local function cell(style, name, unicode)
    if unicode then name = encoded[style][unicode] end
    if not name then return "\\cmapnone" end
    local cp = codes[style][name]
    if not cp then return "\\cmapnone" end
    local d = info[style][name]
    local f = "\\MFBglyph" .. style .. " "
    local ch = string.format("\\char\"%X ", cp)
    local cat = unicode and cats[unicode] or ""
    local function ink(content)
        local b = d.box
        if not b or b[1] == b[3] and b[2] == b[4] then return content end
        local padding = 98304 -- 1.5 pt above and below the actual outline.
        local height = math.max(0, math.ceil(b[4] * d.factor)) + padding
        local depth = math.max(0, math.ceil(-b[2] * d.factor)) + padding
        return string.format("\\cmapink{%d}{%d}{%s}", height, depth, content)
    end
    if d.class == "mark" or cat == "Mn" or cat == "Me" then
        local b = d.box
        if b and b[3] > b[1] then
            local width = math.floor((b[3] - b[1]) * d.factor + 0.5)
            local left = math.floor(-b[1] * d.factor + 0.5)
            return ink(string.format("%s\\hbox to %dsp{\\kern%dsp %s\\hss}", f, width, left, ch))
        end
        return "\\cmapbox{" .. f .. ch .. "}"
    elseif cat == "Zs" or cat == "Cf" or cat == "Cc" or cat == "Zl" or cat == "Zp" then
        return "\\cmapbox{" .. f .. ch .. "}"
    elseif d.box and d.box[1] == d.box[3] and d.box[2] == d.box[4] then
        return "\\cmapbox{" .. f .. ch .. "}"
    end
    return ink(f .. ch)
end

local function row(code, name, description, unicode)
    -- Long Glyphs names also need break opportunities inside undotted words.
    local parts, run = {}, 0
    for c in name:gmatch(".") do
        parts[#parts + 1] = escape(c)
        run = run + 1
        if c == "_" or c == "." or run == 10 then
            parts[#parts + 1] = "\\allowbreak{}"
            run = 0
        end
    end
    local shown = table.concat(parts)
    return string.format("{\\ttfamily\\small %s} & {\\ttfamily\\scriptsize %s} & %s & %s & %s & %s & {\\footnotesize\\textsc{%s}}\\\\",
        code, shown, cell("R", name, unicode), cell("I", name, unicode),
        cell("B", name, unicode), cell("BI", name, unicode), 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)
    -- Reserve the title, column headings, and the first two multi-line rows.
    -- A one-inch reservation can strand the first head and repeat a continued
    -- heading before the real title when those rows move to the next page.
    put("\\Needspace{3in}")
    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 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", "Greek small capitals",
    "Greek centered capitals", "Greek quantity forms", "Greek accent and shaping variants",
    "Internal Greek components", "Figures", "Accented sequences", "Localized forms", "Other glyphs"}

-- Some quantity composites retain Glyphs' descriptive Greek names instead
-- of uniXXXX production names. Recover their base from Unicode names too.
local greek_aliases = {}
for cp, text in pairs(names) do
    if (cp >= 0x370 and cp <= 0x3FF) or (cp >= 0x1F00 and cp <= 0x1FFF) then
        local key = text:lower():gsub("greek small letter ", "")
            :gsub("greek capital letter ", ""):gsub("greek letter ", "")
            :gsub("greek ", ""):gsub(" with ", ""):gsub(" and ", ""):gsub(" ", "")
        greek_aliases[key] = cp
    end
end

local function greek_name(base, full)
    local cp = unicode_of[base] or (full and semantic_of[full]) or greek_aliases[base:lower()]
    if not cp and base:match("^uni%x%x%x%x$") then cp = tonumber(base:sub(4), 16) end
    return cp and ((cp >= 0x370 and cp <= 0x3FF) or (cp >= 0x1F00 and cp <= 0x1FFF))
end

local function greek_description(name, full)
    local text = uname(name)
    if text == name and (name:find("_") or name:match("^uni%x+$") and #name > 7) then
        text = table.concat(components(name), " + ")
    end
    if text == name and full and semantic_of[full] then text = names[semantic_of[full]] end
    if text == name and greek_aliases[name:lower()] then text = names[greek_aliases[name:lower()]] end
    return (text:lower()
        :gsub("greek small letter ", ""):gsub("greek capital letter ", "")
        :gsub("greek letter ", ""):gsub("greek ", "")
        :gsub("psili", "smooth breathing"):gsub("dasia", "rough breathing")
        :gsub("perispomeni", "circumflex"):gsub("oxia", "acute")
        :gsub("varia", "grave"):gsub("ypogegrammeni", "iota subscript")
        :gsub("prosgegrammeni", "iota adscript"))
end

local function greek_variant(base, rest, full)
    local what = greek_description(base, full)
    if rest:find("length", 1, true) then
        local quantity = rest:find("breve", 1, true) and "breve" or "macron"
        local kind = rest:match("%.base$") and "anchor base" or "preposed-accent carrier"
        return "Internal Greek components", "context",
            what .. "; " .. kind .. " used before a separate " .. quantity .. " mark"
    end
    local issc = rest:match("%.sc$") or rest == "sc"
    local iscap = rest:find("allcaps", 1, true) or rest:match("%.uc$")
    local quantity = rest:find("macron", 1, true) or rest:find(".mac", 1, true)
    local breve = rest:find("breve", 1, true) or rest:find(".br", 1, true)
    local details = {}
    if quantity then details[#details + 1] = "macron" end
    if breve then details[#details + 1] = "breve" end
    if rest:find("commaabove", 1, true) or rest:find("psili", 1, true) then
        details[#details + 1] = "smooth breathing"
    elseif rest:find("commareversedabove", 1, true) or rest:find("dasia", 1, true) then
        details[#details + 1] = "rough breathing"
    end
    if rest:find("perispomeni", 1, true) or rest:find("_circ", 1, true) then
        details[#details + 1] = "circumflex"
    elseif rest:find("acute", 1, true) then details[#details + 1] = "acute"
    elseif rest:find("grave", 1, true) then details[#details + 1] = "grave" end
    if #details > 0 then what = what .. "; " .. table.concat(details, ", ") end
    if rest:find("noaccent", 1, true) or rest:find(".na.", 1, true) then
        what = what .. "; breathing and pitch omitted"
    end
    if rest:find("adscriptbase", 1, true) then what = what .. "; adscript base" end
    if rest:find("spacingadscript", 1, true) then what = what .. "; spacing adscript" end
    if issc then return "Greek small capitals", "smcp / c2sc", "small capital: " .. what end
    if iscap then return "Greek centered capitals", "case / ss02", "centered capital: " .. what end
    if quantity or breve then return "Greek quantity forms", "ccmp", what end
    return "Greek accent and shaping variants", "context", what
end

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 name == "mfbGreekAllcapsNoAccent.allcaps" then
        return "Internal Greek components", "context", "zero-width internal all-capital accent control"
    elseif name == "mfbGreekSmallcapsNoAccent.sc" then
        return "Internal Greek components", "context", "zero-width internal small-capital accent control"
    elseif base:match("^mfbGreekSwap") then
        return "Internal Greek components", "ccmp", "zero-width Greek mark-order control"
    elseif greek_name(base, name) or rest:find("allcaps", 1, true)
            or rest:find("grek", 1, true) or rest:find("length", 1, true) then
        return greek_variant(base, rest, name)
    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

-- These are shaped input sequences, not isolated internal carrier glyphs.
-- They demonstrate the same quantity support in every style.
local function quantity_examples(put)
    put("\\cmappart{Greek quantity sequences}")
    put("Macrons and breves are entered as combining marks. The examples below show complete shaped sequences in every style. The internal components listed later need not contain the separately attached quantity mark.\\par\\medskip")
    local vowels = {{0x3B1,0x391,"alpha"},{0x3B5,0x395,"epsilon"},{0x3B7,0x397,"eta"},
        {0x3B9,0x399,"iota"},{0x3BF,0x39F,"omicron"},{0x3C5,0x3A5,"upsilon"},
        {0x3C9,0x3A9,"omega"}}
    local modes = {
        {"Lowercase quantity sequences",1,"","default"},
        {"Capital quantity sequences",2,"","default"},
        {"Centered-capital quantity sequences",2,",RawFeature={+ss02}","ss02"},
        {"Small-capital quantity sequences",1,",RawFeature={+smcp,+c2sc}","smcp / c2sc"}}
    for _, mode in ipairs(modes) do
        local rows = {}
        for _, vowel in ipairs(vowels) do
            for _, q in ipairs({{0x304,"macron"},{0x306,"breve"}}) do
                for _, stacked in ipairs({false,true}) do
                    local points = {vowel[mode[2]],q[1]}
                    local detail = vowel[3] .. " with " .. q[2]
                    if stacked then
                        points[#points + 1], points[#points + 2] = 0x313, 0x342
                        detail = detail .. ", smooth breathing and circumflex"
                    end
                    local input, shown = {}, {}
                    for _, cp in ipairs(points) do
                        input[#input + 1] = string.format("\\char\"%X ",cp)
                        shown[#shown + 1] = string.format("%04X",cp)
                    end
                    local cells = {}
                    for _, style in ipairs(styles) do
                        cells[#cells + 1] = "{\\MFBglyph" .. style ..
                            "\\addfontfeatures{Script=Greek" .. mode[3] .. "}" ..
                            table.concat(input) .. "}"
                    end
                    rows[#rows + 1] = "{\\ttfamily\\small " .. mode[4] ..
                        "} & {\\ttfamily\\scriptsize " .. table.concat(shown," +\\allowbreak{} ") ..
                        "} & " .. table.concat(cells," & ") ..
                        " & {\\footnotesize\\textsc{" .. detail .. "}}\\\\"
                end
            end
        end
        block(put,"Access",mode[1],"Unicode input",rows)
    end
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, encoded_names = {}, {}, {}, {}
    for _, s in ipairs(styles) do
        for cp, name in pairs(encoded[s]) do
            encoded_names[name] = true
            if not seen[cp] then seen[cp] = true; cps[#cps + 1] = cp; glyph_of[cp] = name end
        end
    end
    table.sort(cps)
    put("\\cmappart{Unicode characters}")
    local rows, current, presentation = {}, nil, {}
    for _, cp in ipairs(cps) do
        record("ROW", "unicode", string.format("%X", cp))
        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)", cp)
        end
    end
    if current then block(put, "Code", current[3], current[4], rows) end
    quantity_examples(put)
    -- the glyphs without code points of their own, by kind, in the fonts' glyph order
    local groups, private, variants_seen = {}, {}, {}
    for _, s in ipairs(styles) do
        for name in pairs(codes[s]) do
            if not info[s][name].encoded and not variants_seen[name] then
                variants_seen[name] = true
                private[#private + 1] = name
            end
        end
    end
    table.sort(private, function(a, b)
        if order[a] == order[b] then return a < b end
        return order[a] < order[b]
    end)
    for _, name in ipairs(private) do
        record("ROW", "variant", name)
        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)", cp)
    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, "Access", section, string.format("%d glyphs", #groups[section]), groups[section])
        end
    end
    local report = assert(io.open(tex.jobname .. "-inventory.tsv", "w"))
    report:write(table.concat(inventory, "\n"), "\n")
    report:close()
    texio.write_nl("MFB character map: " .. #cps .. " Unicode rows and " .. #private .. " variant rows.")
    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}
\small
These tables show the available characters in \textit{MFB Oldstyle}. Combining marks are shown in isolation, centered on their ink. Empty glyphs are indicated by a gray width marker. Access labels identify the associated OpenType feature or context.
\normalsize
\bigskip
\tableofcontents
\clearpage

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