diff options
author | Karl Berry <karl@freefriends.org> | 2016-04-16 21:22:41 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2016-04-16 21:22:41 +0000 |
commit | c9383c25c72dffde2894fd8a164d53269ce828a1 (patch) | |
tree | 0eb3ad41c8855ab9c21b0924ef23a2215b30f06e /Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua | |
parent | cb7776b7b205ed1ca821d824dd80f7e6c86dc92e (diff) |
gregoriotex (16apr16)
git-svn-id: svn://tug.org/texlive/trunk@40561 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua | 1052 |
1 files changed, 1052 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua b/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua new file mode 100644 index 00000000000..4f94d1a1f9b --- /dev/null +++ b/Master/texmf-dist/tex/luatex/gregoriotex/gregoriotex.lua @@ -0,0 +1,1052 @@ +--GregorioTeX Lua file. +-- +--Copyright (C) 2008-2015 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/>. + +-- this file contains lua functions used by GregorioTeX when called with LuaTeX. + +local hpack, traverse, traverse_id, has_attribute, count, remove, insert_after, copy = node.hpack, node.traverse, node.traverse_id, node.has_attribute, node.count, node.remove, node.insert_after, node.copy + +gregoriotex = gregoriotex or {} +local gregoriotex = gregoriotex + +local internalversion = '4.1.1' -- GREGORIO_VERSION (comment used by VersionManager.py) + +local err, warn, info, log = luatexbase.provides_module({ + name = "gregoriotex", + version = '4.1.1', -- GREGORIO_VERSION + greinternalversion = internalversion, + date = "2016/03/10", -- GREGORIO_DATE_LTX + description = "GregorioTeX module.", + author = "The Gregorio Project (see CONTRIBUTORS.md)", + copyright = "2008-2015 - The Gregorio Project", + license = "GPLv3+", +}) + +gregoriotex.module = { err = err, warn = warn, info = info, log = log } + +local format = string.format + +local hlist = node.id('hlist') +local vlist = node.id('vlist') +local glyph = node.id('glyph') +local glue = node.id('glue') +local whatsit = node.id('whatsit') + +local hyphen = tex.defaulthyphenchar or 45 + +local dash_attr = luatexbase.attributes['gre@attr@dash'] +local potentialdashvalue = 1 +local nopotentialdashvalue = 2 + +local center_attr = luatexbase.attributes['gre@attr@center'] +local startcenter = 1 +local endcenter = 2 + +local glyph_id_attr = luatexbase.attributes['gre@attr@glyph@id'] +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 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 auxname = nil + +local space_below_staff = 5 +local space_above_staff = 13 + +local score_fonts = {} +local symbol_fonts = {} +local loaded_font_sizes = {} +local font_factors = {} +local font_indexed = {} +local next_variant = 0 +local variant_prefix = 'gre@font@variant@' +local number_to_letter = { + ['0'] = 'A', ['1'] = 'B', ['2'] = 'C', ['3'] = 'D', ['4'] = 'E', + ['5'] = 'F', ['6'] = 'G', ['7'] = 'H', ['8'] = 'I', ['9'] = 'J', +} + +local capture_header_macro = {} + +local catcode_at_letter = luatexbase.catcodetables['gre@atletter'] + +local user_defined_subtype = node.subtype('user_defined') +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 mark(value) + local marker = create_marker() + marker.type = 100 + marker.value = value + marker.attr = node.current_attr() + node.write(marker) +end + +local function mark_translation() + mark(translation_mark) +end + +local function mark_abovelinestext() + mark(abovelinestext_mark) +end + +local function is_mark(node, value) + return node.id == whatsit and node.subtype == user_defined_subtype and + node.user_id == marker_whatsit_id and node.value == value +end + +local function keys_changed(tab1, tab2) + if tab2 == nil then return true end + local id,_ + for id,_ in pairs(tab1) do + if tab2[id] == nil then return true end + end + for id,_ in pairs(tab2) do + if tab1[id] == nil then return true end + end + return false +end + +local function heights_changed() + local id, tab + 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 + end + for id, tab in pairs(var_brace_positions) do + if keys_changed(tab, new_var_brace_positions[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 + end + for id, tab in pairs(pos_saves) do + if keys_changed(tab, new_pos_saves[id]) then return true end + end + return false +end + +local function write_greaux() + if heights_changed() 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) + 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])) + end + aux:write(' },\n') + end + aux:write(' },\n ["var_brace_positions"]={\n') + for id, tab in pairs(new_var_brace_positions) 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])) + end + end + aux:write(' },\n') + end + aux:write(' },\n ["pos_saves"]={\n') + for id, tab in pairs(new_pos_saves) 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])) + end + end + aux:write(' },\n') + end + aux:write(' },\n}\n') + aux:close() + else + err("\n Unable to open %s", auxname) + end + + warn("Line heights or variable brace lengths may have changed. Rerun to fix.") + end +end + +local function init(arg, enable_height_computation) + -- is there a better way to get the output directory? + local outputdir = nil + for k,v in pairs(arg) do + if v:find('%-output%-directory') then + if v:find('%-output%-directory=') then + outputdir=string.explode(v, '=')[2] + else + outputdir=arg[tonumber(k)+1] + end + end + end + if outputdir and lfs.isdir(outputdir) then + auxname = outputdir..'/'..tex.jobname..'.gaux' + else + auxname = tex.jobname..'.gaux' + end + + -- to get latexmk to realize the aux file is a dependency + texio.write_nl('('..auxname..')') + if lfs.isfile(auxname) then + 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 {} + else + line_heights = {} + var_brace_positions = {} + pos_saves = {} + end + + if enable_height_computation then + new_line_heights = {} + + local mcb_version = luatexbase.get_module_version and + luatexbase.get_module_version('luatexbase-mcb') or 9999 + if mcb_version and mcb_version > 0.6 then + luatexbase.add_to_callback('finish_pdffile', write_greaux, + 'gregoriotex.write_greaux') + else + -- The version of luatexbase in TeX Live 2014 does not support it, and + -- luatexbase prevents a direct call to callback.register. Because of + -- this, we lose the LuaTeX statistics and "output written to" messages, + -- but I know of no other workaround. + + luatexbase.add_to_callback('stop_run', write_greaux, + 'gregoriotex.write_greaux') + end + else + warn('Height computation has been skipped. Gregorio will use '.. + 'previously computed values if available but will not recompute '.. + 'line heights. Remove or undefine \\greskipheightcomputation to '.. + 'resume height computation.') + end + new_var_brace_positions = {} + new_pos_saves = {} +end + +-- node factory +local tmpnode = node.new(glyph, 0) +tmpnode.font = 0 +tmpnode.char = hyphen +local function gethyphennode() + return copy(tmpnode) +end + +local function getdashnnode() + local hyphnode = gethyphennode() + local dashnode = hpack(hyphnode) + dashnode.shift = 0 + return dashnode,hyphnode +end + +-- a simple (for now) function to dump nodes for debugging +local function dump_nodes(head) + local n, m + for n in traverse(head) do + if node.type(n.id) == 'penalty' then + log("%s=%d {%d}", node.type(n.id), n.penalty, has_attribute(n, glyph_id_attr)) + elseif n.id == whatsit and n.subtype == user_defined_subtype and n.user_id == marker_whatsit_id then + log("marker-whatsit %s", n.value) + else + log("node %s [%d] {%d}", node.type(n.id), n.subtype, has_attribute(n, glyph_id_attr)) + end + if n.id == hlist then + for m in traverse(n.head) do + if node.type(m.id) == 'penalty' then + log("..%s=%d {%d}", node.type(m.id), m.penalty, has_attribute(n, glyph_id_attr)) + elseif m.id == whatsit and m.subtype == user_defined_subtype and m.user_id == marker_whatsit_id then + log("..marker-whatsit %s", m.value) + else + log("..node %s [%d] {%d}", node.type(m.id), m.subtype, has_attribute(n, glyph_id_attr)) + end + end + end + end + log('--end dump--') +end + +local function center_translation(startnode, endnode, ratio, sign, order) + -- total width between beginning the two centering points + local total_width = node.dimensions(ratio, sign, order, startnode, endnode) + -- definition of translation with a beginning is: + -- \hbox to 0pt{ + -- \kern 0pt + -- \vbox to 0pt{ + -- \vss\hbox to 0pt{ + -- translation\hss + -- } + -- } + -- \kern 0pt + -- } + -- + -- hence translation width is: + local trans_width = node.dimensions(startnode.head.next.head.next.head) + -- now we must transform the kern 0pt into kern Xpt and kern -Xpt where X is: + local X = (total_width - trans_width) / 2 + startnode.head.kern = X + startnode.head.next.next.kern = -X +end + +local debug_types_activated = {['linesglues'] = false} + +local function set_debug_string(debugstring) + for debugtype in string.gmatch(debugstring, "[^,]+") do + debug_types_activated[debugtype] = true + end +end + +local glue_sign_name = {[0] = 'normal', [1] = 'stretching', [2] = 'shrinking'} + +local function debugmessage(type, message) + if (debug_types_activated[type] or debug_types_activated['all']) then + texio.write_nl('GregorioTeX debug: ('..type..'): '..message) + end +end + +-- 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) + -- TODO: to be changed according to the font + local lastseennode = nil + local adddash = false + local currentfont = 0 + local currentshift = 0 + local centerstartnode = nil + local line_id = nil + local line_top = nil + local line_bottom = nil + local line_has_translation = false + local line_has_abovelinestext = false + local linenum = 0 + -- we explore the lines + for line in traverse(h) do + if line.id == glue then + if line.next ~= nil and line.next.id == hlist + and has_attribute(line.next, dash_attr) + and count(hlist, line.next.head) <= 2 then + --log("eating glue") + h, line = remove(h, line) + end + elseif line.id == hlist and has_attribute(line, dash_attr) then + -- the next two lines are to remove the dumb lines + if count(hlist, line.head) <= 2 then + --log("eating line") + 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)) + centerstartnode = nil + line_id = nil + line_top = nil + line_bottom = nil + line_has_translation = false + line_has_abovelinestext = false + for n in traverse_id(hlist, line.head) do + if has_attribute(n, center_attr, startcenter) then + centerstartnode = n + elseif has_attribute(n, center_attr, endcenter) then + if not centerstartnode then + warn("End of a translation centering area encountered on a\nline without translation centering beginning,\nskipping translation...") + else + center_translation(centerstartnode, n, line.glue_set, line.glue_sign, line.glue_order) + end + elseif has_attribute(n, dash_attr, potentialdashvalue) then + adddash=true + lastseennode=n + currentfont = 0 + -- we traverse the list, to detect the font to use, + -- and also not to add an hyphen if there is already one + for g in node.traverse_id(glyph, n.head) do + if currentfont == 0 then + currentfont = g.font + end + if g.char == hyphen or g.char == 45 then + adddash = false + end + end + if currentshift == 0 then + currentshift = n.shift + end + -- if we encounter a text that doesn't need a dash, we acknowledge it + elseif has_attribute(n, dash_attr, nopotentialdashvalue) then + adddash=false + end + + if new_score_heights then + local glyph_id = has_attribute(n, glyph_id_attr) + local glyph_top = has_attribute(n, glyph_top_attr) or 9 -- 'g' + local glyph_bottom = has_attribute(n, glyph_bottom_attr) or 9 -- 'g' + if glyph_id and glyph_id > prev_line_id then + if not line_id or glyph_id > line_id then + line_id = glyph_id + end + if not line_top or glyph_top > line_top then + line_top = glyph_top + end + if not line_bottom or glyph_bottom < line_bottom then + line_bottom = glyph_bottom + end + end + end + end + -- look for marks + if new_score_heights then + for n in traverse_id(whatsit, line.head) do + line_has_translation = line_has_translation or + is_mark(n, translation_mark) + line_has_abovelinestext = line_has_abovelinestext or + is_mark(n, abovelinestext_mark) + end + end + if adddash==true then + local dashnode, hyphnode = getdashnnode() + dashnode.shift = currentshift + hyphnode.font = currentfont + insert_after(line.head, lastseennode, dashnode) + addash=false + end + if line_id then + new_score_heights[prev_line_id] = { line_top, line_bottom, + line_has_translation and 1 or 0, + line_has_abovelinestext and 1 or 0 } + prev_line_id = line_id + end + end + -- we reinitialize the shift value, because it may change according to the line + currentshift=0 + end + end + --dump_nodes(h) + -- due to special cases, we don't return h here (see comments in bug #20974) + return true +end + +-- In gregoriotex, hyphenation is made by the process function, so TeX hyphenation +-- is just a waste of time. This function will be registered in the hyphenate +-- callback to skip this step and thus gain a little time. +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 inclusion = score_inclusion[score_id] or 1 + score_inclusion[score_id] = inclusion + 1 + score_id = score_id..'.'..inclusion + cur_score_id = score_id + if (top_height > top_height_adj or bottom_height < bottom_height_adj + or has_translation ~= 0 or has_above_lines_text ~= 0) + and tex.count['gre@variableheightexpansion'] == 1 then + score_heights = line_heights[score_id] or {} + if new_line_heights then + new_score_heights = {} + new_line_heights[score_id] = new_score_heights + end + prev_line_id = tex.getattribute(glyph_id_attr) + else + score_heights = nil + new_score_heights = nil + end + + luatexbase.add_to_callback('post_linebreak_filter', process, 'gregoriotex.callback', 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') + luatexbase.remove_from_callback("hyphenate", "gregoriotex.disable_hyphenation") +end + +local function clean_old_gtex_files(file_withdir) + local filename = "" + local dirpath = "" + local sep = "" + local onwindows = os.type == "windows" or + string.find(os.getenv("PATH"),";",1,true) + if onwindows then + sep = "\\" + dirpath = string.match(file_withdir, "(.*)"..sep) + else + sep = "/" + dirpath = string.match(file_withdir, "(.*)"..sep) + end + if dirpath then -- dirpath is nil if current directory + filename = "^"..file_withdir:match(".*/".."(.*)").."%-%d+_%d+_%d+[-%a%d]*%.gtex$" + for a in lfs.dir(dirpath) do + if a:match(filename) then + os.remove(dirpath..sep..a) + end + end + else + filename = "^"..file_withdir.."%-%d+_%d+_%d+[-%a%d]*%.gtex$" + for a in lfs.dir(lfs.currentdir()) do + if a:match(filename) then os.remove(a) end + end + end +end + +local function compile_gabc(gabc_file, gtex_file) + info("compiling the score %s...", gabc_file) + local extra_args = '' + if tex.count['gre@generate@pointandclick'] == 1 then + extra_args = ' -p' + end + + res = os.execute(string.format("gregorio %s -o %s %s", extra_args, gtex_file, gabc_file)) + 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) + elseif res ~= 0 then + err("\nAn error occured when compiling the score file\n'%s' with gregorio.\nPlease check your score file.", gabc_file) + else + -- open the gtex file for writing so that LuaTeX records output to it + -- when the -recorder option is used + local gtex = io.open(gtex_file, 'a') + if gtex == nil then + err("\n Unable to open %s", gtex_file) + else + gtex: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 + end + local gtex_file = file_root.."-"..internalversion:gsub("%.", "_")..".gtex" + local gabc_file = file_root..".gabc" + if not lfs.isfile(gtex_file) then + clean_old_gtex_files(file_root) + 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') + if gabc == nil then + err("\n Unable to open %s", gabc_file) + return + else + gabc:close() + end + compile_gabc(gabc_file, gtex_file) + tex.print(string.format([[\input %s\relax]], gtex_file)) + return + else + err("The file %s does not exist.", gabc_file) + return + end + end + local gtex_timestamp = lfs.attributes(gtex_file).modification + local gabc_timestamp = lfs.attributes(gabc_file).modification + -- open the gabc file for reading so that LuaTeX records input from it + -- when the -recorder option is used; do this here so that this happens + -- on every run + local gabc = io.open(gabc_file, 'r') + if gabc == nil then + err("\n Unable to open %s", gabc_file) + else + gabc:close() + 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) + elseif force_gabccompile then + compile_gabc(gabc_file, gtex_file) + 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') + -- 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') + 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) + 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 check_font_version() + local gregoriofont = get_font_by_name('gre@font@music') + if gregoriofont then + local fontversion = gregoriofont.shared.rawdata.metadata.version + if fontversion ~= internalversion then + local fontname = gregoriofont.shared.rawdata.metadata.fontname + local fontfile = gregoriofont.shared.rawdata.metadata.origname + err("\nUncoherent file versions!\ngregoriotex.tex is version %s\nwhile %s.ttf is version %s\nplease update file\n%s", internalversion, fontname, fontversion, fontfile) + end + end +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) + tex.sprint(catcode_at_letter, string.format( + [[\xdef\Gre%s%s{\char%d}]], prefix, glyph, unicode)) + end + end +end + +local function init_variant_font(font_name, for_score, gre_factor) + if font_name ~= '*' then + local font_table = for_score and score_fonts or symbol_fonts + if font_table[font_name] == nil then + local font_csname = variant_prefix..string.gsub(tostring(next_variant), '[0-9]', number_to_letter) + font_table[font_name] = font_csname + log("Registering variant font %s as %s.", font_name, font_csname) + if for_score then + tex.print(catcode_at_letter, string.format( + [[\global\font\%s = {name:%s} at 10 sp\relax ]], + font_csname, font_name)) + -- 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} + if font_factors[font_name] == nil then + font_factors[font_name] = '100000' + end + else + -- is there a nice way to make this string readable? + tex.print(catcode_at_letter, string.format( + [[\gdef\%sSymReload#1{{\edef\localsize{#1}\ifx\localsize\%sSymSize\relax\relax\else\global\font\%s = {name:%s} at \localsize pt\relax\xdef\%sSymSize{\localsize}\fi}}\xdef\%sSymSize{0}\%sSymReload{\gre@symbolfontsize}]], + font_csname, font_csname, font_csname, font_name, font_csname, + font_csname, font_csname)) + end + next_variant = next_variant + 1 + end + end +end + +local function set_score_glyph(csname, font_csname, char) + log([[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) + tex.print(catcode_at_letter, string.format( + [[\edef\%s{{\char%d}}]], csname, char)) +end + +local function set_symbol_glyph(csname, font_csname, char) + tex.print(catcode_at_letter, string.format( + [[\def\%s{\%sSymReload{\gre@symbolfontsize}{\%s\char%d}\relax}]], + csname, font_csname, font_csname, char)) +end + +local function set_sized_symbol_glyph(csname, font_csname, char) + tex.print(catcode_at_letter, string.format( + [[\gdef\%s#1{\%sSymReload{#1}{\%s\char%d}\relax}]], + csname, font_csname, font_csname, char)) +end + +local function def_glyph(csname, font_name, glyph, font_table, setter) + local font_csname = font_table[font_name] + local char + if string.match(glyph, '^%d+$') then + char = tonumber(glyph) + else + local fid = font_id(font_csname) + if fid < 0 then + err('\nFont %s is not defined.', font_name) + end + char = get_font_resources(fid).unicodes[glyph] + if char == nil then + err('\nGlyph %s in font %s was not found.', glyph, font_name) + end + end + 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) + else + def_glyph('GreCP'..glyph_name, font_name, replacement, score_fonts, + set_score_glyph) + end +end + +local function change_score_glyph(glyph_name, font_name, replacement) + 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 other_font + if font_name == '*' then + other_font = get_score_font_resources('greciliae').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 + 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) + end + end + end + else + if string.match(replacement, '^%.') then + replacement = glyph_name..replacement + end + change_single_score_glyph(glyph_name, font_name, replacement) + end +end + +local function reset_score_glyph(glyph_name) + 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 + if not string.match(name, '%.') and char >= 0 and string.match(name, glyph_name) then + set_common_score_glyph('GreCP'..name, nil, char) + end + end + else + local char = get_score_font_resources("greciliae").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) + end +end + +local function set_font_factor(font_name, font_factor) + font_factors[font_name] = font_factor +end + +local function scale_score_fonts(size, 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 + 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])) + loaded_font_sizes[font_name] = {size = size, gre_factor = gre_factor} + end + end +end + +local function def_symbol(csname, font_name, glyph, sized) + def_glyph(csname, font_name, glyph, symbol_fonts, + sized and set_sized_symbol_glyph or set_symbol_glyph) +end + +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 + 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 ]]) + end + end + end +end + +local function var_brace_note_pos(brace, start_end) + tex.print(string.format([[\luatexlatelua{gregoriotex.late_brace_note_pos('%s', %d, %d, \number\pdflastxpos)}]], cur_score_id, brace, start_end)) +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] = {} + 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 + end + end + tex.print('2mm') +end + +local function save_pos(index, which) + tex.print(string.format([[\pdfsavepos\luatexlatelua{gregoriotex.late_save_pos('%s', %d, %d, \number\pdflastxpos, \number\pdflastypos)}]], 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] = {} + end + if new_pos_saves[score_id][index] == nil then + new_pos_saves[score_id][index] = {} + end + new_pos_saves[score_id][index][(2 * which) - 1] = xpos + new_pos_saves[score_id][index][2 * which] = ypos +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 + tex.sprint([[\number0\relax ]]) + end + else + tex.sprint([[\number1\relax ]]) + end +end + +local function width_to_bp(width, value_if_star) + if width == '*' then + tex.print(value_if_star or '0') + else + tex.print(tex.sp(width) * 1.00375 / 65536) + end +end + +-- computes the hypotenuse given the width and height of the right triangle +local function hypotenuse(width, height) + log("width %s height %s", width, height) + local a = tex.sp(width) + local b = tex.sp(height) + tex.sprint(math.sqrt((a * a) + (b * b)) .. 'sp') +end + +-- computes the rotation angle opposite the height of a right triangle +local function rotation(width, height) + local a = tex.sp(width) + local b = tex.sp(height) + tex.sprint(math.deg(math.atan2(b, a))) +end + +local function scale_space(factor) + local skip = tex.getskip('gre@skip@temp@four') + skip.width = skip.width * factor + -- should skip.stretch and skip.shink also be scaled? +end + +local function set_header_capture(header, macro_name, flags) + if macro_name == '' then + capture_header_macro[header] = nil + log("no longer capturing header %s", header) + else + local macro = {} + macro.name = macro_name + flags:gsub("([^,]+)", function(flag) + if flag == "string" then macro.takes_string = true + elseif flag == "name" then macro.takes_header_name = true + else err("unknown header capture flag: %s", flag) + end + end) + capture_header_macro[header] = macro + log("capturing header %s using %s, string=%s, name=%s", header, macro.name, macro.takes_string, macro.takes_header_name) + end +end + +local function capture_header(header, value) + local macro = capture_header_macro[header] + if macro ~= nil then + tex.sprint(string.format([[\%s{]], macro.name)) + if macro.takes_header_name then + tex.sprint(-2, header) + tex.sprint('}{') + end + if macro.takes_string then + tex.sprint(-2, value) + else + tex.sprint(value) + end + tex.print('}') + end +end + +local function mode_part(part) + if part ~= '' then + if not unicode.utf8.match(part, '^%p') then + tex.sprint([[\thinspace]]) + end + tex.print(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 + +dofile(kpse.find_file('gregoriotex-nabc.lua', 'lua')) +dofile(kpse.find_file('gregoriotex-signs.lua', 'lua')) +dofile(kpse.find_file('gregoriotex-symbols.lua', 'lua')) |