Files
Yajbir Singh f1b860b25c
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

643 lines
17 KiB
JavaScript

/*
* (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 EPSILON = 0.001;
const MAX_DIFF = 1000000;
/**
* Class for searching position in the paragraph
* @constructor
*/
function ParagraphSearchPositionXY()
{
this.line = 0;
this.range = 0;
this.page = 0;
this.paragraph = null;
this.centerMode = true; // Search the closest position (relative to the middle of the element), or we search a position beyond the specified x-coordinate
this.stepEnd = false; // Search for position beyond the mark of paragraph
this.curX = 0;
this.curY = 0;
this.x = 0;
this.y = 0;
this.diffX = MAX_DIFF;
this.diffAbs = MAX_DIFF;
this.numbering = false;
this.inText = false;
this.inTextX = false;
this.paraEnd = false;
this.bidiFlow = new AscWord.BidiFlow(this);
this.emptyRun = null;
this.emptyRunHandler = {};
// TODO: Unite with CRunWithPosition class
this.pos = null;
this.posInfo = {
run : null,
pos : 0
};
this.inTextPos = null;
this.inTextPosInfo = {
run : null,
pos : 0
};
this.complexFields = new AscWord.ParagraphComplexFieldStack();
}
ParagraphSearchPositionXY.prototype.init = function(paragraph, stepEnd, centerMode)
{
this.paragraph = paragraph;
this.stepEnd = undefined !== stepEnd ? stepEnd : false;
this.centerMode = undefined !== centerMode ? centerMode : true;
this.bidiFlow.begin(paragraph.isRtlDirection());
};
ParagraphSearchPositionXY.prototype.reset = function()
{
this.bidiFlow.end();
this.emptyRun = null;
this.emptyRunHandler = {};
};
ParagraphSearchPositionXY.prototype.setDiff = function(diff)
{
this.diffX = Math.abs(diff);
this.diffAbs = diff;
};
ParagraphSearchPositionXY.prototype.searchByXY = function(x, y, page)
{
if (this.correctPageAndLineNumber(page))
this.line = this.calculateLineNumber(y, this.page);
else
y = undefined;
this.searchByLine(x, this.line, page, y);
};
ParagraphSearchPositionXY.prototype.searchByLine = function(x, line, page, y)
{
this.pos = null;
this.inTextPos = null;
this.line = line;
this.correctPageAndLineNumber(page);
this.range = this.calculateRangeNumber(x);
if (-1 === this.range)
return;
this.complexFields.resetRange(this.paragraph, this.line, this.range);
let para = this.paragraph;
let paraRange = para.Lines[this.line].Ranges[this.range];
this.y = undefined === y ? 0 : y;
this.x = x;
this.curX = paraRange.XVisible;
this.curY = 0;
let isRtl = para.isRtlDirection();
if (para.Numbering.checkRange(this.range, this.line) && !isRtl)
this.checkNumbering(isRtl);
let startPos = paraRange.StartPos;
let endPos = paraRange.EndPos;
for (let pos = startPos; pos <= endPos; ++pos)
{
para.Content[pos].getParagraphContentPosByXY(this);
}
this.bidiFlow.end();
if (para.Numbering.checkRange(this.range, this.line) && isRtl)
this.checkNumbering(isRtl);
this.checkRangeBounds(x, paraRange);
this.checkInText()
if (this.diffX > MAX_DIFF - 1)
{
this.pos = para.Get_StartRangePos2(this.line, this.range);
this.inTextPos = this.pos.Copy();
}
};
ParagraphSearchPositionXY.prototype.handleRun = function(run)
{
if (this.emptyRun)
return;
if (!run.IsEmpty())
this.emptyRun = null;
else
this.emptyRun = run;
};
ParagraphSearchPositionXY.prototype.handleEmptyRun = function(run)
{
let curX = this.curX;
if (run.IsMathRun())
{
let mathPos = run.ParaMath.GetLinePosition(this.line, this.range);
curX = mathPos.x + run.pos.x;
}
let diff = this.x - curX;
if (this.checkPosition(diff))
{
this.setDiff(diff);
this.posInfo.run = run;
this.posInfo.pos = run.GetElementsCount();
}
};
ParagraphSearchPositionXY.prototype.handleParaMath = function(math)
{
if (this.emptyRun)
{
this.handleEmptyRun(this.emptyRun);
this.emptyRun = null;
}
let curX = this.curX;
let mathW = math.Root.GetWidth(this.line, this.range);
if ((curX <= this.x && this.x < curX + mathW) || this.diffX > MAX_DIFF - 1)
{
let diffX = this.diffX;
this.setDiff(MAX_DIFF);
math.Root.getParagraphContentPosByXY(this);
if (this.inText)
this.diffX = EPSILON;
// TODO: Пересмотреть данную проверку. Надо выяснить насколько сильно она вообще нужна
// Если мы попадаем в формулу, тогда не ищем позицию вне ее. За исключением, случая когда формула идет в начале
// строки. Потому что в последнем случае из формулы 100% придет true, а позиция, возможно, находится за формулой.
if (this.diffX < MAX_DIFF - 1 && diffX < MAX_DIFF - 1)
this.diffX = 0;
else if (this.diffX > MAX_DIFF - 1)
this.diffX = diffX;
}
// Такое возможно, если все элементы до этого (в том числе и этот) были пустыми, тогда, чтобы не возвращать
// неправильную позицию вернем позицию начала данного элемента.
if (this.diffX > MAX_DIFF - 1)
{
this.pos = this.getStartPosOfElement(math);
this.setDiff(0);
}
this.curX = curX + mathW;
this.reset();
};
ParagraphSearchPositionXY.prototype.handleMathBase = function(base)
{
if (this.emptyRun)
{
this.handleEmptyRun(this.emptyRun);
this.emptyRun = null;
}
if (!base.Content.length)
return;
let startPos = 0;
let endPos = base.Content.length - 1;
if (!base.bOneLine)
{
let rangePos = base.getRangePos(this.line, this.range);
startPos = rangePos[0];
endPos = rangePos[1];
}
let x = this.x;
let y = this.y;
let targetPos = -1;
let targetBounds = null;
let diff = null;
for (let pos = 0; pos < base.Content.length; ++pos)
{
if (pos < startPos || pos > endPos)
continue;
let bounds = base.Content[pos].Get_LineBound(this.line, this.range);
if (!bounds || bounds.W < EPSILON || bounds.H < EPSILON)
continue;
if (bounds.X <= x && x <= bounds.X + bounds.W && bounds.Y <= y && y <= bounds.Y + bounds.H)
{
targetPos = pos;
targetBounds = bounds;
break;
}
else
{
// TODO: Rework this hit check
let diffX = x - (bounds.X + bounds.W / 2);
let diffY = y - (bounds.Y + bounds.H / 2);
let curDiff = diffX * diffX + diffY * diffY;
if (null === diff || diff > curDiff)
{
diff = curDiff;
targetPos = pos;
targetBounds = bounds;
}
}
}
if (-1 === targetPos)
return;
this.curX = targetBounds.X;
this.curY = targetBounds.Y;
base.Content[targetPos].getParagraphContentPosByXY(this);
this.reset();
};
ParagraphSearchPositionXY.prototype.handleRunElement = function(element, run, inRunPos)
{
if (!this.complexFields.checkRunElement(element))
return;
if (this.emptyRun)
{
this.emptyRunHandler[element] = this.emptyRun;
this.emptyRun = null;
}
this.bidiFlow.add([element, run, inRunPos], element.getBidiType());
};
ParagraphSearchPositionXY.prototype.handleBidiFlow = function(data, direction)
{
let item = data[0];
let run = data[1];
let inRunPos = data[2];
if (this.emptyRunHandler[item] && direction === AscBidi.DIRECTION.L)
{
this.handleEmptyRun(this.emptyRunHandler[item]);
delete this.emptyRunHandler[item];
}
let w = 0;
if (!item.IsDrawing() || item.IsInline())
w = item.GetWidthVisible();
if (run.IsMathRun())
{
let posLine = run.ParaMath.GetLinePosition(this.line, this.range);
let loc = item.GetLocationOfLetter();
this.curX = posLine.x + loc.x;
}
let diffL = this.x - this.curX;
let diffR = this.x - this.curX - w + (item.RGap ? item.RGap : 0);
if (-EPSILON <= diffL && diffL <= w + EPSILON)
{
this.inTextX = true;
this.inTextPosInfo.run = run;
this.inTextPosInfo.pos = inRunPos;
}
if (direction === AscBidi.DIRECTION.R)
{
let tmp = diffR;
diffR = diffL;
diffL = tmp;
}
if (this.checkPosition(diffL))
{
this.setDiff(diffL);
this.posInfo.run = run;
this.posInfo.pos = inRunPos;
}
if (!item.IsBreak() && this.checkPosition(diffR))
{
if (item.IsParaEnd())
this.paraEnd = true;
if (!item.IsParaEnd() || this.stepEnd)
{
if (item.RGap)
diffR = Math.min(diffR, diffR - item.RGap);
this.setDiff(diffR);
this.posInfo.run = run;
this.posInfo.pos = inRunPos + 1;
}
}
this.curX += w;
if (this.emptyRunHandler[item] && direction === AscBidi.DIRECTION.R)
{
this.handleEmptyRun(this.emptyRunHandler[item]);
delete this.emptyRunHandler[item];
}
};
ParagraphSearchPositionXY.prototype.getPos = function()
{
if (this.pos)
return this.pos;
this.pos = this.getPosByPosInfo(this.posInfo)
return this.pos;
};
ParagraphSearchPositionXY.prototype.getInTextPos = function()
{
if (this.inTextPos)
return this.inTextPos;
this.inTextPos = this.getPosByPosInfo(this.inTextPosInfo);
return this.inTextPos;
};
ParagraphSearchPositionXY.prototype.getLine = function()
{
return this.line;
};
ParagraphSearchPositionXY.prototype.getRange = function()
{
return this.range;
};
ParagraphSearchPositionXY.prototype.isNumbering = function()
{
return this.numbering;
};
ParagraphSearchPositionXY.prototype.isBeyondEnd = function()
{
return this.paraEnd;
};
ParagraphSearchPositionXY.prototype.isInText = function()
{
return this.inText;
};
ParagraphSearchPositionXY.prototype.isInTextByX = function()
{
return this.inTextX;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Private area
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParagraphSearchPositionXY.prototype.correctPageAndLineNumber = function(page)
{
this.page = (-1 === page || undefined === page || null === page ? 0 : page);
let pageCount = this.paragraph.getPageCount();
if (this.page >= pageCount)
{
this.page = pageCount - 1;
this.line = this.paragraph.getLineCount() - 1;
return false;
}
else if (this.page < 0)
{
this.page = 0;
this.line = 0;
return false;
}
return true;
};
ParagraphSearchPositionXY.prototype.calculateLineNumber = function(y, page)
{
let p = this.paragraph;
let line = p.Pages[page].FirstLine;
let lastLine = page >= p.getPageCount() - 1 ? p.getLineCount() - 1 : p.Pages[page + 1].FirstLine - 1;
for (; line < lastLine; ++line)
{
let lineY = p.Pages[page].Y + p.Lines[line].Y + p.Lines[line].Metrics.Descent + p.Lines[line].Metrics.LineGap;
if (y < lineY)
break;
}
return line;
};
ParagraphSearchPositionXY.prototype.calculateRangeNumber = function(x)
{
let p = this.paragraph;
let rangeCount = p.Lines[this.line].Ranges.length;
if (rangeCount <= 0)
return -1;
else if (1 === rangeCount)
return 0;
let range = 0;
if (p.isRtlDirection())
{
for (; range < rangeCount - 1; ++range)
{
let currRange = p.Lines[this.line].Ranges[range];
let nextRange = p.Lines[this.line].Ranges[range + 1];
if (x > (currRange.X + nextRange.XEnd) / 2 || currRange.WEnd > 0.001)
break;
}
}
else
{
for (; range < rangeCount - 1; ++range)
{
let currRange = p.Lines[this.line].Ranges[range];
let nextRange = p.Lines[this.line].Ranges[range + 1];
if (x < (currRange.XEnd + nextRange.X) / 2 || currRange.WEnd > 0.001)
break;
}
}
return Math.max(0, Math.min(range, rangeCount - 1));
};
ParagraphSearchPositionXY.prototype.checkNumbering = function(isRtl)
{
let p = this.paragraph;
let numPr = p.GetNumPr();
let prevNumPr = p.GetPrChangeNumPr();
let numLvl = null;
if (numPr && numPr.IsValid())
numLvl = p.Parent.GetNumbering().GetNum(numPr.NumId).GetLvl(numPr.Lvl);
else if (prevNumPr && prevNumPr.IsValid())
numLvl = p.Parent.GetNumbering().GetNum(prevNumPr.NumId).GetLvl(undefined !== prevNumPr.Lvl && null !== prevNumPr.Lvl ? prevNumPr.Lvl : 0);
let numWidthVisible = p.Numbering.WidthVisible;
if (para_Numbering === p.Numbering.Type && numLvl)
{
let numJc = numLvl.GetJc();
let numX0 = this.curX;
let numX1 = this.curX;
let numWidth = p.Numbering.WidthNum;
if (isRtl)
{
numX0 += numWidthVisible;
numX1 += numWidthVisible;
if (AscCommon.align_Right === numJc)
{
numX1 += numWidth;
}
else if (AscCommon.align_Center === numJc)
{
numX0 -= numWidth / 2;
numX1 += numWidth / 2;
}
else// if (AscCommon.align_Left === numJc)
{
numX0 -= numWidth;
}
}
else
{
if (AscCommon.align_Right === numJc)
{
numX0 -= numWidth;
}
else if (AscCommon.align_Center === numJc)
{
numX0 -= numWidth / 2;
numX1 += numWidth / 2;
}
else// if (AscCommon.align_Left === numJc)
{
numX1 += numWidth;
}
}
if (numX0 <= this.x && this.x <= numX1)
this.numbering = true;
}
this.curX += numWidthVisible;
};
ParagraphSearchPositionXY.prototype.checkPosition = function(diff)
{
return (((diff <= 0 && Math.abs(diff) < this.diffX - EPSILON) || (diff > 0 && diff < this.diffX + EPSILON))
&& (this.centerMode || this.x > this.curX));
}
ParagraphSearchPositionXY.prototype.checkRangeBounds = function(x, range)
{
if (this.stepEnd)
return;
let para = this.paragraph;
if (para.isRtlDirection())
{
if (x < range.XVisible)
{
this.setDiff(range.XVisible - x);
let pos = para.Get_EndRangePos2(this.line, this.range, false);
this.pos = para.GetCursorPlaceablePos(pos);
this.inTextPos = this.pos.Copy();
this.inTextX = false;
}
else if (x > range.XEndVisible)
{
this.setDiff(range.XEndVisible - x);
let pos = para.Get_StartRangePos2(this.line, this.range);
this.pos = para.GetCursorPlaceablePos(pos);
this.inTextPos = this.pos.Copy();
this.inTextX = false;
}
}
else
{
if (x < range.XVisible)
{
this.setDiff(range.XVisible - x);
let pos = para.Get_StartRangePos2(this.line, this.range);
this.pos = para.GetCursorPlaceablePos(pos);
this.inTextPos = this.pos.Copy();
this.inTextX = false;
}
else if (x > range.XEndVisible)
{
this.setDiff(range.XEndVisible - x);
let pos = para.Get_EndRangePos2(this.line, this.range, false);
this.pos = para.GetCursorPlaceablePos(pos);
this.inTextPos = this.pos.Copy();
this.inTextX = false;
}
}
};
ParagraphSearchPositionXY.prototype.checkInText = function()
{
this.inText = false;
if (!this.inTextX || undefined === this.y)
return;
let p = this.paragraph;
let lineTop = p.Pages[this.page].Y + p.Lines[this.line].Y - p.Lines[this.line].Metrics.Ascent - EPSILON;
let lineBottom = p.Pages[this.page].Y + p.Lines[this.line].Y + p.Lines[this.line].Metrics.Descent + p.Lines[this.line].Metrics.LineGap + EPSILON;
this.inText = lineTop <= this.y && this.y <= lineBottom;
};
ParagraphSearchPositionXY.prototype.getPosByPosInfo = function(posInfo)
{
let paraPos = this.paragraph.GetPosByElement(posInfo.run);
paraPos.Update(posInfo.pos, paraPos.GetDepth() + 1);
return this.paragraph.private_GetClosestPosInCombiningMark(paraPos, this.diffAbs);
};
ParagraphSearchPositionXY.prototype.getStartPosOfElement = function(element)
{
let paraPos = this.paragraph.GetPosByElement(element);
element.Get_StartPos(paraPos, paraPos.GetDepth() + 1);
return paraPos;
};
//--------------------------------------------------------export----------------------------------------------------
AscWord.ParagraphSearchPositionXY = ParagraphSearchPositionXY;
})(window);