-- Copyright (C) 2006-2025 The Gregorio Project (see CONTRIBUTORS.md)
--
-- This file is part of Gregorio.
--
-- Gregorio is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- Gregorio is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Gregorio. If not, see <
http://www.gnu.org/licenses/>.
local P = lpeg.P
local R = lpeg.R
local C = lpeg.C
local function sort_keys(table_to_sort, compare)
local sorted = {}, key
for key in pairs(table_to_sort) do
table.insert(sorted, key)
end
table.sort(sorted, compare)
return sorted
end
local function sort_unique_keys(tables, compare)
local set = {}, ignored, table_to_scan, key
for ignored, table_to_scan in pairs(tables) do
if table_to_scan then
for key in pairs(table_to_scan) do
set[key] = true
end
end
end
return sort_keys(set)
end
local EXCLUDE = {
['.notdef'] = true,
['.null'] = true,
notdef = true,
nonmarkingreturn = true,
AscendensOriscusLineBLTR = true,
AscendensOriscusLineTR = true,
BracketLeftZero = true,
BracketLeftSix = true,
BracketLeftSeven = true,
BracketLeftEight = true,
BracketLeftNine = true,
BracketLeftTen = true,
BracketLeftEleven = true,
BracketLeftTwelve = true,
BracketLeftThirteen = true,
BracketLeftFourteen = true,
BracketLeftShortZero = true,
BracketLeftShortSix = true,
BracketLeftShortSeven = true,
BracketLeftShortEight = true,
BracketLeftShortNine = true,
BracketLeftShortTen = true,
BracketLeftShortEleven = true,
BracketLeftShortTwelve = true,
BracketLeftShortThirteen = true,
BracketLeftShortFourteen = true,
BracketLeftLongZero = true,
BracketLeftLongSix = true,
BracketLeftLongSeven = true,
BracketLeftLongEight = true,
BracketLeftLongNine = true,
BracketLeftLongTen = true,
BracketLeftLongEleven = true,
BracketLeftLongTwelve = true,
BracketLeftLongThirteen = true,
BracketLeftLongFourteen = true,
BracketRightZero = true,
BracketRightSix = true,
BracketRightSeven = true,
BracketRightEight = true,
BracketRightNine = true,
BracketRightTen = true,
BracketRightEleven = true,
BracketRightTwelve = true,
BracketRightThirteen = true,
BracketRightFourteen = true,
BracketRightShortZero = true,
BracketRightShortSix = true,
BracketRightShortSeven = true,
BracketRightShortEight = true,
BracketRightShortNine = true,
BracketRightShortTen = true,
BracketRightShortEleven = true,
BracketRightShortTwelve = true,
BracketRightShortThirteen = true,
BracketRightShortFourteen = true,
BracketRightLongZero = true,
BracketRightLongSix = true,
BracketRightLongSeven = true,
BracketRightLongEight = true,
BracketRightLongNine = true,
BracketRightLongTen = true,
BracketRightLongEleven = true,
BracketRightLongTwelve = true,
BracketRightLongThirteen = true,
BracketRightLongFourteen = true,
PunctumAuctusLineBL = true,
PunctumLineBLBR = true,
PunctumLineBR = true,
PunctumLineTR = true,
PunctumSmall = true,
FlexusLineBL = true,
FlexusAmOneLineBL = true,
DescendensOriscusLineTR = true,
DescendensOriscusLineBLTR = true,
QuilismaLineTR = true,
VirgaLineBR = true,
SalicusOriscus = true,
VirgaBaseLineBL = true,
['VirgulaTwo'] = true,
['VirgulaThree'] = true,
['VirgulaFive'] = true,
['VirgulaSix'] = true,
['VirgulaParenTwo'] = true,
['VirgulaParenThree'] = true,
['VirgulaParenFive'] = true,
['VirgulaParenSix'] = true,
['DivisioMinimisTwo'] = true,
['DivisioMinimisThree'] = true,
['DivisioMinimisFive'] = true,
['DivisioMinimisSix'] = true,
['DivisioMinimaTwo'] = true,
['DivisioMinimaThree'] = true,
['DivisioMinimaFive'] = true,
['DivisioMinimaSix'] = true,
['DivisioMinimaParenTwo'] = true,
['DivisioMinimaParenThree'] = true,
['DivisioMinimaParenFive'] = true,
['DivisioMinimaParenSix'] = true,
['DivisioMinorTwo'] = true,
['DivisioMinorThree'] = true,
['DivisioMinorFive'] = true,
['DivisioMaiorTwo'] = true,
['DivisioMaiorThree'] = true,
['DivisioMaiorFive'] = true,
['DivisioMaiorDottedTwo'] = true,
['DivisioMaiorDottedThree'] = true,
['DivisioMaiorDottedFive'] = true,
['DivisioMaiorDottedBackingTwo'] = true,
['DivisioMaiorDottedBackingThree'] = true,
['DivisioMaiorDottedBackingFive'] = true,
}
-- &&& in the following two tables is a placeholder for the cavum shape 'r'
local GABC = {
Accentus = [[\excluded{g}r1]],
AccentusReversus = [[\excluded{g}r2]],
Ancus = [[g&&&ec]],
AncusLongqueue = [[h&&&fd]],
AscendensOriscus = [[g&&&o1]],
AscendensOriscusLineBL = [[\excluded{e}@g&&&o1]],
AscendensOriscusLineTL = [[\excluded{i}@g&&&o1]],
AscendensOriscusScapus = [[g&&&O1]],
AscendensOriscusScapusLongqueue = [[h&&&O1]],
AscendensOriscusScapusOpenqueue = [[a&&&O1]],
AscendensPunctumInclinatum = [[G1&&&]],
AuctumMora = [[\excluded{g}.]],
BarBrace = [[\excluded{,}\_]],
BracketLeft = [=[[[\excluded{ce]]}]=],
BracketLeftShort = [=[[[\excluded{fh]]}]=],
BracketLeftLong = [=[[[\excluded{gi]]}]=],
BracketRight = [=[\excluded{[[ce}]]]=],
BracketRightShort = [=[\excluded{[[fh}]]]=],
BracketRightLong = [=[\excluded{[[gi}]]]=],
CClef = [[c3]],
CClefChange = [[c3]],
Circulus = [[\excluded{g}r3]],
CurlyBrace = '[ocb:1;6mm]',
CustosDownLong = [[j+]],
CustosDownMedium = [[m+]],
CustosDownShort = [[k+]],
CustosUpLong = [[f+]],
CustosUpMedium = [[a+]],
CustosUpShort = [[g+]],
DescendensOriscus = [[g&&&o0]],
DescendensOriscusLineBL = [[\excluded{e}@g&&&o0]],
DescendensOriscusLineTL = [[\excluded{i}@g&&&o0]],
DescendensOriscusScapus = [[g&&&O0]],
DescendensOriscusScapusLongqueue = [[h&&&O0]],
DescendensOriscusScapusOpenqueue = [[a&&&O0]],
DescendensPunctumInclinatum = [[G0&&&]],
DivisioDominican = [[,3]],
DivisioDominicanAlt = [[,4]],
DivisioMaiorFour = [[:]],
DivisioMaiorDottedFour = [[:?]],
DivisioMaiorDottedBackingFour = [[\excluded{:?}]],
DivisioMinimaFour = [[,]],
DivisioMinimaParenFour = [[,?]],
DivisioMinimisFour = [[\textasciicircum{}]],
DivisioMinorFour = [[;]],
FClefChange = [[f3]],
FClef = [[f3]],
Flat = [[gx]],
FlatHole = [[\excluded{gx}]],
FlatParen = [[gx?]],
FlatParenHole = [[\excluded{gx?}]],
Flexus = [[g&&&e]],
FlexusLongqueue = [[h&&&f]],
FlexusNobar = [[@h&&&f]],
FlexusOriscus = [[g&&&oe]],
FlexusOriscusInusitatus = [[g&&&o1e]],
FlexusOriscusScapus = [[g&&&Oe]],
FlexusOriscusScapusInusitatus = [[g&&&O1e]],
FlexusOriscusScapusInusitatusLongqueue = [[h&&&O1f]],
FlexusOriscusScapusLongqueue = [[h&&&Of]],
LeadingOriscus = [[g&&&o\excluded{igig}]],
LeadingPunctum = [[g&&&\excluded{igig}]],
LeadingQuilisma = [[g&&&w\excluded{igig}]],
Linea = [[g&&&=]],
LineaPunctum = [[g&&&R]],
Natural = [[gy]],
NaturalHole = [[\excluded{gy}]],
NaturalParen = [[gy?]],
NaturalParenHole = [[\excluded{gy?}]],
OblatusAscendensOriscus = [[g&&&o1]],
OblatusDescendensOriscus = [[g&&&o0]],
OblatusFlexusOriscus = [[g&&&oe]],
OblatusFlexusOriscusInusitatus = [[g&&&o1e]],
OblatusPesQuassus = [[g&&&oi]],
OblatusPesQuassusLongqueue = [[h&&&oj]],
OblatusPesQuassusInusitatus = [[g&&&o0i]],
OblatusPesQuassusInusitatusLongqueue = [[h&&&o0j]],
Oriscus = [[g&&&o]], -- for Deminutus
Pes = [[g&&&i]],
PesAscendensOriscus = [[g&&&iO\excluded{/j}]],
PesDescendensOriscus = [[g&&&iO\excluded{/h}]],
PesQuadratum = [[g&&&qi]],
PesQuadratumLongqueue = [[h&&&qj]],
PesQuassus = [[g&&&oi]],
PesQuassusInusitatus = [[g&&&o0i]],
PesQuassusInusitatusLongqueue = [[h&&&o0j]],
PesQuassusLongqueue = [[h&&&oj]],
PorrectusFlexus = [[g&&&ege]],
PorrectusFlexusNobar = [[\excluded{e}g&&&ege]],
Porrectus = [[g&&&eg]],
PorrectusLongqueue = [[h&&&fh]],
PorrectusNobar = [[@g&&&eg]],
Punctum = [[g&&&]],
PunctumInclinatum = [[G&&&]], -- for deminutus
PunctumInclinatumAuctus = [[G&&&>]],
PunctumLineBL = [[\excluded{e}@g&&&]],
PunctumLineTL = [[\excluded{i}@g&&&]],
Quilisma = [[g&&&w]],
QuilismaPes = [[g&&&wi]],
QuilismaPesQuadratum = [[g&&&Wi]],
QuilismaPesQuadratumLongqueue = [[h&&&Wj]],
RoundBraceDown = '[ub:1;6mm]',
RoundBrace = '[ob:1;6mm]',
SalicusFlexus = [[g&&&iOki]],
Salicus = [[g&&&iOk]],
SalicusLongqueue = [[h&&&jOl]],
Scandicus = [[g&&&ik]],
Semicirculus = [[\excluded{g}r4]],
SemicirculusReversus = [[\excluded{g}r5]],
Sharp = [[g\#{}]],
SharpHole = [[\excluded{g\#{}}]],
SharpParen = [[g\#{}?]],
SharpParenHole = [[\excluded{g\#{}?}]],
StansPunctumInclinatum = [[G2&&&]],
StrophaAucta = [[g&&&s>]],
StrophaAuctaLongtail = [[h&&&s>]],
Stropha = [[g&&&s]],
Torculus = [[g&&&ig]],
TorculusLiquescens = [[g&&&ige]],
TorculusLiquescensQuilisma = [[g&&&wige]],
TorculusQuilisma = [[g&&&wig]],
TorculusResupinus = [[g&&&igi]],
TorculusResupinusQuilisma = [[g&&&wigi]],
VEpisema = [[\excluded{g}^^^^0027]],
Virga = [[g&&&v]],
VirgaLongqueue = [[h&&&v]],
VirgaOpenqueue = [[a&&&v]],
VirgaReversa = [[g&&&V]],
VirgaReversaLongqueue = [[h&&&V]],
VirgaReversaOpenqueue = [[a&&&V]],
VirgulaFour = [[^^^^0060]],
VirgulaParenFour = [[^^^^0060?]],
}
local GABC_AMBITUS_ONE = {
PorrectusLongqueue = [[h&&&gh]],
PorrectusFlexusLongqueue = [[h&&&ghg]],
FlexusOpenqueue = [[b&&&a]],
FlexusOriscusScapusOpenqueue = [[b&&&Oa]],
PesQuadratumOpenqueue = [[a&&&qb]],
PesQuassusOpenqueue = [[a&&&ob]],
QuilismaPesQuadratumOpenqueue = [[a&&&Wb]],
OblatusPesQuassusInusitatusOpenqueue = [[a&&&o0b]],
OblatusPesQuassusOpenqueue = [[b&&&oc]],
}
-- if the item is a table, the values will replace fuse_head and gabc
local GABC_FUSE = {
Upper = {
Punctum = [[\excluded{e}@]],
AscendensOriscus = [[\excluded{e}@]],
DescendensOriscus = [[\excluded{e}@]],
OblatusAscendensOriscus = [[\excluded{f}@]],
OblatusFlexusOriscusInusitatus = [[\excluded{f}@]],
OblatusPesQuassus = [[\excluded{f}@]],
OblatusPesQuassusLongqueue = [[\excluded{g}@]],
OblatusPesQuassusOpenqueue = [[\excluded{a}@]],
Pes = [[\excluded{e}@]],
PesQuadratum = [[\excluded{e}@]],
PesQuadratumLongqueue = [[\excluded{f}@]],
PesQuadratumOpenqueue = { [[\excluded{a}@]], [[bqc]] },
PesQuassus = [[\excluded{e}@]],
PesQuassusInusitatus = [[\excluded{e}@]],
PesQuassusInusitatusLongqueue = [[\excluded{f}@]],
PesQuassusLongqueue = [[\excluded{f}@]],
PesQuassusOpenqueue = { [[\excluded{a}@]], [[cod]] },
Flexus = [[\excluded{e}@]],
FlexusOriscus = [[\excluded{e}@]],
FlexusOriscusInusitatus = [[\excluded{e}@]],
Virga = [[\excluded{e}@]],
VirgaLongqueue = [[\excluded{f}@]],
VirgaOpenqueue = { [[\excluded{a}@]], [[cv]] },
},
Lower = {
Punctum = [[\excluded{i}@]],
AscendensOriscus = [[\excluded{i}@]],
DescendensOriscus = [[\excluded{i}@]],
OblatusDescendensOriscus = [[\excluded{h}@]],
OblatusFlexusOriscus = [[\excluded{h}@]],
OblatusPesQuassusInusitatus = [[\excluded{h}@]],
OblatusPesQuassusInusitatusLongqueue = [[\excluded{i}@]],
OblatusPesQuassusInusitatusOpenqueue = [[\excluded{b}@]],
Pes = [[\excluded{i}@]],
PesQuadratum = [[\excluded{i}@]],
PesQuadratumLongqueue = [[\excluded{j}@]],
PesQuadratumOpenqueue = [[\excluded{b}@]],
PesQuassus = [[\excluded{i}@]],
PesQuassusInusitatus = [[\excluded{i}@]],
PesQuassusInusitatusLongqueue = [[\excluded{j}@]],
PesQuassusLongqueue = [[\excluded{j}@]],
PesQuassusOpenqueue = [[\excluded{b}@]],
Flexus = [[\excluded{i}@]],
FlexusOriscus = [[\excluded{i}@]],
FlexusOriscusInusitatus = [[\excluded{i}@]],
},
Up = {
Punctum = [[\excluded{@ij}]],
AscendensOriscus = [[\excluded{@ij}]],
AscendensOriscusScapus = [[\excluded{@ij}]],
AscendensOriscusScapusLongqueue = [[\excluded{@jk}]],
DescendensOriscus = [[\excluded{@ij}]],
DescendensOriscusScapus = [[\excluded{@ij}]],
DescendensOriscusScapusLongqueue = [[\excluded{@jk}]],
OblatusAscendensOriscus = [[\excluded{@i}]],
OblatusDescendensOriscus = [[\excluded{@i}]],
Quilisma = [[\excluded{@ij}]],
Flexus = [[\excluded{@gi}]],
FlexusNobar = [[\excluded{@hj}]],
},
Down = {
Punctum = [[\excluded{@eg}]],
AscendensOriscus = [[\excluded{@eg}]],
AscendensOriscusScapus = [[\excluded{@eg}]],
AscendensOriscusScapusLongqueue = [[\excluded{@eg}]],
DescendensOriscus = [[\excluded{@eg}]],
DescendensOriscusScapus = [[\excluded{@eg}]],
DescendensOriscusScapusLongqueue = [[\excluded{@eg}]],
OblatusAscendensOriscus = [[\excluded{@e}]],
OblatusDescendensOriscus = [[\excluded{@e}]],
VirgaReversa = [[\excluded{@eg}]],
VirgaReversaLongqueue = [[\excluded{@fg}]],
},
}
local DEBILIS = {
InitioDebilis = [[-]],
[''] = [[]],
}
local LIQUESCENCE = {
Ascendens = [[<]],
Descendens = [[>]],
Deminutus = [[\~{}]],
Nothing = [[]],
[''] = [[]],
}
GregorioRef = {}
function GregorioRef.emit_score_glyphs(cs_normal, cs_hollow)
local common_glyphs = {}
local normal_variants = {}
local normal_names = {}
local hollow_variants = {}
local hollow_names = {}
local function index_font(csname, variants, names, common)
local glyphs = font.fonts[font.id(csname)].resources.unicodes
-- force-load the code points of the font --
local ignored = glyphs['___magic___']
local glyph, cp
for glyph, cp in pairs(glyphs) do
names[glyph] = true
if cp >= 0xe000 and not EXCLUDE[glyph] and not glyph:match('^HEpisema') then
local name, variant = glyph:match('^([^.]*)(%.%a*)$')
if name then
local glyph_variants = variants[name]
if glyph_variants == nil then
glyph_variants = {}
variants[name] = glyph_variants
end
glyph_variants[variant] = cp
elseif common then
common[glyph] = cp
end
end
end
end
index_font(cs_normal, normal_variants, normal_names, common_glyphs)
index_font(cs_hollow, hollow_variants, hollow_names, common_glyphs)
local function maybe_emit_glyph(csname, variants, name, variant)
local cp = variants[name]
if cp then
cp = cp[variant]
if cp then
tex.sprint(string.format([[&{\%s\char%d}]], csname, cp))
end
end
if not cp then
tex.sprint(string.format([[&{\tiny\itshape N/A}]], csname, cp))
end
end
local function emit_score_glyph(fusion, shape, ambitus, debilis, liquescence)
local name = fusion..shape..ambitus..debilis..liquescence
local char = common_glyphs[name]
local gabc = GABC[shape] or GABC_AMBITUS_ONE[shape]
if gabc then
local fuse_head = ''
local fuse_tail = ''
if fusion ~= '' then
fuse_head = GABC_FUSE[fusion][shape]
if fuse_head == nil then
tex.error('No head fusion for '..name)
end
if type(fuse_head) == 'table' then
fuse_head, gabc = fuse_head[1], fuse_head[2]
end
end
local liq = liquescence
if liq == 'Up' or liq == 'Down' then
fuse_tail = GABC_FUSE[liq][shape]
if fuse_tail == nil then
tex.error('No tail fusion for '..name)
end
liq = ''
end
gabc = '('..fuse_head..DEBILIS[debilis]..gabc..LIQUESCENCE[liq]..fuse_tail..')'
else
texio.write_nl('GregorioRef Warning: missing GABC for '..name)
end
local sorted_normal = sort_unique_keys{normal_variants[name]}
local sorted_hollow = sort_unique_keys{hollow_variants[name]}
local n = math.max(1, #sorted_normal, #sorted_hollow)
local emitted = false, i, variant
for i = 1,n do
if emitted then
tex.sprint([[\nopagebreak&&&]])
else
tex.sprint(string.format(
[[{%s\scriptsize %s{\bfseries %s}{\itshape %s}%s%s}&{\ttfamily\small %s}&{\%s\char%d}&]],
gabc and [[]] or [[\color{red}]],
fusion, shape, ambitus, debilis, liquescence,
gabc and gabc:gsub('&&&', '') or '', cs_normal, char
))
end
variant = sorted_normal[i]
if variant then
tex.sprint(string.format([[{\scriptsize %s}]], variant))
maybe_emit_glyph(cs_normal, normal_variants, name, variant)
else
tex.print([[&]])
end
if emitted or not hollow_names[name] then
tex.sprint([[&&&]])
else
tex.sprint(string.format(
[[&{\ttfamily\small %s}&{\%s\char%d}&]],
gabc and gabc:gsub('&&&', 'r') or '', cs_hollow, char
))
end
variant = sorted_hollow[i]
if variant then
tex.sprint(string.format([[{\scriptsize %s}]], variant))
maybe_emit_glyph(cs_hollow, hollow_variants, name, variant)
else
tex.print([[&]])
end
tex.print([[\\]])
emitted = true
end
end
local glyph_names = {}
local ambitus = P'One' + P'Two' + P'Three' + P'Four' + P'Five'
local majuscule = R'AZ'
local minuscule = R'az'
local fusion = P'Upper' + P'Lower'
local debilis = P'InitioDebilis'
local post_word_liquescentia = P'Nothing' + P'Deminutus' + P'Ascendens' +
P'Descendens'
local liquescentia = post_word_liquescentia + P'Up' + P'Down'
local word = ((majuscule * minuscule^0) - fusion - ambitus - debilis -
post_word_liquescentia) + ((P'Ascendens' + P'Descendens') * P'Oriscus')
local liquescence = debilis^-1 * liquescentia^-1
local pattern = C(fusion^-1) * C(word^1) * C(ambitus^0) * C(debilis^-1) *
C(liquescentia^-1) * -1
local only_twos = P'Two'^1 * -1
local ambitus_one = P'One' * P'Two'^0 * -1
for name in pairs(common_glyphs) do
local a, b, c, d, e = pattern:match(name)
if b then
table.insert(glyph_names, { a, b, c, d, e })
else
-- if parse fails, just use the name
table.insert(glyph_names, { '', name, '', '', '' })
end
end
local function compare(x, y)
local nx = x[1]..x[2]
local ny = y[1]..y[2]
if nx < ny then
return true
elseif nx == ny then
if x[4] < y[4] then
return true
elseif x[4] == y[4] then
if x[5] < y[5] then
return true
elseif x[5] == y[5] and x[3] < y[3] then
return true
end
end
end
return false
end
table.sort(glyph_names, compare)
local first = true
local i, name
for i, name in ipairs(glyph_names) do
local shape = name[2]
local ambitus = name[3]
if shape:match('^Virgula') or shape:match('^Divisio') then
shape = shape..ambitus
ambitus = ''
end
if not EXCLUDE[shape] then
if (ambitus == '' and name[5] == '') or ambitus == '' or only_twos:match(ambitus)
or (GABC_AMBITUS_ONE[shape] and ambitus_one:match(ambitus)) then
if first then
first = false
else
tex.print([[\hline]])
end
emit_score_glyph(name[1], shape, ambitus, name[4], name[5])
end
end
end
end
function GregorioRef.emit_extra_glyphs(csname)
local glyphs = font.fonts[font.id(csname)].resources.unicodes
local first = true
local odd = true
for i, name in ipairs(sort_keys(glyphs)) do
local cp = glyphs[name]
if cp >= 0xe000 and not EXCLUDE[name] then
if first then
first = false
elseif odd then
tex.print([[\hline]])
end
tex.sprint(string.format([[{\scriptsize %s}&{\%s\char%d}]], name, csname, cp))
if odd then
tex.sprint([[&]])
else
tex.print([[\\]])
end
odd = not odd
end
end
if not odd then
tex.print([[&\\]])
end
end
function GregorioRef.emit_dimension(value)
value = string.gsub(value, '(-?%d+%.%d+)%s*(%a+)', [[\unit[%1]{%2}]])
value = string.gsub(value, '(-?%d+%.)%s*(%a+)', [[\unit[%1]{%2}]])
value = string.gsub(value, '(-?%.?%d+)%s*(%a+)', [[\unit[%1]{%2}]])
tex.sprint(value)
end