]> git.donarmstrong.com Git - lilypond.git/blob - scripts/auxiliar/skyline_viewer.py
Add '-dcrop' option to ps and svg backends
[lilypond.git] / scripts / auxiliar / skyline_viewer.py
1 #!/usr/bin/env python
2
3 # This file is part of LilyPond, the GNU music typesetter.
4 #
5 # Copyright (C) 2012 Joe Neeman <joeneeman@gmail.com>
6 #
7 # LilyPond is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # LilyPond is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19
20 # A GTK+ program for debugging skylines. The program reads a sequence
21 # of line segments from stdin (one line segment per line of stdin, in the format
22 # '(x1, y1) (x2, y2)'). A skyline is terminated by an empty line, which
23 # causes the skyline to be displayed on the screen.
24
25 from threading import Thread
26 from math import isinf
27 import gtk
28 import gobject
29 import goocanvas
30 import sys
31 import re
32
33 class GtkSkylineCanvas (goocanvas.Canvas):
34     """A Canvas for displaying skylines."""
35     def __init__ (self):
36         super (GtkSkylineCanvas, self).__init__ ()
37         self.connect ('size-allocate', GtkSkylineCanvas.rescale)
38         self.x_min = float ('inf')
39         self.x_max = float ('-inf')
40         self.y_min = float ('inf')
41         self.y_max = float ('-inf')
42
43         self.colors = ('black', 'red', 'green', 'blue', 'maroon', 'olive', 'teal')
44         self.cur_color_index = 0
45
46     def rescale (self, allocation):
47         width = (self.x_max - self.x_min + 1) * 1.1
48         height = (self.y_max - self.y_min + 1) * 1.1
49         if width <= 0 or height <= 0:
50             return
51
52         scale_x = allocation.width / width
53         scale_y = allocation.height / height
54         scale = min (scale_x, scale_y)
55         self.set_scale (scale)
56
57         center_x = (self.x_max + self.x_min) / 2
58         center_y = (self.y_max + self.y_min) / 2
59         actual_width = allocation.width / scale
60         actual_height = allocation.height / scale
61         actual_min_x = center_x - actual_width / 2
62         actual_max_x = center_x + actual_width / 2
63         actual_min_y = center_y - actual_height / 2
64         actual_max_y = center_y + actual_height / 2
65
66         self.set_bounds (actual_min_x, actual_min_y, actual_max_x, actual_max_y)
67         self.scroll_to (actual_min_x, actual_min_y)
68
69     def add_skyline (self, lines):
70         """Adds a skyline to the current canvas, in a new color.
71
72         The canvas will be rescaled, if necessary, to make room for the
73         new skyline."""
74         # Flip vertically, because goocanvas thinks higher numbers are
75         # further down, while lilypond thinks they're further up.
76         lines = [(x1, -y1, x2, -y2) for (x1, y1, x2, y2) in lines]
77
78         color = self.colors[self.cur_color_index]
79         self.cur_color_index = (self.cur_color_index + 1) % len (self.colors)
80
81         # Update the bounding box of the skylines.
82         x_vals = [s[0] for s in lines] + [s[2] for s in lines]
83         y_vals = [s[1] for s in lines] + [s[3] for s in lines]
84         self.x_min = min ([self.x_min] + x_vals)
85         self.x_max = max ([self.x_max] + x_vals)
86         self.y_min = min ([self.y_min] + y_vals)
87         self.y_max = max ([self.y_max] + y_vals)
88
89         # Add the lines to the canvas.
90         root = self.get_root_item ()
91         for (x1, y1, x2, y2) in lines:
92             goocanvas.polyline_new_line (root, x1, y1, x2, y2,
93                     stroke_color=color,
94                     line_width=0.05)
95         self.rescale (self.get_allocation ())
96
97 # We want to run the gtk main loop in a separate thread so that
98 # the main thread can be responsible for reading stdin.
99 class SkylineWindowThread (Thread):
100     """A thread that runs a Gtk.Window displaying a skyline."""
101
102     def run (self):
103         gtk.gdk.threads_init ()
104         self.window = None
105         self.canvas = None
106         gtk.main ()
107
108     # This should only be called from the Gtk main loop.
109     def _destroy_window (self, window):
110         sys.exit (0)
111
112     # This should only be called from the Gtk main loop.
113     def _setup_window (self):
114         if self.window is None:
115             self.window = gtk.Window ()
116             self.canvas = GtkSkylineCanvas ()
117             self.window.add (self.canvas)
118             self.window.connect ("destroy", self._destroy_window)
119             self.window.show_all ()
120
121     # This should only be called from the Gtk main loop.
122     def _add_skyline (self, lines):
123         self._setup_window ()
124         self.canvas.add_skyline (lines)
125
126     def add_skyline (self, lines):
127         # Copy the lines, just in case someone modifies them.
128         gobject.idle_add (self._add_skyline, list (lines))
129
130 thread = SkylineWindowThread ()
131 thread.setDaemon (True)
132 thread.start ()
133
134 def lines(infile):
135     line = infile.readline()
136     while len(line) > 0:
137         yield line[:-1]
138         line = infile.readline()
139
140 point_re_str = r'\(([a-z.0-9-]*) *,([a-z0-9.-]*)\)'
141 line_re_str = point_re_str + r' +' + point_re_str
142 line_re = re.compile (line_re_str)
143
144 # The main loop just reads lines from stdin and feeds them to the
145 # display.
146 current_skyline = []
147 for line in lines(sys.stdin):
148     if not line:
149         thread.add_skyline(current_skyline)
150         current_skyline = []
151         continue
152
153     m = re.search (line_re, line)
154     if m is None:
155         print('line did not match')
156     else:
157         pts = map(float, m.groups())
158         if not any(map(isinf, pts)):
159             current_skyline.append(pts)