]> git.donarmstrong.com Git - lilypond.git/blob - lily/lookup.cc
* The grand 2005-2006 replace.
[lilypond.git] / lily / lookup.cc
1 /*
2   lookup.cc -- implement simple Lookup methods.
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
7
8   Jan Nieuwenhuizen <janneke@gnu.org>
9 */
10
11 #include "lookup.hh"
12
13 #include <cmath>
14 #include <cctype>
15 using namespace std;
16
17 #include "line-interface.hh"
18 #include "warn.hh"
19 #include "dimensions.hh"
20 #include "bezier.hh"
21 #include "string-convert.hh"
22 #include "file-path.hh"
23 #include "main.hh"
24 #include "lily-guile.hh"
25 #include "font-metric.hh"
26
27 Stencil
28 Lookup::dot (Offset p, Real radius)
29 {
30   SCM at = (scm_list_n (ly_symbol2scm ("dot"),
31                         scm_from_double (p[X_AXIS]),
32                         scm_from_double (p[Y_AXIS]),
33                         scm_from_double (radius),
34                         SCM_UNDEFINED));
35   Box box;
36   box.add_point (p - Offset (radius, radius));
37   box.add_point (p + Offset (radius, radius));
38   return Stencil (box, at);
39 }
40
41 Stencil
42 Lookup::beam (Real slope, Real width, Real thick, Real blot)
43 {
44   Box b;
45
46   Offset p;
47
48   p = Offset (0, thick / 2);
49   b.add_point (p);
50   p += Offset (1, -1) * (blot / 2);
51
52   SCM points = SCM_EOL;
53
54   points = scm_cons (scm_from_double (p[X_AXIS]),
55                      scm_cons (scm_from_double (p[Y_AXIS]),
56                                points));
57
58   p = Offset (0, -thick / 2);
59   b.add_point (p);
60   p += Offset (1, 1) * (blot / 2);
61
62   points = scm_cons (scm_from_double (p[X_AXIS]),
63                      scm_cons (scm_from_double (p[Y_AXIS]),
64                                points));
65
66   p = Offset (width, width * slope - thick / 2);
67   b.add_point (p);
68   p += Offset (-1, 1) * (blot / 2);
69
70   points = scm_cons (scm_from_double (p[X_AXIS]),
71                      scm_cons (scm_from_double (p[Y_AXIS]),
72                                points));
73
74   p = Offset (width, width * slope + thick / 2);
75   b.add_point (p);
76   p += Offset (-1, -1) * (blot / 2);
77
78   points = scm_cons (scm_from_double (p[X_AXIS]),
79                      scm_cons (scm_from_double (p[Y_AXIS]),
80                                points));
81
82   SCM expr = scm_list_n (ly_symbol2scm ("polygon"),
83                          ly_quote_scm (points),
84                          scm_from_double (blot),
85                          SCM_BOOL_T,
86                          SCM_UNDEFINED);
87
88   return Stencil (b, expr);
89 }
90
91 Stencil
92 Lookup::dashed_slur (Bezier b, Real thick, Real dash_period, Real dash_fraction)
93 {
94   SCM l = SCM_EOL;
95
96   Real on = dash_fraction * dash_period;
97   Real off = dash_period - on;
98
99   for (int i = 4; i--;)
100     l = scm_cons (ly_offset2scm (b.control_[i]), l);
101
102   SCM at = (scm_list_n (ly_symbol2scm ("dashed-slur"),
103                         scm_from_double (thick),
104                         scm_from_double (on),
105                         scm_from_double (off),
106                         ly_quote_scm (l),
107                         SCM_UNDEFINED));
108
109   Box box (Interval (0, 0), Interval (0, 0));
110   return Stencil (box, at);
111 }
112
113 Stencil
114 Lookup::horizontal_line (Interval w, Real th)
115 {
116   SCM at = scm_list_n (ly_symbol2scm ("draw-line"),
117                        scm_from_double (th),
118                        scm_from_double (w[LEFT]),
119                        scm_from_double (0),
120                        scm_from_double (w[RIGHT]),
121                        scm_from_double (0),
122                        SCM_UNDEFINED);
123
124   Box box;
125   box[X_AXIS] = w;
126   box[Y_AXIS] = Interval (-th / 2, th / 2);
127
128   return Stencil (box, at);
129 }
130
131 Stencil
132 Lookup::blank (Box b)
133 {
134   return Stencil (b, scm_makfrom0str (""));
135 }
136
137 Stencil
138 Lookup::filled_box (Box b)
139 {
140   return round_filled_box (b, 0.0);
141 }
142
143 /*
144  * round filled box:
145  *
146  *   __________________________________
147  *  /     \  ^           /     \      ^
148  * |         |blot              |     |
149  * |       | |dia       |       |     |
150  * |         |meter             |     |
151  * |\ _ _ /  v           \ _ _ /|     |
152  * |                            |     |
153  * |                            |     | Box
154  * |                    <------>|     | extent
155  * |                      blot  |     | (Y_AXIS)
156  * |                    diameter|     |
157  * |                            |     |
158  * |  _ _                  _ _  |     |
159  * |/     \              /     \|     |
160  * |                            |     |
161  * |       |            |       |     |
162  * |                            |     |
163  * x\_____/______________\_____/|_____v
164  * |(0, 0)                       |
165  * |                            |
166  * |                            |
167  * |<-------------------------->|
168  *       Box extent (X_AXIS)
169  */
170 Stencil
171 Lookup::round_filled_box (Box b, Real blotdiameter)
172 {
173   if (b.x ().length () < blotdiameter)
174     blotdiameter = b.x ().length ();
175   if (b.y ().length () < blotdiameter)
176     blotdiameter = b.y ().length ();
177
178   SCM at = (scm_list_n (ly_symbol2scm ("round-filled-box"),
179                         scm_from_double (-b[X_AXIS][LEFT]),
180                         scm_from_double (b[X_AXIS][RIGHT]),
181                         scm_from_double (-b[Y_AXIS][DOWN]),
182                         scm_from_double (b[Y_AXIS][UP]),
183                         scm_from_double (blotdiameter),
184                         SCM_UNDEFINED));
185
186   return Stencil (b, at);
187 }
188
189 /*
190  * Create Stencil that represents a filled polygon with round edges.
191  *
192  * LIMITATIONS:
193  *
194  * (a) Only outer (convex) edges are rounded.
195  *
196  * (b) This algorithm works as expected only for polygons whose edges
197  * do not intersect.  For example, the polygon ((0, 0), (q, 0), (0,
198  * q), (q, q)) has an intersection at point (q/2, q/2) and therefore
199  * will give a strange result.  Even non-adjacent edges that just
200  * touch each other will in general not work as expected for non-null
201  * blotdiameter.
202  *
203  * (c) Given a polygon ((x0, y0), (x1, y1), ... , (x (n-1), y (n-1))),
204  * if there is a natural number k such that blotdiameter is greater
205  * than the maximum of { | (x (k mod n), y (k mod n)) - (x ((k+1) mod n),
206  * y ((k+1) mod n)) |, | (x (k mod n), y (k mod n)) - (x ((k+2) mod n),
207  * y ((k+2) mod n)) |, | (x ((k+1) mod n), y ((k+1) mod n)) - (x ((k+2)
208  * mod n), y ((k+2) mod n)) | }, then the outline of the rounded
209  * polygon will exceed the outline of the core polygon.  In other
210  * words: Do not draw rounded polygons that have a leg smaller or
211  * thinner than blotdiameter (or set blotdiameter to a sufficiently
212  * small value -- maybe even 0.0)!
213  *
214  * NOTE: Limitations (b) and (c) arise from the fact that round edges
215  * are made by moulding sharp edges to round ones rather than adding
216  * to a core filled polygon.  For details of these two different
217  * approaches, see the thread upon the ledger lines patch that started
218  * on March 25, 2002 on the devel mailing list.  The below version of
219  * round_filled_polygon () sticks to the moulding model, which the
220  * majority of the list participants finally voted for.  This,
221  * however, results in the above limitations and a much increased
222  * complexity of the algorithm, since it has to compute a shrinked
223  * polygon -- which is not trivial define precisely and unambigously.
224  * With the other approach, one simply could move a circle of size
225  * blotdiameter along all edges of the polygon (which is what the
226  * postscript routine in the backend effectively does, but on the
227  * shrinked polygon). --jr
228  */
229 Stencil
230 Lookup::round_filled_polygon (Array<Offset> const &points,
231                               Real blotdiameter)
232 {
233   /* TODO: Maybe print a warning if one of the above limitations
234      applies to the given polygon.  However, this is quite complicated
235      to check. */
236
237   const Real epsilon = 0.01;
238
239 #ifndef NDEBUG
240   /* remove consecutive duplicate points */
241   for (int i = 0; i < points.size (); i++)
242     {
243       int next_i = (i + 1) % points.size ();
244       Real d = (points[i] - points[next_i]).length ();
245       if (d < epsilon)
246         programming_error ("Polygon should not have duplicate points");
247     }
248 #endif
249
250   /* special cases: degenerated polygons */
251   if (points.size () == 0)
252     return Stencil ();
253   if (points.size () == 1)
254     return dot (points[0], 0.5 * blotdiameter);
255   if (points.size () == 2)
256     return Line_interface::make_line (blotdiameter, points[0], points[1]);
257
258   /* shrink polygon in size by 0.5 * blotdiameter */
259   Array<Offset> shrunk_points;
260   shrunk_points.set_size (points.size ());
261   bool ccw = 1; // true, if three adjacent points are counterclockwise ordered
262   for (int i = 0; i < points.size (); i++)
263     {
264       int i0 = i;
265       int i1 = (i + 1) % points.size ();
266       int i2 = (i + 2) % points.size ();
267       Offset p0 = points[i0];
268       Offset p1 = points[i1];
269       Offset p2 = points[i2];
270       Offset p10 = p0 - p1;
271       Offset p12 = p2 - p1;
272       if (p10.length () != 0.0)
273         { // recompute ccw
274           Real phi = p10.arg ();
275           // rotate (p2 - p0) by (-phi)
276           Offset q = complex_multiply (p2 - p0, complex_exp (Offset (1.0, -phi)));
277
278           if (q[Y_AXIS] > 0)
279             ccw = 1;
280           else if (q[Y_AXIS] < 0)
281             ccw = 0;
282           else {} // keep ccw unchanged
283         }
284       else {} // keep ccw unchanged
285       Offset p10n = (1.0 / p10.length ()) * p10; // normalize length to 1.0
286       Offset p12n = (1.0 / p12.length ()) * p12;
287       Offset p13n = 0.5 * (p10n + p12n);
288       Offset p14n = 0.5 * (p10n - p12n);
289       Offset p13;
290       Real d = p13n.length () * p14n.length (); // distance p3n to line (p1..p0)
291       if (d < epsilon)
292         // special case: p0, p1, p2 are on a single line => build
293         // vector orthogonal to (p2-p0) of length 0.5 blotdiameter
294         {
295           p13[X_AXIS] = p10[Y_AXIS];
296           p13[Y_AXIS] = -p10[X_AXIS];
297           p13 = (0.5 * blotdiameter / p13.length ()) * p13;
298         }
299       else
300         p13 = (0.5 * blotdiameter / d) * p13n;
301       shrunk_points[i1] = p1 + ((ccw) ? p13 : -p13);
302     }
303
304   /* build scm expression and bounding box */
305   SCM shrunk_points_scm = SCM_EOL;
306   Box box;
307   for (int i = 0; i < shrunk_points.size (); i++)
308     {
309       SCM x = scm_from_double (shrunk_points[i][X_AXIS]);
310       SCM y = scm_from_double (shrunk_points[i][Y_AXIS]);
311       shrunk_points_scm = scm_cons (x, scm_cons (y, shrunk_points_scm));
312       box.add_point (points[i]);
313     }
314   SCM polygon_scm = scm_list_n (ly_symbol2scm ("polygon"),
315                                 ly_quote_scm (shrunk_points_scm),
316                                 scm_from_double (blotdiameter),
317                                 SCM_BOOL_T,
318                                 SCM_UNDEFINED);
319
320   Stencil polygon = Stencil (box, polygon_scm);
321   shrunk_points.clear ();
322   return polygon;
323 }
324
325 /*
326   TODO: deprecate?
327 */
328 Stencil
329 Lookup::frame (Box b, Real thick, Real blot)
330 {
331   Stencil m;
332   Direction d = LEFT;
333   for (Axis a = X_AXIS; a < NO_AXES; a = Axis (a + 1))
334     {
335       Axis o = Axis ((a + 1)%NO_AXES);
336       do
337         {
338           Box edges;
339           edges[a] = b[a][d] + 0.5 * thick * Interval (-1, 1);
340           edges[o][DOWN] = b[o][DOWN] - thick / 2;
341           edges[o][UP] = b[o][UP] + thick / 2;
342
343           m.add_stencil (round_filled_box (edges, blot));
344         }
345       while (flip (&d) != LEFT);
346     }
347   return m;
348 }
349
350 /*
351   Make a smooth curve along the points
352 */
353 Stencil
354 Lookup::slur (Bezier curve, Real curvethick, Real linethick)
355 {
356   Real alpha = (curve.control_[3] - curve.control_[0]).arg ();
357   Bezier back = curve;
358   Offset perp = curvethick * complex_exp (Offset (0, alpha + M_PI / 2)) * 0.5;
359   back.reverse ();
360   back.control_[1] += perp;
361   back.control_[2] += perp;
362
363   curve.control_[1] -= perp;
364   curve.control_[2] -= perp;
365
366   SCM scontrols[8];
367
368   for (int i = 0; i < 4; i++)
369     scontrols[i] = ly_offset2scm (back.control_[i]);
370   for (int i = 0; i < 4; i++)
371     scontrols[i + 4] = ly_offset2scm (curve.control_[i]);
372
373   /*
374     Need the weird order b.o. the way PS want its arguments
375   */
376   int indices[] = {5, 6, 7, 4, 1, 2, 3, 0};
377   SCM list = SCM_EOL;
378   for (int i = 8; i--;)
379     list = scm_cons (scontrols[indices[i]], list);
380
381   SCM at = (scm_list_n (ly_symbol2scm ("bezier-sandwich"),
382                         ly_quote_scm (list),
383                         scm_from_double (linethick),
384                         SCM_UNDEFINED));
385   Box b (curve.extent (X_AXIS),
386          curve.extent (Y_AXIS));
387
388   b[X_AXIS].unite (back.extent (X_AXIS));
389   b[Y_AXIS].unite (back.extent (Y_AXIS));
390
391   return Stencil (b, at);
392 }
393
394 /*
395  * Bezier Sandwich:
396  *
397  *                               .|
398  *                        .       |
399  *              top .             |
400  *              . curve           |
401  *          .                     |
402  *       .                        |
403  *     .                          |
404  *    |                           |
405  *    |                          .|
406  *    |                     .
407  *    |         bottom .
408  *    |            . curve
409  *    |         .
410  *    |      .
411  *    |   .
412  *    | .
413  *    |.
414  *    |
415  *
416  */
417 Stencil
418 Lookup::bezier_sandwich (Bezier top_curve, Bezier bottom_curve)
419 {
420   /*
421     Need the weird order b.o. the way PS want its arguments
422   */
423   SCM list = SCM_EOL;
424   list = scm_cons (ly_offset2scm (bottom_curve.control_[3]), list);
425   list = scm_cons (ly_offset2scm (bottom_curve.control_[0]), list);
426   list = scm_cons (ly_offset2scm (bottom_curve.control_[1]), list);
427   list = scm_cons (ly_offset2scm (bottom_curve.control_[2]), list);
428   list = scm_cons (ly_offset2scm (top_curve.control_[0]), list);
429   list = scm_cons (ly_offset2scm (top_curve.control_[3]), list);
430   list = scm_cons (ly_offset2scm (top_curve.control_[2]), list);
431   list = scm_cons (ly_offset2scm (top_curve.control_[1]), list);
432
433   SCM horizontal_bend = scm_list_n (ly_symbol2scm ("bezier-sandwich"),
434                                     ly_quote_scm (list),
435                                     scm_from_double (0.0),
436                                     SCM_UNDEFINED);
437
438   Interval x_extent = top_curve.extent (X_AXIS);
439   x_extent.unite (bottom_curve.extent (X_AXIS));
440   Interval y_extent = top_curve.extent (Y_AXIS);
441   y_extent.unite (bottom_curve.extent (Y_AXIS));
442   Box b (x_extent, y_extent);
443
444   return Stencil (b, horizontal_bend);
445 }
446
447 /*
448   TODO: junk me.
449 */
450 Stencil
451 Lookup::accordion (SCM s, Real staff_space, Font_metric *fm)
452 {
453   Stencil m;
454   String sym = ly_scm2string (scm_car (s));
455   String reg = ly_scm2string (scm_car (scm_cdr (s)));
456
457   if (sym == "Discant")
458     {
459       Stencil r = fm->find_by_name ("accordion.accDiscant");
460       m.add_stencil (r);
461       if (reg.left_string (1) == "F")
462         {
463           Stencil d = fm->find_by_name ("accordion.accDot");
464           d.translate_axis (staff_space * 2.5 PT, Y_AXIS);
465           m.add_stencil (d);
466           reg = reg.right_string (reg.length () - 1);
467         }
468       int eflag = 0x00;
469       if (reg.left_string (3) == "EEE")
470         {
471           eflag = 0x07;
472           reg = reg.right_string (reg.length () - 3);
473         }
474       else if (reg.left_string (2) == "EE")
475         {
476           eflag = 0x05;
477           reg = reg.right_string (reg.length () - 2);
478         }
479       else if (reg.left_string (2) == "Eh")
480         {
481           eflag = 0x04;
482           reg = reg.right_string (reg.length () - 2);
483         }
484       else if (reg.left_string (1) == "E")
485         {
486           eflag = 0x02;
487           reg = reg.right_string (reg.length () - 1);
488         }
489       if (eflag & 0x02)
490         {
491           Stencil d = fm->find_by_name ("accordion.accDot");
492           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
493           m.add_stencil (d);
494         }
495       if (eflag & 0x04)
496         {
497           Stencil d = fm->find_by_name ("accordion.accDot");
498           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
499           d.translate_axis (0.8 * staff_space PT, X_AXIS);
500           m.add_stencil (d);
501         }
502       if (eflag & 0x01)
503         {
504           Stencil d = fm->find_by_name ("accordion.accDot");
505           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
506           d.translate_axis (-0.8 * staff_space PT, X_AXIS);
507           m.add_stencil (d);
508         }
509       if (reg.left_string (2) == "SS")
510         {
511           Stencil d = fm->find_by_name ("accordion.accDot");
512           d.translate_axis (0.5 * staff_space PT, Y_AXIS);
513           d.translate_axis (0.4 * staff_space PT, X_AXIS);
514           m.add_stencil (d);
515           d.translate_axis (-0.8 * staff_space PT, X_AXIS);
516           m.add_stencil (d);
517           reg = reg.right_string (reg.length () - 2);
518         }
519       if (reg.left_string (1) == "S")
520         {
521           Stencil d = fm->find_by_name ("accordion.accDot");
522           d.translate_axis (0.5 * staff_space PT, Y_AXIS);
523           m.add_stencil (d);
524           reg = reg.right_string (reg.length () - 1);
525         }
526     }
527   else if (sym == "Freebase")
528     {
529       Stencil r = fm->find_by_name ("accordion.accFreebase");
530       m.add_stencil (r);
531       if (reg.left_string (1) == "F")
532         {
533           Stencil d = fm->find_by_name ("accordion.accDot");
534           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
535           m.add_stencil (d);
536           reg = reg.right_string (reg.length () - 1);
537         }
538       if (reg == "E")
539         {
540           Stencil d = fm->find_by_name ("accordion.accDot");
541           d.translate_axis (staff_space * 0.5 PT, Y_AXIS);
542           m.add_stencil (d);
543         }
544     }
545   else if (sym == "Bayanbase")
546     {
547       Stencil r = fm->find_by_name ("accordion.accBayanbase");
548       m.add_stencil (r);
549       if (reg.left_string (1) == "T")
550         {
551           Stencil d = fm->find_by_name ("accordion.accDot");
552           d.translate_axis (staff_space * 2.5 PT, Y_AXIS);
553           m.add_stencil (d);
554           reg = reg.right_string (reg.length () - 1);
555         }
556       /* include 4' reed just for completeness. You don't want to use this. */
557       if (reg.left_string (1) == "F")
558         {
559           Stencil d = fm->find_by_name ("accordion.accDot");
560           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
561           m.add_stencil (d);
562           reg = reg.right_string (reg.length () - 1);
563         }
564       if (reg.left_string (2) == "EE")
565         {
566           Stencil d = fm->find_by_name ("accordion.accDot");
567           d.translate_axis (staff_space * 0.5 PT, Y_AXIS);
568           d.translate_axis (0.4 * staff_space PT, X_AXIS);
569           m.add_stencil (d);
570           d.translate_axis (-0.8 * staff_space PT, X_AXIS);
571           m.add_stencil (d);
572           reg = reg.right_string (reg.length () - 2);
573         }
574       if (reg.left_string (1) == "E")
575         {
576           Stencil d = fm->find_by_name ("accordion.accDot");
577           d.translate_axis (staff_space * 0.5 PT, Y_AXIS);
578           m.add_stencil (d);
579           reg = reg.right_string (reg.length () - 1);
580         }
581     }
582   else if (sym == "Stdbase")
583     {
584       Stencil r = fm->find_by_name ("accordion.accStdbase");
585       m.add_stencil (r);
586       if (reg.left_string (1) == "T")
587         {
588           Stencil d = fm->find_by_name ("accordion.accDot");
589           d.translate_axis (staff_space * 3.5 PT, Y_AXIS);
590           m.add_stencil (d);
591           reg = reg.right_string (reg.length () - 1);
592         }
593       if (reg.left_string (1) == "F")
594         {
595           Stencil d = fm->find_by_name ("accordion.accDot");
596           d.translate_axis (staff_space * 2.5 PT, Y_AXIS);
597           m.add_stencil (d);
598           reg = reg.right_string (reg.length () - 1);
599         }
600       if (reg.left_string (1) == "M")
601         {
602           Stencil d = fm->find_by_name ("accordion.accDot");
603           d.translate_axis (staff_space * 2 PT, Y_AXIS);
604           d.translate_axis (staff_space PT, X_AXIS);
605           m.add_stencil (d);
606           reg = reg.right_string (reg.length () - 1);
607         }
608       if (reg.left_string (1) == "E")
609         {
610           Stencil d = fm->find_by_name ("accordion.accDot");
611           d.translate_axis (staff_space * 1.5 PT, Y_AXIS);
612           m.add_stencil (d);
613           reg = reg.right_string (reg.length () - 1);
614         }
615       if (reg.left_string (1) == "S")
616         {
617           Stencil d = fm->find_by_name ("accordion.accDot");
618           d.translate_axis (staff_space * 0.5 PT, Y_AXIS);
619           m.add_stencil (d);
620           reg = reg.right_string (reg.length () - 1);
621         }
622     }
623   /* ugh maybe try to use regular font for S.B. and B.B and only use one font
624      for the rectangle */
625   else if (sym == "SB")
626     {
627       Stencil r = fm->find_by_name ("accordion.accSB");
628       m.add_stencil (r);
629     }
630   else if (sym == "BB")
631     {
632       Stencil r = fm->find_by_name ("accordion.accBB");
633       m.add_stencil (r);
634     }
635   else if (sym == "OldEE")
636     {
637       Stencil r = fm->find_by_name ("accordion.accOldEE");
638       m.add_stencil (r);
639     }
640   else if (sym == "OldEES")
641     {
642       Stencil r = fm->find_by_name ("accordion.accOldEES");
643       m.add_stencil (r);
644     }
645   return m;
646 }
647
648 Stencil
649 Lookup::repeat_slash (Real w, Real s, Real t)
650 {
651 #if 0 /*  TODO */
652   Array<Offset> points;
653   Real blotdiameter = 0.0;
654
655   Offset p1 (0, 0);
656   Offset p2 (w, w * s);
657
658   return Lookup::round_filled_polygon (points, blotdiameter);
659 #endif
660
661   SCM wid = scm_from_double (w);
662   SCM sl = scm_from_double (s);
663   SCM thick = scm_from_double (t);
664   SCM slashnodot = scm_list_n (ly_symbol2scm ("repeat-slash"),
665                                wid, sl, thick, SCM_UNDEFINED);
666
667   Box b (Interval (0, w + sqrt (sqr (t / s) + sqr (t))),
668          Interval (0, w * s));
669
670   return Stencil (b, slashnodot); //  http://slashnodot.org
671 }
672
673 Stencil
674 Lookup::bracket (Axis a, Interval iv, Real thick, Real protude, Real blot)
675 {
676   Box b;
677   Axis other = Axis ((a + 1)%2);
678   b[a] = iv;
679   b[other] = Interval (-1, 1) * thick * 0.5;
680
681   Stencil m = round_filled_box (b, blot);
682
683   b[a] = Interval (iv[UP] - thick, iv[UP]);
684   Interval oi = Interval (-thick / 2, thick / 2 + fabs (protude));
685   oi *= sign (protude);
686   b[other] = oi;
687   m.add_stencil (round_filled_box (b, blot));
688   b[a] = Interval (iv[DOWN], iv[DOWN] + thick);
689   m.add_stencil (round_filled_box (b, blot));
690
691   return m;
692 }
693
694 Stencil
695 Lookup::triangle (Interval iv, Real thick, Real protude)
696 {
697   Box b;
698   b[X_AXIS] = Interval (0, iv.length ());
699   b[Y_AXIS] = Interval (min (0., protude), max (0.0, protude));
700
701   Offset z1 (iv[LEFT], 0);
702   Offset z2 (iv[RIGHT], 0);
703   Offset z3 ((z1 + z2)[X_AXIS] / 2, protude);
704
705   /*
706     TODO: move Triangle to Line_interface ?
707   */
708   Stencil tri = Line_interface::make_line (thick, z1, z2);
709   tri.add_stencil (Line_interface::make_line (thick, z2, z3));
710   tri.add_stencil (Line_interface::make_line (thick, z3, z1));
711
712   return tri;
713 }
714