]> git.donarmstrong.com Git - lilypond.git/blob - lily/stencil-integral.cc
Issue 5087: Let extendersOverRests default to ##t
[lilypond.git] / lily / stencil-integral.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2012 Mike Solomon <mike@mikesolomon.org>
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 /*
21 tools for transform-matrices following the standard at
22 http://www.w3.org/TR/SVG/coords.html
23
24 a list in the form
25 (list a b c d e f g)
26 becomes this matrix:
27 [ a c e ]
28 [ b d f ]
29 [ 0 0 1 ]
30 when this transforms a point (x,y), the point is written as matrix:
31 [ x ]
32 [ y ]
33 [ 1 ]
34 */
35
36 #include <pango/pango-matrix.h>
37 #include "box.hh"
38 #include "bezier.hh"
39 #include "dimensions.hh"
40 #include "font-metric.hh"
41 #include "grob.hh"
42 #include "interval.hh"
43 #include "freetype.hh"
44 #include "misc.hh"
45 #include "offset.hh"
46 #include "modified-font-metric.hh"
47 #include "open-type-font.hh"
48 #include "pango-font.hh"
49 #include "pointer-group-interface.hh"
50 #include "lily-guile.hh"
51 #include "real.hh"
52 #include "rest.hh"
53 #include "stencil.hh"
54 #include "string-convert.hh"
55 #include "skyline.hh"
56 #include "skyline-pair.hh"
57 #include "spanner.hh"
58 using namespace std;
59
60 Real QUANTIZATION_UNIT = 0.2;
61
62 void create_path_cap (vector<Box> &boxes,
63                       vector<Drul_array<Offset> > &buildings,
64                       PangoMatrix trans, Offset pt, Real rad, Offset dir);
65
66 struct Transform_matrix_and_expression
67 {
68   PangoMatrix tm_;
69   SCM expr_;
70
71   Transform_matrix_and_expression (PangoMatrix tm, SCM expr);
72 };
73
74 Transform_matrix_and_expression::Transform_matrix_and_expression (PangoMatrix tm, SCM expr)
75 {
76   tm_ = tm;
77   expr_ = expr;
78 }
79
80 PangoMatrix
81 make_transform_matrix (Real p0, Real p1, Real p2, Real p3, Real p4, Real p5)
82 {
83   PangoMatrix out;
84   out.xx = p0;
85   out.xy = p1;
86   out.yx = p2;
87   out.yy = p3;
88   out.x0 = p4;
89   out.y0 = p5;
90   return out;
91 }
92
93 //// UTILITY FUNCTIONS
94
95 /*
96   map x's placement between orig_l and orig_r onto
97   the interval final_l final_r
98 */
99 Real
100 linear_map (Real final_l, Real final_r, Real orig_l, Real orig_r, Real x)
101 {
102   return final_l + ((final_r - final_l) * ((x - orig_l) / (orig_r - orig_l)));
103 }
104
105 /*
106   from a nested SCM list, return the first list of numbers
107   useful for polygons
108 */
109 SCM
110 get_number_list (SCM l)
111 {
112   if (scm_is_pair (l))
113     {
114       if (scm_is_number (scm_car (l)))
115         return l;
116       SCM res = get_number_list (scm_car (l));
117       if (scm_is_false (res))
118         return get_number_list (scm_cdr (l));
119       return res;
120     }
121   return SCM_BOOL_F;
122 }
123
124 /*
125   from a nested SCM list, return the first list of numbers
126   useful for paths
127 */
128 SCM
129 get_path_list (SCM l)
130 {
131   if (scm_is_pair (l))
132     {
133       if (scm_is_true (scm_memv (scm_car (l),
134                                  scm_list_n (ly_symbol2scm ("moveto"),
135                                              ly_symbol2scm ("rmoveto"),
136                                              ly_symbol2scm ("lineto"),
137                                              ly_symbol2scm ("rlineto"),
138                                              ly_symbol2scm ("curveto"),
139                                              ly_symbol2scm ("rcurveto"),
140                                              ly_symbol2scm ("closepath"),
141                                              SCM_UNDEFINED))))
142         return l;
143       SCM res = get_path_list (scm_car (l));
144       if (scm_is_false (res))
145         return get_path_list (scm_cdr (l));
146       return res;
147     }
148   return SCM_BOOL_F;
149 }
150
151 // Gets an orthogonal vector with same size to orig, pointing left
152 // (in the complex domain, a multiplication by i)
153
154 Offset
155 get_normal (Offset orig)
156 {
157   return Offset (-orig[Y_AXIS], orig[X_AXIS]);
158 }
159
160 //// END UTILITY FUNCTIONS
161
162 /*
163   below, for all of the functions make_X_boxes, the expression
164   is always unpacked into variables.
165   then, after a line of /////, there are manipulations of these variables
166   (there may be no manipulations necessary depending on the function)
167   afterwards, there is another ///// followed by the creation of points
168   and boxes
169 */
170
171 void
172 make_draw_line_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr, bool use_building)
173 {
174   Real thick = robust_scm2double (scm_car (expr), 0.0);
175   expr = scm_cdr (expr);
176   Real x0 = robust_scm2double (scm_car (expr), 0.0);
177   expr = scm_cdr (expr);
178   Real y0 = robust_scm2double (scm_car (expr), 0.0);
179   expr = scm_cdr (expr);
180   Real x1 = robust_scm2double (scm_car (expr), 0.0);
181   expr = scm_cdr (expr);
182   Real y1 = robust_scm2double (scm_car (expr), 0.0);
183
184   //////////////////////
185   if (x1 < x0)
186     {
187       swap (x0, x1);
188       swap (y0, y1);
189     }
190   Offset left (x0, y0);
191   Offset right (x1, y1);
192   Offset dir = (right - left).direction ();
193   for (DOWN_and_UP (d))
194     {
195       Offset outward = d * get_normal ((thick / 2) * dir);
196       Offset inter_l = left + outward;
197       Offset inter_r = right + outward;
198       pango_matrix_transform_point (&trans, &inter_l[X_AXIS], &inter_l[Y_AXIS]);
199       pango_matrix_transform_point (&trans, &inter_r[X_AXIS], &inter_r[Y_AXIS]);
200       if ((inter_l[X_AXIS] == inter_r[X_AXIS]) || (inter_l[Y_AXIS] == inter_r[Y_AXIS]))
201         {
202           Box b;
203           b.add_point (inter_l);
204           b.add_point (inter_r);
205           boxes.push_back (b);
206         }
207       else if (use_building)
208         buildings.push_back (Drul_array<Offset> (inter_l, inter_r));
209       else
210         {
211           Real length = (inter_l - inter_r).length ();
212
213           vsize passes = (vsize) ((length * 2) + 1);
214           vector<Offset> points;
215
216           for (vsize i = 0; i < 1 + passes; i++)
217             {
218               Offset pt (linear_map (x0, x1, 0, passes, i),
219                          linear_map (y0, y1, 0, passes, i));
220               Offset inter = pt + outward;
221               pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
222               points.push_back (inter);
223             }
224           for (vsize i = 0; i < points.size () - 1; i++)
225             {
226               Box b;
227               b.add_point (points[i]);
228               b.add_point (points[i + 1]);
229               boxes.push_back (b);
230             }
231         }
232     }
233
234   if (thick > 0.0)
235     {
236       // beg line cap
237       create_path_cap (boxes,
238                        buildings,
239                        trans,
240                        left,
241                        thick / 2,
242                        -dir);
243
244       // end line cap
245       create_path_cap (boxes,
246                        buildings,
247                        trans,
248                        right,
249                        thick / 2,
250                        dir);
251     }
252 }
253
254 void
255 make_partial_ellipse_boxes (vector<Box> &boxes,
256                             vector<Drul_array<Offset> > &buildings,
257                             PangoMatrix trans, SCM expr)
258 {
259   Real x_rad = robust_scm2double (scm_car (expr), 0.0);
260   expr = scm_cdr (expr);
261   Real y_rad = robust_scm2double (scm_car (expr), 0.0);
262   expr = scm_cdr (expr);
263   Offset rad (x_rad, y_rad);
264   Real start = robust_scm2double (scm_car (expr), 0.0);
265   expr = scm_cdr (expr);
266   Real end = robust_scm2double (scm_car (expr), 0.0);
267   expr = scm_cdr (expr);
268   Real th = robust_scm2double (scm_car (expr), 0.0);
269   expr = scm_cdr (expr);
270   bool connect = to_boolean (scm_car (expr));
271   expr = scm_cdr (expr);
272   bool fill = to_boolean (scm_car (expr));
273   //////////////////////
274   if (end == start)
275     end += 360;
276   Offset sp (offset_directed (start).scale (rad));
277   Offset ep (offset_directed (end).scale (rad));
278   //////////////////////
279   Drul_array<vector<Offset> > points;
280   int quantization = max (1, (int) (((x_rad * trans.xx) + (y_rad * trans.yy)) * M_PI / QUANTIZATION_UNIT));
281   for (DOWN_and_UP (d))
282     {
283       for (vsize i = 0; i < 1 + (vsize) quantization; i++)
284         {
285           Real ang = linear_map (start, end, 0, quantization, i);
286           Offset pt (offset_directed (ang).scale (rad));
287           Offset inter = pt + d * get_normal ((th/2) * pt.direction ());
288           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
289           points[d].push_back (inter);
290         }
291     }
292
293   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
294     {
295       Box b;
296       for (DOWN_and_UP (d))
297         {
298           b.add_point (points[d][i]);
299           b.add_point (points[d][i + 1]);
300         }
301       boxes.push_back (b);
302     }
303
304   if (connect || fill)
305     {
306       make_draw_line_boxes (boxes, buildings, trans,
307                             scm_list_5 (scm_from_double (th),
308                                         scm_from_double (sp[X_AXIS]),
309                                         scm_from_double (sp[Y_AXIS]),
310                                         scm_from_double (ep[X_AXIS]),
311                                         scm_from_double (ep[Y_AXIS])),
312                             false);
313     }
314
315   if (th > 0.0)
316     {
317       // beg line cap
318       Offset pt (offset_directed (start).scale (rad));
319       create_path_cap (boxes,
320                        buildings,
321                        trans,
322                        pt,
323                        th / 2,
324                        -get_normal (pt));
325
326       // end line cap
327       pt = offset_directed (end).scale (rad);
328       create_path_cap (boxes,
329                        buildings,
330                        trans,
331                        pt,
332                        th / 2,
333                        get_normal (pt));
334     }
335 }
336
337 void
338 make_round_filled_box_boxes (vector<Box> &boxes, PangoMatrix trans, SCM expr)
339 {
340   Real left = robust_scm2double (scm_car (expr), 0.0);
341   expr = scm_cdr (expr);
342   Real right = robust_scm2double (scm_car (expr), 0.0);
343   expr = scm_cdr (expr);
344   Real bottom = robust_scm2double (scm_car (expr), 0.0);
345   expr = scm_cdr (expr);
346   Real top = robust_scm2double (scm_car (expr), 0.0);
347   expr = scm_cdr (expr);
348   Real th = robust_scm2double (scm_car (expr), 0.0);
349   //////////////////////
350   vector<Offset> points;
351   Box b;
352   Offset p0 = Offset (-left - (th / 2), -bottom - (th / 2));
353   Offset p1 = Offset (right + (th / 2), top + (th / 2));
354   pango_matrix_transform_point (&trans, &p0[X_AXIS], &p0[Y_AXIS]);
355   pango_matrix_transform_point (&trans, &p1[X_AXIS], &p1[Y_AXIS]);
356   b.add_point (p0);
357   b.add_point (p1);
358   boxes.push_back (b);
359 }
360
361 void
362 create_path_cap (vector<Box> &boxes,
363                  vector<Drul_array<Offset> > &buildings,
364                  PangoMatrix trans, Offset pt, Real rad, Offset dir)
365 {
366   Real angle = dir.angle_degrees ();
367   PangoMatrix new_trans (trans);
368   pango_matrix_translate (&new_trans, pt[X_AXIS], pt[Y_AXIS]);
369   make_partial_ellipse_boxes (boxes, buildings, new_trans,
370                               scm_list_n (scm_from_double (rad),
371                                           scm_from_double (rad),
372                                           scm_from_double (angle-90.01),
373                                           scm_from_double (angle+90.01),
374                                           scm_from_double (0.0),
375                                           SCM_BOOL_F,
376                                           SCM_BOOL_F,
377                                           SCM_UNDEFINED));
378 }
379
380 void
381 make_draw_bezier_boxes (vector<Box> &boxes,
382                         vector<Drul_array<Offset> > &buildings,
383                         PangoMatrix trans, SCM expr)
384 {
385   Real th = robust_scm2double (scm_car (expr), 0.0);
386   expr = scm_cdr (expr);
387   Real x0 = robust_scm2double (scm_car (expr), 0.0);
388   expr = scm_cdr (expr);
389   Real y0 = robust_scm2double (scm_car (expr), 0.0);
390   expr = scm_cdr (expr);
391   Real x1 = robust_scm2double (scm_car (expr), 0.0);
392   expr = scm_cdr (expr);
393   Real y1 = robust_scm2double (scm_car (expr), 0.0);
394   expr = scm_cdr (expr);
395   Real x2 = robust_scm2double (scm_car (expr), 0.0);
396   expr = scm_cdr (expr);
397   Real y2 = robust_scm2double (scm_car (expr), 0.0);
398   expr = scm_cdr (expr);
399   Real x3 = robust_scm2double (scm_car (expr), 0.0);
400   expr = scm_cdr (expr);
401   Real y3 = robust_scm2double (scm_car (expr), 0.0);
402   //////////////////////
403   Bezier curve;
404   curve.control_[0] = Offset (x0, y0);
405   curve.control_[1] = Offset (x1, y1);
406   curve.control_[2] = Offset (x2, y2);
407   curve.control_[3] = Offset (x3, y3);
408   Offset temp0 (x0, y0);
409   Offset temp1 (x1, y1);
410   Offset temp2 (x2, y2);
411   Offset temp3 (x3, y3);
412   pango_matrix_transform_point (&trans, &temp0[X_AXIS], &temp0[Y_AXIS]);
413   pango_matrix_transform_point (&trans, &temp1[X_AXIS], &temp1[Y_AXIS]);
414   pango_matrix_transform_point (&trans, &temp2[X_AXIS], &temp2[Y_AXIS]);
415   pango_matrix_transform_point (&trans, &temp3[X_AXIS], &temp3[Y_AXIS]);
416   //////////////////////
417   Drul_array<vector<Offset> > points;
418   int quantization = int (((temp1 - temp0).length ()
419                            + (temp2 - temp1).length ()
420                            + (temp3 - temp2).length ())
421                           / QUANTIZATION_UNIT);
422   for (DOWN_and_UP (d))
423     {
424       Offset first = curve.control_[0]
425         + d * get_normal ((th / 2) * curve.dir_at_point (0.0));
426       pango_matrix_transform_point (&trans, &first[X_AXIS], &first[Y_AXIS]);
427       points[d].push_back (first);
428       for (vsize i = 1; i < (vsize) quantization; i++)
429         {
430           Real pt = (i * 1.0) / quantization;
431           Offset inter = curve.curve_point (pt)
432             + d * get_normal ((th / 2) *curve.dir_at_point (pt));
433           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
434           points[d].push_back (inter);
435         }
436       Offset last = curve.control_[3]
437         + d * get_normal ((th / 2) * curve.dir_at_point (1.0));
438       pango_matrix_transform_point (&trans, &last[X_AXIS], &last[Y_AXIS]);
439       points[d].push_back (last);
440     }
441
442   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
443     {
444       Box b;
445       for (DOWN_and_UP (d))
446         {
447           b.add_point (points[d][i]);
448           b.add_point (points[d][i + 1]);
449         }
450       boxes.push_back (b);
451     }
452
453   if (th >= 0)
454     {
455       // beg line cap
456       create_path_cap (boxes,
457                        buildings,
458                        trans,
459                        curve.control_[0],
460                        th / 2,
461                        -curve.dir_at_point (0.0));
462
463       // end line cap
464       create_path_cap (boxes,
465                        buildings,
466                        trans,
467                        curve.control_[3],
468                        th / 2,
469                        curve.dir_at_point (1.0));
470     }
471 }
472
473 /*
474   converts a path into lists of 4 (line) or 8 (curve) absolute coordinates
475   for example:
476   '(moveto 1 2 lineto 3 4 rlineto -1 -1 curveto
477     3 3 5 5 6 6 rcurveto -1 -1 -1 -1 -1 -1 closepath)
478   becomes
479   '((1 2 3 4)
480     (3 4 2 3)
481     (2 3 3 3 5 5 6 6)
482     (6 6 5 5 4 4 3 3)
483     (3 3 1 2))
484 */
485
486 SCM
487 all_commands_to_absolute_and_group (SCM expr)
488 {
489   SCM out = SCM_EOL;
490   Offset start (0, 0);
491   Offset current (0, 0);
492   bool first = true;
493   while (scm_is_pair (expr))
494     {
495       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("moveto"))
496           || (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")) && first))
497         {
498           Real x = robust_scm2double (scm_cadr (expr), 0.0);
499           Real y = robust_scm2double (scm_caddr (expr), 0.0);
500           start = Offset (x, y);
501           current = start;
502           expr = scm_cdddr (expr);
503         }
504       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")))
505         {
506           Real x = robust_scm2double (scm_cadr (expr), 0.0);
507           Real y = robust_scm2double (scm_caddr (expr), 0.0);
508           start = (Offset (x, y) + current);
509           current = start;
510           expr = scm_cdddr (expr);
511         }
512       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("lineto")))
513         {
514           Real x = robust_scm2double (scm_cadr (expr), 0.0);
515           Real y = robust_scm2double (scm_caddr (expr), 0.0);
516           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
517                                       scm_from_double (current[Y_AXIS]),
518                                       scm_from_double (x),
519                                       scm_from_double (y)),
520                           out);
521           current = Offset (x, y);
522           expr = scm_cdddr (expr);
523         }
524       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rlineto")))
525         {
526           Real x = robust_scm2double (scm_cadr (expr), 0.0);
527           Real y = robust_scm2double (scm_caddr (expr), 0.0);
528           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
529                                       scm_from_double (current[Y_AXIS]),
530                                       scm_from_double (x + current[X_AXIS]),
531                                       scm_from_double (y + current[Y_AXIS])),
532                           out);
533           current = (Offset (x, y) + current);
534           expr = scm_cdddr (expr);
535         }
536       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("curveto")))
537         {
538           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
539           expr = scm_cddr (expr);
540           Real y1 = robust_scm2double (scm_car (expr), 0.0);
541           expr = scm_cdr (expr);
542           Real x2 = robust_scm2double (scm_car (expr), 0.0);
543           expr = scm_cdr (expr);
544           Real y2 = robust_scm2double (scm_car (expr), 0.0);
545           expr = scm_cdr (expr);
546           Real x3 = robust_scm2double (scm_car (expr), 0.0);
547           expr = scm_cdr (expr);
548           Real y3 = robust_scm2double (scm_car (expr), 0.0);
549           expr = scm_cdr (expr);
550           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
551                                       scm_from_double (current[Y_AXIS]),
552                                       scm_from_double (x1),
553                                       scm_from_double (y1),
554                                       scm_from_double (x2),
555                                       scm_from_double (y2),
556                                       scm_from_double (x3),
557                                       scm_from_double (y3),
558                                       SCM_UNDEFINED),
559                           out);
560           current = Offset (x3, y3);
561         }
562       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rcurveto")))
563         {
564           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
565           expr = scm_cddr (expr);
566           Real y1 = robust_scm2double (scm_car (expr), 0.0);
567           expr = scm_cdr (expr);
568           Real x2 = robust_scm2double (scm_car (expr), 0.0);
569           expr = scm_cdr (expr);
570           Real y2 = robust_scm2double (scm_car (expr), 0.0);
571           expr = scm_cdr (expr);
572           Real x3 = robust_scm2double (scm_car (expr), 0.0);
573           expr = scm_cdr (expr);
574           Real y3 = robust_scm2double (scm_car (expr), 0.0);
575           expr = scm_cdr (expr);
576           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
577                                       scm_from_double (current[Y_AXIS]),
578                                       scm_from_double (x1 + current[X_AXIS]),
579                                       scm_from_double (y1 + current[Y_AXIS]),
580                                       scm_from_double (x2 + current[X_AXIS]),
581                                       scm_from_double (y2 + current[Y_AXIS]),
582                                       scm_from_double (x3 + current[X_AXIS]),
583                                       scm_from_double (y3 + current[Y_AXIS]),
584                                       SCM_UNDEFINED),
585                           out);
586           current = (Offset (x3, y3) + current);
587         }
588       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("closepath")))
589         {
590           if ((current[X_AXIS] != start[X_AXIS])
591               || (current[Y_AXIS] != start[Y_AXIS]))
592             {
593               out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
594                                           scm_from_double (current[Y_AXIS]),
595                                           scm_from_double (start[X_AXIS]),
596                                           scm_from_double (start[Y_AXIS])),
597                               out);
598               current = start;
599             }
600           expr = scm_cdr (expr);
601         }
602       else
603         {
604           warning ("Malformed path for path stencil.");
605           return out;
606         }
607       first = false;
608     }
609   return scm_reverse_x (out, SCM_EOL);
610 }
611
612 void
613 internal_make_path_boxes (vector<Box> &boxes,
614                           vector<Drul_array<Offset> > &buildings,
615                           PangoMatrix trans, SCM expr, bool use_building)
616 {
617   SCM blot = scm_car (expr);
618   expr = scm_cdr (expr);
619   SCM path = all_commands_to_absolute_and_group (expr);
620   // note that expr has more stuff that we don't need after this - simply ignore it
621   //////////////////////
622   for (SCM s = path; scm_is_pair (s); s = scm_cdr (s))
623     {
624       scm_to_int (scm_length (scm_car (s))) == 4
625       ? make_draw_line_boxes (boxes, buildings, trans, scm_cons (blot, scm_car (s)), use_building)
626       : make_draw_bezier_boxes (boxes, buildings, trans, scm_cons (blot, scm_car (s)));
627     }
628 }
629
630 void
631 make_path_boxes (vector<Box> &boxes,
632                  vector<Drul_array<Offset> > &buildings,
633                  PangoMatrix trans, SCM expr)
634 {
635   return internal_make_path_boxes (boxes, buildings, trans, scm_cons (scm_car (expr), get_path_list (scm_cdr (expr))), false);
636 }
637
638 void
639 make_polygon_boxes (vector<Box> &boxes,
640                     vector<Drul_array<Offset> > &buildings,
641                     PangoMatrix trans, SCM expr)
642 {
643   SCM coords = get_number_list (scm_car (expr));
644   expr = scm_cdr (expr);
645   SCM blot_diameter = scm_car (expr);
646   //////////////////////
647   bool first = true;
648   SCM l = SCM_EOL;
649   for (SCM s = coords; scm_is_pair (s); s = scm_cddr (s))
650     {
651       l = scm_cons (first ? ly_symbol2scm ("moveto") : ly_symbol2scm ("lineto"), l);
652       l = scm_cons (scm_car (s), l);
653       l = scm_cons (scm_cadr (s), l);
654       first = false;
655     }
656   l = scm_cons (ly_symbol2scm ("closepath"), l);
657   internal_make_path_boxes (boxes, buildings, trans,
658                             scm_cons (blot_diameter, scm_reverse_x (l, SCM_EOL)), true);
659 }
660
661 void
662 make_named_glyph_boxes (vector<Box> &boxes,
663                         vector<Drul_array<Offset> > &buildings,
664                         PangoMatrix trans, SCM expr)
665 {
666   SCM fm_scm = scm_car (expr);
667   Font_metric *fm = unsmob<Font_metric> (fm_scm);
668   expr = scm_cdr (expr);
669   SCM glyph = scm_car (expr);
670   string glyph_s = ly_scm2string (glyph);
671
672   //////////////////////
673   Open_type_font *open_fm
674     = dynamic_cast<Open_type_font *>
675       (dynamic_cast<Modified_font_metric *>(fm)->original_font ());
676   SCM_ASSERT_TYPE (open_fm, fm_scm, SCM_ARG1, __FUNCTION__, "OpenType font");
677
678   size_t gidx = open_fm->name_to_index (glyph_s);
679   // Bbox is the best approximation of the width based on how it would be
680   // calculated in open-type-font.cc if it were based on real extents
681   Box bbox = open_fm->get_unscaled_indexed_char_dimensions (gidx);
682   bbox.scale (dynamic_cast<Modified_font_metric *> (fm)->get_magnification ()
683               * open_fm->design_size () / open_fm->get_units_per_EM ());
684   // Real bbox is the real bbox of the object
685   Box real_bbox = open_fm->get_glyph_outline_bbox (gidx);
686
687   Real scale = bbox[X_AXIS].length () / real_bbox[X_AXIS].length ();
688
689   pango_matrix_scale (&trans, scale, scale);
690
691   SCM outline = open_fm->get_glyph_outline (gidx);
692   //////////////////////
693   for (SCM s = outline;
694        scm_is_pair (s);
695        s = scm_cdr (s))
696     {
697       scm_to_int (scm_length (scm_car (s))) == 4
698       ? make_draw_line_boxes (boxes, buildings, trans,
699                               scm_cons (scm_from_double (0), scm_car (s)),
700                               false)
701       : make_draw_bezier_boxes (boxes, buildings, trans,
702                                 scm_cons (scm_from_double (0), scm_car (s)));
703     }
704 }
705
706 void
707 make_glyph_string_boxes (vector<Box> &boxes,
708                          vector<Drul_array<Offset> > &buildings,
709                          PangoMatrix trans, SCM expr)
710 {
711   SCM fm_scm = scm_car (expr);
712   Font_metric *fm = unsmob<Font_metric> (fm_scm);
713   expr = scm_cdr (expr);
714   expr = scm_cdr (expr); // font-name
715   expr = scm_cdr (expr); // size
716   expr = scm_cdr (expr); // cid?
717   SCM whxy = scm_cadar (expr);
718   vector<Real> widths;
719   vector<Interval> heights;
720   vector<Real> xos;
721   vector<Real> yos;
722   vector<string> char_ids;
723   //////////////////////
724   Pango_font *pango_fm = dynamic_cast<Pango_font *> (fm);
725   SCM_ASSERT_TYPE (pango_fm, fm_scm, SCM_ARG1, __FUNCTION__, "Pango font");
726
727   for (SCM s = whxy; scm_is_pair (s); s = scm_cdr (s))
728     {
729       SCM now = scm_car (s);
730       widths.push_back (robust_scm2double (scm_car (now), 0.0));
731       now = scm_cdr (now);
732       heights.push_back (robust_scm2interval (scm_car (now), Interval (0, 0)));
733       now = scm_cdr (now);
734       xos.push_back (robust_scm2double (scm_car (now), 0.0));
735       now = scm_cdr (now);
736       yos.push_back (robust_scm2double (scm_car (now), 0.0));
737       now = scm_cdr (now);
738       char_ids.push_back (robust_scm2string (scm_car (now), ""));
739     }
740   Real cumulative_x = 0.0;
741   for (vsize i = 0; i < widths.size (); i++)
742     {
743       PangoMatrix transcopy (trans);
744       Offset pt0 (cumulative_x + xos[i], heights[i][DOWN] + yos[i]);
745       Offset pt1 (cumulative_x + widths[i] + xos[i], heights[i][UP] + yos[i]);
746       cumulative_x += widths[i];
747
748       Box kerned_bbox;
749       kerned_bbox.add_point (pt0);
750       kerned_bbox.add_point (pt1);
751       size_t gidx = pango_fm->name_to_index (char_ids[i]);
752       Box real_bbox = pango_fm->get_scaled_indexed_char_dimensions (gidx);
753       Box bbox = pango_fm->get_unscaled_indexed_char_dimensions (gidx);
754       SCM outline = pango_fm->get_glyph_outline (gidx);
755
756       // scales may have rounding error but should be close
757       Real xlen = real_bbox[X_AXIS].length () / bbox[X_AXIS].length ();
758       Real ylen = real_bbox[Y_AXIS].length () / bbox[Y_AXIS].length ();
759
760       /*
761         TODO:
762
763         The value will be nan for whitespace, in which case we just want
764         filler, so the kerned bbox is ok.
765
766         However, if the value is inf, this likely means that LilyPond is
767         using a font that is currently difficult to get the measurements
768         from the Pango_font.  This should eventually be fixed.  The solution
769         for now is just to use the bounding box.
770       */
771       if (isnan (xlen) || isnan (ylen) || isinf (xlen) || isinf (ylen))
772         outline = box_to_scheme_lines (kerned_bbox);
773       else
774         {
775           assert (abs (xlen - ylen) < 10e-3);
776
777           Real scale_factor = max (xlen, ylen);
778           // the three operations below move the stencil from its original coordinates to current coordinates
779           pango_matrix_translate (&transcopy, kerned_bbox[X_AXIS][LEFT],
780                                   kerned_bbox[Y_AXIS][DOWN] - real_bbox[Y_AXIS][DOWN]);
781           pango_matrix_translate (&transcopy, real_bbox[X_AXIS][LEFT],
782                                   real_bbox[Y_AXIS][DOWN]);
783           pango_matrix_scale (&transcopy, scale_factor, scale_factor);
784           pango_matrix_translate (&transcopy, -bbox[X_AXIS][LEFT],
785                                   -bbox[Y_AXIS][DOWN]);
786         }
787       //////////////////////
788       for (SCM s = outline;
789            scm_is_pair (s);
790            s = scm_cdr (s))
791         {
792           scm_to_int (scm_length (scm_car (s))) == 4
793           ? make_draw_line_boxes (boxes, buildings, transcopy,
794                                   scm_cons (scm_from_double (0), scm_car (s)),
795                                   false)
796           : make_draw_bezier_boxes (boxes, buildings, transcopy,
797                                     scm_cons (scm_from_double (0), scm_car (s)));
798         }
799     }
800 }
801
802 /*
803   receives a stencil expression and a transform matrix
804   depending on the stencil name, dispatches it to the appropriate function
805 */
806
807 void
808 stencil_dispatcher (vector<Box> &boxes,
809                     vector<Drul_array<Offset> > &buildings,
810                     PangoMatrix trans, SCM expr)
811 {
812   if (not scm_is_pair (expr))
813     return;
814   if (scm_is_eq (scm_car (expr), ly_symbol2scm ("draw-line")))
815     make_draw_line_boxes (boxes, buildings, trans, scm_cdr (expr), true);
816   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("dashed-line")))
817     {
818       expr = scm_cdr (expr);
819       SCM th = scm_car (expr);
820       expr = scm_cdr (expr);
821       expr = scm_cdr (expr); // on
822       expr = scm_cdr (expr); // off
823       SCM x1 = scm_car (expr);
824       expr = scm_cdr (expr);
825       SCM x2 = scm_car (expr);
826       make_draw_line_boxes (boxes, buildings, trans,
827                             scm_list_5 (th, scm_from_double (0.0),
828                                         scm_from_double (0.0), x1, x2), true);
829     }
830   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("circle")))
831     {
832       expr = scm_cdr (expr);
833       SCM rad = scm_car (expr);
834       expr = scm_cdr (expr);
835       SCM th = scm_car (expr);
836       make_partial_ellipse_boxes (boxes, buildings, trans,
837                                   scm_list_n (rad,
838                                               rad,
839                                               scm_from_double (0.0),
840                                               scm_from_double (360.0),
841                                               th,
842                                               SCM_BOOL_F,
843                                               SCM_BOOL_T,
844                                               SCM_UNDEFINED));
845     }
846   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("ellipse")))
847     {
848       expr = scm_cdr (expr);
849       SCM x_rad = scm_car (expr);
850       expr = scm_cdr (expr);
851       SCM y_rad = scm_car (expr);
852       expr = scm_cdr (expr);
853       SCM th = scm_car (expr);
854       make_partial_ellipse_boxes (boxes, buildings, trans,
855                                   scm_list_n (x_rad,
856                                               y_rad,
857                                               scm_from_double (0.0),
858                                               scm_from_double (360.0),
859                                               th,
860                                               SCM_BOOL_F,
861                                               SCM_BOOL_T,
862                                               SCM_UNDEFINED));
863     }
864   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("partial-ellipse")))
865     make_partial_ellipse_boxes (boxes, buildings, trans, scm_cdr (expr));
866   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("round-filled-box")))
867     make_round_filled_box_boxes (boxes, trans, scm_cdr (expr));
868   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("named-glyph")))
869     make_named_glyph_boxes (boxes, buildings, trans, scm_cdr (expr));
870   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("polygon")))
871     make_polygon_boxes (boxes, buildings, trans, scm_cdr (expr));
872   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("path")))
873     make_path_boxes (boxes, buildings, trans, scm_cdr (expr));
874   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("glyph-string")))
875     make_glyph_string_boxes (boxes, buildings, trans, scm_cdr (expr));
876   else
877     {
878 #if 0
879       warning ("Stencil expression not supported by the veritcal skylines.");
880 #endif
881       /*
882         We don't issue a warning here, as we assume that stencil-expression.cc
883         is doing stencil-checking correctly.
884       */
885     }
886 }
887
888 /*
889   traverses a stencil expression, returning a vector of Transform_matrix_and_expression
890   the struct Transform_matrix_and_expression contains two members,
891   a Transform_matrix that indicates where to move a stencil and the stencil expression
892   to show how to construct the stencil
893 */
894 vector<Transform_matrix_and_expression>
895 stencil_traverser (PangoMatrix trans, SCM expr)
896 {
897   if (scm_is_null (expr)
898       || (scm_is_string (expr) && scm_is_true (scm_string_null_p (expr))))
899     return vector<Transform_matrix_and_expression> ();
900   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("combine-stencil")))
901     {
902       vector<Transform_matrix_and_expression> out;
903       for (SCM s = scm_cdr (expr); scm_is_pair (s); s = scm_cdr (s))
904         {
905           vector<Transform_matrix_and_expression> res =
906             stencil_traverser (trans, scm_car (s));
907           out.insert (out.end (), res.begin (), res.end ());
908         }
909       return out;
910     }
911   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("footnote")))
912     return vector<Transform_matrix_and_expression> ();
913   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("translate-stencil")))
914     {
915       Real x = robust_scm2double (scm_caadr (expr), 0.0);
916       Real y = robust_scm2double (scm_cdadr (expr), 0.0);
917       pango_matrix_translate (&trans, x, y);
918       return stencil_traverser (trans, scm_caddr (expr));
919     }
920   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("scale-stencil")))
921     {
922       Real x = robust_scm2double (scm_caadr (expr), 0.0);
923       Real y = robust_scm2double (scm_cadadr (expr), 0.0);
924       pango_matrix_scale (&trans, x, y);
925       return stencil_traverser (trans, scm_caddr (expr));
926     }
927   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rotate-stencil")))
928     {
929       Real ang = robust_scm2double (scm_caadr (expr), 0.0);
930       Real x = robust_scm2double (scm_car (scm_cadadr (expr)), 0.0);
931       Real y = robust_scm2double (scm_cdr (scm_cadadr (expr)), 0.0);
932       pango_matrix_translate (&trans, x, y);
933       pango_matrix_rotate (&trans, -ang);
934       pango_matrix_translate (&trans, -x, -y);
935       return stencil_traverser (trans, scm_caddr (expr));
936     }
937   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("delay-stencil-evaluation")))
938     // should not use the place-holder text, but no need for the warning below
939     return vector<Transform_matrix_and_expression> ();
940   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("grob-cause")))
941     return stencil_traverser (trans, scm_caddr (expr));
942   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("color")))
943     return stencil_traverser (trans, scm_caddr (expr));
944   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("transparent-stencil")))
945     return stencil_traverser (trans, scm_cadr (expr));
946   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("output-attributes")))
947     return stencil_traverser (trans, scm_caddr (expr));
948   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("with-outline")))
949     return stencil_traverser (trans, scm_cadr (expr));
950   else
951     {
952       vector<Transform_matrix_and_expression> out;
953       out.push_back (Transform_matrix_and_expression (trans, expr));
954       return out;
955     }
956   warning ("Stencil expression not supported by the veritcal skylines.");
957   return vector<Transform_matrix_and_expression> ();
958 }
959
960 SCM
961 Grob::maybe_pure_internal_simple_skylines_from_extents (Grob *me, Axis a, bool pure, int beg, int end, bool ignore_x, bool ignore_y)
962 {
963   vector<Box> boxes;
964   // we don't know how far spanners stretch along the X axis before
965   // line breaking. better have them take up the whole thing
966   Interval xex = ignore_x
967                  ? Interval (-infinity_f, infinity_f)
968                  : me->extent (me, X_AXIS);
969
970   // If we're looking at the x exent of a cross staff grob, it could be
971   // very early on in the computation process.  We won't know its height
972   // until way later, so we give a brute force approximation.
973   Interval yex = ignore_y
974                  ? Interval (-infinity_f, infinity_f)
975                  : me->maybe_pure_extent (me, Y_AXIS, pure, beg, end);
976
977   if (xex.is_empty () || yex.is_empty ())
978     return Skyline_pair ().smobbed_copy ();
979
980   boxes.push_back (Box (xex, yex));
981   return Skyline_pair (boxes, a).smobbed_copy ();
982 }
983
984 MAKE_SCHEME_CALLBACK (Grob, pure_simple_vertical_skylines_from_extents, 3);
985 SCM
986 Grob::pure_simple_vertical_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
987 {
988   Grob *me = unsmob<Grob> (smob);
989   int beg = robust_scm2int (begscm, 0);
990   int end = robust_scm2int (endscm, INT_MAX);
991   // We cannot measure the widths before line breaking,
992   // so we assume that the width is infinite: pass ignore_x=true
993   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, true, beg, end, true, false);
994 }
995
996 MAKE_SCHEME_CALLBACK (Grob, simple_vertical_skylines_from_extents, 1);
997 SCM
998 Grob::simple_vertical_skylines_from_extents (SCM smob)
999 {
1000   Grob *me = unsmob<Grob> (smob);
1001   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, false, 0, 0, false, false);
1002 }
1003
1004 MAKE_SCHEME_CALLBACK (Grob, pure_simple_horizontal_skylines_from_extents, 3);
1005 SCM
1006 Grob::pure_simple_horizontal_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
1007 {
1008   Grob *me = unsmob<Grob> (smob);
1009   int beg = robust_scm2int (begscm, 0);
1010   int end = robust_scm2int (endscm, INT_MAX);
1011   // If the grob is cross staff, we cannot measure its Y-extent before
1012   // wayyyy downstream (after spacing of axis groups is done).
1013   // Thus, we assume that the Y extent is infinite for cross staff grobs.
1014   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, true, beg, end, false, to_boolean (me->get_property ("cross-staff")));
1015 }
1016
1017 MAKE_SCHEME_CALLBACK (Grob, simple_horizontal_skylines_from_extents, 1);
1018 SCM
1019 Grob::simple_horizontal_skylines_from_extents (SCM smob)
1020 {
1021   Grob *me = unsmob<Grob> (smob);
1022   // See comment in function above.
1023   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, false, 0, 0, false, to_boolean (me->get_property ("cross-staff")));
1024 }
1025
1026 SCM
1027 Stencil::skylines_from_stencil (SCM sten, Real pad, Axis a)
1028 {
1029   Stencil *s = unsmob<Stencil> (sten);
1030   if (!s)
1031     return Skyline_pair ().smobbed_copy ();
1032
1033   vector<Transform_matrix_and_expression> data
1034     = stencil_traverser (make_transform_matrix (1.0, 0.0, 0.0, 1.0, 0.0, 0.0),
1035                          s->expr ());
1036   vector<Box> boxes;
1037   vector<Drul_array<Offset> > buildings;
1038   for (vsize i = 0; i < data.size (); i++)
1039     stencil_dispatcher (boxes, buildings, data[i].tm_, data[i].expr_);
1040
1041   // we use the bounding box if there are no boxes
1042   if (!boxes.size () && !buildings.size ())
1043     boxes.push_back (Box (s->extent (X_AXIS), s->extent (Y_AXIS)));
1044
1045   Skyline_pair out (boxes, a);
1046   out.merge (Skyline_pair (buildings, a));
1047
1048   for (DOWN_and_UP (d))
1049     out[d] = out[d].padded (pad);
1050
1051   return out.smobbed_copy ();
1052 }
1053
1054 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_stencil, 1);
1055 SCM
1056 Grob::vertical_skylines_from_stencil (SCM smob)
1057 {
1058   Grob *me = unsmob<Grob> (smob);
1059
1060   Real pad = robust_scm2double (me->get_property ("skyline-horizontal-padding"), 0.0);
1061   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, X_AXIS);
1062
1063   return out;
1064 }
1065
1066 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_stencil, 1);
1067 SCM
1068 Grob::horizontal_skylines_from_stencil (SCM smob)
1069 {
1070   Grob *me = unsmob<Grob> (smob);
1071
1072   Real pad = robust_scm2double (me->get_property ("skyline-vertical-padding"), 0.0);
1073   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, Y_AXIS);
1074
1075   return out;
1076 }
1077
1078 SCM
1079 Grob::internal_skylines_from_element_stencils (Grob *me, Axis a, bool pure, int beg, int end)
1080 {
1081
1082   extract_grob_set (me, "elements", elts);
1083   vector<Real> x_pos;
1084   vector<Real> y_pos;
1085   Grob *x_common = common_refpoint_of_array (elts, me, X_AXIS);
1086   Grob *y_common = common_refpoint_of_array (elts, me, Y_AXIS);
1087   for (vsize i = 0; i < elts.size (); i++)
1088     {
1089       x_pos.push_back (elts[i]->relative_coordinate (x_common, X_AXIS));
1090       y_pos.push_back (elts[i]->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end));
1091     }
1092   Real my_x = me->relative_coordinate (x_common, X_AXIS);
1093   Real my_y = me->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end);
1094
1095   Skyline_pair res;
1096   for (vsize i = 0; i < elts.size (); i++)
1097     {
1098       Skyline_pair *skyp = unsmob<Skyline_pair> (elts[i]->get_maybe_pure_property (a == X_AXIS ? "vertical-skylines" : "horizontal-skylines", pure, beg, end));
1099       if (skyp)
1100         {
1101           /*
1102             Here, copying is essential.  Otherwise, the skyline pair will
1103             get doubly shifted!
1104           */
1105           /*
1106             It took Mike about 6 months of his life to add the `else' clause
1107             below.  For horizontal skylines, the raise and shift calls need
1108             to be reversed.  This is what was causing the problems in the
1109             shifting with all of the tests. RIP 6 months!
1110           */
1111           Skyline_pair copy = Skyline_pair (*skyp);
1112           if (a == X_AXIS)
1113             {
1114               copy.shift (x_pos[i] - my_x);
1115               copy.raise (y_pos[i] - my_y);
1116             }
1117           else
1118             {
1119               copy.raise (x_pos[i] - my_x);
1120               copy.shift (y_pos[i] - my_y);
1121             }
1122           res.merge (copy);
1123         }
1124     }
1125   return res.smobbed_copy ();
1126 }
1127
1128 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_element_stencils, 1);
1129 SCM
1130 Grob::vertical_skylines_from_element_stencils (SCM smob)
1131 {
1132   Grob *me = unsmob<Grob> (smob);
1133   return internal_skylines_from_element_stencils (me, X_AXIS, false, 0, INT_MAX);
1134 }
1135
1136 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_element_stencils, 1);
1137 SCM
1138 Grob::horizontal_skylines_from_element_stencils (SCM smob)
1139 {
1140   Grob *me = unsmob<Grob> (smob);
1141   return internal_skylines_from_element_stencils (me, Y_AXIS, false, 0, INT_MAX);
1142 }
1143
1144 MAKE_SCHEME_CALLBACK (Grob, pure_vertical_skylines_from_element_stencils, 3);
1145 SCM
1146 Grob::pure_vertical_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1147 {
1148   Grob *me = unsmob<Grob> (smob);
1149   int beg = robust_scm2int (beg_scm, 0);
1150   int end = robust_scm2int (end_scm, 0);
1151   return internal_skylines_from_element_stencils (me, X_AXIS, true, beg, end);
1152 }
1153
1154 MAKE_SCHEME_CALLBACK (Grob, pure_horizontal_skylines_from_element_stencils, 3);
1155 SCM
1156 Grob::pure_horizontal_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1157 {
1158   Grob *me = unsmob<Grob> (smob);
1159   int beg = robust_scm2int (beg_scm, 0);
1160   int end = robust_scm2int (end_scm, 0);
1161   return internal_skylines_from_element_stencils (me, Y_AXIS, true, beg, end);
1162 }