2 lookup.cc -- implement simple Lookup methods.
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
8 Jan Nieuwenhuizen <janneke@gnu.org>
17 #include "line-interface.hh"
19 #include "dimensions.hh"
21 #include "file-path.hh"
23 #include "lily-guile.hh"
26 Lookup::dot (Offset p
, Real radius
)
28 SCM at
= (scm_list_n (ly_symbol2scm ("dot"),
29 scm_from_double (p
[X_AXIS
]),
30 scm_from_double (p
[Y_AXIS
]),
31 scm_from_double (radius
),
34 box
.add_point (p
- Offset (radius
, radius
));
35 box
.add_point (p
+ Offset (radius
, radius
));
36 return Stencil (box
, at
);
40 Lookup::beam (Real slope
, Real width
, Real thick
, Real blot
)
46 p
= Offset (0, thick
/ 2);
48 p
+= Offset (1, -1) * (blot
/ 2);
52 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
53 scm_cons (scm_from_double (p
[Y_AXIS
]),
56 p
= Offset (0, -thick
/ 2);
58 p
+= Offset (1, 1) * (blot
/ 2);
60 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
61 scm_cons (scm_from_double (p
[Y_AXIS
]),
64 p
= Offset (width
, width
* slope
- thick
/ 2);
66 p
+= Offset (-1, 1) * (blot
/ 2);
68 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
69 scm_cons (scm_from_double (p
[Y_AXIS
]),
72 p
= Offset (width
, width
* slope
+ thick
/ 2);
74 p
+= Offset (-1, -1) * (blot
/ 2);
76 points
= scm_cons (scm_from_double (p
[X_AXIS
]),
77 scm_cons (scm_from_double (p
[Y_AXIS
]),
80 SCM expr
= scm_list_n (ly_symbol2scm ("polygon"),
81 ly_quote_scm (points
),
82 scm_from_double (blot
),
86 return Stencil (b
, expr
);
90 Lookup::rotated_box (Real slope
, Real width
, Real thick
, Real blot
)
93 Offset
rot (1, slope
);
97 rot
/= sqrt (1 + slope
*slope
);
98 pts
.push_back (Offset (0, -thick
/ 2) * rot
);
99 pts
.push_back (Offset (width
, -thick
/ 2) * rot
);
100 pts
.push_back (Offset (width
, thick
/ 2) * rot
);
101 pts
.push_back (Offset (0, thick
/ 2) * rot
);
102 return Lookup::round_filled_polygon (pts
, blot
);
106 Lookup::horizontal_line (Interval w
, Real th
)
108 SCM at
= scm_list_n (ly_symbol2scm ("draw-line"),
109 scm_from_double (th
),
110 scm_from_double (w
[LEFT
]),
112 scm_from_double (w
[RIGHT
]),
118 box
[Y_AXIS
] = Interval (-th
/ 2, th
/ 2);
120 return Stencil (box
, at
);
124 Lookup::blank (Box b
)
126 return Stencil (b
, scm_from_locale_string (""));
130 Lookup::filled_box (Box b
)
132 return round_filled_box (b
, 0.0);
138 * __________________________________
143 * |\ _ _ / v \ _ _ /| |
146 * | <------>| | extent
147 * | blot | | (Y_AXIS)
155 * x\_____/______________\_____/|_____v
159 * |<-------------------------->|
160 * Box extent (X_AXIS)
163 Lookup::round_filled_box (Box b
, Real blotdiameter
)
165 if (b
.x ().length () < blotdiameter
)
166 blotdiameter
= b
.x ().length ();
167 if (b
.y ().length () < blotdiameter
)
168 blotdiameter
= b
.y ().length ();
170 SCM at
= (scm_list_n (ly_symbol2scm ("round-filled-box"),
171 scm_from_double (-b
[X_AXIS
][LEFT
]),
172 scm_from_double (b
[X_AXIS
][RIGHT
]),
173 scm_from_double (-b
[Y_AXIS
][DOWN
]),
174 scm_from_double (b
[Y_AXIS
][UP
]),
175 scm_from_double (blotdiameter
),
178 return Stencil (b
, at
);
182 * Create Stencil that represents a filled polygon with round edges.
186 * (a) Only outer (convex) edges are rounded.
188 * (b) This algorithm works as expected only for polygons whose edges
189 * do not intersect. For example, the polygon ((0, 0), (q, 0), (0,
190 * q), (q, q)) has an intersection at point (q/2, q/2) and therefore
191 * will give a strange result. Even non-adjacent edges that just
192 * touch each other will in general not work as expected for non-null
195 * (c) Given a polygon ((x0, y0), (x1, y1), ... , (x (n-1), y (n-1))),
196 * if there is a natural number k such that blotdiameter is greater
197 * than the maximum of { | (x (k mod n), y (k mod n)) - (x ((k+1) mod n),
198 * y ((k+1) mod n)) |, | (x (k mod n), y (k mod n)) - (x ((k+2) mod n),
199 * y ((k+2) mod n)) |, | (x ((k+1) mod n), y ((k+1) mod n)) - (x ((k+2)
200 * mod n), y ((k+2) mod n)) | }, then the outline of the rounded
201 * polygon will exceed the outline of the core polygon. In other
202 * words: Do not draw rounded polygons that have a leg smaller or
203 * thinner than blotdiameter (or set blotdiameter to a sufficiently
204 * small value -- maybe even 0.0)!
206 * NOTE: Limitations (b) and (c) arise from the fact that round edges
207 * are made by moulding sharp edges to round ones rather than adding
208 * to a core filled polygon. For details of these two different
209 * approaches, see the thread upon the ledger lines patch that started
210 * on March 25, 2002 on the devel mailing list. The below version of
211 * round_filled_polygon () sticks to the moulding model, which the
212 * majority of the list participants finally voted for. This,
213 * however, results in the above limitations and a much increased
214 * complexity of the algorithm, since it has to compute a shrinked
215 * polygon -- which is not trivial define precisely and unambigously.
216 * With the other approach, one simply could move a circle of size
217 * blotdiameter along all edges of the polygon (which is what the
218 * postscript routine in the backend effectively does, but on the
219 * shrinked polygon). --jr
222 Lookup::round_filled_polygon (vector
<Offset
> const &points
,
225 /* TODO: Maybe print a warning if one of the above limitations
226 applies to the given polygon. However, this is quite complicated
229 const Real epsilon
= 0.01;
232 /* remove consecutive duplicate points */
233 for (vsize i
= 0; i
< points
.size (); i
++)
235 int next
= (i
+ 1) % points
.size ();
236 Real d
= (points
[i
] - points
[next
]).length ();
238 programming_error ("Polygon should not have duplicate points");
242 /* special cases: degenerated polygons */
243 if (points
.size () == 0)
245 if (points
.size () == 1)
246 return dot (points
[0], 0.5 * blotdiameter
);
247 if (points
.size () == 2)
248 return Line_interface::make_line (blotdiameter
, points
[0], points
[1]);
250 /* shrink polygon in size by 0.5 * blotdiameter */
251 vector
<Offset
> shrunk_points
;
252 shrunk_points
.resize (points
.size ());
253 bool ccw
= 1; // true, if three adjacent points are counterclockwise ordered
254 for (vsize i
= 0; i
< points
.size (); i
++)
257 int i1
= (i
+ 1) % points
.size ();
258 int i2
= (i
+ 2) % points
.size ();
259 Offset p0
= points
[i0
];
260 Offset p1
= points
[i1
];
261 Offset p2
= points
[i2
];
262 Offset p10
= p0
- p1
;
263 Offset p12
= p2
- p1
;
264 if (p10
.length () != 0.0)
266 Real phi
= p10
.arg ();
267 // rotate (p2 - p0) by (-phi)
268 Offset q
= complex_multiply (p2
- p0
, complex_exp (Offset (1.0, -phi
)));
272 else if (q
[Y_AXIS
] < 0)
274 else {} // keep ccw unchanged
276 else {} // keep ccw unchanged
277 Offset p10n
= (1.0 / p10
.length ()) * p10
; // normalize length to 1.0
278 Offset p12n
= (1.0 / p12
.length ()) * p12
;
279 Offset p13n
= 0.5 * (p10n
+ p12n
);
280 Offset p14n
= 0.5 * (p10n
- p12n
);
282 Real d
= p13n
.length () * p14n
.length (); // distance p3n to line (p1..p0)
284 // special case: p0, p1, p2 are on a single line => build
285 // vector orthogonal to (p2-p0) of length 0.5 blotdiameter
287 p13
[X_AXIS
] = p10
[Y_AXIS
];
288 p13
[Y_AXIS
] = -p10
[X_AXIS
];
289 p13
= (0.5 * blotdiameter
/ p13
.length ()) * p13
;
292 p13
= (0.5 * blotdiameter
/ d
) * p13n
;
293 shrunk_points
[i1
] = p1
+ ((ccw
) ? p13
: -p13
);
296 /* build scm expression and bounding box */
297 SCM shrunk_points_scm
= SCM_EOL
;
299 for (vsize i
= 0; i
< shrunk_points
.size (); i
++)
301 SCM x
= scm_from_double (shrunk_points
[i
][X_AXIS
]);
302 SCM y
= scm_from_double (shrunk_points
[i
][Y_AXIS
]);
303 shrunk_points_scm
= scm_cons (x
, scm_cons (y
, shrunk_points_scm
));
304 box
.add_point (points
[i
]);
306 SCM polygon_scm
= scm_list_n (ly_symbol2scm ("polygon"),
307 ly_quote_scm (shrunk_points_scm
),
308 scm_from_double (blotdiameter
),
312 Stencil polygon
= Stencil (box
, polygon_scm
);
313 shrunk_points
.clear ();
321 Lookup::frame (Box b
, Real thick
, Real blot
)
325 for (Axis a
= X_AXIS
; a
< NO_AXES
; a
= Axis (a
+ 1))
327 Axis o
= Axis ((a
+ 1)%NO_AXES
);
331 edges
[a
] = b
[a
][d
] + 0.5 * thick
* Interval (-1, 1);
332 edges
[o
][DOWN
] = b
[o
][DOWN
] - thick
/ 2;
333 edges
[o
][UP
] = b
[o
][UP
] + thick
/ 2;
335 m
.add_stencil (round_filled_box (edges
, blot
));
337 while (flip (&d
) != LEFT
);
343 Make a smooth curve along the points
346 Lookup::slur (Bezier curve
, Real curvethick
, Real linethick
,
349 Stencil return_value
;
352 calculate the offset for the two beziers that make the sandwich
355 Real alpha
= (curve
.control_
[3] - curve
.control_
[0]).arg ();
357 Offset perp
= curvethick
* complex_exp (Offset (0, alpha
+ M_PI
/ 2)) * 0.5;
358 back
.control_
[1] += perp
;
359 back
.control_
[2] += perp
;
361 curve
.control_
[1] -= perp
;
362 curve
.control_
[2] -= perp
;
364 if (!scm_is_pair (dash_details
))
367 return_value
= bezier_sandwich (back
, curve
, linethick
);
371 /* dashed or combination slur */
372 int num_segments
= scm_to_int (scm_length (dash_details
));
373 for (int i
=0; i
<num_segments
; i
++)
375 SCM dash_pattern
= scm_list_ref (dash_details
, scm_from_int (i
));
376 Real t_min
= robust_scm2double (scm_car (dash_pattern
), 0);
377 Real t_max
= robust_scm2double (scm_cadr (dash_pattern
), 1.0);
379 robust_scm2double (scm_caddr (dash_pattern
), 1.0);
381 robust_scm2double (scm_cadddr (dash_pattern
), 0.75);
382 Bezier back_segment
= back
.extract (t_min
, t_max
);
383 Bezier curve_segment
= curve
.extract (t_min
, t_max
);
384 if (dash_fraction
== 1.0)
385 return_value
.add_stencil (bezier_sandwich (back_segment
,
390 Bezier back_dash
, curve_dash
;
391 Real seg_length
= (back_segment
.control_
[3] -
392 back_segment
.control_
[0]).length ();
393 int pattern_count
= seg_length
/ dash_period
;
394 Real pattern_length
= 1.0 / (pattern_count
+ dash_fraction
);
396 for (int p
= 0; p
<= pattern_count
; p
++)
398 start_t
= p
* pattern_length
;
399 end_t
= (p
+ dash_fraction
) * pattern_length
;
401 back_segment
.extract (start_t
, end_t
);
403 curve_segment
.extract (start_t
, end_t
);
404 return_value
.add_stencil (bezier_sandwich (back_dash
,
439 Lookup::bezier_sandwich (Bezier top_curve
, Bezier bottom_curve
, Real thickness
)
442 Need the weird order b.o. the way PS want its arguments
445 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[3]), list
);
446 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[0]), list
);
447 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[1]), list
);
448 list
= scm_cons (ly_offset2scm (bottom_curve
.control_
[2]), list
);
449 list
= scm_cons (ly_offset2scm (top_curve
.control_
[0]), list
);
450 list
= scm_cons (ly_offset2scm (top_curve
.control_
[3]), list
);
451 list
= scm_cons (ly_offset2scm (top_curve
.control_
[2]), list
);
452 list
= scm_cons (ly_offset2scm (top_curve
.control_
[1]), list
);
454 SCM horizontal_bend
= scm_list_n (ly_symbol2scm ("bezier-sandwich"),
456 scm_from_double (thickness
),
459 Interval x_extent
= top_curve
.extent (X_AXIS
);
460 x_extent
.unite (bottom_curve
.extent (X_AXIS
));
461 Interval y_extent
= top_curve
.extent (Y_AXIS
);
462 y_extent
.unite (bottom_curve
.extent (Y_AXIS
));
463 Box
b (x_extent
, y_extent
);
465 b
.widen (0.5 * thickness
, 0.5 * thickness
);
466 return Stencil (b
, horizontal_bend
);
470 Lookup::repeat_slash (Real w
, Real s
, Real t
)
473 vector
<Offset
> points
;
474 Real blotdiameter
= 0.0;
477 Offset
p2 (w
, w
* s
);
479 return Lookup::round_filled_polygon (points
, blotdiameter
);
482 SCM wid
= scm_from_double (w
);
483 SCM sl
= scm_from_double (s
);
484 SCM thick
= scm_from_double (t
);
485 SCM slashnodot
= scm_list_n (ly_symbol2scm ("repeat-slash"),
486 wid
, sl
, thick
, SCM_UNDEFINED
);
488 Box
b (Interval (0, w
+ sqrt (sqr (t
/ s
) + sqr (t
))),
489 Interval (0, w
* s
));
491 return Stencil (b
, slashnodot
); // http://slashnodot.org
495 Lookup::bracket (Axis a
, Interval iv
, Real thick
, Real protrude
, Real blot
)
498 Axis other
= Axis ((a
+ 1)%2);
500 b
[other
] = Interval (-1, 1) * thick
* 0.5;
502 Stencil m
= round_filled_box (b
, blot
);
504 b
[a
] = Interval (iv
[UP
] - thick
, iv
[UP
]);
505 Interval oi
= Interval (-thick
/ 2, thick
/ 2 + fabs (protrude
));
506 oi
*= sign (protrude
);
508 m
.add_stencil (round_filled_box (b
, blot
));
509 b
[a
] = Interval (iv
[DOWN
], iv
[DOWN
] + thick
);
510 m
.add_stencil (round_filled_box (b
, blot
));
516 Lookup::triangle (Interval iv
, Real thick
, Real protrude
)
519 b
[X_AXIS
] = Interval (0, iv
.length ());
520 b
[Y_AXIS
] = Interval (min (0., protrude
), max (0.0, protrude
));
522 vector
<Offset
> points
;
523 points
.push_back (Offset (iv
[LEFT
], 0));
524 points
.push_back (Offset (iv
[RIGHT
], 0));
525 points
.push_back (Offset (iv
.center (), protrude
));
527 return points_to_line_stencil (thick
, points
);
534 Lookup::points_to_line_stencil (Real thick
, vector
<Offset
> const &points
)
537 for (vsize i
= 1; i
< points
.size (); i
++)
539 if (points
[i
-1].is_sane () && points
[i
].is_sane ())
542 = Line_interface::make_line (thick
, points
[i
-1], points
[i
]);
543 ret
.add_stencil (line
);