| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.gvt.text; |
| |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.TextLayout; |
| import java.text.AttributedCharacterIterator; |
| import java.text.AttributedString; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * An attributed character iterator that does the reordering of the characters |
| * for bidirectional text. It reorders the characters so they are in visual order. |
| * It also assigns a BIDI_LEVEL attribute to each character which can be used |
| * to split the reordered ACI into text runs based on direction. ie. characters |
| * in a text run will all have the same bidi level. |
| * |
| * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a> |
| * @version $Id$ |
| */ |
| public class BidiAttributedCharacterIterator implements AttributedCharacterIterator { |
| |
| private AttributedCharacterIterator reorderedACI; |
| private FontRenderContext frc; |
| private int chunkStart; |
| private int [] newCharOrder; |
| private static final Float FLOAT_NAN = Float.NaN; |
| |
| |
| protected BidiAttributedCharacterIterator |
| (AttributedCharacterIterator reorderedACI, |
| FontRenderContext frc, |
| int chunkStart, |
| int [] newCharOrder) { |
| this.reorderedACI = reorderedACI; |
| this.frc = frc; |
| this.chunkStart = chunkStart; |
| this.newCharOrder = newCharOrder; |
| } |
| |
| |
| /** |
| * Constructs a character iterator that represents the visual display order |
| * of characters within bidirectional text. |
| * |
| * @param aci The character iterator containing the characters in logical |
| * order. |
| * @param frc The current font render context |
| */ |
| public BidiAttributedCharacterIterator(AttributedCharacterIterator aci, |
| FontRenderContext frc, |
| int chunkStart) { |
| |
| this.frc = frc; |
| this.chunkStart = chunkStart; |
| aci.first(); |
| int numChars = aci.getEndIndex()-aci.getBeginIndex(); |
| AttributedString as; |
| |
| // Ideally we would do a 'quick' check on chars and |
| // attributes to decide if we really need to do bidi or not. |
| if (false) { |
| // Believe it or not this is much slower than the else case |
| // but the two are exactly equivalent (including the stripping |
| // of null keys/values). |
| as = new AttributedString(aci); |
| } else { |
| StringBuffer strB = new StringBuffer( numChars ); |
| char c = aci.first(); |
| for (int i = 0; i < numChars; i++) { |
| strB.append(c); |
| c = aci.next(); |
| } |
| as = new AttributedString(strB.toString()); |
| int start=aci.getBeginIndex(); |
| int end =aci.getEndIndex(); |
| int index = start; |
| while (index < end) { |
| aci.setIndex(index); |
| Map attrMap = aci.getAttributes(); |
| int extent = aci.getRunLimit(); |
| Map destMap = new HashMap(attrMap.size()); |
| for (Object o : attrMap.entrySet()) { |
| // Font doesn't like getting attribute sets with |
| // null keys or values so we strip them here. |
| Map.Entry e = (Map.Entry) o; |
| Object key = e.getKey(); |
| if (key == null) continue; |
| Object value = e.getValue(); |
| if (value == null) continue; |
| destMap.put(key, value); |
| } |
| // System.out.println("Run: " + (index-start) + "->" + |
| // (extent-start) + " of " + numChars); |
| as.addAttributes (destMap, index-start, extent-start); |
| index = extent; |
| } |
| } |
| |
| // We Just want it to do BIDI for us... |
| // In 1.4 we might be able to use the BIDI class... |
| TextLayout tl = new TextLayout(as.getIterator(), frc); |
| |
| int[] charIndices = new int[numChars]; |
| int[] charLevels = new int[numChars]; |
| |
| int runStart = 0; |
| int currBiDi = tl.getCharacterLevel(0); |
| charIndices[0] = 0; |
| charLevels [0] = currBiDi; |
| int maxBiDi = currBiDi; |
| |
| for (int i = 1; i < numChars; i++) { |
| int newBiDi = tl.getCharacterLevel(i); |
| charIndices[i] = i; |
| charLevels [i] = newBiDi; |
| |
| if (newBiDi != currBiDi) { |
| as.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL, |
| currBiDi, runStart, i); |
| runStart = i; |
| currBiDi = newBiDi; |
| if (newBiDi > maxBiDi) maxBiDi = newBiDi; |
| } |
| } |
| as.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL, |
| currBiDi, runStart, numChars); |
| |
| aci = as.getIterator(); |
| |
| if ((runStart == 0) && (currBiDi==0)) { |
| // This avoids all the mucking about we need to do when |
| // bidi is actually performed for cases where it |
| // is not actually needed. |
| this.reorderedACI = aci; |
| newCharOrder = new int[numChars]; |
| for (int i=0; i<numChars; i++) |
| newCharOrder[i] = chunkStart+i; |
| return; |
| } |
| |
| // work out the new character order |
| newCharOrder = doBidiReorder(charIndices, charLevels, |
| numChars, maxBiDi); |
| |
| // construct the string in the new order |
| StringBuffer reorderedString = new StringBuffer( numChars ); |
| int reorderedFirstChar = 0; |
| for (int i = 0; i < numChars; i++) { |
| int srcIdx = newCharOrder[i]; |
| char c = aci.setIndex(srcIdx); |
| if (srcIdx == 0) reorderedFirstChar = i; |
| |
| // check for mirrored char |
| int bidiLevel = tl.getCharacterLevel(srcIdx); |
| if ((bidiLevel & 0x01) != 0) { |
| // bidi level is odd so writing dir is right to left |
| // So get the mirror version of the char if there |
| // is one. |
| c = (char)mirrorChar(c); |
| } |
| |
| reorderedString.append(c); |
| } |
| |
| // construct the reordered ACI |
| AttributedString reorderedAS |
| = new AttributedString(reorderedString.toString()); |
| Map [] attrs = new Map[numChars]; |
| int start=aci.getBeginIndex(); |
| int end =aci.getEndIndex(); |
| int index = start; |
| while (index < end) { |
| aci.setIndex(index); |
| Map attrMap = aci.getAttributes(); |
| int extent = aci.getRunLimit(); |
| for (int i=index; i<extent; i++) |
| attrs[i-start] = attrMap; |
| index = extent; |
| } |
| |
| runStart=0; |
| Map prevAttrMap = attrs[newCharOrder[0]]; |
| for (int i = 1; i < numChars; i++) { |
| Map attrMap = attrs[newCharOrder[i]]; |
| if (attrMap != prevAttrMap) { |
| // Change in attrs set last run... |
| reorderedAS.addAttributes(prevAttrMap, runStart, i); |
| prevAttrMap = attrMap; |
| runStart = i; |
| } |
| } |
| reorderedAS.addAttributes(prevAttrMap, runStart, numChars); |
| |
| // transfer any position atttributes to the new first char |
| aci.first(); |
| Float x = (Float) aci.getAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.X); |
| if (x != null && !x.isNaN()) { |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.X, |
| FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1); |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.X, x, 0, 1); |
| } |
| |
| Float y = (Float) aci.getAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.Y); |
| if (y != null && !y.isNaN()) { |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.Y, |
| FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1); |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.Y, y, 0, 1); |
| } |
| |
| |
| Float dx = (Float) aci.getAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DX); |
| if (dx != null && !dx.isNaN()) { |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DX, |
| FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1); |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DX, dx, 0, 1); |
| } |
| Float dy = (Float) aci.getAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DY); |
| if (dy != null && !dy.isNaN()) { |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DY, |
| FLOAT_NAN, reorderedFirstChar, reorderedFirstChar+1); |
| reorderedAS.addAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.DY, dy, 0, 1); |
| } |
| |
| // assign arabic form attributes to any arabic chars in the string |
| reorderedAS = ArabicTextHandler.assignArabicForms(reorderedAS); |
| |
| // Shift the values to match the source text string... |
| for (int i=0; i<newCharOrder.length; i++) { |
| newCharOrder[i] += chunkStart; |
| } |
| reorderedACI = reorderedAS.getIterator(); |
| } |
| |
| // Returns an array that give the character index in the source ACI for |
| // each character in this ACI. |
| public int[] getCharMap() { return newCharOrder; } |
| |
| /** |
| * Calculates the display order of the characters based on the specified |
| * character levels. This method is recursive. |
| * |
| * @param charIndices An array contianing the original indices of each char. |
| * @param charLevels An array containing the current levels of each char. |
| * @param numChars The number of chars to reorder. |
| * |
| * @return An array contianing the reordered character indices. |
| */ |
| private int[] doBidiReorder(int[] charIndices, int[] charLevels, |
| int numChars, int highestLevel) { |
| if (highestLevel == 0) return charIndices; |
| |
| // find all groups of chars at the highest level and reverse |
| // their order |
| int currentIndex = 0; |
| while (currentIndex < numChars) { |
| |
| // find the first char at the highest index |
| while ((currentIndex < numChars) && |
| (charLevels[currentIndex] < highestLevel)) { |
| currentIndex++; |
| } |
| if (currentIndex == numChars) { |
| // have reached the end of the string |
| break; |
| } |
| int startIndex = currentIndex; |
| |
| currentIndex++; |
| // now find the index where the run at the highestLevel end |
| while ((currentIndex < numChars) && |
| (charLevels[currentIndex] == highestLevel)) { |
| currentIndex++; |
| } |
| int endIndex = currentIndex-1; |
| |
| // now reverse the chars between startIndex and endIndex |
| |
| // Calculate the middle of the swap region, we include |
| // the middle char when region is an odd number of |
| // chars wide so we properly decrement it's charLevel. |
| int middle = ((endIndex-startIndex)>>1)+1; |
| for (int i = 0; i<middle; i++) { |
| int tmp = charIndices[startIndex+i]; |
| charIndices[startIndex+i] = charIndices[endIndex-i]; |
| charIndices[endIndex -i] = tmp; |
| |
| charLevels [startIndex+i] = highestLevel-1; |
| charLevels [endIndex -i] = highestLevel-1; |
| } |
| } |
| return doBidiReorder(charIndices, charLevels, numChars, highestLevel-1); |
| } |
| |
| |
| |
| /** |
| * Get the keys of all attributes defined on the iterator's text range. |
| */ |
| public Set getAllAttributeKeys() { |
| return reorderedACI.getAllAttributeKeys(); |
| } |
| |
| /** |
| * Get the value of the named attribute for the current |
| * character. |
| */ |
| public Object getAttribute(AttributedCharacterIterator.Attribute attribute) { |
| return reorderedACI.getAttribute(attribute); |
| } |
| |
| /** |
| * Returns a map with the attributes defined on the current |
| * character. |
| */ |
| public Map getAttributes() { |
| return reorderedACI.getAttributes(); |
| } |
| |
| /** |
| * Get the index of the first character following the |
| * run with respect to all attributes containing the current |
| * character. |
| */ |
| public int getRunLimit() { |
| return reorderedACI.getRunLimit(); |
| } |
| |
| /** |
| * Get the index of the first character following the |
| * run with respect to the given attribute containing the current |
| * character. |
| */ |
| public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { |
| return reorderedACI.getRunLimit(attribute); |
| } |
| |
| /** |
| * Get the index of the first character following the |
| * run with respect to the given attributes containing the current |
| * character. |
| */ |
| public int getRunLimit(Set attributes) { |
| return reorderedACI.getRunLimit(attributes); |
| } |
| |
| /** |
| * Get the index of the first character of the run with |
| * respect to all attributes containing the current character. |
| */ |
| public int getRunStart() { |
| return reorderedACI.getRunStart(); |
| } |
| |
| /** |
| * Get the index of the first character of the run with |
| * respect to the given attribute containing the current character. |
| * @param attribute The attribute for whose appearance the first offset |
| * is requested. |
| */ |
| public int getRunStart(AttributedCharacterIterator.Attribute attribute) { |
| return reorderedACI.getRunStart(attribute); |
| } |
| |
| /** |
| * Get the index of the first character of the run with |
| * respect to the given attributes containing the current character. |
| * @param attributes the Set of attributes which begins at the returned index. |
| */ |
| public int getRunStart(Set attributes) { |
| return reorderedACI.getRunStart(attributes); |
| } |
| |
| /** |
| * Creates a copy of this iterator. |
| */ |
| public Object clone() { |
| return new BidiAttributedCharacterIterator |
| ((AttributedCharacterIterator)reorderedACI.clone(), |
| frc, chunkStart, (int [])newCharOrder.clone()); |
| } |
| |
| /** |
| * Gets the character at the current position (as returned by getIndex()). |
| */ |
| public char current() { |
| return reorderedACI.current(); |
| } |
| |
| /** |
| * Sets the position to getBeginIndex() and returns the character at |
| * that position. |
| */ |
| public char first() { |
| return reorderedACI.first(); |
| } |
| |
| /** |
| * Returns the start index of the text. |
| */ |
| public int getBeginIndex() { |
| return reorderedACI.getBeginIndex(); |
| } |
| |
| /** |
| * Returns the end index of the text. |
| */ |
| public int getEndIndex() { |
| return reorderedACI.getEndIndex(); |
| } |
| |
| /** |
| * Returns the current index. |
| */ |
| public int getIndex() { |
| return reorderedACI.getIndex(); |
| } |
| |
| /** |
| * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty) |
| * and returns the character at that position. |
| */ |
| public char last() { |
| return reorderedACI.last(); |
| } |
| |
| /** |
| * Increments the iterator's index by one and returns the character at |
| * the new index. |
| */ |
| public char next() { |
| return reorderedACI.next(); |
| } |
| |
| /** |
| * Decrements the iterator's index by one and returns the character at the new index. |
| */ |
| public char previous() { |
| return reorderedACI.previous(); |
| } |
| |
| /** |
| * Sets the position to the specified position in the text and returns that character. |
| */ |
| public char setIndex(int position) { |
| return reorderedACI.setIndex(position); |
| } |
| |
| /** |
| * @param c the character to 'mirror' |
| * @return either the 'mirror'-character for c or c itself |
| */ |
| public static int mirrorChar(int c) { |
| |
| // note: the switch-statement is compiled to a tableswitch, |
| // which is evaluated by doing a binary search through the sorted case-list. |
| // the ca 130 cases are searched with max 8 compares |
| |
| switch(c) { |
| // set up the mirrored glyph case statement; |
| case 0x0028: return 0x0029; //LEFT PARENTHESIS |
| case 0x0029: return 0x0028; //RIGHT PARENTHESIS |
| case 0x003C: return 0x003E; //LESS-THAN SIGN |
| case 0x003E: return 0x003C; //GREATER-THAN SIGN |
| case 0x005B: return 0x005D; //LEFT SQUARE BRACKET |
| case 0x005D: return 0x005B; //RIGHT SQUARE BRACKET |
| case 0x007B: return 0x007D; //LEFT CURLY BRACKET |
| case 0x007D: return 0x007B; //RIGHT CURLY BRACKET |
| case 0x00AB: return 0x00BB; //LEFT-POINTING DOUBLE ANGLE QUOTATION MARK |
| case 0x00BB: return 0x00AB; //RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK |
| case 0x2039: return 0x203A; //SINGLE LEFT-POINTING ANGLE QUOTATION MARK |
| case 0x203A: return 0x2039; //SINGLE RIGHT-POINTING ANGLE QUOTATION MARK |
| case 0x2045: return 0x2046; //LEFT SQUARE BRACKET WITH QUILL |
| case 0x2046: return 0x2045; //RIGHT SQUARE BRACKET WITH QUILL |
| case 0x207D: return 0x207E; //SUPERSCRIPT LEFT PARENTHESIS |
| case 0x207E: return 0x207D; //SUPERSCRIPT RIGHT PARENTHESIS |
| case 0x208D: return 0x208E; //SUBSCRIPT LEFT PARENTHESIS |
| case 0x208E: return 0x208D; //SUBSCRIPT RIGHT PARENTHESIS |
| case 0x2208: return 0x220B; //ELEMENT OF |
| case 0x2209: return 0x220C; //NOT AN ELEMENT OF |
| case 0x220A: return 0x220D; //SMALL ELEMENT OF |
| case 0x220B: return 0x2208; //CONTAINS AS MEMBER |
| case 0x220C: return 0x2209; //DOES NOT CONTAIN AS MEMBER |
| case 0x220D: return 0x220A; //SMALL CONTAINS AS MEMBER |
| case 0x223C: return 0x223D; //TILDE OPERATOR |
| case 0x223D: return 0x223C; //REVERSED TILDE |
| case 0x2243: return 0x22CD; //ASYMPTOTICALLY EQUAL TO |
| case 0x2252: return 0x2253; //APPROXIMATELY EQUAL TO OR THE IMAGE OF |
| case 0x2253: return 0x2252; //IMAGE OF OR APPROXIMATELY EQUAL TO |
| case 0x2254: return 0x2255; //COLON EQUALS |
| case 0x2255: return 0x2254; //EQUALS COLON |
| case 0x2264: return 0x2265; //LESS-THAN OR EQUAL TO |
| case 0x2265: return 0x2264; //GREATER-THAN OR EQUAL TO |
| case 0x2266: return 0x2267; //LESS-THAN OVER EQUAL TO |
| case 0x2267: return 0x2266; //GREATER-THAN OVER EQUAL TO |
| case 0x2268: return 0x2269; //[BEST FIT] LESS-THAN BUT NOT EQUAL TO |
| case 0x2269: return 0x2268; //[BEST FIT] GREATER-THAN BUT NOT EQUAL TO |
| case 0x226A: return 0x226B; //MUCH LESS-THAN |
| case 0x226B: return 0x226A; //MUCH GREATER-THAN |
| case 0x226E: return 0x226F; //[BEST FIT] NOT LESS-THAN |
| case 0x226F: return 0x226E; //[BEST FIT] NOT GREATER-THAN |
| case 0x2270: return 0x2271; //[BEST FIT] NEITHER LESS-THAN NOR EQUAL TO |
| case 0x2271: return 0x2270; //[BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO |
| case 0x2272: return 0x2273; //[BEST FIT] LESS-THAN OR EQUIVALENT TO |
| case 0x2273: return 0x2272; //[BEST FIT] GREATER-THAN OR EQUIVALENT TO |
| case 0x2274: return 0x2275; //[BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO |
| case 0x2275: return 0x2274; //[BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO |
| case 0x2276: return 0x2277; //LESS-THAN OR GREATER-THAN |
| case 0x2277: return 0x2276; //GREATER-THAN OR LESS-THAN |
| case 0x2278: return 0x2279; //NEITHER LESS-THAN NOR GREATER-THAN |
| case 0x2279: return 0x2278; //NEITHER GREATER-THAN NOR LESS-THAN |
| case 0x227A: return 0x227B; //PRECEDES |
| case 0x227B: return 0x227A; //SUCCEEDS |
| case 0x227C: return 0x227D; //PRECEDES OR EQUAL TO |
| case 0x227D: return 0x227C; //SUCCEEDS OR EQUAL TO |
| case 0x227E: return 0x227F; //[BEST FIT] PRECEDES OR EQUIVALENT TO |
| case 0x227F: return 0x227E; //[BEST FIT] SUCCEEDS OR EQUIVALENT TO |
| case 0x2280: return 0x2281; //[BEST FIT] DOES NOT PRECEDE |
| case 0x2281: return 0x2280; //[BEST FIT] DOES NOT SUCCEED |
| case 0x2282: return 0x2283; //SUBSET OF |
| case 0x2283: return 0x2282; //SUPERSET OF |
| case 0x2284: return 0x2285; //[BEST FIT] NOT A SUBSET OF |
| case 0x2285: return 0x2284; //[BEST FIT] NOT A SUPERSET OF |
| case 0x2286: return 0x2287; //SUBSET OF OR EQUAL TO |
| case 0x2287: return 0x2286; //SUPERSET OF OR EQUAL TO |
| case 0x2288: return 0x2289; //[BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO |
| case 0x2289: return 0x2288; //[BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO |
| case 0x228A: return 0x228B; //[BEST FIT] SUBSET OF WITH NOT EQUAL TO |
| case 0x228B: return 0x228A; //[BEST FIT] SUPERSET OF WITH NOT EQUAL TO |
| case 0x228F: return 0x2290; //SQUARE IMAGE OF |
| case 0x2290: return 0x228F; //SQUARE ORIGINAL OF |
| case 0x2291: return 0x2292; //SQUARE IMAGE OF OR EQUAL TO |
| case 0x2292: return 0x2291; //SQUARE ORIGINAL OF OR EQUAL TO |
| case 0x22A2: return 0x22A3; //RIGHT TACK |
| case 0x22A3: return 0x22A2; //LEFT TACK |
| case 0x22B0: return 0x22B1; //PRECEDES UNDER RELATION |
| case 0x22B1: return 0x22B0; //SUCCEEDS UNDER RELATION |
| case 0x22B2: return 0x22B3; //NORMAL SUBGROUP OF |
| case 0x22B3: return 0x22B2; //CONTAINS AS NORMAL SUBGROUP |
| case 0x22B4: return 0x22B5; //NORMAL SUBGROUP OF OR EQUAL TO |
| case 0x22B5: return 0x22B4; //CONTAINS AS NORMAL SUBGROUP OR EQUAL TO |
| case 0x22B6: return 0x22B7; //ORIGINAL OF |
| case 0x22B7: return 0x22B6; //IMAGE OF |
| case 0x22C9: return 0x22CA; //LEFT NORMAL FACTOR SEMIDIRECT PRODUCT |
| case 0x22CA: return 0x22C9; //RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT |
| case 0x22CB: return 0x22CC; //LEFT SEMIDIRECT PRODUCT |
| case 0x22CC: return 0x22CB; //RIGHT SEMIDIRECT PRODUCT |
| case 0x22CD: return 0x2243; //REVERSED TILDE EQUALS |
| case 0x22D0: return 0x22D1; //DOUBLE SUBSET |
| case 0x22D1: return 0x22D0; //DOUBLE SUPERSET |
| case 0x22D6: return 0x22D7; //LESS-THAN WITH DOT |
| case 0x22D7: return 0x22D6; //GREATER-THAN WITH DOT |
| case 0x22D8: return 0x22D9; //VERY MUCH LESS-THAN |
| case 0x22D9: return 0x22D8; //VERY MUCH GREATER-THAN |
| case 0x22DA: return 0x22DB; //LESS-THAN EQUAL TO OR GREATER-THAN |
| case 0x22DB: return 0x22DA; //GREATER-THAN EQUAL TO OR LESS-THAN |
| case 0x22DC: return 0x22DD; //EQUAL TO OR LESS-THAN |
| case 0x22DD: return 0x22DC; //EQUAL TO OR GREATER-THAN |
| case 0x22DE: return 0x22DF; //EQUAL TO OR PRECEDES |
| case 0x22DF: return 0x22DE; //EQUAL TO OR SUCCEEDS |
| case 0x22E0: return 0x22E1; //[BEST FIT] DOES NOT PRECEDE OR EQUAL |
| case 0x22E1: return 0x22E0; //[BEST FIT] DOES NOT SUCCEED OR EQUAL |
| case 0x22E2: return 0x22E3; //[BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO |
| case 0x22E3: return 0x22E2; //[BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO |
| case 0x22E4: return 0x22E5; //[BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO |
| case 0x22E5: return 0x22E4; //[BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO |
| case 0x22E6: return 0x22E7; //[BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO |
| case 0x22E7: return 0x22E6; //[BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO |
| case 0x22E8: return 0x22E9; //[BEST FIT] PRECEDES BUT NOT EQUIVALENT TO |
| case 0x22E9: return 0x22E8; //[BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO |
| case 0x22EA: return 0x22EB; //[BEST FIT] NOT NORMAL SUBGROUP OF |
| case 0x22EB: return 0x22EA; //[BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP |
| case 0x22EC: return 0x22ED; //[BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO |
| case 0x22ED: return 0x22EC; //[BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL |
| case 0x22F0: return 0x22F1; //UP RIGHT DIAGONAL ELLIPSIS |
| case 0x22F1: return 0x22F0; //DOWN RIGHT DIAGONAL ELLIPSIS |
| case 0x2308: return 0x2309; //LEFT CEILING |
| case 0x2309: return 0x2308; //RIGHT CEILING |
| case 0x230A: return 0x230B; //LEFT FLOOR |
| case 0x230B: return 0x230A; //RIGHT FLOOR |
| case 0x2329: return 0x232A; //LEFT-POINTING ANGLE BRACKET |
| case 0x232A: return 0x2329; //RIGHT-POINTING ANGLE BRACKET |
| case 0x3008: return 0x3009; //LEFT ANGLE BRACKET |
| case 0x3009: return 0x3008; //RIGHT ANGLE BRACKET |
| case 0x300A: return 0x300B; //LEFT DOUBLE ANGLE BRACKET |
| case 0x300B: return 0x300A; //RIGHT DOUBLE ANGLE BRACKET |
| case 0x300C: return 0x300D; //[BEST FIT] LEFT CORNER BRACKET |
| case 0x300D: return 0x300C; //[BEST FIT] RIGHT CORNER BRACKET |
| case 0x300E: return 0x300F; //[BEST FIT] LEFT WHITE CORNER BRACKET |
| case 0x300F: return 0x300E; //[BEST FIT] RIGHT WHITE CORNER BRACKET |
| case 0x3010: return 0x3011; //LEFT BLACK LENTICULAR BRACKET |
| case 0x3011: return 0x3010; //RIGHT BLACK LENTICULAR BRACKET |
| case 0x3014: return 0x3015; //[BEST FIT] LEFT TORTOISE SHELL BRACKET |
| case 0x3015: return 0x3014; //[BEST FIT] RIGHT TORTOISE SHELL BRACKET |
| case 0x3016: return 0x3017; //LEFT WHITE LENTICULAR BRACKET |
| case 0x3017: return 0x3016; //RIGHT WHITE LENTICULAR BRACKET |
| case 0x3018: return 0x3019; //LEFT WHITE TORTOISE SHELL BRACKET |
| case 0x3019: return 0x3018; //RIGHT WHITE TORTOISE SHELL BRACKET |
| case 0x301A: return 0x301B; //LEFT WHITE SQUARE BRACKET |
| case 0x301B: return 0x301A; //RIGHT WHITE SQUARE BRACKET |
| default: break; |
| } |
| return c; |
| } |
| } |