aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEli Zaretskii2010-01-01 06:17:13 -0500
committerEli Zaretskii2010-01-01 06:17:13 -0500
commitbe39f003e91ecb81161e5cf14ec0b635a6dc229d (patch)
tree79803593f9048654741537843dc2e004ca56f09a /src
parent6bff64970571457eeab84a5921547816b444b732 (diff)
downloademacs-be39f003e91ecb81161e5cf14ec0b635a6dc229d.tar.gz
emacs-be39f003e91ecb81161e5cf14ec0b635a6dc229d.zip
Retrospective commit from 2009-10-04.
Continue working on determining paragraph's base direction. bidi.c (bidi_at_paragraph_end): Check for paragraph-start if paragraph-separate failed to match. Return the length of the matched separator. (bidi_line_init): New function. (bidi_paragraph_init): Use bidi_line_init. Do nothing if in the middle of a paragraph-separate sequence. Don't override existing paragraph direction if no strong characters found in this paragraph. Set separator_limit according to what bidi_at_paragraph_end returns. Reset new_paragraph flag when a new paragraph is found. (bidi_init_it): Reset separator_limit. dispextern.h (struct bidi_it): New member separator_limit. bidi.c (bidi_find_paragraph_start): Return the byte position of the paragraph beginning. xdisp.c (set_iterator_to_next): Call bidi_paragraph_init if the new_paragraph flag is set in the bidi iterator. bidi.c (bidi_at_paragraph_end, bidi_find_paragraph_start): Use the buffer-local value of paragraph-start and paragraph-separate.
Diffstat (limited to 'src')
-rw-r--r--src/ChangeLog.bidi25
-rw-r--r--src/bidi.c160
-rw-r--r--src/dispextern.h3
-rw-r--r--src/xdisp.c40
4 files changed, 176 insertions, 52 deletions
diff --git a/src/ChangeLog.bidi b/src/ChangeLog.bidi
index a2bcb3bf026..e581be7dc5b 100644
--- a/src/ChangeLog.bidi
+++ b/src/ChangeLog.bidi
@@ -1,3 +1,28 @@
12009-10-04 Eli Zaretskii <eliz@gnu.org>
2
3 * bidi.c (bidi_at_paragraph_end): Check for paragraph-start if
4 paragraph-separate failed to match. Return the length of the
5 matched separator.
6 (bidi_line_init): New function.
7 (bidi_paragraph_init): Use bidi_line_init. Do nothing if in the
8 middle of a paragraph-separate sequence. Don't override existing
9 paragraph direction if no strong characters found in this
10 paragraph. Set separator_limit according to what
11 bidi_at_paragraph_end returns. Reset new_paragraph flag when a
12 new paragraph is found.
13 (bidi_init_it): Reset separator_limit.
14
15 * dispextern.h (struct bidi_it): New member separator_limit.
16
17 * bidi.c (bidi_find_paragraph_start): Return the byte position of
18 the paragraph beginning.
19
20 * xdisp.c (set_iterator_to_next): Call bidi_paragraph_init if the
21 new_paragraph flag is set in the bidi iterator.
22
23 * bidi.c (bidi_at_paragraph_end, bidi_find_paragraph_start): Use
24 the buffer-local value of paragraph-start and paragraph-separate.
25
12009-10-03 Eli Zaretskii <eliz@gnu.org> 262009-10-03 Eli Zaretskii <eliz@gnu.org>
2 27
3 * bidi.c (bidi_set_paragraph_end): Don't set the new_paragraph 28 * bidi.c (bidi_set_paragraph_end): Don't set the new_paragraph
diff --git a/src/bidi.c b/src/bidi.c
index 2798b20aaeb..8d9e32d5c3b 100644
--- a/src/bidi.c
+++ b/src/bidi.c
@@ -733,17 +733,35 @@ bidi_peek_at_next_level (struct bidi_it *bidi_it)
733 return bidi_cache[bidi_cache_last_idx + bidi_it->scan_dir].resolved_level; 733 return bidi_cache[bidi_cache_last_idx + bidi_it->scan_dir].resolved_level;
734} 734}
735 735
736/* Return non-zero if buffer's byte position POS is the end of a 736/* Check if buffer position CHARPOS/BYTEPOS is the end of a paragraph.
737 paragraph. */ 737 Value is the non-negative length of the paragraph separator
738int 738 following the buffer position, -1 if position is at the beginning
739 of a new paragraph, or -2 if position is neither at beginning nor
740 at end of a paragraph. */
741EMACS_INT
739bidi_at_paragraph_end (EMACS_INT charpos, EMACS_INT bytepos) 742bidi_at_paragraph_end (EMACS_INT charpos, EMACS_INT bytepos)
740{ 743{
741 Lisp_Object re = XSYMBOL (Qparagraph_separate)->value; 744 Lisp_Object sep_re = Fbuffer_local_value (Qparagraph_separate,
742 745 Fcurrent_buffer ());
743 if (!STRINGP (re)) 746 Lisp_Object start_re = Fbuffer_local_value (Qparagraph_start,
744 re = fallback_paragraph_separate_re; 747 Fcurrent_buffer ());
748 EMACS_INT val;
749
750 if (!STRINGP (sep_re))
751 sep_re = fallback_paragraph_separate_re;
752 if (!STRINGP (start_re))
753 start_re = fallback_paragraph_start_re;
754
755 val = fast_looking_at (sep_re, charpos, bytepos, ZV, ZV_BYTE, Qnil);
756 if (val < 0)
757 {
758 if (fast_looking_at (start_re, charpos, bytepos, ZV, ZV_BYTE, Qnil) >= 0)
759 val = -1;
760 else
761 val = -2;
762 }
745 763
746 return fast_looking_at (re, charpos, bytepos, ZV, ZV_BYTE, Qnil) > 0; 764 return val;
747} 765}
748 766
749/* Determine the start-of-run (sor) directional type given the two 767/* Determine the start-of-run (sor) directional type given the two
@@ -779,12 +797,28 @@ bidi_set_sor_type (struct bidi_it *bidi_it, int level_before, int level_after)
779 bidi_it->ignore_bn_limit = 0; /* meaning it's unknown */ 797 bidi_it->ignore_bn_limit = 0; /* meaning it's unknown */
780} 798}
781 799
782/* Find the beginning of this paragraph by looking back in the
783 buffer. */
784static void 800static void
801bidi_line_init (struct bidi_it *bidi_it)
802{
803 bidi_it->scan_dir = 1; /* FIXME: do we need to have control on this? */
804 bidi_it->resolved_level = bidi_it->level_stack[0].level;
805 bidi_it->level_stack[0].override = NEUTRAL_DIR; /* X1 */
806 bidi_it->invalid_levels = 0;
807 bidi_it->invalid_rl_levels = -1;
808 bidi_it->next_en_pos = -1;
809 bidi_it->next_for_ws.type = UNKNOWN_BT;
810 bidi_set_sor_type (bidi_it, bidi_it->paragraph_dir,
811 bidi_it->level_stack[0].level); /* X10 */
812
813 bidi_cache_reset ();
814}
815
816/* Find the beginning of this paragraph by looking back in the buffer.
817 Value is the byte position of the paragraph's beginning. */
818static EMACS_INT
785bidi_find_paragraph_start (struct bidi_it *bidi_it) 819bidi_find_paragraph_start (struct bidi_it *bidi_it)
786{ 820{
787 Lisp_Object re = XSYMBOL (Qparagraph_start)->value; 821 Lisp_Object re = Fbuffer_local_value (Qparagraph_start, Fcurrent_buffer ());
788 EMACS_INT pos = bidi_it->charpos; 822 EMACS_INT pos = bidi_it->charpos;
789 EMACS_INT pos_byte = bidi_it->bytepos; 823 EMACS_INT pos_byte = bidi_it->bytepos;
790 EMACS_INT limit = ZV, limit_byte = ZV_BYTE; 824 EMACS_INT limit = ZV, limit_byte = ZV_BYTE;
@@ -794,10 +828,14 @@ bidi_find_paragraph_start (struct bidi_it *bidi_it)
794 while (pos_byte > BEGV_BYTE 828 while (pos_byte > BEGV_BYTE
795 && fast_looking_at (re, pos, pos_byte, limit, limit_byte, Qnil) < 0) 829 && fast_looking_at (re, pos, pos_byte, limit, limit_byte, Qnil) < 0)
796 { 830 {
797 find_next_newline_no_quit (pos, -1); 831 pos = find_next_newline_no_quit (pos - 1, -1);
832 pos_byte = CHAR_TO_BYTE (pos);
798 } 833 }
834 return pos_byte;
799} 835}
800 836
837/* Determine the direction, a.k.a. base embedding level, of the
838 paragraph we are about to iterate through. */
801void 839void
802bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it) 840bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it)
803{ 841{
@@ -807,18 +845,41 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it)
807 if (bytepos >= ZV_BYTE || bytepos < BEGV_BYTE) 845 if (bytepos >= ZV_BYTE || bytepos < BEGV_BYTE)
808 abort (); 846 abort ();
809 847
810 bidi_it->level_stack[0].level = 0; /* default for L2R */ 848 if (dir == L2R)
811 bidi_it->paragraph_dir = L2R; 849 {
812 if (dir == R2L) 850 bidi_it->paragraph_dir = L2R;
813 bidi_it->level_stack[0].level = 1; 851 bidi_it->new_paragraph = 0;
852 }
853 else if (dir == R2L)
854 {
855 bidi_it->paragraph_dir = R2L;
856 bidi_it->new_paragraph = 0;
857 }
814 else if (dir == NEUTRAL_DIR) /* P2 */ 858 else if (dir == NEUTRAL_DIR) /* P2 */
815 { 859 {
816 int ch, ch_len; 860 int ch, ch_len;
817 EMACS_INT pos; 861 EMACS_INT pos;
818 bidi_type_t type; 862 bidi_type_t type;
819 863 EMACS_INT sep_len;
820 /* Search back to where this paragraph starts. */ 864
821 bidi_find_paragraph_start (bidi_it); 865 /* If we are inside a paragraph separator, we are just waiting
866 for the separator to be exhausted; use the previous paragraph
867 direction. */
868 if (bidi_it->charpos < bidi_it->separator_limit)
869 return;
870
871 /* If we are before another paragraph separator, continue
872 through that with the previous paragraph direction. */
873 sep_len = bidi_at_paragraph_end (bidi_it->charpos, bytepos);
874 if (sep_len >= 0)
875 {
876 bidi_it->separator_limit += sep_len + 1;
877 return;
878 }
879 else if (sep_len == -2)
880 /* We are in the middle of a paragraph. Search back to where
881 this paragraph starts. */
882 bytepos = bidi_find_paragraph_start (bidi_it);
822 883
823 /* We should always be at the beginning of a new line at this 884 /* We should always be at the beginning of a new line at this
824 point. */ 885 point. */
@@ -827,9 +888,11 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it)
827 || FETCH_CHAR (bytepos - 1) == '\n')) 888 || FETCH_CHAR (bytepos - 1) == '\n'))
828 abort (); 889 abort ();
829 890
891 bidi_it->separator_limit = -1;
892 bidi_it->new_paragraph = 0;
830 ch = FETCH_CHAR (bytepos); 893 ch = FETCH_CHAR (bytepos);
831 ch_len = CHAR_BYTES (ch); 894 ch_len = CHAR_BYTES (ch);
832 pos = bidi_it->charpos; 895 pos = BYTE_TO_CHAR (bytepos);
833 type = bidi_get_type (ch, NEUTRAL_DIR); 896 type = bidi_get_type (ch, NEUTRAL_DIR);
834 897
835 for (pos++, bytepos += ch_len; 898 for (pos++, bytepos += ch_len;
@@ -843,27 +906,28 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it)
843 || type == LRE || type == LRO)); 906 || type == LRE || type == LRO));
844 type = bidi_get_type (ch, NEUTRAL_DIR)) 907 type = bidi_get_type (ch, NEUTRAL_DIR))
845 { 908 {
846 if (type == NEUTRAL_B || bidi_at_paragraph_end (pos, bytepos)) 909 if (type == NEUTRAL_B && bidi_at_paragraph_end (pos, bytepos) >= -1)
847 break; 910 break;
848 FETCH_CHAR_ADVANCE (ch, pos, bytepos); 911 FETCH_CHAR_ADVANCE (ch, pos, bytepos);
849 } 912 }
850 if (type == STRONG_R || type == STRONG_AL) /* P3 */ 913 if (type == STRONG_R || type == STRONG_AL) /* P3 */
851 bidi_it->level_stack[0].level = 1; 914 bidi_it->paragraph_dir = R2L;
915 else if (type == STRONG_L)
916 bidi_it->paragraph_dir = L2R;
852 } 917 }
853 if (bidi_it->level_stack[0].level == 1) 918 else
854 bidi_it->paragraph_dir = R2L; 919 abort ();
855 bidi_it->scan_dir = 1; /* FIXME: do we need to have control on this? */
856 bidi_it->resolved_level = bidi_it->level_stack[0].level;
857 bidi_it->level_stack[0].override = NEUTRAL_DIR; /* X1 */
858 bidi_it->invalid_levels = 0;
859 bidi_it->invalid_rl_levels = -1;
860 bidi_it->new_paragraph = 0;
861 bidi_it->next_en_pos = -1;
862 bidi_it->next_for_ws.type = UNKNOWN_BT;
863 bidi_set_sor_type (bidi_it, bidi_it->paragraph_dir,
864 bidi_it->level_stack[0].level); /* X10 */
865 920
866 bidi_cache_reset (); 921 /* Contrary to UAX#9 clause P3, we only default to L2R if we have no
922 previous usable paragraph direction. */
923 if (bidi_it->paragraph_dir == NEUTRAL_DIR)
924 bidi_it->paragraph_dir = L2R; /* P3 */
925 if (bidi_it->paragraph_dir == R2L)
926 bidi_it->level_stack[0].level == 1;
927 else
928 bidi_it->level_stack[0].level == 0;
929
930 bidi_line_init (bidi_it);
867} 931}
868 932
869/* Do whatever UAX#9 clause X8 says should be done at paragraph's 933/* Do whatever UAX#9 clause X8 says should be done at paragraph's
@@ -888,6 +952,7 @@ bidi_init_it (EMACS_INT charpos, EMACS_INT bytepos, struct bidi_it *bidi_it)
888 bidi_it->first_elt = 1; 952 bidi_it->first_elt = 1;
889 bidi_set_paragraph_end (bidi_it); 953 bidi_set_paragraph_end (bidi_it);
890 bidi_it->new_paragraph = 1; 954 bidi_it->new_paragraph = 1;
955 bidi_it->separator_limit = -1;
891 bidi_it->type = NEUTRAL_B; 956 bidi_it->type = NEUTRAL_B;
892 bidi_it->type_after_w1 = UNKNOWN_BT; 957 bidi_it->type_after_w1 = UNKNOWN_BT;
893 bidi_it->orig_type = UNKNOWN_BT; 958 bidi_it->orig_type = UNKNOWN_BT;
@@ -1802,6 +1867,10 @@ bidi_get_next_char_visually (struct bidi_it *bidi_it)
1802 bidi_it->scan_dir = 1; /* default to logical order */ 1867 bidi_it->scan_dir = 1; /* default to logical order */
1803 } 1868 }
1804 1869
1870 /* If we just passed a newline, initialize for the next line. */
1871 if (!bidi_it->first_elt && bidi_it->orig_type == NEUTRAL_B)
1872 bidi_line_init (bidi_it);
1873
1805 /* Prepare the sentinel iterator state. */ 1874 /* Prepare the sentinel iterator state. */
1806 if (bidi_cache_idx == 0) 1875 if (bidi_cache_idx == 0)
1807 { 1876 {
@@ -1875,14 +1944,23 @@ bidi_get_next_char_visually (struct bidi_it *bidi_it)
1875 } 1944 }
1876 1945
1877 /* Take note when we are at the end of the paragraph. The next time 1946 /* Take note when we are at the end of the paragraph. The next time
1878 we are about to be called, next_element_from_buffer will 1947 we are about to be called, set_iterator_to_next will
1879 automatically reinit the paragraph direction, if needed. */ 1948 automatically reinit the paragraph direction, if needed. */
1880 if (bidi_it->scan_dir == 1 1949 if (bidi_it->scan_dir == 1
1881 && bidi_it->type == NEUTRAL_B 1950 && bidi_it->orig_type == NEUTRAL_B
1882 && bidi_it->bytepos < ZV_BYTE 1951 && bidi_it->bytepos < ZV_BYTE)
1883 && bidi_at_paragraph_end (bidi_it->charpos + 1, 1952 {
1884 bidi_it->bytepos + bidi_it->ch_len)) 1953 EMACS_INT sep_len =
1885 bidi_it->new_paragraph = 1; 1954 bidi_at_paragraph_end (bidi_it->charpos + 1,
1955 bidi_it->bytepos + bidi_it->ch_len);
1956 if (sep_len >= 0)
1957 {
1958 bidi_it->new_paragraph = 1;
1959 /* Record the buffer position of the first character after
1960 the paragraph separator. */
1961 bidi_it->separator_limit = bidi_it->charpos + 1 + sep_len + 1;
1962 }
1963 }
1886 1964
1887 if (bidi_it->scan_dir == 1 && bidi_cache_idx) 1965 if (bidi_it->scan_dir == 1 && bidi_cache_idx)
1888 { 1966 {
diff --git a/src/dispextern.h b/src/dispextern.h
index b5b6dc7f618..6928d8ae1b8 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -1772,7 +1772,8 @@ struct bidi_it {
1772 int resolved_level; /* final resolved level of this character */ 1772 int resolved_level; /* final resolved level of this character */
1773 int invalid_levels; /* how many PDFs to ignore */ 1773 int invalid_levels; /* how many PDFs to ignore */
1774 int invalid_rl_levels; /* how many PDFs from RLE/RLO to ignore */ 1774 int invalid_rl_levels; /* how many PDFs from RLE/RLO to ignore */
1775 int new_paragraph; /* if non-zero, a new paragraph begins here */ 1775 int new_paragraph; /* if non-zero, we expect a new paragraph */
1776 EMACS_INT separator_limit; /* where paragraph separator should end */
1776 bidi_dir_t paragraph_dir; /* current paragraph direction */ 1777 bidi_dir_t paragraph_dir; /* current paragraph direction */
1777 int prev_was_pdf; /* if non-zero, previous char was PDF */ 1778 int prev_was_pdf; /* if non-zero, previous char was PDF */
1778 struct bidi_saved_info prev; /* info about previous character */ 1779 struct bidi_saved_info prev; /* info about previous character */
diff --git a/src/xdisp.c b/src/xdisp.c
index e77a197006d..7597b2c98ed 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -6103,6 +6103,10 @@ set_iterator_to_next (it, reseat_p)
6103 } 6103 }
6104 else 6104 else
6105 { 6105 {
6106 /* If this is a new paragraph, determine its base
6107 direction (a.k.a. its base embedding level). */
6108 if (it->bidi_it.new_paragraph)
6109 bidi_paragraph_init (NEUTRAL_DIR, &it->bidi_it);
6106 bidi_get_next_char_visually (&it->bidi_it); 6110 bidi_get_next_char_visually (&it->bidi_it);
6107 IT_BYTEPOS (*it) = it->bidi_it.bytepos; 6111 IT_BYTEPOS (*it) = it->bidi_it.bytepos;
6108 IT_CHARPOS (*it) = it->bidi_it.charpos; 6112 IT_CHARPOS (*it) = it->bidi_it.charpos;
@@ -6508,8 +6512,10 @@ next_element_from_buffer (it)
6508 6512
6509 xassert (IT_CHARPOS (*it) >= BEGV); 6513 xassert (IT_CHARPOS (*it) >= BEGV);
6510 6514
6511 /* With bidi reordering, the character to display might not be 6515 /* With bidi reordering, the character to display might not be the
6512 the character at IT_CHARPOS. */ 6516 character at IT_CHARPOS. BIDI_IT.FIRST_ELT non-zero means that
6517 we were reseat()ed to a new buffer position, which is potentially
6518 a different paragraph. */
6513 if (it->bidi_p && it->bidi_it.first_elt) 6519 if (it->bidi_p && it->bidi_it.first_elt)
6514 { 6520 {
6515 it->bidi_it.charpos = IT_CHARPOS (*it); 6521 it->bidi_it.charpos = IT_CHARPOS (*it);
@@ -6521,13 +6527,9 @@ next_element_from_buffer (it)
6521 || FETCH_CHAR (it->bidi_it.bytepos - 1) == '\n' 6527 || FETCH_CHAR (it->bidi_it.bytepos - 1) == '\n'
6522 || FETCH_CHAR (it->bidi_it.bytepos) == '\n') 6528 || FETCH_CHAR (it->bidi_it.bytepos) == '\n')
6523 { 6529 {
6524 /* FIXME: L2R below is just for easyness of testing, as we 6530 /* FIXME: NEUTRAL_DIR below should be user-definable and/or
6525 currently support only left-to-right paragraphs. The 6531 come from some ``higher protocol''. */
6526 value should be user-definable and/or come from some 6532 bidi_paragraph_init (NEUTRAL_DIR, &it->bidi_it);
6527 ``higher protocol''. In the absence of any other
6528 guidance, the default for this initialization should be
6529 NEUTRAL_DIR. */
6530 bidi_paragraph_init (L2R, &it->bidi_it);
6531 bidi_get_next_char_visually (&it->bidi_it); 6533 bidi_get_next_char_visually (&it->bidi_it);
6532 } 6534 }
6533 else 6535 else
@@ -6541,7 +6543,7 @@ next_element_from_buffer (it)
6541 IT_BYTEPOS (*it) = CHAR_TO_BYTE (IT_CHARPOS (*it)); 6543 IT_BYTEPOS (*it) = CHAR_TO_BYTE (IT_CHARPOS (*it));
6542 it->bidi_it.charpos = IT_CHARPOS (*it); 6544 it->bidi_it.charpos = IT_CHARPOS (*it);
6543 it->bidi_it.bytepos = IT_BYTEPOS (*it); 6545 it->bidi_it.bytepos = IT_BYTEPOS (*it);
6544 bidi_paragraph_init (L2R, &it->bidi_it); 6546 bidi_paragraph_init (NEUTRAL_DIR, &it->bidi_it);
6545 do { 6547 do {
6546 /* Now return to buffer position where we were asked to 6548 /* Now return to buffer position where we were asked to
6547 get the next display element, and produce that. */ 6549 get the next display element, and produce that. */
@@ -16314,6 +16316,7 @@ extend_face_to_end_of_line (it)
16314 Lisp_Object saved_object; 16316 Lisp_Object saved_object;
16315 enum display_element_type saved_what = it->what; 16317 enum display_element_type saved_what = it->what;
16316 int saved_face_id = it->face_id; 16318 int saved_face_id = it->face_id;
16319 int text_len = it->glyph_row->used[TEXT_AREA];
16317 16320
16318 saved_object = it->object; 16321 saved_object = it->object;
16319 saved_pos = it->position; 16322 saved_pos = it->position;
@@ -16330,6 +16333,23 @@ extend_face_to_end_of_line (it)
16330 while (it->current_x <= it->last_visible_x) 16333 while (it->current_x <= it->last_visible_x)
16331 PRODUCE_GLYPHS (it); 16334 PRODUCE_GLYPHS (it);
16332 16335
16336 /* If the paragraph base direction is right to left, reverse the
16337 glyphs of non-empty line. */
16338 if (it->bidi_p && it->bidi_it.level_stack[0].level == 1
16339 && text_len > 0)
16340 {
16341 struct glyph *gleft = it->glyph_row->glyphs[TEXT_AREA];
16342 struct glyph *gright = gleft + it->glyph_row->used[TEXT_AREA] - 1;
16343 struct glyph tem;
16344
16345 for ( ; gleft < gright; gleft++, gright--)
16346 {
16347 tem = *gleft;
16348 *gleft = *gright;
16349 *gright = tem;
16350 }
16351 }
16352
16333 /* Don't count these blanks really. It would let us insert a left 16353 /* Don't count these blanks really. It would let us insert a left
16334 truncation glyph below and make us set the cursor on them, maybe. */ 16354 truncation glyph below and make us set the cursor on them, maybe. */
16335 it->current_x = saved_x; 16355 it->current_x = saved_x;