UNPKG

devexpress-richedit

Version:

DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.

184 lines (183 loc) 11.1 kB
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 { rowFormatter; tabPositions; defaultTabStop; lastTabBoxIndex; lastTabPosition; paragraphHorizontalBoundsStart; 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, true)); } 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(); } tryAddTabBox() { this.shiftBoxesAfterLastTab(); let tabPosition = this.getNextCustomTabPosition(this.currInterval.startOfFreeSpace); const isCustomNonLeftTabPosition = tabPosition && !tabPosition.isLeftAlignment; let tabXPosRelativePage = tabPosition ? tabPosition.position : this.getNextDefaultTabPosition(this.currInterval.startOfFreeSpace); if (tabXPosRelativePage > this.currInterval.end) { if (this.rowFormatter.manager.model.compatibilitySettings.compatibilityMode < CompatibilityMode.Word2013 && tabPosition && !tabPosition.isParagraphIndent) { const interval = this.rowFormatter.rowSizesManager.rowFormattingInfo.outerHorizontalRowContentBounds; interval.length = Number.MAX_SAFE_INTEGER - interval.start; this.rowFormatter.rowSizesManager.isInfinityWidth = true; this.rowFormatter.rowSizesManager.rowFormattingInfo.recalculate(interval); this.row.width = Number.MAX_SAFE_INTEGER; return this.tryAddTabBox(); } else { let ind = this.rowFormatter.rowSizesManager.rowFormattingInfo.indexOfIntervalContainsPositon(tabXPosRelativePage); if (ind != this.rowFormatter.rowSizesManager.rowFormattingInfo.currIndex) { if (isCustomNonLeftTabPosition) { this.addTabBox(tabPosition, this.currInterval.end); return true; } else { this.rowFormatter.rowSizesManager.finishLogicalRow(ind, this.currInterval.end); tabPosition = this.getNextCustomTabPosition(this.currInterval.startOfFreeSpace); return this.tryAddTabBox(); } } } const extraSpace = this.rowFormatter.paragraphHorizontalBounds.end - this.currInterval.end; if (extraSpace > 0) { let canIncrease = !isCustomNonLeftTabPosition; if (canIncrease && this.rowFormatter.rowSizesManager.rowFormattingInfo.isFloatingIntersectRow) canIncrease = !IntervalAlgorithms.getIntersection(new FixedInterval(this.currInterval.end, extraSpace), ListUtils.last(this.rowFormatter.rowSizesManager.rowFormattingInfo.busyIntervals)); if (canIncrease) { this.currInterval.end = this.rowFormatter.paragraphHorizontalBounds.end; this.currInterval.avaliableWidth += extraSpace; this.row.width += extraSpace; return this.tryAddTabBox(); } } 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.tryAddTabBox(); } } this.addTabBox(tabPosition, tabXPosRelativePage); return true; } addTabBox(tabPosition, tabXPosRelativePage) { const box = this.rowFormatter.currBox; box.x = this.currInterval.startOfFreeSpace; box.width = (!tabPosition || tabPosition.isLeftAlignment) ? tabXPosRelativePage - box.x : 0; const tabBox = box.getLayoutTabBox(tabPosition ? tabPosition.leader : TabLeaderType.None); 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; } 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)); } }