Dear all,

I made a extension for including Lilypond music notes. I made the extension
by modifying mathbase.py and pngmath.py. Most codes are copied and pasted
with some tiny changes. I don't know whether it is ok and I also don't know
what to put in the :copyright: area, so I just put my name there. If you
know what to put, please tell me.

The functionalities of the extension are:

- A new role 'lily'. the 'lily' role can input a standalone music markup.
   For example, a G clef can be inserted by

   :lily:`clefs.G`

   The purpose of the 'lily' role is writing music comments or learning notes.
   So only one markup is allowed.

- A new directive 'lily'. The 'lily' directive can input a piece of music
   script, for example,

   .. lily::

      \relative c'' {
        c4 a d c
      }

- A new config 'pnglily_fontsize', which can be used to set fontsize of
   'lily' role and 'lily' directive.

   pnglily_fontsize = ['6', '-3']

   The first value is for 'lily' role setting in absolute fontsize. The
   second value is for 'lily' directive setting in relative fontsize.


Hope you enjoy with it!


Best wishes,
Wei-Wei




--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"sphinx-dev" group.
To post to this group, send email to sphinx-dev@googlegroups.com
To unsubscribe from this group, send email to 
sphinx-dev+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/sphinx-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

# -*- coding: utf-8 -*-
"""
    Sphinx Extension lilypond
    ~~~~~~~~~~~~~~~~~~~~~~~~~

    Allow Lilypond music notes to be included in Sphinx-generated documents
    inline and outline. 

    :copyright: Copyright 2009 by Wei-Wei Guo.
    :license: BSD, see LICENSE for details.
"""

import re
import shutil
import tempfile
import posixpath
from os import path
from subprocess import Popen, PIPE
try:
    from hashlib import sha1 as sha
except ImportError:
    from sha import sha

from docutils import nodes, utils
from docutils.parsers.rst import directives

from sphinx.util.compat import Directive

from sphinx.errors import SphinxError
from sphinx.util import ensuredir

class LilyExtError(SphinxError):
    category = 'Lilypond extension error'

DOC_HEAD = r'''
\version "2.12.1"

\paper{
  indent=0\mm
  line-width=120\mm
  oddFooterMarkup=##f
  oddHeaderMarkup=##f
  bookTitleMarkup = ##f
  scoreTitleMarkup = ##f
}
'''

Inline_HEAD = r'''
\markup \abs-fontsize #%s { \musicglyph 
'''

Inline_BACK = r'''
}
'''

Directive_HEAD = r"""
\new Score \with {
  fontSize = #%s
  \override StaffSymbol #'staff-space = #(magstep %s)
}{ <<
"""

Directive_BACK = r"""
>> }
"""

class lily(nodes.Inline, nodes.TextElement):
    pass

class displaylily(nodes.Part, nodes.Element):
    pass

def lily_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
    music = utils.unescape(text, restore_backslashes=True)
    return [lily(music=music)], []

class LilyDirective(Directive):

    has_content = True
    required_arguments = 0
    optional_arguments = 1
    final_argument_whitespace = True

    def run(self):
        music = '\n'.join(self.content)
        node = displaylily()
        node['music'] = music
        node['docname'] = self.state.document.settings.env.docname
        return [node]

def render_lily(self, lily):
    """
    Render the Lilypond music expression *lily* using lilypond.
    """
    shasum = "%s.png" % sha(lily.encode('utf-8')).hexdigest()
    relfn = posixpath.join(self.builder.imgpath, 'lily', shasum)
    outfn = path.join(self.builder.outdir, '_images', 'lily', shasum)
    if path.isfile(outfn):
        return relfn

    if hasattr(self.builder, '_lilypng_warned'):
        return None, None

    music = DOC_HEAD + self.builder.config.pnglily_preamble + lily
    if isinstance(music, unicode):
        music = music.encode('utf-8')

    # use only one tempdir per build -- the use of a directory is cleaner
    # than using temporary files, since we can clean up everything at once
    # just removing the whole directory (see cleanup_tempdir_lily)
    if not hasattr(self.builder, '_lilypng_tempdir'):
        tempdir = self.builder._lilypng_tempdir = tempfile.mkdtemp()
    else:
        tempdir = self.builder._lilypng_tempdir

    tf = open(path.join(tempdir, 'music.ly'), 'w')
    tf.write(music)
    tf.close()

    ensuredir(path.dirname(outfn))
    # use some standard lilypond arguments
    lilypond_args = [self.builder.config.pnglily_lilypond]
    #lilypond_args += ['-o', tempdir, '--png']
    lilypond_args += ['-dbackend=eps', '-dno-gs-load-fonts', 
'-dinclude-eps-fonts',
                      '-o', tempdir, '--png']
    # add custom ones from config value
    lilypond_args.extend(self.builder.config.pnglily_lilypond_args)

    # last, the input file name
    lilypond_args.append(path.join(tempdir, 'music.ly'))
    try:
        p = Popen(lilypond_args, stdout=PIPE, stderr=PIPE)
    except OSError, err:
        if err.errno != 2:   # No such file or directory
            raise
        self.builder.warn('lilypond command %r cannot be run (needed for music '
                          'display), check the pnglily_lilypond setting' %
                          self.builder.config.pnglily_lilypond)
        self.builder._lilypng_warned = True
        return None, None
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise LilyExtError('lilypond exited with error:\n[stderr]\n%s\n'
                           '[stdout]\n%s' % (stderr, stdout))

    shutil.copyfile(path.join(tempdir, 'music.png'), outfn)
    #Popen(['mogrify', '-trim', outfn], stdout=PIPE, stderr=PIPE)

    return relfn

def cleanup_tempdir_lily(app, exc):
    if exc:
        return
    if not hasattr(app.builder, '_lilypng_tempdir'):
        return
    try:
        shutil.rmtree(app.builder._lilypng_tempdir)
    except Exception:
        pass

def latex_visit_lily(self, node):
    self.body.append('{' + node['music'] + '}')
    raise nodes.SkipNode

def latex_visit_displaylily(self, node):
    self.body.append(node['music'])
    raise nodes.SkipNode

def html_visit_lily(self, node):
    music = Inline_HEAD % self.builder.config.pnglily_fontsize[0]
    music += '#"' + node['music'] + '"' + Inline_BACK
    try:
        fname = render_lily(self, music)
    except LilyExtError, exc:
        sm = nodes.system_message(str(exc), type='WARNING', level=2,
                                  backrefs=[], source=node['music'])
        sm.walkabout(self)
        self.builder.warn('display lilypond %r: ' % node['music'] + str(exc))
        raise nodes.SkipNode
    if fname is None:
        # something failed -- use text-only as a bad substitute
        self.body.append('<span class="lily">%s</span>' %
                         self.encode(node['music']).strip())
    else:
        self.body.append(
            '<img class="lily" src="%s" alt="%s" align="absbottom"/>' %
            (fname, self.encode(node['music']).strip()))
    raise nodes.SkipNode


def html_visit_displaylily(self, node):
    music = Directive_HEAD % (self.builder.config.pnglily_fontsize[1],
                              self.builder.config.pnglily_fontsize[1])
    music += node['music'] + Directive_BACK
    try:
        fname = render_lily(self, music)
    except LilyExtError, exc:
        sm = nodes.system_message(str(exc), type='WARNING', level=2,
                                  backrefs=[], source=node['music'])
        sm.walkabout(self)
        self.builder.warn('inline lilypond %r: ' % node['music'] + str(exc))
        raise nodes.SkipNode
    self.body.append(self.starttag(node, 'div', CLASS='lily'))
    self.body.append('<p>')
    if fname is None:
        # something failed -- use text-only as a bad substitute
        self.body.append('<span class="lily">%s</span>' %
                         self.encode(node['music']).strip())
    else:
        self.body.append('<img src="%s" alt="%s" />\n</div>' %
                         (fname, self.encode(node['music']).strip()))
    self.body.append('</p>')
    raise nodes.SkipNode


def setup(app):
    app.add_node(lily,
                 latex=(latex_visit_lily, None),
                 html=(html_visit_lily, None))
    app.add_node(displaylily,
                 latex=(latex_visit_displaylily, None),
                 html=(html_visit_displaylily, None))
    app.add_role('lily', lily_role)
    app.add_directive('lily', LilyDirective)
    app.add_config_value('pnglily_preamble', '', False)
    app.add_config_value('pnglily_fontsize', ['6', '-3'], False)
    app.add_config_value('pnglily_lilypond', 'lilypond', False)
    app.add_config_value('pnglily_lilypond_args', [], False)
    app.connect('build-finished', cleanup_tempdir_lily)

Reply via email to