]> git.donarmstrong.com Git - lilypond.git/blob - lily/stencil-integral.cc
Issue 5041: Using eq? on numbers is undefined behavior
[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
949     {
950       vector<Transform_matrix_and_expression> out;
951       out.push_back (Transform_matrix_and_expression (trans, expr));
952       return out;
953     }
954   warning ("Stencil expression not supported by the veritcal skylines.");
955   return vector<Transform_matrix_and_expression> ();
956 }
957
958 SCM
959 Grob::maybe_pure_internal_simple_skylines_from_extents (Grob *me, Axis a, bool pure, int beg, int end, bool ignore_x, bool ignore_y)
960 {
961   vector<Box> boxes;
962   // we don't know how far spanners stretch along the X axis before
963   // line breaking. better have them take up the whole thing
964   Interval xex = ignore_x
965                  ? Interval (-infinity_f, infinity_f)
966                  : me->extent (me, X_AXIS);
967
968   // If we're looking at the x exent of a cross staff grob, it could be
969   // very early on in the computation process.  We won't know its height
970   // until way later, so we give a brute force approximation.
971   Interval yex = ignore_y
972                  ? Interval (-infinity_f, infinity_f)
973                  : me->maybe_pure_extent (me, Y_AXIS, pure, beg, end);
974
975   if (xex.is_empty () || yex.is_empty ())
976     return Skyline_pair ().smobbed_copy ();
977
978   boxes.push_back (Box (xex, yex));
979   return Skyline_pair (boxes, a).smobbed_copy ();
980 }
981
982 MAKE_SCHEME_CALLBACK (Grob, pure_simple_vertical_skylines_from_extents, 3);
983 SCM
984 Grob::pure_simple_vertical_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
985 {
986   Grob *me = unsmob<Grob> (smob);
987   int beg = robust_scm2int (begscm, 0);
988   int end = robust_scm2int (endscm, INT_MAX);
989   // We cannot measure the widths before line breaking,
990   // so we assume that the width is infinite: pass ignore_x=true
991   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, true, beg, end, true, false);
992 }
993
994 MAKE_SCHEME_CALLBACK (Grob, simple_vertical_skylines_from_extents, 1);
995 SCM
996 Grob::simple_vertical_skylines_from_extents (SCM smob)
997 {
998   Grob *me = unsmob<Grob> (smob);
999   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, false, 0, 0, false, false);
1000 }
1001
1002 MAKE_SCHEME_CALLBACK (Grob, pure_simple_horizontal_skylines_from_extents, 3);
1003 SCM
1004 Grob::pure_simple_horizontal_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
1005 {
1006   Grob *me = unsmob<Grob> (smob);
1007   int beg = robust_scm2int (begscm, 0);
1008   int end = robust_scm2int (endscm, INT_MAX);
1009   // If the grob is cross staff, we cannot measure its Y-extent before
1010   // wayyyy downstream (after spacing of axis groups is done).
1011   // Thus, we assume that the Y extent is infinite for cross staff grobs.
1012   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, true, beg, end, false, to_boolean (me->get_property ("cross-staff")));
1013 }
1014
1015 MAKE_SCHEME_CALLBACK (Grob, simple_horizontal_skylines_from_extents, 1);
1016 SCM
1017 Grob::simple_horizontal_skylines_from_extents (SCM smob)
1018 {
1019   Grob *me = unsmob<Grob> (smob);
1020   // See comment in function above.
1021   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, false, 0, 0, false, to_boolean (me->get_property ("cross-staff")));
1022 }
1023
1024 SCM
1025 Stencil::skylines_from_stencil (SCM sten, Real pad, Axis a)
1026 {
1027   Stencil *s = unsmob<Stencil> (sten);
1028   if (!s)
1029     return Skyline_pair ().smobbed_copy ();
1030
1031   vector<Transform_matrix_and_expression> data
1032     = stencil_traverser (make_transform_matrix (1.0, 0.0, 0.0, 1.0, 0.0, 0.0),
1033                          s->expr ());
1034   vector<Box> boxes;
1035   vector<Drul_array<Offset> > buildings;
1036   for (vsize i = 0; i < data.size (); i++)
1037     stencil_dispatcher (boxes, buildings, data[i].tm_, data[i].expr_);
1038
1039   // we use the bounding box if there are no boxes
1040   if (!boxes.size () && !buildings.size ())
1041     boxes.push_back (Box (s->extent (X_AXIS), s->extent (Y_AXIS)));
1042
1043   Skyline_pair out (boxes, a);
1044   out.merge (Skyline_pair (buildings, a));
1045
1046   for (DOWN_and_UP (d))
1047     out[d] = out[d].padded (pad);
1048
1049   return out.smobbed_copy ();
1050 }
1051
1052 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_stencil, 1);
1053 SCM
1054 Grob::vertical_skylines_from_stencil (SCM smob)
1055 {
1056   Grob *me = unsmob<Grob> (smob);
1057
1058   Real pad = robust_scm2double (me->get_property ("skyline-horizontal-padding"), 0.0);
1059   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, X_AXIS);
1060
1061   return out;
1062 }
1063
1064 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_stencil, 1);
1065 SCM
1066 Grob::horizontal_skylines_from_stencil (SCM smob)
1067 {
1068   Grob *me = unsmob<Grob> (smob);
1069
1070   Real pad = robust_scm2double (me->get_property ("skyline-vertical-padding"), 0.0);
1071   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, Y_AXIS);
1072
1073   return out;
1074 }
1075
1076 SCM
1077 Grob::internal_skylines_from_element_stencils (Grob *me, Axis a, bool pure, int beg, int end)
1078 {
1079
1080   extract_grob_set (me, "elements", elts);
1081   vector<Real> x_pos;
1082   vector<Real> y_pos;
1083   Grob *x_common = common_refpoint_of_array (elts, me, X_AXIS);
1084   Grob *y_common = common_refpoint_of_array (elts, me, Y_AXIS);
1085   for (vsize i = 0; i < elts.size (); i++)
1086     {
1087       x_pos.push_back (elts[i]->relative_coordinate (x_common, X_AXIS));
1088       y_pos.push_back (elts[i]->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end));
1089     }
1090   Real my_x = me->relative_coordinate (x_common, X_AXIS);
1091   Real my_y = me->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end);
1092
1093   Skyline_pair res;
1094   for (vsize i = 0; i < elts.size (); i++)
1095     {
1096       Skyline_pair *skyp = unsmob<Skyline_pair> (elts[i]->get_maybe_pure_property (a == X_AXIS ? "vertical-skylines" : "horizontal-skylines", pure, beg, end));
1097       if (skyp)
1098         {
1099           /*
1100             Here, copying is essential.  Otherwise, the skyline pair will
1101             get doubly shifted!
1102           */
1103           /*
1104             It took Mike about 6 months of his life to add the `else' clause
1105             below.  For horizontal skylines, the raise and shift calls need
1106             to be reversed.  This is what was causing the problems in the
1107             shifting with all of the tests. RIP 6 months!
1108           */
1109           Skyline_pair copy = Skyline_pair (*skyp);
1110           if (a == X_AXIS)
1111             {
1112               copy.shift (x_pos[i] - my_x);
1113               copy.raise (y_pos[i] - my_y);
1114             }
1115           else
1116             {
1117               copy.raise (x_pos[i] - my_x);
1118               copy.shift (y_pos[i] - my_y);
1119             }
1120           res.merge (copy);
1121         }
1122     }
1123   return res.smobbed_copy ();
1124 }
1125
1126 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_element_stencils, 1);
1127 SCM
1128 Grob::vertical_skylines_from_element_stencils (SCM smob)
1129 {
1130   Grob *me = unsmob<Grob> (smob);
1131   return internal_skylines_from_element_stencils (me, X_AXIS, false, 0, INT_MAX);
1132 }
1133
1134 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_element_stencils, 1);
1135 SCM
1136 Grob::horizontal_skylines_from_element_stencils (SCM smob)
1137 {
1138   Grob *me = unsmob<Grob> (smob);
1139   return internal_skylines_from_element_stencils (me, Y_AXIS, false, 0, INT_MAX);
1140 }
1141
1142 MAKE_SCHEME_CALLBACK (Grob, pure_vertical_skylines_from_element_stencils, 3);
1143 SCM
1144 Grob::pure_vertical_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1145 {
1146   Grob *me = unsmob<Grob> (smob);
1147   int beg = robust_scm2int (beg_scm, 0);
1148   int end = robust_scm2int (end_scm, 0);
1149   return internal_skylines_from_element_stencils (me, X_AXIS, true, beg, end);
1150 }
1151
1152 MAKE_SCHEME_CALLBACK (Grob, pure_horizontal_skylines_from_element_stencils, 3);
1153 SCM
1154 Grob::pure_horizontal_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1155 {
1156   Grob *me = unsmob<Grob> (smob);
1157   int beg = robust_scm2int (beg_scm, 0);
1158   int end = robust_scm2int (end_scm, 0);
1159   return internal_skylines_from_element_stencils (me, Y_AXIS, true, beg, end);
1160 }