001/* 002 * $Id: BidiLine.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.util.ArrayList; 047 048import com.itextpdf.text.Chunk; 049import com.itextpdf.text.Image; 050import com.itextpdf.text.Utilities; 051 052/** Does all the line bidirectional processing with PdfChunk assembly. 053 * 054 * @author Paulo Soares 055 */ 056public class BidiLine { 057 058 protected int runDirection; 059 protected int pieceSize = 256; 060 protected char text[] = new char[pieceSize]; 061 protected PdfChunk detailChunks[] = new PdfChunk[pieceSize]; 062 protected int totalTextLength = 0; 063 064 protected byte orderLevels[] = new byte[pieceSize]; 065 protected int indexChars[] = new int[pieceSize]; 066 067 protected ArrayList<PdfChunk> chunks = new ArrayList<PdfChunk>(); 068 protected int indexChunk = 0; 069 protected int indexChunkChar = 0; 070 protected int currentChar = 0; 071 072 protected int storedRunDirection; 073 protected char storedText[] = new char[0]; 074 protected PdfChunk storedDetailChunks[] = new PdfChunk[0]; 075 protected int storedTotalTextLength = 0; 076 077 protected byte storedOrderLevels[] = new byte[0]; 078 protected int storedIndexChars[] = new int[0]; 079 080 protected int storedIndexChunk = 0; 081 protected int storedIndexChunkChar = 0; 082 protected int storedCurrentChar = 0; 083 084 protected boolean shortStore; 085// protected ArabicShaping arabic = new ArabicShaping(ArabicShaping.LETTERS_SHAPE | ArabicShaping.LENGTH_GROW_SHRINK | ArabicShaping.TEXT_DIRECTION_LOGICAL); 086 protected static final IntHashtable mirrorChars = new IntHashtable(); 087 protected int arabicOptions; 088 089 /** Creates new BidiLine */ 090 public BidiLine() { 091 } 092 093 public BidiLine(BidiLine org) { 094 runDirection = org.runDirection; 095 pieceSize = org.pieceSize; 096 text = org.text.clone(); 097 detailChunks = org.detailChunks.clone(); 098 totalTextLength = org.totalTextLength; 099 100 orderLevels = org.orderLevels.clone(); 101 indexChars = org.indexChars.clone(); 102 103 chunks = new ArrayList<PdfChunk>(org.chunks); 104 indexChunk = org.indexChunk; 105 indexChunkChar = org.indexChunkChar; 106 currentChar = org.currentChar; 107 108 storedRunDirection = org.storedRunDirection; 109 storedText = org.storedText.clone(); 110 storedDetailChunks = org.storedDetailChunks.clone(); 111 storedTotalTextLength = org.storedTotalTextLength; 112 113 storedOrderLevels = org.storedOrderLevels.clone(); 114 storedIndexChars = org.storedIndexChars.clone(); 115 116 storedIndexChunk = org.storedIndexChunk; 117 storedIndexChunkChar = org.storedIndexChunkChar; 118 storedCurrentChar = org.storedCurrentChar; 119 120 shortStore = org.shortStore; 121 arabicOptions = org.arabicOptions; 122 } 123 124 public boolean isEmpty() { 125 return currentChar >= totalTextLength && indexChunk >= chunks.size(); 126 } 127 128 public void clearChunks() { 129 chunks.clear(); 130 totalTextLength = 0; 131 currentChar = 0; 132 } 133 134 public boolean getParagraph(int runDirection) { 135 this.runDirection = runDirection; 136 currentChar = 0; 137 totalTextLength = 0; 138 boolean hasText = false; 139 char c; 140 char uniC; 141 BaseFont bf; 142 for (; indexChunk < chunks.size(); ++indexChunk) { 143 PdfChunk ck = chunks.get(indexChunk); 144 bf = ck.font().getFont(); 145 String s = ck.toString(); 146 int len = s.length(); 147 for (; indexChunkChar < len; ++indexChunkChar) { 148 c = s.charAt(indexChunkChar); 149 uniC = (char)bf.getUnicodeEquivalent(c); 150 if (uniC == '\r' || uniC == '\n') { 151 // next condition is never true for CID 152 if (uniC == '\r' && indexChunkChar + 1 < len && s.charAt(indexChunkChar + 1) == '\n') 153 ++indexChunkChar; 154 ++indexChunkChar; 155 if (indexChunkChar >= len) { 156 indexChunkChar = 0; 157 ++indexChunk; 158 } 159 hasText = true; 160 if (totalTextLength == 0) 161 detailChunks[0] = ck; 162 break; 163 } 164 addPiece(c, ck); 165 } 166 if (hasText) 167 break; 168 indexChunkChar = 0; 169 } 170 if (totalTextLength == 0) 171 return hasText; 172 173 // remove trailing WS 174 totalTextLength = trimRight(0, totalTextLength - 1) + 1; 175 if (totalTextLength == 0) { 176 return true; 177 } 178 179 if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) { 180 if (orderLevels.length < totalTextLength) { 181 orderLevels = new byte[pieceSize]; 182 indexChars = new int[pieceSize]; 183 } 184 ArabicLigaturizer.processNumbers(text, 0, totalTextLength, arabicOptions); 185 BidiOrder order = new BidiOrder(text, 0, totalTextLength, (byte)(runDirection == PdfWriter.RUN_DIRECTION_RTL ? 1 : 0)); 186 byte od[] = order.getLevels(); 187 for (int k = 0; k < totalTextLength; ++k) { 188 orderLevels[k] = od[k]; 189 indexChars[k] = k; 190 } 191 doArabicShapping(); 192 mirrorGlyphs(); 193 } 194 totalTextLength = trimRightEx(0, totalTextLength - 1) + 1; 195 return true; 196 } 197 198 public void addChunk(PdfChunk chunk) { 199 chunks.add(chunk); 200 } 201 202 public void addChunks(ArrayList<PdfChunk> chunks) { 203 this.chunks.addAll(chunks); 204 } 205 206 public void addPiece(char c, PdfChunk chunk) { 207 if (totalTextLength >= pieceSize) { 208 char tempText[] = text; 209 PdfChunk tempDetailChunks[] = detailChunks; 210 pieceSize *= 2; 211 text = new char[pieceSize]; 212 detailChunks = new PdfChunk[pieceSize]; 213 System.arraycopy(tempText, 0, text, 0, totalTextLength); 214 System.arraycopy(tempDetailChunks, 0, detailChunks, 0, totalTextLength); 215 } 216 text[totalTextLength] = c; 217 detailChunks[totalTextLength++] = chunk; 218 } 219 220 public void save() { 221 if (indexChunk > 0) { 222 if (indexChunk >= chunks.size()) 223 chunks.clear(); 224 else { 225 for (--indexChunk; indexChunk >= 0; --indexChunk) 226 chunks.remove(indexChunk); 227 } 228 indexChunk = 0; 229 } 230 storedRunDirection = runDirection; 231 storedTotalTextLength = totalTextLength; 232 storedIndexChunk = indexChunk; 233 storedIndexChunkChar = indexChunkChar; 234 storedCurrentChar = currentChar; 235 shortStore = currentChar < totalTextLength; 236 if (!shortStore) { 237 // long save 238 if (storedText.length < totalTextLength) { 239 storedText = new char[totalTextLength]; 240 storedDetailChunks = new PdfChunk[totalTextLength]; 241 } 242 System.arraycopy(text, 0, storedText, 0, totalTextLength); 243 System.arraycopy(detailChunks, 0, storedDetailChunks, 0, totalTextLength); 244 } 245 if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) { 246 if (storedOrderLevels.length < totalTextLength) { 247 storedOrderLevels = new byte[totalTextLength]; 248 storedIndexChars = new int[totalTextLength]; 249 } 250 System.arraycopy(orderLevels, currentChar, storedOrderLevels, currentChar, totalTextLength - currentChar); 251 System.arraycopy(indexChars, currentChar, storedIndexChars, currentChar, totalTextLength - currentChar); 252 } 253 } 254 255 public void restore() { 256 runDirection = storedRunDirection; 257 totalTextLength = storedTotalTextLength; 258 indexChunk = storedIndexChunk; 259 indexChunkChar = storedIndexChunkChar; 260 currentChar = storedCurrentChar; 261 if (!shortStore) { 262 // long restore 263 System.arraycopy(storedText, 0, text, 0, totalTextLength); 264 System.arraycopy(storedDetailChunks, 0, detailChunks, 0, totalTextLength); 265 } 266 if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) { 267 System.arraycopy(storedOrderLevels, currentChar, orderLevels, currentChar, totalTextLength - currentChar); 268 System.arraycopy(storedIndexChars, currentChar, indexChars, currentChar, totalTextLength - currentChar); 269 } 270 } 271 272 public void mirrorGlyphs() { 273 for (int k = 0; k < totalTextLength; ++k) { 274 if ((orderLevels[k] & 1) == 1) { 275 int mirror = mirrorChars.get(text[k]); 276 if (mirror != 0) 277 text[k] = (char)mirror; 278 } 279 } 280 } 281 282 public void doArabicShapping() { 283 int src = 0; 284 int dest = 0; 285 for (;;) { 286 while (src < totalTextLength) { 287 char c = text[src]; 288 if (c >= 0x0600 && c <= 0x06ff) 289 break; 290 if (src != dest) { 291 text[dest] = text[src]; 292 detailChunks[dest] = detailChunks[src]; 293 orderLevels[dest] = orderLevels[src]; 294 } 295 ++src; 296 ++dest; 297 } 298 if (src >= totalTextLength) { 299 totalTextLength = dest; 300 return; 301 } 302 int startArabicIdx = src; 303 ++src; 304 while (src < totalTextLength) { 305 char c = text[src]; 306 if (c < 0x0600 || c > 0x06ff) 307 break; 308 ++src; 309 } 310 int arabicWordSize = src - startArabicIdx; 311 int size = ArabicLigaturizer.arabic_shape(text, startArabicIdx, arabicWordSize, text, dest, arabicWordSize, arabicOptions); 312 if (startArabicIdx != dest) { 313 for (int k = 0; k < size; ++k) { 314 detailChunks[dest] = detailChunks[startArabicIdx]; 315 orderLevels[dest++] = orderLevels[startArabicIdx++]; 316 } 317 } 318 else 319 dest += size; 320 } 321 } 322 323 public PdfLine processLine(float leftX, float width, int alignment, int runDirection, int arabicOptions) { 324 this.arabicOptions = arabicOptions; 325 save(); 326 boolean isRTL = runDirection == PdfWriter.RUN_DIRECTION_RTL; 327 if (currentChar >= totalTextLength) { 328 boolean hasText = getParagraph(runDirection); 329 if (!hasText) 330 return null; 331 if (totalTextLength == 0) { 332 ArrayList<PdfChunk> ar = new ArrayList<PdfChunk>(); 333 PdfChunk ck = new PdfChunk("", detailChunks[0]); 334 ar.add(ck); 335 return new PdfLine(0, 0, 0, alignment, true, ar, isRTL); 336 } 337 } 338 float originalWidth = width; 339 int lastSplit = -1; 340 if (currentChar != 0) 341 currentChar = trimLeftEx(currentChar, totalTextLength - 1); 342 int oldCurrentChar = currentChar; 343 int uniC = 0; 344 PdfChunk ck = null; 345 float charWidth = 0; 346 PdfChunk lastValidChunk = null; 347 boolean splitChar = false; 348 boolean surrogate = false; 349 for (; currentChar < totalTextLength; ++currentChar) { 350 ck = detailChunks[currentChar]; 351 surrogate = Utilities.isSurrogatePair(text, currentChar); 352 if (surrogate) 353 uniC = ck.getUnicodeEquivalent(Utilities.convertToUtf32(text, currentChar)); 354 else 355 uniC = ck.getUnicodeEquivalent(text[currentChar]); 356 if (PdfChunk.noPrint(uniC)) 357 continue; 358 if (surrogate) 359 charWidth = ck.getCharWidth(uniC); 360 else 361 charWidth = ck.getCharWidth(text[currentChar]); 362 splitChar = ck.isExtSplitCharacter(oldCurrentChar, currentChar, totalTextLength, text, detailChunks); 363 if (splitChar && Character.isWhitespace((char)uniC)) 364 lastSplit = currentChar; 365 if (width - charWidth < 0) { 366 // If the chunk is an image and it is the first one in line, check if resize requested 367 // If so, resize to fit the current line width 368 if (lastValidChunk == null && ck.isImage()) { 369 Image img = ck.getImage(); 370 if (img.isScaleToFitLineWhenOverflow()) { 371 float scalePercent = width / img.getWidth() * 100; 372 img.scalePercent(scalePercent); 373 charWidth = width; 374 } 375 } 376 } 377 if (width - charWidth < 0) 378 break; 379 if (splitChar) 380 lastSplit = currentChar; 381 width -= charWidth; 382 lastValidChunk = ck; 383 if (ck.isTab()) { 384 Object[] tab = (Object[])ck.getAttribute(Chunk.TAB); 385 float tabPosition = ((Float)tab[1]).floatValue(); 386 boolean newLine = ((Boolean)tab[2]).booleanValue(); 387 if (newLine && tabPosition < originalWidth - width) { 388 return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL); 389 } 390 detailChunks[currentChar].adjustLeft(leftX); 391 width = originalWidth - tabPosition; 392 } 393 if (surrogate) 394 ++currentChar; 395 } 396 if (lastValidChunk == null) { 397 // not even a single char fit; must output the first char 398 ++currentChar; 399 if (surrogate) 400 ++currentChar; 401 return new PdfLine(0, originalWidth, 0, alignment, false, createArrayOfPdfChunks(currentChar - 1, currentChar - 1), isRTL); 402 } 403 if (currentChar >= totalTextLength) { 404 // there was more line than text 405 return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, totalTextLength - 1), isRTL); 406 } 407 int newCurrentChar = trimRightEx(oldCurrentChar, currentChar - 1); 408 if (newCurrentChar < oldCurrentChar) { 409 // only WS 410 return new PdfLine(0, originalWidth, width, alignment, false, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL); 411 } 412 if (newCurrentChar == currentChar - 1) { // middle of word 413 HyphenationEvent he = (HyphenationEvent)lastValidChunk.getAttribute(Chunk.HYPHENATION); 414 if (he != null) { 415 int word[] = getWord(oldCurrentChar, newCurrentChar); 416 if (word != null) { 417 float testWidth = width + getWidth(word[0], currentChar - 1); 418 String pre = he.getHyphenatedWordPre(new String(text, word[0], word[1] - word[0]), lastValidChunk.font().getFont(), lastValidChunk.font().size(), testWidth); 419 String post = he.getHyphenatedWordPost(); 420 if (pre.length() > 0) { 421 PdfChunk extra = new PdfChunk(pre, lastValidChunk); 422 currentChar = word[1] - post.length(); 423 return new PdfLine(0, originalWidth, testWidth - lastValidChunk.font().width(pre), alignment, false, createArrayOfPdfChunks(oldCurrentChar, word[0] - 1, extra), isRTL); 424 } 425 } 426 } 427 } 428 if (lastSplit == -1 || lastSplit >= newCurrentChar) { 429 // no split point or split point ahead of end 430 return new PdfLine(0, originalWidth, width + getWidth(newCurrentChar + 1, currentChar - 1), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL); 431 } 432 // standard split 433 currentChar = lastSplit + 1; 434 newCurrentChar = trimRightEx(oldCurrentChar, lastSplit); 435 if (newCurrentChar < oldCurrentChar) { 436 // only WS again 437 newCurrentChar = currentChar - 1; 438 } 439 return new PdfLine(0, originalWidth, originalWidth - getWidth(oldCurrentChar, newCurrentChar), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL); 440 } 441 442 /** Gets the width of a range of characters. 443 * @param startIdx the first index to calculate 444 * @param lastIdx the last inclusive index to calculate 445 * @return the sum of all widths 446 */ 447 public float getWidth(int startIdx, int lastIdx) { 448 char c = 0; 449 PdfChunk ck = null; 450 float width = 0; 451 for (; startIdx <= lastIdx; ++startIdx) { 452 boolean surrogate = Utilities.isSurrogatePair(text, startIdx); 453 if (surrogate) { 454 width += detailChunks[startIdx].getCharWidth(Utilities.convertToUtf32(text, startIdx)); 455 ++startIdx; 456 } 457 else { 458 c = text[startIdx]; 459 ck = detailChunks[startIdx]; 460 if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c))) 461 continue; 462 width += detailChunks[startIdx].getCharWidth(c); 463 } 464 } 465 return width; 466 } 467 468 public ArrayList<PdfChunk> createArrayOfPdfChunks(int startIdx, int endIdx) { 469 return createArrayOfPdfChunks(startIdx, endIdx, null); 470 } 471 472 public ArrayList<PdfChunk> createArrayOfPdfChunks(int startIdx, int endIdx, PdfChunk extraPdfChunk) { 473 boolean bidi = runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL; 474 if (bidi) 475 reorder(startIdx, endIdx); 476 ArrayList<PdfChunk> ar = new ArrayList<PdfChunk>(); 477 PdfChunk refCk = detailChunks[startIdx]; 478 PdfChunk ck = null; 479 StringBuffer buf = new StringBuffer(); 480 char c; 481 int idx = 0; 482 for (; startIdx <= endIdx; ++startIdx) { 483 idx = bidi ? indexChars[startIdx] : startIdx; 484 c = text[idx]; 485 ck = detailChunks[idx]; 486 if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c))) 487 continue; 488 if (ck.isImage() || ck.isSeparator() || ck.isTab()) { 489 if (buf.length() > 0) { 490 ar.add(new PdfChunk(buf.toString(), refCk)); 491 buf = new StringBuffer(); 492 } 493 ar.add(ck); 494 } 495 else if (ck == refCk) { 496 buf.append(c); 497 } 498 else { 499 if (buf.length() > 0) { 500 ar.add(new PdfChunk(buf.toString(), refCk)); 501 buf = new StringBuffer(); 502 } 503 if (!ck.isImage() && !ck.isSeparator() && !ck.isTab()) 504 buf.append(c); 505 refCk = ck; 506 } 507 } 508 if (buf.length() > 0) { 509 ar.add(new PdfChunk(buf.toString(), refCk)); 510 } 511 if (extraPdfChunk != null) 512 ar.add(extraPdfChunk); 513 return ar; 514 } 515 516 public int[] getWord(int startIdx, int idx) { 517 int last = idx; 518 int first = idx; 519 // forward 520 for (; last < totalTextLength; ++last) { 521 if (!Character.isLetter(text[last])) 522 break; 523 } 524 if (last == idx) 525 return null; 526 // backward 527 for (; first >= startIdx; --first) { 528 if (!Character.isLetter(text[first])) 529 break; 530 } 531 ++first; 532 return new int[]{first, last}; 533 } 534 535 public int trimRight(int startIdx, int endIdx) { 536 int idx = endIdx; 537 char c; 538 for (; idx >= startIdx; --idx) { 539 c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]); 540 if (!isWS(c)) 541 break; 542 } 543 return idx; 544 } 545 546 public int trimLeft(int startIdx, int endIdx) { 547 int idx = startIdx; 548 char c; 549 for (; idx <= endIdx; ++idx) { 550 c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]); 551 if (!isWS(c)) 552 break; 553 } 554 return idx; 555 } 556 557 public int trimRightEx(int startIdx, int endIdx) { 558 int idx = endIdx; 559 char c = 0; 560 for (; idx >= startIdx; --idx) { 561 c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]); 562 if (!isWS(c) && !PdfChunk.noPrint(c)) 563 break; 564 } 565 return idx; 566 } 567 568 public int trimLeftEx(int startIdx, int endIdx) { 569 int idx = startIdx; 570 char c = 0; 571 for (; idx <= endIdx; ++idx) { 572 c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]); 573 if (!isWS(c) && !PdfChunk.noPrint(c)) 574 break; 575 } 576 return idx; 577 } 578 579 public void reorder(int start, int end) { 580 byte maxLevel = orderLevels[start]; 581 byte minLevel = maxLevel; 582 byte onlyOddLevels = maxLevel; 583 byte onlyEvenLevels = maxLevel; 584 for (int k = start + 1; k <= end; ++k) { 585 byte b = orderLevels[k]; 586 if (b > maxLevel) 587 maxLevel = b; 588 else if (b < minLevel) 589 minLevel = b; 590 onlyOddLevels &= b; 591 onlyEvenLevels |= b; 592 } 593 if ((onlyEvenLevels & 1) == 0) // nothing to do 594 return; 595 if ((onlyOddLevels & 1) == 1) { // single inversion 596 flip(start, end + 1); 597 return; 598 } 599 minLevel |= 1; 600 for (; maxLevel >= minLevel; --maxLevel) { 601 int pstart = start; 602 for (;;) { 603 for (;pstart <= end; ++pstart) { 604 if (orderLevels[pstart] >= maxLevel) 605 break; 606 } 607 if (pstart > end) 608 break; 609 int pend = pstart + 1; 610 for (; pend <= end; ++pend) { 611 if (orderLevels[pend] < maxLevel) 612 break; 613 } 614 flip(pstart, pend); 615 pstart = pend + 1; 616 } 617 } 618 } 619 620 public void flip(int start, int end) { 621 int mid = (start + end) / 2; 622 --end; 623 for (; start < mid; ++start, --end) { 624 int temp = indexChars[start]; 625 indexChars[start] = indexChars[end]; 626 indexChars[end] = temp; 627 } 628 } 629 630 public static boolean isWS(char c) { 631 return c <= ' '; 632 } 633 634 static { 635 mirrorChars.put(0x0028, 0x0029); // LEFT PARENTHESIS 636 mirrorChars.put(0x0029, 0x0028); // RIGHT PARENTHESIS 637 mirrorChars.put(0x003C, 0x003E); // LESS-THAN SIGN 638 mirrorChars.put(0x003E, 0x003C); // GREATER-THAN SIGN 639 mirrorChars.put(0x005B, 0x005D); // LEFT SQUARE BRACKET 640 mirrorChars.put(0x005D, 0x005B); // RIGHT SQUARE BRACKET 641 mirrorChars.put(0x007B, 0x007D); // LEFT CURLY BRACKET 642 mirrorChars.put(0x007D, 0x007B); // RIGHT CURLY BRACKET 643 mirrorChars.put(0x00AB, 0x00BB); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK 644 mirrorChars.put(0x00BB, 0x00AB); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK 645 mirrorChars.put(0x2039, 0x203A); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK 646 mirrorChars.put(0x203A, 0x2039); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK 647 mirrorChars.put(0x2045, 0x2046); // LEFT SQUARE BRACKET WITH QUILL 648 mirrorChars.put(0x2046, 0x2045); // RIGHT SQUARE BRACKET WITH QUILL 649 mirrorChars.put(0x207D, 0x207E); // SUPERSCRIPT LEFT PARENTHESIS 650 mirrorChars.put(0x207E, 0x207D); // SUPERSCRIPT RIGHT PARENTHESIS 651 mirrorChars.put(0x208D, 0x208E); // SUBSCRIPT LEFT PARENTHESIS 652 mirrorChars.put(0x208E, 0x208D); // SUBSCRIPT RIGHT PARENTHESIS 653 mirrorChars.put(0x2208, 0x220B); // ELEMENT OF 654 mirrorChars.put(0x2209, 0x220C); // NOT AN ELEMENT OF 655 mirrorChars.put(0x220A, 0x220D); // SMALL ELEMENT OF 656 mirrorChars.put(0x220B, 0x2208); // CONTAINS AS MEMBER 657 mirrorChars.put(0x220C, 0x2209); // DOES NOT CONTAIN AS MEMBER 658 mirrorChars.put(0x220D, 0x220A); // SMALL CONTAINS AS MEMBER 659 mirrorChars.put(0x2215, 0x29F5); // DIVISION SLASH 660 mirrorChars.put(0x223C, 0x223D); // TILDE OPERATOR 661 mirrorChars.put(0x223D, 0x223C); // REVERSED TILDE 662 mirrorChars.put(0x2243, 0x22CD); // ASYMPTOTICALLY EQUAL TO 663 mirrorChars.put(0x2252, 0x2253); // APPROXIMATELY EQUAL TO OR THE IMAGE OF 664 mirrorChars.put(0x2253, 0x2252); // IMAGE OF OR APPROXIMATELY EQUAL TO 665 mirrorChars.put(0x2254, 0x2255); // COLON EQUALS 666 mirrorChars.put(0x2255, 0x2254); // EQUALS COLON 667 mirrorChars.put(0x2264, 0x2265); // LESS-THAN OR EQUAL TO 668 mirrorChars.put(0x2265, 0x2264); // GREATER-THAN OR EQUAL TO 669 mirrorChars.put(0x2266, 0x2267); // LESS-THAN OVER EQUAL TO 670 mirrorChars.put(0x2267, 0x2266); // GREATER-THAN OVER EQUAL TO 671 mirrorChars.put(0x2268, 0x2269); // [BEST FIT] LESS-THAN BUT NOT EQUAL TO 672 mirrorChars.put(0x2269, 0x2268); // [BEST FIT] GREATER-THAN BUT NOT EQUAL TO 673 mirrorChars.put(0x226A, 0x226B); // MUCH LESS-THAN 674 mirrorChars.put(0x226B, 0x226A); // MUCH GREATER-THAN 675 mirrorChars.put(0x226E, 0x226F); // [BEST FIT] NOT LESS-THAN 676 mirrorChars.put(0x226F, 0x226E); // [BEST FIT] NOT GREATER-THAN 677 mirrorChars.put(0x2270, 0x2271); // [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO 678 mirrorChars.put(0x2271, 0x2270); // [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO 679 mirrorChars.put(0x2272, 0x2273); // [BEST FIT] LESS-THAN OR EQUIVALENT TO 680 mirrorChars.put(0x2273, 0x2272); // [BEST FIT] GREATER-THAN OR EQUIVALENT TO 681 mirrorChars.put(0x2274, 0x2275); // [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO 682 mirrorChars.put(0x2275, 0x2274); // [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO 683 mirrorChars.put(0x2276, 0x2277); // LESS-THAN OR GREATER-THAN 684 mirrorChars.put(0x2277, 0x2276); // GREATER-THAN OR LESS-THAN 685 mirrorChars.put(0x2278, 0x2279); // NEITHER LESS-THAN NOR GREATER-THAN 686 mirrorChars.put(0x2279, 0x2278); // NEITHER GREATER-THAN NOR LESS-THAN 687 mirrorChars.put(0x227A, 0x227B); // PRECEDES 688 mirrorChars.put(0x227B, 0x227A); // SUCCEEDS 689 mirrorChars.put(0x227C, 0x227D); // PRECEDES OR EQUAL TO 690 mirrorChars.put(0x227D, 0x227C); // SUCCEEDS OR EQUAL TO 691 mirrorChars.put(0x227E, 0x227F); // [BEST FIT] PRECEDES OR EQUIVALENT TO 692 mirrorChars.put(0x227F, 0x227E); // [BEST FIT] SUCCEEDS OR EQUIVALENT TO 693 mirrorChars.put(0x2280, 0x2281); // [BEST FIT] DOES NOT PRECEDE 694 mirrorChars.put(0x2281, 0x2280); // [BEST FIT] DOES NOT SUCCEED 695 mirrorChars.put(0x2282, 0x2283); // SUBSET OF 696 mirrorChars.put(0x2283, 0x2282); // SUPERSET OF 697 mirrorChars.put(0x2284, 0x2285); // [BEST FIT] NOT A SUBSET OF 698 mirrorChars.put(0x2285, 0x2284); // [BEST FIT] NOT A SUPERSET OF 699 mirrorChars.put(0x2286, 0x2287); // SUBSET OF OR EQUAL TO 700 mirrorChars.put(0x2287, 0x2286); // SUPERSET OF OR EQUAL TO 701 mirrorChars.put(0x2288, 0x2289); // [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO 702 mirrorChars.put(0x2289, 0x2288); // [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO 703 mirrorChars.put(0x228A, 0x228B); // [BEST FIT] SUBSET OF WITH NOT EQUAL TO 704 mirrorChars.put(0x228B, 0x228A); // [BEST FIT] SUPERSET OF WITH NOT EQUAL TO 705 mirrorChars.put(0x228F, 0x2290); // SQUARE IMAGE OF 706 mirrorChars.put(0x2290, 0x228F); // SQUARE ORIGINAL OF 707 mirrorChars.put(0x2291, 0x2292); // SQUARE IMAGE OF OR EQUAL TO 708 mirrorChars.put(0x2292, 0x2291); // SQUARE ORIGINAL OF OR EQUAL TO 709 mirrorChars.put(0x2298, 0x29B8); // CIRCLED DIVISION SLASH 710 mirrorChars.put(0x22A2, 0x22A3); // RIGHT TACK 711 mirrorChars.put(0x22A3, 0x22A2); // LEFT TACK 712 mirrorChars.put(0x22A6, 0x2ADE); // ASSERTION 713 mirrorChars.put(0x22A8, 0x2AE4); // TRUE 714 mirrorChars.put(0x22A9, 0x2AE3); // FORCES 715 mirrorChars.put(0x22AB, 0x2AE5); // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE 716 mirrorChars.put(0x22B0, 0x22B1); // PRECEDES UNDER RELATION 717 mirrorChars.put(0x22B1, 0x22B0); // SUCCEEDS UNDER RELATION 718 mirrorChars.put(0x22B2, 0x22B3); // NORMAL SUBGROUP OF 719 mirrorChars.put(0x22B3, 0x22B2); // CONTAINS AS NORMAL SUBGROUP 720 mirrorChars.put(0x22B4, 0x22B5); // NORMAL SUBGROUP OF OR EQUAL TO 721 mirrorChars.put(0x22B5, 0x22B4); // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO 722 mirrorChars.put(0x22B6, 0x22B7); // ORIGINAL OF 723 mirrorChars.put(0x22B7, 0x22B6); // IMAGE OF 724 mirrorChars.put(0x22C9, 0x22CA); // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT 725 mirrorChars.put(0x22CA, 0x22C9); // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT 726 mirrorChars.put(0x22CB, 0x22CC); // LEFT SEMIDIRECT PRODUCT 727 mirrorChars.put(0x22CC, 0x22CB); // RIGHT SEMIDIRECT PRODUCT 728 mirrorChars.put(0x22CD, 0x2243); // REVERSED TILDE EQUALS 729 mirrorChars.put(0x22D0, 0x22D1); // DOUBLE SUBSET 730 mirrorChars.put(0x22D1, 0x22D0); // DOUBLE SUPERSET 731 mirrorChars.put(0x22D6, 0x22D7); // LESS-THAN WITH DOT 732 mirrorChars.put(0x22D7, 0x22D6); // GREATER-THAN WITH DOT 733 mirrorChars.put(0x22D8, 0x22D9); // VERY MUCH LESS-THAN 734 mirrorChars.put(0x22D9, 0x22D8); // VERY MUCH GREATER-THAN 735 mirrorChars.put(0x22DA, 0x22DB); // LESS-THAN EQUAL TO OR GREATER-THAN 736 mirrorChars.put(0x22DB, 0x22DA); // GREATER-THAN EQUAL TO OR LESS-THAN 737 mirrorChars.put(0x22DC, 0x22DD); // EQUAL TO OR LESS-THAN 738 mirrorChars.put(0x22DD, 0x22DC); // EQUAL TO OR GREATER-THAN 739 mirrorChars.put(0x22DE, 0x22DF); // EQUAL TO OR PRECEDES 740 mirrorChars.put(0x22DF, 0x22DE); // EQUAL TO OR SUCCEEDS 741 mirrorChars.put(0x22E0, 0x22E1); // [BEST FIT] DOES NOT PRECEDE OR EQUAL 742 mirrorChars.put(0x22E1, 0x22E0); // [BEST FIT] DOES NOT SUCCEED OR EQUAL 743 mirrorChars.put(0x22E2, 0x22E3); // [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO 744 mirrorChars.put(0x22E3, 0x22E2); // [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO 745 mirrorChars.put(0x22E4, 0x22E5); // [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO 746 mirrorChars.put(0x22E5, 0x22E4); // [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO 747 mirrorChars.put(0x22E6, 0x22E7); // [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO 748 mirrorChars.put(0x22E7, 0x22E6); // [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO 749 mirrorChars.put(0x22E8, 0x22E9); // [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO 750 mirrorChars.put(0x22E9, 0x22E8); // [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO 751 mirrorChars.put(0x22EA, 0x22EB); // [BEST FIT] NOT NORMAL SUBGROUP OF 752 mirrorChars.put(0x22EB, 0x22EA); // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP 753 mirrorChars.put(0x22EC, 0x22ED); // [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO 754 mirrorChars.put(0x22ED, 0x22EC); // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL 755 mirrorChars.put(0x22F0, 0x22F1); // UP RIGHT DIAGONAL ELLIPSIS 756 mirrorChars.put(0x22F1, 0x22F0); // DOWN RIGHT DIAGONAL ELLIPSIS 757 mirrorChars.put(0x22F2, 0x22FA); // ELEMENT OF WITH LONG HORIZONTAL STROKE 758 mirrorChars.put(0x22F3, 0x22FB); // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE 759 mirrorChars.put(0x22F4, 0x22FC); // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE 760 mirrorChars.put(0x22F6, 0x22FD); // ELEMENT OF WITH OVERBAR 761 mirrorChars.put(0x22F7, 0x22FE); // SMALL ELEMENT OF WITH OVERBAR 762 mirrorChars.put(0x22FA, 0x22F2); // CONTAINS WITH LONG HORIZONTAL STROKE 763 mirrorChars.put(0x22FB, 0x22F3); // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE 764 mirrorChars.put(0x22FC, 0x22F4); // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE 765 mirrorChars.put(0x22FD, 0x22F6); // CONTAINS WITH OVERBAR 766 mirrorChars.put(0x22FE, 0x22F7); // SMALL CONTAINS WITH OVERBAR 767 mirrorChars.put(0x2308, 0x2309); // LEFT CEILING 768 mirrorChars.put(0x2309, 0x2308); // RIGHT CEILING 769 mirrorChars.put(0x230A, 0x230B); // LEFT FLOOR 770 mirrorChars.put(0x230B, 0x230A); // RIGHT FLOOR 771 mirrorChars.put(0x2329, 0x232A); // LEFT-POINTING ANGLE BRACKET 772 mirrorChars.put(0x232A, 0x2329); // RIGHT-POINTING ANGLE BRACKET 773 mirrorChars.put(0x2768, 0x2769); // MEDIUM LEFT PARENTHESIS ORNAMENT 774 mirrorChars.put(0x2769, 0x2768); // MEDIUM RIGHT PARENTHESIS ORNAMENT 775 mirrorChars.put(0x276A, 0x276B); // MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT 776 mirrorChars.put(0x276B, 0x276A); // MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT 777 mirrorChars.put(0x276C, 0x276D); // MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT 778 mirrorChars.put(0x276D, 0x276C); // MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT 779 mirrorChars.put(0x276E, 0x276F); // HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT 780 mirrorChars.put(0x276F, 0x276E); // HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT 781 mirrorChars.put(0x2770, 0x2771); // HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT 782 mirrorChars.put(0x2771, 0x2770); // HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT 783 mirrorChars.put(0x2772, 0x2773); // LIGHT LEFT TORTOISE SHELL BRACKET 784 mirrorChars.put(0x2773, 0x2772); // LIGHT RIGHT TORTOISE SHELL BRACKET 785 mirrorChars.put(0x2774, 0x2775); // MEDIUM LEFT CURLY BRACKET ORNAMENT 786 mirrorChars.put(0x2775, 0x2774); // MEDIUM RIGHT CURLY BRACKET ORNAMENT 787 mirrorChars.put(0x27D5, 0x27D6); // LEFT OUTER JOIN 788 mirrorChars.put(0x27D6, 0x27D5); // RIGHT OUTER JOIN 789 mirrorChars.put(0x27DD, 0x27DE); // LONG RIGHT TACK 790 mirrorChars.put(0x27DE, 0x27DD); // LONG LEFT TACK 791 mirrorChars.put(0x27E2, 0x27E3); // WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK 792 mirrorChars.put(0x27E3, 0x27E2); // WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK 793 mirrorChars.put(0x27E4, 0x27E5); // WHITE SQUARE WITH LEFTWARDS TICK 794 mirrorChars.put(0x27E5, 0x27E4); // WHITE SQUARE WITH RIGHTWARDS TICK 795 mirrorChars.put(0x27E6, 0x27E7); // MATHEMATICAL LEFT WHITE SQUARE BRACKET 796 mirrorChars.put(0x27E7, 0x27E6); // MATHEMATICAL RIGHT WHITE SQUARE BRACKET 797 mirrorChars.put(0x27E8, 0x27E9); // MATHEMATICAL LEFT ANGLE BRACKET 798 mirrorChars.put(0x27E9, 0x27E8); // MATHEMATICAL RIGHT ANGLE BRACKET 799 mirrorChars.put(0x27EA, 0x27EB); // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET 800 mirrorChars.put(0x27EB, 0x27EA); // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET 801 mirrorChars.put(0x2983, 0x2984); // LEFT WHITE CURLY BRACKET 802 mirrorChars.put(0x2984, 0x2983); // RIGHT WHITE CURLY BRACKET 803 mirrorChars.put(0x2985, 0x2986); // LEFT WHITE PARENTHESIS 804 mirrorChars.put(0x2986, 0x2985); // RIGHT WHITE PARENTHESIS 805 mirrorChars.put(0x2987, 0x2988); // Z NOTATION LEFT IMAGE BRACKET 806 mirrorChars.put(0x2988, 0x2987); // Z NOTATION RIGHT IMAGE BRACKET 807 mirrorChars.put(0x2989, 0x298A); // Z NOTATION LEFT BINDING BRACKET 808 mirrorChars.put(0x298A, 0x2989); // Z NOTATION RIGHT BINDING BRACKET 809 mirrorChars.put(0x298B, 0x298C); // LEFT SQUARE BRACKET WITH UNDERBAR 810 mirrorChars.put(0x298C, 0x298B); // RIGHT SQUARE BRACKET WITH UNDERBAR 811 mirrorChars.put(0x298D, 0x2990); // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER 812 mirrorChars.put(0x298E, 0x298F); // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER 813 mirrorChars.put(0x298F, 0x298E); // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER 814 mirrorChars.put(0x2990, 0x298D); // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER 815 mirrorChars.put(0x2991, 0x2992); // LEFT ANGLE BRACKET WITH DOT 816 mirrorChars.put(0x2992, 0x2991); // RIGHT ANGLE BRACKET WITH DOT 817 mirrorChars.put(0x2993, 0x2994); // LEFT ARC LESS-THAN BRACKET 818 mirrorChars.put(0x2994, 0x2993); // RIGHT ARC GREATER-THAN BRACKET 819 mirrorChars.put(0x2995, 0x2996); // DOUBLE LEFT ARC GREATER-THAN BRACKET 820 mirrorChars.put(0x2996, 0x2995); // DOUBLE RIGHT ARC LESS-THAN BRACKET 821 mirrorChars.put(0x2997, 0x2998); // LEFT BLACK TORTOISE SHELL BRACKET 822 mirrorChars.put(0x2998, 0x2997); // RIGHT BLACK TORTOISE SHELL BRACKET 823 mirrorChars.put(0x29B8, 0x2298); // CIRCLED REVERSE SOLIDUS 824 mirrorChars.put(0x29C0, 0x29C1); // CIRCLED LESS-THAN 825 mirrorChars.put(0x29C1, 0x29C0); // CIRCLED GREATER-THAN 826 mirrorChars.put(0x29C4, 0x29C5); // SQUARED RISING DIAGONAL SLASH 827 mirrorChars.put(0x29C5, 0x29C4); // SQUARED FALLING DIAGONAL SLASH 828 mirrorChars.put(0x29CF, 0x29D0); // LEFT TRIANGLE BESIDE VERTICAL BAR 829 mirrorChars.put(0x29D0, 0x29CF); // VERTICAL BAR BESIDE RIGHT TRIANGLE 830 mirrorChars.put(0x29D1, 0x29D2); // BOWTIE WITH LEFT HALF BLACK 831 mirrorChars.put(0x29D2, 0x29D1); // BOWTIE WITH RIGHT HALF BLACK 832 mirrorChars.put(0x29D4, 0x29D5); // TIMES WITH LEFT HALF BLACK 833 mirrorChars.put(0x29D5, 0x29D4); // TIMES WITH RIGHT HALF BLACK 834 mirrorChars.put(0x29D8, 0x29D9); // LEFT WIGGLY FENCE 835 mirrorChars.put(0x29D9, 0x29D8); // RIGHT WIGGLY FENCE 836 mirrorChars.put(0x29DA, 0x29DB); // LEFT DOUBLE WIGGLY FENCE 837 mirrorChars.put(0x29DB, 0x29DA); // RIGHT DOUBLE WIGGLY FENCE 838 mirrorChars.put(0x29F5, 0x2215); // REVERSE SOLIDUS OPERATOR 839 mirrorChars.put(0x29F8, 0x29F9); // BIG SOLIDUS 840 mirrorChars.put(0x29F9, 0x29F8); // BIG REVERSE SOLIDUS 841 mirrorChars.put(0x29FC, 0x29FD); // LEFT-POINTING CURVED ANGLE BRACKET 842 mirrorChars.put(0x29FD, 0x29FC); // RIGHT-POINTING CURVED ANGLE BRACKET 843 mirrorChars.put(0x2A2B, 0x2A2C); // MINUS SIGN WITH FALLING DOTS 844 mirrorChars.put(0x2A2C, 0x2A2B); // MINUS SIGN WITH RISING DOTS 845 mirrorChars.put(0x2A2D, 0x2A2C); // PLUS SIGN IN LEFT HALF CIRCLE 846 mirrorChars.put(0x2A2E, 0x2A2D); // PLUS SIGN IN RIGHT HALF CIRCLE 847 mirrorChars.put(0x2A34, 0x2A35); // MULTIPLICATION SIGN IN LEFT HALF CIRCLE 848 mirrorChars.put(0x2A35, 0x2A34); // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE 849 mirrorChars.put(0x2A3C, 0x2A3D); // INTERIOR PRODUCT 850 mirrorChars.put(0x2A3D, 0x2A3C); // RIGHTHAND INTERIOR PRODUCT 851 mirrorChars.put(0x2A64, 0x2A65); // Z NOTATION DOMAIN ANTIRESTRICTION 852 mirrorChars.put(0x2A65, 0x2A64); // Z NOTATION RANGE ANTIRESTRICTION 853 mirrorChars.put(0x2A79, 0x2A7A); // LESS-THAN WITH CIRCLE INSIDE 854 mirrorChars.put(0x2A7A, 0x2A79); // GREATER-THAN WITH CIRCLE INSIDE 855 mirrorChars.put(0x2A7D, 0x2A7E); // LESS-THAN OR SLANTED EQUAL TO 856 mirrorChars.put(0x2A7E, 0x2A7D); // GREATER-THAN OR SLANTED EQUAL TO 857 mirrorChars.put(0x2A7F, 0x2A80); // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE 858 mirrorChars.put(0x2A80, 0x2A7F); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE 859 mirrorChars.put(0x2A81, 0x2A82); // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE 860 mirrorChars.put(0x2A82, 0x2A81); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE 861 mirrorChars.put(0x2A83, 0x2A84); // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT 862 mirrorChars.put(0x2A84, 0x2A83); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT 863 mirrorChars.put(0x2A8B, 0x2A8C); // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN 864 mirrorChars.put(0x2A8C, 0x2A8B); // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN 865 mirrorChars.put(0x2A91, 0x2A92); // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL 866 mirrorChars.put(0x2A92, 0x2A91); // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL 867 mirrorChars.put(0x2A93, 0x2A94); // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL 868 mirrorChars.put(0x2A94, 0x2A93); // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL 869 mirrorChars.put(0x2A95, 0x2A96); // SLANTED EQUAL TO OR LESS-THAN 870 mirrorChars.put(0x2A96, 0x2A95); // SLANTED EQUAL TO OR GREATER-THAN 871 mirrorChars.put(0x2A97, 0x2A98); // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE 872 mirrorChars.put(0x2A98, 0x2A97); // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE 873 mirrorChars.put(0x2A99, 0x2A9A); // DOUBLE-LINE EQUAL TO OR LESS-THAN 874 mirrorChars.put(0x2A9A, 0x2A99); // DOUBLE-LINE EQUAL TO OR GREATER-THAN 875 mirrorChars.put(0x2A9B, 0x2A9C); // DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN 876 mirrorChars.put(0x2A9C, 0x2A9B); // DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN 877 mirrorChars.put(0x2AA1, 0x2AA2); // DOUBLE NESTED LESS-THAN 878 mirrorChars.put(0x2AA2, 0x2AA1); // DOUBLE NESTED GREATER-THAN 879 mirrorChars.put(0x2AA6, 0x2AA7); // LESS-THAN CLOSED BY CURVE 880 mirrorChars.put(0x2AA7, 0x2AA6); // GREATER-THAN CLOSED BY CURVE 881 mirrorChars.put(0x2AA8, 0x2AA9); // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL 882 mirrorChars.put(0x2AA9, 0x2AA8); // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL 883 mirrorChars.put(0x2AAA, 0x2AAB); // SMALLER THAN 884 mirrorChars.put(0x2AAB, 0x2AAA); // LARGER THAN 885 mirrorChars.put(0x2AAC, 0x2AAD); // SMALLER THAN OR EQUAL TO 886 mirrorChars.put(0x2AAD, 0x2AAC); // LARGER THAN OR EQUAL TO 887 mirrorChars.put(0x2AAF, 0x2AB0); // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN 888 mirrorChars.put(0x2AB0, 0x2AAF); // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN 889 mirrorChars.put(0x2AB3, 0x2AB4); // PRECEDES ABOVE EQUALS SIGN 890 mirrorChars.put(0x2AB4, 0x2AB3); // SUCCEEDS ABOVE EQUALS SIGN 891 mirrorChars.put(0x2ABB, 0x2ABC); // DOUBLE PRECEDES 892 mirrorChars.put(0x2ABC, 0x2ABB); // DOUBLE SUCCEEDS 893 mirrorChars.put(0x2ABD, 0x2ABE); // SUBSET WITH DOT 894 mirrorChars.put(0x2ABE, 0x2ABD); // SUPERSET WITH DOT 895 mirrorChars.put(0x2ABF, 0x2AC0); // SUBSET WITH PLUS SIGN BELOW 896 mirrorChars.put(0x2AC0, 0x2ABF); // SUPERSET WITH PLUS SIGN BELOW 897 mirrorChars.put(0x2AC1, 0x2AC2); // SUBSET WITH MULTIPLICATION SIGN BELOW 898 mirrorChars.put(0x2AC2, 0x2AC1); // SUPERSET WITH MULTIPLICATION SIGN BELOW 899 mirrorChars.put(0x2AC3, 0x2AC4); // SUBSET OF OR EQUAL TO WITH DOT ABOVE 900 mirrorChars.put(0x2AC4, 0x2AC3); // SUPERSET OF OR EQUAL TO WITH DOT ABOVE 901 mirrorChars.put(0x2AC5, 0x2AC6); // SUBSET OF ABOVE EQUALS SIGN 902 mirrorChars.put(0x2AC6, 0x2AC5); // SUPERSET OF ABOVE EQUALS SIGN 903 mirrorChars.put(0x2ACD, 0x2ACE); // SQUARE LEFT OPEN BOX OPERATOR 904 mirrorChars.put(0x2ACE, 0x2ACD); // SQUARE RIGHT OPEN BOX OPERATOR 905 mirrorChars.put(0x2ACF, 0x2AD0); // CLOSED SUBSET 906 mirrorChars.put(0x2AD0, 0x2ACF); // CLOSED SUPERSET 907 mirrorChars.put(0x2AD1, 0x2AD2); // CLOSED SUBSET OR EQUAL TO 908 mirrorChars.put(0x2AD2, 0x2AD1); // CLOSED SUPERSET OR EQUAL TO 909 mirrorChars.put(0x2AD3, 0x2AD4); // SUBSET ABOVE SUPERSET 910 mirrorChars.put(0x2AD4, 0x2AD3); // SUPERSET ABOVE SUBSET 911 mirrorChars.put(0x2AD5, 0x2AD6); // SUBSET ABOVE SUBSET 912 mirrorChars.put(0x2AD6, 0x2AD5); // SUPERSET ABOVE SUPERSET 913 mirrorChars.put(0x2ADE, 0x22A6); // SHORT LEFT TACK 914 mirrorChars.put(0x2AE3, 0x22A9); // DOUBLE VERTICAL BAR LEFT TURNSTILE 915 mirrorChars.put(0x2AE4, 0x22A8); // VERTICAL BAR DOUBLE LEFT TURNSTILE 916 mirrorChars.put(0x2AE5, 0x22AB); // DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE 917 mirrorChars.put(0x2AEC, 0x2AED); // DOUBLE STROKE NOT SIGN 918 mirrorChars.put(0x2AED, 0x2AEC); // REVERSED DOUBLE STROKE NOT SIGN 919 mirrorChars.put(0x2AF7, 0x2AF8); // TRIPLE NESTED LESS-THAN 920 mirrorChars.put(0x2AF8, 0x2AF7); // TRIPLE NESTED GREATER-THAN 921 mirrorChars.put(0x2AF9, 0x2AFA); // DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO 922 mirrorChars.put(0x2AFA, 0x2AF9); // DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO 923 mirrorChars.put(0x3008, 0x3009); // LEFT ANGLE BRACKET 924 mirrorChars.put(0x3009, 0x3008); // RIGHT ANGLE BRACKET 925 mirrorChars.put(0x300A, 0x300B); // LEFT DOUBLE ANGLE BRACKET 926 mirrorChars.put(0x300B, 0x300A); // RIGHT DOUBLE ANGLE BRACKET 927 mirrorChars.put(0x300C, 0x300D); // [BEST FIT] LEFT CORNER BRACKET 928 mirrorChars.put(0x300D, 0x300C); // [BEST FIT] RIGHT CORNER BRACKET 929 mirrorChars.put(0x300E, 0x300F); // [BEST FIT] LEFT WHITE CORNER BRACKET 930 mirrorChars.put(0x300F, 0x300E); // [BEST FIT] RIGHT WHITE CORNER BRACKET 931 mirrorChars.put(0x3010, 0x3011); // LEFT BLACK LENTICULAR BRACKET 932 mirrorChars.put(0x3011, 0x3010); // RIGHT BLACK LENTICULAR BRACKET 933 mirrorChars.put(0x3014, 0x3015); // LEFT TORTOISE SHELL BRACKET 934 mirrorChars.put(0x3015, 0x3014); // RIGHT TORTOISE SHELL BRACKET 935 mirrorChars.put(0x3016, 0x3017); // LEFT WHITE LENTICULAR BRACKET 936 mirrorChars.put(0x3017, 0x3016); // RIGHT WHITE LENTICULAR BRACKET 937 mirrorChars.put(0x3018, 0x3019); // LEFT WHITE TORTOISE SHELL BRACKET 938 mirrorChars.put(0x3019, 0x3018); // RIGHT WHITE TORTOISE SHELL BRACKET 939 mirrorChars.put(0x301A, 0x301B); // LEFT WHITE SQUARE BRACKET 940 mirrorChars.put(0x301B, 0x301A); // RIGHT WHITE SQUARE BRACKET 941 mirrorChars.put(0xFF08, 0xFF09); // FULLWIDTH LEFT PARENTHESIS 942 mirrorChars.put(0xFF09, 0xFF08); // FULLWIDTH RIGHT PARENTHESIS 943 mirrorChars.put(0xFF1C, 0xFF1E); // FULLWIDTH LESS-THAN SIGN 944 mirrorChars.put(0xFF1E, 0xFF1C); // FULLWIDTH GREATER-THAN SIGN 945 mirrorChars.put(0xFF3B, 0xFF3D); // FULLWIDTH LEFT SQUARE BRACKET 946 mirrorChars.put(0xFF3D, 0xFF3B); // FULLWIDTH RIGHT SQUARE BRACKET 947 mirrorChars.put(0xFF5B, 0xFF5D); // FULLWIDTH LEFT CURLY BRACKET 948 mirrorChars.put(0xFF5D, 0xFF5B); // FULLWIDTH RIGHT CURLY BRACKET 949 mirrorChars.put(0xFF5F, 0xFF60); // FULLWIDTH LEFT WHITE PARENTHESIS 950 mirrorChars.put(0xFF60, 0xFF5F); // FULLWIDTH RIGHT WHITE PARENTHESIS 951 mirrorChars.put(0xFF62, 0xFF63); // [BEST FIT] HALFWIDTH LEFT CORNER BRACKET 952 mirrorChars.put(0xFF63, 0xFF62); // [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET 953 } 954}