]> git.donarmstrong.com Git - lilypond.git/blob - lily/grob.cc
``slikken kreng''
[lilypond.git] / lily / grob.cc
1 /*
2   grob.cc -- implement Grob
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include <string.h>
11 #include <math.h>
12
13 #include "main.hh"
14 #include "input-smob.hh"
15 #include "warn.hh"
16 #include "group-interface.hh"
17 #include "misc.hh"
18 #include "paper-score.hh"
19 #include "molecule.hh"
20 #include "grob.hh"
21 #include "warn.hh"
22 #include "spanner.hh"
23 #include "system.hh"
24 #include "item.hh"
25 #include "molecule.hh"
26 #include "misc.hh"
27 #include "music.hh"
28 #include "item.hh"
29
30 #include "ly-smobs.icc"
31
32 /*
33 TODO:
34
35 remove dynamic_cast<Spanner,Item> and put this code into respective
36   subclass.
37 */
38
39 //#define HASHING_FOR_MUTABLE_PROPS
40 #define HASH_SIZE 3
41 #define INFINITY_MSG "Infinity or NaN encountered"
42
43 Grob::Grob (SCM basicprops)
44 {
45   /*
46     fixme: default should be no callback.
47    */
48
49   pscore_=0;
50   status_ = 0;
51   original_ = 0;
52   immutable_property_alist_ =  basicprops;
53   mutable_property_alist_ = SCM_EOL;
54
55   /*
56     We do smobify_self() as the first step. Since the object lives on
57     the heap, none of its SCM variables are protected from GC. After
58     smobify_self(), they are.
59    */
60   smobify_self ();
61
62
63 #ifdef HASHING_FOR_MUTABLE_PROPS
64   mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
65 #endif
66   
67   SCM meta = get_grob_property ("meta");
68   if (gh_pair_p (meta))
69     {
70       SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
71
72       /*
73         Switch off interface checks for the moment.
74        */
75       bool itc = internal_type_checking_global_b;
76       internal_type_checking_global_b = false;
77       internal_set_grob_property (ly_symbol2scm ("interfaces"), gh_cdr(ifs));
78       internal_type_checking_global_b = itc;
79     }
80   
81   /*
82     TODO:
83
84     destill this into a function, so we can re-init the immutable
85     properties with a new BASICPROPS value after creation. Convenient
86     eg. when using \override with StaffSymbol.  */
87   
88   char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
89   char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
90   
91   for (int a = X_AXIS; a <= Y_AXIS; a++)
92     {
93       SCM l = get_grob_property (onames[a]);
94
95       if (scm_ilength (l) >=0)
96         {
97           dim_cache_[a].offset_callbacks_ = l;
98           dim_cache_[a].offsets_left_ = scm_ilength (l);
99         }
100       else
101         {
102           programming_error ("[XY]-offset-callbacks must be a list");
103         }
104
105       SCM cb = get_grob_property (enames[a]);
106
107       /*
108         Should change default to be empty? 
109       */
110       if (cb != SCM_BOOL_F
111           && !gh_procedure_p (cb) && !gh_pair_p (cb)
112           && gh_procedure_p (get_grob_property ("molecule-callback"))
113           )
114         cb = molecule_extent_proc;
115     
116       dim_cache_[a].dimension_ = cb;
117     }
118
119 }
120
121 Grob::Grob (Grob const&s)
122    : dim_cache_ (s.dim_cache_)
123 {
124   original_ = (Grob*) &s;
125   immutable_property_alist_ = s.immutable_property_alist_;
126
127   mutable_property_alist_ = SCM_EOL;
128   
129   /*
130     No properties are copied. That is the job of handle_broken_dependencies.
131    */
132   
133   status_ = s.status_;
134   pscore_ = s.pscore_;
135
136   smobify_self ();
137
138 #ifdef HASHING_FOR_MUTABLE_PROPS
139   mutable_property_alist_ = scm_c_make_hash_table (HASH_SIZE);
140 #endif
141 }
142
143 Grob::~Grob ()
144 {
145   /*
146     do nothing scm-ish and no unprotecting here.
147    */
148 }
149
150
151
152
153
154 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
155 SCM
156 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
157 {
158   Grob *s = unsmob_grob (element_smob);
159   Axis a = (Axis) gh_scm2int (scm_axis);
160
161   Molecule *m = s->get_molecule ();
162   Interval e ;
163   if (m)
164     e = m->extent (a);
165   return ly_interval2scm (e);
166 }
167
168 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
169 SCM
170 Grob::preset_extent (SCM element_smob, SCM scm_axis)
171 {
172   Grob *s = unsmob_grob (element_smob);
173   Axis a = (Axis) gh_scm2int (scm_axis);
174
175   SCM ext = s->get_grob_property ((a == X_AXIS)
176                                  ? "extent-X"
177                                  : "extent-Y");
178   
179   if (gh_pair_p (ext))
180     {
181       Real l = gh_scm2double (ly_car (ext));
182       Real r = gh_scm2double (ly_cdr (ext));
183       return ly_interval2scm (Interval (l, r));
184     }
185   
186   return ly_interval2scm (Interval ());
187 }
188
189
190
191 Paper_def*
192 Grob::get_paper ()  const
193 {
194  return pscore_ ? pscore_->paper_ : 0;
195 }
196
197 void
198 Grob::calculate_dependencies (int final, int busy, SCM funcname)
199 {
200   if (status_ >= final)
201     return;
202
203   if (status_== busy)
204     {
205       programming_error ("Element is busy, come back later");
206       return;
207     }
208   
209   status_= busy;
210
211   for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
212        d = ly_cdr (d))
213     {
214       unsmob_grob (ly_car (d))
215         ->calculate_dependencies (final, busy, funcname);
216     }
217
218   
219   SCM proc = internal_get_grob_property (funcname);
220   if (gh_procedure_p (proc))
221     gh_call1 (proc, this->self_scm ());
222  
223   status_= final;
224 }
225
226 Molecule *
227 Grob::get_molecule ()  const
228 {
229   if (!live())
230     {
231       return 0;
232       
233     }
234   
235   SCM mol = get_grob_property ("molecule");
236   if (unsmob_molecule (mol))
237     return unsmob_molecule (mol);
238
239   mol =  get_uncached_molecule ();
240   
241   Grob *me = (Grob*)this;
242   me->set_grob_property ("molecule", mol);
243   
244   return unsmob_molecule (mol);  
245 }
246 SCM
247 Grob::get_uncached_molecule ()const
248 {
249   SCM proc = get_grob_property ("molecule-callback");
250
251   SCM  mol = SCM_EOL;
252   if (gh_procedure_p (proc)) 
253     mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
254
255   
256   Molecule *m = unsmob_molecule (mol);
257   
258   if (unsmob_molecule (mol))
259     {
260       SCM origin = ly_symbol2scm ("no-origin");
261       
262       if (store_locations_global_b){
263         SCM cause = get_grob_property ("cause");
264         if (Music*m = unsmob_music (cause))
265           {
266             SCM music_origin = m->get_mus_property ("origin");
267             if (unsmob_input (music_origin))
268               origin = music_origin;
269           }
270       }
271
272       // ugr.
273       
274       mol = Molecule (m->extent_box (),
275                       scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
276                       ). smobbed_copy ();
277
278       m = unsmob_molecule (mol);
279     }
280   
281   /*
282     transparent retains dimensions of element.
283    */
284   if (m && to_boolean (get_grob_property ("transparent")))
285     mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
286
287   return mol;
288 }
289
290 /*
291   
292   VIRTUAL STUBS
293
294  */
295 void
296 Grob::do_break_processing ()
297 {
298 }
299
300
301
302
303
304
305 System *
306 Grob::get_system () const
307 {
308   return 0;
309 }
310
311 LY_DEFINE (get_system,
312            "get-system",
313            1, 0, 0, (SCM grob),
314            "
315 Return the System Grob of @var{grob}.
316 ")
317 {
318   Grob *me = unsmob_grob (grob);
319   SCM_ASSERT_TYPE (me, grob, SCM_ARG1, __FUNCTION__, "grob");
320   
321   if (Grob *g = me->get_system ())
322     return g->self_scm ();
323     
324   return SCM_EOL;
325 }
326
327 LY_DEFINE (get_original,
328            "get-original",
329            1, 0, 0, (SCM grob),
330            "
331 Return the original Grob of @var{grob}
332 ")
333 {
334   Grob *me = unsmob_grob (grob);
335   SCM_ASSERT_TYPE (me, grob, SCM_ARG1, __FUNCTION__, "grob");
336   return me->original_ ? me->original_->self_scm () : me->self_scm ();
337 }
338
339 void
340 Grob::add_dependency (Grob*e)
341 {
342   if (e)
343     {
344       Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
345     }
346   else
347     programming_error ("Null dependency added");
348 }
349
350
351
352 void
353 Grob::handle_broken_dependencies ()
354 {
355   Spanner * s= dynamic_cast<Spanner*> (this);
356   if (original_ && s)
357     return;
358
359   if (s)
360     {
361       for (int i = 0;  i< s->broken_intos_ .size (); i++)
362         {
363           Grob * sc = s->broken_intos_[i];
364           System * l = sc->get_system ();
365
366           sc->substitute_mutable_properties (l ? l->self_scm () : SCM_UNDEFINED,
367                                    mutable_property_alist_);
368         }
369     }
370
371
372   System *system = get_system ();
373
374   if (live ()
375       && system && common_refpoint (system, X_AXIS) && common_refpoint (system, Y_AXIS))
376     {
377       substitute_mutable_properties (system ? system->self_scm () : SCM_UNDEFINED,
378                                mutable_property_alist_);
379     }
380   else if (dynamic_cast <System*> (this))
381     {
382       substitute_mutable_properties (SCM_UNDEFINED, mutable_property_alist_);
383     }
384   else
385     {
386       /*
387         This element is `invalid'; it has been removed from all
388         dependencies, so let's junk the element itself.
389
390         do not do this for System, since that would remove references
391         to the originals of score-grobs, which get then GC'd (a bad
392         thing.)
393  
394       */
395       suicide ();
396     }
397 }
398
399 /*
400  Note that we still want references to this element to be
401  rearranged, and not silently thrown away, so we keep pointers
402  like {broken_into_{drul,array}, original}
403 */
404 void
405 Grob::suicide ()
406 {
407   mutable_property_alist_ = SCM_EOL;
408   immutable_property_alist_ = SCM_EOL;
409
410   set_extent (SCM_EOL, Y_AXIS);
411   set_extent (SCM_EOL, X_AXIS);
412
413   for (int a= X_AXIS; a <= Y_AXIS; a++)
414     {
415       dim_cache_[a].offset_callbacks_ = SCM_EOL;
416       dim_cache_[a].offsets_left_ = 0;
417     }
418 }
419
420 void
421 Grob::handle_prebroken_dependencies ()
422 {
423   /*
424     Don't do this in the derived method, since we want to keep access to
425     mutable_property_alist_ centralized.
426    */
427   if (original_)
428     {
429       Item * it = dynamic_cast<Item*> (this);
430       substitute_mutable_properties (gh_int2scm (it->break_status_dir ()),
431                                original_->mutable_property_alist_);
432     }
433 }
434
435 Grob*
436 Grob::find_broken_piece (System*) const
437 {
438   return 0;
439 }
440
441 /*
442   translate in one direction
443 */
444 void
445 Grob::translate_axis (Real y, Axis a)
446 {
447   if (isinf (y) || isnan (y))
448     programming_error (_ (INFINITY_MSG));
449   else
450     {
451       dim_cache_[a].offset_ += y;
452     }
453 }  
454
455
456 /*
457   Find the offset relative to D.  If   D equals THIS, then it is 0.
458   Otherwise, it recursively defd as
459   
460   OFFSET_ + PARENT_L_->relative_coordinate (D)
461 */
462 Real
463 Grob::relative_coordinate (Grob const*refp, Axis a) const
464 {
465   if (refp == this)
466     return 0.0;
467
468   /*
469     We catch PARENT_L_ == nil case with this, but we crash if we did
470     not ask for the absolute coordinate (ie. REFP == nil.)
471     
472    */
473   if (refp == dim_cache_[a].parent_)
474     return get_offset (a);
475   else
476     return get_offset (a) + dim_cache_[a].parent_->relative_coordinate (refp, a);
477 }
478
479
480   
481 /*
482   Invoke callbacks to get offset relative to parent.
483 */
484 Real
485 Grob::get_offset (Axis a) const
486 {
487   Grob *me = (Grob*) this;
488   while (dim_cache_[a].offsets_left_)
489     {
490       int l = --me->dim_cache_[a].offsets_left_;
491       SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_,  gh_int2scm (l));
492       SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
493
494       Real r =  gh_scm2double (retval);
495       if (isinf (r) || isnan (r))
496         {
497           programming_error (INFINITY_MSG);
498           r = 0.0;
499         }
500       me->dim_cache_[a].offset_ +=r;
501     }
502   return dim_cache_[a].offset_;
503 }
504
505
506 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
507 SCM
508 Grob::point_dimension_callback (SCM , SCM)
509 {
510   return ly_interval2scm (Interval (0,0));
511 }
512
513 bool
514 Grob::empty_b (Axis a)const
515 {
516   return ! (gh_pair_p (dim_cache_[a].dimension_) ||
517             gh_procedure_p (dim_cache_[a].dimension_));
518 }
519
520 Interval
521 Grob::extent (Grob * refp, Axis a) const
522 {
523   Real x = relative_coordinate (refp, a);
524
525   
526   Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
527   Interval ext ;   
528   if (gh_pair_p (d->dimension_))
529     ;
530   else if (gh_procedure_p (d->dimension_))
531     {
532       /*
533         FIXME: add doco on types, and should typecheck maybe? 
534        */
535       d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
536     }
537   else
538     return ext;
539
540   if (!gh_pair_p (d->dimension_))
541     return ext;
542   
543   ext = ly_scm2interval (d->dimension_);
544
545   SCM extra = get_grob_property (a == X_AXIS
546                                 ? "extra-extent-X"
547                                 : "extra-extent-Y");
548
549   /*
550     signs ?
551    */
552   if (gh_pair_p (extra))
553     {
554       ext[BIGGER] +=  gh_scm2double (ly_cdr (extra));
555       ext[SMALLER] +=   gh_scm2double (ly_car (extra));
556     }
557   
558   extra = get_grob_property (a == X_AXIS
559                                 ? "minimum-extent-X"
560                                 : "minimum-extent-Y");
561   if (gh_pair_p (extra))
562     {
563       ext.unite (Interval (gh_scm2double (ly_car (extra)),
564                            gh_scm2double (ly_cdr (extra))));
565     }
566
567   ext.translate (x);
568   
569   return ext;
570 }
571
572 /*
573   Find the group-element which has both #this# and #s#
574 */
575 Grob * 
576 Grob::common_refpoint (Grob const* s, Axis a) const
577 {
578   /*
579     I don't like the quadratic aspect of this code, but I see no other
580     way. The largest chain of parents might be 10 high or so, so
581     it shouldn't be a real issue. */
582   for (Grob const *c = this; c; c = c->dim_cache_[a].parent_)
583     for (Grob const * d = s; d; d = d->dim_cache_[a].parent_)
584       if (d == c)
585         return (Grob*)d;
586
587   return 0;
588 }
589
590
591 Grob *
592 common_refpoint_of_list (SCM elist, Grob *common, Axis a) 
593 {
594   for (; gh_pair_p (elist); elist = ly_cdr (elist))
595     {
596       Grob * s = unsmob_grob (ly_car (elist));
597       if (!s)
598         continue;
599       if (common)
600         common = common->common_refpoint (s, a);
601       else
602         common = s;
603     }
604
605   return common;
606 }
607
608
609
610 Grob *
611 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a) 
612 {
613   for (int i = arr.size() ; i--; )
614     {
615       Grob * s = arr[i];
616       if (!s)
617         continue;
618
619       if (common)
620         common = common->common_refpoint (s, a);
621       else
622         common = s;
623     }
624
625   return common;
626 }
627
628 String
629 Grob::name () const
630 {
631   SCM meta = get_grob_property ("meta");
632   SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
633   nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
634   return  gh_symbol_p (nm) ? ly_symbol2string (nm) :  classname (this);  
635 }
636
637 void
638 Grob::add_offset_callback (SCM cb, Axis a)
639 {
640   if (!has_offset_callback_b (cb, a))
641   {
642     dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
643     dim_cache_[a].offsets_left_ ++;
644   }
645 }
646
647 bool
648 Grob::has_extent_callback_b (SCM cb, Axis a)const
649 {
650   return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
651 }
652
653
654 bool
655 Grob::has_offset_callback_b (SCM cb, Axis a)const
656 {
657   return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
658 }
659
660 void
661 Grob::set_extent (SCM dc, Axis a)
662 {
663   dim_cache_[a].dimension_ =dc;
664 }
665
666 void
667 Grob::set_parent (Grob *g, Axis a)
668 {
669   dim_cache_[a].parent_ = g;
670 }
671
672 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
673 SCM
674 Grob::fixup_refpoint (SCM smob)
675 {
676   Grob *me = unsmob_grob (smob);
677   for (int a = X_AXIS; a < NO_AXES; a ++)
678     {
679       Axis ax = (Axis)a;
680       Grob * parent = me->get_parent (ax);
681
682       if (!parent)
683         continue;
684       
685       if (parent->get_system () != me->get_system () && me->get_system ())
686         {
687           Grob * newparent = parent->find_broken_piece (me->get_system ());
688           me->set_parent (newparent, ax);
689         }
690
691       if (Item * i  = dynamic_cast<Item*> (me))
692         {
693           Item *parenti = dynamic_cast<Item*> (parent);
694
695           if (parenti && i)
696             {
697               Direction  my_dir = i->break_status_dir () ;
698               if (my_dir!= parenti->break_status_dir ())
699                 {
700                   Item *newparent =  parenti->find_prebroken_piece (my_dir);
701                   me->set_parent (newparent, ax);
702                 }
703             }
704         }
705     }
706   return smob;
707 }
708
709 void
710 Grob::warning (String s)const
711 {
712   SCM cause = self_scm();
713   while (cause != SCM_EOL && !unsmob_music (cause))
714     {
715       Grob * g = unsmob_grob (cause);
716       cause = g->get_grob_property ("cause");
717     }
718
719   if (Music *m = unsmob_music (cause))
720     {
721       m->origin()->warning (s);
722     }
723   else
724     ::warning (s);
725 }
726
727 void
728 Grob::programming_error (String s)const
729 {
730   s = "Programming error: "  + s;
731   warning (s);
732 }
733
734
735 /****************************************************
736   SMOB funcs
737  ****************************************************/
738
739
740
741 IMPLEMENT_SMOBS (Grob);
742 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
743
744 SCM
745 Grob::mark_smob (SCM ses)
746 {
747   Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
748   scm_gc_mark (s->immutable_property_alist_);
749
750   for (int a =0 ; a < 2; a++)
751     {
752       scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
753       scm_gc_mark (s->dim_cache_[a].dimension_);
754       
755       /*
756         don't mark the parents. The pointers in the mutable property
757         list form two tree like structures (one for X relations, one
758         for Y relations). Marking these can be done in limited stack
759         space.  If we add the parents, we will jump between X and Y in
760         an erratic manner, leading to much more recursion depth (and
761         core dumps if we link to pthreads.)
762        */
763     }
764   
765   if (s->original_)
766     scm_gc_mark (s->original_->self_scm ());
767
768   s->do_derived_mark ();  
769   return s->mutable_property_alist_;
770 }
771
772 int
773 Grob::print_smob (SCM s, SCM port, scm_print_state *)
774 {
775   Grob *sc = (Grob *) ly_cdr (s);
776      
777   scm_puts ("#<Grob ", port);
778   scm_puts ((char *)sc->name ().to_str0 (), port);
779
780   /*
781     don't try to print properties, that is too much hassle.
782    */
783   scm_puts (" >", port);
784   return 1;
785 }
786
787 SCM
788 Grob::do_derived_mark ()
789 {
790   return SCM_EOL;
791 }
792
793
794
795 void
796 Grob::discretionary_processing ()
797 {
798 }
799
800 bool
801 Grob::internal_has_interface (SCM k)
802 {
803   SCM ifs = get_grob_property ("interfaces");
804
805   return scm_memq (k, ifs) != SCM_BOOL_F;
806 }
807
808
809 /** Return Array of Grobs in SCM list L */
810 Link_array<Grob>
811 ly_scm2grobs (SCM l)
812 {
813   Link_array<Grob> arr;
814
815   for (SCM s = l; gh_pair_p (s); s = gh_cdr (s))
816     {
817       SCM e = gh_car (s);
818       arr.push (unsmob_grob (e));
819     }
820
821   arr.reverse ();
822   return arr;
823 }
824
825 /** Return SCM list of Grob array A */
826 SCM
827 ly_grobs2scm (Link_array<Grob> a)
828 {
829   SCM s = SCM_EOL;
830   for (int i = a.size (); i; i--)
831     s = gh_cons (a[i-1]->self_scm (), s);
832
833   return s;
834 }
835
836
837 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
838
839 ADD_INTERFACE (Grob, "grob-interface",
840   "In music notation, lots of symbols are related in some way.  You can
841 think of music notation as a graph where nodes are formed by the
842 symbols, and the arcs by their relations. A grob is a node in that
843 graph.  The directed edges in the graph are formed by references to
844 other grobs (i.e. pointers).  This big graph of grobs specifies the
845 notation problem. The solution of this problem is a description of the
846 printout in closed form, i.e. a list of values.  These values are
847 Molecules.
848
849 All grobs have an X and Y-position on the page.  These X and Y positions
850 are stored in a relative format, so they can easily be combined by
851 stacking them, hanging one grob to the side of another, and coupling
852 them into a grouping-grob.
853
854 Each grob has a reference point (a.k.a.  parent): the position of a grob
855 is stored relative to that reference point. For example the X-reference
856 point of a staccato dot usually is the note head that it applies
857 to. When the note head is moved, the staccato dot moves along
858 automatically.
859
860 A grob is often associated with a symbol, but some grobs do not print
861 any symbols. They take care of grouping objects. For example, there is a
862 separate grob that stacks staves vertically. The @ref{NoteCollision}
863 is also an abstract grob: it only moves around chords, but doesn't print
864 anything.
865 ",
866   "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
867 Y-extent-callback molecule-callback extra-offset spacing-procedure
868 staff-symbol interfaces dependencies extent-X extent-Y extra-extent-X
869 causes meta layer before-line-breaking-callback
870 after-line-breaking-callback extra-extent-Y minimum-extent-X
871 minimum-extent-Y transparent");
872
873