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