ucpgba.c revision 1.3
1/*	$NetBSD: ucpgba.c,v 1.3 2021/08/14 16:14:57 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1998-2021 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17/* Copyright 2001 Computing Research Labs, New Mexico State University
18 *
19 * Permission is hereby granted, free of charge, to any person obtaining a
20 * copy of this software and associated documentation files (the "Software"),
21 * to deal in the Software without restriction, including without limitation
22 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
23 * and/or sell copies of the Software, and to permit persons to whom the
24 * Software is furnished to do so, subject to the following conditions:
25 *
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
28 *
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
32 * THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY
33 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
34 * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
35 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 */
37/* Id: ucpgba.c,v 1.5 2001/01/02 18:46:20 mleisher Exp  */
38
39#include <sys/cdefs.h>
40__RCSID("$NetBSD: ucpgba.c,v 1.3 2021/08/14 16:14:57 christos Exp $");
41
42#include "portable.h"
43
44#include <stdio.h>
45#include <stdlib.h>
46
47#include "ucdata.h"
48#include "ucpgba.h"
49
50/*
51 * These macros are used while reordering of RTL runs of text for the
52 * special case of non-spacing characters being in runs of weakly
53 * directional text.  They check for weak and non-spacing, and digits and
54 * non-spacing.
55 */
56#define ISWEAKSPECIAL(cc)  ucisprop(cc, UC_EN|UC_ES|UC_MN, UC_ET|UC_AN|UC_CS)
57#define ISDIGITSPECIAL(cc) ucisprop(cc, UC_ND|UC_MN, 0)
58
59/*
60 * These macros are used while breaking a string into runs of text in
61 * different directions.  Descriptions:
62 *
63 * ISLTR_LTR - Test for members of an LTR run in an LTR context.  This looks
64 *             for characters with ltr, non-spacing, weak, and neutral
65 *             properties.
66 *
67 * ISRTL_RTL - Test for members of an RTL run in an RTL context.  This looks
68 *             for characters with rtl, non-spacing, weak, and neutral
69 *             properties.
70 *
71 * ISRTL_NEUTRAL  - Test for RTL or neutral characters.
72 *
73 * ISWEAK_NEUTRAL - Test for weak or neutral characters.
74 */
75#define ISLTR_LTR(cc) ucisprop(cc, UC_L|UC_MN|UC_EN|UC_ES,\
76                               UC_ET|UC_CS|UC_B|UC_S|UC_WS|UC_ON)
77
78#define ISRTL_RTL(cc) ucisprop(cc, UC_R|UC_MN|UC_EN|UC_ES,\
79                               UC_ET|UC_AN|UC_CS|UC_B|UC_S|UC_WS|UC_ON)
80
81#define ISRTL_NEUTRAL(cc) ucisprop(cc, UC_R, UC_B|UC_S|UC_WS|UC_ON)
82#define ISWEAK_NEUTRAL(cc) ucisprop(cc, UC_EN|UC_ES, \
83                                    UC_B|UC_S|UC_WS|UC_ON|UC_ET|UC_AN|UC_CS)
84
85/*
86 * This table is temporarily hard-coded here until it can be constructed
87 * automatically somehow.
88 */
89static unsigned long _symmetric_pairs[] = {
90    0x0028, 0x0029, 0x0029, 0x0028, 0x003C, 0x003E, 0x003E, 0x003C,
91    0x005B, 0x005D, 0x005D, 0x005B, 0x007B, 0x007D, 0x007D, 0x007B,
92    0x2045, 0x2046, 0x2046, 0x2045, 0x207D, 0x207E, 0x207E, 0x207D,
93    0x208D, 0x208E, 0x208E, 0x208D, 0x3008, 0x3009, 0x3009, 0x3008,
94    0x300A, 0x300B, 0x300B, 0x300A, 0x300C, 0x300D, 0x300D, 0x300C,
95    0x300E, 0x300F, 0x300F, 0x300E, 0x3010, 0x3011, 0x3011, 0x3010,
96    0x3014, 0x3015, 0x3015, 0x3014, 0x3016, 0x3017, 0x3017, 0x3016,
97    0x3018, 0x3019, 0x3019, 0x3018, 0x301A, 0x301B, 0x301B, 0x301A,
98    0xFD3E, 0xFD3F, 0xFD3F, 0xFD3E, 0xFE59, 0xFE5A, 0xFE5A, 0xFE59,
99    0xFE5B, 0xFE5C, 0xFE5C, 0xFE5B, 0xFE5D, 0xFE5E, 0xFE5E, 0xFE5D,
100    0xFF08, 0xFF09, 0xFF09, 0xFF08, 0xFF3B, 0xFF3D, 0xFF3D, 0xFF3B,
101    0xFF5B, 0xFF5D, 0xFF5D, 0xFF5B, 0xFF62, 0xFF63, 0xFF63, 0xFF62,
102};
103
104static int _symmetric_pairs_size =
105sizeof(_symmetric_pairs)/sizeof(_symmetric_pairs[0]);
106
107/*
108 * This routine looks up the other form of a symmetric pair.
109 */
110static unsigned long
111_ucsymmetric_pair(unsigned long c)
112{
113    int i;
114
115    for (i = 0; i < _symmetric_pairs_size; i += 2) {
116        if (_symmetric_pairs[i] == c)
117          return _symmetric_pairs[i+1];
118    }
119    return c;
120}
121
122/*
123 * This routine creates a new run, copies the text into it, links it into the
124 * logical text order chain and returns it to the caller to be linked into
125 * the visual text order chain.
126 */
127static ucrun_t *
128_add_run(ucstring_t *str, unsigned long *src,
129         unsigned long start, unsigned long end, int direction)
130{
131    long i, t;
132    ucrun_t *run;
133
134    run = (ucrun_t *) malloc(sizeof(ucrun_t));
135    run->visual_next = run->visual_prev = 0;
136    run->direction = direction;
137
138    run->cursor = ~0;
139
140    run->chars = (unsigned long *)
141        malloc(sizeof(unsigned long) * ((end - start) << 1));
142    run->positions = run->chars + (end - start);
143
144    run->source = src;
145    run->start = start;
146    run->end = end;
147
148    if (direction == UCPGBA_RTL) {
149        /*
150         * Copy the source text into the run in reverse order and select
151         * replacements for the pairwise punctuation and the <> characters.
152         */
153        for (i = 0, t = end - 1; start < end; start++, t--, i++) {
154            run->positions[i] = t;
155            if (ucissymmetric(src[t]) || src[t] == '<' || src[t] == '>')
156              run->chars[i] = _ucsymmetric_pair(src[t]);
157            else
158              run->chars[i] = src[t];
159        }
160    } else {
161        /*
162         * Copy the source text into the run directly.
163         */
164        for (i = start; i < end; i++) {
165            run->positions[i - start] = i;
166            run->chars[i - start] = src[i];
167        }
168    }
169
170    /*
171     * Add the run to the logical list for cursor traversal.
172     */
173    if (str->logical_first == 0)
174      str->logical_first = str->logical_last = run;
175    else {
176        run->logical_prev = str->logical_last;
177        str->logical_last->logical_next = run;
178        str->logical_last = run;
179    }
180
181    return run;
182}
183
184static void
185_ucadd_rtl_segment(ucstring_t *str, unsigned long *source, unsigned long start,
186                   unsigned long end)
187{
188    unsigned long s, e;
189    ucrun_t *run, *lrun;
190
191    /*
192     * This is used to splice runs into strings with overall LTR direction.
193     * The `lrun' variable will never be NULL because at least one LTR run was
194     * added before this RTL run.
195     */
196    lrun = str->visual_last;
197
198    for (e = s = start; s < end;) {
199        for (; e < end && ISRTL_NEUTRAL(source[e]); e++) ;
200
201        if (e > s) {
202            run = _add_run(str, source, s, e, UCPGBA_RTL);
203
204            /*
205             * Add the run to the visual list for cursor traversal.
206             */
207            if (str->visual_first != 0) {
208                if (str->direction == UCPGBA_LTR) {
209                    run->visual_prev = lrun;
210                    run->visual_next = lrun->visual_next;
211                    if (lrun->visual_next != 0)
212                      lrun->visual_next->visual_prev = run;
213                    lrun->visual_next = run;
214                    if (lrun == str->visual_last)
215                      str->visual_last = run;
216                } else {
217                    run->visual_next = str->visual_first;
218                    str->visual_first->visual_prev = run;
219                    str->visual_first = run;
220                }
221            } else
222              str->visual_first = str->visual_last = run;
223        }
224
225        /*
226         * Handle digits in a special way.  This makes sure the weakly
227         * directional characters appear on the expected sides of a number
228         * depending on whether that number is Arabic or not.
229         */
230        for (s = e; e < end && ISWEAKSPECIAL(source[e]); e++) {
231            if (!ISDIGITSPECIAL(source[e]) &&
232                (e + 1 == end || !ISDIGITSPECIAL(source[e + 1])))
233              break;
234        }
235
236        if (e > s) {
237            run = _add_run(str, source, s, e, UCPGBA_LTR);
238
239            /*
240             * Add the run to the visual list for cursor traversal.
241             */
242            if (str->visual_first != 0) {
243                if (str->direction == UCPGBA_LTR) {
244                    run->visual_prev = lrun;
245                    run->visual_next = lrun->visual_next;
246                    if (lrun->visual_next != 0)
247                      lrun->visual_next->visual_prev = run;
248                    lrun->visual_next = run;
249                    if (lrun == str->visual_last)
250                      str->visual_last = run;
251                } else {
252                    run->visual_next = str->visual_first;
253                    str->visual_first->visual_prev = run;
254                    str->visual_first = run;
255                }
256            } else
257              str->visual_first = str->visual_last = run;
258        }
259
260        /*
261         * Collect all weak non-digit sequences for an RTL segment.  These
262         * will appear as part of the next RTL segment or will be added as
263         * an RTL segment by themselves.
264         */
265        for (s = e; e < end && ucisweak(source[e]) && !ucisdigit(source[e]);
266             e++) ;
267    }
268
269    /*
270     * Capture any weak non-digit sequences that occur at the end of the RTL
271     * run.
272     */
273    if (e > s) {
274        run = _add_run(str, source, s, e, UCPGBA_RTL);
275
276        /*
277         * Add the run to the visual list for cursor traversal.
278         */
279        if (str->visual_first != 0) {
280            if (str->direction == UCPGBA_LTR) {
281                run->visual_prev = lrun;
282                run->visual_next = lrun->visual_next;
283                if (lrun->visual_next != 0)
284                  lrun->visual_next->visual_prev = run;
285                lrun->visual_next = run;
286                if (lrun == str->visual_last)
287                  str->visual_last = run;
288            } else {
289                run->visual_next = str->visual_first;
290                str->visual_first->visual_prev = run;
291                str->visual_first = run;
292            }
293        } else
294          str->visual_first = str->visual_last = run;
295    }
296}
297
298static void
299_ucadd_ltr_segment(ucstring_t *str, unsigned long *source, unsigned long start,
300                   unsigned long end)
301{
302    ucrun_t *run;
303
304    run = _add_run(str, source, start, end, UCPGBA_LTR);
305
306    /*
307     * Add the run to the visual list for cursor traversal.
308     */
309    if (str->visual_first != 0) {
310        if (str->direction == UCPGBA_LTR) {
311            run->visual_prev = str->visual_last;
312            str->visual_last->visual_next = run;
313            str->visual_last = run;
314        } else {
315            run->visual_next = str->visual_first;
316            str->visual_first->visual_prev = run;
317            str->visual_first = run;
318        }
319    } else
320      str->visual_first = str->visual_last = run;
321}
322
323ucstring_t *
324ucstring_create(unsigned long *source, unsigned long start, unsigned long end,
325                int default_direction, int cursor_motion)
326{
327    int rtl_first;
328    unsigned long s, e, ld;
329    ucstring_t *str;
330
331    str = (ucstring_t *) malloc(sizeof(ucstring_t));
332
333    /*
334     * Set the initial values.
335     */
336    str->cursor_motion = cursor_motion;
337    str->logical_first = str->logical_last = 0;
338    str->visual_first = str->visual_last = str->cursor = 0;
339    str->source = source;
340    str->start = start;
341    str->end = end;
342
343    /*
344     * If the length of the string is 0, then just return it at this point.
345     */
346    if (start == end)
347      return str;
348
349    /*
350     * This flag indicates whether the collection loop for RTL is called
351     * before the LTR loop the first time.
352     */
353    rtl_first = 0;
354
355    /*
356     * Look for the first character in the string that has strong
357     * directionality.
358     */
359    for (s = start; s < end && !ucisstrong(source[s]); s++) ;
360
361    if (s == end)
362      /*
363       * If the string contains no characters with strong directionality, use
364       * the default direction.
365       */
366      str->direction = default_direction;
367    else
368      str->direction = ucisrtl(source[s]) ? UCPGBA_RTL : UCPGBA_LTR;
369
370    if (str->direction == UCPGBA_RTL)
371      /*
372       * Set the flag that causes the RTL collection loop to run first.
373       */
374      rtl_first = 1;
375
376    /*
377     * This loop now separates the string into runs based on directionality.
378     */
379    for (s = e = 0; s < end; s = e) {
380        if (!rtl_first) {
381            /*
382             * Determine the next run of LTR text.
383             */
384
385            ld = s;
386            while (e < end && ISLTR_LTR(source[e])) {
387                if (ucisdigit(source[e]) &&
388                    !(0x660 <= source[e] && source[e] <= 0x669))
389                  ld = e;
390                e++;
391            }
392            if (str->direction != UCPGBA_LTR) {
393                while (e > ld && ISWEAK_NEUTRAL(source[e - 1]))
394                  e--;
395            }
396
397            /*
398             * Add the LTR segment to the string.
399             */
400            if (e > s)
401              _ucadd_ltr_segment(str, source, s, e);
402        }
403
404        /*
405         * Determine the next run of RTL text.
406         */
407        ld = s = e;
408        while (e < end && ISRTL_RTL(source[e])) {
409            if (ucisdigit(source[e]) &&
410                !(0x660 <= source[e] && source[e] <= 0x669))
411              ld = e;
412            e++;
413        }
414        if (str->direction != UCPGBA_RTL) {
415            while (e > ld && ISWEAK_NEUTRAL(source[e - 1]))
416              e--;
417        }
418
419        /*
420         * Add the RTL segment to the string.
421         */
422        if (e > s)
423          _ucadd_rtl_segment(str, source, s, e);
424
425        /*
426         * Clear the flag that allowed the RTL collection loop to run first
427         * for strings with overall RTL directionality.
428         */
429        rtl_first = 0;
430    }
431
432    /*
433     * Set up the initial cursor run.
434     */
435    str->cursor = str->logical_first;
436    if (str != 0)
437      str->cursor->cursor = (str->cursor->direction == UCPGBA_RTL) ?
438          str->cursor->end - str->cursor->start : 0;
439
440    return str;
441}
442
443void
444ucstring_free(ucstring_t *s)
445{
446    ucrun_t *l, *r;
447
448    if (s == 0)
449      return;
450
451    for (l = 0, r = s->visual_first; r != 0; r = r->visual_next) {
452        if (r->end > r->start)
453          free((char *) r->chars);
454        if (l)
455          free((char *) l);
456        l = r;
457    }
458    if (l)
459      free((char *) l);
460
461    free((char *) s);
462}
463
464int
465ucstring_set_cursor_motion(ucstring_t *str, int cursor_motion)
466{
467    int n;
468
469    if (str == 0)
470      return -1;
471
472    n = str->cursor_motion;
473    str->cursor_motion = cursor_motion;
474    return n;
475}
476
477static int
478_ucstring_visual_cursor_right(ucstring_t *str, int count)
479{
480    int cnt = count;
481    unsigned long size;
482    ucrun_t *cursor;
483
484    if (str == 0)
485      return 0;
486
487    cursor = str->cursor;
488    while (cnt > 0) {
489        size = cursor->end - cursor->start;
490        if ((cursor->direction == UCPGBA_RTL && cursor->cursor + 1 == size) ||
491            cursor->cursor + 1 > size) {
492            /*
493             * If the next run is NULL, then the cursor is already on the
494             * far right end already.
495             */
496            if (cursor->visual_next == 0)
497              /*
498               * If movement occurred, then report it.
499               */
500              return (cnt != count);
501
502            /*
503             * Move to the next run.
504             */
505            str->cursor = cursor = cursor->visual_next;
506            cursor->cursor = (cursor->direction == UCPGBA_RTL) ? -1 : 0;
507            size = cursor->end - cursor->start;
508        } else
509          cursor->cursor++;
510        cnt--;
511    }
512    return 1;
513}
514
515static int
516_ucstring_logical_cursor_right(ucstring_t *str, int count)
517{
518    int cnt = count;
519    unsigned long size;
520    ucrun_t *cursor;
521
522    if (str == 0)
523      return 0;
524
525    cursor = str->cursor;
526    while (cnt > 0) {
527        size = cursor->end - cursor->start;
528        if (str->direction == UCPGBA_RTL) {
529            if (cursor->direction == UCPGBA_RTL) {
530                if (cursor->cursor + 1 == size) {
531                    if (cursor == str->logical_first)
532                      /*
533                       * Already at the beginning of the string.
534                       */
535                      return (cnt != count);
536
537                    str->cursor = cursor = cursor->logical_prev;
538                    size = cursor->end - cursor->start;
539                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
540                        size : 0;
541                } else
542                  cursor->cursor++;
543            } else {
544                if (cursor->cursor == 0) {
545                    if (cursor == str->logical_first)
546                      /*
547                       * At the beginning of the string already.
548                       */
549                      return (cnt != count);
550
551                    str->cursor = cursor = cursor->logical_prev;
552                    size = cursor->end - cursor->start;
553                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
554                        size : 0;
555                } else
556                  cursor->cursor--;
557            }
558        } else {
559            if (cursor->direction == UCPGBA_RTL) {
560                if (cursor->cursor == 0) {
561                    if (cursor == str->logical_last)
562                      /*
563                       * Already at the end of the string.
564                       */
565                      return (cnt != count);
566
567                    str->cursor = cursor = cursor->logical_next;
568                    size = cursor->end - cursor->start;
569                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
570                        0 : size - 1;
571                } else
572                  cursor->cursor--;
573            } else {
574                if (cursor->cursor + 1 > size) {
575                    if (cursor == str->logical_last)
576                      /*
577                       * Already at the end of the string.
578                       */
579                      return (cnt != count);
580
581                    str->cursor = cursor = cursor->logical_next;
582                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
583                        0 : size - 1;
584                } else
585                  cursor->cursor++;
586            }
587        }
588        cnt--;
589    }
590    return 1;
591}
592
593int
594ucstring_cursor_right(ucstring_t *str, int count)
595{
596    if (str == 0)
597      return 0;
598    return (str->cursor_motion == UCPGBA_CURSOR_VISUAL) ?
599        _ucstring_visual_cursor_right(str, count) :
600        _ucstring_logical_cursor_right(str, count);
601}
602
603static int
604_ucstring_visual_cursor_left(ucstring_t *str, int count)
605{
606    int cnt = count;
607    unsigned long size;
608    ucrun_t *cursor;
609
610    if (str == 0)
611      return 0;
612
613    cursor = str->cursor;
614    while (cnt > 0) {
615        size = cursor->end - cursor->start;
616        if ((cursor->direction == UCPGBA_LTR && cursor->cursor == 0) ||
617            cursor->cursor - 1 < -1) {
618            /*
619             * If the preceding run is NULL, then the cursor is already on the
620             * far left end already.
621             */
622            if (cursor->visual_prev == 0)
623              /*
624               * If movement occurred, then report it.
625               */
626              return (cnt != count);
627
628            /*
629             * Move to the previous run.
630             */
631            str->cursor = cursor = cursor->visual_prev;
632            size = cursor->end - cursor->start;
633            cursor->cursor = (cursor->direction == UCPGBA_RTL) ?
634                size : size - 1;
635        } else
636          cursor->cursor--;
637        cnt--;
638    }
639    return 1;
640}
641
642static int
643_ucstring_logical_cursor_left(ucstring_t *str, int count)
644{
645    int cnt = count;
646    unsigned long size;
647    ucrun_t *cursor;
648
649    if (str == 0)
650      return 0;
651
652    cursor = str->cursor;
653    while (cnt > 0) {
654        size = cursor->end - cursor->start;
655        if (str->direction == UCPGBA_RTL) {
656            if (cursor->direction == UCPGBA_RTL) {
657                if (cursor->cursor == -1) {
658                    if (cursor == str->logical_last)
659                      /*
660                       * Already at the end of the string.
661                       */
662                      return (cnt != count);
663
664                    str->cursor = cursor = cursor->logical_next;
665                    size = cursor->end - cursor->start;
666                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
667                        0 : size - 1;
668                } else
669                  cursor->cursor--;
670            } else {
671                if (cursor->cursor + 1 > size) {
672                    if (cursor == str->logical_last)
673                      /*
674                       * At the end of the string already.
675                       */
676                      return (cnt != count);
677
678                    str->cursor = cursor = cursor->logical_next;
679                    size = cursor->end - cursor->start;
680                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
681                        0 : size - 1;
682                } else
683                  cursor->cursor++;
684            }
685        } else {
686            if (cursor->direction == UCPGBA_RTL) {
687                if (cursor->cursor + 1 == size) {
688                    if (cursor == str->logical_first)
689                      /*
690                       * Already at the beginning of the string.
691                       */
692                      return (cnt != count);
693
694                    str->cursor = cursor = cursor->logical_prev;
695                    size = cursor->end - cursor->start;
696                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
697                        size : 0;
698                } else
699                  cursor->cursor++;
700            } else {
701                if (cursor->cursor == 0) {
702                    if (cursor == str->logical_first)
703                      /*
704                       * Already at the beginning of the string.
705                       */
706                      return (cnt != count);
707
708                    str->cursor = cursor = cursor->logical_prev;
709                    cursor->cursor = (cursor->direction == UCPGBA_LTR) ?
710                        size : 0;
711                } else
712                  cursor->cursor--;
713            }
714        }
715        cnt--;
716    }
717    return 1;
718}
719
720int
721ucstring_cursor_left(ucstring_t *str, int count)
722{
723    if (str == 0)
724      return 0;
725    return (str->cursor_motion == UCPGBA_CURSOR_VISUAL) ?
726        _ucstring_visual_cursor_left(str, count) :
727        _ucstring_logical_cursor_left(str, count);
728}
729
730void
731ucstring_cursor_info(ucstring_t *str, int *direction, unsigned long *position)
732{
733    long c;
734    unsigned long size;
735    ucrun_t *cursor;
736
737    if (str == 0 || direction == 0 || position == 0)
738      return;
739
740    cursor = str->cursor;
741
742    *direction = cursor->direction;
743
744    c = cursor->cursor;
745    size = cursor->end - cursor->start;
746
747    if (c == size)
748      *position = (cursor->direction == UCPGBA_RTL) ?
749          cursor->start : cursor->positions[c - 1];
750    else if (c == -1)
751      *position = (cursor->direction == UCPGBA_RTL) ?
752          cursor->end : cursor->start;
753    else
754      *position = cursor->positions[c];
755}
756