]> git.donarmstrong.com Git - lilypond.git/blob - lily/stencil-integral.cc
Issue 4468/2: Avoid skyline interruptions at line caps
[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   Direction d = DOWN;
194   do
195     {
196       Offset outward = d * get_normal ((thick / 2) * dir);
197       Offset inter_l = left + outward;
198       Offset inter_r = right + outward;
199       pango_matrix_transform_point (&trans, &inter_l[X_AXIS], &inter_l[Y_AXIS]);
200       pango_matrix_transform_point (&trans, &inter_r[X_AXIS], &inter_r[Y_AXIS]);
201       if ((inter_l[X_AXIS] == inter_r[X_AXIS]) || (inter_l[Y_AXIS] == inter_r[Y_AXIS]))
202         {
203           Box b;
204           b.add_point (inter_l);
205           b.add_point (inter_r);
206           boxes.push_back (b);
207         }
208       else if (use_building)
209         buildings.push_back (Drul_array<Offset> (inter_l, inter_r));
210       else
211         {
212           Real length = (inter_l - inter_r).length ();
213
214           vsize passes = (vsize) ((length * 2) + 1);
215           vector<Offset> points;
216
217           for (vsize i = 0; i < 1 + passes; i++)
218             {
219               Offset pt (linear_map (x0, x1, 0, passes, i),
220                          linear_map (y0, y1, 0, passes, i));
221               Offset inter = pt + outward;
222               pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
223               points.push_back (inter);
224             }
225           for (vsize i = 0; i < points.size () - 1; i++)
226             {
227               Box b;
228               b.add_point (points[i]);
229               b.add_point (points[i + 1]);
230               boxes.push_back (b);
231             }
232         }
233     }
234   while (flip (&d) != DOWN);
235
236   if (thick > 0.0)
237     {
238       // beg line cap
239       create_path_cap (boxes,
240                        buildings,
241                        trans,
242                        left,
243                        thick / 2,
244                        -dir);
245
246       // end line cap
247       create_path_cap (boxes,
248                        buildings,
249                        trans,
250                        right,
251                        thick / 2,
252                        dir);
253     }
254 }
255
256 void
257 make_partial_ellipse_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, 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   Direction d = DOWN;
282   int quantization = max (1, (int) (((x_rad * trans.xx) + (y_rad * trans.yy)) * M_PI / QUANTIZATION_UNIT));
283   do
284     {
285       for (vsize i = 0; i < 1 + (vsize) quantization; i++)
286         {
287           Real ang = linear_map (start, end, 0, quantization, i);
288           Offset pt (cos (ang) * x_rad, sin (ang) * y_rad);
289           Offset inter = pt + d * get_normal ((th/2) * pt.direction ());
290           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
291           points[d].push_back (inter);
292         }
293     }
294   while (flip (&d) != DOWN);
295
296   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
297     {
298       Box b;
299       do
300         {
301           b.add_point (points[d][i]);
302           b.add_point (points[d][i + 1]);
303         }
304       while (flip (&d) != DOWN);
305       boxes.push_back (b);
306     }
307
308   if (connect || fill)
309     {
310       make_draw_line_boxes (boxes, buildings, trans, scm_list_5 (scm_from_double (th),
311                                                                  scm_from_double (sp[X_AXIS]),
312                                                                  scm_from_double (sp[Y_AXIS]),
313                                                                  scm_from_double (ep[X_AXIS]),
314                                                                  scm_from_double (ep[Y_AXIS])),
315                             false);
316     }
317
318   if (th > 0.0)
319     {
320       // beg line cap
321       Offset pt (cos (start) * x_rad, sin (start) * y_rad);
322       create_path_cap (boxes,
323                        buildings,
324                        trans,
325                        pt,
326                        th / 2,
327                        -get_normal (pt));
328
329       // end line cap
330       pt = Offset (cos (end) * x_rad, sin (end) * y_rad);
331       create_path_cap (boxes,
332                        buildings,
333                        trans,
334                        pt,
335                        th / 2,
336                        get_normal (pt));
337     }
338 }
339
340 void
341 make_round_filled_box_boxes (vector<Box> &boxes, PangoMatrix trans, SCM expr)
342 {
343   Real left = robust_scm2double (scm_car (expr), 0.0);
344   expr = scm_cdr (expr);
345   Real right = robust_scm2double (scm_car (expr), 0.0);
346   expr = scm_cdr (expr);
347   Real bottom = robust_scm2double (scm_car (expr), 0.0);
348   expr = scm_cdr (expr);
349   Real top = robust_scm2double (scm_car (expr), 0.0);
350   expr = scm_cdr (expr);
351   Real th = robust_scm2double (scm_car (expr), 0.0);
352   //////////////////////
353   vector<Offset> points;
354   Box b;
355   Offset p0 = Offset (-left - (th / 2), -bottom - (th / 2));
356   Offset p1 = Offset (right + (th / 2), top + (th / 2));
357   pango_matrix_transform_point (&trans, &p0[X_AXIS], &p0[Y_AXIS]);
358   pango_matrix_transform_point (&trans, &p1[X_AXIS], &p1[Y_AXIS]);
359   b.add_point (p0);
360   b.add_point (p1);
361   boxes.push_back (b);
362 }
363
364 void
365 create_path_cap (vector<Box> &boxes,
366                  vector<Drul_array<Offset> > &buildings,
367                  PangoMatrix trans, Offset pt, Real rad, Offset dir)
368 {
369   Real angle = dir.angle_degrees ();
370   PangoMatrix new_trans (trans);
371   pango_matrix_translate (&new_trans, pt[X_AXIS], pt[Y_AXIS]);
372   make_partial_ellipse_boxes (boxes, buildings, new_trans,
373                               scm_list_n (scm_from_double (rad),
374                                           scm_from_double (rad),
375                                           scm_from_double (angle-90.01),
376                                           scm_from_double (angle+90.01),
377                                           scm_from_double (0.0),
378                                           SCM_BOOL_F,
379                                           SCM_BOOL_F,
380                                           SCM_UNDEFINED));
381 }
382
383 void
384 make_draw_bezier_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, 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   Direction d = DOWN;
420   int quantization = int (((temp1 - temp0).length ()
421                            + (temp2 - temp1).length ()
422                            + (temp3 - temp2).length ())
423                           / QUANTIZATION_UNIT);
424   do
425     {
426       Offset first = curve.control_[0]
427         + d * get_normal ((th / 2) * curve.dir_at_point (0.0));
428       pango_matrix_transform_point (&trans, &first[X_AXIS], &first[Y_AXIS]);
429       points[d].push_back (first);
430       for (vsize i = 1; i < (vsize) quantization; i++)
431         {
432           Real pt = (i * 1.0) / quantization;
433           Offset inter = curve.curve_point (pt)
434             + d * get_normal ((th / 2) *curve.dir_at_point (pt));
435           pango_matrix_transform_point (&trans, &inter[X_AXIS], &inter[Y_AXIS]);
436           points[d].push_back (inter);
437         }
438       Offset last = curve.control_[3]
439         + d * get_normal ((th / 2) * curve.dir_at_point (1.0));
440       pango_matrix_transform_point (&trans, &last[X_AXIS], &last[Y_AXIS]);
441       points[d].push_back (last);
442     }
443   while (flip (&d) != DOWN);
444
445   for (vsize i = 0; i < points[DOWN].size () - 1; i++)
446     {
447       Box b;
448       do
449         {
450           b.add_point (points[d][i]);
451           b.add_point (points[d][i + 1]);
452         }
453       while (flip (&d) != DOWN);
454       boxes.push_back (b);
455     }
456
457   if (th >= 0)
458     {
459       // beg line cap
460       create_path_cap (boxes,
461                        buildings,
462                        trans,
463                        curve.control_[0],
464                        th / 2,
465                        -curve.dir_at_point (0.0));
466
467       // end line cap
468       create_path_cap (boxes,
469                        buildings,
470                        trans,
471                        curve.control_[3],
472                        th / 2,
473                        curve.dir_at_point (1.0));
474     }
475 }
476
477 /*
478   converts a path into lists of 4 (line) or 8 (curve) absolute coordinates
479   for example:
480   '(moveto 1 2 lineto 3 4 rlineto -1 -1 curveto 3 3 5 5 6 6 rcurveto -1 -1 -1 -1 -1 -1 closepath)
481   becomes
482   '((1 2 3 4)
483     (3 4 2 3)
484     (2 3 3 3 5 5 6 6)
485     (6 6 5 5 4 4 3 3)
486     (3 3 1 2))
487 */
488
489 SCM
490 all_commands_to_absolute_and_group (SCM expr)
491 {
492   SCM out = SCM_EOL;
493   Offset start (0, 0);
494   Offset current (0, 0);
495   bool first = true;
496   while (scm_is_pair (expr))
497     {
498       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("moveto"))
499           || (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")) && first))
500         {
501           Real x = robust_scm2double (scm_cadr (expr), 0.0);
502           Real y = robust_scm2double (scm_caddr (expr), 0.0);
503           start = Offset (x, y);
504           current = start;
505           expr = scm_cdddr (expr);
506         }
507       if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rmoveto")))
508         {
509           Real x = robust_scm2double (scm_cadr (expr), 0.0);
510           Real y = robust_scm2double (scm_caddr (expr), 0.0);
511           start = (Offset (x, y) + current);
512           current = start;
513           expr = scm_cdddr (expr);
514         }
515       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("lineto")))
516         {
517           Real x = robust_scm2double (scm_cadr (expr), 0.0);
518           Real y = robust_scm2double (scm_caddr (expr), 0.0);
519           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
520                                       scm_from_double (current[Y_AXIS]),
521                                       scm_from_double (x),
522                                       scm_from_double (y)),
523                           out);
524           current = Offset (x, y);
525           expr = scm_cdddr (expr);
526         }
527       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rlineto")))
528         {
529           Real x = robust_scm2double (scm_cadr (expr), 0.0);
530           Real y = robust_scm2double (scm_caddr (expr), 0.0);
531           out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
532                                       scm_from_double (current[Y_AXIS]),
533                                       scm_from_double (x + current[X_AXIS]),
534                                       scm_from_double (y + current[Y_AXIS])),
535                           out);
536           current = (Offset (x, y) + current);
537           expr = scm_cdddr (expr);
538         }
539       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("curveto")))
540         {
541           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
542           expr = scm_cddr (expr);
543           Real y1 = robust_scm2double (scm_car (expr), 0.0);
544           expr = scm_cdr (expr);
545           Real x2 = robust_scm2double (scm_car (expr), 0.0);
546           expr = scm_cdr (expr);
547           Real y2 = robust_scm2double (scm_car (expr), 0.0);
548           expr = scm_cdr (expr);
549           Real x3 = robust_scm2double (scm_car (expr), 0.0);
550           expr = scm_cdr (expr);
551           Real y3 = robust_scm2double (scm_car (expr), 0.0);
552           expr = scm_cdr (expr);
553           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
554                                       scm_from_double (current[Y_AXIS]),
555                                       scm_from_double (x1),
556                                       scm_from_double (y1),
557                                       scm_from_double (x2),
558                                       scm_from_double (y2),
559                                       scm_from_double (x3),
560                                       scm_from_double (y3),
561                                       SCM_UNDEFINED),
562                           out);
563           current = Offset (x3, y3);
564         }
565       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rcurveto")))
566         {
567           Real x1 = robust_scm2double (scm_cadr (expr), 0.0);
568           expr = scm_cddr (expr);
569           Real y1 = robust_scm2double (scm_car (expr), 0.0);
570           expr = scm_cdr (expr);
571           Real x2 = robust_scm2double (scm_car (expr), 0.0);
572           expr = scm_cdr (expr);
573           Real y2 = robust_scm2double (scm_car (expr), 0.0);
574           expr = scm_cdr (expr);
575           Real x3 = robust_scm2double (scm_car (expr), 0.0);
576           expr = scm_cdr (expr);
577           Real y3 = robust_scm2double (scm_car (expr), 0.0);
578           expr = scm_cdr (expr);
579           out = scm_cons (scm_list_n (scm_from_double (current[X_AXIS]),
580                                       scm_from_double (current[Y_AXIS]),
581                                       scm_from_double (x1 + current[X_AXIS]),
582                                       scm_from_double (y1 + current[Y_AXIS]),
583                                       scm_from_double (x2 + current[X_AXIS]),
584                                       scm_from_double (y2 + current[Y_AXIS]),
585                                       scm_from_double (x3 + current[X_AXIS]),
586                                       scm_from_double (y3 + current[Y_AXIS]),
587                                       SCM_UNDEFINED),
588                           out);
589           current = (Offset (x3, y3) + current);
590         }
591       else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("closepath")))
592         {
593           if ((current[X_AXIS] != start[X_AXIS]) || (current[Y_AXIS] != start[Y_AXIS]))
594             {
595               out = scm_cons (scm_list_4 (scm_from_double (current[X_AXIS]),
596                                           scm_from_double (current[Y_AXIS]),
597                                           scm_from_double (start[X_AXIS]),
598                                           scm_from_double (start[Y_AXIS])),
599                               out);
600               current = start;
601             }
602           expr = scm_cdr (expr);
603         }
604       else
605         {
606           warning ("Malformed path for path stencil.");
607           return out;
608         }
609       first = false;
610     }
611   return scm_reverse_x (out, SCM_EOL);
612 }
613
614 void
615 internal_make_path_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, 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, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr)
632 {
633   return internal_make_path_boxes (boxes, buildings, trans, scm_cons (scm_car (expr), get_path_list (scm_cdr (expr))), false);
634 }
635
636 void
637 make_polygon_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr)
638 {
639   SCM coords = get_number_list (scm_car (expr));
640   expr = scm_cdr (expr);
641   SCM blot_diameter = scm_car (expr);
642   //////////////////////
643   bool first = true;
644   SCM l = SCM_EOL;
645   for (SCM s = coords; scm_is_pair (s); s = scm_cddr (s))
646     {
647       l = scm_cons (first ? ly_symbol2scm ("moveto") : ly_symbol2scm ("lineto"), l);
648       l = scm_cons (scm_car (s), l);
649       l = scm_cons (scm_cadr (s), l);
650       first = false;
651     }
652   l = scm_cons (ly_symbol2scm ("closepath"), l);
653   internal_make_path_boxes (boxes, buildings, trans, scm_cons (blot_diameter, scm_reverse_x (l, SCM_EOL)), true);
654 }
655
656 void
657 make_named_glyph_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr)
658 {
659   SCM fm_scm = scm_car (expr);
660   Font_metric *fm = unsmob<Font_metric> (fm_scm);
661   expr = scm_cdr (expr);
662   SCM glyph = scm_car (expr);
663   string glyph_s = ly_scm2string (glyph);
664
665   //////////////////////
666   Open_type_font *open_fm
667     = dynamic_cast<Open_type_font *>
668       (dynamic_cast<Modified_font_metric *>(fm)->original_font ());
669   SCM_ASSERT_TYPE (open_fm, fm_scm, SCM_ARG1, __FUNCTION__, "OpenType font");
670
671   size_t gidx = open_fm->name_to_index (glyph_s);
672   // Bbox is the best approximation of the width based on how it would be
673   // calculated in open-type-font.cc if it were based on real extents
674   Box bbox = open_fm->get_unscaled_indexed_char_dimensions (gidx);
675   bbox.scale (dynamic_cast<Modified_font_metric *>(fm)->get_magnification () * open_fm->design_size () / open_fm->get_units_per_EM ());
676   // Real bbox is the real bbox of the object
677   Box real_bbox = open_fm->get_glyph_outline_bbox (gidx);
678
679   Real scale = bbox[X_AXIS].length () / real_bbox[X_AXIS].length ();
680
681   pango_matrix_scale (&trans, scale, scale);
682
683   SCM outline = open_fm->get_glyph_outline (gidx);
684   //////////////////////
685   for (SCM s = outline;
686        scm_is_pair (s);
687        s = scm_cdr (s))
688     {
689       scm_to_int (scm_length (scm_car (s))) == 4
690       ? make_draw_line_boxes (boxes, buildings, trans, scm_cons (scm_from_double (0), scm_car (s)), false)
691       : make_draw_bezier_boxes (boxes, buildings, trans, scm_cons (scm_from_double (0), scm_car (s)));
692     }
693 }
694
695 void
696 make_glyph_string_boxes (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr)
697 {
698   SCM fm_scm = scm_car (expr);
699   Font_metric *fm = unsmob<Font_metric> (fm_scm);
700   expr = scm_cdr (expr);
701   expr = scm_cdr (expr); // font-name
702   expr = scm_cdr (expr); // size
703   expr = scm_cdr (expr); // cid?
704   SCM whxy = scm_cadar (expr);
705   vector<Real> widths;
706   vector<Interval> heights;
707   vector<Real> xos;
708   vector<Real> yos;
709   vector<string> char_ids;
710   //////////////////////
711   Pango_font *pango_fm = dynamic_cast<Pango_font *> (fm);
712   SCM_ASSERT_TYPE (pango_fm, fm_scm, SCM_ARG1, __FUNCTION__, "Pango font");
713
714   for (SCM s = whxy; scm_is_pair (s); s = scm_cdr (s))
715     {
716       SCM now = scm_car (s);
717       widths.push_back (robust_scm2double (scm_car (now), 0.0));
718       now = scm_cdr (now);
719       heights.push_back (robust_scm2interval (scm_car (now), Interval (0, 0)));
720       now = scm_cdr (now);
721       xos.push_back (robust_scm2double (scm_car (now), 0.0));
722       now = scm_cdr (now);
723       yos.push_back (robust_scm2double (scm_car (now), 0.0));
724       now = scm_cdr (now);
725       char_ids.push_back (robust_scm2string (scm_car (now), ""));
726     }
727   Real cumulative_x = 0.0;
728   for (vsize i = 0; i < widths.size (); i++)
729     {
730       PangoMatrix transcopy (trans);
731       Offset pt0 (cumulative_x + xos[i], heights[i][DOWN] + yos[i]);
732       Offset pt1 (cumulative_x + widths[i] + xos[i], heights[i][UP] + yos[i]);
733       cumulative_x += widths[i];
734
735       Box kerned_bbox;
736       kerned_bbox.add_point (pt0);
737       kerned_bbox.add_point (pt1);
738       size_t gidx = pango_fm->name_to_index (char_ids[i]);
739       Box real_bbox = pango_fm->get_scaled_indexed_char_dimensions (gidx);
740       Box bbox = pango_fm->get_unscaled_indexed_char_dimensions (gidx);
741       SCM outline = pango_fm->get_glyph_outline (gidx);
742
743       // scales may have rounding error but should be close
744       Real xlen = real_bbox[X_AXIS].length () / bbox[X_AXIS].length ();
745       Real ylen = real_bbox[Y_AXIS].length () / bbox[Y_AXIS].length ();
746
747       /*
748         TODO:
749
750         The value will be nan for whitespace, in which case we just want
751         filler, so the kerned bbox is ok.
752
753         However, if the value is inf, this likely means that LilyPond is
754         using a font that is currently difficult to get the measurements
755         from the Pango_font.  This should eventually be fixed.  The solution
756         for now is just to use the bounding box.
757       */
758       if (isnan (xlen) || isnan (ylen) || isinf (xlen) || isinf (ylen))
759         outline = box_to_scheme_lines (kerned_bbox);
760       else
761         {
762           assert (abs (xlen - ylen) < 10e-3);
763
764           Real scale_factor = max (xlen, ylen);
765           // the three operations below move the stencil from its original coordinates to current coordinates
766           pango_matrix_translate (&transcopy, kerned_bbox[X_AXIS][LEFT], kerned_bbox[Y_AXIS][DOWN] - real_bbox[Y_AXIS][DOWN]);
767           pango_matrix_translate (&transcopy, real_bbox[X_AXIS][LEFT], real_bbox[Y_AXIS][DOWN]);
768           pango_matrix_scale (&transcopy, scale_factor, scale_factor);
769           pango_matrix_translate (&transcopy, -bbox[X_AXIS][LEFT], -bbox[Y_AXIS][DOWN]);
770         }
771       //////////////////////
772       for (SCM s = outline;
773            scm_is_pair (s);
774            s = scm_cdr (s))
775         {
776           scm_to_int (scm_length (scm_car (s))) == 4
777           ? make_draw_line_boxes (boxes, buildings, transcopy, scm_cons (scm_from_double (0), scm_car (s)), false)
778           : make_draw_bezier_boxes (boxes, buildings, transcopy, scm_cons (scm_from_double (0), scm_car (s)));
779         }
780     }
781 }
782
783 /*
784   receives a stencil expression and a transform matrix
785   depending on the stencil name, dispatches it to the appropriate function
786 */
787
788 void
789 stencil_dispatcher (vector<Box> &boxes, vector<Drul_array<Offset> > &buildings, PangoMatrix trans, SCM expr)
790 {
791   if (not scm_is_pair (expr))
792     return;
793   if (scm_is_eq (scm_car (expr), ly_symbol2scm ("draw-line")))
794     make_draw_line_boxes (boxes, buildings, trans, scm_cdr (expr), true);
795   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("dashed-line")))
796     {
797       expr = scm_cdr (expr);
798       SCM th = scm_car (expr);
799       expr = scm_cdr (expr);
800       expr = scm_cdr (expr); // on
801       expr = scm_cdr (expr); // off
802       SCM x1 = scm_car (expr);
803       expr = scm_cdr (expr);
804       SCM x2 = scm_car (expr);
805       make_draw_line_boxes (boxes, buildings, trans, scm_list_5 (th, scm_from_double (0.0), scm_from_double (0.0), x1, x2), true);
806     }
807   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("circle")))
808     {
809       expr = scm_cdr (expr);
810       SCM rad = scm_car (expr);
811       expr = scm_cdr (expr);
812       SCM th = scm_car (expr);
813       make_partial_ellipse_boxes (boxes, buildings, trans,
814                                   scm_list_n (rad,
815                                               rad,
816                                               scm_from_double (0.0),
817                                               scm_from_double (360.0),
818                                               th,
819                                               SCM_BOOL_F,
820                                               SCM_BOOL_T,
821                                               SCM_UNDEFINED));
822     }
823   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("ellipse")))
824     {
825       expr = scm_cdr (expr);
826       SCM x_rad = scm_car (expr);
827       expr = scm_cdr (expr);
828       SCM y_rad = scm_car (expr);
829       expr = scm_cdr (expr);
830       SCM th = scm_car (expr);
831       make_partial_ellipse_boxes (boxes, buildings, trans,
832                                   scm_list_n (x_rad,
833                                               y_rad,
834                                               scm_from_double (0.0),
835                                               scm_from_double (360.0),
836                                               th,
837                                               SCM_BOOL_F,
838                                               SCM_BOOL_T,
839                                               SCM_UNDEFINED));
840     }
841   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("partial-ellipse")))
842     make_partial_ellipse_boxes (boxes, buildings, trans, scm_cdr (expr));
843   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("round-filled-box")))
844     make_round_filled_box_boxes (boxes, trans, scm_cdr (expr));
845   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("named-glyph")))
846     make_named_glyph_boxes (boxes, buildings, trans, scm_cdr (expr));
847   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("polygon")))
848     make_polygon_boxes (boxes, buildings, trans, scm_cdr (expr));
849   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("path")))
850     make_path_boxes (boxes, buildings, trans, scm_cdr (expr));
851   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("glyph-string")))
852     make_glyph_string_boxes (boxes, buildings, trans, scm_cdr (expr));
853   else
854     {
855 #if 0
856       warning ("Stencil expression not supported by the veritcal skylines.");
857 #endif
858       /*
859         We don't issue a warning here, as we assume that stencil-expression.cc
860         is doing stencil-checking correctly.
861       */
862     }
863 }
864
865 /*
866   traverses a stencil expression, returning a vector of Transform_matrix_and_expression
867   the struct Transform_matrix_and_expression contains two members,
868   a Transform_matrix that indicates where to move a stencil and the stencil expression
869   to show how to construct the stencil
870 */
871 vector<Transform_matrix_and_expression>
872 stencil_traverser (PangoMatrix trans, SCM expr)
873 {
874   if (scm_is_null (expr))
875     return vector<Transform_matrix_and_expression> ();
876   else if (scm_is_eq (expr, ly_string2scm ("")))
877     return vector<Transform_matrix_and_expression> ();
878   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("combine-stencil")))
879     {
880       vector<Transform_matrix_and_expression> out;
881       for (SCM s = scm_cdr (expr); scm_is_pair (s); s = scm_cdr (s))
882         {
883           vector<Transform_matrix_and_expression> res = stencil_traverser (trans, scm_car (s));
884           out.insert (out.end (), res.begin (), res.end ());
885         }
886       return out;
887     }
888   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("footnote")))
889     return vector<Transform_matrix_and_expression> ();
890   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("translate-stencil")))
891     {
892       Real x = robust_scm2double (scm_caadr (expr), 0.0);
893       Real y = robust_scm2double (scm_cdadr (expr), 0.0);
894       pango_matrix_translate (&trans, x, y);
895       return stencil_traverser (trans, scm_caddr (expr));
896     }
897   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("scale-stencil")))
898     {
899       Real x = robust_scm2double (scm_caadr (expr), 0.0);
900       Real y = robust_scm2double (scm_cadadr (expr), 0.0);
901       pango_matrix_scale (&trans, x, y);
902       return stencil_traverser (trans, scm_caddr (expr));
903     }
904   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("rotate-stencil")))
905     {
906       Real ang = robust_scm2double (scm_caadr (expr), 0.0);
907       Real x = robust_scm2double (scm_car (scm_cadadr (expr)), 0.0);
908       Real y = robust_scm2double (scm_cdr (scm_cadadr (expr)), 0.0);
909       pango_matrix_translate (&trans, x, y);
910       pango_matrix_rotate (&trans, -ang);
911       pango_matrix_translate (&trans, -x, -y);
912       return stencil_traverser (trans, scm_caddr (expr));
913     }
914   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("delay-stencil-evaluation")))
915     // should not use the place-holder text, but no need for the warning below
916     return vector<Transform_matrix_and_expression> ();
917   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("grob-cause")))
918     return stencil_traverser (trans, scm_caddr (expr));
919   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("color")))
920     return stencil_traverser (trans, scm_caddr (expr));
921   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("transparent-stencil")))
922     return stencil_traverser (trans, scm_cadr (expr));
923   else if (scm_is_eq (scm_car (expr), ly_symbol2scm ("id")))
924     return stencil_traverser (trans, scm_caddr (expr));
925   else
926     {
927       vector<Transform_matrix_and_expression> out;
928       out.push_back (Transform_matrix_and_expression (trans, expr));
929       return out;
930     }
931   warning ("Stencil expression not supported by the veritcal skylines.");
932   return vector<Transform_matrix_and_expression> ();
933 }
934
935 SCM
936 Grob::maybe_pure_internal_simple_skylines_from_extents (Grob *me, Axis a, bool pure, int beg, int end, bool ignore_x, bool ignore_y)
937 {
938   vector<Box> boxes;
939   // we don't know how far spanners stretch along the X axis before
940   // line breaking. better have them take up the whole thing
941   Interval xex = ignore_x
942                  ? Interval (-infinity_f, infinity_f)
943                  : me->extent (me, X_AXIS);
944
945   // If we're looking at the x exent of a cross staff grob, it could be
946   // very early on in the computation process.  We won't know its height
947   // until way later, so we give a brute force approximation.
948   Interval yex = ignore_y
949                  ? Interval (-infinity_f, infinity_f)
950                  : me->maybe_pure_extent (me, Y_AXIS, pure, beg, end);
951
952   if (xex.is_empty () || yex.is_empty ())
953     return Skyline_pair ().smobbed_copy ();
954
955   boxes.push_back (Box (xex, yex));
956   return Skyline_pair (boxes, a).smobbed_copy ();
957 }
958
959 MAKE_SCHEME_CALLBACK (Grob, pure_simple_vertical_skylines_from_extents, 3);
960 SCM
961 Grob::pure_simple_vertical_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
962 {
963   Grob *me = unsmob<Grob> (smob);
964   int beg = robust_scm2int (begscm, 0);
965   int end = robust_scm2int (endscm, INT_MAX);
966   // We cannot measure the widths before line breaking,
967   // so we assume that the width is infinite: pass ignore_x=true
968   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, true, beg, end, true, false);
969 }
970
971 MAKE_SCHEME_CALLBACK (Grob, simple_vertical_skylines_from_extents, 1);
972 SCM
973 Grob::simple_vertical_skylines_from_extents (SCM smob)
974 {
975   Grob *me = unsmob<Grob> (smob);
976   return maybe_pure_internal_simple_skylines_from_extents (me, X_AXIS, false, 0, 0, false, false);
977 }
978
979 MAKE_SCHEME_CALLBACK (Grob, pure_simple_horizontal_skylines_from_extents, 3);
980 SCM
981 Grob::pure_simple_horizontal_skylines_from_extents (SCM smob, SCM begscm, SCM endscm)
982 {
983   Grob *me = unsmob<Grob> (smob);
984   int beg = robust_scm2int (begscm, 0);
985   int end = robust_scm2int (endscm, INT_MAX);
986   // If the grob is cross staff, we cannot measure its Y-extent before
987   // wayyyy downstream (after spacing of axis groups is done).
988   // Thus, we assume that the Y extent is infinite for cross staff grobs.
989   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, true, beg, end, false, to_boolean (me->get_property ("cross-staff")));
990 }
991
992 MAKE_SCHEME_CALLBACK (Grob, simple_horizontal_skylines_from_extents, 1);
993 SCM
994 Grob::simple_horizontal_skylines_from_extents (SCM smob)
995 {
996   Grob *me = unsmob<Grob> (smob);
997   // See comment in function above.
998   return maybe_pure_internal_simple_skylines_from_extents (me, Y_AXIS, false, 0, 0, false, to_boolean (me->get_property ("cross-staff")));
999 }
1000
1001 SCM
1002 Stencil::skylines_from_stencil (SCM sten, Real pad, Axis a)
1003 {
1004   Stencil *s = unsmob<Stencil> (sten);
1005   if (!s)
1006     return Skyline_pair ().smobbed_copy ();
1007
1008   vector<Transform_matrix_and_expression> data
1009     = stencil_traverser (make_transform_matrix (1.0, 0.0, 0.0, 1.0, 0.0, 0.0),
1010                          s->expr ());
1011   vector<Box> boxes;
1012   vector<Drul_array<Offset> > buildings;
1013   for (vsize i = 0; i < data.size (); i++)
1014     stencil_dispatcher (boxes, buildings, data[i].tm_, data[i].expr_);
1015
1016   // we use the bounding box if there are no boxes
1017   if (!boxes.size () && !buildings.size ())
1018     boxes.push_back (Box (s->extent (X_AXIS), s->extent (Y_AXIS)));
1019
1020   Skyline_pair out (boxes, a);
1021   out.merge (Skyline_pair (buildings, a));
1022
1023   for (DOWN_and_UP (d))
1024     out[d] = out[d].padded (pad);
1025
1026   return out.smobbed_copy ();
1027 }
1028
1029 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_stencil, 1);
1030 SCM
1031 Grob::vertical_skylines_from_stencil (SCM smob)
1032 {
1033   Grob *me = unsmob<Grob> (smob);
1034
1035   Real pad = robust_scm2double (me->get_property ("skyline-horizontal-padding"), 0.0);
1036   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, X_AXIS);
1037
1038   return out;
1039 }
1040
1041 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_stencil, 1);
1042 SCM
1043 Grob::horizontal_skylines_from_stencil (SCM smob)
1044 {
1045   Grob *me = unsmob<Grob> (smob);
1046
1047   Real pad = robust_scm2double (me->get_property ("skyline-vertical-padding"), 0.0);
1048   SCM out = Stencil::skylines_from_stencil (me->get_property ("stencil"), pad, Y_AXIS);
1049
1050   return out;
1051 }
1052
1053 SCM
1054 Grob::internal_skylines_from_element_stencils (Grob *me, Axis a, bool pure, int beg, int end)
1055 {
1056
1057   extract_grob_set (me, "elements", elts);
1058   vector<Real> x_pos;
1059   vector<Real> y_pos;
1060   Grob *x_common = common_refpoint_of_array (elts, me, X_AXIS);
1061   Grob *y_common = common_refpoint_of_array (elts, me, Y_AXIS);
1062   for (vsize i = 0; i < elts.size (); i++)
1063     {
1064       x_pos.push_back (elts[i]->relative_coordinate (x_common, X_AXIS));
1065       y_pos.push_back (elts[i]->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end));
1066     }
1067   Real my_x = me->relative_coordinate (x_common, X_AXIS);
1068   Real my_y = me->maybe_pure_coordinate (y_common, Y_AXIS, pure, beg, end);
1069
1070   Skyline_pair res;
1071   for (vsize i = 0; i < elts.size (); i++)
1072     {
1073       Skyline_pair *skyp = unsmob<Skyline_pair> (elts[i]->get_maybe_pure_property (a == X_AXIS ? "vertical-skylines" : "horizontal-skylines", pure, beg, end));
1074       if (skyp)
1075         {
1076           /*
1077             Here, copying is essential.  Otherwise, the skyline pair will
1078             get doubly shifted!
1079           */
1080           /*
1081             It took Mike about 6 months of his life to add the `else' clause
1082             below.  For horizontal skylines, the raise and shift calls need
1083             to be reversed.  This is what was causing the problems in the
1084             shifting with all of the tests. RIP 6 months!
1085           */
1086           Skyline_pair copy = Skyline_pair (*skyp);
1087           if (a == X_AXIS)
1088             {
1089               copy.shift (x_pos[i] - my_x);
1090               copy.raise (y_pos[i] - my_y);
1091             }
1092           else
1093             {
1094               copy.raise (x_pos[i] - my_x);
1095               copy.shift (y_pos[i] - my_y);
1096             }
1097           res.merge (copy);
1098         }
1099     }
1100   return res.smobbed_copy ();
1101 }
1102
1103 MAKE_SCHEME_CALLBACK (Grob, vertical_skylines_from_element_stencils, 1);
1104 SCM
1105 Grob::vertical_skylines_from_element_stencils (SCM smob)
1106 {
1107   Grob *me = unsmob<Grob> (smob);
1108   return internal_skylines_from_element_stencils (me, X_AXIS, false, 0, INT_MAX);
1109 }
1110
1111 MAKE_SCHEME_CALLBACK (Grob, horizontal_skylines_from_element_stencils, 1);
1112 SCM
1113 Grob::horizontal_skylines_from_element_stencils (SCM smob)
1114 {
1115   Grob *me = unsmob<Grob> (smob);
1116   return internal_skylines_from_element_stencils (me, Y_AXIS, false, 0, INT_MAX);
1117 }
1118
1119 MAKE_SCHEME_CALLBACK (Grob, pure_vertical_skylines_from_element_stencils, 3);
1120 SCM
1121 Grob::pure_vertical_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1122 {
1123   Grob *me = unsmob<Grob> (smob);
1124   int beg = robust_scm2int (beg_scm, 0);
1125   int end = robust_scm2int (end_scm, 0);
1126   return internal_skylines_from_element_stencils (me, X_AXIS, true, beg, end);
1127 }
1128
1129 MAKE_SCHEME_CALLBACK (Grob, pure_horizontal_skylines_from_element_stencils, 3);
1130 SCM
1131 Grob::pure_horizontal_skylines_from_element_stencils (SCM smob, SCM beg_scm, SCM end_scm)
1132 {
1133   Grob *me = unsmob<Grob> (smob);
1134   int beg = robust_scm2int (beg_scm, 0);
1135   int end = robust_scm2int (end_scm, 0);
1136   return internal_skylines_from_element_stencils (me, Y_AXIS, true, beg, end);
1137 }