]> git.donarmstrong.com Git - lilypond.git/blob - lily/stencil-integral.cc
Issue 4548: eliminate flip(Direction*)
[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   Real start = robust_scm2double (scm_car (expr), 0.0);
264   expr = scm_cdr (expr);
265   Real end = robust_scm2double (scm_car (expr), 0.0);
266   expr = scm_cdr (expr);
267   Real th = robust_scm2double (scm_car (expr), 0.0);
268   expr = scm_cdr (expr);
269   bool connect = to_boolean (scm_car (expr));
270   expr = scm_cdr (expr);
271   bool fill = to_boolean (scm_car (expr));
272   //////////////////////
273   start = M_PI * start / 180;
274   end = M_PI * end / 180;
275   if (end == start)
276     end += (2 * M_PI);
277   Offset sp (cos (start) * x_rad, sin (start) * y_rad);
278   Offset ep (cos (end) * x_rad, sin (end) * y_rad);
279   //////////////////////
280   Drul_array<vector<Offset> > points;
281   int quantization = max (1, (int) (((x_rad * trans.xx) + (y_rad * trans.yy)) * M_PI / QUANTIZATION_UNIT));
282   for (DOWN_and_UP (d))
283     {
284       for (vsize i = 0; i < 1 + (vsize) quantization; i++)
285         {
286           Real ang = linear_map (start, end, 0, quantization, i);
287           Offset pt (cos (ang) * x_rad, sin (ang) * y_rad);
288           Offset inter = pt + d * get_normal ((th/2) * pt.direction ());
289           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
290           points[d].push_back (inter);
291         }
292     }
293
294   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
295     {
296       Box b;
297       for (DOWN_and_UP (d))
298         {
299           b.add_point (points[d][i]);
300           b.add_point (points[d][i + 1]);
301         }
302       boxes.push_back (b);
303     }
304
305   if (connect || fill)
306     {
307       make_draw_line_boxes (boxes, buildings, trans,
308                             scm_list_5 (scm_from_double (th),
309                                         scm_from_double (sp[X_AXIS]),
310                                         scm_from_double (sp[Y_AXIS]),
311                                         scm_from_double (ep[X_AXIS]),
312                                         scm_from_double (ep[Y_AXIS])),
313                             false);
314     }
315
316   if (th > 0.0)
317     {
318       // beg line cap
319       Offset pt (cos (start) * x_rad, sin (start) * y_rad);
320       create_path_cap (boxes,
321                        buildings,
322                        trans,
323                        pt,
324                        th / 2,
325                        -get_normal (pt));
326
327       // end line cap
328       pt = Offset (cos (end) * x_rad, sin (end) * y_rad);
329       create_path_cap (boxes,
330                        buildings,
331                        trans,
332                        pt,
333                        th / 2,
334                        get_normal (pt));
335     }
336 }
337
338 void
339 make_round_filled_box_boxes (vector<Box> &boxes, PangoMatrix trans, SCM expr)
340 {
341   Real left = robust_scm2double (scm_car (expr), 0.0);
342   expr = scm_cdr (expr);
343   Real right = robust_scm2double (scm_car (expr), 0.0);
344   expr = scm_cdr (expr);
345   Real bottom = robust_scm2double (scm_car (expr), 0.0);
346   expr = scm_cdr (expr);
347   Real top = robust_scm2double (scm_car (expr), 0.0);
348   expr = scm_cdr (expr);
349   Real th = robust_scm2double (scm_car (expr), 0.0);
350   //////////////////////
351   vector<Offset> points;
352   Box b;
353   Offset p0 = Offset (-left - (th / 2), -bottom - (th / 2));
354   Offset p1 = Offset (right + (th / 2), top + (th / 2));
355   pango_matrix_transform_point (&trans, &p0[X_AXIS], &p0[Y_AXIS]);
356   pango_matrix_transform_point (&trans, &p1[X_AXIS], &p1[Y_AXIS]);
357   b.add_point (p0);
358   b.add_point (p1);
359   boxes.push_back (b);
360 }
361
362 void
363 create_path_cap (vector<Box> &boxes,
364                  vector<Drul_array<Offset> > &buildings,
365                  PangoMatrix trans, Offset pt, Real rad, Offset dir)
366 {
367   Real angle = dir.angle_degrees ();
368   PangoMatrix new_trans (trans);
369   pango_matrix_translate (&new_trans, pt[X_AXIS], pt[Y_AXIS]);
370   make_partial_ellipse_boxes (boxes, buildings, new_trans,
371                               scm_list_n (scm_from_double (rad),
372                                           scm_from_double (rad),
373                                           scm_from_double (angle-90.01),
374                                           scm_from_double (angle+90.01),
375                                           scm_from_double (0.0),
376                                           SCM_BOOL_F,
377                                           SCM_BOOL_F,
378                                           SCM_UNDEFINED));
379 }
380
381 void
382 make_draw_bezier_boxes (vector<Box> &boxes,
383                         vector<Drul_array<Offset> > &buildings,
384                         PangoMatrix trans, SCM expr)
385 {
386   Real th = robust_scm2double (scm_car (expr), 0.0);
387   expr = scm_cdr (expr);
388   Real x0 = robust_scm2double (scm_car (expr), 0.0);
389   expr = scm_cdr (expr);
390   Real y0 = robust_scm2double (scm_car (expr), 0.0);
391   expr = scm_cdr (expr);
392   Real x1 = robust_scm2double (scm_car (expr), 0.0);
393   expr = scm_cdr (expr);
394   Real y1 = robust_scm2double (scm_car (expr), 0.0);
395   expr = scm_cdr (expr);
396   Real x2 = robust_scm2double (scm_car (expr), 0.0);
397   expr = scm_cdr (expr);
398   Real y2 = robust_scm2double (scm_car (expr), 0.0);
399   expr = scm_cdr (expr);
400   Real x3 = robust_scm2double (scm_car (expr), 0.0);
401   expr = scm_cdr (expr);
402   Real y3 = robust_scm2double (scm_car (expr), 0.0);
403   //////////////////////
404   Bezier curve;
405   curve.control_[0] = Offset (x0, y0);
406   curve.control_[1] = Offset (x1, y1);
407   curve.control_[2] = Offset (x2, y2);
408   curve.control_[3] = Offset (x3, y3);
409   Offset temp0 (x0, y0);
410   Offset temp1 (x1, y1);
411   Offset temp2 (x2, y2);
412   Offset temp3 (x3, y3);
413   pango_matrix_transform_point (&trans, &temp0[X_AXIS], &temp0[Y_AXIS]);
414   pango_matrix_transform_point (&trans, &temp1[X_AXIS], &temp1[Y_AXIS]);
415   pango_matrix_transform_point (&trans, &temp2[X_AXIS], &temp2[Y_AXIS]);
416   pango_matrix_transform_point (&trans, &temp3[X_AXIS], &temp3[Y_AXIS]);
417   //////////////////////
418   Drul_array<vector<Offset> > points;
419   int quantization = int (((temp1 - temp0).length ()
420                            + (temp2 - temp1).length ()
421                            + (temp3 - temp2).length ())
422                           / QUANTIZATION_UNIT);
423   for (DOWN_and_UP (d))
424     {
425       Offset first = curve.control_[0]
426         + d * get_normal ((th / 2) * curve.dir_at_point (0.0));
427       pango_matrix_transform_point (&trans, &first[X_AXIS], &first[Y_AXIS]);
428       points[d].push_back (first);
429       for (vsize i = 1; i < (vsize) quantization; i++)
430         {
431           Real pt = (i * 1.0) / quantization;
432           Offset inter = curve.curve_point (pt)
433             + d * get_normal ((th / 2) *curve.dir_at_point (pt));
434           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
435           points[d].push_back (inter);
436         }
437       Offset last = curve.control_[3]
438         + d * get_normal ((th / 2) * curve.dir_at_point (1.0));
439       pango_matrix_transform_point (&trans, &last[X_AXIS], &last[Y_AXIS]);
440       points[d].push_back (last);
441     }
442
443   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
444     {
445       Box b;
446       for (DOWN_and_UP (d))
447         {
448           b.add_point (points[d][i]);
449           b.add_point (points[d][i + 1]);
450         }
451       boxes.push_back (b);
452     }
453
454   if (th >= 0)
455     {
456       // beg line cap
457       create_path_cap (boxes,
458                        buildings,
459                        trans,
460                        curve.control_[0],
461                        th / 2,
462                        -curve.dir_at_point (0.0));
463
464       // end line cap
465       create_path_cap (boxes,
466                        buildings,
467                        trans,
468                        curve.control_[3],
469                        th / 2,
470                        curve.dir_at_point (1.0));
471     }
472 }
473
474 /*
475   converts a path into lists of 4 (line) or 8 (curve) absolute coordinates
476   for example:
477   '(moveto 1 2 lineto 3 4 rlineto -1 -1 curveto
478     3 3 5 5 6 6 rcurveto -1 -1 -1 -1 -1 -1 closepath)
479   becomes
480   '((1 2 3 4)
481     (3 4 2 3)
482     (2 3 3 3 5 5 6 6)
483     (6 6 5 5 4 4 3 3)
484     (3 3 1 2))
485 */
486
487 SCM
488 all_commands_to_absolute_and_group (SCM expr)
489 {
490   SCM out = SCM_EOL;
491   Offset start (0, 0);
492   Offset current (0, 0);
493   bool first = true;
494   while (scm_is_pair (expr))
495     {
496       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("moveto"))
497           || (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")) && first))
498         {
499           Real x = robust_scm2double (scm_cadr (expr), 0.0);
500           Real y = robust_scm2double (scm_caddr (expr), 0.0);
501           start = Offset (x, y);
502           current = start;
503           expr = scm_cdddr (expr);
504         }
505       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")))
506         {
507           Real x = robust_scm2double (scm_cadr (expr), 0.0);
508           Real y = robust_scm2double (scm_caddr (expr), 0.0);
509           start = (Offset (x, y) + current);
510           current = start;
511           expr = scm_cdddr (expr);
512         }
513       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("lineto")))
514         {
515           Real x = robust_scm2double (scm_cadr (expr), 0.0);
516           Real y = robust_scm2double (scm_caddr (expr), 0.0);
517           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
518                                       scm_from_double (current[Y_AXIS]),
519                                       scm_from_double (x),
520                                       scm_from_double (y)),
521                           out);
522           current = Offset (x, y);
523           expr = scm_cdddr (expr);
524         }
525       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rlineto")))
526         {
527           Real x = robust_scm2double (scm_cadr (expr), 0.0);
528           Real y = robust_scm2double (scm_caddr (expr), 0.0);
529           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
530                                       scm_from_double (current[Y_AXIS]),
531                                       scm_from_double (x + current[X_AXIS]),
532                                       scm_from_double (y + current[Y_AXIS])),
533                           out);
534           current = (Offset (x, y) + current);
535           expr = scm_cdddr (expr);
536         }
537       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("curveto")))
538         {
539           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
540           expr = scm_cddr (expr);
541           Real y1 = robust_scm2double (scm_car (expr), 0.0);
542           expr = scm_cdr (expr);
543           Real x2 = robust_scm2double (scm_car (expr), 0.0);
544           expr = scm_cdr (expr);
545           Real y2 = robust_scm2double (scm_car (expr), 0.0);
546           expr = scm_cdr (expr);
547           Real x3 = robust_scm2double (scm_car (expr), 0.0);
548           expr = scm_cdr (expr);
549           Real y3 = robust_scm2double (scm_car (expr), 0.0);
550           expr = scm_cdr (expr);
551           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
552                                       scm_from_double (current[Y_AXIS]),
553                                       scm_from_double (x1),
554                                       scm_from_double (y1),
555                                       scm_from_double (x2),
556                                       scm_from_double (y2),
557                                       scm_from_double (x3),
558                                       scm_from_double (y3),
559                                       SCM_UNDEFINED),
560                           out);
561           current = Offset (x3, y3);
562         }
563       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rcurveto")))
564         {
565           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
566           expr = scm_cddr (expr);
567           Real y1 = robust_scm2double (scm_car (expr), 0.0);
568           expr = scm_cdr (expr);
569           Real x2 = robust_scm2double (scm_car (expr), 0.0);
570           expr = scm_cdr (expr);
571           Real y2 = robust_scm2double (scm_car (expr), 0.0);
572           expr = scm_cdr (expr);
573           Real x3 = robust_scm2double (scm_car (expr), 0.0);
574           expr = scm_cdr (expr);
575           Real y3 = robust_scm2double (scm_car (expr), 0.0);
576           expr = scm_cdr (expr);
577           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
578                                       scm_from_double (current[Y_AXIS]),
579                                       scm_from_double (x1 + current[X_AXIS]),
580                                       scm_from_double (y1 + current[Y_AXIS]),
581                                       scm_from_double (x2 + current[X_AXIS]),
582                                       scm_from_double (y2 + current[Y_AXIS]),
583                                       scm_from_double (x3 + current[X_AXIS]),
584                                       scm_from_double (y3 + current[Y_AXIS]),
585                                       SCM_UNDEFINED),
586                           out);
587           current = (Offset (x3, y3) + current);
588         }
589       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("closepath")))
590         {
591           if ((current[X_AXIS] != start[X_AXIS])
592               || (current[Y_AXIS] != start[Y_AXIS]))
593             {
594               out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
595                                           scm_from_double (current[Y_AXIS]),
596                                           scm_from_double (start[X_AXIS]),
597                                           scm_from_double (start[Y_AXIS])),
598                               out);
599               current = start;
600             }
601           expr = scm_cdr (expr);
602         }
603       else
604         {
605           warning ("Malformed path for path stencil.");
606           return out;
607         }
608       first = false;
609     }
610   return scm_reverse_x (out, SCM_EOL);
611 }
612
613 void
614 internal_make_path_boxes (vector<Box> &boxes,
615                           vector<Drul_array<Offset> > &buildings,
616                           PangoMatrix trans, SCM expr, bool use_building)
617 {
618   SCM blot = scm_car (expr);
619   expr = scm_cdr (expr);
620   SCM path = all_commands_to_absolute_and_group (expr);
621   // note that expr has more stuff that we don't need after this - simply ignore it
622   //////////////////////
623   for (SCM s = path; scm_is_pair (s); s = scm_cdr (s))
624     {
625       scm_to_int (scm_length (scm_car (s))) == 4
626       ? make_draw_line_boxes (boxes, buildings, trans, scm_cons (blot, scm_car (s)), use_building)
627       : make_draw_bezier_boxes (boxes, buildings, trans, scm_cons (blot, scm_car (s)));
628     }
629 }
630
631 void
632 make_path_boxes (vector<Box> &boxes,
633                  vector<Drul_array<Offset> > &buildings,
634                  PangoMatrix trans, SCM expr)
635 {
636   return internal_make_path_boxes (boxes, buildings, trans, scm_cons (scm_car (expr), get_path_list (scm_cdr (expr))), false);
637 }
638
639 void
640 make_polygon_boxes (vector<Box> &boxes,
641                     vector<Drul_array<Offset> > &buildings,
642                     PangoMatrix trans, SCM expr)
643 {
644   SCM coords = get_number_list (scm_car (expr));
645   expr = scm_cdr (expr);
646   SCM blot_diameter = scm_car (expr);
647   //////////////////////
648   bool first = true;
649   SCM l = SCM_EOL;
650   for (SCM s = coords; scm_is_pair (s); s = scm_cddr (s))
651     {
652       l = scm_cons (first ? ly_symbol2scm ("moveto") : ly_symbol2scm ("lineto"), l);
653       l = scm_cons (scm_car (s), l);
654       l = scm_cons (scm_cadr (s), l);
655       first = false;
656     }
657   l = scm_cons (ly_symbol2scm ("closepath"), l);
658   internal_make_path_boxes (boxes, buildings, trans,
659                             scm_cons (blot_diameter, scm_reverse_x (l, SCM_EOL)), true);
660 }
661
662 void
663 make_named_glyph_boxes (vector<Box> &boxes,
664                         vector<Drul_array<Offset> > &buildings,
665                         PangoMatrix trans, SCM expr)
666 {
667   SCM fm_scm = scm_car (expr);
668   Font_metric *fm = unsmob<Font_metric> (fm_scm);
669   expr = scm_cdr (expr);
670   SCM glyph = scm_car (expr);
671   string glyph_s = ly_scm2string (glyph);
672
673   //////////////////////
674   Open_type_font *open_fm
675     = dynamic_cast<Open_type_font *>
676       (dynamic_cast<Modified_font_metric *>(fm)->original_font ());
677   SCM_ASSERT_TYPE (open_fm, fm_scm, SCM_ARG1, __FUNCTION__, "OpenType font");
678
679   size_t gidx = open_fm->name_to_index (glyph_s);
680   // Bbox is the best approximation of the width based on how it would be
681   // calculated in open-type-font.cc if it were based on real extents
682   Box bbox = open_fm->get_unscaled_indexed_char_dimensions (gidx);
683   bbox.scale (dynamic_cast<Modified_font_metric *> (fm)->get_magnification ()
684               * open_fm->design_size () / open_fm->get_units_per_EM ());
685   // Real bbox is the real bbox of the object
686   Box real_bbox = open_fm->get_glyph_outline_bbox (gidx);
687
688   Real scale = bbox[X_AXIS].length () / real_bbox[X_AXIS].length ();
689
690   pango_matrix_scale (&trans, scale, scale);
691
692   SCM outline = open_fm->get_glyph_outline (gidx);
693   //////////////////////
694   for (SCM s = outline;
695        scm_is_pair (s);
696        s = scm_cdr (s))
697     {
698       scm_to_int (scm_length (scm_car (s))) == 4
699       ? make_draw_line_boxes (boxes, buildings, trans,
700                               scm_cons (scm_from_double (0), scm_car (s)),
701                               false)
702       : make_draw_bezier_boxes (boxes, buildings, trans,
703                                 scm_cons (scm_from_double (0), scm_car (s)));
704     }
705 }
706
707 void
708 make_glyph_string_boxes (vector<Box> &boxes,
709                          vector<Drul_array<Offset> > &buildings,
710                          PangoMatrix trans, SCM expr)
711 {
712   SCM fm_scm = scm_car (expr);
713   Font_metric *fm = unsmob<Font_metric> (fm_scm);
714   expr = scm_cdr (expr);
715   expr = scm_cdr (expr); // font-name
716   expr = scm_cdr (expr); // size
717   expr = scm_cdr (expr); // cid?
718   SCM whxy = scm_cadar (expr);
719   vector<Real> widths;
720   vector<Interval> heights;
721   vector<Real> xos;
722   vector<Real> yos;
723   vector<string> char_ids;
724   //////////////////////
725   Pango_font *pango_fm = dynamic_cast<Pango_font *> (fm);
726   SCM_ASSERT_TYPE (pango_fm, fm_scm, SCM_ARG1, __FUNCTION__, "Pango font");
727
728   for (SCM s = whxy; scm_is_pair (s); s = scm_cdr (s))
729     {
730       SCM now = scm_car (s);
731       widths.push_back (robust_scm2double (scm_car (now), 0.0));
732       now = scm_cdr (now);
733       heights.push_back (robust_scm2interval (scm_car (now), Interval (0, 0)));
734       now = scm_cdr (now);
735       xos.push_back (robust_scm2double (scm_car (now), 0.0));
736       now = scm_cdr (now);
737       yos.push_back (robust_scm2double (scm_car (now), 0.0));
738       now = scm_cdr (now);
739       char_ids.push_back (robust_scm2string (scm_car (now), ""));
740     }
741   Real cumulative_x = 0.0;
742   for (vsize i = 0; i < widths.size (); i++)
743     {
744       PangoMatrix transcopy (trans);
745       Offset pt0 (cumulative_x + xos[i], heights[i][DOWN] + yos[i]);
746       Offset pt1 (cumulative_x + widths[i] + xos[i], heights[i][UP] + yos[i]);
747       cumulative_x += widths[i];
748
749       Box kerned_bbox;
750       kerned_bbox.add_point (pt0);
751       kerned_bbox.add_point (pt1);
752       size_t gidx = pango_fm->name_to_index (char_ids[i]);
753       Box real_bbox = pango_fm->get_scaled_indexed_char_dimensions (gidx);
754       Box bbox = pango_fm->get_unscaled_indexed_char_dimensions (gidx);
755       SCM outline = pango_fm->get_glyph_outline (gidx);
756
757       // scales may have rounding error but should be close
758       Real xlen = real_bbox[X_AXIS].length () / bbox[X_AXIS].length ();
759       Real ylen = real_bbox[Y_AXIS].length () / bbox[Y_AXIS].length ();
760
761       /*
762         TODO:
763
764         The value will be nan for whitespace, in which case we just want
765         filler, so the kerned bbox is ok.
766
767         However, if the value is inf, this likely means that LilyPond is
768         using a font that is currently difficult to get the measurements
769         from the Pango_font.  This should eventually be fixed.  The solution
770         for now is just to use the bounding box.
771       */
772       if (isnan (xlen) || isnan (ylen) || isinf (xlen) || isinf (ylen))
773         outline = box_to_scheme_lines (kerned_bbox);
774       else
775         {
776           assert (abs (xlen - ylen) < 10e-3);
777
778           Real scale_factor = max (xlen, ylen);
779           // the three operations below move the stencil from its original coordinates to current coordinates
780           pango_matrix_translate (&transcopy, kerned_bbox[X_AXIS][LEFT],
781                                   kerned_bbox[Y_AXIS][DOWN] - real_bbox[Y_AXIS][DOWN]);
782           pango_matrix_translate (&transcopy, real_bbox[X_AXIS][LEFT],
783                                   real_bbox[Y_AXIS][DOWN]);
784           pango_matrix_scale (&transcopy, scale_factor, scale_factor);
785           pango_matrix_translate (&transcopy, -bbox[X_AXIS][LEFT],
786                                   -bbox[Y_AXIS][DOWN]);
787         }
788       //////////////////////
789       for (SCM s = outline;
790            scm_is_pair (s);
791            s = scm_cdr (s))
792         {
793           scm_to_int (scm_length (scm_car (s))) == 4
794           ? make_draw_line_boxes (boxes, buildings, transcopy,
795                                   scm_cons (scm_from_double (0), scm_car (s)),
796                                   false)
797           : make_draw_bezier_boxes (boxes, buildings, transcopy,
798                                     scm_cons (scm_from_double (0), scm_car (s)));
799         }
800     }
801 }
802
803 /*
804   receives a stencil expression and a transform matrix
805   depending on the stencil name, dispatches it to the appropriate function
806 */
807
808 void
809 stencil_dispatcher (vector<Box> &boxes,
810                     vector<Drul_array<Offset> > &buildings,
811                     PangoMatrix trans, SCM expr)
812 {
813   if (not scm_is_pair (expr))
814     return;
815   if (scm_is_eq (scm_car (expr), ly_symbol2scm ("draw-line")))
816     make_draw_line_boxes (boxes, buildings, trans, scm_cdr (expr), true);
817   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("dashed-line")))
818     {
819       expr = scm_cdr (expr);
820       SCM th = scm_car (expr);
821       expr = scm_cdr (expr);
822       expr = scm_cdr (expr); // on
823       expr = scm_cdr (expr); // off
824       SCM x1 = scm_car (expr);
825       expr = scm_cdr (expr);
826       SCM x2 = scm_car (expr);
827       make_draw_line_boxes (boxes, buildings, trans,
828                             scm_list_5 (th, scm_from_double (0.0),
829                                         scm_from_double (0.0), x1, x2), true);
830     }
831   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("circle")))
832     {
833       expr = scm_cdr (expr);
834       SCM rad = scm_car (expr);
835       expr = scm_cdr (expr);
836       SCM th = scm_car (expr);
837       make_partial_ellipse_boxes (boxes, buildings, trans,
838                                   scm_list_n (rad,
839                                               rad,
840                                               scm_from_double (0.0),
841                                               scm_from_double (360.0),
842                                               th,
843                                               SCM_BOOL_F,
844                                               SCM_BOOL_T,
845                                               SCM_UNDEFINED));
846     }
847   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("ellipse")))
848     {
849       expr = scm_cdr (expr);
850       SCM x_rad = scm_car (expr);
851       expr = scm_cdr (expr);
852       SCM y_rad = scm_car (expr);
853       expr = scm_cdr (expr);
854       SCM th = scm_car (expr);
855       make_partial_ellipse_boxes (boxes, buildings, trans,
856                                   scm_list_n (x_rad,
857                                               y_rad,
858                                               scm_from_double (0.0),
859                                               scm_from_double (360.0),
860                                               th,
861                                               SCM_BOOL_F,
862                                               SCM_BOOL_T,
863                                               SCM_UNDEFINED));
864     }
865   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("partial-ellipse")))
866     make_partial_ellipse_boxes (boxes, buildings, trans, scm_cdr (expr));
867   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("round-filled-box")))
868     make_round_filled_box_boxes (boxes, trans, scm_cdr (expr));
869   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("named-glyph")))
870     make_named_glyph_boxes (boxes, buildings, trans, scm_cdr (expr));
871   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("polygon")))
872     make_polygon_boxes (boxes, buildings, trans, scm_cdr (expr));
873   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("path")))
874     make_path_boxes (boxes, buildings, trans, scm_cdr (expr));
875   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("glyph-string")))
876     make_glyph_string_boxes (boxes, buildings, trans, scm_cdr (expr));
877   else
878     {
879 #if 0
880       warning ("Stencil expression not supported by the veritcal skylines.");
881 #endif
882       /*
883         We don't issue a warning here, as we assume that stencil-expression.cc
884         is doing stencil-checking correctly.
885       */
886     }
887 }
888
889 /*
890   traverses a stencil expression, returning a vector of Transform_matrix_and_expression
891   the struct Transform_matrix_and_expression contains two members,
892   a Transform_matrix that indicates where to move a stencil and the stencil expression
893   to show how to construct the stencil
894 */
895 vector<Transform_matrix_and_expression>
896 stencil_traverser (PangoMatrix trans, SCM expr)
897 {
898   if (scm_is_null (expr))
899     return vector<Transform_matrix_and_expression> ();
900   else if (scm_is_eq (expr, ly_string2scm ("")))
901     return vector<Transform_matrix_and_expression> ();
902   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("combine-stencil")))
903     {
904       vector<Transform_matrix_and_expression> out;
905       for (SCM s = scm_cdr (expr); scm_is_pair (s); s = scm_cdr (s))
906         {
907           vector<Transform_matrix_and_expression> res =
908             stencil_traverser (trans, scm_car (s));
909           out.insert (out.end (), res.begin (), res.end ());
910         }
911       return out;
912     }
913   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("footnote")))
914     return vector<Transform_matrix_and_expression> ();
915   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("translate-stencil")))
916     {
917       Real x = robust_scm2double (scm_caadr (expr), 0.0);
918       Real y = robust_scm2double (scm_cdadr (expr), 0.0);
919       pango_matrix_translate (&trans, x, y);
920       return stencil_traverser (trans, scm_caddr (expr));
921     }
922   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("scale-stencil")))
923     {
924       Real x = robust_scm2double (scm_caadr (expr), 0.0);
925       Real y = robust_scm2double (scm_cadadr (expr), 0.0);
926       pango_matrix_scale (&trans, x, y);
927       return stencil_traverser (trans, scm_caddr (expr));
928     }
929   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rotate-stencil")))
930     {
931       Real ang = robust_scm2double (scm_caadr (expr), 0.0);
932       Real x = robust_scm2double (scm_car (scm_cadadr (expr)), 0.0);
933       Real y = robust_scm2double (scm_cdr (scm_cadadr (expr)), 0.0);
934       pango_matrix_translate (&trans, x, y);
935       pango_matrix_rotate (&trans, -ang);
936       pango_matrix_translate (&trans, -x, -y);
937       return stencil_traverser (trans, scm_caddr (expr));
938     }
939   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("delay-stencil-evaluation")))
940     // should not use the place-holder text, but no need for the warning below
941     return vector<Transform_matrix_and_expression> ();
942   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("grob-cause")))
943     return stencil_traverser (trans, scm_caddr (expr));
944   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("color")))
945     return stencil_traverser (trans, scm_caddr (expr));
946   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("transparent-stencil")))
947     return stencil_traverser (trans, scm_cadr (expr));
948   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("id")))
949     return stencil_traverser (trans, scm_caddr (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 }