Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* GRAPHITE2 LICENSING |
michael@0 | 2 | |
michael@0 | 3 | Copyright 2011, SIL International |
michael@0 | 4 | All rights reserved. |
michael@0 | 5 | |
michael@0 | 6 | This library is free software; you can redistribute it and/or modify |
michael@0 | 7 | it under the terms of the GNU Lesser General Public License as published |
michael@0 | 8 | by the Free Software Foundation; either version 2.1 of License, or |
michael@0 | 9 | (at your option) any later version. |
michael@0 | 10 | |
michael@0 | 11 | This program is distributed in the hope that it will be useful, |
michael@0 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
michael@0 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
michael@0 | 14 | Lesser General Public License for more details. |
michael@0 | 15 | |
michael@0 | 16 | You should also have received a copy of the GNU Lesser General Public |
michael@0 | 17 | License along with this library in the file named "LICENSE". |
michael@0 | 18 | If not, write to the Free Software Foundation, 51 Franklin Street, |
michael@0 | 19 | suite 500, Boston, MA 02110-1335, USA or visit their web page on the |
michael@0 | 20 | internet at http://www.fsf.org/licenses/lgpl.html. |
michael@0 | 21 | |
michael@0 | 22 | Alternatively, the contents of this file may be used under the terms of the |
michael@0 | 23 | Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public |
michael@0 | 24 | License, as published by the Free Software Foundation, either version 2 |
michael@0 | 25 | of the License or (at your option) any later version. |
michael@0 | 26 | */ |
michael@0 | 27 | #include "inc/Main.h" |
michael@0 | 28 | #include "inc/Slot.h" |
michael@0 | 29 | #include "inc/Segment.h" |
michael@0 | 30 | #include "inc/Bidi.h" |
michael@0 | 31 | |
michael@0 | 32 | using namespace graphite2; |
michael@0 | 33 | |
michael@0 | 34 | enum DirCode { // Hungarian: dirc |
michael@0 | 35 | Unk = -1, |
michael@0 | 36 | N = 0, // other neutrals (default) - ON |
michael@0 | 37 | L = 1, // left-to-right, strong - L |
michael@0 | 38 | R = 2, // right-to-left, strong - R |
michael@0 | 39 | AL = 3, // Arabic letter, right-to-left, strong, AR |
michael@0 | 40 | EN = 4, // European number, left-to-right, weak - EN |
michael@0 | 41 | EUS = 5, // European separator, left-to-right, weak - ES |
michael@0 | 42 | ET = 6, // European number terminator, left-to-right, weak - ET |
michael@0 | 43 | AN = 7, // Arabic number, left-to-right, weak - AN |
michael@0 | 44 | CUS = 8, // Common number separator, left-to-right, weak - CS |
michael@0 | 45 | WS = 9, // white space, neutral - WS |
michael@0 | 46 | BN = 10, // boundary neutral - BN |
michael@0 | 47 | |
michael@0 | 48 | LRO = 11, // LTR override |
michael@0 | 49 | RLO = 12, // RTL override |
michael@0 | 50 | LRE = 13, // LTR embedding |
michael@0 | 51 | RLE = 14, // RTL embedding |
michael@0 | 52 | PDF = 15, // pop directional format |
michael@0 | 53 | NSM = 16, // non-space mark |
michael@0 | 54 | LRI = 17, // LRI isolate |
michael@0 | 55 | RLI = 18, // RLI isolate |
michael@0 | 56 | FSI = 19, // FSI isolate |
michael@0 | 57 | PDI = 20, // pop isolate |
michael@0 | 58 | OPP = 21, // opening paired parenthesis |
michael@0 | 59 | CPP = 22, // closing paired parenthesis |
michael@0 | 60 | |
michael@0 | 61 | ON = N |
michael@0 | 62 | }; |
michael@0 | 63 | |
michael@0 | 64 | enum DirMask { |
michael@0 | 65 | WSflag = (1 << 7), // keep track of WS for eos handling |
michael@0 | 66 | WSMask = ~(1 << 7) |
michael@0 | 67 | }; |
michael@0 | 68 | |
michael@0 | 69 | inline uint8 BaseClass(Slot *s) { return s->getBidiClass() & WSMask; } |
michael@0 | 70 | |
michael@0 | 71 | unsigned int bidi_class_map[] = { 0, 1, 2, 5, 4, 8, 9, 3, 7, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0 }; |
michael@0 | 72 | // Algorithms based on Unicode reference standard code. Thanks Asmus Freitag. |
michael@0 | 73 | |
michael@0 | 74 | void resolveWeak(Slot *start, int sos, int eos); |
michael@0 | 75 | void resolveNeutrals(Slot *s, int baseLevel, int sos, int eos); |
michael@0 | 76 | void processParens(Slot *s, Segment *seg, uint8 aMirror, int level, BracketPairStack &stack); |
michael@0 | 77 | |
michael@0 | 78 | inline int calc_base_level(Slot *s) |
michael@0 | 79 | { |
michael@0 | 80 | int count = 0; |
michael@0 | 81 | for ( ; s; s = s->next()) |
michael@0 | 82 | { |
michael@0 | 83 | int cls = s->getBidiClass(); |
michael@0 | 84 | if (count) |
michael@0 | 85 | { |
michael@0 | 86 | switch(cls) |
michael@0 | 87 | { |
michael@0 | 88 | case LRI : |
michael@0 | 89 | case RLI : |
michael@0 | 90 | case FSI : |
michael@0 | 91 | ++count; |
michael@0 | 92 | break; |
michael@0 | 93 | case PDI : |
michael@0 | 94 | --count; |
michael@0 | 95 | } |
michael@0 | 96 | } |
michael@0 | 97 | else |
michael@0 | 98 | { |
michael@0 | 99 | switch(cls) |
michael@0 | 100 | { |
michael@0 | 101 | case L : |
michael@0 | 102 | return 0; |
michael@0 | 103 | case R : |
michael@0 | 104 | case AL : |
michael@0 | 105 | return 1; |
michael@0 | 106 | case LRI : |
michael@0 | 107 | case RLI : |
michael@0 | 108 | case FSI : |
michael@0 | 109 | ++count; |
michael@0 | 110 | } |
michael@0 | 111 | } |
michael@0 | 112 | } |
michael@0 | 113 | return 0; |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | // inline or not? |
michael@0 | 117 | void do_resolves(Slot *start, int level, int sos, int eos, int &bmask, Segment *seg, uint8 aMirror, BracketPairStack &stack) |
michael@0 | 118 | { |
michael@0 | 119 | if (bmask & 0x1F1178) |
michael@0 | 120 | resolveWeak(start, sos, eos); |
michael@0 | 121 | if (bmask & 0x200000) |
michael@0 | 122 | processParens(start, seg, aMirror, level, stack); |
michael@0 | 123 | if (bmask & 0x7E0361) |
michael@0 | 124 | resolveNeutrals(start, level, sos, eos); |
michael@0 | 125 | bmask = 0; |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | enum maxs |
michael@0 | 129 | { |
michael@0 | 130 | MAX_LEVEL = 125, |
michael@0 | 131 | }; |
michael@0 | 132 | |
michael@0 | 133 | // returns where we are up to in processing |
michael@0 | 134 | Slot *process_bidi(Slot *start, int level, int prelevel, int &nextLevel, int dirover, int isol, int &cisol, int &isolerr, int &embederr, int init, Segment *seg, uint8 aMirror, BracketPairStack &bstack) |
michael@0 | 135 | { |
michael@0 | 136 | int bmask = 0; |
michael@0 | 137 | Slot *s = start; |
michael@0 | 138 | Slot *slast = start; |
michael@0 | 139 | Slot *scurr = 0; |
michael@0 | 140 | Slot *stemp; |
michael@0 | 141 | int lnextLevel = nextLevel; |
michael@0 | 142 | int newLevel; |
michael@0 | 143 | int empty = 1; |
michael@0 | 144 | for ( ; s; s = s ? s->next() : s) |
michael@0 | 145 | { |
michael@0 | 146 | int cls = s->getBidiClass(); |
michael@0 | 147 | bmask |= (1 << cls); |
michael@0 | 148 | s->setBidiLevel(level); |
michael@0 | 149 | // we keep s->prev() pointing backwards for PDI repeating |
michael@0 | 150 | |
michael@0 | 151 | switch (cls) |
michael@0 | 152 | { |
michael@0 | 153 | case BN : |
michael@0 | 154 | if (slast == s) slast = s->next(); // ignore if at front of text |
michael@0 | 155 | continue; |
michael@0 | 156 | case LRE : |
michael@0 | 157 | case LRO : |
michael@0 | 158 | case RLE : |
michael@0 | 159 | case RLO : |
michael@0 | 160 | switch (cls) |
michael@0 | 161 | { |
michael@0 | 162 | case LRE : |
michael@0 | 163 | case LRO : |
michael@0 | 164 | newLevel = level + (level & 1 ? 1 : 2); |
michael@0 | 165 | break; |
michael@0 | 166 | case RLE : |
michael@0 | 167 | case RLO : |
michael@0 | 168 | newLevel = level + (level & 1 ? 2 : 1); |
michael@0 | 169 | break; |
michael@0 | 170 | } |
michael@0 | 171 | s->setBidiClass(BN); |
michael@0 | 172 | if (isolerr || newLevel > MAX_LEVEL || embederr) |
michael@0 | 173 | { |
michael@0 | 174 | if (!isolerr) ++embederr; |
michael@0 | 175 | break; |
michael@0 | 176 | } |
michael@0 | 177 | stemp = scurr; |
michael@0 | 178 | if (scurr) |
michael@0 | 179 | scurr->prev(0); // don't include control in string |
michael@0 | 180 | lnextLevel = newLevel; |
michael@0 | 181 | scurr = s; |
michael@0 | 182 | s->setBidiLevel(newLevel); // to make it vanish |
michael@0 | 183 | // recurse for the new subsequence. A sequence only contains text at the same level |
michael@0 | 184 | s = process_bidi(s->next(), newLevel, level, lnextLevel, cls < LRE, 0, cisol, isolerr, embederr, 0, seg, aMirror, bstack); |
michael@0 | 185 | // s points at PDF or end of sequence |
michael@0 | 186 | // try to keep extending the run and not process it until we have to |
michael@0 | 187 | if (lnextLevel != level || !s) // if the subsequence really had something in it, or we are at the end of the run |
michael@0 | 188 | { |
michael@0 | 189 | if (slast != scurr) // process the run now, don't try to extend it |
michael@0 | 190 | { |
michael@0 | 191 | // process text preceeding embedding |
michael@0 | 192 | do_resolves(slast, level, (prelevel > level ? prelevel : level) & 1, lnextLevel & 1, bmask, seg, aMirror, bstack); |
michael@0 | 193 | empty = 0; |
michael@0 | 194 | nextLevel = level; |
michael@0 | 195 | } |
michael@0 | 196 | else if (lnextLevel != level) // the subsequence had something |
michael@0 | 197 | { |
michael@0 | 198 | empty = 0; // so we aren't empty either |
michael@0 | 199 | nextLevel = lnextLevel; // but since we really are empty, pass back our level from the subsequence |
michael@0 | 200 | } |
michael@0 | 201 | if (s) // if still more to process |
michael@0 | 202 | { |
michael@0 | 203 | prelevel = lnextLevel; // future text starts out with sos of the higher subsequence |
michael@0 | 204 | lnextLevel = level; // and eos is our level |
michael@0 | 205 | } |
michael@0 | 206 | slast = s ? s->next() : s; |
michael@0 | 207 | } |
michael@0 | 208 | else if (stemp) |
michael@0 | 209 | stemp->prev(s); |
michael@0 | 210 | break; |
michael@0 | 211 | |
michael@0 | 212 | case PDF : |
michael@0 | 213 | s->setBidiClass(BN); |
michael@0 | 214 | s->prev(0); // unstitch us since we skip final stitching code when we return |
michael@0 | 215 | if (isol || isolerr || init) // boundary error conditions |
michael@0 | 216 | break; |
michael@0 | 217 | if (embederr) |
michael@0 | 218 | { |
michael@0 | 219 | --embederr; |
michael@0 | 220 | break; |
michael@0 | 221 | } |
michael@0 | 222 | if (slast != s) |
michael@0 | 223 | { |
michael@0 | 224 | scurr->prev(0); // if slast, then scurr. Terminate before here |
michael@0 | 225 | do_resolves(slast, level, level & 1, level & 1, bmask, seg, aMirror, bstack); |
michael@0 | 226 | empty = 0; |
michael@0 | 227 | } |
michael@0 | 228 | if (empty) |
michael@0 | 229 | { |
michael@0 | 230 | nextLevel = prelevel; // no contents? set our level to that of parent |
michael@0 | 231 | s->setBidiLevel(prelevel); |
michael@0 | 232 | } |
michael@0 | 233 | return s; |
michael@0 | 234 | |
michael@0 | 235 | case FSI : |
michael@0 | 236 | case LRI : |
michael@0 | 237 | case RLI : |
michael@0 | 238 | switch (cls) |
michael@0 | 239 | { |
michael@0 | 240 | case FSI : |
michael@0 | 241 | if (calc_base_level(s->next())) |
michael@0 | 242 | newLevel = level + (level & 1 ? 2 : 1); |
michael@0 | 243 | else |
michael@0 | 244 | newLevel = level + (level & 1 ? 1 : 2); |
michael@0 | 245 | break; |
michael@0 | 246 | case LRI : |
michael@0 | 247 | newLevel = level + (level & 1 ? 1 : 2); |
michael@0 | 248 | break; |
michael@0 | 249 | case RLI : |
michael@0 | 250 | newLevel = level + (level & 1 ? 2 : 1); |
michael@0 | 251 | break; |
michael@0 | 252 | } |
michael@0 | 253 | if (newLevel > MAX_LEVEL || isolerr) |
michael@0 | 254 | { |
michael@0 | 255 | ++isolerr; |
michael@0 | 256 | s->setBidiClass(ON | WSflag); |
michael@0 | 257 | break; |
michael@0 | 258 | } |
michael@0 | 259 | ++cisol; |
michael@0 | 260 | if (scurr) scurr->prev(s); |
michael@0 | 261 | scurr = s; // include FSI |
michael@0 | 262 | lnextLevel = newLevel; |
michael@0 | 263 | // recurse for the new sub sequence |
michael@0 | 264 | s = process_bidi(s->next(), newLevel, newLevel, lnextLevel, 0, 1, cisol, isolerr, embederr, 0, seg, aMirror, bstack); |
michael@0 | 265 | // s points at PDI |
michael@0 | 266 | if (s) |
michael@0 | 267 | { |
michael@0 | 268 | bmask |= 1 << BaseClass(s); // include the PDI in the mask |
michael@0 | 269 | s->setBidiLevel(level); // reset its level to our level |
michael@0 | 270 | } |
michael@0 | 271 | lnextLevel = level; |
michael@0 | 272 | break; |
michael@0 | 273 | |
michael@0 | 274 | case PDI : |
michael@0 | 275 | if (isolerr) |
michael@0 | 276 | { |
michael@0 | 277 | --isolerr; |
michael@0 | 278 | s->setBidiClass(ON | WSflag); |
michael@0 | 279 | break; |
michael@0 | 280 | } |
michael@0 | 281 | if (init || !cisol) |
michael@0 | 282 | { |
michael@0 | 283 | s->setBidiClass(ON | WSflag); |
michael@0 | 284 | break; |
michael@0 | 285 | } |
michael@0 | 286 | embederr = 0; |
michael@0 | 287 | if (!isol) // we are in an embedded subsequence, we have to return through all those |
michael@0 | 288 | { |
michael@0 | 289 | if (empty) // if empty, reset the level to tell embedded parent |
michael@0 | 290 | nextLevel = prelevel; |
michael@0 | 291 | return s->prev(); // keep working up the stack pointing at this PDI until we get to an isolate entry |
michael@0 | 292 | } |
michael@0 | 293 | else // we are terminating an isolate sequence |
michael@0 | 294 | { |
michael@0 | 295 | if (slast != s) // process any remaining content in this subseqence |
michael@0 | 296 | { |
michael@0 | 297 | scurr->prev(0); |
michael@0 | 298 | do_resolves(slast, level, prelevel & 1, level & 1, bmask, seg, aMirror, bstack); |
michael@0 | 299 | } |
michael@0 | 300 | --cisol; // pop the isol sequence from the stack |
michael@0 | 301 | return s; |
michael@0 | 302 | } |
michael@0 | 303 | |
michael@0 | 304 | default : |
michael@0 | 305 | if (dirover) |
michael@0 | 306 | s->setBidiClass((level & 1 ? R : L) | (WSflag * (cls == WS))); |
michael@0 | 307 | } |
michael@0 | 308 | if (s) s->prev(0); // unstitch us |
michael@0 | 309 | if (scurr) // stitch in text for processing |
michael@0 | 310 | scurr->prev(s); |
michael@0 | 311 | scurr = s; // add us to text to process |
michael@0 | 312 | } |
michael@0 | 313 | if (slast != s) |
michael@0 | 314 | { |
michael@0 | 315 | do_resolves(slast, level, (level > prelevel ? level : prelevel) & 1, lnextLevel & 1, bmask, seg, aMirror, bstack); |
michael@0 | 316 | empty = 0; |
michael@0 | 317 | } |
michael@0 | 318 | if (empty || isol) |
michael@0 | 319 | nextLevel = prelevel; |
michael@0 | 320 | return s; |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | // === RESOLVE WEAK TYPES ================================================ |
michael@0 | 324 | |
michael@0 | 325 | enum bidi_state // possible states |
michael@0 | 326 | { |
michael@0 | 327 | xa, // arabic letter |
michael@0 | 328 | xr, // right leter |
michael@0 | 329 | xl, // left letter |
michael@0 | 330 | |
michael@0 | 331 | ao, // arabic lett. foll by ON |
michael@0 | 332 | ro, // right lett. foll by ON |
michael@0 | 333 | lo, // left lett. foll by ON |
michael@0 | 334 | |
michael@0 | 335 | rt, // ET following R |
michael@0 | 336 | lt, // ET following L |
michael@0 | 337 | |
michael@0 | 338 | cn, // EN, AN following AL |
michael@0 | 339 | ra, // arabic number foll R |
michael@0 | 340 | re, // european number foll R |
michael@0 | 341 | la, // arabic number foll L |
michael@0 | 342 | le, // european number foll L |
michael@0 | 343 | |
michael@0 | 344 | ac, // CS following cn |
michael@0 | 345 | rc, // CS following ra |
michael@0 | 346 | rs, // CS,ES following re |
michael@0 | 347 | lc, // CS following la |
michael@0 | 348 | ls, // CS,ES following le |
michael@0 | 349 | |
michael@0 | 350 | ret, // ET following re |
michael@0 | 351 | let, // ET following le |
michael@0 | 352 | } ; |
michael@0 | 353 | |
michael@0 | 354 | const bidi_state stateWeak[][10] = |
michael@0 | 355 | { |
michael@0 | 356 | // N, L, R, AN, EN, AL,NSM, CS, ES, ET, |
michael@0 | 357 | { /*xa*/ ao, xl, xr, cn, cn, xa, xa, ao, ao, ao, /* arabic letter */ }, |
michael@0 | 358 | { /*xr*/ ro, xl, xr, ra, re, xa, xr, ro, ro, rt, /* right letter */ }, |
michael@0 | 359 | { /*xl*/ lo, xl, xr, la, le, xa, xl, lo, lo, lt, /* left letter */ }, |
michael@0 | 360 | |
michael@0 | 361 | { /*ao*/ ao, xl, xr, cn, cn, xa, ao, ao, ao, ao, /* arabic lett. foll by ON*/ }, |
michael@0 | 362 | { /*ro*/ ro, xl, xr, ra, re, xa, ro, ro, ro, rt, /* right lett. foll by ON */ }, |
michael@0 | 363 | { /*lo*/ lo, xl, xr, la, le, xa, lo, lo, lo, lt, /* left lett. foll by ON */ }, |
michael@0 | 364 | |
michael@0 | 365 | { /*rt*/ ro, xl, xr, ra, re, xa, rt, ro, ro, rt, /* ET following R */ }, |
michael@0 | 366 | { /*lt*/ lo, xl, xr, la, le, xa, lt, lo, lo, lt, /* ET following L */ }, |
michael@0 | 367 | |
michael@0 | 368 | { /*cn*/ ao, xl, xr, cn, cn, xa, cn, ac, ao, ao, /* EN, AN following AL */ }, |
michael@0 | 369 | { /*ra*/ ro, xl, xr, ra, re, xa, ra, rc, ro, rt, /* arabic number foll R */ }, |
michael@0 | 370 | { /*re*/ ro, xl, xr, ra, re, xa, re, rs, rs,ret, /* european number foll R */ }, |
michael@0 | 371 | { /*la*/ lo, xl, xr, la, le, xa, la, lc, lo, lt, /* arabic number foll L */ }, |
michael@0 | 372 | { /*le*/ lo, xl, xr, la, le, xa, le, ls, ls,let, /* european number foll L */ }, |
michael@0 | 373 | |
michael@0 | 374 | { /*ac*/ ao, xl, xr, cn, cn, xa, ao, ao, ao, ao, /* CS following cn */ }, |
michael@0 | 375 | { /*rc*/ ro, xl, xr, ra, re, xa, ro, ro, ro, rt, /* CS following ra */ }, |
michael@0 | 376 | { /*rs*/ ro, xl, xr, ra, re, xa, ro, ro, ro, rt, /* CS,ES following re */ }, |
michael@0 | 377 | { /*lc*/ lo, xl, xr, la, le, xa, lo, lo, lo, lt, /* CS following la */ }, |
michael@0 | 378 | { /*ls*/ lo, xl, xr, la, le, xa, lo, lo, lo, lt, /* CS,ES following le */ }, |
michael@0 | 379 | |
michael@0 | 380 | { /*ret*/ ro, xl, xr, ra, re, xa,ret, ro, ro,ret, /* ET following re */ }, |
michael@0 | 381 | { /*let*/ lo, xl, xr, la, le, xa,let, lo, lo,let, /* ET following le */ }, |
michael@0 | 382 | |
michael@0 | 383 | |
michael@0 | 384 | }; |
michael@0 | 385 | |
michael@0 | 386 | enum bidi_action // possible actions |
michael@0 | 387 | { |
michael@0 | 388 | // primitives |
michael@0 | 389 | IX = 0x100, // increment |
michael@0 | 390 | XX = 0xF, // no-op |
michael@0 | 391 | |
michael@0 | 392 | // actions |
michael@0 | 393 | xxx = (XX << 4) + XX, // no-op |
michael@0 | 394 | xIx = IX + xxx, // increment run |
michael@0 | 395 | xxN = (XX << 4) + ON, // set current to N |
michael@0 | 396 | xxE = (XX << 4) + EN, // set current to EN |
michael@0 | 397 | xxA = (XX << 4) + AN, // set current to AN |
michael@0 | 398 | xxR = (XX << 4) + R, // set current to R |
michael@0 | 399 | xxL = (XX << 4) + L, // set current to L |
michael@0 | 400 | Nxx = (ON << 4) + 0xF, // set run to neutral |
michael@0 | 401 | Axx = (AN << 4) + 0xF, // set run to AN |
michael@0 | 402 | ExE = (EN << 4) + EN, // set run to EN, set current to EN |
michael@0 | 403 | NIx = (ON << 4) + 0xF + IX, // set run to N, increment |
michael@0 | 404 | NxN = (ON << 4) + ON, // set run to N, set current to N |
michael@0 | 405 | NxR = (ON << 4) + R, // set run to N, set current to R |
michael@0 | 406 | NxE = (ON << 4) + EN, // set run to N, set current to EN |
michael@0 | 407 | |
michael@0 | 408 | AxA = (AN << 4) + AN, // set run to AN, set current to AN |
michael@0 | 409 | NxL = (ON << 4) + L, // set run to N, set current to L |
michael@0 | 410 | LxL = (L << 4) + L, // set run to L, set current to L |
michael@0 | 411 | }; |
michael@0 | 412 | |
michael@0 | 413 | |
michael@0 | 414 | const bidi_action actionWeak[][10] = |
michael@0 | 415 | { |
michael@0 | 416 | // N,.. L, R, AN, EN, AL, NSM, CS,..ES, ET, |
michael@0 | 417 | { /*xa*/ xxx, xxx, xxx, xxx, xxA, xxR, xxR, xxN, xxN, xxN, /* arabic letter */ }, |
michael@0 | 418 | { /*xr*/ xxx, xxx, xxx, xxx, xxE, xxR, xxR, xxN, xxN, xIx, /* right leter */ }, |
michael@0 | 419 | { /*xl*/ xxx, xxx, xxx, xxx, xxL, xxR, xxL, xxN, xxN, xIx, /* left letter */ }, |
michael@0 | 420 | |
michael@0 | 421 | { /*ao*/ xxx, xxx, xxx, xxx, xxA, xxR, xxN, xxN, xxN, xxN, /* arabic lett. foll by ON */ }, |
michael@0 | 422 | { /*ro*/ xxx, xxx, xxx, xxx, xxE, xxR, xxN, xxN, xxN, xIx, /* right lett. foll by ON */ }, |
michael@0 | 423 | { /*lo*/ xxx, xxx, xxx, xxx, xxL, xxR, xxN, xxN, xxN, xIx, /* left lett. foll by ON */ }, |
michael@0 | 424 | |
michael@0 | 425 | { /*rt*/ Nxx, Nxx, Nxx, Nxx, ExE, NxR, xIx, NxN, NxN, xIx, /* ET following R */ }, |
michael@0 | 426 | { /*lt*/ Nxx, Nxx, Nxx, Nxx, LxL, NxR, xIx, NxN, NxN, xIx, /* ET following L */ }, |
michael@0 | 427 | |
michael@0 | 428 | { /*cn*/ xxx, xxx, xxx, xxx, xxA, xxR, xxA, xIx, xxN, xxN, /* EN, AN following AL */ }, |
michael@0 | 429 | { /*ra*/ xxx, xxx, xxx, xxx, xxE, xxR, xxA, xIx, xxN, xIx, /* arabic number foll R */ }, |
michael@0 | 430 | { /*re*/ xxx, xxx, xxx, xxx, xxE, xxR, xxE, xIx, xIx, xxE, /* european number foll R */ }, |
michael@0 | 431 | { /*la*/ xxx, xxx, xxx, xxx, xxL, xxR, xxA, xIx, xxN, xIx, /* arabic number foll L */ }, |
michael@0 | 432 | { /*le*/ xxx, xxx, xxx, xxx, xxL, xxR, xxL, xIx, xIx, xxL, /* european number foll L */ }, |
michael@0 | 433 | |
michael@0 | 434 | { /*ac*/ Nxx, Nxx, Nxx, Axx, AxA, NxR, NxN, NxN, NxN, NxN, /* CS following cn */ }, |
michael@0 | 435 | { /*rc*/ Nxx, Nxx, Nxx, Axx, NxE, NxR, NxN, NxN, NxN, NIx, /* CS following ra */ }, |
michael@0 | 436 | { /*rs*/ Nxx, Nxx, Nxx, Nxx, ExE, NxR, NxN, NxN, NxN, NIx, /* CS,ES following re */ }, |
michael@0 | 437 | { /*lc*/ Nxx, Nxx, Nxx, Axx, NxL, NxR, NxN, NxN, NxN, NIx, /* CS following la */ }, |
michael@0 | 438 | { /*ls*/ Nxx, Nxx, Nxx, Nxx, LxL, NxR, NxN, NxN, NxN, NIx, /* CS,ES following le */ }, |
michael@0 | 439 | |
michael@0 | 440 | { /*ret*/xxx, xxx, xxx, xxx, xxE, xxR, xxE, xxN, xxN, xxE, /* ET following re */ }, |
michael@0 | 441 | { /*let*/xxx, xxx, xxx, xxx, xxL, xxR, xxL, xxN, xxN, xxL, /* ET following le */ }, |
michael@0 | 442 | }; |
michael@0 | 443 | |
michael@0 | 444 | inline uint8 GetDeferredType(bidi_action a) { return (a >> 4) & 0xF; } |
michael@0 | 445 | inline uint8 GetResolvedType(bidi_action a) { return a & 0xF; } |
michael@0 | 446 | inline DirCode EmbeddingDirection(int l) { return l & 1 ? R : L; } |
michael@0 | 447 | |
michael@0 | 448 | // Neutrals |
michael@0 | 449 | enum neutral_action |
michael@0 | 450 | { |
michael@0 | 451 | // action to resolve previous input |
michael@0 | 452 | nL = L, // resolve EN to L |
michael@0 | 453 | En = 3 << 4, // resolve neutrals run to embedding level direction |
michael@0 | 454 | Rn = R << 4, // resolve neutrals run to strong right |
michael@0 | 455 | Ln = L << 4, // resolved neutrals run to strong left |
michael@0 | 456 | In = (1<<8), // increment count of deferred neutrals |
michael@0 | 457 | LnL = (1<<4)+L, // set run and EN to L |
michael@0 | 458 | }; |
michael@0 | 459 | |
michael@0 | 460 | // ->prev() here means ->next() |
michael@0 | 461 | void SetDeferredRunClass(Slot *s, Slot *sRun, int nval) |
michael@0 | 462 | { |
michael@0 | 463 | if (!sRun || s == sRun) return; |
michael@0 | 464 | for (Slot *p = sRun; p != s; p = p->prev()) |
michael@0 | 465 | if (p->getBidiClass() == WS) p->setBidiClass(nval | WSflag); |
michael@0 | 466 | else if (BaseClass(p) != BN) p->setBidiClass(nval | (p->getBidiClass() & WSflag)); |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | void SetThisDeferredRunClass(Slot *s, Slot *sRun, int nval) |
michael@0 | 470 | { |
michael@0 | 471 | if (!sRun) return; |
michael@0 | 472 | for (Slot *p = sRun, *e = s->prev(); p != e; p = p->prev()) |
michael@0 | 473 | if (p->getBidiClass() == WS) p->setBidiClass(nval | WSflag); |
michael@0 | 474 | else if (BaseClass(p) != BN) p->setBidiClass(nval | (p->getBidiClass() & WSflag)); |
michael@0 | 475 | } |
michael@0 | 476 | |
michael@0 | 477 | void resolveWeak(Slot *start, int sos, int eos) |
michael@0 | 478 | { |
michael@0 | 479 | int state = (sos & 1) ? xr : xl; |
michael@0 | 480 | int cls; |
michael@0 | 481 | Slot *s = start; |
michael@0 | 482 | Slot *sRun = NULL; |
michael@0 | 483 | Slot *sLast = s; |
michael@0 | 484 | |
michael@0 | 485 | for ( ; s; s = s->prev()) |
michael@0 | 486 | { |
michael@0 | 487 | sLast = s; |
michael@0 | 488 | cls = BaseClass(s); |
michael@0 | 489 | switch (cls) |
michael@0 | 490 | { |
michael@0 | 491 | case BN : |
michael@0 | 492 | if (s == start) start = s->prev(); // skip initial BNs for NSM resolving |
michael@0 | 493 | continue; |
michael@0 | 494 | case LRI : |
michael@0 | 495 | case RLI : |
michael@0 | 496 | case FSI : |
michael@0 | 497 | case PDI : |
michael@0 | 498 | { |
michael@0 | 499 | Slot *snext = s->prev(); |
michael@0 | 500 | if (snext && snext->getBidiClass() == NSM) |
michael@0 | 501 | snext->setBidiClass(ON); |
michael@0 | 502 | s->setBidiClass(ON | WSflag); |
michael@0 | 503 | } |
michael@0 | 504 | break; |
michael@0 | 505 | |
michael@0 | 506 | case NSM : |
michael@0 | 507 | if (s == start) |
michael@0 | 508 | { |
michael@0 | 509 | cls = EmbeddingDirection(sos); |
michael@0 | 510 | s->setBidiClass(cls); |
michael@0 | 511 | } |
michael@0 | 512 | break; |
michael@0 | 513 | } |
michael@0 | 514 | |
michael@0 | 515 | bidi_action action = actionWeak[state][bidi_class_map[cls]]; |
michael@0 | 516 | int clsRun = GetDeferredType(action); |
michael@0 | 517 | if (clsRun != XX) |
michael@0 | 518 | { |
michael@0 | 519 | SetDeferredRunClass(s, sRun, clsRun); |
michael@0 | 520 | sRun = NULL; |
michael@0 | 521 | } |
michael@0 | 522 | int clsNew = GetResolvedType(action); |
michael@0 | 523 | if (clsNew != XX) |
michael@0 | 524 | s->setBidiClass(clsNew); |
michael@0 | 525 | if (!sRun && (IX & action)) |
michael@0 | 526 | sRun = s; |
michael@0 | 527 | state = stateWeak[state][bidi_class_map[cls]]; |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | cls = EmbeddingDirection(eos); |
michael@0 | 531 | int clsRun = GetDeferredType(actionWeak[state][bidi_class_map[cls]]); |
michael@0 | 532 | if (clsRun != XX) |
michael@0 | 533 | SetThisDeferredRunClass(sLast, sRun, clsRun); |
michael@0 | 534 | } |
michael@0 | 535 | |
michael@0 | 536 | void processParens(Slot *s, Segment *seg, uint8 aMirror, int level, BracketPairStack &stack) |
michael@0 | 537 | { |
michael@0 | 538 | uint8 mask = 0; |
michael@0 | 539 | int8 lastDir = -1; |
michael@0 | 540 | BracketPair *p; |
michael@0 | 541 | for ( ; s; s = s->prev()) // walk the sequence |
michael@0 | 542 | { |
michael@0 | 543 | uint16 ogid = seg->glyphAttr(s->gid(), aMirror); |
michael@0 | 544 | int cls = BaseClass(s); |
michael@0 | 545 | |
michael@0 | 546 | switch(cls) |
michael@0 | 547 | { |
michael@0 | 548 | case OPP : |
michael@0 | 549 | stack.orin(mask); |
michael@0 | 550 | stack.push(ogid, s, lastDir, lastDir != CPP); |
michael@0 | 551 | mask = 0; |
michael@0 | 552 | lastDir = OPP; |
michael@0 | 553 | break; |
michael@0 | 554 | case CPP : |
michael@0 | 555 | stack.orin(mask); |
michael@0 | 556 | p = stack.scan(s->gid()); |
michael@0 | 557 | if (!p) break; |
michael@0 | 558 | mask = 0; |
michael@0 | 559 | stack.close(p, s); |
michael@0 | 560 | lastDir = CPP; |
michael@0 | 561 | break; |
michael@0 | 562 | case L : |
michael@0 | 563 | lastDir = L; |
michael@0 | 564 | mask |= 1; |
michael@0 | 565 | break; |
michael@0 | 566 | case R : |
michael@0 | 567 | case AL : |
michael@0 | 568 | case AN : |
michael@0 | 569 | case EN : |
michael@0 | 570 | lastDir = R; |
michael@0 | 571 | mask |= 2; |
michael@0 | 572 | } |
michael@0 | 573 | } |
michael@0 | 574 | for (p = stack.start(); p; p =p->next()) // walk the stack |
michael@0 | 575 | { |
michael@0 | 576 | if (p->close() && p->mask()) |
michael@0 | 577 | { |
michael@0 | 578 | int dir = (level & 1) + 1; |
michael@0 | 579 | if (p->mask() & dir) |
michael@0 | 580 | { } |
michael@0 | 581 | else if (p->mask() & (1 << (~level & 1))) // if inside has strong other embedding |
michael@0 | 582 | { |
michael@0 | 583 | int ldir = p->before(); |
michael@0 | 584 | if ((p->before() == OPP || p->before() == CPP) && p->prev()) |
michael@0 | 585 | { |
michael@0 | 586 | for (BracketPair *q = p->prev(); q; q = q->prev()) |
michael@0 | 587 | { |
michael@0 | 588 | ldir = q->open()->getBidiClass(); |
michael@0 | 589 | if (ldir < 3) break; |
michael@0 | 590 | ldir = q->before(); |
michael@0 | 591 | if (ldir < 3) break; |
michael@0 | 592 | } |
michael@0 | 593 | if (ldir > 2) ldir = 0; |
michael@0 | 594 | } |
michael@0 | 595 | if (ldir > 0 && (ldir - 1) != (level & 1)) // is dir given opp. to level dir (ldir == R or L) |
michael@0 | 596 | dir = (~level & 1) + 1; |
michael@0 | 597 | } |
michael@0 | 598 | p->open()->setBidiClass(dir); |
michael@0 | 599 | p->close()->setBidiClass(dir); |
michael@0 | 600 | } |
michael@0 | 601 | } |
michael@0 | 602 | stack.clear(); |
michael@0 | 603 | } |
michael@0 | 604 | |
michael@0 | 605 | int GetDeferredNeutrals(int action, int level) |
michael@0 | 606 | { |
michael@0 | 607 | action = (action >> 4) & 0xF; |
michael@0 | 608 | if (action == (En >> 4)) |
michael@0 | 609 | return EmbeddingDirection(level); |
michael@0 | 610 | else |
michael@0 | 611 | return action; |
michael@0 | 612 | } |
michael@0 | 613 | |
michael@0 | 614 | int GetResolvedNeutrals(int action) |
michael@0 | 615 | { |
michael@0 | 616 | return action & 0xF; |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | // state values |
michael@0 | 620 | enum neutral_state |
michael@0 | 621 | { |
michael@0 | 622 | // new temporary class |
michael@0 | 623 | r, // R and characters resolved to R |
michael@0 | 624 | l, // L and characters resolved to L |
michael@0 | 625 | rn, // N preceded by right |
michael@0 | 626 | ln, // N preceded by left |
michael@0 | 627 | a, // AN preceded by left (the abbrev 'la' is used up above) |
michael@0 | 628 | na, // N preceeded by a |
michael@0 | 629 | } ; |
michael@0 | 630 | |
michael@0 | 631 | const uint8 neutral_class_map[] = { 0, 1, 2, 0, 4, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
michael@0 | 632 | |
michael@0 | 633 | const int actionNeutrals[][5] = |
michael@0 | 634 | { |
michael@0 | 635 | // cls= N, L, R, AN, EN, state = |
michael@0 | 636 | { In, 0, 0, 0, 0, }, // r right |
michael@0 | 637 | { In, 0, 0, 0, L, }, // l left |
michael@0 | 638 | |
michael@0 | 639 | { In, En, Rn, Rn, Rn, }, // rn N preceded by right |
michael@0 | 640 | { In, Ln, En, En, LnL, }, // ln N preceded by left |
michael@0 | 641 | |
michael@0 | 642 | { In, 0, 0, 0, L, }, // a AN preceded by left |
michael@0 | 643 | { In, En, Rn, Rn, En, }, // na N preceded by a |
michael@0 | 644 | } ; |
michael@0 | 645 | |
michael@0 | 646 | const int stateNeutrals[][5] = |
michael@0 | 647 | { |
michael@0 | 648 | // cls= N, L, R, AN, EN state = |
michael@0 | 649 | { rn, l, r, r, r, }, // r right |
michael@0 | 650 | { ln, l, r, a, l, }, // l left |
michael@0 | 651 | |
michael@0 | 652 | { rn, l, r, r, r, }, // rn N preceded by right |
michael@0 | 653 | { ln, l, r, a, l, }, // ln N preceded by left |
michael@0 | 654 | |
michael@0 | 655 | { na, l, r, a, l, }, // a AN preceded by left |
michael@0 | 656 | { na, l, r, a, l, }, // na N preceded by la |
michael@0 | 657 | } ; |
michael@0 | 658 | |
michael@0 | 659 | void resolveNeutrals(Slot *s, int baseLevel, int sos, int eos) |
michael@0 | 660 | { |
michael@0 | 661 | int state = (sos & 1) ? r : l; |
michael@0 | 662 | int cls; |
michael@0 | 663 | Slot *sRun = NULL; |
michael@0 | 664 | Slot *sLast = s; |
michael@0 | 665 | int level = baseLevel; |
michael@0 | 666 | |
michael@0 | 667 | for ( ; s; s = s->prev()) |
michael@0 | 668 | { |
michael@0 | 669 | sLast = s; |
michael@0 | 670 | cls = BaseClass(s); |
michael@0 | 671 | switch (cls) |
michael@0 | 672 | { |
michael@0 | 673 | case BN : |
michael@0 | 674 | continue; |
michael@0 | 675 | case LRI : |
michael@0 | 676 | case RLI : |
michael@0 | 677 | case FSI : |
michael@0 | 678 | s->setBidiClass(BN | WSflag); |
michael@0 | 679 | continue; |
michael@0 | 680 | |
michael@0 | 681 | default : |
michael@0 | 682 | int action = actionNeutrals[state][neutral_class_map[cls]]; |
michael@0 | 683 | int clsRun = GetDeferredNeutrals(action, level); |
michael@0 | 684 | if (clsRun != N) |
michael@0 | 685 | { |
michael@0 | 686 | SetDeferredRunClass(s, sRun, clsRun); |
michael@0 | 687 | sRun = NULL; |
michael@0 | 688 | } |
michael@0 | 689 | int clsNew = GetResolvedNeutrals(action); |
michael@0 | 690 | if (clsNew != N) |
michael@0 | 691 | s->setBidiClass(clsNew); |
michael@0 | 692 | if (!sRun && (action & In)) |
michael@0 | 693 | sRun = s; |
michael@0 | 694 | state = stateNeutrals[state][neutral_class_map[cls]]; |
michael@0 | 695 | } |
michael@0 | 696 | } |
michael@0 | 697 | cls = EmbeddingDirection(eos); |
michael@0 | 698 | int clsRun = GetDeferredNeutrals(actionNeutrals[state][neutral_class_map[cls]], level); |
michael@0 | 699 | if (clsRun != N) |
michael@0 | 700 | SetThisDeferredRunClass(sLast, sRun, clsRun); |
michael@0 | 701 | } |
michael@0 | 702 | |
michael@0 | 703 | const int addLevel[][4] = |
michael@0 | 704 | { |
michael@0 | 705 | // cls = L, R, AN, EN level = |
michael@0 | 706 | /* even */ { 0, 1, 2, 2, }, // EVEN |
michael@0 | 707 | /* odd */ { 1, 0, 1, 1, }, // ODD |
michael@0 | 708 | |
michael@0 | 709 | }; |
michael@0 | 710 | |
michael@0 | 711 | void resolveImplicit(Slot *s, Segment *seg, uint8 aMirror) |
michael@0 | 712 | { |
michael@0 | 713 | bool rtl = seg->dir() & 1; |
michael@0 | 714 | int level = rtl; |
michael@0 | 715 | Slot *slast = 0; |
michael@0 | 716 | for ( ; s; s = s->next()) |
michael@0 | 717 | { |
michael@0 | 718 | int cls = BaseClass(s); |
michael@0 | 719 | s->prev(slast); // restitch the prev() side of the doubly linked list |
michael@0 | 720 | slast = s; |
michael@0 | 721 | if (cls == AN) |
michael@0 | 722 | cls = AL; // use AL value as the index for AN, no property change |
michael@0 | 723 | if (cls < 5 && cls > 0) |
michael@0 | 724 | { |
michael@0 | 725 | level = s->getBidiLevel(); |
michael@0 | 726 | level += addLevel[level & 1][cls - 1]; |
michael@0 | 727 | s->setBidiLevel(level); |
michael@0 | 728 | } |
michael@0 | 729 | if (aMirror) |
michael@0 | 730 | { |
michael@0 | 731 | int hasChar = seg->glyphAttr(s->gid(), aMirror + 1); |
michael@0 | 732 | if ( ((level & 1) && (!(seg->dir() & 4) || !hasChar)) |
michael@0 | 733 | || ((rtl ^ (level & 1)) && (seg->dir() & 4) && hasChar) ) |
michael@0 | 734 | { |
michael@0 | 735 | unsigned short g = seg->glyphAttr(s->gid(), aMirror); |
michael@0 | 736 | if (g) s->setGlyph(seg, g); |
michael@0 | 737 | } |
michael@0 | 738 | } |
michael@0 | 739 | } |
michael@0 | 740 | } |
michael@0 | 741 | |
michael@0 | 742 | void resolveWhitespace(int baseLevel, Slot *s) |
michael@0 | 743 | { |
michael@0 | 744 | for ( ; s; s = s->prev()) |
michael@0 | 745 | { |
michael@0 | 746 | int8 cls = s->getBidiClass(); |
michael@0 | 747 | if (cls == WS || cls & WSflag) |
michael@0 | 748 | s->setBidiLevel(baseLevel); |
michael@0 | 749 | else if (cls != BN) |
michael@0 | 750 | break; |
michael@0 | 751 | } |
michael@0 | 752 | } |
michael@0 | 753 | |
michael@0 | 754 | |
michael@0 | 755 | /* |
michael@0 | 756 | Stitch two spans together to make another span (with ends tied together). |
michael@0 | 757 | If the level is odd then swap the order of the two spans |
michael@0 | 758 | */ |
michael@0 | 759 | inline |
michael@0 | 760 | Slot * join(int level, Slot * a, Slot * b) |
michael@0 | 761 | { |
michael@0 | 762 | if (!a) return b; |
michael@0 | 763 | if (level & 1) { Slot * const t = a; a = b; b = t; } |
michael@0 | 764 | Slot * const t = b->prev(); |
michael@0 | 765 | a->prev()->next(b); b->prev(a->prev()); // splice middle |
michael@0 | 766 | t->next(a); a->prev(t); // splice ends |
michael@0 | 767 | return a; |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | /* |
michael@0 | 771 | Given the first slot in a run of slots with the same bidi level, turn the run |
michael@0 | 772 | into it's own little doubly linked list ring (a span) with the two ends joined together. |
michael@0 | 773 | If the run is rtl then reverse its direction. |
michael@0 | 774 | Returns the first slot after the span |
michael@0 | 775 | */ |
michael@0 | 776 | Slot * span(Slot * & cs, const bool rtl) |
michael@0 | 777 | { |
michael@0 | 778 | Slot * r = cs, * re = cs; cs = cs->next(); |
michael@0 | 779 | if (rtl) |
michael@0 | 780 | { |
michael@0 | 781 | Slot * t = r->next(); r->next(r->prev()); r->prev(t); |
michael@0 | 782 | for (int l = r->getBidiLevel(); cs && (l == cs->getBidiLevel() || cs->getBidiClass() == BN); cs = cs->prev()) |
michael@0 | 783 | { |
michael@0 | 784 | re = cs; |
michael@0 | 785 | t = cs->next(); cs->next(cs->prev()); cs->prev(t); |
michael@0 | 786 | } |
michael@0 | 787 | r->next(re); |
michael@0 | 788 | re->prev(r); |
michael@0 | 789 | r = re; |
michael@0 | 790 | } |
michael@0 | 791 | else |
michael@0 | 792 | { |
michael@0 | 793 | for (int l = r->getBidiLevel(); cs && (l == cs->getBidiLevel() || cs->getBidiClass() == BN); cs = cs->next()) |
michael@0 | 794 | re = cs; |
michael@0 | 795 | r->prev(re); |
michael@0 | 796 | re->next(r); |
michael@0 | 797 | } |
michael@0 | 798 | if (cs) cs->prev(0); |
michael@0 | 799 | return r; |
michael@0 | 800 | } |
michael@0 | 801 | |
michael@0 | 802 | inline int getlevel(const Slot *cs, const int level) |
michael@0 | 803 | { |
michael@0 | 804 | while (cs && cs->getBidiClass() == BN) |
michael@0 | 805 | { cs = cs->next(); } |
michael@0 | 806 | if (cs) |
michael@0 | 807 | return cs->getBidiLevel(); |
michael@0 | 808 | else |
michael@0 | 809 | return level; |
michael@0 | 810 | } |
michael@0 | 811 | |
michael@0 | 812 | Slot *resolveOrder(Slot * & cs, const bool reordered, const int level) |
michael@0 | 813 | { |
michael@0 | 814 | Slot * r = 0; |
michael@0 | 815 | int ls; |
michael@0 | 816 | while (cs && level <= (ls = getlevel(cs, level) - reordered)) |
michael@0 | 817 | { |
michael@0 | 818 | r = join(level, r, level < ls |
michael@0 | 819 | ? resolveOrder(/* updates */cs, reordered, level+1) // find span of heighest level |
michael@0 | 820 | : span(/* updates */cs, level & 1)); |
michael@0 | 821 | } |
michael@0 | 822 | return r; |
michael@0 | 823 | } |