From 6052adfdab868601aab5c2847f7d4476d57dbc12 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Sun, 28 May 2006 18:03:49 +0000 Subject: [PATCH] * ly/engraver-init.ly: remove Ledger_line_engraver from Voice context. This fixes double ledger lines in output. * lily/system.cc (get_paper_system): remove 3 layer limit. * buildscripts/output-distance.py (SystemLink.distance): new file. Compare signatures. * python/safeeval.py (SafeEval.visitUnarySub): new file. * lily/grob-array.cc (remove_duplicates): rename from uniq(). Sort before calling uniq() so it actually works. --- ChangeLog | 15 ++ buildscripts/output-distance.py | 273 ++++++++++++++++++++++++++++++++ lily/grob-array.cc | 5 +- lily/include/grob-array.hh | 2 +- lily/system.cc | 88 +++++----- ly/engraver-init.ly | 1 - python/safeeval.py | 84 ++++++++++ scm/stencil.scm | 2 +- 8 files changed, 430 insertions(+), 40 deletions(-) create mode 100644 buildscripts/output-distance.py create mode 100644 python/safeeval.py diff --git a/ChangeLog b/ChangeLog index 554b5e46a3..144493cd3c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2006-05-28 Han-Wen Nienhuys + + * ly/engraver-init.ly: remove Ledger_line_engraver from Voice + context. This fixes double ledger lines in output. + + * lily/system.cc (get_paper_system): remove 3 layer limit. + + * buildscripts/output-distance.py (SystemLink.distance): new + file. Compare signatures. + + * python/safeeval.py (SafeEval.visitUnarySub): new file. + + * lily/grob-array.cc (remove_duplicates): rename from + uniq(). Sort before calling uniq() so it actually works. + 2006-05-27 Han-Wen Nienhuys * scripts/lilypond-book.py (Lilypond_file_snippet.ly): strip diff --git a/buildscripts/output-distance.py b/buildscripts/output-distance.py new file mode 100644 index 0000000000..c534d8cb2b --- /dev/null +++ b/buildscripts/output-distance.py @@ -0,0 +1,273 @@ +#!@TARGET_PYTHON@ +import sys + +sys.path.insert (0, 'python') +import safeeval + +X_AXIS = 0 +Y_AXIS = 1 +INFTY = 1e6 + +OUTPUT_EXPRESSION_PENALTY = 100 +ORPHAN_GROB_PENALTY = 1000 + +def max_distance (x1, x2): + dist = 0.0 + + for (p,q) in zip (x1, x2): + dist = max (abs (p-q), dist) + + return dist + + +empty_interval = (INFTY, -INFTY) +empty_bbox = (empty_interval, empty_interval) + +def interval_length (i): + return max (i[1]-i[0], 0) + +def interval_union (i1, i2): + return (min (i1[0], i2[0]), + max (i1[1], i2[1])) + +def interval_intersect (i1, i2): + return (max (i1[0], i2[0]), + min (i1[1], i2[1])) + +def bbox_union (b1, b2): + return (interval_union (b1[X_AXIS], b2[X_AXIS]), + interval_union (b2[Y_AXIS], b2[Y_AXIS])) + +def bbox_intersection (b1, b2): + return (interval_intersect (b1[X_AXIS], b2[X_AXIS]), + interval_intersect (b2[Y_AXIS], b2[Y_AXIS])) + +def bbox_area (b): + return interval_length (b[X_AXIS]) * interval_length (b[Y_AXIS]) + +def bbox_diameter (b): + return max (interval_length (b[X_AXIS]), + interval_length (b[Y_AXIS])) + + +def difference_area (a, b): + return bbox_area (a) - bbox_area (bbox_intersection (a,b)) + +class GrobSignature: + def __init__ (self, exp_list): + (self.name, self.origin, bbox_x, + bbox_y, self.output_expression) = tuple (exp_list) + + self.bbox = (bbox_x, bbox_y) + self.centroid = (bbox_x[0] + bbox_x[1], bbox_y[0] + bbox_y[1]) + + def __repr__ (self): + return '%s: (%.2f,%.2f), (%.2f,%.2f)\n' % (self.name, + self.bbox[0][0], + self.bbox[0][1], + self.bbox[1][0], + self.bbox[1][1]) + + def axis_centroid (self, axis): + return apply (sum, self.bbox[axis]) / 2 + + def centroid_distance (self, other, scale): + return max_distance (self.centroid, other.centroid) / scale + + def bbox_distance (self, other): + divisor = bbox_area (self.bbox) + bbox_area (other.bbox) + + if divisor: + return (difference_area (self.bbox, other.bbox) + + difference_area (other.bbox, self.bbox)) / divisor + else: + return 0.0 + + def expression_distance (self, other): + if self.output_expression == other.output_expression: + return 0.0 + else: + return OUTPUT_EXPRESSION_PENALTY + + def distance(self, other, max_distance): + return (self.expression_distance (other) + + self.centroid_distance (other, max_distance) + + self.bbox_distance (other)) + +class SystemSignature: + def __init__ (self, grob_sigs): + d = {} + for g in grob_sigs: + val = d.setdefault (g.name, []) + val += [g] + + self.grob_dict = d + self.set_all_bbox (grob_sigs) + + def set_all_bbox (self, grobs): + self.bbox = empty_bbox + for g in grobs: + self.bbox = bbox_union (g.bbox, self.bbox) + + def closest (self, grob_name, centroid): + min_d = INFTY + min_g = None + try: + grobs = self.grob_dict[grob_name] + + for g in grobs: + d = max_distance (g.centroid, centroid) + if d < min_d: + min_d = d + min_g = g + + + return min_g + + except KeyError: + return None + def grobs (self): + return reduce (lambda x,y: x+y, self.grob_dict.values(), []) + +class SystemLink: + def __init__ (self, system1, system2): + self.system1 = system1 + self.system2 = system2 + + self.link_list_dict = {} + self.back_link_dict = {} + + for g in system1.grobs (): + closest = system2.closest (g.name, g.centroid) + + self.link_list_dict.setdefault (closest, []) + self.link_list_dict[closest].append (g) + self.back_link_dict[g] = closest + + def distance (self): + d = 0.0 + + scale = max (bbox_diameter (self.system1.bbox), + bbox_diameter (self.system2.bbox)) + + for (g1,g2) in self.back_link_dict.items (): + if g2 == None: + d += ORPHAN_GROB_PENALTY + else: + d += g1.distance (g2, scale) + + for (g1,g2s) in self.link_list_dict.items (): + if len (g2s) != 1: + print g1, g2s + d += ORPHAN_GROB_PENALTY + + return d + +def read_signature_file (name): + exp_str = ("[%s]" % open (name).read ()) + entries = safeeval.safe_eval (exp_str) + + grob_sigs = [GrobSignature (e) for e in entries] + sig = SystemSignature (grob_sigs) + return sig + + + + +def compare_directories (dir1, dir2): + + pass + + +################################################################ +# TESTING + +def test (): + def system (x): + print 'invoking', x + stat = os.system (x) + assert stat == 0 + + import os + dir = 'output-distance-test' + + print 'test results in dir' + system ('rm -rf ' + dir) + os.mkdir (dir) + os.chdir (dir) + ly_template = r"""#(set! toplevel-score-handler print-score-with-defaults) +#(set! toplevel-music-handler + (lambda (p m) + (if (not (eq? (ly:music-property m 'void) #t)) + (print-score-with-defaults + p (scorify-music m p))))) + +#(ly:set-option 'point-and-click) + + + +%(papermod)s + +\relative c { + c^"%(userstring)s" %(extragrob)s + } +""" + + + dicts = [{ 'papermod' : '', + 'name' : '20', + 'extragrob': '', + 'userstring': 'test' }, + { 'papermod' : '#(set-global-staff-size 19.5)', + 'name' : '19', + 'extragrob': '', + 'userstring': 'test' }, + { 'papermod' : '', + 'name' : '20expr', + 'extragrob': '', + 'userstring': 'blabla' }, + { 'papermod' : '', + 'name' : '20grob', + 'extragrob': 'c4', + 'userstring': 'test' }] + + + for d in dicts: + open (d['name'] + '.ly','w').write (ly_template % d) + + names = [d['name'] for d in dicts] + + system ('lilypond -ddump-signatures -b eps ' + ' '.join (names)) + + sigs = dict ((n, read_signature_file ('%s-0.signature' % n)) for n in names) + + combinations = {} + for (n1, s1) in sigs.items(): + for (n2, s2) in sigs.items(): + combinations['%s-%s' % (n1, n2)] = SystemLink (s1,s2).distance () + + + results = combinations.items () + results.sort () + for k,v in results: + print '%-20s' % k, v + + assert combinations['20-20'] == 0.0 + assert combinations['20-20expr'] > 50.0 + assert combinations['20-19'] < 10.0 + + +def test_sigs (a,b): + sa = read_signature_file (a) + sb = read_signature_file (b) + link = SystemLink (sa, sb) + print link.distance() + +if __name__ == '__main__': + if sys.argv[1:]: + test_sigs (sys.argv[1], + sys.argv[2]) + else: + test () + + diff --git a/lily/grob-array.cc b/lily/grob-array.cc index f9982f1514..dff5102a83 100644 --- a/lily/grob-array.cc +++ b/lily/grob-array.cc @@ -83,8 +83,11 @@ Grob_array::clear () } void -Grob_array::uniq () +Grob_array::remove_duplicates () { + assert (!ordered_); + + vector_sort (grobs_, default_compare); ::uniq (grobs_); } diff --git a/lily/include/grob-array.hh b/lily/include/grob-array.hh index f80c83632d..ac4ba3e0c9 100644 --- a/lily/include/grob-array.hh +++ b/lily/include/grob-array.hh @@ -29,7 +29,7 @@ public: Grob *grob (vsize i) { return grobs_.at (i); } vsize size () const { return grobs_.size (); } bool empty () const; - void uniq (); + void remove_duplicates (); void clear (); void add (Grob *x) { grobs_.push_back (x); } void set_array (vector const &src); diff --git a/lily/system.cc b/lily/system.cc index d1581b9e06..dda8fdf132 100644 --- a/lily/system.cc +++ b/lily/system.cc @@ -47,13 +47,14 @@ System::init_elements () { SCM scm_arr = Grob_array::make_array (); all_elements_ = unsmob_grob_array (scm_arr); + all_elements_->set_ordered (false); set_object ("all-elements", scm_arr); } Grob * -System::clone (int count) const +System::clone (int index) const { - return new System (*this, count); + return new System (*this, index); } int @@ -171,7 +172,7 @@ System::get_paper_systems () for (vsize i = 0; i < broken_intos_.size (); i++) { System *child = dynamic_cast (broken_intos_[i]); - child->all_elements_->uniq (); + child->all_elements_->remove_duplicates (); } if (be_verbose_global) @@ -320,46 +321,61 @@ System::post_processing () } } +struct Layer_entry +{ + Grob *grob_; + int layer_; +}; + +bool +operator< (Layer_entry const &a, + Layer_entry const &b) +{ + return a.layer_ < b.layer_; +} + + SCM System::get_paper_system () { - static int const LAYER_COUNT = 3; - SCM exprs = SCM_EOL; SCM *tail = &exprs; - /* Output stencils in three layers: 0, 1, 2. Default layer: 1. + vector entries; + for (vsize j = 0; j < all_elements_->size (); j++) + { + Layer_entry e; + e.grob_ = all_elements_->grob (j); + e.layer_ = robust_scm2int (e.grob_->get_property ("layer"), 1); + + entries.push_back (e); + } - FIXME: softcode this. - */ - for (int i = 0; i < LAYER_COUNT; i++) - for (vsize j = 0; j < all_elements_->size (); j++) - { - Grob *g = all_elements_->grob (j); - if (robust_scm2int (g->get_property ("layer"), 1) != i) - continue; - - Stencil st = g->get_print_stencil (); - - if (st.expr() == SCM_EOL) - continue; - - Offset o; - for (int a = X_AXIS; a < NO_AXES; a++) - o[Axis (a)] = g->relative_coordinate (this, Axis (a)); - - Offset extra = robust_scm2offset (g->get_property ("extra-offset"), - Offset (0, 0)) - * Staff_symbol_referencer::staff_space (g); - - /* Must copy the stencil, for we cannot change the stencil - cached in G. */ - - st.translate (o + extra); - - *tail = scm_cons (st.expr (), SCM_EOL); - tail = SCM_CDRLOC (*tail); - } + vector_sort (entries, default_compare); + for (vsize j = 0; j < entries.size (); j++) + { + Grob *g = entries[j].grob_; + Stencil st = g->get_print_stencil (); + + if (st.expr() == SCM_EOL) + continue; + + Offset o; + for (int a = X_AXIS; a < NO_AXES; a++) + o[Axis (a)] = g->relative_coordinate (this, Axis (a)); + + Offset extra = robust_scm2offset (g->get_property ("extra-offset"), + Offset (0, 0)) + * Staff_symbol_referencer::staff_space (g); + + /* Must copy the stencil, for we cannot change the stencil + cached in G. */ + + st.translate (o + extra); + + *tail = scm_cons (st.expr (), SCM_EOL); + tail = SCM_CDRLOC (*tail); + } if (Stencil *me = get_stencil ()) exprs = scm_cons (me->expr (), exprs); diff --git a/ly/engraver-init.ly b/ly/engraver-init.ly index 9137bd6a9e..8ad404160a 100644 --- a/ly/engraver-init.ly +++ b/ly/engraver-init.ly @@ -141,7 +141,6 @@ contained staves are not connected vertically." \consists "Separating_line_group_engraver" \consists "Dot_column_engraver" \consists "Bar_engraver" - \consists "Ledger_line_engraver" \consists "Staff_symbol_engraver" \consists "Pitch_squash_engraver" \consists "Time_signature_engraver" diff --git a/python/safeeval.py b/python/safeeval.py new file mode 100644 index 0000000000..41076200dd --- /dev/null +++ b/python/safeeval.py @@ -0,0 +1,84 @@ + +## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/364469 +import compiler + +class Unsafe_Source_Error(Exception): + def __init__(self,error,descr = None,node = None): + self.error = error + self.descr = descr + self.node = node + self.lineno = getattr(node,"lineno",None) + + def __repr__(self): + return "Line %d. %s: %s" % (self.lineno, self.error, self.descr) + __str__ = __repr__ + +class SafeEval(object): + + def visit(self, node,**kw): + cls = node.__class__ + meth = getattr(self,'visit'+cls.__name__,self.default) + return meth(node, **kw) + + def default(self, node, **kw): + for child in node.getChildNodes(): + return self.visit(child, **kw) + + visitExpression = default + + def visitConst(self, node, **kw): + return node.value + + def visitDict(self,node,**kw): + return dict([(self.visit(k),self.visit(v)) for k,v in node.items]) + + def visitTuple(self,node, **kw): + return tuple(self.visit(i) for i in node.nodes) + + def visitList(self,node, **kw): + return [self.visit(i) for i in node.nodes] + + def visitUnarySub(self, node, **kw): + return - self.visit (node.getChildNodes ()[0]) + +class SafeEvalWithErrors(SafeEval): + + def default(self, node, **kw): + raise Unsafe_Source_Error("Unsupported source construct", + node.__class__,node) + + def visitName(self,node, **kw): + raise Unsafe_Source_Error("Strings must be quoted", + node.name, node) + + # Add more specific errors if desired + + +def safe_eval(source, fail_on_error = True): + walker = fail_on_error and SafeEvalWithErrors() or SafeEval() + try: + ast = compiler.parse(source,"eval") + except SyntaxError, err: + raise + try: + return walker.visit(ast) + except Unsafe_Source_Error, err: + raise + + +def safe_eval(source, fail_on_error = True): + walker = fail_on_error and SafeEvalWithErrors() or SafeEval() + try: + ast = compiler.parse(source,"eval") + except SyntaxError, err: + raise + try: + return walker.visit(ast) + except Unsafe_Source_Error, err: + raise + +def test (): + print safe_eval ('{1: [2,3], "4": (-1,2)}') + +if __name__ == '__main__': + test () diff --git a/scm/stencil.scm b/scm/stencil.scm index 539b9c849b..fdbefd1f0f 100644 --- a/scm/stencil.scm +++ b/scm/stencil.scm @@ -352,7 +352,7 @@ grestore ((eq? head 'combine-stencil) (for-each (lambda (e) (interpret e)) (cdr expr))) (else - (collect (ffold-false-pairs (strip-floats expr)))) + (collect (fold-false-pairs (strip-floats expr)))) ))) -- 2.39.2