devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
175 lines (174 loc) • 10.9 kB
JavaScript
import { UnitConverter } from '@devexpress/utils/lib/class/unit-converter';
import { Errors } from '@devexpress/utils/lib/errors';
import { EnumUtils } from '@devexpress/utils/lib/utils/enum';
import { ListUtils } from '@devexpress/utils/lib/utils/list';
import { MathUtils } from '@devexpress/utils/lib/utils/math';
import { SearchUtils } from '@devexpress/utils/lib/utils/search';
import { StringUtils } from '@devexpress/utils/lib/utils/string';
import { LayoutBoxType } from '../../layout/main-structures/layout-boxes/layout-box';
import { TabLeaderType } from '../../layout/main-structures/layout-boxes/layout-tab-space-box';
import { TabAlign } from '../../model/paragraph/paragraph';
import { ParagraphFirstLineIndent } from '../../model/paragraph/paragraph-properties';
import { TabInfo } from '../../model/paragraph/paragraph-style';
import { NumberUtils } from '../formatter/utils/number-utils';
import { BoxAligner } from './utils/box-aligner';
import { CompatibilityMode } from '../../model/document-model';
import { IntervalAlgorithms } from '@devexpress/utils/lib/intervals/algorithms';
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
export class RowTabInfo {
get row() {
return this.rowFormatter.row;
}
get currIntervalOrLastNonEmpty() {
const currInterval = this.rowFormatter.rowSizesManager.rowFormattingInfo.currInterval;
return currInterval ? currInterval : this.rowFormatter.rowSizesManager.rowFormattingInfo.lastNonEmptyInterval;
}
get currInterval() { return this.rowFormatter.rowSizesManager.rowFormattingInfo.currInterval; }
constructor(rowFormatter, paragraphHorizontalBoundsStart) {
this.rowFormatter = rowFormatter;
this.paragraphHorizontalBoundsStart = paragraphHorizontalBoundsStart;
const tabsInfo = this.rowFormatter.paragraph.getTabs();
this.defaultTabStop = UnitConverter.twipsToPixelsF(tabsInfo.defaultTabStop);
this.tabPositions = tabsInfo.positions;
for (let tabPosition of this.tabPositions)
tabPosition.position = UnitConverter.twipsToPixelsF(tabPosition.position);
if (this.rowFormatter.paragraphProps.firstLineIndentType == ParagraphFirstLineIndent.Hanging && !this.rowFormatter.paragraph.isInList()) {
const pos = UnitConverter.twipsToPixelsF(this.rowFormatter.paragraphProps.leftIndent);
const ind = Math.max(0, SearchUtils.normedInterpolationIndexOf(this.tabPositions, (t) => t.position, pos));
const tab = this.tabPositions[ind];
const oldTabPos = tab ? tab.position : Number.MAX_VALUE;
if (oldTabPos != pos)
this.tabPositions.splice(pos > oldTabPos ? ind + 1 : ind, 0, new TabInfo(pos, TabAlign.Left, TabLeaderType.None, false, false));
}
for (let pos of this.tabPositions)
pos.position += this.paragraphHorizontalBoundsStart;
this.restart();
}
restart() {
this.lastTabPosition = null;
this.lastTabBoxIndex = -1;
}
shiftBoxesAfterLastTab() {
if (!this.lastTabPosition || EnumUtils.isAnyOf(this.lastTabPosition.alignment, TabAlign.Left, TabAlign.Numbering))
return;
const prevTabBox = this.row.boxes[this.lastTabBoxIndex];
const prevTabNewWidth = this.calculateActualTabWidth(prevTabBox);
if (prevTabNewWidth > 0) {
prevTabBox.width = prevTabNewWidth;
this.currIntervalOrLastNonEmpty.avaliableWidth -= prevTabNewWidth;
ListUtils.forEach(this.row.boxes, (box) => box.x += prevTabNewWidth, this.lastTabBoxIndex + 1);
}
this.restart();
}
addTabBox() {
const box = this.rowFormatter.currBox;
this.shiftBoxesAfterLastTab();
let tabPosition = this.getNextCustomTabPosition(this.currInterval.startOfFreeSpace);
const isCustomNonLeftTabPosition = tabPosition && !EnumUtils.isAnyOf(tabPosition.alignment, TabAlign.Left, TabAlign.Numbering);
let tabXPosRelativePage = tabPosition ? tabPosition.position : this.getNextDefaultTabPosition(this.currInterval.startOfFreeSpace);
if (tabXPosRelativePage > this.currInterval.end) {
let ind = this.rowFormatter.rowSizesManager.rowFormattingInfo.indexOfIntervalContainsPositon(tabXPosRelativePage);
if (ind != this.rowFormatter.rowSizesManager.rowFormattingInfo.currIndex) {
if (isCustomNonLeftTabPosition)
tabXPosRelativePage = this.currInterval.end;
else {
this.rowFormatter.rowSizesManager.finishLogicalRow(ind, this.currInterval.end);
tabPosition = this.getNextCustomTabPosition(this.currInterval.startOfFreeSpace);
tabXPosRelativePage = tabPosition ? tabPosition.position : this.getNextDefaultTabPosition(this.currInterval.startOfFreeSpace);
}
}
}
box.x = this.currInterval.startOfFreeSpace;
box.width = !isCustomNonLeftTabPosition ? tabXPosRelativePage - box.x : 0;
const tabBox = box.getLayoutTabBox(tabPosition ? tabPosition.leader : TabLeaderType.None);
const lastInterval = ListUtils.last(this.rowFormatter.rowSizesManager.rowFormattingInfo.intervals);
if (tabXPosRelativePage > lastInterval.end) {
if (lastInterval.end < this.rowFormatter.paragraphHorizontalBounds.end) {
if (this.rowFormatter.manager.model.compatibilitySettings.compatibilityMode < CompatibilityMode.Word2013) {
this.currInterval.length = Number.MAX_SAFE_INTEGER;
this.currInterval.avaliableWidth = Number.MAX_SAFE_INTEGER;
this.row.width = Number.MAX_SAFE_INTEGER;
return this.addTabBox();
}
else if (tabBox.right < this.rowFormatter.paragraphHorizontalBounds.end) {
const delta = this.rowFormatter.paragraphHorizontalBounds.end - lastInterval.end;
let canIncrease = !isCustomNonLeftTabPosition;
if (canIncrease && this.rowFormatter.rowSizesManager.rowFormattingInfo.isFloatingIntersectRow) {
canIncrease = !IntervalAlgorithms.getIntersection(new FixedInterval(lastInterval.end, delta), ListUtils.last(this.rowFormatter.rowSizesManager.rowFormattingInfo.busyIntervals));
}
if (canIncrease) {
lastInterval.end = this.rowFormatter.paragraphHorizontalBounds.end;
lastInterval.avaliableWidth += delta;
this.row.width += delta;
return this.addTabBox();
}
}
}
if (!this.row.isEmpty() && !isCustomNonLeftTabPosition)
return false;
if (this.rowFormatter.rowSizesManager.rowFormattingInfo.isFloatingIntersectRow && !isCustomNonLeftTabPosition) {
this.rowFormatter.rowSizesManager.rowFormattingInfo.findNextYPos();
this.rowFormatter.rowSizesManager.restartAllRow(false);
while (EnumUtils.isAnyOf(this.rowFormatter.currBox.getType(), LayoutBoxType.AnchorPicture, LayoutBoxType.AnchorTextBox))
this.rowFormatter.setBoxInfo(true);
return this.addTabBox();
}
}
this.rowFormatter.rowSizesManager.addTabBox(tabBox);
this.lastTabPosition = tabPosition ? tabPosition : new TabInfo(tabXPosRelativePage, TabAlign.Left, TabLeaderType.None, false, false);
this.lastTabBoxIndex = this.row.boxes.length - 1;
return true;
}
calculateActualTabWidth(prevTabBox) {
const prevTabBoxXPos = prevTabBox.x;
switch (this.lastTabPosition.alignment) {
case TabAlign.Decimal: {
const decimalSeparatorChar = StringUtils.getDecimalSeparator();
for (let i = this.lastTabBoxIndex + 1, box; box = this.row.boxes[i]; i++) {
const charIndex = box.getCharIndex(decimalSeparatorChar);
if (charIndex >= 0) {
const charXOffset = box.getCharOffsetXInPixels(this.rowFormatter.manager.measurer, charIndex);
return this.getFinalCustomTabWidth(prevTabBoxXPos, box.x + charXOffset - prevTabBoxXPos);
}
}
}
case TabAlign.Right: {
const lastTextBoxRightBound = this.calcLastVisibleBoxRightBounds(prevTabBoxXPos);
const tabPosition = Math.min(this.lastTabPosition.position, this.currIntervalOrLastNonEmpty.start + this.currIntervalOrLastNonEmpty.length);
return Math.max(0, tabPosition - lastTextBoxRightBound);
}
case TabAlign.Center: {
const lastTextBoxRightBound = this.calcLastVisibleBoxRightBounds(prevTabBoxXPos);
return this.getFinalCustomTabWidth(prevTabBoxXPos, Math.ceil((lastTextBoxRightBound - prevTabBoxXPos) / 2));
}
case TabAlign.Left:
case TabAlign.Numbering:
return 0;
default:
throw new Error(Errors.InternalException);
}
}
calcLastVisibleBoxRightBounds(prevTabBoxXPos) {
const startIndex = this.row.boxes.length - 1;
const endIndex = this.lastTabBoxIndex + 1;
const lastVisibleBox = this.row.boxes[BoxAligner.findLastVisibleBoxIndex(this.row.boxes, startIndex, endIndex)];
return lastVisibleBox ? lastVisibleBox.right : prevTabBoxXPos;
}
getFinalCustomTabWidth(prevTabBoxXPos, textLengthBetweenTabBoxAndTabMark) {
return MathUtils.restrictValue(this.lastTabPosition.position - prevTabBoxXPos - textLengthBetweenTabBoxAndTabMark, 0, this.currIntervalOrLastNonEmpty.avaliableWidth);
}
getNextDefaultTabPosition(xOffsetRelativePage) {
if (!this.defaultTabStop)
return xOffsetRelativePage;
if (xOffsetRelativePage >= this.paragraphHorizontalBoundsStart) {
const tab = this.paragraphHorizontalBoundsStart +
this.defaultTabStop * (Math.floor((xOffsetRelativePage - this.paragraphHorizontalBoundsStart) / this.defaultTabStop) + 1);
return tab - xOffsetRelativePage > 1 ? tab : tab + this.defaultTabStop;
}
return this.paragraphHorizontalBoundsStart -
this.defaultTabStop * Math.floor((this.paragraphHorizontalBoundsStart - xOffsetRelativePage) / this.defaultTabStop);
}
getNextCustomTabPosition(xOffsetRelativePage) {
return ListUtils.elementBy(this.tabPositions, (tabPos) => NumberUtils.lessThan(xOffsetRelativePage, tabPos.position));
}
}