/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (function(window) { const NON_LETTER_SYMBOLS = []; NON_LETTER_SYMBOLS[0x00A0] = 1; NON_LETTER_SYMBOLS[0x00AE] = 1; function isNonLetter(code) { if (0x2070 <= code && code <= 0x209F) return true; return !!NON_LETTER_SYMBOLS[code]; } const CHECKED_LIMIT = 2000; // Если значения совпадают - значит апостроф развернут в правильную сторону, если нет, то в значении лежит апостроф в нужном направлении const APOSTROPHES = { 0x0027 : 0x0027, 0x02BC : 0x02BC, 0x02BD : 0x02BC, 0x2018 : 0x2019, 0x2019 : 0x2019 }; function isCorrectApostrophe(codePoint) { return APOSTROPHES[codePoint] === codePoint; } /** * Класс для проверки орфографии внутри параграфа * @param oSpellChecker * @param isForceFullCheck * @constructor */ function CParagraphSpellCheckerCollector(oSpellChecker, isForceFullCheck) { this.ContentPos = new AscWord.CParagraphContentPos(); this.SpellChecker = oSpellChecker; this.ParaBidi = oSpellChecker.Paragraph.isRtlDirection(); this.Lang = null; this.CurLcid = -1; this.bWord = false; this.sWord = ""; this.startRun = null; this.startInRunPos = 0; this.endRun = null; this.endInRunPos = 0; this.Prefix = null; this.apostrophe = null; this.lastApostrophe = null; // Защита от проверки орфографии в большом параграфе // TODO: Возможно стоить заменить проверку с количества пройденных элементов на время выполнения this.CheckedCounter = 0; this.CheckedLimit = CHECKED_LIMIT; this.FindStart = false; this.ForceFullCheck = !!isForceFullCheck; } /** * Обновляем текущую позицию на заданной глубине * @param nPos {number} * @param nDepth {number} */ CParagraphSpellCheckerCollector.prototype.UpdatePos = function(nPos, nDepth) { this.ContentPos.Update(nPos, nDepth); }; /** * Получаем текущую позицию на заданном уровне * @param nDepth * @return {number} */ CParagraphSpellCheckerCollector.prototype.GetPos = function(nDepth) { return this.ContentPos.Get(nDepth); }; /** * Проверяем превышен ли лимит возможнных проверок в параграфе за один проход таймера * @return {boolean} */ CParagraphSpellCheckerCollector.prototype.IsExceedLimit = function() { return (!this.ForceFullCheck && this.CheckedCounter >= this.CheckedLimit); }; /** * Увеличиваем счетчик проверенных элементов */ CParagraphSpellCheckerCollector.prototype.IncreaseCheckedCounter = function() { this.CheckedCounter++; }; /** * Перестартовываем счетчик */ CParagraphSpellCheckerCollector.prototype.ResetCheckedCounter = function() { this.CheckedCounter = 0; }; /** * Если проверка была приостановлена и сейчас мы ищем начальную позицию * @return {boolean} */ CParagraphSpellCheckerCollector.prototype.IsFindStart = function() { return this.FindStart; }; /** * Выставляем ищем ли мы место, где закончили проверку прошлый раз * @param isFind {boolean} */ CParagraphSpellCheckerCollector.prototype.SetFindStart = function(isFind) { this.FindStart = isFind; }; /** * Получиаем возможную приставку до слова (обычно это знак "-") * @returns {number} */ CParagraphSpellCheckerCollector.prototype.GetPrefix = function() { if (this.Prefix && this.Prefix.IsHyphen()) return this.Prefix.GetCharCode(); return 0; }; CParagraphSpellCheckerCollector.prototype.CheckPrefix = function(oItem) { this.Prefix = oItem; }; /** * Данная команда останавливает сборку элемента для проверки орфографии */ CParagraphSpellCheckerCollector.prototype.FlushWord = function() { if (this.bWord) { this.SpellChecker.Add(this.startRun, this.startInRunPos, this.endRun, this.endInRunPos, this.sWord, this.CurLcid, this.GetPrefix(), 0, this.apostrophe); this.bWord = false; this.sWord = ""; this.apostrophe = null; this.lastApostrophe = null; this.CurLcid = -1; } }; /** * @param {AscWord.CRunElementBase} oElement * @param {CTextPr} oTextPr * @param {AscWord.Run} run * @param {number} inRunPos */ CParagraphSpellCheckerCollector.prototype.HandleRunElement = function(oElement, oTextPr, run, inRunPos) { if (this.IsWordLetter(oElement)) { this.CheckLang(oElement.GetDirectionFlag()); if (!this.bWord) { this.startRun = run; this.startInRunPos = inRunPos; this.endRun = run; this.endInRunPos = inRunPos + 1; this.bWord = true; this.sWord = oElement.GetCharForSpellCheck(oTextPr.Caps); } else { if (this.lastApostrophe) { this.sWord += isCorrectApostrophe(this.lastApostrophe) ? String.fromCharCode(0x0027) : String.fromCharCode(0x0020); this.apostrophe = APOSTROPHES[this.lastApostrophe]; this.lastApostrophe = null; } this.sWord += oElement.GetCharForSpellCheck(oTextPr.Caps); this.endRun = run; this.endInRunPos = inRunPos + 1; } } else if (this.bWord && this.IsApostrophe(oElement)) { if (this.lastApostrophe) this.FlushWord(); else this.lastApostrophe = oElement.GetCodePoint(); } else { if (this.bWord) { this.SpellChecker.Add(this.startRun, this.startInRunPos, this.endRun, this.endInRunPos, this.sWord, this.CurLcid, this.GetPrefix(), oElement.IsDot() ? oElement.GetCharCode() : 0, this.apostrophe); this.bWord = false; this.sWord = ""; this.apostrophe = null; this.lastApostrophe = null; this.CheckPrefix(null); } else { this.CheckPrefix(oElement); } } this.IncreaseCheckedCounter(); }; CParagraphSpellCheckerCollector.prototype.HandleLang = function(lang) { this.Lang = lang; }; CParagraphSpellCheckerCollector.prototype.IsPunctuation = function(oElement) { if (!oElement.IsPunctuation()) return false; // Исключения, полученнные опытным путем let nUnicode = oElement.GetCodePoint(); return (!(0x2019 === nUnicode && lcid_frFR === this.CurLcid) && !(0x2018 === nUnicode && (lcid_uzLatnUZ === this.CurLcid || lcid_uzCyrlUZ === this.CurLcid))); }; CParagraphSpellCheckerCollector.prototype.IsWordLetter = function(oElement) { return (oElement.IsText() && !this.IsPunctuation(oElement) && !isNonLetter(oElement.GetCodePoint())); }; CParagraphSpellCheckerCollector.prototype.IsApostrophe = function(oElement) { if (!oElement.IsText()) return false; return !!(APOSTROPHES[oElement.GetCodePoint()]); }; CParagraphSpellCheckerCollector.prototype.CheckLang = function(dirFlag) { let lcid = -1; if (AscBidi.DIRECTION_FLAG.LTR === dirFlag) lcid = this.Lang.Val; else if (AscBidi.DIRECTION_FLAG.RTL === dirFlag) lcid = this.Lang.Bidi; if (-1 === lcid) return; if (-1 !== this.CurLcid && this.CurLcid !== lcid) this.FlushWord(); this.CurLcid = lcid; }; /** * Метка начала элемента для проверки * @constructor */ function SpellMarkStart(spellCheckElement) { this.Element = spellCheckElement; } SpellMarkStart.prototype.getElement = function() { return this.Element; }; SpellMarkStart.prototype.isStart = function() { return true; }; SpellMarkStart.prototype.onAdd = function(pos, count) { if (this.Element.startInRunPos >= pos) this.Element.startInRunPos += count; }; SpellMarkStart.prototype.onRemove = function(pos, count) { if (this.Element.startInRunPos > pos + count) this.Element.startInRunPos -= count; else if (this.Element.startInRunPos > pos) this.Element.startInRunPos = Math.max(0, pos); }; SpellMarkStart.prototype.movePos = function(shift) { this.Element.startInRunPos += shift; }; SpellMarkStart.prototype.getPos = function() { return this.Element.startInRunPos; }; SpellMarkStart.prototype.isMisspelled = function() { return false === this.Element.Checked; }; /** * Метка конца элемента для проверки * @constructor */ function SpellMarkEnd(spellCheckElement) { this.Element = spellCheckElement; } SpellMarkEnd.prototype.getElement = function() { return this.Element; }; SpellMarkEnd.prototype.isStart = function() { return false; }; SpellMarkEnd.prototype.onAdd = function(pos, count) { if (this.Element.endInRunPos >= pos) this.Element.endInRunPos += count; }; SpellMarkEnd.prototype.onRemove = function(pos, count) { if (this.Element.endInRunPos > pos + count) this.Element.endInRunPos -= count; else if (this.Element.endInRunPos > pos) this.Element.endInRunPos = Math.max(0, pos); }; SpellMarkEnd.prototype.movePos = function(shift) { this.Element.endInRunPos += shift; }; SpellMarkEnd.prototype.getPos = function() { return this.Element.endInRunPos; }; SpellMarkEnd.prototype.isMisspelled = function() { return false === this.Element.Checked; }; //--------------------------------------------------------export---------------------------------------------------- AscWord.CParagraphSpellCheckerCollector = CParagraphSpellCheckerCollector; window['AscWord'] = window['AscWord'] || {}; window['AscWord'].SpellMarkStart = SpellMarkStart; window['AscWord'].SpellMarkEnd = SpellMarkEnd; })(window);