e61efd379871e570f55b20a9e3e74adc5cd8682b
[m17n/m17n-lib.git] / src / mtext-lbrk.c
1 /* mtext-lbrk.c -- line break
2    Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010
3      National Institute of Advanced Industrial Science and Technology (AIST)
4      Registration Number H15PRO112
5
6    This file is part of the m17n library.
7
8    The m17n library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public License
10    as published by the Free Software Foundation; either version 2.1 of
11    the License, or (at your option) any later version.
12
13    The m17n library 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 GNU
16    Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with the m17n library; if not, write to the Free
20    Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21    02111-1307, USA.  */
22
23 #if !defined (FOR_DOXYGEN) || defined (DOXYGEN_INTERNAL_MODULE)
24 /*** @addtogroup m17nInternal
25      @{ */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "config.h"
32 #include "m17n.h"
33 #include "m17n-misc.h"
34 #include "internal.h"
35 #include "mtext.h"
36
37 enum LineBreakClass
38   {
39     LBC_OP, /* open */
40     LBC_CL, /* close */
41     LBC_QU, /* quotation */
42     LBC_GL, /* glue */
43     LBC_NS, /* no-start */
44     LBC_EX, /* exclamation/interrogation */
45     LBC_SY, /* Syntax (slash) */
46     LBC_IS, /* infix (numeric) separator */
47     LBC_PR, /* prefix */
48     LBC_PO, /* postfix */
49     LBC_NU, /* numeric */
50     LBC_AL, /* alphabetic */
51     LBC_ID, /* ideograph (atomic) */
52     LBC_IN, /* inseparable */
53     LBC_HY, /* hyphen */
54     LBC_BA, /* break after */
55     LBC_BB, /* break before */
56     LBC_B2, /* break both */
57     LBC_ZW, /* ZW space */
58     LBC_CM, /* combining mark */
59     LBC_WJ, /* word joiner */
60
61     /* used for 4.1 pair table */
62     LBC_H2, /* Hamgul 2 Jamo Syllable */
63     LBC_H3, /* Hangul 3 Jamo Syllable */
64     LBC_JL, /* Jamo leading consonant */
65     LBC_JV, /* Jamo vowel */
66     LBC_JT, /* Jamo trailing consonant */
67
68     /* These are not handled in the pair tables. */
69     LBC_SA, /* south (east) asian */
70     LBC_SP, /* space */
71     LBC_PS, /* paragraph and line separators */
72     LBC_BK, /* hard break (newline) */
73     LBC_CR, /* carriage return */
74     LBC_LF, /* line feed */
75     LBC_NL, /* next line */
76     LBC_CB, /* contingent break opportunity */
77     LBC_SG, /* surrogate */
78     LBC_AI, /* ambiguous */
79     LBC_XX, /* unknown */
80     LBC_MAX
81   };
82
83 enum LineBreakAction
84   {
85     LBA_DIRECT =                '_',
86     LBA_INDIRECT =              '%',
87     LBA_COMBINING_INDIRECT =    '#',
88     LBA_COMBINING_PROHIBITED =  '@',
89     LBA_PROHIBITED =            '^',
90     LBA_MAX
91   };
92
93 /* The pair table of line break actions.  */
94 static char *lba_pair_table[] =
95   /* OP GL SY PO ID BA ZW H2 JV
96       CL NS IS NU IN BB CM H3 JT
97        QU EX PR AL HY B2 WJ JL  */
98   { "^^^^^^^^^^^^^^^^^^^@^^^^^^", /* OP */
99     "_^%%^^^^_%____%%__^#^_____", /* CL */
100     "^^%%%^^^%%%%%%%%%%^#^%%%%%", /* QU */
101     "%^%%%^^^%%%%%%%%%%^#^%%%%%", /* GL */
102     "_^%%%^^^______%%__^#^_____", /* NS */
103     "_^%%%^^^______%%__^#^_____", /* EX */
104     "_^%%%^^^__%___%%__^#^_____", /* SY */
105     "_^%%%^^^__%%__%%__^#^_____", /* IS */
106     "%^%%%^^^__%%%_%%__^#^%%%%%", /* PR */
107     "_^%%%^^^______%%__^#^_____", /* PO */
108     "_^%%%^^^_%%%_%%%__^#^_____", /* NU */
109     "_^%%%^^^__%%_%%%__^#^_____", /* AL */
110     "_^%%%^^^_%___%%%__^#^_____", /* ID */
111     "_^%%%^^^_____%%%__^#^_____", /* IN */
112     "_^%%%^^^__%___%%__^#^_____", /* HY */
113     "_^%%%^^^______%%__^#^_____", /* BA */
114     "%^%%%^^^%%%%%%%%%%^#^%%%%%", /* BB */
115     "_^%%%^^^______%%_^^#^_____", /* B2 */
116     "__________________^_______", /* ZW */
117     "_^%%%^^^__%%_%%%__^#^_____", /* CM */
118     "%^%%%^^^%%%%%%%%%%^#^%%%%%", /* WJ */
119     "_^%%%^^^_%___%%%__^#^___%%", /* H2 */
120     "_^%%%^^^_%___%%%__^#^____%", /* H3 */
121     "_^%%%^^^_%___%%%__^#^%%%%_", /* JL */
122     "_^%%%^^^_%___%%%__^#^___%%", /* JV */
123     "_^%%%^^^_%___%%%__^#^____%"  /* JT */
124   };
125
126 static MCharTable *lbc_table;
127
128 /* Set LBC to enum LineBreakClass of the character at POS of MT
129    (length is LEN) while converting LBC_AI and LBC_XX to LBC_AL,
130    LBC_CB to LBC_B2, LBC_CR, LBC_LF, and LBC_NL to LBC_BK.  If POS is
131    out of range, set LBC to LBC_BK.  */
132
133 #define GET_LBC(LBC, MT, LEN, POS, OPTION)                              \
134   do {                                                                  \
135     if ((POS) < 0 || (POS) >= (LEN))                                    \
136       (LBC) = LBC_BK;                                                   \
137     else                                                                \
138       {                                                                 \
139         int c = mtext_ref_char ((MT), (POS));                           \
140         (LBC) = (enum LineBreakClass) mchartable_lookup (lbc_table, c); \
141         if ((LBC) == LBC_NL)                                            \
142           (LBC) = LBC_BK;                                               \
143         else if ((LBC) == LBC_AI)                                       \
144           (LBC) = ((OPTION) & MTEXT_LBO_AI_AS_ID) ? LBC_ID : LBC_AL;    \
145         else if (! ((OPTION) & MTEXT_LBO_KOREAN_SP)                     \
146                  && (LBC) >= LBC_H2 && (LBC) <= LBC_JT)                 \
147           (LBC) = LBC_AL;                                               \
148         else if ((LBC) == LBC_CB)                                       \
149           (LBC) = LBC_B2;                                               \
150         else if ((LBC) == LBC_XX)                                       \
151           (LBC) = LBC_AL;                                               \
152       }                                                                 \
153   } while (0)
154
155
156 /*** @} */
157 #endif /* !FOR_DOXYGEN || DOXYGEN_INTERNAL_MODULE */
158
159 \f
160 /* External API */
161
162 /*** @addtogroup m17nMtext */
163 /*** @{ */
164 /*=*/
165
166 /***en
167     @brief Find a linebreak postion of an M-text.
168
169     The mtext_line_break () function checks if position $POS is a
170     proper linebreak position of an M-text $MT according to the
171     algorithm of The Unicode Standard 4.0 UAX#14.  It so, it returns
172     $POS.  Otherwise, it returns a proper linebreak position before
173     $POS.
174
175     If $OPTION is nonzero, it controls the algorithm by logical-or of
176     the members of #MTextLineBreakOption.
177
178     If $AFTER is not NULL, a proper linebreak position after $POS is
179     stored there.  */
180
181 int
182 mtext_line_break (MText *mt, int pos, int option, int *after)
183 {
184   int break_before, break_after;
185   int len = mtext_len (mt);
186   enum LineBreakClass lbc;
187   enum LineBreakClass Blbc, Albc; /* B(efore) and A(fter) lbcs.  */
188   int Bpos, Apos;                 /* B(efore) and A(fter) positions.  */
189   enum LineBreakAction action;
190   
191   if (pos >= len)
192     {
193       /* The end of text is an explicit break position.  */
194       if (after)
195         *after = pos;
196       return pos;
197     }
198
199   if (! lbc_table)
200     {
201       MSymbol key = mchar_define_property ("linebreak", Minteger);
202
203       lbc_table = mchar_get_prop_table (key, NULL);
204     }
205
206   GET_LBC (lbc, mt, len, pos, option);
207   Apos = pos;
208   Albc = lbc;
209   if (Albc == LBC_SP)
210     {
211       if (option & MTEXT_LBO_SP_CM)
212         {
213           GET_LBC (Albc, mt, len, Apos + 1, option);
214           Albc = (Albc == LBC_CM) ? LBC_ID : LBC_SP;
215         }
216       while (Albc == LBC_SP)
217         {
218           Apos--;
219           GET_LBC (Albc, mt, len, Apos, option);
220         }
221     }
222   if ((option & MTEXT_LBO_SP_CM) && (Albc == LBC_CM))
223     {
224       Apos--;
225       GET_LBC (Albc, mt, len, Apos, option);
226       if (Albc == LBC_SP)
227         Albc = LBC_ID;
228       else
229         Apos++, Albc = LBC_CM;
230     }
231
232   if (Albc == LBC_CR)
233     Albc = LBC_BK;
234   else if (Albc == LBC_LF)
235     {
236       GET_LBC (Albc, mt, len, Apos - 1, option);
237       if (Albc == LBC_CR)
238         Apos--;
239       Albc = LBC_BK;
240     }
241   else if (Albc == LBC_SA)
242     Albc = mtext__word_segment (mt, Apos, &Apos, NULL) > 0 ? LBC_BB : LBC_AL;
243   Bpos = Apos;
244   /* After exiting from the following loop, if Apos is positive, it is
245      the previous (including POS) break position.  */
246   while (Apos > 0)
247     {
248       int indirect;
249       int next = -1;
250
251       /* Now Bpos == Apos.  */
252       do {
253         Bpos--;
254         GET_LBC (Blbc, mt, len, Bpos, option);
255       } while (Blbc == LBC_SP);
256
257       if (Blbc == LBC_BK || Blbc == LBC_LF || Blbc == LBC_CR)
258         {
259           /* Explicit break.  */
260           break;
261         }
262
263       indirect = Bpos + 1 < Apos;
264
265       if (Blbc == LBC_CM)
266         {
267           do {
268               Bpos--;
269               GET_LBC (Blbc, mt, len, Bpos, option);
270           } while (Blbc == LBC_CM);
271           if ((option & MTEXT_LBO_SP_CM) && (Blbc == LBC_SP))
272             Blbc = LBC_ID;
273           else if (Blbc == LBC_SP || Blbc == LBC_ZW
274                    || Blbc == LBC_BK || Blbc == LBC_LF || Blbc == LBC_CR)
275             {
276               Blbc = LBC_AL;
277               Bpos++;
278             }
279         }                  
280       if (Blbc == LBC_SA)
281         {
282           mtext__word_segment (mt, Bpos, &next, NULL);
283           Blbc = LBC_AL;
284         }
285
286       if (Albc != LBC_BK)
287         {
288           action = lba_pair_table[Blbc][Albc];
289           if (action == LBA_DIRECT)
290             break;
291           else if (action == LBA_INDIRECT)
292             {
293               if (indirect)
294                 break;
295             }
296           else if (action == LBA_COMBINING_INDIRECT)
297             {
298               if (indirect)
299                 break;
300             }
301         }
302       if (next >= 0)
303         Apos = next, Albc = LBC_BB;
304       else
305         Apos = Bpos, Albc = Blbc;
306     }
307   break_before = Apos;
308   if (break_before > 0)
309     {
310       if (! after)
311         return break_before;
312       if (break_before == pos)
313         {
314           if (after)
315             *after = break_before;
316           return break_before;
317         }
318     }
319
320   /* Now find a break position after POS.  */
321   break_after = 0;
322   Bpos = pos;
323   Blbc = lbc;
324   if (Blbc == LBC_CM)
325     {
326       do {
327         Bpos--;
328         GET_LBC (Blbc, mt, len, Bpos, option);
329       } while (Blbc == LBC_CM);
330       if (Blbc == LBC_SP || Blbc == LBC_ZW
331           || Blbc == LBC_BK || Blbc == LBC_LF || Blbc == LBC_CR)
332         {
333           if ((Blbc == LBC_SP) && (option & MTEXT_LBO_SP_CM))
334             Blbc = LBC_ID;
335           else
336             Blbc = LBC_AL;
337         }
338       Bpos = pos;
339     }
340   if (Blbc == LBC_SA)
341     {
342       mtext__word_segment (mt, Bpos, NULL, &Bpos);
343       Blbc = LBC_AL;
344     }
345   else if (Blbc == LBC_SP)
346     {
347       if (option & MTEXT_LBO_SP_CM)
348         {
349           GET_LBC (Blbc, mt, len, Bpos + 1, option);
350           if (Blbc == LBC_CM)
351             Blbc = LBC_ID, Bpos++;
352           else
353             Blbc = LBC_SP;
354         }
355       while (Blbc == LBC_SP)
356         {
357           Bpos--;
358           GET_LBC (Blbc, mt, len, Bpos, option);
359         }
360       if (Bpos < 0)
361         Bpos = pos;
362     }
363   Apos = Bpos;
364   /* After exiting from the following loop, if Apos is positive, it is
365      the next break position.  */
366   while (1)
367     {
368       int indirect;
369       int next = -1;
370
371       /* Now Bpos == Apos.  */
372       if (Blbc == LBC_LF || Blbc == LBC_BK || Blbc == LBC_CR)
373         {
374           Apos++;
375           if (Blbc == LBC_CR)
376             {
377               GET_LBC (Blbc, mt, len, Bpos + 1, option);
378               if (Blbc == LBC_LF)
379                 Apos++;
380             }
381           break;
382         }
383
384       do {
385         Apos++;
386         GET_LBC (Albc, mt, len, Apos, option);
387       } while (Albc == LBC_SP);
388       
389       if (Blbc == LBC_SP)
390         break;
391
392       if (Apos == len)
393         /* Explicit break at the end of text.  */
394         break;
395
396       indirect = Bpos + 1 < Apos;
397
398       if (Albc == LBC_SA)
399         Albc = mtext__word_segment (mt, Apos, NULL, &next) ? LBC_BB : LBC_AL;
400
401       action = lba_pair_table[Blbc][Albc];
402       if (action == LBA_DIRECT)
403         /* Direct break at Apos.  */
404         break;
405       else if (action == LBA_INDIRECT)
406         {
407           if (indirect)
408             break;
409         }
410       else if (action == LBA_COMBINING_INDIRECT)
411         {
412           if (indirect)
413             {
414               if (option & MTEXT_LBO_SP_CM)
415                 Apos--;
416               break;
417             }
418         }
419       if (next >= 0)
420         Bpos = next, Blbc = LBC_AL;
421       else
422         {
423           Bpos = Apos;
424           if (Albc != LBC_CM)
425             Blbc = Albc;
426         }
427     }
428   break_after = Apos;
429   if (after)
430     *after = break_after;
431
432   return (break_before > 0 ? break_before : break_after);
433 }
434
435 /*** @} */ 
436
437 /*
438   Local Variables:
439   coding: euc-japan
440   End:
441 */