/// <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);
        }
        private BrailleProcessor(ZhuyinReverseConverter zhuyinConverter)
        {
            m_Converters = new List <WordConverter>();

            m_ContextTagConverter = new ContextTagConverter();
            m_ChineseConverter    = new ChineseWordConverter(zhuyinConverter);
            m_EnglishConverter    = new EnglishWordConverter();
            m_MathConverter       = new MathConverter();
            m_CoordConverter      = new CoordinateConverter();
            m_TableConverter      = new TableConverter();
            m_PhoneticConverter   = new PhoneticConverter();

            m_Tags = new Hashtable();
            m_Tags.Add(TextTag.Name, "╴╴");             // key/value = 標籤/替換字元
            m_Tags.Add(TextTag.BookName, "﹏﹏");
            m_Tags.Add(TextTag.NumericItem, "#");
            m_Tags.Add(TextTag.OrgPageNumber, s_DashesForOrgPageNumber); // 原書頁碼
            m_Tags.Add(TextTag.Unit1End, new string('ˍ', 20));           // 大單元結束
            m_Tags.Add(TextTag.Unit2End, new string('﹍', 20));           // 小單元結束
            m_Tags.Add(TextTag.Unit3End, new string('﹋', 20));           // 小題結束
            m_Tags.Add(TextTag.BrailleComment, "★");                     // 點譯者註

            m_ContextTagManager = new ContextTagManager();

            m_InvalidChars   = new List <CharPosition>();
            m_ErrorMsg       = new StringBuilder();
            m_SuppressEvents = false;
        }
        /// <summary>
        /// 編排點字文件。
        /// </summary>
        public void FormatDocument(BrailleDocument doc)
        {
            ContextTagManager context = new ContextTagManager();

            int index = 0;

            while (index < doc.Lines.Count)
            {
                ProcessIndentTags(doc, index, context);
                index += FormatLine(doc, index, context);
            }
        }
        /// <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;
                }
            }
        }
        /// <summary>
        /// 將一行點字串列斷成多行。
        /// </summary>
        /// <param name="brLine">來源點字串列。</param>
        /// <param name="cellsPerLine">每行最大方數。</param>
        /// <param name="context">情境物件。</param>
        /// <returns>斷行之後的多行串列。若為 null 表示無需斷行(指定的點字串列未超過每行最大方數)。</returns>
        public List <BrailleLine> BreakLine(BrailleLine brLine, int cellsPerLine, ContextTagManager context)
        {
            if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
            {
                // 每列最大方數要扣掉縮排數量,並於事後補縮排的空方。
                // NOTE: 必須在斷行之後才補縮排的空方!
                cellsPerLine -= context.IndentCount;
            }

            // 若指定的點字串列未超過每行最大方數,則無須斷行,傳回 null。
            if (brLine.CellCount <= cellsPerLine)
            {
                // 補縮排的空方。
                if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
                {
                    this.Indent(brLine, context.IndentCount);
                }
                return(null);
            }

            List <BrailleLine> lines   = new List <BrailleLine>();
            BrailleLine        newLine = null;
            int  wordIndex             = 0;
            int  breakIndex            = 0;
            bool needHyphen            = false;
            bool isBroken = false; // 是否已經斷行了?
            int  indents  = 0;     // 第一次斷行時,不會有系統自斷加上的縮排,因此初始為 0。
            int  maxCells = cellsPerLine;;

            // 計算折行之後的縮排格數。
            indents = BrailleProcessor.CalcNewLineIndents(brLine);

            while (wordIndex < brLine.WordCount)
            {
                breakIndex = BrailleProcessor.CalcBreakPoint(brLine, maxCells, out needHyphen);

                newLine = brLine.Copy(wordIndex, breakIndex); // 複製到新行。
                if (needHyphen)                               // 是否要附加連字號?
                {
                    newLine.Words.Add(new BrailleWord("-", BrailleCellCode.Hyphen));
                }
                newLine.TrimEnd();                      // 去尾空白。

                // 如果是折下來的新行,就自動補上需要縮排的格數。
                if (isBroken)
                {
                    for (int i = 0; i < indents; i++)
                    {
                        newLine.Insert(0, BrailleWord.NewBlank());
                    }
                }

                brLine.RemoveRange(0, breakIndex);                                              // 從原始串列中刪除掉已經複製到新行的點字。
                wordIndex = 0;
                lines.Add(newLine);

                // 防錯:檢驗每個斷行後的 line 的方數是否超過每列最大方數。
                // 若超過,即表示之前的斷行處理有問題,須立即停止執行,否則錯誤會
                // 直到在雙視編輯的 Grid 顯示時才出現 index out of range,不易抓錯!
                System.Diagnostics.Debug.Assert(newLine.CellCount <= cellsPerLine, "斷行錯誤! 超過每列最大方數!");

                // 被折行之後的第一個字需要再根據規則調整。
                EnglishBrailleRule.ApplyCapitalRule(brLine); // 套用大寫規則。
                EnglishBrailleRule.ApplyDigitRule(brLine);   // 套用數字規則。

                isBroken = true;                             // 已經至少折了一行
                maxCells = cellsPerLine - indents;           // 下一行開始就要自動縮排,共縮 indents 格。
            }

            // 補縮排的空方。
            if (context != null && context.IndentCount > 0) // 若目前位於縮排區塊中
            {
                indents = context.IndentCount;
                foreach (BrailleLine aLine in lines)
                {
                    this.Indent(aLine, indents);
                }
            }

            return(lines);
        }