2023-06-21 12:46:23 -04:00

946 lines
49 KiB
C#

using System;
using System.Collections;
using System.Text;
/*
*
* Copyright 2002 Paulo Soares
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* http://www.lowagie.com/iText/
*/
namespace iTextSharp.text.pdf {
/** Does all the line bidirectional processing with PdfChunk assembly.
*
* @author Paulo Soares (psoares@consiste.pt)
*/
public class BidiLine {
private const int pieceSizeStart = 256;
protected int runDirection;
protected int pieceSize = pieceSizeStart;
protected char[] text = new char[pieceSizeStart];
protected PdfChunk[] detailChunks = new PdfChunk[pieceSizeStart];
protected int totalTextLength = 0;
protected byte[] orderLevels = new byte[pieceSizeStart];
protected int[] indexChars = new int[pieceSizeStart];
protected ArrayList chunks = new ArrayList();
protected int indexChunk = 0;
protected int indexChunkChar = 0;
protected int currentChar = 0;
protected int storedRunDirection;
protected char[] storedText = new char[0];
protected PdfChunk[] storedDetailChunks = new PdfChunk[0];
protected int storedTotalTextLength = 0;
protected byte[] storedOrderLevels = new byte[0];
protected int[] storedIndexChars = new int[0];
protected int storedIndexChunk = 0;
protected int storedIndexChunkChar = 0;
protected int storedCurrentChar = 0;
protected bool shortStore;
protected static IntHashtable mirrorChars = new IntHashtable();
protected int arabicOptions;
/** Creates new BidiLine */
public BidiLine() {
}
public BidiLine(BidiLine org) {
runDirection = org.runDirection;
pieceSize = org.pieceSize;
text = (char[])org.text.Clone();
detailChunks = (PdfChunk[])org.detailChunks.Clone();
totalTextLength = org.totalTextLength;
orderLevels = (byte[])org.orderLevels.Clone();
indexChars = (int[])org.indexChars.Clone();
chunks = new ArrayList(org.chunks);
indexChunk = org.indexChunk;
indexChunkChar = org.indexChunkChar;
currentChar = org.currentChar;
storedRunDirection = org.storedRunDirection;
storedText = (char[])org.storedText.Clone();
storedDetailChunks = (PdfChunk[])org.storedDetailChunks.Clone();
storedTotalTextLength = org.storedTotalTextLength;
storedOrderLevels = (byte[])org.storedOrderLevels.Clone();
storedIndexChars = (int[])org.storedIndexChars.Clone();
storedIndexChunk = org.storedIndexChunk;
storedIndexChunkChar = org.storedIndexChunkChar;
storedCurrentChar = org.storedCurrentChar;
shortStore = org.shortStore;
arabicOptions = org.arabicOptions;
}
public bool IsEmpty() {
return (currentChar >= totalTextLength && indexChunk >= chunks.Count);
}
public void ClearChunks() {
chunks.Clear();
totalTextLength = 0;
currentChar = 0;
}
public bool GetParagraph(int runDirection) {
this.runDirection = runDirection;
currentChar = 0;
totalTextLength = 0;
bool hasText = false;
char c;
char uniC;
BaseFont bf;
for (; indexChunk < chunks.Count; ++indexChunk) {
PdfChunk ck = (PdfChunk)chunks[indexChunk];
bf = ck.Font.Font;
string s = ck.ToString();
int len = s.Length;
for (; indexChunkChar < len; ++indexChunkChar) {
c = s[indexChunkChar];
uniC = (char)bf.GetUnicodeEquivalent(c);
if (uniC == '\r' || uniC == '\n') {
// next condition is never true for CID
if (uniC == '\r' && indexChunkChar + 1 < len && s[indexChunkChar + 1] == '\n')
++indexChunkChar;
++indexChunkChar;
if (indexChunkChar >= len) {
indexChunkChar = 0;
++indexChunk;
}
hasText = true;
if (totalTextLength == 0)
detailChunks[0] = ck;
break;
}
AddPiece(c, ck);
}
if (hasText)
break;
indexChunkChar = 0;
}
if (totalTextLength == 0)
return hasText;
// remove trailing WS
totalTextLength = TrimRight(0, totalTextLength - 1) + 1;
if (totalTextLength == 0)
return true;
if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
if (orderLevels.Length < totalTextLength) {
orderLevels = new byte[pieceSize];
indexChars = new int[pieceSize];
}
ArabicLigaturizer.ProcessNumbers(text, 0, totalTextLength, arabicOptions);
BidiOrder order = new BidiOrder(text, 0, totalTextLength, (sbyte)(runDirection == PdfWriter.RUN_DIRECTION_RTL ? 1 : 0));
byte[] od = order.GetLevels();
for (int k = 0; k < totalTextLength; ++k) {
orderLevels[k] = od[k];
indexChars[k] = k;
}
DoArabicShapping();
MirrorGlyphs();
}
totalTextLength = TrimRightEx(0, totalTextLength - 1) + 1;
return true;
}
public void AddChunk(PdfChunk chunk) {
chunks.Add(chunk);
}
public void AddChunks(ArrayList chunks) {
this.chunks.AddRange(chunks);
}
public void AddPiece(char c, PdfChunk chunk) {
if (totalTextLength >= pieceSize) {
char[] tempText = text;
PdfChunk[] tempDetailChunks = detailChunks;
pieceSize *= 2;
text = new char[pieceSize];
detailChunks = new PdfChunk[pieceSize];
Array.Copy(tempText, 0, text, 0, totalTextLength);
Array.Copy(tempDetailChunks, 0, detailChunks, 0, totalTextLength);
}
text[totalTextLength] = c;
detailChunks[totalTextLength++] = chunk;
}
public void Save() {
if (indexChunk > 0) {
if (indexChunk >= chunks.Count)
chunks.Clear();
else {
for (--indexChunk; indexChunk >= 0; --indexChunk)
chunks.RemoveAt(indexChunk);
}
indexChunk = 0;
}
storedRunDirection = runDirection;
storedTotalTextLength = totalTextLength;
storedIndexChunk = indexChunk;
storedIndexChunkChar = indexChunkChar;
storedCurrentChar = currentChar;
shortStore = (currentChar < totalTextLength);
if (!shortStore) {
// long save
if (storedText.Length < totalTextLength) {
storedText = new char[totalTextLength];
storedDetailChunks = new PdfChunk[totalTextLength];
}
Array.Copy(text, 0, storedText, 0, totalTextLength);
Array.Copy(detailChunks, 0, storedDetailChunks, 0, totalTextLength);
}
if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
if (storedOrderLevels.Length < totalTextLength) {
storedOrderLevels = new byte[totalTextLength];
storedIndexChars = new int[totalTextLength];
}
Array.Copy(orderLevels, currentChar, storedOrderLevels, currentChar, totalTextLength - currentChar);
Array.Copy(indexChars, currentChar, storedIndexChars, currentChar, totalTextLength - currentChar);
}
}
public void Restore() {
runDirection = storedRunDirection;
totalTextLength = storedTotalTextLength;
indexChunk = storedIndexChunk;
indexChunkChar = storedIndexChunkChar;
currentChar = storedCurrentChar;
if (!shortStore) {
// long restore
Array.Copy(storedText, 0, text, 0, totalTextLength);
Array.Copy(storedDetailChunks, 0, detailChunks, 0, totalTextLength);
}
if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
Array.Copy(storedOrderLevels, currentChar, orderLevels, currentChar, totalTextLength - currentChar);
Array.Copy(storedIndexChars, currentChar, indexChars, currentChar, totalTextLength - currentChar);
}
}
public void MirrorGlyphs() {
for (int k = 0; k < totalTextLength; ++k) {
if ((orderLevels[k] & 1) == 1) {
int mirror = mirrorChars[text[k]];
if (mirror != 0)
text[k] = (char)mirror;
}
}
}
public void DoArabicShapping() {
int src = 0;
int dest = 0;
for (;;) {
while (src < totalTextLength) {
char c = text[src];
if (c >= 0x0600 && c <= 0x06ff)
break;
if (src != dest) {
text[dest] = text[src];
detailChunks[dest] = detailChunks[src];
orderLevels[dest] = orderLevels[src];
}
++src;
++dest;
}
if (src >= totalTextLength) {
totalTextLength = dest;
return;
}
int startArabicIdx = src;
++src;
while (src < totalTextLength) {
char c = text[src];
if (c < 0x0600 || c > 0x06ff)
break;
++src;
}
int arabicWordSize = src - startArabicIdx;
int size = ArabicLigaturizer.Arabic_shape(text, startArabicIdx, arabicWordSize, text, dest, arabicWordSize, arabicOptions);
if (startArabicIdx != dest) {
for (int k = 0; k < size; ++k) {
detailChunks[dest] = detailChunks[startArabicIdx];
orderLevels[dest++] = orderLevels[startArabicIdx++];
}
}
else
dest += size;
}
}
public PdfLine ProcessLine(float leftX, float width, int alignment, int runDirection, int arabicOptions) {
this.arabicOptions = arabicOptions;
Save();
bool isRTL = (runDirection == PdfWriter.RUN_DIRECTION_RTL);
if (currentChar >= totalTextLength) {
bool hasText = GetParagraph(runDirection);
if (!hasText)
return null;
if (totalTextLength == 0) {
ArrayList ar = new ArrayList();
PdfChunk ckx = new PdfChunk("", detailChunks[0]);
ar.Add(ckx);
return new PdfLine(0, 0, 0, alignment, true, ar, isRTL);
}
}
float originalWidth = width;
int lastSplit = -1;
if (currentChar != 0)
currentChar = TrimLeftEx(currentChar, totalTextLength - 1);
int oldCurrentChar = currentChar;
int uniC = 0;
PdfChunk ck = null;
float charWidth = 0;
PdfChunk lastValidChunk = null;
bool splitChar = false;
bool surrogate = false;
for (; currentChar < totalTextLength; ++currentChar) {
ck = detailChunks[currentChar];
surrogate = Utilities.IsSurrogatePair(text, currentChar);
if (surrogate)
uniC = ck.GetUnicodeEquivalent(Utilities.ConvertToUtf32(text, currentChar));
else
uniC = ck.GetUnicodeEquivalent(text[currentChar]);
if (PdfChunk.NoPrint(uniC))
continue;
if (surrogate)
charWidth = ck.GetCharWidth(uniC);
else
charWidth = ck.GetCharWidth(text[currentChar]);
splitChar = ck.IsExtSplitCharacter(oldCurrentChar, currentChar, totalTextLength, text, detailChunks);
if (splitChar && Char.IsWhiteSpace((char)uniC))
lastSplit = currentChar;
if (width - charWidth < 0)
break;
if (splitChar)
lastSplit = currentChar;
width -= charWidth;
lastValidChunk = ck;
if (surrogate)
++currentChar;
if (ck.IsTab()) {
Object[] tab = (Object[])ck.GetAttribute(Chunk.TAB);
float tabPosition = (float)tab[1];
bool newLine = (bool)tab[2];
if (newLine && tabPosition < originalWidth - width) {
return new PdfLine(0, originalWidth, width, alignment, true, CreateArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
}
detailChunks[currentChar].AdjustLeft(leftX);
width = originalWidth - tabPosition;
}
}
if (lastValidChunk == null) {
// not even a single char fit; must output the first char
++currentChar;
if (surrogate)
++currentChar;
return new PdfLine(0, originalWidth, 0, alignment, false, CreateArrayOfPdfChunks(currentChar - 1, currentChar - 1), isRTL);
}
if (currentChar >= totalTextLength) {
// there was more line than text
return new PdfLine(0, originalWidth, width, alignment, true, CreateArrayOfPdfChunks(oldCurrentChar, totalTextLength - 1), isRTL);
}
int newCurrentChar = TrimRightEx(oldCurrentChar, currentChar - 1);
if (newCurrentChar < oldCurrentChar) {
// only WS
return new PdfLine(0, originalWidth, width, alignment, false, CreateArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
}
if (newCurrentChar == currentChar - 1) { // middle of word
IHyphenationEvent he = (IHyphenationEvent)lastValidChunk.GetAttribute(Chunk.HYPHENATION);
if (he != null) {
int[] word = GetWord(oldCurrentChar, newCurrentChar);
if (word != null) {
float testWidth = width + GetWidth(word[0], currentChar - 1);
String pre = he.GetHyphenatedWordPre(new String(text, word[0], word[1] - word[0]), lastValidChunk.Font.Font, lastValidChunk.Font.Size, testWidth);
String post = he.HyphenatedWordPost;
if (pre.Length > 0) {
PdfChunk extra = new PdfChunk(pre, lastValidChunk);
currentChar = word[1] - post.Length;
return new PdfLine(0, originalWidth, testWidth - lastValidChunk.Font.Width(pre), alignment, false, CreateArrayOfPdfChunks(oldCurrentChar, word[0] - 1, extra), isRTL);
}
}
}
}
if (lastSplit == -1 || lastSplit >= newCurrentChar) {
// no split point or split point ahead of end
return new PdfLine(0, originalWidth, width + GetWidth(newCurrentChar + 1, currentChar - 1), alignment, false, CreateArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
}
// standard split
currentChar = lastSplit + 1;
newCurrentChar = TrimRightEx(oldCurrentChar, lastSplit);
if (newCurrentChar < oldCurrentChar) {
// only WS again
newCurrentChar = currentChar - 1;
}
return new PdfLine(0, originalWidth, originalWidth - GetWidth(oldCurrentChar, newCurrentChar), alignment, false, CreateArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
}
/** Gets the width of a range of characters.
* @param startIdx the first index to calculate
* @param lastIdx the last inclusive index to calculate
* @return the sum of all widths
*/
public float GetWidth(int startIdx, int lastIdx) {
char c = (char)0;
PdfChunk ck = null;
float width = 0;
for (; startIdx <= lastIdx; ++startIdx) {
bool surrogate = Utilities.IsSurrogatePair(text, startIdx);
if (surrogate) {
width += detailChunks[startIdx].GetCharWidth(Utilities.ConvertToUtf32(text, startIdx));
++startIdx;
}
else {
c = text[startIdx];
ck = detailChunks[startIdx];
if (PdfChunk.NoPrint(ck.GetUnicodeEquivalent(c)))
continue;
width += detailChunks[startIdx].GetCharWidth(c);
}
}
return width;
}
public ArrayList CreateArrayOfPdfChunks(int startIdx, int endIdx) {
return CreateArrayOfPdfChunks(startIdx, endIdx, null);
}
public ArrayList CreateArrayOfPdfChunks(int startIdx, int endIdx, PdfChunk extraPdfChunk) {
bool bidi = (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL);
if (bidi)
Reorder(startIdx, endIdx);
ArrayList ar = new ArrayList();
PdfChunk refCk = detailChunks[startIdx];
PdfChunk ck = null;
StringBuilder buf = new StringBuilder();
char c;
int idx = 0;
for (; startIdx <= endIdx; ++startIdx) {
idx = bidi ? indexChars[startIdx] : startIdx;
c = text[idx];
ck = detailChunks[idx];
if (PdfChunk.NoPrint(ck.GetUnicodeEquivalent(c)))
continue;
if (ck.IsImage() || ck.IsSeparator() || ck.IsTab()) {
if (buf.Length > 0) {
ar.Add(new PdfChunk(buf.ToString(), refCk));
buf = new StringBuilder();
}
ar.Add(ck);
}
else if (ck == refCk) {
buf.Append(c);
}
else {
if (buf.Length > 0) {
ar.Add(new PdfChunk(buf.ToString(), refCk));
buf = new StringBuilder();
}
if (!ck.IsImage() && !ck.IsSeparator() && !ck.IsTab())
buf.Append(c);
refCk = ck;
}
}
if (buf.Length > 0) {
ar.Add(new PdfChunk(buf.ToString(), refCk));
}
if (extraPdfChunk != null)
ar.Add(extraPdfChunk);
return ar;
}
public int[] GetWord(int startIdx, int idx) {
int last = idx;
int first = idx;
// forward
for (; last < totalTextLength; ++last) {
if (!char.IsLetter(text[last]))
break;
}
if (last == idx)
return null;
// backward
for (; first >= startIdx; --first) {
if (!char.IsLetter(text[first]))
break;
}
++first;
return new int[]{first, last};
}
public int TrimRight(int startIdx, int endIdx) {
int idx = endIdx;
char c;
for (; idx >= startIdx; --idx) {
c = (char)detailChunks[idx].GetUnicodeEquivalent(text[idx]);
if (!IsWS(c))
break;
}
return idx;
}
public int TrimLeft(int startIdx, int endIdx) {
int idx = startIdx;
char c;
for (; idx <= endIdx; ++idx) {
c = (char)detailChunks[idx].GetUnicodeEquivalent(text[idx]);
if (!IsWS(c))
break;
}
return idx;
}
public int TrimRightEx(int startIdx, int endIdx) {
int idx = endIdx;
char c = (char)0;
for (; idx >= startIdx; --idx) {
c = (char)detailChunks[idx].GetUnicodeEquivalent(text[idx]);
if (!IsWS(c) && !PdfChunk.NoPrint(c))
break;
}
return idx;
}
public int TrimLeftEx(int startIdx, int endIdx) {
int idx = startIdx;
char c = (char)0;
for (; idx <= endIdx; ++idx) {
c = (char)detailChunks[idx].GetUnicodeEquivalent(text[idx]);
if (!IsWS(c) && !PdfChunk.NoPrint(c))
break;
}
return idx;
}
public void Reorder(int start, int end) {
byte maxLevel = orderLevels[start];
byte minLevel = maxLevel;
byte onlyOddLevels = maxLevel;
byte onlyEvenLevels = maxLevel;
for (int k = start + 1; k <= end; ++k) {
byte b = orderLevels[k];
if (b > maxLevel)
maxLevel = b;
else if (b < minLevel)
minLevel = b;
onlyOddLevels &= b;
onlyEvenLevels |= b;
}
if ((onlyEvenLevels & 1) == 0) // nothing to do
return;
if ((onlyOddLevels & 1) == 1) { // single inversion
Flip(start, end + 1);
return;
}
minLevel |= 1;
for (; maxLevel >= minLevel; --maxLevel) {
int pstart = start;
for (;;) {
for (;pstart <= end; ++pstart) {
if (orderLevels[pstart] >= maxLevel)
break;
}
if (pstart > end)
break;
int pend = pstart + 1;
for (; pend <= end; ++pend) {
if (orderLevels[pend] < maxLevel)
break;
}
Flip(pstart, pend);
pstart = pend + 1;
}
}
}
public void Flip(int start, int end) {
int mid = (start + end) / 2;
--end;
for (; start < mid; ++start, --end) {
int temp = indexChars[start];
indexChars[start] = indexChars[end];
indexChars[end] = temp;
}
}
public static bool IsWS(char c) {
return (c <= ' ');
}
static BidiLine() {
mirrorChars[0x0028] = 0x0029; // LEFT PARENTHESIS
mirrorChars[0x0029] = 0x0028; // RIGHT PARENTHESIS
mirrorChars[0x003C] = 0x003E; // LESS-THAN SIGN
mirrorChars[0x003E] = 0x003C; // GREATER-THAN SIGN
mirrorChars[0x005B] = 0x005D; // LEFT SQUARE BRACKET
mirrorChars[0x005D] = 0x005B; // RIGHT SQUARE BRACKET
mirrorChars[0x007B] = 0x007D; // LEFT CURLY BRACKET
mirrorChars[0x007D] = 0x007B; // RIGHT CURLY BRACKET
mirrorChars[0x00AB] = 0x00BB; // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
mirrorChars[0x00BB] = 0x00AB; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
mirrorChars[0x2039] = 0x203A; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
mirrorChars[0x203A] = 0x2039; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
mirrorChars[0x2045] = 0x2046; // LEFT SQUARE BRACKET WITH QUILL
mirrorChars[0x2046] = 0x2045; // RIGHT SQUARE BRACKET WITH QUILL
mirrorChars[0x207D] = 0x207E; // SUPERSCRIPT LEFT PARENTHESIS
mirrorChars[0x207E] = 0x207D; // SUPERSCRIPT RIGHT PARENTHESIS
mirrorChars[0x208D] = 0x208E; // SUBSCRIPT LEFT PARENTHESIS
mirrorChars[0x208E] = 0x208D; // SUBSCRIPT RIGHT PARENTHESIS
mirrorChars[0x2208] = 0x220B; // ELEMENT OF
mirrorChars[0x2209] = 0x220C; // NOT AN ELEMENT OF
mirrorChars[0x220A] = 0x220D; // SMALL ELEMENT OF
mirrorChars[0x220B] = 0x2208; // CONTAINS AS MEMBER
mirrorChars[0x220C] = 0x2209; // DOES NOT CONTAIN AS MEMBER
mirrorChars[0x220D] = 0x220A; // SMALL CONTAINS AS MEMBER
mirrorChars[0x2215] = 0x29F5; // DIVISION SLASH
mirrorChars[0x223C] = 0x223D; // TILDE OPERATOR
mirrorChars[0x223D] = 0x223C; // REVERSED TILDE
mirrorChars[0x2243] = 0x22CD; // ASYMPTOTICALLY EQUAL TO
mirrorChars[0x2252] = 0x2253; // APPROXIMATELY EQUAL TO OR THE IMAGE OF
mirrorChars[0x2253] = 0x2252; // IMAGE OF OR APPROXIMATELY EQUAL TO
mirrorChars[0x2254] = 0x2255; // COLON EQUALS
mirrorChars[0x2255] = 0x2254; // EQUALS COLON
mirrorChars[0x2264] = 0x2265; // LESS-THAN OR EQUAL TO
mirrorChars[0x2265] = 0x2264; // GREATER-THAN OR EQUAL TO
mirrorChars[0x2266] = 0x2267; // LESS-THAN OVER EQUAL TO
mirrorChars[0x2267] = 0x2266; // GREATER-THAN OVER EQUAL TO
mirrorChars[0x2268] = 0x2269; // [BEST FIT] LESS-THAN BUT NOT EQUAL TO
mirrorChars[0x2269] = 0x2268; // [BEST FIT] GREATER-THAN BUT NOT EQUAL TO
mirrorChars[0x226A] = 0x226B; // MUCH LESS-THAN
mirrorChars[0x226B] = 0x226A; // MUCH GREATER-THAN
mirrorChars[0x226E] = 0x226F; // [BEST FIT] NOT LESS-THAN
mirrorChars[0x226F] = 0x226E; // [BEST FIT] NOT GREATER-THAN
mirrorChars[0x2270] = 0x2271; // [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO
mirrorChars[0x2271] = 0x2270; // [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO
mirrorChars[0x2272] = 0x2273; // [BEST FIT] LESS-THAN OR EQUIVALENT TO
mirrorChars[0x2273] = 0x2272; // [BEST FIT] GREATER-THAN OR EQUIVALENT TO
mirrorChars[0x2274] = 0x2275; // [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO
mirrorChars[0x2275] = 0x2274; // [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO
mirrorChars[0x2276] = 0x2277; // LESS-THAN OR GREATER-THAN
mirrorChars[0x2277] = 0x2276; // GREATER-THAN OR LESS-THAN
mirrorChars[0x2278] = 0x2279; // NEITHER LESS-THAN NOR GREATER-THAN
mirrorChars[0x2279] = 0x2278; // NEITHER GREATER-THAN NOR LESS-THAN
mirrorChars[0x227A] = 0x227B; // PRECEDES
mirrorChars[0x227B] = 0x227A; // SUCCEEDS
mirrorChars[0x227C] = 0x227D; // PRECEDES OR EQUAL TO
mirrorChars[0x227D] = 0x227C; // SUCCEEDS OR EQUAL TO
mirrorChars[0x227E] = 0x227F; // [BEST FIT] PRECEDES OR EQUIVALENT TO
mirrorChars[0x227F] = 0x227E; // [BEST FIT] SUCCEEDS OR EQUIVALENT TO
mirrorChars[0x2280] = 0x2281; // [BEST FIT] DOES NOT PRECEDE
mirrorChars[0x2281] = 0x2280; // [BEST FIT] DOES NOT SUCCEED
mirrorChars[0x2282] = 0x2283; // SUBSET OF
mirrorChars[0x2283] = 0x2282; // SUPERSET OF
mirrorChars[0x2284] = 0x2285; // [BEST FIT] NOT A SUBSET OF
mirrorChars[0x2285] = 0x2284; // [BEST FIT] NOT A SUPERSET OF
mirrorChars[0x2286] = 0x2287; // SUBSET OF OR EQUAL TO
mirrorChars[0x2287] = 0x2286; // SUPERSET OF OR EQUAL TO
mirrorChars[0x2288] = 0x2289; // [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO
mirrorChars[0x2289] = 0x2288; // [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO
mirrorChars[0x228A] = 0x228B; // [BEST FIT] SUBSET OF WITH NOT EQUAL TO
mirrorChars[0x228B] = 0x228A; // [BEST FIT] SUPERSET OF WITH NOT EQUAL TO
mirrorChars[0x228F] = 0x2290; // SQUARE IMAGE OF
mirrorChars[0x2290] = 0x228F; // SQUARE ORIGINAL OF
mirrorChars[0x2291] = 0x2292; // SQUARE IMAGE OF OR EQUAL TO
mirrorChars[0x2292] = 0x2291; // SQUARE ORIGINAL OF OR EQUAL TO
mirrorChars[0x2298] = 0x29B8; // CIRCLED DIVISION SLASH
mirrorChars[0x22A2] = 0x22A3; // RIGHT TACK
mirrorChars[0x22A3] = 0x22A2; // LEFT TACK
mirrorChars[0x22A6] = 0x2ADE; // ASSERTION
mirrorChars[0x22A8] = 0x2AE4; // TRUE
mirrorChars[0x22A9] = 0x2AE3; // FORCES
mirrorChars[0x22AB] = 0x2AE5; // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
mirrorChars[0x22B0] = 0x22B1; // PRECEDES UNDER RELATION
mirrorChars[0x22B1] = 0x22B0; // SUCCEEDS UNDER RELATION
mirrorChars[0x22B2] = 0x22B3; // NORMAL SUBGROUP OF
mirrorChars[0x22B3] = 0x22B2; // CONTAINS AS NORMAL SUBGROUP
mirrorChars[0x22B4] = 0x22B5; // NORMAL SUBGROUP OF OR EQUAL TO
mirrorChars[0x22B5] = 0x22B4; // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO
mirrorChars[0x22B6] = 0x22B7; // ORIGINAL OF
mirrorChars[0x22B7] = 0x22B6; // IMAGE OF
mirrorChars[0x22C9] = 0x22CA; // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT
mirrorChars[0x22CA] = 0x22C9; // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT
mirrorChars[0x22CB] = 0x22CC; // LEFT SEMIDIRECT PRODUCT
mirrorChars[0x22CC] = 0x22CB; // RIGHT SEMIDIRECT PRODUCT
mirrorChars[0x22CD] = 0x2243; // REVERSED TILDE EQUALS
mirrorChars[0x22D0] = 0x22D1; // DOUBLE SUBSET
mirrorChars[0x22D1] = 0x22D0; // DOUBLE SUPERSET
mirrorChars[0x22D6] = 0x22D7; // LESS-THAN WITH DOT
mirrorChars[0x22D7] = 0x22D6; // GREATER-THAN WITH DOT
mirrorChars[0x22D8] = 0x22D9; // VERY MUCH LESS-THAN
mirrorChars[0x22D9] = 0x22D8; // VERY MUCH GREATER-THAN
mirrorChars[0x22DA] = 0x22DB; // LESS-THAN EQUAL TO OR GREATER-THAN
mirrorChars[0x22DB] = 0x22DA; // GREATER-THAN EQUAL TO OR LESS-THAN
mirrorChars[0x22DC] = 0x22DD; // EQUAL TO OR LESS-THAN
mirrorChars[0x22DD] = 0x22DC; // EQUAL TO OR GREATER-THAN
mirrorChars[0x22DE] = 0x22DF; // EQUAL TO OR PRECEDES
mirrorChars[0x22DF] = 0x22DE; // EQUAL TO OR SUCCEEDS
mirrorChars[0x22E0] = 0x22E1; // [BEST FIT] DOES NOT PRECEDE OR EQUAL
mirrorChars[0x22E1] = 0x22E0; // [BEST FIT] DOES NOT SUCCEED OR EQUAL
mirrorChars[0x22E2] = 0x22E3; // [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO
mirrorChars[0x22E3] = 0x22E2; // [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO
mirrorChars[0x22E4] = 0x22E5; // [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO
mirrorChars[0x22E5] = 0x22E4; // [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO
mirrorChars[0x22E6] = 0x22E7; // [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO
mirrorChars[0x22E7] = 0x22E6; // [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO
mirrorChars[0x22E8] = 0x22E9; // [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO
mirrorChars[0x22E9] = 0x22E8; // [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO
mirrorChars[0x22EA] = 0x22EB; // [BEST FIT] NOT NORMAL SUBGROUP OF
mirrorChars[0x22EB] = 0x22EA; // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP
mirrorChars[0x22EC] = 0x22ED; // [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO
mirrorChars[0x22ED] = 0x22EC; // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
mirrorChars[0x22F0] = 0x22F1; // UP RIGHT DIAGONAL ELLIPSIS
mirrorChars[0x22F1] = 0x22F0; // DOWN RIGHT DIAGONAL ELLIPSIS
mirrorChars[0x22F2] = 0x22FA; // ELEMENT OF WITH LONG HORIZONTAL STROKE
mirrorChars[0x22F3] = 0x22FB; // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
mirrorChars[0x22F4] = 0x22FC; // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
mirrorChars[0x22F6] = 0x22FD; // ELEMENT OF WITH OVERBAR
mirrorChars[0x22F7] = 0x22FE; // SMALL ELEMENT OF WITH OVERBAR
mirrorChars[0x22FA] = 0x22F2; // CONTAINS WITH LONG HORIZONTAL STROKE
mirrorChars[0x22FB] = 0x22F3; // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
mirrorChars[0x22FC] = 0x22F4; // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
mirrorChars[0x22FD] = 0x22F6; // CONTAINS WITH OVERBAR
mirrorChars[0x22FE] = 0x22F7; // SMALL CONTAINS WITH OVERBAR
mirrorChars[0x2308] = 0x2309; // LEFT CEILING
mirrorChars[0x2309] = 0x2308; // RIGHT CEILING
mirrorChars[0x230A] = 0x230B; // LEFT FLOOR
mirrorChars[0x230B] = 0x230A; // RIGHT FLOOR
mirrorChars[0x2329] = 0x232A; // LEFT-POINTING ANGLE BRACKET
mirrorChars[0x232A] = 0x2329; // RIGHT-POINTING ANGLE BRACKET
mirrorChars[0x2768] = 0x2769; // MEDIUM LEFT PARENTHESIS ORNAMENT
mirrorChars[0x2769] = 0x2768; // MEDIUM RIGHT PARENTHESIS ORNAMENT
mirrorChars[0x276A] = 0x276B; // MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
mirrorChars[0x276B] = 0x276A; // MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
mirrorChars[0x276C] = 0x276D; // MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
mirrorChars[0x276D] = 0x276C; // MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
mirrorChars[0x276E] = 0x276F; // HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
mirrorChars[0x276F] = 0x276E; // HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
mirrorChars[0x2770] = 0x2771; // HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
mirrorChars[0x2771] = 0x2770; // HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
mirrorChars[0x2772] = 0x2773; // LIGHT LEFT TORTOISE SHELL BRACKET
mirrorChars[0x2773] = 0x2772; // LIGHT RIGHT TORTOISE SHELL BRACKET
mirrorChars[0x2774] = 0x2775; // MEDIUM LEFT CURLY BRACKET ORNAMENT
mirrorChars[0x2775] = 0x2774; // MEDIUM RIGHT CURLY BRACKET ORNAMENT
mirrorChars[0x27D5] = 0x27D6; // LEFT OUTER JOIN
mirrorChars[0x27D6] = 0x27D5; // RIGHT OUTER JOIN
mirrorChars[0x27DD] = 0x27DE; // LONG RIGHT TACK
mirrorChars[0x27DE] = 0x27DD; // LONG LEFT TACK
mirrorChars[0x27E2] = 0x27E3; // WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK
mirrorChars[0x27E3] = 0x27E2; // WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK
mirrorChars[0x27E4] = 0x27E5; // WHITE SQUARE WITH LEFTWARDS TICK
mirrorChars[0x27E5] = 0x27E4; // WHITE SQUARE WITH RIGHTWARDS TICK
mirrorChars[0x27E6] = 0x27E7; // MATHEMATICAL LEFT WHITE SQUARE BRACKET
mirrorChars[0x27E7] = 0x27E6; // MATHEMATICAL RIGHT WHITE SQUARE BRACKET
mirrorChars[0x27E8] = 0x27E9; // MATHEMATICAL LEFT ANGLE BRACKET
mirrorChars[0x27E9] = 0x27E8; // MATHEMATICAL RIGHT ANGLE BRACKET
mirrorChars[0x27EA] = 0x27EB; // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
mirrorChars[0x27EB] = 0x27EA; // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
mirrorChars[0x2983] = 0x2984; // LEFT WHITE CURLY BRACKET
mirrorChars[0x2984] = 0x2983; // RIGHT WHITE CURLY BRACKET
mirrorChars[0x2985] = 0x2986; // LEFT WHITE PARENTHESIS
mirrorChars[0x2986] = 0x2985; // RIGHT WHITE PARENTHESIS
mirrorChars[0x2987] = 0x2988; // Z NOTATION LEFT IMAGE BRACKET
mirrorChars[0x2988] = 0x2987; // Z NOTATION RIGHT IMAGE BRACKET
mirrorChars[0x2989] = 0x298A; // Z NOTATION LEFT BINDING BRACKET
mirrorChars[0x298A] = 0x2989; // Z NOTATION RIGHT BINDING BRACKET
mirrorChars[0x298B] = 0x298C; // LEFT SQUARE BRACKET WITH UNDERBAR
mirrorChars[0x298C] = 0x298B; // RIGHT SQUARE BRACKET WITH UNDERBAR
mirrorChars[0x298D] = 0x2990; // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
mirrorChars[0x298E] = 0x298F; // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
mirrorChars[0x298F] = 0x298E; // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
mirrorChars[0x2990] = 0x298D; // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
mirrorChars[0x2991] = 0x2992; // LEFT ANGLE BRACKET WITH DOT
mirrorChars[0x2992] = 0x2991; // RIGHT ANGLE BRACKET WITH DOT
mirrorChars[0x2993] = 0x2994; // LEFT ARC LESS-THAN BRACKET
mirrorChars[0x2994] = 0x2993; // RIGHT ARC GREATER-THAN BRACKET
mirrorChars[0x2995] = 0x2996; // DOUBLE LEFT ARC GREATER-THAN BRACKET
mirrorChars[0x2996] = 0x2995; // DOUBLE RIGHT ARC LESS-THAN BRACKET
mirrorChars[0x2997] = 0x2998; // LEFT BLACK TORTOISE SHELL BRACKET
mirrorChars[0x2998] = 0x2997; // RIGHT BLACK TORTOISE SHELL BRACKET
mirrorChars[0x29B8] = 0x2298; // CIRCLED REVERSE SOLIDUS
mirrorChars[0x29C0] = 0x29C1; // CIRCLED LESS-THAN
mirrorChars[0x29C1] = 0x29C0; // CIRCLED GREATER-THAN
mirrorChars[0x29C4] = 0x29C5; // SQUARED RISING DIAGONAL SLASH
mirrorChars[0x29C5] = 0x29C4; // SQUARED FALLING DIAGONAL SLASH
mirrorChars[0x29CF] = 0x29D0; // LEFT TRIANGLE BESIDE VERTICAL BAR
mirrorChars[0x29D0] = 0x29CF; // VERTICAL BAR BESIDE RIGHT TRIANGLE
mirrorChars[0x29D1] = 0x29D2; // BOWTIE WITH LEFT HALF BLACK
mirrorChars[0x29D2] = 0x29D1; // BOWTIE WITH RIGHT HALF BLACK
mirrorChars[0x29D4] = 0x29D5; // TIMES WITH LEFT HALF BLACK
mirrorChars[0x29D5] = 0x29D4; // TIMES WITH RIGHT HALF BLACK
mirrorChars[0x29D8] = 0x29D9; // LEFT WIGGLY FENCE
mirrorChars[0x29D9] = 0x29D8; // RIGHT WIGGLY FENCE
mirrorChars[0x29DA] = 0x29DB; // LEFT DOUBLE WIGGLY FENCE
mirrorChars[0x29DB] = 0x29DA; // RIGHT DOUBLE WIGGLY FENCE
mirrorChars[0x29F5] = 0x2215; // REVERSE SOLIDUS OPERATOR
mirrorChars[0x29F8] = 0x29F9; // BIG SOLIDUS
mirrorChars[0x29F9] = 0x29F8; // BIG REVERSE SOLIDUS
mirrorChars[0x29FC] = 0x29FD; // LEFT-POINTING CURVED ANGLE BRACKET
mirrorChars[0x29FD] = 0x29FC; // RIGHT-POINTING CURVED ANGLE BRACKET
mirrorChars[0x2A2B] = 0x2A2C; // MINUS SIGN WITH FALLING DOTS
mirrorChars[0x2A2C] = 0x2A2B; // MINUS SIGN WITH RISING DOTS
mirrorChars[0x2A2D] = 0x2A2C; // PLUS SIGN IN LEFT HALF CIRCLE
mirrorChars[0x2A2E] = 0x2A2D; // PLUS SIGN IN RIGHT HALF CIRCLE
mirrorChars[0x2A34] = 0x2A35; // MULTIPLICATION SIGN IN LEFT HALF CIRCLE
mirrorChars[0x2A35] = 0x2A34; // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE
mirrorChars[0x2A3C] = 0x2A3D; // INTERIOR PRODUCT
mirrorChars[0x2A3D] = 0x2A3C; // RIGHTHAND INTERIOR PRODUCT
mirrorChars[0x2A64] = 0x2A65; // Z NOTATION DOMAIN ANTIRESTRICTION
mirrorChars[0x2A65] = 0x2A64; // Z NOTATION RANGE ANTIRESTRICTION
mirrorChars[0x2A79] = 0x2A7A; // LESS-THAN WITH CIRCLE INSIDE
mirrorChars[0x2A7A] = 0x2A79; // GREATER-THAN WITH CIRCLE INSIDE
mirrorChars[0x2A7D] = 0x2A7E; // LESS-THAN OR SLANTED EQUAL TO
mirrorChars[0x2A7E] = 0x2A7D; // GREATER-THAN OR SLANTED EQUAL TO
mirrorChars[0x2A7F] = 0x2A80; // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
mirrorChars[0x2A80] = 0x2A7F; // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
mirrorChars[0x2A81] = 0x2A82; // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
mirrorChars[0x2A82] = 0x2A81; // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
mirrorChars[0x2A83] = 0x2A84; // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT
mirrorChars[0x2A84] = 0x2A83; // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT
mirrorChars[0x2A8B] = 0x2A8C; // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN
mirrorChars[0x2A8C] = 0x2A8B; // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN
mirrorChars[0x2A91] = 0x2A92; // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL
mirrorChars[0x2A92] = 0x2A91; // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL
mirrorChars[0x2A93] = 0x2A94; // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL
mirrorChars[0x2A94] = 0x2A93; // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL
mirrorChars[0x2A95] = 0x2A96; // SLANTED EQUAL TO OR LESS-THAN
mirrorChars[0x2A96] = 0x2A95; // SLANTED EQUAL TO OR GREATER-THAN
mirrorChars[0x2A97] = 0x2A98; // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE
mirrorChars[0x2A98] = 0x2A97; // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE
mirrorChars[0x2A99] = 0x2A9A; // DOUBLE-LINE EQUAL TO OR LESS-THAN
mirrorChars[0x2A9A] = 0x2A99; // DOUBLE-LINE EQUAL TO OR GREATER-THAN
mirrorChars[0x2A9B] = 0x2A9C; // DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN
mirrorChars[0x2A9C] = 0x2A9B; // DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN
mirrorChars[0x2AA1] = 0x2AA2; // DOUBLE NESTED LESS-THAN
mirrorChars[0x2AA2] = 0x2AA1; // DOUBLE NESTED GREATER-THAN
mirrorChars[0x2AA6] = 0x2AA7; // LESS-THAN CLOSED BY CURVE
mirrorChars[0x2AA7] = 0x2AA6; // GREATER-THAN CLOSED BY CURVE
mirrorChars[0x2AA8] = 0x2AA9; // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
mirrorChars[0x2AA9] = 0x2AA8; // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
mirrorChars[0x2AAA] = 0x2AAB; // SMALLER THAN
mirrorChars[0x2AAB] = 0x2AAA; // LARGER THAN
mirrorChars[0x2AAC] = 0x2AAD; // SMALLER THAN OR EQUAL TO
mirrorChars[0x2AAD] = 0x2AAC; // LARGER THAN OR EQUAL TO
mirrorChars[0x2AAF] = 0x2AB0; // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN
mirrorChars[0x2AB0] = 0x2AAF; // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN
mirrorChars[0x2AB3] = 0x2AB4; // PRECEDES ABOVE EQUALS SIGN
mirrorChars[0x2AB4] = 0x2AB3; // SUCCEEDS ABOVE EQUALS SIGN
mirrorChars[0x2ABB] = 0x2ABC; // DOUBLE PRECEDES
mirrorChars[0x2ABC] = 0x2ABB; // DOUBLE SUCCEEDS
mirrorChars[0x2ABD] = 0x2ABE; // SUBSET WITH DOT
mirrorChars[0x2ABE] = 0x2ABD; // SUPERSET WITH DOT
mirrorChars[0x2ABF] = 0x2AC0; // SUBSET WITH PLUS SIGN BELOW
mirrorChars[0x2AC0] = 0x2ABF; // SUPERSET WITH PLUS SIGN BELOW
mirrorChars[0x2AC1] = 0x2AC2; // SUBSET WITH MULTIPLICATION SIGN BELOW
mirrorChars[0x2AC2] = 0x2AC1; // SUPERSET WITH MULTIPLICATION SIGN BELOW
mirrorChars[0x2AC3] = 0x2AC4; // SUBSET OF OR EQUAL TO WITH DOT ABOVE
mirrorChars[0x2AC4] = 0x2AC3; // SUPERSET OF OR EQUAL TO WITH DOT ABOVE
mirrorChars[0x2AC5] = 0x2AC6; // SUBSET OF ABOVE EQUALS SIGN
mirrorChars[0x2AC6] = 0x2AC5; // SUPERSET OF ABOVE EQUALS SIGN
mirrorChars[0x2ACD] = 0x2ACE; // SQUARE LEFT OPEN BOX OPERATOR
mirrorChars[0x2ACE] = 0x2ACD; // SQUARE RIGHT OPEN BOX OPERATOR
mirrorChars[0x2ACF] = 0x2AD0; // CLOSED SUBSET
mirrorChars[0x2AD0] = 0x2ACF; // CLOSED SUPERSET
mirrorChars[0x2AD1] = 0x2AD2; // CLOSED SUBSET OR EQUAL TO
mirrorChars[0x2AD2] = 0x2AD1; // CLOSED SUPERSET OR EQUAL TO
mirrorChars[0x2AD3] = 0x2AD4; // SUBSET ABOVE SUPERSET
mirrorChars[0x2AD4] = 0x2AD3; // SUPERSET ABOVE SUBSET
mirrorChars[0x2AD5] = 0x2AD6; // SUBSET ABOVE SUBSET
mirrorChars[0x2AD6] = 0x2AD5; // SUPERSET ABOVE SUPERSET
mirrorChars[0x2ADE] = 0x22A6; // SHORT LEFT TACK
mirrorChars[0x2AE3] = 0x22A9; // DOUBLE VERTICAL BAR LEFT TURNSTILE
mirrorChars[0x2AE4] = 0x22A8; // VERTICAL BAR DOUBLE LEFT TURNSTILE
mirrorChars[0x2AE5] = 0x22AB; // DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE
mirrorChars[0x2AEC] = 0x2AED; // DOUBLE STROKE NOT SIGN
mirrorChars[0x2AED] = 0x2AEC; // REVERSED DOUBLE STROKE NOT SIGN
mirrorChars[0x2AF7] = 0x2AF8; // TRIPLE NESTED LESS-THAN
mirrorChars[0x2AF8] = 0x2AF7; // TRIPLE NESTED GREATER-THAN
mirrorChars[0x2AF9] = 0x2AFA; // DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO
mirrorChars[0x2AFA] = 0x2AF9; // DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO
mirrorChars[0x3008] = 0x3009; // LEFT ANGLE BRACKET
mirrorChars[0x3009] = 0x3008; // RIGHT ANGLE BRACKET
mirrorChars[0x300A] = 0x300B; // LEFT DOUBLE ANGLE BRACKET
mirrorChars[0x300B] = 0x300A; // RIGHT DOUBLE ANGLE BRACKET
mirrorChars[0x300C] = 0x300D; // [BEST FIT] LEFT CORNER BRACKET
mirrorChars[0x300D] = 0x300C; // [BEST FIT] RIGHT CORNER BRACKET
mirrorChars[0x300E] = 0x300F; // [BEST FIT] LEFT WHITE CORNER BRACKET
mirrorChars[0x300F] = 0x300E; // [BEST FIT] RIGHT WHITE CORNER BRACKET
mirrorChars[0x3010] = 0x3011; // LEFT BLACK LENTICULAR BRACKET
mirrorChars[0x3011] = 0x3010; // RIGHT BLACK LENTICULAR BRACKET
mirrorChars[0x3014] = 0x3015; // LEFT TORTOISE SHELL BRACKET
mirrorChars[0x3015] = 0x3014; // RIGHT TORTOISE SHELL BRACKET
mirrorChars[0x3016] = 0x3017; // LEFT WHITE LENTICULAR BRACKET
mirrorChars[0x3017] = 0x3016; // RIGHT WHITE LENTICULAR BRACKET
mirrorChars[0x3018] = 0x3019; // LEFT WHITE TORTOISE SHELL BRACKET
mirrorChars[0x3019] = 0x3018; // RIGHT WHITE TORTOISE SHELL BRACKET
mirrorChars[0x301A] = 0x301B; // LEFT WHITE SQUARE BRACKET
mirrorChars[0x301B] = 0x301A; // RIGHT WHITE SQUARE BRACKET
mirrorChars[0xFF08] = 0xFF09; // FULLWIDTH LEFT PARENTHESIS
mirrorChars[0xFF09] = 0xFF08; // FULLWIDTH RIGHT PARENTHESIS
mirrorChars[0xFF1C] = 0xFF1E; // FULLWIDTH LESS-THAN SIGN
mirrorChars[0xFF1E] = 0xFF1C; // FULLWIDTH GREATER-THAN SIGN
mirrorChars[0xFF3B] = 0xFF3D; // FULLWIDTH LEFT SQUARE BRACKET
mirrorChars[0xFF3D] = 0xFF3B; // FULLWIDTH RIGHT SQUARE BRACKET
mirrorChars[0xFF5B] = 0xFF5D; // FULLWIDTH LEFT CURLY BRACKET
mirrorChars[0xFF5D] = 0xFF5B; // FULLWIDTH RIGHT CURLY BRACKET
mirrorChars[0xFF5F] = 0xFF60; // FULLWIDTH LEFT WHITE PARENTHESIS
mirrorChars[0xFF60] = 0xFF5F; // FULLWIDTH RIGHT WHITE PARENTHESIS
mirrorChars[0xFF62] = 0xFF63; // [BEST FIT] HALFWIDTH LEFT CORNER BRACKET
mirrorChars[0xFF63] = 0xFF62; // [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET
}
}
}