diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua | 938 |
1 files changed, 683 insertions, 255 deletions
diff --git a/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua b/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua index 6f15499f3ac..e87bbc553e0 100644 --- a/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua +++ b/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua @@ -1,6 +1,6 @@ --GregorioTeX Lua file. -- ---Copyright (C) 2008-2016 The Gregorio Project (see CONTRIBUTORS.md) +--Copyright (C) 2008-2017 The Gregorio Project (see CONTRIBUTORS.md) -- --This file is part of Gregorio. -- @@ -24,19 +24,21 @@ local hpack, traverse, traverse_id, has_attribute, count, remove, insert_after, gregoriotex = gregoriotex or {} local gregoriotex = gregoriotex -local internalversion = '4.1.5' -- GREGORIO_VERSION (comment used by VersionManager.py) +local internalversion = '5.0.1' -- GREGORIO_VERSION (comment used by VersionManager.py) local err, warn, info, log = luatexbase.provides_module({ name = "gregoriotex", - version = '4.1.5', -- GREGORIO_VERSION + version = '5.0.1', -- GREGORIO_VERSION greinternalversion = internalversion, - date = "2016/08/18", -- GREGORIO_DATE_LTX + date = "2017/04/16", -- GREGORIO_DATE_LTX description = "GregorioTeX module.", author = "The Gregorio Project (see CONTRIBUTORS.md)", - copyright = "2008-2016 - The Gregorio Project", + copyright = "2008-2017 - The Gregorio Project", license = "GPLv3+", }) +local real_gregorio_exe = nil + gregoriotex.module = { err = err, warn = warn, info = info, log = log } local format = string.format @@ -49,7 +51,7 @@ local whatsit = node.id('whatsit') local hyphen = tex.defaulthyphenchar or 45 -local dash_attr = luatexbase.attributes['gre@attr@dash'] +local dash_attr = luatexbase.attributes['gre@attr@dash'] local potentialdashvalue = 1 local nopotentialdashvalue = 2 @@ -62,17 +64,28 @@ local glyph_top_attr = luatexbase.attributes['gre@attr@glyph@top'] local glyph_bottom_attr = luatexbase.attributes['gre@attr@glyph@bottom'] local prev_line_id = nil +local syllable_id_attr = luatexbase.attributes['gre@attr@syllable@id'] + local cur_score_id = nil local score_inclusion = {} local line_heights = nil local new_line_heights = nil local score_heights = nil local new_score_heights = nil -local var_brace_positions = nil -local new_var_brace_positions = nil -local pos_saves = nil -local new_pos_saves = nil +local saved_positions = nil +local saved_lengths = nil +local new_saved_lengths = nil +local saved_newline_before_euouae = nil +local new_saved_newline_before_euouae = nil +local last_syllables = nil +local new_last_syllables = nil +local score_last_syllables = nil +local new_score_last_syllables = nil +local state_hashes = nil +local new_state_hashes = nil local auxname = nil +local snippet_filename = nil +local snippet_logname = nil local space_below_staff = 5 local space_above_staff = 13 @@ -90,6 +103,13 @@ local number_to_letter = { } local capture_header_macro = {} +local hashed_spaces = {} +local space_hash = '' + +local per_line_dims = {} +local per_line_counts = {} +local saved_dims = {} +local saved_counts = {} local catcode_at_letter = luatexbase.catcodetables['gre@atletter'] @@ -98,9 +118,41 @@ local create_marker = luatexbase.new_user_whatsit('marker', 'gregoriotex') local marker_whatsit_id = luatexbase.get_user_whatsit_id('marker', 'gregoriotex') local translation_mark = 1 local abovelinestext_mark = 2 - log("marker whatsit id is %d", marker_whatsit_id) +local function gregorio_exe() + if real_gregorio_exe == nil then + local exe_version + + -- first look for one with the exact version + real_gregorio_exe = 'gregorio-5_0_1' -- FILENAME_VERSION + exe_version = io.popen(real_gregorio_exe..' --version', 'r') + if exe_version then + exe_version = exe_version:read("*line") + end + if not exe_version then + -- look for suffix-less executable + real_gregorio_exe = 'gregorio' + exe_version = io.popen(real_gregorio_exe..' --version', 'r') + exe_version = exe_version:read("*line") + if not exe_version or string.match(exe_version,"%d+%.%d+%.") + ~= string.match(internalversion,"%d+%.%d+%.") then + real_gregorio_exe = nil + err("Unable to find gregorio executable.\n".. + "shell-escape mode may not be activated. Try\n\n".. + "%s --shell-escape %s.tex\n\n".. + "See the documentation of Gregorio or your TeX\n".. + "distribution to automatize it.", + tex.formatname, tex.jobname) + end + end + + log("will use %s", real_gregorio_exe) + end + + return real_gregorio_exe +end + local function mark(value) local marker = create_marker() marker.type = 100 @@ -134,69 +186,130 @@ local function keys_changed(tab1, tab2) return false end -local function heights_changed() +local function saved_computations_changed(tab1, tab2) + local score_ids = {} + local id,_ + for id,_ in pairs(tab1) do + score_ids[id] = true + end + for id,_ in pairs(tab2) do + score_ids[id] = true + end + for id,_ in pairs(score_ids) do + local inner1 = tab1[id] + local inner2 = tab2[id] + if inner1 == nil or inner2 == nil then return true end + local keys = {} + local index + for index,_ in pairs(inner1) do + keys[index] = true + end + for index,_ in pairs(inner2) do + keys[index] = true + end + for index,_ in pairs(keys) do + if inner1[index] ~= inner2[index] then return true end + end + end + return false +end + +local function entries_changed(tab1, tab2) + local k, v + for k, v in pairs(tab1) do + if tab2[k] ~= v then return true end + end + return false +end + +local function is_greaux_write_needed() local id, tab + if entries_changed(state_hashes, new_state_hashes) or + entries_changed(new_state_hashes, state_hashes) then + return true + end for id, tab in pairs(new_line_heights) do if keys_changed(tab, line_heights[id]) then return true end end for id, tab in pairs(line_heights) do if keys_changed(tab, new_line_heights[id]) then return true end end - for id, tab in pairs(new_var_brace_positions) do - if keys_changed(tab, var_brace_positions[id]) then return true end + for id, tab in pairs(new_last_syllables) do + if keys_changed(tab, last_syllables[id]) then return true end end - for id, tab in pairs(var_brace_positions) do - if keys_changed(tab, new_var_brace_positions[id]) then return true end + for id, tab in pairs(last_syllables) do + if keys_changed(tab, new_last_syllables[id]) then return true end end - for id, tab in pairs(new_pos_saves) do - if keys_changed(tab, pos_saves[id]) then return true end + if saved_computations_changed(saved_lengths, new_saved_lengths) then + return true end - for id, tab in pairs(pos_saves) do - if keys_changed(tab, new_pos_saves[id]) then return true end + if saved_computations_changed(saved_newline_before_euouae, new_saved_newline_before_euouae) then + return true end return false end local function write_greaux() - if heights_changed() then + if is_greaux_write_needed() then -- only write this if heights change; since table ordering is not -- predictable, this ensures a steady state if the heights are unchanged. local aux = io.open(auxname, 'w') if aux then log("Writing %s", auxname) + local id, tab, id2, line, value aux:write('return {\n ["line_heights"]={\n') - local id, tab, id2, line for id, tab in pairs(new_line_heights) do aux:write(string.format(' ["%s"]={\n', id)) for id2, line in pairs(tab) do - aux:write(string.format(' [%d]={%d,%d,%d,%d},\n', id2, line[1], - line[2], line[3], line[4])) + if id2 == 'last' then + aux:write(string.format(' ["%s"]=%d,\n', id2, line)) + else + aux:write(string.format(' [%d]={%d,%d,%d,%d,%d},\n', id2, line[1], + line[2], line[3], line[4], line[5])) + end end aux:write(' },\n') end - aux:write(' },\n ["var_brace_positions"]={\n') - for id, tab in pairs(new_var_brace_positions) do + aux:write(' },\n ["last_syllables"]={\n') + for id, tab in pairs(new_last_syllables) do aux:write(string.format(' ["%s"]={\n', id)) - for id2, line in pairs(tab) do - if line[1] ~= nil and line[2] ~= nil then - aux:write(string.format(' [%d]={%d,%d},\n', id2, line[1], - line[2])) + for id2, value in pairs(tab) do + if id2 == 'state' then + aux:write(string.format(' state="%s",\n', value)) + else + aux:write(string.format(' [%d]=%d,\n', id2, value)) end end aux:write(' },\n') end - aux:write(' },\n ["pos_saves"]={\n') - for id, tab in pairs(new_pos_saves) do + aux:write(' },\n ["saved_lengths"]={\n') + for id, tab in pairs(new_saved_lengths) do aux:write(string.format(' ["%s"]={\n', id)) - for id2, line in pairs(tab) do - if line[1] ~= nil and line[2] ~= nil and line[3] ~= nil and - line[4] ~= nil then - aux:write(string.format(' [%d]={%d,%d,%d,%d},\n', id2, line[1], - line[2], line[3], line[4])) + for id2, value in pairs(tab) do + if value ~= nil then + aux:write(string.format(' [%d]=%d,\n', id2, value)) end end aux:write(' },\n') end + aux:write(' },\n ["saved_newline_before_euouae"]={\n') + for id, tab in pairs(new_saved_newline_before_euouae) do + aux:write(string.format(' ["%s"]={\n', id)) + for id2, value in pairs(tab) do + if value ~= nil then + if value then + aux:write(string.format(' [%d]=true,\n', id2)) + else + aux:write(string.format(' [%d]=false,\n', id2)) + end + end + end + aux:write(' },\n') + end + aux:write(' },\n ["state_hashes"]={\n') + for id, value in pairs(new_state_hashes) do + aux:write(string.format(' ["%s"]="%s",\n', id, value)) + end aux:write(' },\n}\n') aux:close() else @@ -221,8 +334,12 @@ local function init(arg, enable_height_computation) end if outputdir and lfs.isdir(outputdir) then auxname = outputdir..'/'..tex.jobname..'.gaux' + snippet_filename = outputdir..'/'..tex.jobname..'.gsnippet' + snippet_logname = outputdir..'/'..tex.jobname..'.gsniplog' else auxname = tex.jobname..'.gaux' + snippet_filename = tex.jobname..'.gsnippet' + snippet_logname = tex.jobname..'.gsniplog' end -- to get latexmk to realize the aux file is a dependency @@ -231,16 +348,22 @@ local function init(arg, enable_height_computation) log("Reading %s", auxname) local score_info = dofile(auxname) line_heights = score_info.line_heights or {} - var_brace_positions = score_info.var_brace_positions or {} - pos_saves = score_info.pos_saves or {} + last_syllables = score_info.last_syllables or {} + state_hashes = score_info.state_hashes or {} + saved_lengths = score_info.saved_lengths or {} + saved_newline_before_euouae = score_info.saved_newline_before_euouae or {} else line_heights = {} - var_brace_positions = {} - pos_saves = {} + last_syllables = {} + state_hashes = {} + saved_lengths = {} + saved_newline_before_euouae = {} end if enable_height_computation then new_line_heights = {} + new_last_syllables = {} + new_state_hashes = {} local mcb_version = luatexbase.get_module_version and luatexbase.get_module_version('luatexbase-mcb') or 9999 @@ -262,8 +385,9 @@ local function init(arg, enable_height_computation) 'line heights. Remove or undefine \\greskipheightcomputation to '.. 'resume height computation.') end - new_var_brace_positions = {} - new_pos_saves = {} + saved_positions = {} + new_saved_lengths = {} + new_saved_newline_before_euouae = {} end -- node factory @@ -357,15 +481,17 @@ end local glue_sign_name = {[0] = 'normal', [1] = 'stretching', [2] = 'shrinking'} -local function debugmessage(type, message) +local function debugmessage(type, ...) if (debug_types_activated[type] or debug_types_activated['all']) then - texio.write_nl('GregorioTeX debug: ('..type..'): '..message) + texio.write_nl('GregorioTeX debug: ('..type..'): '..format(...)) end end +gregoriotex.module.debugmessage = debugmessage + -- in each function we check if we really are inside a score, -- which we can see with the dash_attr being set or not -local function process (h, groupcode, glyphes) +local function post_linebreak(h, groupcode, glyphes) -- TODO: to be changed according to the font local lastseennode = nil local adddash = false @@ -378,6 +504,7 @@ local function process (h, groupcode, glyphes) local line_has_translation = false local line_has_abovelinestext = false local linenum = 0 + local syl_id = nil -- we explore the lines for line in traverse(h) do if line.id == glue then @@ -394,7 +521,7 @@ local function process (h, groupcode, glyphes) h, line = remove(h, line) else linenum = linenum + 1 - debugmessage('linesglues', format('line %d: %s factor %d%%', linenum, glue_sign_name[line.glue_sign], line.glue_set*100)) + debugmessage('linesglues', 'line %d: %s factor %d%%', linenum, glue_sign_name[line.glue_sign], line.glue_set*100) centerstartnode = nil line_id = nil line_top = nil @@ -402,6 +529,7 @@ local function process (h, groupcode, glyphes) line_has_translation = false line_has_abovelinestext = false for n in traverse_id(hlist, line.head) do + syl_id = has_attribute(n, syllable_id_attr) or syl_id if has_attribute(n, center_attr, startcenter) then centerstartnode = n elseif has_attribute(n, center_attr, endcenter) then @@ -466,11 +594,15 @@ local function process (h, groupcode, glyphes) addash=false end if line_id then - new_score_heights[prev_line_id] = { line_top, line_bottom, + new_score_heights[prev_line_id] = { linenum, line_top, line_bottom, line_has_translation and 1 or 0, line_has_abovelinestext and 1 or 0 } + new_score_heights['last'] = prev_line_id prev_line_id = line_id end + if new_score_last_syllables and syl_id then + new_score_last_syllables[syl_id] = syl_id + end end -- we reinitialize the shift value, because it may change according to the line currentshift=0 @@ -488,8 +620,75 @@ local function disable_hyphenation() return false end -local function atScoreBeginning (score_id, top_height, bottom_height, - has_translation, has_above_lines_text, top_height_adj, bottom_height_adj) +local get_font_by_id = font.getfont --- cached, indexes fonts.hashes.identifiers +local font_id = font.id + +-- an "unsafe" version of get_font_by_id that can see all fonts +-- LuaTeX can see, which is needed when we need the font size of +-- the user-selected current font +local function unsafe_get_font_by_id(id) + return get_font_by_id(id) or font.fonts[id] +end + +-- get_font_by_name(name) -- Look up a font identifier in the +-- ``score_fonts`` table. Returns the id of the font if found, -1 +-- otherwise. +local function get_font_by_name(name) + local id = font_id(name) + return id >= 0 and get_font_by_id(id) +end + +-- get_score_font_id(name) -- Look up a font identifier in the +-- ``score_fonts`` table. Returns the id of the font if found, -1 +-- otherwise. +local function get_score_font_id(name) + local sfnt = score_fonts[name] + if sfnt then + return font_id(sfnt) + end + return -1 +end + +local resource_dummy = { unicodes = { } } + +-- get_font_resources(number) -- Retrieve the resource table +-- associated with a font id. Always returns a Lua table value whose +-- ``unicodes`` field is indexable. +local function get_font_resources(id) + local fnt = get_font_by_id(id) + if fnt then + return fnt.resources or resource_dummy + end + return resource_dummy +end + +-- get_score_font_resources(string) -- Retrieve the resource table +-- belonging to a font in the ``score_font`` table. Always returns +-- a table whose ``unicodes`` field is indexable. +local function get_score_font_resources(name) + return get_font_resources(get_score_font_id(name)) +end + +local function get_score_font_unicode_pairs(name) + local unicodes = get_score_font_resources(name).unicodes + if not font_indexed[name] then + -- The unicodes table may be lazy-loaded, so iterating it may not + -- return everything. Attempting to retrieve the code point of a + -- glyph that has not already been loaded will trigger the __index + -- method in the metatable (implemented by the fontloader) to load + -- the table until the glyph is found. When iterating the + -- unicodes, we want the whole table to be filled, so we try to + -- access a non-existing glyph in order to force load the entire + -- table. + local ignored = unicodes['_this_is_hopefully_a_nonexistent_glyph_'] + font_indexed[name] = true + end + return pairs(unicodes) +end + +local function at_score_beginning(score_id, top_height, bottom_height, + has_translation, has_above_lines_text, top_height_adj, bottom_height_adj, + score_font_name) local inclusion = score_inclusion[score_id] or 1 score_inclusion[score_id] = inclusion + 1 score_id = score_id..'.'..inclusion @@ -507,14 +706,33 @@ local function atScoreBeginning (score_id, top_height, bottom_height, score_heights = nil new_score_heights = nil end + local text_font = unsafe_get_font_by_id(font.current()) + local score_font = unsafe_get_font_by_id(font.id('gre@font@music')) + local state = md5.sumhexa(string.format('%s|%d|%s|%d|%s', text_font.name, + text_font.size, score_font.name, score_font.size, space_hash)) + score_last_syllables = last_syllables[score_id] + if score_last_syllables and state_hashes[score_id] ~= state then + score_last_syllables = nil + end + if new_state_hashes then + new_state_hashes[score_id] = state + end + if new_last_syllables then + new_score_last_syllables = {} + new_last_syllables[score_id] = new_score_last_syllables + end - luatexbase.add_to_callback('post_linebreak_filter', process, 'gregoriotex.callback', 1) + luatexbase.add_to_callback('post_linebreak_filter', post_linebreak, 'gregoriotex.post_linebreak', 1) luatexbase.add_to_callback("hyphenate", disable_hyphenation, "gregoriotex.disable_hyphenation", 1) end -local function atScoreEnd () - luatexbase.remove_from_callback('post_linebreak_filter', 'gregoriotex.callback') +local function at_score_end() + luatexbase.remove_from_callback('post_linebreak_filter', 'gregoriotex.post_linebreak') luatexbase.remove_from_callback("hyphenate", "gregoriotex.disable_hyphenation") + per_line_dims = {} + per_line_counts = {} + saved_dims = {} + saved_counts = {} end local function clean_old_gtex_files(file_withdir) @@ -545,19 +763,44 @@ local function clean_old_gtex_files(file_withdir) end end -local function compile_gabc(gabc_file, gtex_file) +local function compile_gabc(gabc_file, gtex_file, glog_file, allow_deprecated) info("compiling the score %s...", gabc_file) local extra_args = '' if tex.count['gre@generate@pointandclick'] == 1 then - extra_args = ' -p' + extra_args = extra_args..' -p' + end + if not allow_deprecated then + extra_args = extra_args..' -D' end - res = os.execute(string.format("gregorio %s -o %s %s", extra_args, gtex_file, gabc_file)) + local cmd = string.format('%s %s -W -o %s -l %s "%s"', gregorio_exe(), + extra_args, gtex_file, glog_file, gabc_file) + res = os.execute(cmd) if res == nil then - err("\nSomething went wrong when executing\n 'gregorio -o %s %s'.\n" - .."shell-escape mode may not be activated. Try\n\n%s --shell-escape %s.tex\n\nSee the documentation of gregorio or your TeX\ndistribution to automatize it.", gtex_file, gabc_file, tex.formatname, tex.jobname) + err("\nSomething went wrong when executing\n '%s'.\n" + .."shell-escape mode may not be activated. Try\n\n" + .."%s --shell-escape %s.tex\n\n" + .."See the documentation of Gregorio or your TeX\n" + .."distribution to automatize it.", + cmd, tex.formatname, tex.jobname) elseif res ~= 0 then - err("\nAn error occured when compiling the score file\n'%s' with gregorio.\nPlease check your score file.", gabc_file) + local glog = io.open(glog_file, 'a+') + if glog == nil then + err("\n Unable to open %s", glog_file) + else + local size = glog:seek('end') + if size > 0 then + glog:seek('set') + local line + for line in glog:lines() do + warn(line) + end + end + glog:close() + end + err("\nAn error occured when compiling the score file\n" + .."'%s' with %s.\nPlease check your score file.", gabc_file, + gregorio_exe()) else -- open the gtex file for writing so that LuaTeX records output to it -- when the -recorder option is used @@ -567,24 +810,49 @@ local function compile_gabc(gabc_file, gtex_file) else gtex:close() end + local glog = io.open(glog_file, 'a+') + if glog == nil then + err("\n Unable to open %s", glog_file) + else + local size = glog:seek('end') + if size > 0 then + glog:seek('set') + local line + for line in glog:lines() do + warn(line) + end + warn("*** end of warnings for %s ***", gabc_file) + end + glog:close() + end end end -local function include_score(input_file, force_gabccompile) - local file_root - if string.match(input_file:sub(-5), '%.gtex') then - file_root = input_file:sub(1,-6) - elseif string.match(input_file:sub(-4), '%.tex') then - file_root = input_file:sub(1,-5) - elseif string.match(input_file:sub(-5), '%.gabc') then - file_root = input_file:sub(1,-6) - elseif not file_root then - file_root = input_file +local function include_score(input_file, force_gabccompile, allow_deprecated) + if string.match(input_file, "[#%%]") then + err("GABC filename contains invalid character(s): # %%\n" + .."Rename the file and retry: %s", input_file) end - local gtex_file = file_root.."-"..internalversion:gsub("%.", "_")..".gtex" - local gabc_file = file_root..".gabc" + local has_extention = false + local file_dir,input_name + local extensions = {['gabc']=true, ['gtex']=true, ['tex']=true} + if extensions[string.match(input_file, "([^%.\\/]*)$")] then + has_extention = true + end + if has_extention then + file_dir,input_name = string.match(input_file, "(.-)([^\\/]-)%.?[^%.\\/]*$") + else + file_dir,input_name = string.match(input_file, "(.-)([^\\/]*)$") + end + + local cleaned_filename = input_name:gsub("[%s%+%&%*%?$@:;!\"\'`]", "-") + local gabc_file = string.format("%s%s.gabc", file_dir, input_name) + local gtex_file = string.format("%s%s-%s.gtex", file_dir, cleaned_filename, + internalversion:gsub("%.", "_")) + local glog_file = string.format("%s%s-%s.glog", file_dir, cleaned_filename, + internalversion:gsub("%.", "_")) if not lfs.isfile(gtex_file) then - clean_old_gtex_files(file_root) + clean_old_gtex_files(file_dir..cleaned_filename) log("The file %s does not exist. Searching for a gabc file", gtex_file) if lfs.isfile(gabc_file) then local gabc = io.open(gabc_file, 'r') @@ -594,7 +862,7 @@ local function include_score(input_file, force_gabccompile) else gabc:close() end - compile_gabc(gabc_file, gtex_file) + compile_gabc(gabc_file, gtex_file, glog_file, allow_deprecated) tex.print(string.format([[\input %s\relax]], gtex_file)) return else @@ -615,103 +883,65 @@ local function include_score(input_file, force_gabccompile) end if gtex_timestamp < gabc_timestamp then log("%s has been modified and %s needs to be updated. Recompiling the gabc file.", gabc_file, gtex_file) - compile_gabc(gabc_file, gtex_file) + compile_gabc(gabc_file, gtex_file, glog_file, allow_deprecated) elseif force_gabccompile then - compile_gabc(gabc_file, gtex_file) + compile_gabc(gabc_file, gtex_file, glog_file, allow_deprecated) end tex.print(string.format([[\input %s\relax]], gtex_file)) return end -local function direct_gabc(gabc, header) - tmpname = os.tmpname() - local f = io.open(tmpname, 'w') +local function direct_gabc(gabc, header, allow_deprecated) + info('Processing gabc snippet...') + local deprecated + if allow_deprecated then + deprecated = '' + else + deprecated = '-D ' + end + local f = io.open(snippet_filename, 'w') -- trims spaces on both ends (trim6 from http://lua-users.org/wiki/StringTrim) gabc = gabc:match('^()%s*$') and '' or gabc:match('^%s*(.*%S)') f:write('name:direct-gabc;\n'..(header or '')..'\n%%\n'..gabc:gsub('\\par ', '\n')) f:close() - local p = io.popen('gregorio -S '..tmpname, 'r') + local cmd = string.format('%s -W %s-S -l %s %s', gregorio_exe(), deprecated, + snippet_logname, snippet_filename) + local p = io.popen(cmd, 'r') if p == nil then - err("\nSomething went wrong when executing\n gregorio -S "..tmpname..".\n" - .."shell-escape mode may not be activated. Try\n\n%s --shell-escape %s.tex\n\nSee the documentation of gregorio or your TeX\ndistribution to automatize it.", tex.formatname, tex.jobname) - end - tex.print(p:read("*a"):explode('\n')) - p:close() - os.remove(tmpname) -end - -local function get_gregorioversion() - return internalversion -end - -local get_font_by_id = font.getfont --- cached, indexes fonts.hashes.identifiers -local font_id = font.id - --- an "unsafe" version of get_font_by_id that can see all fonts --- LuaTeX can see, which is needed when we need the font size of --- the user-selected current font -local function unsafe_get_font_by_id(id) - return get_font_by_id(id) or font.fonts[id] -end - --- get_font_by_name(name) -- Look up a font identifier in the --- ``score_fonts`` table. Returns the id of the font if found, -1 --- otherwise. -local function get_font_by_name(name) - local id = font_id(name) - return id >= 0 and get_font_by_id(id) -end - --- get_score_font_id(name) -- Look up a font identifier in the --- ``score_fonts`` table. Returns the id of the font if found, -1 --- otherwise. -local function get_score_font_id(name) - local sfnt = score_fonts[name] - if sfnt then - return font_id(sfnt) + err("\nSomething went wrong when executing\n %s\n" + .."shell-escape mode may not be activated. Try\n\n" + .."%s --shell-escape %s.tex\n\n" + .."See the documentation of Gregorio or your TeX\n" + .."distribution to automatize it.", cmd, tex.formatname, tex.jobname) + else + tex.print(p:read("*a"):explode('\n')) + p:close() end - return -1 -end - -local resource_dummy = { unicodes = { } } - --- get_font_resources(number) -- Retrieve the resource table --- associated with a font id. Always returns a Lua table value whose --- ``unicodes`` field is indexable. -local function get_font_resources(id) - local fnt = get_font_by_id(id) - if fnt then - return fnt.resources or resource_dummy + local glog = io.open(snippet_logname, 'a+') + if glog == nil then + err("\n Unable to open %s", snippet_logname) + else + local size = glog:seek('end') + if size > 0 then + glog:seek('set') + local line + for line in glog:lines() do + warn(line) + end + warn("*** end of warnings/errors processing snippet ***") + end + glog:close() end - return resource_dummy -end - --- get_score_font_resources(string) -- Retrieve the resource table --- belonging to a font in the ``score_font`` table. Always returns --- a table whose ``unicodes`` field is indexable. -local function get_score_font_resources(name) - return get_font_resources(get_score_font_id(name)) + os.remove(snippet_filename) + os.remove(snippet_logname) end -local function get_score_font_unicode_pairs(name) - local unicodes = get_score_font_resources(name).unicodes - if not font_indexed[name] then - -- The unicodes table may be lazy-loaded, so iterating it may not - -- return everything. Attempting to retrieve the code point of a - -- glyph that has not already been loaded will trigger the __index - -- method in the metatable (implemented by the fontloader) to load - -- the table until the glyph is found. When iterating the - -- unicodes, we want the whole table to be filled, so we try to - -- access a non-existing glyph in order to force load the entire - -- table. - local ignored = unicodes['_this_is_hopefully_a_nonexistent_glyph_'] - font_indexed[name] = true - end - return pairs(unicodes) +local function get_gregoriotexluaversion() + return internalversion end -local function check_font_version() - local gregoriofont = get_font_by_name('gre@font@music') +local function check_one_font_version(name) + local gregoriofont = get_font_by_name(name) if gregoriofont then local fontversion = gregoriofont.shared.rawdata.metadata.version if fontversion and string.match(fontversion, "%d+%.%d+%.%d+") ~= string.match(internalversion, "%d+%.%d+%.%d+") then @@ -721,12 +951,18 @@ local function check_font_version() end end +local function check_font_version() + check_one_font_version('gre@font@music') + check_one_font_version('gre@font@music@hollow') + check_one_font_version('gre@font@music@hole') +end + local function map_font(name, prefix) log("Mapping font %s", name) local glyph, unicode for glyph, unicode in get_score_font_unicode_pairs(name) do if unicode >= 0 and not string.match(glyph, '%.') then - log("Setting \\Gre%s%s to \\char%d", prefix, glyph, unicode) + debugmessage("mapfont", "Setting \\Gre%s%s to \\char%d", prefix, glyph, unicode) tex.sprint(catcode_at_letter, string.format( [[\xdef\Gre%s%s{\char%d}]], prefix, glyph, unicode)) end @@ -741,13 +977,18 @@ local function init_variant_font(font_name, for_score, gre_factor) font_table[font_name] = font_csname log("Registering variant font %s as %s.", font_name, font_csname) if for_score then + local _, name, factor + _, _, name, factor = string.find(font_name, '^([^@]*)@(%d*)$') + name = name or font_name + factor = factor or '100000' + local size = gre_factor * tonumber(factor) tex.print(catcode_at_letter, string.format( - [[\global\font\%s = {name:%s} at 10 sp\relax ]], - font_csname, font_name)) + [[\global\font\%s = {name:%s} at %s sp\relax ]], + font_csname, name, size)) -- loaded_font_sizes will only be given a value if the font is for_score - loaded_font_sizes[font_name] = {size = '10', gre_factor = gre_factor} + loaded_font_sizes[font_name] = {size = size, gre_factor = gre_factor} if font_factors[font_name] == nil then - font_factors[font_name] = '100000' + font_factors[font_name] = factor end else -- is there a nice way to make this string readable? @@ -762,14 +1003,14 @@ local function init_variant_font(font_name, for_score, gre_factor) end local function set_score_glyph(csname, font_csname, char) - log([[Setting \%s to \%s\char%d]], csname, font_csname, char) + debugmessage('changeglyph', [[Setting \%s to \%s\char%d]], csname, font_csname, char) tex.print(catcode_at_letter, string.format( [[\edef\%s{{\noexpand\%s\char%d}}]], csname, font_csname, char)) end local function set_common_score_glyph(csname, font_csname, char) -- font_csname is ignored - log([[Setting \%s to \char%d]], csname, char) + debugmessage('changeglyph', [[Setting \%s to \char%d]], csname, char) tex.print(catcode_at_letter, string.format( [[\edef\%s{{\char%d}}]], csname, char)) end @@ -804,32 +1045,46 @@ local function def_glyph(csname, font_name, glyph, font_table, setter) setter(csname, font_csname, char) end -local function change_single_score_glyph(glyph_name, font_name, replacement) - if font_name == '*' then def_glyph('GreCP'..glyph_name, 'greciliae', replacement, score_fonts, set_common_score_glyph) +local function general_font_for(cavum) + if cavum == 'Hollow' then + return 'greciliae-hollow' + elseif cavum == 'Hole' then + return 'greciliae-hole' + else + return 'greciliae' + end +end + +local function change_single_score_glyph(glyph_name, cavum, font_name, replacement) + if font_name == '*' then + def_glyph('Gre'..cavum..'CP'..glyph_name, general_font_for(cavum), + replacement, score_fonts, set_common_score_glyph) else - def_glyph('GreCP'..glyph_name, font_name, replacement, score_fonts, - set_score_glyph) + def_glyph('Gre'..cavum..'CP'..glyph_name, font_name, replacement, + score_fonts, set_score_glyph) end end -local function change_score_glyph(glyph_name, font_name, replacement) +local function change_score_glyph(glyph_name, font_name, replacement, cavum) + cavum = cavum or '' if string.match(glyph_name, '%*') then glyph_name = '^'..glyph_name:gsub('%*', '.*')..'$' if not string.match(replacement, '^%.') then err('If a wildcard is supplied for glyph name, replacement must start with a dot.') end + local general_font = general_font_for(cavum) local other_font if font_name == '*' then - other_font = get_score_font_resources('greciliae').unicodes + other_font = get_score_font_resources(general_font).unicodes else other_font = get_score_font_resources(font_name).unicodes end local name, char - for name, char in get_score_font_unicode_pairs('greciliae') do + for name, char in get_score_font_unicode_pairs(general_font) do if not string.match(name, '%.') and char >= 0 and string.match(name, glyph_name) then local matched_replacement = name..replacement if other_font[matched_replacement] ~= nil and other_font[matched_replacement] >= 0 then - change_single_score_glyph(name, font_name, matched_replacement) + change_single_score_glyph(name, cavum, font_name, matched_replacement) end end end @@ -837,25 +1092,27 @@ local function change_score_glyph(glyph_name, font_name, replacement) if string.match(replacement, '^%.') then replacement = glyph_name..replacement end - change_single_score_glyph(glyph_name, font_name, replacement) + change_single_score_glyph(glyph_name, cavum, font_name, replacement) end end -local function reset_score_glyph(glyph_name) +local function reset_score_glyph(glyph_name, cavum) + cavum = cavum or '' + local general_font = general_font_for(cavum) if string.match(glyph_name, '%*') then glyph_name = '^'..glyph_name:gsub('%*', '.*')..'$' local name, char - for name, char in get_score_font_unicode_pairs('greciliae') do + for name, char in get_score_font_unicode_pairs(general_font) do if not string.match(name, '%.') and char >= 0 and string.match(name, glyph_name) then - set_common_score_glyph('GreCP'..name, nil, char) + set_common_score_glyph('Gre'..cavum..'CP'..name, nil, char) end end else - local char = get_score_font_resources("greciliae").unicodes[glyph_name] + local char = get_score_font_resources(general_font).unicodes[glyph_name] if char == nil then err('\nGlyph %s was not found.', glyph_name) end - set_common_score_glyph('GreCP'..glyph_name, nil, char) + set_common_score_glyph('Gre'..cavum..'CP'..glyph_name, nil, char) end end @@ -863,12 +1120,17 @@ local function set_font_factor(font_name, font_factor) font_factors[font_name] = font_factor end -local function scale_score_fonts(size, gre_factor) +local function scale_score_fonts(gre_factor) for font_name, font_csname in pairs(score_fonts) do if loaded_font_sizes[font_name] and font_factors[font_name] and loaded_font_sizes[font_name].size ~= gre_factor * font_factors[font_name] then + local _, name + _, _, name, _ = string.find(font_name, '^([^@]*)@(%d*)$') + name = name or font_name + local size = gre_factor * font_factors[font_name] + log("%s : %s : rescaling %s to %s", font_name, font_csname, name, size) tex.print(catcode_at_letter, string.format( [[\global\font\%s = {name:%s} at %s sp\relax ]], - font_csname, font_name, gre_factor * font_factors[font_name])) + font_csname, name, size)) loaded_font_sizes[font_name] = {size = size, gre_factor = gre_factor} end end @@ -883,78 +1145,209 @@ local function font_size() tex.print(string.format('%.2f', (unsafe_get_font_by_id(font.current()).size / 65536.0))) end -local function adjust_line_height(inside_discretionary) - if score_heights then - local heights = score_heights[tex.getattribute(glyph_id_attr)] - if heights then +local function adjust_line_height_internal(heights, inside_discretionary, for_next_line) + local backup_dims = saved_dims + local backup_counts = saved_counts + -- restore saved dims + local name, value + for name, value in pairs(saved_dims) do + tex.sprint(catcode_at_letter, string.format( + [[\grechangedim{%s}{%s}{%s}]], name, value[1], value[2])) + end + for name, value in pairs(saved_counts) do + tex.sprint(catcode_at_letter, string.format( + [[\grechangecount{%s}{%s}]], name, value)) + end + -- clear saved dims + saved_dims = {} + saved_counts = {} + -- apply per-line dims + local line_dims = per_line_dims[heights[1]] + if line_dims ~= nil then + for name, value in pairs(line_dims) do + tex.sprint(catcode_at_letter, string.format( + [[\gre@changedimforline{%s}{%s}{%s}]], name, value[1], value[2])) + end + end + local line_counts = per_line_counts[heights[1]] + if line_counts ~= nil then + for name, value in pairs(line_counts) do + tex.sprint(catcode_at_letter, string.format( + [[\gre@changecountforline{%s}{%s}]], name, value)) + end + end + -- recalculate spaces + tex.sprint(catcode_at_letter, string.format( + [[\gre@calculate@additionalspaces{%d}{%d}{%d}{%d}]], + heights[2], heights[3], heights[4], heights[5])) + if inside_discretionary == 0 then + tex.sprint(catcode_at_letter, [[\gre@updateleftbox ]]) + end + if for_next_line then + -- IS THIS GOOD ENOUGH??? + -- restore saved dims (from current line) + local name, value + for name, value in pairs(saved_dims) do + tex.sprint(catcode_at_letter, string.format( + [[\grechangedim{%s}{%s}{%s}]], name, value[1], value[2])) + end + for name, value in pairs(saved_counts) do tex.sprint(catcode_at_letter, string.format( - [[\gre@calculate@additionalspaces{%d}{%d}{%d}{%d}]], - heights[1], heights[2], heights[3], heights[4])) - if inside_discretionary == 0 then - tex.sprint(catcode_at_letter, [[\gre@updateleftbox ]]) + [[\grechangecount{%s}{%s}]], name, value)) + end + -- put previous saved dims back + saved_dims = backup_dims + saved_counts = backup_counts + end +end + +local function adjust_line_height(inside_discretionary, for_next_line) + if score_heights then + local heights = nil + if for_next_line then + local last = score_heights['last'] + if last then + local target_id = tex.getattribute(glyph_id_attr) + 1 + while target_id <= last do + heights = score_heights[target_id] + if heights then break end + target_id = target_id + 1 + end end + else + heights = score_heights[tex.getattribute(glyph_id_attr)] + end + if heights then + adjust_line_height_internal(heights, inside_discretionary, for_next_line) end end end -local function var_brace_note_pos(brace, start_end) - tex.print(catcode_at_letter, string.format([[\luatexlatelua{gregoriotex.late_brace_note_pos('%s', %d, %d, \number\gre@lastxpos)}]], cur_score_id, brace, start_end)) +local function save_dim(name, value, modifier) + saved_dims[name] = { value, modifier } end -local function late_brace_note_pos(score_id, brace, start_end, pos) - if new_var_brace_positions[score_id] == nil then - new_var_brace_positions[score_id] = {} - end - if new_var_brace_positions[score_id][brace] == nil then - new_var_brace_positions[score_id][brace] = {} +local function save_count(name, value) + saved_counts[name] = value +end + +local function change_next_score_line_dim(line_expr, name, value, modifier) + local linenum_str + for linenum_str in string.gmatch(line_expr, "%s*([^,]+)%s*") do + local linenum = tonumber(linenum_str) + local line_dims = per_line_dims[linenum] + if line_dims == nil then + line_dims = {} + per_line_dims[linenum] = line_dims + end + line_dims[name] = { value, modifier } end - new_var_brace_positions[score_id][brace][start_end] = pos end -local function var_brace_len(brace) - if var_brace_positions[cur_score_id] ~= nil then - if var_brace_positions[cur_score_id][brace] ~= nil then - local posend = var_brace_positions[cur_score_id][brace][2] - local posstart = var_brace_positions[cur_score_id][brace][1] - if posend > posstart then - tex.print(string.format('%dsp', posend - posstart)) - else - warn('Dynamically sized braces spanning multiple lines unsupported, using length 2mm.') - tex.print('2mm') - end - return +local function change_next_score_line_count(line_expr, name, value) + local linenum_str + for linenum_str in string.gmatch(line_expr, "([^,]+)") do + local linenum = tonumber(linenum_str) + local line_counts = per_line_counts[linenum] + if line_counts == nil then + line_counts = {} + per_line_counts[linenum] = line_counts end + line_counts[name] = value end - tex.print('2mm') end -local function save_pos(index, which) - tex.print(catcode_at_letter, string.format([[\gre@savepos\luatexlatelua{gregoriotex.late_save_pos('%s', %d, %d, \number\gre@lastxpos, \number\gre@lastypos)}]], cur_score_id, index, which)) +local function prep_save_position(index, fn) + if saved_positions[cur_score_id] == nil then + saved_positions[cur_score_id] = {} + end + saved_positions[cur_score_id][index] = { fn = fn } +end + +local function save_position(index, which) + tex.print(catcode_at_letter, string.format([[\luatexlatelua{gregoriotex.late_save_position('%s', %d, %d, \number\gre@lastxpos, \number\gre@lastypos)}]], cur_score_id, index, which)) end -local function late_save_pos(score_id, index, which, xpos, ypos) - if new_pos_saves[score_id] == nil then - new_pos_saves[score_id] = {} +local function late_save_position(score_id, index, which, xpos, ypos) + info('saving %s, %d [%d] (%d,%d)', score_id, index, which, xpos, ypos) + local pos = saved_positions[score_id][index] + if pos == nil then + err('Attempting to use unprepared position save slot %d', index) + return end - if new_pos_saves[score_id][index] == nil then - new_pos_saves[score_id][index] = {} + --[[ + if pos.fn == nil then + err('Attempting to reuse position save slot %d', index) + return + end + --]] + pos['x'..which] = xpos + pos['y'..which] = ypos + if pos.x1 ~= nil and pos.y1 ~= nil and pos.x2 ~= nil and pos.y2 ~= nil then + pos.fn(score_id, index, pos) + --pos.fn = nil end - new_pos_saves[score_id][index][(2 * which) - 1] = xpos - new_pos_saves[score_id][index][2 * which] = ypos +end + +local function compute_saved_length(score_id, index, pos) + if new_saved_lengths[score_id] == nil then + new_saved_lengths[score_id] = {} + end + new_saved_lengths[score_id][index] = pos.x2 - pos.x1 + info('computed length for %s, %d: %d', score_id, index, + new_saved_lengths[score_id][index]) +end + +local function save_length(index, which) + if which == 1 then + prep_save_position(index, compute_saved_length) + end + save_position(index, which) +end + +local function compute_saved_newline_before_euouae(score_id, index, pos) + if new_saved_newline_before_euouae[score_id] == nil then + new_saved_newline_before_euouae[score_id] = {} + end + new_saved_newline_before_euouae[score_id][index] = pos.y2 ~= pos.y1 + info('computed euouae for %s, %d: %s', score_id, index, + new_saved_newline_before_euouae[score_id][index]) +end + +local function save_euouae(index, which) + if which == 1 then + prep_save_position(index, compute_saved_newline_before_euouae) + end + tex.sprint(catcode_at_letter, [[\gre@savepos]]) + save_position(index, which) +end + +local function var_brace_len(brace) + if saved_lengths[cur_score_id] ~= nil then + local length = saved_lengths[cur_score_id][brace] + if saved_lengths[cur_score_id][brace] ~= nil then + if length > 0 then + tex.print(string.format('%dsp', length)) + return + else + warn('Dynamically sized signs spanning multiple lines unsupported, using length 2mm.') + end + end + end + tex.print('2mm') end -- this function is meant to be used from \ifcase; prints 0 for true and 1 for false local function is_ypos_different(index) - if pos_saves[cur_score_id] ~= nil then - local saved_pos = pos_saves[cur_score_id][index] - if saved_pos == nil or saved_pos[2] == saved_pos[4] then - tex.sprint([[\number1\relax ]]) - else + if saved_newline_before_euouae[cur_score_id] ~= nil then + local newline_before_euouae = + saved_newline_before_euouae[cur_score_id][index] + if newline_before_euouae then tex.sprint([[\number0\relax ]]) + return end - else - tex.sprint([[\number1\relax ]]) end + tex.sprint([[\number1\relax ]]) end local function width_to_bp(width, value_if_star) @@ -1031,39 +1424,74 @@ local function mode_part(part) end end -gregoriotex.number_to_letter = number_to_letter -gregoriotex.init = init -gregoriotex.include_score = include_score -gregoriotex.atScoreEnd = atScoreEnd -gregoriotex.atScoreBeginning = atScoreBeginning -gregoriotex.check_font_version = check_font_version -gregoriotex.get_gregorioversion = get_gregorioversion -gregoriotex.map_font = map_font -gregoriotex.init_variant_font = init_variant_font -gregoriotex.change_score_glyph = change_score_glyph -gregoriotex.reset_score_glyph = reset_score_glyph -gregoriotex.scale_score_fonts = scale_score_fonts -gregoriotex.set_font_factor = set_font_factor -gregoriotex.def_symbol = def_symbol -gregoriotex.font_size = font_size -gregoriotex.direct_gabc = direct_gabc -gregoriotex.adjust_line_height = adjust_line_height -gregoriotex.var_brace_len = var_brace_len -gregoriotex.var_brace_note_pos = var_brace_note_pos -gregoriotex.late_brace_note_pos = late_brace_note_pos -gregoriotex.mark_translation = mark_translation -gregoriotex.mark_abovelinestext = mark_abovelinestext -gregoriotex.width_to_bp = width_to_bp -gregoriotex.hypotenuse = hypotenuse -gregoriotex.rotation = rotation -gregoriotex.scale_space = scale_space -gregoriotex.set_header_capture = set_header_capture -gregoriotex.capture_header = capture_header -gregoriotex.save_pos = save_pos -gregoriotex.late_save_pos = late_save_pos -gregoriotex.is_ypos_different = is_ypos_different -gregoriotex.mode_part = mode_part -gregoriotex.set_debug_string = set_debug_string +-- this function is meant to be used from \ifcase; prints 0 for true and 1 for false +local function is_last_syllable_on_line() + if score_last_syllables then + if score_last_syllables[tex.getattribute(syllable_id_attr)] then + tex.print(0) + else + tex.print(1) + end + else + -- if the last syllable is not computed, treat all syllables as the + -- last on a line + tex.print(0) + end +end + +local function hash_spaces(name, value) + hashed_spaces[name] = value + local k, _ + local keys = {} + for k,_ in pairs(hashed_spaces) do + table.insert(keys, k) + end + table.sort(keys) + local mash = '' + for _,k in ipairs(keys) do + mash = string.format('%s%s:%s|', mash, k, hashed_spaces[k]) + end + space_hash = md5.sumhexa(mash) +end + +gregoriotex.number_to_letter = number_to_letter +gregoriotex.init = init +gregoriotex.include_score = include_score +gregoriotex.at_score_end = at_score_end +gregoriotex.at_score_beginning = at_score_beginning +gregoriotex.check_font_version = check_font_version +gregoriotex.get_gregoriotexluaversion = get_gregoriotexluaversion +gregoriotex.map_font = map_font +gregoriotex.init_variant_font = init_variant_font +gregoriotex.change_score_glyph = change_score_glyph +gregoriotex.reset_score_glyph = reset_score_glyph +gregoriotex.scale_score_fonts = scale_score_fonts +gregoriotex.set_font_factor = set_font_factor +gregoriotex.def_symbol = def_symbol +gregoriotex.font_size = font_size +gregoriotex.direct_gabc = direct_gabc +gregoriotex.adjust_line_height = adjust_line_height +gregoriotex.var_brace_len = var_brace_len +gregoriotex.save_length = save_length +gregoriotex.mark_translation = mark_translation +gregoriotex.mark_abovelinestext = mark_abovelinestext +gregoriotex.width_to_bp = width_to_bp +gregoriotex.hypotenuse = hypotenuse +gregoriotex.rotation = rotation +gregoriotex.scale_space = scale_space +gregoriotex.set_header_capture = set_header_capture +gregoriotex.capture_header = capture_header +gregoriotex.is_ypos_different = is_ypos_different +gregoriotex.save_euouae = save_euouae +gregoriotex.mode_part = mode_part +gregoriotex.set_debug_string = set_debug_string +gregoriotex.late_save_position = late_save_position +gregoriotex.is_last_syllable_on_line = is_last_syllable_on_line +gregoriotex.hash_spaces = hash_spaces +gregoriotex.save_dim = save_dim +gregoriotex.save_count = save_count +gregoriotex.change_next_score_line_dim = change_next_score_line_dim +gregoriotex.change_next_score_line_count = change_next_score_line_count dofile(kpse.find_file('gregoriotex-nabc.lua', 'lua')) dofile(kpse.find_file('gregoriotex-signs.lua', 'lua')) |