]> git.donarmstrong.com Git - lilypond.git/blob - lily/stencil.cc
Web-ja: update introduction
[lilypond.git] / lily / stencil.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "stencil.hh"
21
22 #include "main.hh"
23 #include "font-metric.hh"
24 #include "input.hh"
25 #include "string-convert.hh"
26 #include "warn.hh"
27
28
29 Stencil::Stencil ()
30 {
31   expr_ = SCM_EOL;
32   set_empty (true);
33 }
34
35 Stencil::Stencil (Box b, SCM func)
36 {
37   expr_ = func;
38   dim_ = b;
39 }
40
41 SCM
42 Stencil::mark_smob () const
43 {
44   return expr_;
45 }
46
47 const char * const Stencil::type_p_name_ = "ly:stencil?";
48
49 Interval
50 Stencil::extent (Axis a) const
51 {
52   return dim_[a];
53 }
54
55 bool
56 Stencil::is_empty () const
57 {
58   return (scm_is_null (expr_)
59           || dim_.is_empty ());
60 }
61
62 bool
63 Stencil::is_empty (Axis a) const
64 {
65   return dim_.is_empty (a);
66 }
67
68 SCM
69 Stencil::expr () const
70 {
71   return expr_;
72 }
73
74 Box
75 Stencil::extent_box () const
76 {
77   return dim_;
78 }
79
80 void
81 Stencil::rotate (Real a, Offset off)
82 {
83   rotate_degrees (a, off);
84 }
85
86 /*
87   Rotate this stencil around the point ABSOLUTE_OFF.
88
89  */
90 void
91 Stencil::rotate_degrees_absolute (Real a, Offset absolute_off)
92 {
93   const Real x = absolute_off[X_AXIS];
94   const Real y = absolute_off[Y_AXIS];
95
96   /*
97    * Build scheme expression (processed in stencil-interpret.cc)
98    */
99   /* TODO: by hanwenn 2008/09/10 14:38:56:
100    * in effect, this copies the underlying expression.  It might be a
101    * little bit nicer to mirror this in the api, ie. make a
102    *         Stencil::rotated()
103    * and have Stencil::rotate be an abbrev of
104    *         *this = rotated()
105    */
106
107   expr_ = scm_list_3 (ly_symbol2scm ("rotate-stencil"),
108                       scm_list_2 (scm_from_double (a),
109                                   scm_cons (scm_from_double (x), scm_from_double (y))),
110                       expr_);
111
112   /*
113    * Calculate the new bounding box
114    */
115   Box shifted_box = extent_box ();
116   shifted_box.translate (-absolute_off);
117
118   vector<Offset> pts;
119   pts.push_back (Offset (shifted_box.x ().at (LEFT), shifted_box.y ().at (DOWN)));
120   pts.push_back (Offset (shifted_box.x ().at (RIGHT), shifted_box.y ().at (DOWN)));
121   pts.push_back (Offset (shifted_box.x ().at (RIGHT), shifted_box.y ().at (UP)));
122   pts.push_back (Offset (shifted_box.x ().at (LEFT), shifted_box.y ().at (UP)));
123
124   const Offset rot (offset_directed (a));
125   dim_.set_empty ();
126   for (vsize i = 0; i < pts.size (); i++)
127     dim_.add_point (pts[i] * rot + absolute_off);
128 }
129
130 /*
131   Rotate this stencil around the point RELATIVE_OFF.
132
133   RELATIVE_OFF is measured in terms of the extent of the stencil, so
134   -1 = LEFT/DOWN edge, 1 = RIGHT/UP edge.
135  */
136 void
137 Stencil::rotate_degrees (Real a, Offset relative_off)
138 {
139   /*
140    * Calculate the center of rotation
141    */
142   const Real x = extent (X_AXIS).linear_combination (relative_off[X_AXIS]);
143   const Real y = extent (Y_AXIS).linear_combination (relative_off[Y_AXIS]);
144   rotate_degrees_absolute (a, Offset (x, y));
145 }
146
147 void
148 Stencil::translate (Offset o)
149 {
150   Axis a = X_AXIS;
151   while (a < NO_AXES)
152     {
153       if (isinf (o[a])
154           || isnan (o[a])
155
156           // ugh, hardcoded.
157           || fabs (o[a]) > 1e6)
158         {
159           programming_error (String_convert::form_string ("Improbable offset for stencil: %f staff space", o[a])
160                              + "\n"
161                              + "Setting to zero.");
162           o[a] = 0.0;
163           if (strict_infinity_checking)
164             scm_misc_error (__FUNCTION__, "Improbable offset.", SCM_EOL);
165         }
166       incr (a);
167     }
168
169   if (!scm_is_null (expr_))
170     expr_ = scm_list_3 (ly_symbol2scm ("translate-stencil"),
171                         ly_offset2scm (o),
172                         expr_);
173   dim_.translate (o);
174 }
175
176 void
177 Stencil::translate_axis (Real x, Axis a)
178 {
179   Offset o (0, 0);
180   o[a] = x;
181   translate (o);
182 }
183
184 void
185 Stencil::scale (Real x, Real y)
186 {
187   expr_ = scm_list_3 (ly_symbol2scm ("scale-stencil"),
188                       scm_list_2 (scm_from_double (x),
189                                   scm_from_double (y)),
190                       expr_);
191   dim_[X_AXIS] *= x;
192   dim_[Y_AXIS] *= y;
193 }
194
195 void
196 Stencil::add_stencil (Stencil const &s)
197 {
198   SCM cs = ly_symbol2scm ("combine-stencil");
199   if (scm_is_null (expr_))
200     expr_ = s.expr_;
201   else if (scm_is_null (s.expr_))
202     ;
203   else if (scm_is_pair (expr_)
204       && scm_is_eq (cs, scm_car (expr_)))
205     {
206       if (scm_is_pair (s.expr_)
207           && scm_is_eq (cs, scm_car (s.expr_)))
208         expr_ = scm_append (scm_list_2 (s.expr_, scm_cdr (expr_)));
209       else
210         expr_ = scm_cons2 (cs, s.expr_, scm_cdr (expr_));
211     }
212   else
213     {
214       if (scm_is_pair (s.expr_)
215           && scm_is_eq (cs, scm_car (s.expr_)))
216         expr_ = scm_append (scm_list_2 (s.expr_, scm_list_1 (expr_)));
217       else
218         expr_ = scm_list_3 (cs, s.expr_, expr_);
219     }
220   dim_.unite (s.dim_);
221 }
222
223 void
224 Stencil::set_empty (bool e)
225 {
226   if (e)
227     {
228       dim_[X_AXIS].set_empty ();
229       dim_[Y_AXIS].set_empty ();
230     }
231   else
232     {
233       dim_[X_AXIS] = Interval (0, 0);
234       dim_[Y_AXIS] = Interval (0, 0);
235     }
236 }
237
238 void
239 Stencil::align_to (Axis a, Real x)
240 {
241   if (is_empty (a))
242     return;
243
244   Interval i (extent (a));
245   translate_axis (-i.linear_combination (x), a);
246 }
247
248 /*  See scheme Function.  */
249
250 // Any stencil that is empty in the orthogonal axis is spacing.
251 // Spacing is not subjected to the max (0) rule and can thus be
252 // negative.
253
254 void
255 Stencil::add_at_edge (Axis a, Direction d, Stencil const &s, Real padding)
256 {
257   // Material that is empty in the axis of reference has only limited
258   // usefulness for combining.  We still retain as much information as
259   // available since there may be uses like setting page links or
260   // background color or watermarks, and off-axis extents.
261
262   if (is_empty (a))
263     {
264       add_stencil (s);
265       return;
266     }
267
268   Interval first_extent = extent (a);
269
270   if (s.is_empty (a))
271     {
272       Stencil toadd (s);
273       // translation does not affect axis-empty extent box.
274       toadd.translate_axis (first_extent[d], a);
275       add_stencil (toadd);
276       return;
277     }
278
279   Interval next_extent = s.extent (a);
280
281   bool first_is_spacing = is_empty (other_axis (a));
282   bool next_is_spacing = s.is_empty (other_axis (a));
283
284   Real offset = first_extent[d] - next_extent[-d];
285
286   if (!(first_is_spacing || next_is_spacing))
287     {
288       offset += d * padding;
289     }
290
291   Stencil toadd (s);
292   toadd.translate_axis (offset, a);
293   add_stencil (toadd);
294 }
295
296 // Stencil::stack is mainly used for assembling lines or columns
297 // of stencils.  For the most common case of adding at the right, the
298 // reference point of the added stencil is usually placed at the right
299 // edge of the current one, unless the added stencil has a negative
300 // left extent in which case its left edge is placed at the right edge
301 // of the current one.
302 //
303 // Spacing is special in that it is applied without padding.  Spacing
304 // at the right edge shifts the right edge accordingly.
305 //
306 // For spacing at the left edge, there are several approaches.  In
307 // order to get to predictable behavior, we want to have at least a
308 // continuous approach.  An obvious idea is to do a "translate" by the
309 // appropriate amount.  Doing that while retaining the nominal left
310 // edge seems like the most straightforward way.
311
312 void
313 Stencil::stack (Axis a, Direction d, Stencil const &s, Real padding, Real mindist)
314 {
315   // Material that is empty in the axis of reference can't be sensibly
316   // stacked.  We just revert to add_at_edge behavior then.
317
318   if (is_empty (a))
319     {
320       Stencil toadd (s);
321       toadd.add_stencil (*this);
322       expr_ = toadd.expr ();
323       dim_ = toadd.extent_box ();
324       return;
325     }
326
327   Interval first_extent = extent (a);
328
329   if (s.is_empty (a))
330     {
331       Stencil toadd (s);
332       toadd.translate_axis (first_extent[d], a);
333       toadd.add_stencil (*this);
334       expr_ = toadd.expr ();
335       dim_ = toadd.extent_box ();
336       return;
337     }
338
339   Interval next_extent = s.extent (a);
340
341   // It is somewhat tedious to special-case all spacing, but it turns
342   // out that not doing so makes it astonishingly hard to make the
343   // code do the correct thing.
344
345   // If first is spacing, we translate second accordingly without
346   // letting this affect its backward edge.
347   if (is_empty (other_axis (a)))
348     {
349       Stencil toadd (s);
350       Real offset = d * first_extent.delta ();
351       toadd.translate_axis (offset, a);
352       toadd.add_stencil (*this);
353       expr_ = toadd.expr ();
354       dim_ = toadd.extent_box ();
355       dim_[a][-d] = next_extent[-d];
356       dim_[a][d] = next_extent[d] + offset;
357       return;
358     }
359
360   // If next is spacing, similar action:
361   if (s.is_empty (other_axis (a)))
362     {
363       Stencil toadd (s);
364       Real offset = first_extent [d];
365       toadd.translate_axis (offset, a);
366       toadd.add_stencil (*this);
367       expr_ = toadd.expr ();
368       dim_ = toadd.extent_box ();
369       dim_[a][-d] = first_extent[-d];
370       dim_[a][d] = first_extent[d] + d * next_extent.delta ();
371       return;
372     }
373
374
375   Real offset = first_extent[d];
376
377   // If the added stencil has a backwardly protruding edge, we make
378   // room for it when combining.
379
380   if (d * next_extent [-d] < 0)
381     offset -= next_extent [-d];
382
383   offset += d * padding;
384
385   if (offset * d < mindist)
386     offset = d * mindist;
387
388   Stencil toadd (s);
389   toadd.translate_axis (offset, a);
390   toadd.add_stencil (*this);
391   expr_ = toadd.expr ();
392   dim_ = toadd.extent_box ();
393   dim_[a][-d] = first_extent [-d];
394   dim_[a][d] = next_extent [d] + offset;
395 }
396
397
398 Stencil
399 Stencil::in_color (Real r, Real g, Real b) const
400 {
401   Stencil new_stencil (extent_box (),
402                        scm_list_3 (ly_symbol2scm ("color"),
403                                    scm_list_3 (scm_from_double (r),
404                                                scm_from_double (g),
405                                                scm_from_double (b)),
406                                    expr ()));
407   return new_stencil;
408 }
409
410 /* convenience */
411 Stencil
412 Stencil::translated (Offset z) const
413 {
414   Stencil s (*this);
415   s.translate (z);
416   return s;
417 }
418
419 Stencil
420 Stencil::with_outline (Stencil const &ol) const
421 {
422   Stencil new_stencil (ol.extent_box (),
423                        scm_list_3 (ly_symbol2scm ("with-outline"),
424                                    ol.expr (),
425                                    expr ()));
426   return new_stencil;
427 }