diff --git a/.gitignore b/.gitignore index 0056030..88822fb 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,5 @@ sympy-plots-for-*.tex/ # lyluatex temp directories *tmp-ly/ /lyluatex.tex +lyluatex.code-workspace +lyluatex.lua.old diff --git a/Makefile b/Makefile index 63befdd..8934952 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ test: TEXINPUTS=luaoptions: lualatex -interaction=nonstopmode -shell-escape test.tex + TEXINPUTS=luaoptions: lualatex -interaction=nonstopmode -shell-escape test-clipregions.tex manual: @lua -e "if tonumber(io.popen('pandoc -v'):read():gsub('pandoc (.*)', '%1'):sub(1,1)) < 2 then print('Pandoc >= 2 required') ; os.exit(1) ; end" diff --git a/lyluatex.lua b/lyluatex.lua index 1351a7c..e8093c9 100644 --- a/lyluatex.lua +++ b/lyluatex.lua @@ -90,6 +90,30 @@ local LY_HEAD = [[ ]] +--- Clip-regions engraver to force a snippet to start on a new system. It can manage multiple fragments, but in lyluatex the feature is limited to a single fragment in the form: "clip-regions=aa-bb", where aa and bb are measure numbers. +local CLIP_BREAK_ENGRAVER = [[ +#(define (Clip_break_engraver context) + (let ((break-measures '())) + (make-engraver + (acknowledgers + ((non-musical-paper-column-interface engraver grob source-engraver) + (if (memv (ly:context-property context 'currentBarNumber) break-measures) + (ly:grob-set-property! grob 'line-break-permission 'force)))) + ((initialize engraver) + (let* ((paper (ly:context-output-def context)) + (regions (ly:output-def-lookup paper 'clip-regions))) + (if (list? regions) + (for-each + (lambda (reg) + ;; reg is (start-loc . end-loc), loc is (measure . moment) + (let ((start (car (car reg))) + (fine (car (cdr reg)))) + (set! break-measures + (cons* start fine break-measures)))) + regions))))))) +]] + + --[[ ========================== Helper functions ========================== --]] -- dirty fix as info doesn't work as expected local oldinfo = info @@ -237,8 +261,50 @@ end local function set_lyscore(score) ly.score = score + + if score['clip-regions'] and score['clip-regions'] ~= '' then + score.protrusion_left = 0 + score.indent_offset = 0 + score.leftgutter = 0 + + local base_name = score.output:match("[^/]*$") + local clips = {} + + pcall(function() + for file in lfs.dir(score.tmpdir) do + if file:find("^" .. base_name) and file:find("%-clip") and file:sub(-4) == ".pdf" then + local clip_name = file:gsub("%.pdf$", "") + table.insert(clips, clip_name) + end + end + end) + + table.sort(clips, function(a, b) + local num_a = a:match("%-clip%-(%d+)$") + local num_b = b:match("%-clip%-(%d+)$") + + local n_a = num_a and tonumber(num_a) + local n_b = num_b and tonumber(num_b) + + if n_a and n_b then return n_a > n_b end + if n_a and not n_b then return true end + if not n_a and n_b then return false end + return a < b + end) + + for _, clip_file in ipairs(clips) do + table.insert(score, score.tmpdir .. '/' .. clip_file) + end + + score.nsystems = #clips + score.range = {} + for i = 1, score.nsystems do table.insert(score.range, i) end + + return + end + ly.score.nsystems = ly.score:count_systems() - if score.insert ~= 'fullpage' then -- systems and inline + if score.insert ~= 'fullpage' then local hoffset = ly.score.protrusion or 0 if hoffset == '' then hoffset = 0 end ly.score.hoffset = hoffset..'pt' @@ -357,13 +423,26 @@ function latex_includesystems(filename, range, protrusion, gutter, staffsize, in local h_offset = protrusion + indent_offset local texoutput = '\\ifx\\preLilyPondExample\\undefined\\else\\preLilyPondExample\\fi\n' texoutput = texoutput..'\\par\n' + + local is_clip = ly.score and ly.score['clip-regions'] and ly.score['clip-regions'] ~= '' + for index, system in pairs(range) do - if not lfs.isfile(filename..'-'..system..'.eps') then break end + local path_graphic + local h_space = h_offset + gutter + + if is_clip then + path_graphic = ly.score[index] + if not path_graphic then break end + else -- Default lyluatex behavior + if not lfs.isfile(filename..'-'..system..'.eps') then break end + path_graphic = filename..'-'..system + end + texoutput = texoutput.. string.format([[ \noindent\hspace*{%fpt}\includegraphics{%s}%% ]], - h_offset + gutter, filename..'-'..system + h_offset + gutter,path_graphic ) if index < #range then texoutput = texoutput.. @@ -763,10 +842,20 @@ function Score:count_systems(force) local count = self.system_count if force or not count then count = 0 - local systems = self.output:match("[^/]*$").."%-%d+%.eps" - for f in lfs.dir(self.tmpdir) do - if f:match(systems) then - count = count + 1 + local base_name = self.output:match("[^/]*$") + + if self['clip-regions'] and self['clip-regions'] ~= '' then + for f in lfs.dir(self.tmpdir) do + if f:find(base_name, 1, true) and f:find("-clip", 1, true) and f:sub(-4) == ".pdf" then + count = count + 1 + end + end + else -- original logic + local systems = base_name.."%-%d+%.eps" + for f in lfs.dir(self.tmpdir) do + if f:match(systems) then + count = count + 1 + end end end self.system_count = count @@ -776,11 +865,23 @@ end function Score:delete_intermediate_files() for _, filename in pairs(self.output_names) do - if self.insert == 'fullpage' then os.remove(filename..'.ps') + if self.insert == 'fullpage' then + os.remove(filename..'.ps') else os.remove(filename..'-systems.tex') os.remove(filename..'-systems.texi') - os.remove(filename..'.eps') + + -- If clip-regions + if self['clip-regions'] and self['clip-regions'] ~= '' then + local base_name = filename:match("[^/]*$") + for file in lfs.dir(self.tmpdir) do + if file:find(base_name, 1, true) and file:sub(-4) == ".eps" then + os.remove(self.tmpdir..'/'..file) + end + end + else + os.remove(filename..'.eps') + end end end end @@ -834,6 +935,16 @@ function Score:header() warn([[Ignoring 'write-headers' for non-file score.]]) end end + + if self['clip-regions'] and self['clip-regions'] ~= '' then + header = CLIP_BREAK_ENGRAVER .. "\n" .. header + header = header:gsub( + [[%\include "lilypond%-book%-preamble.ly"]], + [[\include "lilypond-book-preamble.ly" + #(define default-toplevel-book-handler print-book-with-defaults)]] --This is to allow dclip-systems to work + ) + end + return header end @@ -863,6 +974,12 @@ function Score:lilypond_cmd() local cmd = shell_quote(self.program) .. ' ' .. (self.insert == "fullpage" and "" or "-E ") .. "-dno-point-and-click -djob-count=2 -dno-delete-intermediate-files " + + if self['clip-regions'] and self['clip-regions'] ~= '' then + cmd = cmd .. "-dclip-systems " + cmd = cmd:gsub("%-E%s*", "") --remove -E option if present. + end + if self:lilypond_version() >= ly.v{2, 24} then cmd = cmd.."-dtall-page-formats=pdf " end if self['optimize-pdf'] and self:lilypond_has_TeXGS() then cmd = cmd.."-O TeX-GS -dgs-never-embed-fonts " @@ -1085,7 +1202,23 @@ function Score:ly_staffprops() if self.notiming then timing = [[\context { \Score timing = ##f }]] end if self.notimesig then timesig = [[\context { \Staff \remove "Time_signature_engraver" }]] end if self.nostaffsymbol then staff = [[\context { \Staff \remove "Staff_symbol_engraver" }]] end - return string.format('%s\n%s\n%s\n%s', clef, timing, timesig, staff) + + local clip_code = '%% no clip-regions set' + if self['clip-regions'] and self['clip-regions'] ~= '' then + local clean = self['clip-regions']:gsub('%s+', '') + local start, fine = clean:match("(%d+)%-(%d+)") + if start and fine then + clip_code = string.format([[ + clip-regions = #(list (cons (make-rhythmic-location %s 0 1) (make-rhythmic-location %s 0 1))) + \context { + \Score + \consists \Clip_break_engraver + }]], start, tonumber(fine) + 1) + else + clip_code = '%% Wrong format for clip-regions. Should be start-end, e.g. 52-74 for measures 52 to 74. Ignoring.' + end + end + return string.format('%s\n%s\n%s\n%s\n%s', clef, timing, timesig, staff, clip_code) end function Score:ly_twoside() if self.twoside then return 't' else return 'f' end end diff --git a/lyluatex.sty b/lyluatex.sty index 7c2de17..0436c35 100644 --- a/lyluatex.sty +++ b/lyluatex.sty @@ -46,6 +46,7 @@ ['addversion'] = {'false', 'true', ''}, ['autoindent'] = {'true', 'false', ''}, ['cleantmp'] = {'false', 'true', ''}, + ['clip-regions'] = {''}, ['currfiledir'] = {}, ['debug'] = {'false', 'true', ''}, ['extra-bottom-margin'] = {'0', _opt.is_dim}, diff --git a/test-clipregions.tex b/test-clipregions.tex new file mode 100644 index 0000000..7fa1c1e --- /dev/null +++ b/test-clipregions.tex @@ -0,0 +1,23 @@ +\documentclass[a4paper,fontsize=12pt]{scrbook} +\usepackage[program=lilypond]{lyluatex} + +\title{} +\author{} + +\begin{document} + + Clip-regions example + + \begin{enumerate} + \item Single system, no ragged--right + \lilypondfile[staffsize=13, clip-regions=5-9 ]{ly/Beethoven/opus-18-1.ly} + + \item Single system, ragged--right + \lilypondfile[staffsize=14, clip-regions=5-9, ragged-right=true]{ly/Beethoven/opus-18-1.ly} + + \item Multiple system, ragged--right + \lilypondfile[clip-regions=2-14,ragged-right=true]{ly/Ave Maria De Lourdes/Ave Maria.ly} + + \end{enumerate} + +\end{document}