summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/lyluatex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2019-05-28 21:25:42 +0000
committerKarl Berry <karl@freefriends.org>2019-05-28 21:25:42 +0000
commita77f7d3afb1f5e076c5190ab9c84372e72dd67ba (patch)
tree07abaa851e7402501a55f39bf35690554541cbdb /Master/texmf-dist/scripts/lyluatex
parent65ff5cb34596f15fda21e8d5b484b6d938b72c8a (diff)
lyluatex (28may19)
git-svn-id: svn://tug.org/texlive/trunk@51252 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/lyluatex')
-rwxr-xr-xMaster/texmf-dist/scripts/lyluatex/lyluatex-lib.lua163
-rwxr-xr-xMaster/texmf-dist/scripts/lyluatex/lyluatex-options.lua266
-rwxr-xr-xMaster/texmf-dist/scripts/lyluatex/lyluatex.lua662
3 files changed, 725 insertions, 366 deletions
diff --git a/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua b/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua
new file mode 100755
index 00000000000..ebe6745eb59
--- /dev/null
+++ b/Master/texmf-dist/scripts/lyluatex/lyluatex-lib.lua
@@ -0,0 +1,163 @@
+-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status
+local err, warn, info, log = luatexbase.provides_module({
+ name = "lyluatex-lib",
+ version = '1.0f', --LYLUATEX_VERSION
+ date = "2019/05/27", --LYLUATEX_DATE
+ description = "Module lyluatex-lib.",
+ author = "The Gregorio Project − (see Contributors.md)",
+ copyright = "2015-2019 - jperon and others",
+ license = "MIT",
+})
+
+local lib = {}
+lib.TEX_UNITS = {'bp', 'cc', 'cm', 'dd', 'in', 'mm', 'pc', 'pt', 'sp', 'em',
+'ex'}
+
+-------------------------
+-- General tool functions
+
+function lib.basename(str)
+--[[
+ Given the full path to a file, return only the file name (without path).
+ If there is no slash, return the full name.
+--]]
+ return str:gsub(".*/(.*)", "%1") or str
+end
+
+
+function lib.contains(table_var, value)
+--[[
+ Returns the key if the given table contains the given value, or nil.
+ A value of 'false' (string) is considered equal to false (Boolean).
+--]]
+ for k, v in pairs(table_var) do
+ if v == value then return k
+ elseif v == 'false' and value == false then return k
+ end
+ end
+end
+
+
+function lib.contains_key(table_var, key)
+-- Returs true if the given key is present in the table, nil otherwise.
+ for k in pairs(table_var) do
+ if k == key then return true end
+ end
+end
+
+
+function lib.convert_unit(value)
+--[[
+ Convert a LaTeX unit, if possible.
+ TODO: Understand what this *really* does, what is accepted and returned.
+--]]
+ if not value then return 0
+ elseif value == '' then return false
+ elseif value:match('\\') then
+ local n, u = value:match('^%d*%.?%d*'), value:match('%a+')
+ if n == '' then n = 1 end
+ return tonumber(n) * tex.dimen[u] / tex.sp("1pt")
+ else return ('%f'):format(tonumber(value) or tex.sp(value) / tex.sp("1pt"))
+ end
+end
+
+
+function lib.dirname(str)
+--[[
+ Given the full path to a file, return only the path (without file name),
+ including the last slash. If there is no slash, return an empty string.
+--]]
+ return str:gsub("(.*/).*", "%1") or ''
+end
+
+
+local fontdata = fonts.hashes.identifiers
+function lib.fontinfo(id)
+--[[
+ Return a LuaTeX font object based on the given ID
+--]]
+ return fontdata[id] or font.fonts[id]
+end
+
+
+function lib.max(a, b)
+ a, b = tonumber(a), tonumber(b)
+ if a > b then return a else return b end
+end
+
+
+function lib.min(a, b)
+ a, b = tonumber(a), tonumber(b)
+ if a < b then return a else return b end
+end
+
+
+function lib.mkdirs(str)
+ local path
+ if str:sub(1, 1) == '/' then path = '' else path = '.' end
+ for dir in str:gmatch('([^%/]+)') do
+ path = path .. '/' .. dir
+ lfs.mkdir(path)
+ end
+end
+
+
+function lib.orderedpairs(t)
+ local key
+ local i = 0
+ local orderedIndex = {}
+ for k in pairs(t) do table.insert(orderedIndex, k) end
+ table.sort(orderedIndex)
+ return function ()
+ i = i + 1
+ key = orderedIndex[i]
+ if key then return key, t[key] end
+ end
+end
+
+
+function lib.readlinematching(s, f)
+ if f then
+ local result = ''
+ while result and not result:find(s) do result = f:read() end
+ f:close()
+ return result
+ end
+end
+
+
+function lib.splitext(str, ext)
+--[[
+ If 'ext' is supplied return str stripped of the given extension,
+ otherwise return the base and extension (if any)
+--]]
+ return ext and (str:match('(.*)%.'..ext..'$') or str)
+ or (str:match('(.*)%.(%w*)$') or str)
+end
+
+
+------------------------------------
+-- Engine, version, TeX distribution
+
+local tex_engine = {}
+setmetatable(tex_engine, tex_engine)
+
+function tex_engine:__call()
+--[[
+ Defines the properties extracted from the first line of jobname.log.
+--]]
+ local f = io.open(tex.jobname..'.log')
+ if not f then return end
+ self.engine, self.engine_version, self.dist, self.dist_version, self.format, self.format_version =
+ f:read():match(
+ 'This is ([^,]*), Version ([^%(]*) %(([^%)]*) ([^%)]*)%)%s+%(format=([^%)]*) ([^)]*)%)'
+ )
+ f:close()
+ return self
+end
+
+function tex_engine:__index(k) return rawget(self(), k) end
+
+
+lib.tex_engine = tex_engine
+return lib
diff --git a/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua b/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua
new file mode 100755
index 00000000000..62ec72a3c3c
--- /dev/null
+++ b/Master/texmf-dist/scripts/lyluatex/lyluatex-options.lua
@@ -0,0 +1,266 @@
+-- luacheck: ignore ly warn info log self luatexbase internalversion font fonts tex token kpse status
+local err, warn, info, log = luatexbase.provides_module({
+ name = "lyluatex-options",
+ version = '1.0f', --LYLUATEX_VERSION
+ date = "2019/05/27", --LYLUATEX_DATE
+ description = "Module lyluatex-options.",
+ author = "The Gregorio Project − (see Contributors.md)",
+ copyright = "2015-2019 - jperon and others",
+ license = "MIT",
+})
+
+--[[
+ This module provides functionality to handle package options and make them
+ configurable in a fine-grained fashion as
+ - package options
+ - local options (for individual instances of commands/environments)
+ - changed “from here on” within a document.
+
+-- ]]
+
+local lib = require(kpse.find_file("lyluatex-lib.lua") or "lyluatex-lib.lua")
+local optlib = {} -- namespace for the returned table
+local Opts = {options = {}} -- Options class
+Opts.__index = function (self, k) return self.options[k] or rawget(Opts, k) end
+setmetatable(Opts, Opts)
+
+
+function Opts:new(opt_prefix, declarations)
+--[[
+ Declare package options along with their default and
+ accepted values. To *some* extent also provide type checking.
+ - opt_prefix: the prefix/name by which the lyluatex-options module is
+ referenced in the parent LaTeX document (preamble or package).
+ This is required to write the code calling optlib.set_option into
+ the option declaration.
+ - declarations: a definition table stored in the calling module (see below)
+ Each entry in the 'declarations' table represents one package option, with each
+ value being an array (table with integer indexes instead of keys). For
+ details please refer to the manual.
+--]]
+ local o = setmetatable(
+ {
+ declarations = declarations,
+ options = {}
+ },
+ self
+ )
+ local exopt = ''
+ for k, v in pairs(declarations) do
+ o.options[k] = v[1] or ''
+ tex.sprint(string.format([[
+\DeclareOptionX{%s}{\directlua{
+ %s:set_option('%s', '\luatexluaescapestring{#1}')
+}}%%
+]],
+ k, opt_prefix, k
+ ))
+ exopt = exopt..k..'='..(v[1] or '')..','
+ end
+ tex.sprint([[\ExecuteOptionsX{]]..exopt..[[}%%]], [[\ProcessOptionsX]])
+ return o
+end
+
+function Opts:check_local_options(opts, ignore_declarations)
+--[[
+ Parse the given options (options passed to a command/environment),
+ sanitize them against the global package options and return a table
+ with the local options that should then supersede the global options.
+ If ignore_declaration is given any non-false value the sanitization
+ step is skipped (i.e. local options are only parsed and duplicates
+ rejected).
+--]]
+ local options = {}
+ local next_opt = opts:gmatch('([^,]+)') -- iterator over options
+ for opt in next_opt do
+ local k, v = opt:match('([^=]+)=?(.*)')
+ if k then
+ if v and v:sub(1, 1) == '{' then -- handle keys with {multiple, values}
+ while select(2, v:gsub('{', '')) ~= select(2, v:gsub('}', '')) do v = v..','..next_opt() end
+ v = v:sub(2, -2) -- remove { }
+ end
+ if not ignore_declarations then
+ k, v = self:sanitize_option(k:gsub('^%s', ''), v:gsub('^%s', ''))
+ end
+ if k then
+ if options[k] then err('Option %s is set two times for the same score.', k)
+ else options[k] = v
+ end
+ end
+ end
+ end
+ return options
+end
+
+function Opts:is_neg(k)
+--[[
+ Type check for a 'negative' option. This is an existing option
+ name prefixed with 'no' (e.g. 'noalign')
+--]]
+ local _, i = k:find('^no')
+ return i and lib.contains_key(self.declarations, k:sub(i + 1))
+end
+
+function Opts:sanitize_option(k, v)
+--[[
+ Check and (if necessary) adjust the value of a given option.
+ Reject undefined options
+ Check 'negative' options
+ Handle boolean options (empty strings or 'false'), set them to real booleans
+--]]
+ local declarations = self.declarations
+ if k == '' or k == 'noarg' then return end
+ if not lib.contains_key(declarations, k) then err('Unknown option: '..k) end
+ -- aliases
+ if declarations[k] and declarations[k][2] == optlib.is_alias then
+ if declarations[k][1] == v then return
+ else k = declarations[k] end
+ end
+ -- boolean
+ if v == 'false' then v = false end
+ -- negation (for example, noindent is the negation of indent)
+ if self:is_neg(k) then
+ if v ~= nil and v ~= 'default' then
+ k = k:gsub('^no(.*)', '%1')
+ v = not v
+ else return
+ end
+ end
+ return k, v
+end
+
+function Opts:set_option(k, v)
+--[[
+ Set an option for the given prefix to be in effect from this point on.
+ Raises an error if the option is not declared or does not meet the
+ declared expectations. (TODO: The latter has to be integrated by extracting
+ optlib.validate_option from optlib.validate_options and call it in
+ sanitize_option).
+--]]
+ k, v = self:sanitize_option(k, v)
+ if k then
+ self.options[k] = v
+ self:validate_option(k)
+ end
+end
+
+function Opts:validate_option(key, options_obj)
+--[[
+ Validate an (already sanitized) option against its expected values.
+ With options_obj a local options table can be provided,
+ otherwise the global options stored in OPTIONS are checked.
+--]]
+ local package_opts = self.declarations
+ local options = options_obj or self.options
+ local unexpected
+ if options[key] == 'default' then
+ -- Replace 'default' with an actual value
+ options[key] = package_opts[key][1]
+ unexpected = options[key] == nil
+ end
+ if not lib.contains(package_opts[key], options[key]) and package_opts[key][2] then
+ -- option value is not in the array of accepted values
+ if type(package_opts[key][2]) == 'function' then package_opts[key][2](key, options[key])
+ else unexpected = true
+ end
+ end
+ if unexpected then
+ err([[
+ Unexpected value "%s" for option %s:
+ authorized values are "%s"
+ ]],
+ options[key], key, table.concat(package_opts[key], ', ')
+ )
+ end
+end
+
+function Opts:validate_options(options_obj)
+--[[
+ Validate the given set of options against the option declaration
+ table for the given prefix.
+ With options_obj a local options table can be provided,
+ otherwise the global options stored in OPTIONS are checked.
+--]]
+ for k, _ in lib.orderedpairs(self.declarations) do
+ self:validate_option(k, options_obj)
+ end
+end
+
+
+function optlib.is_alias()
+--[[
+ This function doesn't do anything, but if an option is defined
+ as an alias, its second parameter will be this function, so the
+ test declarations[k][2] == optlib.is_alias in Opts:sanitize_options
+ will return true.
+--]]
+end
+
+
+function optlib.is_dim(k, v)
+--[[
+ Type checking for options that accept a LaTeX dimension.
+ This can be
+ - a number (integer or float)
+ - a number with unit
+ - a (multiplied) TeX length
+ (see error message in code for examples)
+--]]
+ if v == '' or v == false or tonumber(v) then return true end
+ local n, sl, u = v:match('^%d*%.?%d*'), v:match('\\'), v:match('%a+')
+ -- a value of number - backslash - length is a dimension
+ -- invalid input will be prevented in by the LaTeX parser already
+ if n and sl and u then return true end
+ if n and lib.contains(lib.TEX_UNITS, u) then return true end
+ err([[
+Unexpected value "%s" for dimension %s:
+should be either a number (for example "12"),
+a number with unit, without space ("12pt"),
+or a (multiplied) TeX length (".8\linewidth")
+]],
+ v, k
+ )
+end
+
+
+function optlib.is_neg(k, _)
+--[[
+ Type check for a 'negative' option. At this stage,
+ we only check that it begins with 'no'.
+--]]
+ return k:find('^no')
+end
+
+
+function optlib.is_num(_, v)
+--[[
+ Type check for number options
+--]]
+ return v == '' or tonumber(v)
+end
+
+
+function optlib.is_str(_, v)
+--[[
+ Type check for string options
+--]]
+ return type(v) == 'string'
+end
+
+
+function optlib.merge_options(base_opt, super_opt)
+--[[
+ Merge two tables.
+ Create a new table as a copy of base_opt, then merge with
+ super_opt. Entries in super_opt supersede (i.e. overwrite)
+ colliding entries in base_opt.
+--]]
+ local result = {}
+ for k, v in pairs(base_opt) do result[k] = v end
+ for k, v in pairs(super_opt) do result[k] = v end
+ return result
+end
+
+
+optlib.Opts = Opts
+return optlib
diff --git a/Master/texmf-dist/scripts/lyluatex/lyluatex.lua b/Master/texmf-dist/scripts/lyluatex/lyluatex.lua
index fd54cd22c87..1023e20ef9a 100755
--- a/Master/texmf-dist/scripts/lyluatex/lyluatex.lua
+++ b/Master/texmf-dist/scripts/lyluatex/lyluatex.lua
@@ -1,24 +1,29 @@
--- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status
+-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status ly_opts
local err, warn, info, log = luatexbase.provides_module({
name = "lyluatex",
- version = '1.0b', --LYLUATEX_VERSION
- date = "2018/03/12", --LYLUATEX_DATE
+ version = '1.0f', --LYLUATEX_VERSION
+ date = "2019/05/27", --LYLUATEX_DATE
description = "Module lyluatex.",
author = "The Gregorio Project − (see Contributors.md)",
- copyright = "2015-2018 - jperon and others",
+ copyright = "2015-2019 - jperon and others",
license = "MIT",
})
+local lib = require(kpse.find_file("lyluatex-lib.lua") or "lyluatex-lib.lua")
+local ly_opts = ly_opts -- global ly_opts has been defined before in lyluatex.sty
+
local md5 = require 'md5'
local lfs = require 'lfs'
local latex = {}
-local ly = {}
-local obj = {}
-local Score = {}
+local ly = {
+ err = err,
+ varwidth_available = kpse.find_file('varwidth.sty')
+}
+local Score = ly_opts.options
+Score.__index = Score
local FILELIST
-local OPTIONS = {}
local DIM_OPTIONS = {
'extra-bottom-margin',
'extra-top-margin',
@@ -38,6 +43,8 @@ local DIM_OPTIONS = {
local HASHIGNORE = {
'autoindent',
'cleantmp',
+ 'do-not-print',
+ 'force-compilation',
'hpadding',
'max-left-protrusion',
'max-right-protrusion',
@@ -56,7 +63,6 @@ local MXML_OPTIONS = {
'verbose',
}
local TEXINFO_OPTIONS = {'doctitle', 'nogettext', 'texidoc'}
-local TEX_UNITS = {'bp', 'cc', 'cm', 'dd', 'in', 'mm', 'pc', 'pt', 'sp', 'em', 'ex'}
local LY_HEAD = [[
%%File header
\version "<<<version>>>"
@@ -81,13 +87,15 @@ local LY_HEAD = [[
}
\layout{
<<<staffprops>>>
+ <<<fixbadlycroppedstaffgroupbrackets>>>
}
+<<<header>>>
%%Follows original score
]]
---[[ ========================== Helper functions ========================== ]]
+--[[ ========================== Helper functions ========================== --]]
-- dirty fix as info doesn't work as expected
local oldinfo = info
function info(...)
@@ -100,55 +108,33 @@ local function debug(...)
end
-local function contains(table_var, value)
- for _, v in pairs(table_var) do
- if v == value then return true
- elseif v == 'false' and value == false then return true
- end
- end
-end
-
-
-local function contains_key(table_var, key)
- for k in pairs(table_var) do
- if k == key then return true end
- end
-end
-
-
-local function convert_unit(value)
- if not value then return 0
- elseif value == '' then return false
- elseif value:match('\\') then
- local n, u = value:match('^%d*%.?%d*'), value:match('%a+')
- if n == '' then n = 1 end
- return tonumber(n) * tex.dimen[u] / tex.sp("1pt")
- else return ('%f'):format(tonumber(value) or tex.sp(value) / tex.sp("1pt"))
- end
-end
-
-
-local function dirname(str) return str:gsub("(.*/)(.*)", "%1") or '' end
-
-
local function extract_includepaths(includepaths)
includepaths = includepaths:explode(',')
local cfd = Score.currfiledir:gsub('^$', './')
table.insert(includepaths, 1, cfd)
for i, path in ipairs(includepaths) do
-- delete initial space (in case someone puts a space after the comma)
- includepaths[i] = path:gsub('^ ', ''):gsub('^~', os.getenv("HOME"))
+ includepaths[i] = path:gsub('^ ', ''):gsub('^~', os.getenv("HOME")):gsub('^%.%.', './..')
end
return includepaths
end
-local fontdata = fonts.hashes.identifiers
-local function fontinfo(id) return fontdata[id] or font.fonts[id] end
+local function font_default_staffsize()
+ return lib.fontinfo(font.current()).size/39321.6
+end
-local function font_default_staffsize()
- return fontinfo(font.current()).size/39321.6
+local function includes_parse(list)
+ local includes = ''
+ if list then
+ list = list:explode(',')
+ for _, included_file in ipairs(list) do
+ warn(included_file)
+ includes = includes .. '\\include "'..included_file..'.ly"\n'
+ end
+ end
+ return includes
end
@@ -165,70 +151,12 @@ local function locate(file, includepaths, ext)
end
-local function max(a, b)
- a, b = tonumber(a), tonumber(b)
- if a > b then return a else return b end
-end
-
-
-local function min(a, b)
- a, b = tonumber(a), tonumber(b)
- if a < b then return a else return b end
-end
-
-
-local function mkdirs(str)
- local path = '.'
- for dir in str:gmatch('([^%/]+)') do
- path = path .. '/' .. dir
- lfs.mkdir(path)
- end
-end
-
-
-local function orderedpairs(t)
- local key
- local i = 0
- local orderedIndex = {}
- for k in pairs(t) do table.insert(orderedIndex, k) end
- table.sort(orderedIndex)
- return function ()
- i = i + 1
- key = orderedIndex[i]
- if key then return key, t[key] end
- end
-end
-
-
-local function process_options(k, v)
- if k == '' or k == 'noarg' then return end
- if not contains_key(OPTIONS, k) then err('Unknown option: '..k) end
- -- aliases
- if OPTIONS[k] and OPTIONS[k][2] == ly.is_alias then
- if OPTIONS[k][1] == v then return
- else k = OPTIONS[k][1]
- end
- end
- -- boolean
- if v == 'false' then v = false end
- -- negation (for example, noindent is the negation of indent)
- if ly.is_neg(k) then
- if v ~= nil and v ~= 'default' then
- k = k:gsub('^no(.*)', '%1')
- v = not v
- else return
- end
- end
- return k, v
-end
-
-
local function range_parse(range, nsystems)
local num = tonumber(range)
if num then return {num} end
-- if nsystems is set, we have insert=systems
if nsystems ~= 0 and range:sub(-1) == '-' then range = range..nsystems end
- if not range:match('^%d+%s*-%s*%d*$') then
+ if not (range == '' or range:match('^%d+%s*-%s*%d*$')) then
warn([[
Invalid value '%s' for item
in list of page ranges. Possible entries:
@@ -252,16 +180,6 @@ This item will be skipped!
end
-local function readlinematching(s, f)
- if f then
- local result = ''
- while result and not result:find(s) do result = f:read() end
- f:close()
- return result
- end
-end
-
-
local function set_lyscore(score)
ly.score = score
ly.score.nsystems = ly.score:count_systems()
@@ -277,23 +195,34 @@ local function set_lyscore(score)
end
-local function splitext(str, ext) return str:match('(.*)%.'..ext..'$') or str end
-
-
---[[ ================ Bounding box calculations =========================== ]]
+--[[ ================ Bounding box calculations =========================== --]]
local bbox = {}
function bbox.get(filename, line_width)
return bbox.read(filename) or bbox.parse(filename, line_width)
end
+function bbox.calc(x_1, x_2, y_1, y_2, line_width)
+ local bb = {
+ ['protrusion'] = -lib.convert_unit(("%fbp"):format(x_1)),
+ ['r_protrusion'] = lib.convert_unit(("%fbp"):format(x_2)) - line_width,
+ ['width'] = lib.convert_unit(("%fbp"):format(x_2))
+ }
+ --FIX #192: height is only calculated if really needed, to prevent errors with huge scores.
+ function bb.__index(_, k)
+ if k == 'height' then return lib.convert_unit(("%fbp"):format(y_2)) - lib.convert_unit(("%fbp"):format(y_1)) end
+ end
+ setmetatable(bb, bb)
+ return bb
+end
+
function bbox.parse(filename, line_width)
-- get BoundingBox from EPS file
- local bbline = readlinematching('^%%%%BoundingBox', io.open(filename..'.eps', 'r'))
+ local bbline = lib.readlinematching('^%%%%BoundingBox', io.open(filename..'.eps', 'r'))
if not bbline then return end
local x_1, y_1, x_2, y_2 = bbline:match('(%--%d+)%s(%--%d+)%s(%--%d+)%s(%--%d+)')
-- try to get HiResBoundingBox from PDF (if 'gs' works)
- bbline = readlinematching(
+ bbline = lib.readlinematching(
'^%%%%HiResBoundingBox',
io.popen('gs -sDEVICE=bbox -q -dBATCH -dNOPAUSE '..filename..'.pdf 2>&1', 'r')
)
@@ -308,20 +237,24 @@ function bbox.parse(filename, line_width)
x_1, y_1, x_2, y_2 = pbb() + x_1, pbb() + y_1, pbb() + x_1, pbb() + y_1
else warn([[gs couldn't be launched; there could be rounding errors.]])
end
- local bb = {
- ['protrusion'] = -convert_unit(("%fbp"):format(x_1)),
- ['r_protrusion'] = convert_unit(("%fbp"):format(x_2)) - line_width,
- ['width'] = convert_unit(("%fbp"):format(x_2)),
- ['height'] = convert_unit(("%fbp"):format(y_2)) - convert_unit(("%fbp"):format(y_1))
- }
- obj.serialize(bb, filename..'.bbox')
- return bb
+ local f = io.open(filename .. '.bbox', 'w')
+ f:write(
+ string.format("return %s, %s, %s, %s, %s", x_1, y_1, x_2, y_2, line_width)
+ )
+ f:close()
+ return bbox.calc(x_1, x_2, y_1, y_2, line_width)
end
-function bbox.read(f) return obj.deserialize(f..'.bbox') end
+function bbox.read(f)
+ f = f .. '.bbox'
+ if lfs.isfile(f) then
+ local x_1, y_1, x_2, y_2, line_width = dofile(f)
+ return bbox.calc(x_1, x_2, y_1, y_2, line_width)
+ end
+end
---[[ =============== Functions that output LaTeX code ===================== ]]
+--[[ =============== Functions that output LaTeX code ===================== --]]
function latex.filename(printfilename, insert, input_file)
if printfilename and input_file then
@@ -347,8 +280,8 @@ end
function latex.includeinline(pdfname, height, valign, hpadding, voffset)
local v_base
if valign == 'bottom' then v_base = 0
- elseif valign == 'top' then v_base = convert_unit('1em') - height
- else v_base = (convert_unit('1em') - height) / 2
+ elseif valign == 'top' then v_base = lib.convert_unit('1em') - height
+ else v_base = (lib.convert_unit('1em') - height) / 2
end
tex.sprint(
string.format(
@@ -407,8 +340,8 @@ function latex.verbatim(verbatim, ly_code, intertext, version)
'.*%%%s*begin verbatim', ''):gsub(
'%%%s*end verbatim.*', '')
--[[ We unfortunately need an external file,
- as verbatim environments are quite special. ]]
- local fname = ly.get_option('tmpdir')..'/verb.tex'
+ as verbatim environments are quite special. --]]
+ local fname = ly_opts.tmpdir..'/verb.tex'
local f = io.open(fname, 'w')
f:write(
ly.verbenv[1]..'\n'..
@@ -422,36 +355,12 @@ function latex.verbatim(verbatim, ly_code, intertext, version)
end
---[[ =============== Functions that operate on objects ==================== ]]
-
-function obj.deserialize(f) if lfs.isfile(f) then return dofile(f) end end
-
-function obj.serialize(o, f)
- f = io.open(f, 'w')
- if not f then return end
- f:write('local o = '..obj.str(o)..'\nreturn o')
- f:close()
-end
-
-function obj.str(o)
- if type(o) == "number" then return o
- elseif type(o) == "string" then return string.format("%q", o)
- elseif type(o) == "table" then
- local r = '{\n'
- for k, v in pairs(o) do r = r.." ["..obj.str(k).."] = "..obj.str(v)..",\n" end
- return r.."}\n"
- else error("cannot convert " .. type(o).." to string")
- end
-end
-
-
---[[ =============================== Classes =============================== ]]
+--[[ =============================== Classes =============================== --]]
-- Score class
function Score:new(ly_code, options, input_file)
local o = options or {}
setmetatable(o, self)
- self.__index = self
o.output_names = {}
o.input_file = input_file
o.ly_code = ly_code
@@ -475,6 +384,10 @@ end
function Score:calc_properties()
self:calc_staff_properties()
+ -- add includes to lilypond code
+ self.ly_code = includes_parse(self.include_before_body)
+ .. self.ly_code
+ .. includes_parse(self.include_after_body)
-- fragment and relative
if self.relative and not self.fragment then
-- local option takes precedence over global option
@@ -506,7 +419,7 @@ function Score:calc_properties()
end
-- dimensions that can be given by LaTeX
for _, dimension in pairs(DIM_OPTIONS) do
- self[dimension] = convert_unit(self[dimension])
+ self[dimension] = lib.convert_unit(self[dimension])
end
self['max-left-protrusion'] = self['max-left-protrusion'] or self['max-protrusion']
self['max-right-protrusion'] = self['max-right-protrusion'] or self['max-protrusion']
@@ -533,21 +446,30 @@ end
function Score:calc_range()
local nsystems = self:count_systems(true)
- if self['print-only'] == '' then
- if nsystems == 0 then self['print-only'] = '1-'
- else self['print-only'] = '1-'..nsystems
+ local printonly, donotprint = self['print-only'], self['do-not-print']
+ if printonly == '' then printonly = '1-' end
+ local result = tonumber(printonly) and {tonumber(printonly)} or {}
+ if not result[1] then
+ for _, r in pairs(printonly:explode(',')) do
+ local range = range_parse(r:gsub('^%s', ''):gsub('%s$', ''), nsystems)
+ if range then
+ for _, v in pairs(range) do table.insert(result, v) end
+ end
end
end
- local result = {}
- if tonumber(self['print-only']) then result = {self['print-only']}
- else
- for _, r in pairs(self['print-only']:explode(',')) do
+ local rm_result = tonumber(donotprint) and {tonumber(donotprint)} or {}
+ if not rm_result[1] then
+ for _, r in pairs(donotprint:explode(',')) do
local range = range_parse(r:gsub('^%s', ''):gsub('%s$', ''), nsystems)
if range then
- for _, v in pairs(range) do table.insert(result, v) end
+ for _, v in pairs(range) do table.insert(rm_result, v) end
end
end
end
+ for _, v in pairs(rm_result) do
+ local k = lib.contains(result, v)
+ if k then table.remove(result, k) end
+ end
return result
end
@@ -651,9 +573,9 @@ function Score:check_indent(lp)
if lp.shorten > 0 then
if not self.indent or self.indent == 0 then
self.indent = lp.overflow_left
- lp.shorten = max(lp.shorten - lp.overflow_left, 0)
+ lp.shorten = lib.max(lp.shorten - lp.overflow_left, 0)
else
- self.indent = max(self.indent - lp.overflow_left, 0)
+ self.indent = lib.max(self.indent - lp.overflow_left, 0)
end
lp.changed_indent = true
end
@@ -700,26 +622,7 @@ function Score:check_indent(lp)
end
function Score:check_properties()
- local unexpected = false
- for k, _ in orderedpairs(OPTIONS) do
- if self[k] == 'default' then
- self[k] = OPTIONS[k][1] or nil
- unexpected = not self[k]
- end
- if not contains(OPTIONS[k], self[k]) and OPTIONS[k][2] then
- if type(OPTIONS[k][2]) == 'function' then OPTIONS[k][2](k, self[k])
- else unexpected = true
- end
- end
- if unexpected then
- err([[
-Unexpected value "%s" for option %s:
-authorized values are "%s"
-]],
- self[k], k, table.concat(OPTIONS[k], ', ')
- )
- end
- end
+ ly_opts:validate_options(self)
for _, k in pairs(TEXINFO_OPTIONS) do
if self[k] then info([[Option %s is specific to Texinfo: ignoring it.]], k) end
end
@@ -750,20 +653,20 @@ function Score:check_protrusion(bbox_func)
-- line_props lp
local lp = {}
-- Determine offset due to left protrusion
- lp.overflow_left = max(bb.protrusion - math.floor(self['max-left-protrusion']), 0)
+ lp.overflow_left = lib.max(bb.protrusion - math.floor(self['max-left-protrusion']), 0)
self.protrusion_left = lp.overflow_left - bb.protrusion
-- Determine further line properties
- lp.stave_extent = lp.overflow_left + min(self['line-width'], bb.width)
+ lp.stave_extent = lp.overflow_left + lib.min(self['line-width'], bb.width)
lp.available = self.original_lw + self['max-right-protrusion']
lp.total_extent = lp.stave_extent + bb.r_protrusion
-- Check if stafflines protrude into the right margin after offsetting
-- Note: we can't *reliably* determine this with ragged one-system scores,
-- possibly resulting in unnecessarily short lines when right protrusion is
-- present
- lp.stave_overflow_right = max(lp.stave_extent - self.original_lw, 0)
+ lp.stave_overflow_right = lib.max(lp.stave_extent - self.original_lw, 0)
-- Check if image as a whole protrudes over max-right-protrusion
- lp.overflow_right = max(lp.total_extent - lp.available, 0)
- lp.shorten = max(lp.stave_overflow_right, lp.overflow_right)
+ lp.overflow_right = lib.max(lp.total_extent - lp.available, 0)
+ lp.shorten = lib.max(lp.stave_overflow_right, lp.overflow_right)
lp.changed_indent = false
self:check_indent(lp, bb)
if lp.shorten > 0 or lp.changed_indent then
@@ -789,6 +692,7 @@ end
function Score:content()
local n = ''
+ local ly_code = self.ly_code
if self.relative then
self.fragment = 'true' -- in case it would serve later
if self.relative < 0 then
@@ -796,9 +700,9 @@ function Score:content()
elseif self.relative > 0 then
for _ = 1, self.relative do n = n.."'" end
end
- return string.format([[\relative c%s {%s}]], n, self.ly_code)
- elseif self.fragment then return [[{]]..self.ly_code..[[}]]
- else return self.ly_code
+ return string.format([[\relative c%s {%s}]], n, ly_code)
+ elseif self.fragment then return [[{]]..ly_code..[[}]]
+ else return ly_code
end
end
@@ -832,9 +736,13 @@ function Score:flatten_content(ly_code)
including referenced files (if they can be opened.
Other files (from LilyPond's include path) are considered
irrelevant for the purpose of a hashsum.) --]]
+
+ -- Replace percent signs with another character that doesn't
+ -- meddle with Lua's gsub escape character.
+ ly_code = ly_code:gsub('%%', '#')
local f
- local includepaths = self.includepaths
- if self.input_file then includepaths = self.includepaths..','..dirname(self.input_file) end
+ local includepaths = self.includepaths..','..self.tmpdir
+ if self.input_file then includepaths = self.includepaths..','..lib.dirname(self.input_file) end
for iline in ly_code:gmatch('\\include%s*"[^"]*"') do
f = io.open(locate(iline:match('\\include%s*"([^"]*)"'), includepaths, '.ly') or '')
if f then
@@ -845,46 +753,75 @@ function Score:flatten_content(ly_code)
return ly_code
end
+function Score:footer()
+ return includes_parse(self.include_footer)
+end
+
function Score:header()
local header = LY_HEAD
for element in LY_HEAD:gmatch('<<<(%w+)>>>') do
header = header:gsub('<<<'..element..'>>>', self['ly_'..element](self) or '')
end
+ local wh_dest = self['write-headers']
+ if wh_dest then
+ if self.input_file then
+ local _, ext = lib.splitext(wh_dest)
+ local header_file = ext and wh_dest
+ or wh_dest..'/'..lib.splitext(lib.basename(self.input_file), 'ly').."-lyluatex-headers.ily"
+ lib.mkdirs(lib.dirname(header_file))
+ local f = io.open(header_file, 'w')
+ f:write(header
+ :gsub([[%\include "lilypond%-book%-preamble.ly"]], '')
+ :gsub([[%#%(define inside%-lyluatex %#t%)]], '')
+ :gsub('\n+', '\n')
+ )
+ f:close()
+ else
+ warn([[Ignoring 'write-headers' for non-file score.]])
+ end
+ end
return header
end
function Score:is_compiled()
+ if self['force-compilation'] then return false end
return lfs.isfile(self.output..'.pdf') or self:count_systems(true) ~= 0
end
function Score:is_odd_page() return tex.count['c@page'] % 2 == 1 end
-function Score:lilypond_cmd(ly_code)
+function Score:lilypond_cmd()
local input, mode = '-s -', 'w'
if self.debug then
local f = io.open(self.output..'.ly', 'w')
- f:write(ly_code)
+ f:write(self.complete_ly_code)
f:close()
input = self.output..".ly 2>&1"
mode = 'r'
end
- local cmd = self.program.." "..
+ local cmd = '"'..self.program..'" '..
"-dno-point-and-click "..
"-djob-count=2 "..
"-dno-delete-intermediate-files "
+ if self['optimize-pdf'] and self:lilypond_has_TeXGS() then cmd = cmd.."-O TeX-GS " end
if self.input_file then
- cmd = cmd..'-I "'..dirname(self.input_file):gsub('%./', lfs.currentdir()..'/')..'" '
+ cmd = cmd..'-I "'..lib.dirname(self.input_file):gsub('^%./', lfs.currentdir()..'/')..'" '
end
for _, dir in ipairs(extract_includepaths(self.includepaths)) do
cmd = cmd..'-I "'..dir:gsub('^%./', lfs.currentdir()..'/')..'" '
end
cmd = cmd..'-o "'..self.output..'" '..input
+ if lib.tex_engine.dist == 'MiKTeX' then cmd = '"'..cmd..'"' end
debug("Command:\n"..cmd)
return cmd, mode
end
+function Score:lilypond_has_TeXGS()
+ return lib.readlinematching('TeX%-GS', io.popen('"'..self.program..'" --help', 'r'))
+end
+
function Score:lilypond_version(number)
- local result = readlinematching('GNU LilyPond', io.popen(self.program..' --version', 'r'))
+ local result = lib.readlinematching('GNU LilyPond', io.popen('"'..self.program..'" --version', 'r'))
if result then
if number then return result:match('%d+%.%d+%.?%d*')
else
@@ -893,18 +830,22 @@ function Score:lilypond_version(number)
self.output, self.program
)
debug(result)
+ return true
end
- else
- err([[
-LilyPond could not be started.
-Please check that LuaLaTeX is started with the
---shell-escape option, and that 'program'
-points to a valid LilyPond executable.
-]]
- )
end
end
+function Score:ly_fixbadlycroppedstaffgroupbrackets()
+ return self.fix_badly_cropped_staffgroup_brackets and [[\context {
+ \Score
+ \override SystemStartBracket.after-line-breaking =
+ #(lambda (grob)
+ (let ((Y-off (ly:grob-property grob 'Y-extent)))
+ (ly:grob-set-property! grob 'Y-extent
+ (cons (- (car Y-off) 1.7) (+ (cdr Y-off) 1.7)))))
+ }]]
+end
+
function Score:ly_fonts()
if self['pass-fonts'] then
return string.format([[
@@ -921,6 +862,10 @@ function Score:ly_fonts()
end
end
+function Score:ly_header()
+ return includes_parse(self.include_header)
+end
+
function Score:ly_indent()
if not (self.indent == false and self.insert == 'fullpage') then
return [[indent = ]]..(self.indent or 0)..[[\pt]]
@@ -936,43 +881,45 @@ function Score:ly_linewidth() return self['line-width'] end
function Score:ly_staffsize() return self.staffsize end
function Score:ly_margins()
+ local horizontal_margins =
+ self.twoside and string.format([[
+ inner-margin = %s\pt]], self:tex_margin_inner())
+ or string.format([[
+ left-margin = %s\pt]], self:tex_margin_left())
+
local tex_top = self['extra-top-margin'] + self:tex_margin_top()
local tex_bottom = self['extra-bottom-margin'] + self:tex_margin_bottom()
if self.fullpagealign == 'crop' then
return string.format([[
-top-margin = %s\pt
-bottom-margin = %s\pt
-inner-margin = %s\pt
-left-margin = %s\pt
-]],
- tex_top, tex_bottom, self:tex_margin_inner(), self:tex_margin_left()
+ top-margin = %s\pt
+ bottom-margin = %s\pt
+ %s]],
+ tex_top, tex_bottom, horizontal_margins
)
elseif self.fullpagealign == 'staffline' then
local top_distance = 4 * tex_top / self.staffsize + 2
local bottom_distance = 4 * tex_bottom / self.staffsize + 2
return string.format([[
-top-margin = 0\pt
-bottom-margin = 0\pt
-inner-margin = %s\pt
-left-margin = %s\pt
-top-system-spacing =
+ top-margin = 0\pt
+ bottom-margin = 0\pt
+ %s
+ top-system-spacing =
#'((basic-distance . %s)
(minimum-distance . %s)
(padding . 0)
(stretchability . 0))
-top-markup-spacing =
+ top-markup-spacing =
#'((basic-distance . %s)
(minimum-distance . %s)
(padding . 0)
(stretchability . 0))
-last-bottom-spacing =
+ last-bottom-spacing =
#'((basic-distance . %s)
(minimum-distance . %s)
(padding . 0)
(stretchability . 0))
]],
- self:tex_margin_inner(),
- self:tex_margin_left(),
+ horizontal_margins,
top_distance,
top_distance,
top_distance,
@@ -992,19 +939,33 @@ Given: %s
end
function Score:ly_paper()
+ local system_count =
+ self['system-count'] == 0 and ''
+ or 'system-count = '..self['system-count']..'\n '
+
local papersize = '#(set-paper-size "'..(self.papersize or 'lyluatexfmt')..'")'
if self.insert == 'fullpage' then
- local ppn = 'f'
- if self['print-page-number'] then ppn = 't' end
+ local first_page_number = self['first-page-number'] or tex.count['c@page']
+ local pfpn = self['print-first-page-number'] and 't' or 'f'
+ local ppn = self['print-page-number'] and 't' or 'f'
return string.format([[
-%s
-print-page-number = ##%s
-print-first-page-number = ##t
-first-page-number = %s
+%s%s
+ print-page-number = ##%s
+ print-first-page-number = ##%s
+ first-page-number = %s
%s]],
- papersize, ppn, tex.count['c@page'], self:ly_margins()
+ system_count, papersize, ppn, pfpn,
+ first_page_number, self:ly_margins()
)
- elseif self.papersize then return papersize
+ else
+ if self.papersize then
+ papersize = papersize..[[
+]]
+ else
+ papersize = ''
+ end
+
+ return string.format([[%s%s]], papersize, system_count)
end
end
@@ -1019,7 +980,7 @@ function Score:ly_preamble()
end
function Score:ly_raggedright()
- if not self['ragged-right'] == 'default' then
+ if self['ragged-right'] ~= 'default' then
if self['ragged-right'] then return 'ragged-right = ##t'
else return 'ragged-right = ##f'
end
@@ -1041,6 +1002,20 @@ function Score:ly_version() return self['ly-version'] end
function Score:optimize_pdf()
if not self['optimize-pdf'] then return end
+ if self:lilypond_has_TeXGS() and not ly.final_optimization_message then
+ ly.final_optimization_message = true
+ luatexbase.add_to_callback(
+ 'stop_run',
+ function()
+ info(
+ [[Optimization enabled: remember to run
+ 'gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=%s %s'.]],
+ tex.jobname..'-final.pdf', tex.jobname..'.pdf'
+ )
+ end,
+ 'lyluatex optimize-pdf'
+ )
+ end
local pdf2ps, ps2pdf, path
for file in lfs.dir(self.tmpdir) do
path = self.tmpdir..'/'..file
@@ -1069,8 +1044,8 @@ end
function Score:output_filename()
local properties = ''
- for k, _ in orderedpairs(OPTIONS) do
- if (not contains(HASHIGNORE, k)) and self[k] and type(self[k]) ~= 'function' then
+ for k, _ in lib.orderedpairs(ly_opts.declarations) do
+ if (not lib.contains(HASHIGNORE, k)) and self[k] and type(self[k]) ~= 'function' then
properties = properties..'\n'..k..'\t'..self[k]
end
end
@@ -1086,12 +1061,34 @@ end
function Score:process()
self:check_properties()
self:calc_properties()
- -- with bbox.read check_ptrotrusion will only execute with
+ if not self:lilypond_version() then
+ local warning = [[
+LilyPond could not be started.
+Please check that LuaLaTeX is started with the
+--shell-escape option, and that 'program'
+points to a valid LilyPond executable.
+]]
+ if self.showfailed then
+ warn(warning)
+ tex.sprint(string.format([[
+\begin{quote}
+\minibox[frame]{LilyPond could not be started.}
+\end{quote}
+
+]]))
+ return
+ else
+ err(warning)
+ end
+ end
+ -- with bbox.read check_protrusion will only execute with
-- a prior compilation, otherwise it will be ignored
local do_compile = not self:check_protrusion(bbox.read)
- if do_compile then
+ if self['force-compilation'] or do_compile then
repeat
- self:run_lilypond(self:header()..self:content())
+ self.complete_ly_code = self:header()..self:content()..self:footer()
+ self:run_lilypond()
+ self['force-compilation'] = false
if self:is_compiled() then table.insert(self.output_names, self.output)
else
self:clean_failed_compilation()
@@ -1102,36 +1099,47 @@ function Score:process()
else table.insert(self.output_names, self.output)
end
set_lyscore(self)
+ if self:count_systems() == 0 then
+ warn([[
+The score doesn't contain any music:
+this will probably cause bad output.]]
+ )
+ end
if not self['raw-pdf'] then self:write_latex(do_compile) end
self:write_to_filelist()
if not self.debug then self:delete_intermediate_files() end
end
-function Score:run_lilypond(ly_code)
+function Score:run_lily_proc(p)
+ if self.debug then
+ local f = io.open(self.output..".log", 'w')
+ f:write(p:read('*a'))
+ f:close()
+ else p:write(self.complete_ly_code)
+ end
+ return p:close()
+ end
+
+function Score:run_lilypond()
if self:is_compiled() then return end
- mkdirs(dirname(self.output))
- self:lilypond_version()
- local p = io.popen(self:lilypond_cmd(ly_code))
- if self.debug then
- local f = io.open(self.output..".log", 'w')
- f:write(p:read('*a'))
- f:close()
- else p:write(ly_code)
+ lib.mkdirs(lib.dirname(self.output))
+ if not self:run_lily_proc(io.popen(self:lilypond_cmd(self.complete_ly_code))) and not self.debug then
+ self.debug = true
+ self.lilypond_error = not self:run_lily_proc(io.popen(self:lilypond_cmd(self.complete_ly_code)))
end
- self.lilypond_error = not p:close()
end
function Score:tex_margin_bottom()
self._tex_margin_bottom = self._tex_margin_bottom or
- convert_unit(tex.dimen.paperheight..'sp')
+ lib.convert_unit(tex.dimen.paperheight..'sp')
- self:tex_margin_top()
- - convert_unit(tex.dimen.textheight..'sp')
+ - lib.convert_unit(tex.dimen.textheight..'sp')
return self._tex_margin_bottom
end
function Score:tex_margin_inner()
self._tex_margin_inner = self._tex_margin_inner or
- convert_unit((
+ lib.convert_unit((
tex.sp('1in') + tex.dimen.oddsidemargin + tex.dimen.hoffset
)..'sp')
return self._tex_margin_inner
@@ -1139,26 +1147,26 @@ end
function Score:tex_margin_outer()
self._tex_margin_outer = self._tex_margin_outer or
- convert_unit((tex.dimen.paperwidth - tex.dimen.textwidth)..'sp')
+ lib.convert_unit((tex.dimen.paperwidth - tex.dimen.textwidth)..'sp')
- self:tex_margin_inner()
return self._tex_margin_outer
end
function Score:tex_margin_left()
- if self:is_odd_page() then return self:tex_margin_inner()
+ if self:is_odd_page() or not self.twopage then return self:tex_margin_inner()
else return self:tex_margin_outer()
end
end
function Score:tex_margin_right()
- if self:is_odd_page() then return self:tex_margin_outer()
+ if self:is_odd_page() or not self.twopage then return self:tex_margin_outer()
else return self:tex_margin_inner()
end
end
function Score:tex_margin_top()
self._tex_margin_top = self._tex_margin_top or
- convert_unit((
+ lib.convert_unit((
tex.sp('1in') + tex.dimen.voffset + tex.dimen.topmargin
+ tex.dimen.headheight + tex.dimen.headsep
)..'sp')
@@ -1186,9 +1194,12 @@ Score with more than one system included inline.
This will probably cause bad output.]]
)
end
- latex.includeinline(
- self.output, self:bbox(1).height, self.valign, self.hpadding, self.voffset
- )
+ local bb = self:bbox(1)
+ if bb then
+ latex.includeinline(
+ self.output, bb.height, self.valign, self.hpadding, self.voffset
+ )
+ end
end
end
@@ -1202,9 +1213,9 @@ function Score:write_to_filelist()
end
---[[ ========================== Public functions ========================== ]]
+--[[ ========================== Public functions ========================== --]]
+
-ly.score_content = {}
function ly.buffenv_begin()
function ly.buffenv(line)
@@ -1259,47 +1270,18 @@ Transcript written on %s.log.
end
-function ly.declare_package_options(options)
- OPTIONS = options
- local exopt = ''
- for k, v in pairs(options) do
- tex.sprint(string.format([[
-\DeclareOptionX{%s}{\directlua{
- ly.set_property('%s', '\luatexluaescapestring{#1}')
-}}%%
-]],
- k, k
- ))
- exopt = exopt..k..'='..(v[1] or '')..','
- end
- tex.sprint([[\ExecuteOptionsX{]]..exopt..[[}%%]], [[\ProcessOptionsX]])
- mkdirs(options.tmpdir[1])
- FILELIST = options.tmpdir[1]..'/'..splitext(status.log_name, 'log')..'.list'
+function ly.make_list_file()
+ local tmpdir = ly_opts.tmpdir
+ lib.mkdirs(tmpdir)
+ FILELIST = tmpdir..'/'..lib.splitext(status.log_name, 'log')..'.list'
os.remove(FILELIST)
end
-
-function ly.env_begin(opts)
- ly.state = 'env'
- ly.env_no_args = opts == 'noarg'
- if ly.env_no_args then tex.sprint(40, [[\ly@compilely]])
- else tex.sprint(40, [[\ly@bufferenv]])
- end
-end
-
-
-function ly.env_end()
- if ly.env_no_args then tex.sprint(40, [[\endly@compilely]])
- else tex.sprint(40, [[\endly@bufferenv]])
- end
-end
-
-
function ly.file(input_file, options)
--[[ Here, we only take in account global option includepaths,
- as it really doesn't mean anything as a local option. ]]
+ as it really doesn't mean anything as a local option. --]]
local file = locate(input_file, Score.includepaths, '.ly')
- options = ly.set_local_options(options)
+ options = ly_opts:check_local_options(options)
if not file then err("File %s doesn't exist.", input_file) end
local i = io.open(file, 'r')
ly.score = Score:new(i:read('*a'), options, file)
@@ -1309,9 +1291,9 @@ end
function ly.file_musicxml(input_file, options)
--[[ Here, we only take in account global option includepaths,
- as it really doesn't mean anything as a local option. ]]
+ as it really doesn't mean anything as a local option. --]]
local file = locate(input_file, Score.includepaths, '.xml')
- options = ly.set_local_options(options)
+ options = ly_opts:check_local_options(options)
if not file then err("File %s doesn't exist.", input_file) end
local xmlopts = ''
for _, opt in pairs(MXML_OPTIONS) do
@@ -1321,16 +1303,17 @@ function ly.file_musicxml(input_file, options)
xmlopts = xmlopts..' '..options[opt]
end
end
- elseif ly.get_option(opt) then xmlopts = xmlopts..' --'..opt
+ elseif ly_opts[opt] then xmlopts = xmlopts..' --'..opt
end
end
- local i = io.popen(ly.get_option('xml2ly')..' --out=-'..xmlopts..' "'..file..'"', 'r')
+ local i = io.popen(ly_opts.xml2ly..' --out=-'..xmlopts..' "'..file..'"', 'r')
if not i then
err([[
-LilyPond could not be started.
+%s could not be started.
Please check that LuaLaTeX is started with the
--shell-escape option.
-]]
+]],
+ ly_opts.xml2ly
)
end
ly.score = Score:new(i:read('*a'), options, file)
@@ -1339,7 +1322,7 @@ end
function ly.fragment(ly_code, options)
- options = ly.set_local_options(options)
+ options = ly_opts:check_local_options(options)
if type(ly_code) == 'string' then
ly_code = ly_code:gsub('\\par ', '\n'):gsub('\\([^%s]*) %-([^%s])', '\\%1-%2')
else ly_code = table.concat(ly_code, '\n')
@@ -1349,43 +1332,10 @@ end
function ly.get_font_family(font_id)
- return fontinfo(font_id).shared.rawdata.metadata['familyname']
+ return lib.fontinfo(font_id).shared.rawdata.metadata['familyname']
end
-function ly.get_option(opt) return Score[opt] end
-
-
-function ly.is_alias() end
-
-
-function ly.is_dim(k, v)
- if v == '' or v == false or tonumber(v) then return true end
- local n, sl, u = v:match('^%d*%.?%d*'), v:match('\\'), v:match('%a+')
- -- a value of number - backslash - length is a dimension
- -- invalid input will be prevented in by the LaTeX parser already
- if n and sl and u then return true end
- if n and contains(TEX_UNITS, u) then return true end
- err([[
-Unexpected value "%s" for dimension %s:
-should be either a number (for example "12"),
-a number with unit, without space ("12pt"),
-or a (multiplied) TeX length (".8\linewidth")
-]],
- v, k
- )
-end
-
-
-function ly.is_neg(k, _)
- local _, i = k:find('^no')
- return i and contains_key(OPTIONS, k:sub(i + 1))
-end
-
-
-function ly.is_num(_, v) return v == '' or tonumber(v) end
-
-
function ly.newpage_if_fullpage()
if ly.score.insert == 'fullpage' then tex.sprint([[\newpage]]) end
end
@@ -1408,32 +1358,12 @@ end
if ly.score.ttfamily == '' then ly.score.ttfamily = ly.get_font_family(tt) end
end
-function ly.set_local_options(opts)
- local options = {}
- local next_opt = opts:gmatch('([^,]+)') -- iterator over options
- for opt in next_opt do
- local k, v = opt:match('([^=]+)=?(.*)')
- if k then
- if v and v:sub(1, 1) == '{' then -- handle keys with {multiple, values}
- while v:sub(-1) ~= '}' do v = v..','..next_opt() end
- v = v:sub(2, -2) -- remove { }
- end
- k, v = process_options(k:gsub('^%s', ''), v:gsub('^%s', ''))
- if k then
- if options[k] then err('Option %s is set two times for the same score.', k)
- else options[k] = v
- end
- end
- end
- end
- return options
-end
-
-function ly.set_property(k, v)
- k, v = process_options(k, v)
- if k then Score[k] = v end
+function ly.write_to_file(file, content)
+ local f = io.open(Score.tmpdir..'/'..file, 'w')
+ if not f then err('Unable to write to file %s', file) end
+ f:write(content)
+ f:close()
end
-
return ly