/// <summary>
        /// 計算折行之後的縮排格數。
        /// </summary>
        /// <param name="brLine"></param>
        /// <returns>縮排格數。</returns>
        private static int CalcNewLineIndents(BrailleLine brLine)
        {
            if (AppGlobals.Config.Braille.AutoIndentNumberedLine)
            {
                int  count            = 0;
                bool foundOrderedItem = false;

                // 如果是以數字編號開頭(空白略過),自動計算折行的列要縮排幾格。
                foreach (BrailleWord brWord in brLine.Words)
                {
                    if (BrailleWord.IsBlank(brWord))
                    {
                        count++;
                        continue;
                    }

                    if (BrailleWord.IsOrderedListItem(brWord))
                    {
                        count++;
                        foundOrderedItem = true;
                        break;
                    }
                }

                if (foundOrderedItem)
                {
                    return(count);
                }
            }

            return(0);
        }
 private void Indent(BrailleLine brLine, int indents)
 {
     for (int i = 0; i < indents; i++)
     {
         brLine.Insert(0, BrailleWord.NewBlank());
     }
 }
        /// <summary>
        /// 移除代表數字的點位。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="beginIdx"></param>
        /// <param name="endIdx"></param>
        public static void RemoveDigitCell(BrailleLine brLine, int beginIdx, int endIdx)
        {
            if (beginIdx < 0 || endIdx < 0 || beginIdx > endIdx)
            {
                return;
            }

            int         wordIdx = beginIdx;
            BrailleWord brWord;
            string      text;

            while (wordIdx < brLine.WordCount)
            {
                brWord = brLine[wordIdx];
                text   = brWord.Text;

                if (text.Length > 0 && Char.IsDigit(text[0]) &&
                    brWord.Cells[0].Value == (byte)BrailleCellCode.Digit)
                {
                    brWord.Cells.RemoveAt(0);   // 移除小數點位.
                }

                wordIdx++;
            }
        }
        /// <summary>
        /// 處理包著數字的中括號,例如:【12】。
        /// </summary>
        /// <param name="brLine"></param>
        public static void ApplyBracketRule(BrailleLine brLine)
        {
            int         wordIdx = 0;
            BrailleWord brWord;
            string      text;
            int         beginIdx = -1;
            int         endIdx   = -1;

            while (wordIdx < brLine.WordCount)
            {
                brWord = brLine[wordIdx];
                text   = brWord.Text;

                // 判斷是否為'【'
                if ("【".Equals(text))
                {
                    beginIdx = wordIdx;
                }
                else if ("】".Equals(text))
                {
                    if (beginIdx >= 0)
                    {
                        endIdx = wordIdx;
                        RemoveDigitCell(brLine, beginIdx + 1, endIdx - 1);
                    }
                }
                wordIdx++;
            }
        }
        /// <summary>
        /// 編排指定的列。此函式會將指定的列斷行。
        /// </summary>
        /// <param name="brDoc">點字文件。</param>
        /// <param name="lineIndex">欲重新編排的列索引。</param>
        /// <returns>傳回編排後的列數。</returns>
        public int FormatLine(BrailleDocument brDoc, int lineIndex, ContextTagManager context)
        {
            BrailleLine brLine = brDoc.Lines[lineIndex];

            RemoveContextTagsButTitle(brLine);   // 清除情境標籤,除了標題標籤。

            if (brLine.WordCount == 0)
            {
                brDoc.RemoveLine(lineIndex);
                return(0);
            }

            List <BrailleLine> newLines;

            newLines = BreakLine(brLine, brDoc.CellsPerLine, context);

            if (newLines == null)   // 沒有斷行?
            {
                return(1);
            }

            // 移除原始的 line
            brLine.Clear();
            brDoc.RemoveLine(lineIndex);

            // 加入斷行後的 lines
            brDoc.Lines.InsertRange(lineIndex, newLines);

            return(newLines.Count);
        }
Esempio n. 6
0
        /// <summary>
        /// 將指定的點字列附加至此點字列。
        /// </summary>
        /// <param name="brLine"></param>
        public void Append(BrailleLine brLine)
        {
            if (brLine == null || brLine.WordCount < 1)
            {
                return;
            }

            m_Words.AddRange(brLine.Words);
        }
        private void ProcessLine(int lineNumber, string line)
        {
            BrailleLine brLine = m_Processor.ConvertLine(lineNumber, line);

            if (brLine != null)
            {
                AddLine(brLine);
            }
        }
Esempio n. 8
0
        /// <summary>
        /// 在指定位置的左邊及右邊各加一個空方。若該位置已經有空方,則不做任何處理。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns>這次調整一共增加或刪除了幾個 word。</returns>
        private static int EncloseBlankCells(BrailleLine brLine, int index)
        {
            int wordOffset = 0;

            // NOTE: 一定要先加後面的空方,再插入前面的空方,否則 index 參數必須調整。
            wordOffset += PostfixBlankCell(brLine, index);
            wordOffset += PrefixBlankCell(brLine, index);
            return(wordOffset);
        }
Esempio n. 9
0
        /// <summary>
        /// 根據起始列索引更新起始的 BrailleLine 物件。
        /// </summary>
        /// <param name="brDoc"></param>
        /// <returns></returns>
        public bool UpdateLineObject(BrailleDocument brDoc)
        {
            if (m_BeginLineIndex < 0 || m_BeginLineIndex >= brDoc.LineCount)
            {
                return(false);
            }

            m_BeginLine = brDoc.Lines[m_BeginLineIndex];
            return(true);
        }
Esempio n. 10
0
        /// <summary>
        /// 在指定的位置左邊附加一個空方,若該位置已經有空方,則不做任何處理。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns>這次調整一共增加或刪除了幾個 word。</returns>
        private static int PrefixBlankCell(BrailleLine brLine, int index)
        {
            int wordOffset = 0;

            if (index > 0 && !BrailleWord.IsBlank(brLine[index - 1]))
            {
                brLine.Words.Insert(index, BrailleWord.NewBlank());
                wordOffset = 1;
            }
            return(wordOffset);
        }
Esempio n. 11
0
        /// <summary>
        /// 深層複製。
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            BrailleLine brLine  = new BrailleLine();
            BrailleWord newWord = null;

            foreach (BrailleWord brWord in m_Words)
            {
                newWord = brWord.Copy();
                brLine.Words.Add(newWord);
            }
            return(brLine);
        }
        /// <summary>
        /// 將指定的列與下一列相結合(下一列附加至本列)。
        /// </summary>
        /// <param name="brDoc">點字文件。</param>
        /// <param name="lineIndex">本列的列索引。</param>
        public void JoinNextLine(BrailleDocument brDoc, int lineIndex)
        {
            BrailleLine brLine = brDoc.Lines[lineIndex];

            // 將下一列附加至本列,以結合成一列。
            int nextIndex = lineIndex + 1;

            if (nextIndex < brDoc.Lines.Count)
            {
                brLine.Append(brDoc.Lines[nextIndex]);
                brDoc.Lines.RemoveAt(nextIndex);
            }
        }
        /// <summary>
        /// 移除所有情境標籤,除了標題標籤。
        /// </summary>
        public void RemoveContextTagsButTitle(BrailleLine brLine)
        {
            BrailleWord brWord;

            for (int i = brLine.WordCount - 1; i >= 0; i--)
            {
                brWord = brLine.Words[i];
                if (brWord.IsContextTag && !ContextTag.IsTitleTag(brWord.Text))
                {
                    brLine.Words.RemoveAt(i);
                }
            }
        }
Esempio n. 14
0
        /// <summary>
        /// 在指定的位置右邊附加一個空方,若該位置已經有空方,則不做任何處理。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns>這次調整一共增加或刪除了幾個 word。</returns>
        private static int PostfixBlankCell(BrailleLine brLine, int index)
        {
            int wordOffset = 0;

            index++;
            if (index < brLine.WordCount) // 如果已經到結尾,就不加空方。
            {
                if (!BrailleWord.IsBlank(brLine[index]))
                {
                    brLine.Words.Insert(index, BrailleWord.NewBlank());
                    wordOffset = 1;
                }
            }
            return(wordOffset);
        }
Esempio n. 15
0
        /// <summary>
        /// 從指定的起始位置複製指定個數的點字 (BrailleWord) 到新建立的點字串列。
        /// </summary>
        /// <param name="index">起始位置</param>
        /// <param name="count">要複製幾個點字。</param>
        /// <returns>新的點字串列。</returns>
        public BrailleLine Copy(int index, int count)
        {
            BrailleLine brLine  = new BrailleLine();
            BrailleWord newWord = null;

            while (index < m_Words.Count && count > 0)
            {
                //newWord = m_Words[index].Copy();
                newWord = m_Words[index];
                brLine.Words.Add(newWord);

                index++;
                count--;
            }
            return(brLine);
        }
        /// <summary>
        /// 根據中文點字的冒號規則修正傳入的點字行。
        /// 規則:冒號之後若是 "我" 字,必須加一空方。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns>這次調整一共增加或刪除了幾個 word。</returns>
        private static int ApplyColonRule(BrailleLine brLine, int index)
        {
            int wordOffset = 0;

            index++;
            if (index < brLine.WordCount)
            {
                // 若下一個點字是我,則加一空方。
                if (brLine[index].Text == "我")
                {
                    brLine.Words.Insert(index, BrailleWord.NewBlank());
                    wordOffset = 1;     // 跳過空方
                }
            }
            return(wordOffset);
        }
Esempio n. 17
0
        public void SetTitleLine(BrailleDocument brDoc, int index)
        {
            m_TitleLine = brDoc.Lines[index];
            m_TitleLine.RemoveContextTags();         // 移除所有情境標籤(這裡主要是把標題標籤拿掉)。

            m_BeginLineIndex = index + 1;            // 從下一列開始就是使用此標題。

            if (m_BeginLineIndex >= brDoc.LineCount) // 標題列就是文件的最後一列?
            {
                //System.Diagnostics.Trace.WriteLine("BraillePageTitle.SetTitleLine: 標題列後面沒有文字內容!");
                m_BeginLineIndex = -1;
                m_BeginLine      = null;
                return;
            }

            m_BeginLine = brDoc.Lines[m_BeginLineIndex];
        }
Esempio n. 18
0
        /// <summary>
        /// 更新頁標題的起始列索引。
        /// </summary>
        /// <param name="brDoc"></param>
        /// <returns></returns>
        public bool UpdateLineIndex(BrailleDocument brDoc)
        {
            if (m_BeginLine == null)
            {
                return(false);
            }

            int idx = brDoc.Lines.IndexOf(m_BeginLine);

            if (idx < 0)
            {
                return(false);
            }
            m_BeginLine      = brDoc.Lines[idx];
            m_BeginLineIndex = idx;
            return(true);
        }
Esempio n. 19
0
        /// <summary>
        /// 補加必要的空白:在英數字母和中文字之間補上空白。
        /// </summary>
        /// <param name="brLine"></param>
        public static void AddSpaces(BrailleLine brLine)
        {
            int         wordIdx = 0;
            BrailleWord brWord;
            BrailleWord lastBrWord;
            int         wordOffset;

            while (wordIdx < brLine.WordCount)
            {
                brWord = brLine[wordIdx];
                if (brWord.Text.Length < 1)
                {
                    wordIdx++;
                    continue;
                }
                if (Char.IsWhiteSpace(brWord.Text[0]))
                {
                    wordIdx++;
                    continue;
                }
                if (wordIdx == 0)
                {
                    wordIdx++;
                    continue;
                }

                wordOffset = AddBlankForSpecialCharacters(brLine, wordIdx);
                if (wordOffset > 0)
                {
                    wordIdx += wordOffset;
                    continue;
                }

                // 取前一個字元。
                lastBrWord = brLine[wordIdx - 1];

                if (NeedSpace(lastBrWord, brWord))
                {
                    brLine.Words.Insert(wordIdx, BrailleWord.NewBlank());
                    wordIdx++;
                }
                wordIdx++;
            }
        }
        /// <summary>
        /// 處理縮排情境標籤:碰到縮排標籤時,將縮排次數更新至 ContextTagManager 物件,並移除此縮排標籤。
        ///
        /// NOTE: 縮排標籤必須位於列首,一列可有多個連續縮排標籤,例如:<縮排><縮排>。
        /// </summary>
        /// <param name="brDoc"></param>
        /// <param name="lineIndex"></param>
        /// <param name="context">情境物件。</param>
        /// <returns></returns>
        public void ProcessIndentTags(BrailleDocument brDoc, int lineIndex, ContextTagManager context)
        {
            BrailleLine brLine  = brDoc.Lines[lineIndex];
            int         wordIdx = 0;
            ContextTag  ctag;

            while (brLine.WordCount > 0)
            {
                ctag = context.Parse(brLine[0].Text, ContextTagNames.Indent);
                if (ctag != null)
                {
                    brLine.RemoveAt(wordIdx);
                }
                else
                {
                    break;
                }
            }
        }
Esempio n. 21
0
        /// <summary>
        /// 把編號的數字修正成上位點。
        /// 注意:此函式會把點字串列中的 # 點字物件刪除。
        /// </summary>
        /// <param name="brLine"></param>
        public static void FixNumbers(BrailleLine brLine, EnglishBrailleTable brTable)
        {
            BrailleWord brWord;
            bool        isNumberMode = false;
            string      brCode;

            int index = 0;

            while (index < brLine.WordCount)
            {
                brWord = brLine[index];
                if (brWord.Text == "#")
                {
                    isNumberMode = true;
                    brLine.Words.RemoveAt(index);
                    continue;
                }
                if (Char.IsDigit(brWord.Text[0]))
                {
                    if (isNumberMode)
                    {
                        // 把編號的數字改成上位點。
                        brCode = brTable.FindDigit(brWord.Text, true);
                        if (brWord.Cells.Count > 1)                             // 第 0 個 cell 可能是數字記號。
                        {
                            brWord.Cells[1] = BrailleCell.GetInstance(brCode);
                        }
                        else
                        {
                            brWord.Cells[0] = BrailleCell.GetInstance(brCode);
                        }
                    }
                }
                else
                {
                    if (isNumberMode && brWord.Text != "." && brWord.Text != "-" && brWord.Text != ",")
                    {
                        isNumberMode = false;
                    }
                }
                index++;
            }
        }
        /// <summary>
        /// 套用問號、驚嘆號的點字規則。
        /// 規則:後面要加一空方,若後面跟著下引號('」'),則不加空方。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private static int ApplyQuestionRule(BrailleLine brLine, int index)
        {
            index++;
            if (index >= brLine.WordCount)  // 如果已經到結尾或超過,就不加空方。
            {
                return(0);
            }

            int         wordOffset = 0;
            BrailleWord brWord     = brLine[index];

            if (!BrailleWord.IsBlank(brWord))  // 若原本已有空方,就不再多加。
            {
                if (!brWord.Text.Equals("」"))
                {
                    brLine.Words.Insert(index, BrailleWord.NewBlank());
                    wordOffset = 1;
                }
            }
            return(wordOffset);
        }
Esempio n. 23
0
        /// <summary>
        /// 處理那些需要在左右兩邊加空方的字元。
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static int AddBlankForSpecialCharacters(BrailleLine brLine, int wordIdx)
        {
            int         wordOffset = 0;
            BrailleWord currWord   = brLine[wordIdx]; // 目前的字
            BrailleWord prevWord   = null;            // 上一個字
            BrailleWord nextWord   = null;            // 下一個字

            if ((wordIdx - 1) >= 0)
            {
                prevWord = brLine[wordIdx - 1];
            }
            if ((wordIdx + 1) < brLine.WordCount)
            {
                nextWord = brLine[wordIdx + 1];
            }

            switch (brLine[wordIdx].Text)
            {
            case "=":
                wordOffset += EncloseBlankCells(brLine, wordIdx);
                break;

// 注意!! '/' 符號不可以自動加空方,因為在表示分數時,'/' 不加空方。其他情況請編輯時自行加空方。
//				case "/":
//                    wordOffset += EncloseBlankCells(brLine, wordIdx);
//                    break;
            case "%":
                wordOffset += PostfixBlankCell(brLine, wordIdx);
                if (prevWord != null && !Char.IsDigit(prevWord.Text[0]))
                {
                    // % 符號的左邊如果不是數字,則加一空方。
                    wordOffset += PrefixBlankCell(brLine, wordIdx);
                }
                break;

            default:
                break;
            }
            return(wordOffset);
        }
Esempio n. 24
0
        /// <summary>
        /// 把多個連續空白刪到只剩一個。
        /// </summary>
        /// <param name="brLine"></param>
        public static void ShrinkSpaces(BrailleLine brLine)
        {
            int         i = 0;
            int         firstSpaceIndex = -1;
            int         spaceCount      = 0;
            BrailleWord brWord;

            while (i < brLine.WordCount)
            {
                brWord = brLine[i];
                if (brWord.Text == " ")
                {
                    spaceCount++;
                    if (firstSpaceIndex < 0)
                    {
                        firstSpaceIndex = i;
                    }
                }
                else
                {
                    // 不是全形空白,把之前取得的全形空白數量刪到剩一個。
                    if (firstSpaceIndex >= 0 && spaceCount > 1)
                    {
                        int cnt = spaceCount - 1;
                        brLine.Words.RemoveRange(firstSpaceIndex, cnt);
                        i = i - cnt;
                    }
                    firstSpaceIndex = -1;
                    spaceCount      = 0;
                }
                i++;
            }

            // 去掉最後的連續空白
            if (firstSpaceIndex >= 0 && spaceCount > 1)
            {
                int cnt = spaceCount - 1;
                brLine.Words.RemoveRange(firstSpaceIndex, cnt);
            }
        }
        /// <summary>
        /// 套用句號規則:
        /// 1.在同一點字行中,句號之後,須空一方,再點寫下文;
        ///   如在行末不夠點寫句號時,須將原句最末一字與句號移至次一行連書,
        ///   然後空方接寫下文;如句號恰在一行之最末一方,下文換行點寫時,次行之首無須空方。
        /// 2.句號可與後引號(包括後單或雙引號)、刪節號、後括弧
        ///   (包括後圓括弧、後方括弧、後大括弧)、後夾註號連書,但不得單獨書於一行之首)。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private static int ApplyPeriodRule(BrailleLine brLine, int index)
        {
            index++;
            if (index >= brLine.WordCount)  // 如果已經到結尾或超過,就不加空方。
            {
                return(0);
            }

            int         wordOffset = 0;
            BrailleWord brWord     = brLine[index];

            if (!BrailleWord.IsBlank(brWord))  // 若原本已有空方,就不再多加。
            {
                // 句號可與標點符號連書而無須加空方。
                if (!BrailleWord.IsChinesePunctuation(brWord))
                {
                    brLine.Words.Insert(index, BrailleWord.NewBlank());
                    wordOffset = 1;
                }
            }
            return(wordOffset);
        }
Esempio n. 26
0
        /// <summary>
        /// 在指定的索引處的 BrailleWord 物件中加入數符。
        /// 此函式會自動判斷是否有不需加入數符的例外狀況,例如:次方。
        /// </summary>
        /// <param name="brLine"></param>
        /// <param name="index"></param>
        private static void AddDigitSymbol(BrailleLine brLine, int index)
        {
            BrailleCell digitCell = BrailleCell.GetInstance(BrailleCellCode.Digit);

            bool needDigitSymbol = true;

            if (index > 0)                                    // 先檢查前一個字元,是否為不需加數符的特例。
            {
                if (brLine.Words[index - 1].Text.Equals("^")) // 次方。
                {
                    needDigitSymbol = false;
                }
            }
            if (needDigitSymbol)
            {
                BrailleWord firstDigitWord = brLine.Words[index];
                // 如果已經有加上數字記號就不再重複加。
                if (!firstDigitWord.Cells[0].Equals(digitCell))
                {
                    firstDigitWord.Cells.Insert(0, digitCell);
                }
            }
        }
Esempio n. 27
0
 /// <summary>
 /// 移除指定的列。注意:只做移除列的動作,不可清除列的內容物件!!
 /// </summary>
 /// <param name="index"></param>
 public void RemoveLine(int index)
 {
     BrailleLine brLine = m_Lines[index];
     m_Lines.RemoveAt(index);
 }
Esempio n. 28
0
 /// <summary>
 /// 加入一列。
 /// </summary>
 /// <param name="brLine"></param>
 public void AddLine(BrailleLine brLine)
 {
     m_Lines.Add(brLine);
 }
        /// <summary>
        /// 計算斷行位置。
        /// </summary>
        /// <param name="brLine">點字串列。</param>
        /// <param name="cellsPerLine">每行最大允許幾方。</param>
        /// <param name="needHyphen">是否在斷行處附加一個連字號 '-'。</param>
        /// <returns>傳回可斷行的點字索引。</returns>
        private static int CalcBreakPoint(BrailleLine brLine, int cellsPerLine,
                                          out bool needHyphen)
        {
            needHyphen = false;

            // 先根據每列最大方數取得要斷行的字元索引。
            int fixedBreakIndex = brLine.CalcBreakPoint(cellsPerLine);

            if (fixedBreakIndex >= brLine.WordCount)               // 無需斷行?
            {
                return(fixedBreakIndex);
            }

            // 需斷行,根據點字規則調整斷行位置。

            int breakIndex = fixedBreakIndex;

            BrailleWord breakWord;

            // 必須和前一個字元一起斷至下一行的字元。亦即,只要剛好斷在這些字元,就要改成斷前一個字元。
            char[] joinLeftChars = { ',', '.', '。', '、', ',', ';', '?', '!', '」', '』', '‧' };
            int    loopCount     = 0;

            while (breakIndex >= 0)
            {
                loopCount++;
                if (loopCount > 10000)
                {
                    throw new Exception("偵測到無窮回圈於 BrailleProcessor.CalcBreakPoint(),請通知程式設計師!");
                }

                breakWord = brLine[breakIndex];

                if (breakWord.DontBreakLineHere)    // 如果之前已經設定這個字不能在此處斷行
                {
                    breakIndex--;
                    continue;
                }

                if (breakWord.Text.IndexOfAny(joinLeftChars) >= 0)
                {
                    // 前一個字要和此字元一起移到下一行。
                    breakIndex--;
                    continue;                           // 繼續判斷前一個字元可否斷行。
                }

                if (breakWord.IsWhiteSpace) // 找到空白處,可斷開
                {
                    breakIndex++;           // 斷在空白右邊的字元。
                    break;
                }

                // 處理數字的斷字:連續數字不可斷開。
                if (breakWord.IsDigit)
                {
                    breakIndex--;
                    while (breakIndex >= 0)
                    {
                        if (!brLine[breakIndex].IsDigit)
                        {
                            break;
                        }
                        breakIndex--;
                    }
                }
                else if (breakWord.IsLetter)    // 英文單字不斷字。
                {
                    breakIndex--;
                    while (breakIndex >= 0)
                    {
                        if (!brLine[breakIndex].IsLetter)
                        {
                            break;
                        }
                        breakIndex--;
                    }
                }
                else if (breakWord.Text.Equals("_"))    // 連續底線不斷字。
                {
                    breakIndex--;
                    while (breakIndex >= 0)
                    {
                        if (!brLine[breakIndex].Text.Equals("_"))
                        {
                            break;
                        }
                        breakIndex--;
                    }
                }
                else
                {
                    break;
                }
            } // of while (breakIndex >= 0)

            if (breakIndex <= 0)
            {
                // 若此處 breakIndex < 0,表示找不到任何可斷行的位置;
                // 若此處 breakIndex == 0,表示可斷在第一個字元,那也沒有意義,因此也視為找不到斷行位置。

                //Trace.WriteLine("無法找到適當的斷行位置,使用每列最大方數斷行!");
                breakIndex = fixedBreakIndex;
            }

            // 注意!! 若 breakIndex 傳回 0 會導致呼叫的函式進入無窮迴圈!!

            return(breakIndex);
        }
        /// <summary>
        /// 把一行明眼字串轉換成點字串列。
        /// 此時不考慮一行幾方和斷行的問題,只進行單純的轉換。
        /// 斷行由其他函式負責處理,因為有些點字規則必須在斷行時才能處理。
        /// </summary>
        /// <param name="lineNumber">字串的行號。此參數只是用來當轉換失敗時,傳給轉換失敗事件處理常式的資訊。</param>
        /// <param name="line">輸入的明眼字串。</param>
        /// <param name="isTitle">輸出參數,是否為標題。</param>
        /// <returns>點字串列。若則傳回 null,表示該列不需要轉成點字。</returns>
        public BrailleLine ConvertLine(int lineNumber, string line)
        {
            if (line == null)
            {
                return(null);
            }

            BrailleLine brLine = new BrailleLine();

            string orgLine = line;              // 保存原始的字串。

            // 把換行符號之後的字串去掉
            int i = line.IndexOfAny(new char[] { '\r', '\n' });

            if (i >= 0)
            {
                line = line.Substring(0, i);
            }

            // 若去掉換行字元之後變成空字串,則傳回只包含一個空方的列。
            if (String.IsNullOrEmpty(line))
            {
                brLine.Words.Add(BrailleWord.NewBlank());
                return(brLine);
            }

            // 預先處理特殊標籤的字元替換。
            line = PreprocessTagsForLine(line);
            if (line == null)
            {
                return(null);
            }

            // 如果是原書頁碼,先檢查格式是否正確。
            try
            {
                GetOrgPageNumber(line);
            }
            catch (Exception ex)
            {
                m_ErrorMsg.Append(String.Format("第 {0} 列 : ", lineNumber));
                m_ErrorMsg.Append(ex.Message);
                m_ErrorMsg.Append("\r\n");
                return(null);
            }

            line = StrHelper.Reverse(line);
            Stack <char> charStack = new Stack <char>(line);

            char ch;
            List <BrailleWord> brWordList;
            StringBuilder      text = new StringBuilder();

            ConvertionFailedEventArgs cvtFailedArgs = new ConvertionFailedEventArgs();
            TextConvertedEventArgs    textCvtArgs   = new TextConvertedEventArgs();

            while (charStack.Count > 0)
            {
                brWordList = ConvertWord(charStack);

                if (brWordList != null && brWordList.Count > 0)
                {
                    // 成功轉換成點字,有 n 個字元會從串流中取出
                    brLine.Words.AddRange(brWordList);

                    text.Length = 0;
                    foreach (BrailleWord brWord in brWordList)
                    {
                        text.Append(brWord.Text);
                    }
                    textCvtArgs.SetArgValues(lineNumber, text.ToString());
                    OnTextConverted(textCvtArgs);
                }
                else
                {
                    // 無法判斷和處理的字元應該會留存在串流中,將之取出。
                    ch = charStack.Pop();

                    int charIndex = line.Length - charStack.Count;

                    // 引發事件。
                    cvtFailedArgs.SetArgs(lineNumber, charIndex, orgLine, ch);
                    OnConvertionFailed(cvtFailedArgs);
                    if (cvtFailedArgs.Stop)
                    {
                        break;
                    }
                }

                // 如果進入分數情境,就把整個分數處理完。
                if (m_ContextTagManager.IsActive(ContextTagNames.Fraction))
                {
                    try
                    {
                        brWordList = ConvertFraction(lineNumber, charStack);
                        if (brWordList != null && brWordList.Count > 0)
                        {
                            // 成功轉換成點字,有 n 個字元會從串流中取出
                            brLine.Words.AddRange(brWordList);
                        }
                    }
                    catch (Exception ex)
                    {
                        m_ErrorMsg.Append(String.Format("第 {0} 列 : ", lineNumber));
                        m_ErrorMsg.Append(ex.Message);
                        m_ErrorMsg.Append("\r\n");
                    }
                }
            }

            ChineseBrailleRule.ApplyPunctuationRules(brLine);   // 套用中文標點符號規則。

            // 不刪除多餘空白,因為原本輸入時可能就希望縮排。
            //ChineseBrailleRule.ShrinkSpaces(brLine);	// 把連續全形空白刪到只剩一個。

            // 將編號的數字修正成上位點。
            if (m_EnglishConverter != null)
            {
                EnglishBrailleRule.FixNumbers(brLine, m_EnglishConverter.BrailleTable as EnglishBrailleTable);
            }

            EnglishBrailleRule.ApplyCapitalRule(brLine);    // 套用大寫規則。
            EnglishBrailleRule.ApplyDigitRule(brLine);      // 套用數字規則。
            EnglishBrailleRule.AddSpaces(brLine);           // 補加必要的空白。

            ChineseBrailleRule.ApplyBracketRule(brLine);    // 套用括弧規則。

            // 不刪除多於空白,因為原本輸入時可能就希望縮排。
            //EnglishBrailleRule.ShrinkSpaces(brLine);        // 把連續空白刪到只剩一個。

            return(brLine);
        }