2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 Jan Nieuwenhuizen <janneke@gnu.org>
8 LilyPond is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 LilyPond is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
28 #include "line-interface.hh"
30 #include "dimensions.hh"
32 #include "file-path.hh"
34 #include "lily-guile.hh"
37 Lookup::dot (Offset p
, Real radius
)
39 SCM at
= (scm_list_n (ly_symbol2scm ("dot"),
40 scm_from_double (p
[X_AXIS
]),
41 scm_from_double (p
[Y_AXIS
]),
42 scm_from_double (radius
),
45 box
.add_point (p
- Offset (radius
, radius
));
46 box
.add_point (p
+ Offset (radius
, radius
));
47 return Stencil (box
, at
);
51 Lookup::beam (Real slope
, Real width
, Real thick
, Real blot
)
57 p
= Offset (0, thick
/ 2);
59 p
+= Offset (1, -1) * (blot
/ 2);
63 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
64 scm_cons (scm_from_double (p
[Y_AXIS
]),
67 p
= Offset (0, -thick
/ 2);
69 p
+= Offset (1, 1) * (blot
/ 2);
71 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
72 scm_cons (scm_from_double (p
[Y_AXIS
]),
75 p
= Offset (width
, width
* slope
- thick
/ 2);
77 p
+= Offset (-1, 1) * (blot
/ 2);
79 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
80 scm_cons (scm_from_double (p
[Y_AXIS
]),
83 p
= Offset (width
, width
* slope
+ thick
/ 2);
85 p
+= Offset (-1, -1) * (blot
/ 2);
87 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
88 scm_cons (scm_from_double (p
[Y_AXIS
]),
91 SCM expr
= scm_list_n (ly_symbol2scm ("polygon"),
92 ly_quote_scm (points
),
93 scm_from_double (blot
),
97 return Stencil (b
, expr
);
101 Lookup::rotated_box (Real slope
, Real width
, Real thick
, Real blot
)
104 Offset
rot (1, slope
);
108 rot
/= sqrt (1 + slope
*slope
);
109 pts
.push_back (Offset (0, -thick
/ 2) * rot
);
110 pts
.push_back (Offset (width
, -thick
/ 2) * rot
);
111 pts
.push_back (Offset (width
, thick
/ 2) * rot
);
112 pts
.push_back (Offset (0, thick
/ 2) * rot
);
113 return Lookup::round_filled_polygon (pts
, blot
);
117 Lookup::horizontal_line (Interval w
, Real th
)
119 SCM at
= scm_list_n (ly_symbol2scm ("draw-line"),
120 scm_from_double (th
),
121 scm_from_double (w
[LEFT
]),
123 scm_from_double (w
[RIGHT
]),
129 box
[Y_AXIS
] = Interval (-th
/ 2, th
/ 2);
131 return Stencil (box
, at
);
135 Lookup::blank (Box b
)
137 return Stencil (b
, scm_from_locale_string (""));
141 Lookup::filled_box (Box b
)
143 return round_filled_box (b
, 0.0);
149 * __________________________________
154 * |\ _ _ / v \ _ _ /| |
157 * | <------>| | extent
158 * | blot | | (Y_AXIS)
166 * x\_____/______________\_____/|_____v
170 * |<-------------------------->|
171 * Box extent (X_AXIS)
174 Lookup::round_filled_box (Box b
, Real blotdiameter
)
176 if (b
.x ().length () < blotdiameter
)
177 blotdiameter
= b
.x ().length ();
178 if (b
.y ().length () < blotdiameter
)
179 blotdiameter
= b
.y ().length ();
181 SCM at
= (scm_list_n (ly_symbol2scm ("round-filled-box"),
182 scm_from_double (-b
[X_AXIS
][LEFT
]),
183 scm_from_double (b
[X_AXIS
][RIGHT
]),
184 scm_from_double (-b
[Y_AXIS
][DOWN
]),
185 scm_from_double (b
[Y_AXIS
][UP
]),
186 scm_from_double (blotdiameter
),
189 return Stencil (b
, at
);
193 * Create Stencil that represents a filled polygon with round edges.
197 * (a) Only outer (convex) edges are rounded.
199 * (b) This algorithm works as expected only for polygons whose edges
200 * do not intersect. For example, the polygon ((0, 0), (q, 0), (0,
201 * q), (q, q)) has an intersection at point (q/2, q/2) and therefore
202 * will give a strange result. Even non-adjacent edges that just
203 * touch each other will in general not work as expected for non-null
206 * (c) Given a polygon ((x0, y0), (x1, y1), ... , (x (n-1), y (n-1))),
207 * if there is a natural number k such that blotdiameter is greater
208 * than the maximum of { | (x (k mod n), y (k mod n)) - (x ((k+1) mod n),
209 * y ((k+1) mod n)) |, | (x (k mod n), y (k mod n)) - (x ((k+2) mod n),
210 * y ((k+2) mod n)) |, | (x ((k+1) mod n), y ((k+1) mod n)) - (x ((k+2)
211 * mod n), y ((k+2) mod n)) | }, then the outline of the rounded
212 * polygon will exceed the outline of the core polygon. In other
213 * words: Do not draw rounded polygons that have a leg smaller or
214 * thinner than blotdiameter (or set blotdiameter to a sufficiently
215 * small value -- maybe even 0.0)!
217 * NOTE: Limitations (b) and (c) arise from the fact that round edges
218 * are made by moulding sharp edges to round ones rather than adding
219 * to a core filled polygon. For details of these two different
220 * approaches, see the thread upon the ledger lines patch that started
221 * on March 25, 2002 on the devel mailing list. The below version of
222 * round_filled_polygon () sticks to the moulding model, which the
223 * majority of the list participants finally voted for. This,
224 * however, results in the above limitations and a much increased
225 * complexity of the algorithm, since it has to compute a shrinked
226 * polygon -- which is not trivial define precisely and unambigously.
227 * With the other approach, one simply could move a circle of size
228 * blotdiameter along all edges of the polygon (which is what the
229 * postscript routine in the backend effectively does, but on the
230 * shrinked polygon). --jr
233 Lookup::round_filled_polygon (vector
<Offset
> const &points
,
236 /* TODO: Maybe print a warning if one of the above limitations
237 applies to the given polygon. However, this is quite complicated
240 const Real epsilon
= 0.01;
243 /* remove consecutive duplicate points */
244 for (vsize i
= 0; i
< points
.size (); i
++)
246 int next
= (i
+ 1) % points
.size ();
247 Real d
= (points
[i
] - points
[next
]).length ();
249 programming_error ("Polygon should not have duplicate points");
253 /* special cases: degenerated polygons */
254 if (points
.size () == 0)
256 if (points
.size () == 1)
257 return dot (points
[0], 0.5 * blotdiameter
);
258 if (points
.size () == 2)
259 return Line_interface::make_line (blotdiameter
, points
[0], points
[1]);
261 /* shrink polygon in size by 0.5 * blotdiameter */
262 vector
<Offset
> shrunk_points
;
263 shrunk_points
.resize (points
.size ());
264 bool ccw
= 1; // true, if three adjacent points are counterclockwise ordered
265 for (vsize i
= 0; i
< points
.size (); i
++)
268 int i1
= (i
+ 1) % points
.size ();
269 int i2
= (i
+ 2) % points
.size ();
270 Offset p0
= points
[i0
];
271 Offset p1
= points
[i1
];
272 Offset p2
= points
[i2
];
273 Offset p10
= p0
- p1
;
274 Offset p12
= p2
- p1
;
275 if (p10
.length () != 0.0)
277 Real phi
= p10
.arg ();
278 // rotate (p2 - p0) by (-phi)
279 Offset q
= complex_multiply (p2
- p0
, complex_exp (Offset (1.0, -phi
)));
283 else if (q
[Y_AXIS
] < 0)
285 else {} // keep ccw unchanged
287 else {} // keep ccw unchanged
288 Offset p10n
= (1.0 / p10
.length ()) * p10
; // normalize length to 1.0
289 Offset p12n
= (1.0 / p12
.length ()) * p12
;
290 Offset p13n
= 0.5 * (p10n
+ p12n
);
291 Offset p14n
= 0.5 * (p10n
- p12n
);
293 Real d
= p13n
.length () * p14n
.length (); // distance p3n to line (p1..p0)
295 // special case: p0, p1, p2 are on a single line => build
296 // vector orthogonal to (p2-p0) of length 0.5 blotdiameter
298 p13
[X_AXIS
] = p10
[Y_AXIS
];
299 p13
[Y_AXIS
] = -p10
[X_AXIS
];
300 p13
= (0.5 * blotdiameter
/ p13
.length ()) * p13
;
303 p13
= (0.5 * blotdiameter
/ d
) * p13n
;
304 shrunk_points
[i1
] = p1
+ ((ccw
) ? p13
: -p13
);
307 /* build scm expression and bounding box */
308 SCM shrunk_points_scm
= SCM_EOL
;
310 for (vsize i
= 0; i
< shrunk_points
.size (); i
++)
312 SCM x
= scm_from_double (shrunk_points
[i
][X_AXIS
]);
313 SCM y
= scm_from_double (shrunk_points
[i
][Y_AXIS
]);
314 shrunk_points_scm
= scm_cons (x
, scm_cons (y
, shrunk_points_scm
));
315 box
.add_point (points
[i
]);
317 SCM polygon_scm
= scm_list_n (ly_symbol2scm ("polygon"),
318 ly_quote_scm (shrunk_points_scm
),
319 scm_from_double (blotdiameter
),
323 Stencil polygon
= Stencil (box
, polygon_scm
);
324 shrunk_points
.clear ();
332 Lookup::frame (Box b
, Real thick
, Real blot
)
336 for (Axis a
= X_AXIS
; a
< NO_AXES
; a
= Axis (a
+ 1))
338 Axis o
= Axis ((a
+ 1)%NO_AXES
);
342 edges
[a
] = b
[a
][d
] + 0.5 * thick
* Interval (-1, 1);
343 edges
[o
][DOWN
] = b
[o
][DOWN
] - thick
/ 2;
344 edges
[o
][UP
] = b
[o
][UP
] + thick
/ 2;
346 m
.add_stencil (round_filled_box (edges
, blot
));
348 while (flip (&d
) != LEFT
);
354 Make a smooth curve along the points
357 Lookup::slur (Bezier curve
, Real curvethick
, Real linethick
,
360 Stencil return_value
;
363 calculate the offset for the two beziers that make the sandwich
366 Real alpha
= (curve
.control_
[3] - curve
.control_
[0]).arg ();
368 Offset perp
= curvethick
* complex_exp (Offset (0, alpha
+ M_PI
/ 2)) * 0.5;
369 back
.control_
[1] += perp
;
370 back
.control_
[2] += perp
;
372 curve
.control_
[1] -= perp
;
373 curve
.control_
[2] -= perp
;
375 if (!scm_is_pair (dash_details
))
378 return_value
= bezier_sandwich (back
, curve
, linethick
);
382 /* dashed or combination slur */
383 int num_segments
= scm_to_int (scm_length (dash_details
));
384 for (int i
=0; i
<num_segments
; i
++)
386 SCM dash_pattern
= scm_list_ref (dash_details
, scm_from_int (i
));
387 Real t_min
= robust_scm2double (scm_car (dash_pattern
), 0);
388 Real t_max
= robust_scm2double (scm_cadr (dash_pattern
), 1.0);
390 robust_scm2double (scm_caddr (dash_pattern
), 1.0);
392 robust_scm2double (scm_cadddr (dash_pattern
), 0.75);
393 Bezier back_segment
= back
.extract (t_min
, t_max
);
394 Bezier curve_segment
= curve
.extract (t_min
, t_max
);
395 if (dash_fraction
== 1.0)
396 return_value
.add_stencil (bezier_sandwich (back_segment
,
401 Bezier back_dash
, curve_dash
;
402 Real seg_length
= (back_segment
.control_
[3] -
403 back_segment
.control_
[0]).length ();
404 int pattern_count
= (int) (seg_length
/ dash_period
);
405 Real pattern_length
= 1.0 / (pattern_count
+ dash_fraction
);
407 for (int p
= 0; p
<= pattern_count
; p
++)
409 start_t
= p
* pattern_length
;
410 end_t
= (p
+ dash_fraction
) * pattern_length
;
412 back_segment
.extract (start_t
, end_t
);
414 curve_segment
.extract (start_t
, end_t
);
415 return_value
.add_stencil (bezier_sandwich (back_dash
,
450 Lookup::bezier_sandwich (Bezier top_curve
, Bezier bottom_curve
, Real thickness
)
453 Need the weird order b.o. the way PS want its arguments
456 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[3]), list
);
457 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[0]), list
);
458 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[1]), list
);
459 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[2]), list
);
460 list
= scm_cons (ly_offset2scm (top_curve
.control_
[0]), list
);
461 list
= scm_cons (ly_offset2scm (top_curve
.control_
[3]), list
);
462 list
= scm_cons (ly_offset2scm (top_curve
.control_
[2]), list
);
463 list
= scm_cons (ly_offset2scm (top_curve
.control_
[1]), list
);
465 SCM horizontal_bend
= scm_list_n (ly_symbol2scm ("bezier-sandwich"),
467 scm_from_double (thickness
),
470 Interval x_extent
= top_curve
.extent (X_AXIS
);
471 x_extent
.unite (bottom_curve
.extent (X_AXIS
));
472 Interval y_extent
= top_curve
.extent (Y_AXIS
);
473 y_extent
.unite (bottom_curve
.extent (Y_AXIS
));
474 Box
b (x_extent
, y_extent
);
476 b
.widen (0.5 * thickness
, 0.5 * thickness
);
477 return Stencil (b
, horizontal_bend
);
481 Lookup::repeat_slash (Real w
, Real s
, Real t
)
484 vector
<Offset
> points
;
485 Real blotdiameter
= 0.0;
488 Offset
p2 (w
, w
* s
);
490 return Lookup::round_filled_polygon (points
, blotdiameter
);
493 SCM wid
= scm_from_double (w
);
494 SCM sl
= scm_from_double (s
);
495 SCM thick
= scm_from_double (t
);
496 SCM slashnodot
= scm_list_n (ly_symbol2scm ("repeat-slash"),
497 wid
, sl
, thick
, SCM_UNDEFINED
);
499 Box
b (Interval (0, w
+ sqrt (sqr (t
/ s
) + sqr (t
))),
500 Interval (0, w
* s
));
502 return Stencil (b
, slashnodot
); // http://slashnodot.org
506 Lookup::bracket (Axis a
, Interval iv
, Real thick
, Real protrude
, Real blot
)
509 Axis other
= Axis ((a
+ 1)%2);
511 b
[other
] = Interval (-1, 1) * thick
* 0.5;
513 Stencil m
= round_filled_box (b
, blot
);
515 b
[a
] = Interval (iv
[UP
] - thick
, iv
[UP
]);
516 Interval oi
= Interval (-thick
/ 2, thick
/ 2 + fabs (protrude
));
517 oi
*= sign (protrude
);
519 m
.add_stencil (round_filled_box (b
, blot
));
520 b
[a
] = Interval (iv
[DOWN
], iv
[DOWN
] + thick
);
521 m
.add_stencil (round_filled_box (b
, blot
));
527 Lookup::triangle (Interval iv
, Real thick
, Real protrude
)
530 b
[X_AXIS
] = Interval (0, iv
.length ());
531 b
[Y_AXIS
] = Interval (min (0., protrude
), max (0.0, protrude
));
533 vector
<Offset
> points
;
534 points
.push_back (Offset (iv
[LEFT
], 0));
535 points
.push_back (Offset (iv
[RIGHT
], 0));
536 points
.push_back (Offset (iv
.center (), protrude
));
537 points
.push_back (Offset (iv
[LEFT
], 0)); // close triangle
539 return points_to_line_stencil (thick
, points
);
546 Lookup::points_to_line_stencil (Real thick
, vector
<Offset
> const &points
)
549 for (vsize i
= 1; i
< points
.size (); i
++)
551 if (points
[i
-1].is_sane () && points
[i
].is_sane ())
554 = Line_interface::make_line (thick
, points
[i
-1], points
[i
]);
555 ret
.add_stencil (line
);