UNPKG

devexpress-richedit

Version:

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

136 lines (135 loc) 7.01 kB
import { Rectangle } from '@devexpress/utils/lib/geometry/rectangle'; import { IntervalAlgorithms } from '@devexpress/utils/lib/intervals/algorithms'; import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed'; import { ListUtils } from '@devexpress/utils/lib/utils/list'; import { Log } from '../../../rich-utils/debug/logger/base-logger/log'; import { LogObjToStr } from '../../../rich-utils/debug/logger/base-logger/log-obj-to-str'; import { LogSource } from '../../../rich-utils/debug/logger/base-logger/log-source'; import { RectangleUtils } from '../utils/rectangle-utils'; export class RowIntervalInfo extends FixedInterval { constructor(start, length, avaliableWidth = length) { super(start, length); this.avaliableWidth = avaliableWidth; } get rawUsedWidth() { return this.length - this.avaliableWidth; } get usedWidth() { return Math.min(this.rawUsedWidth, this.length); } get startOfFreeSpace() { return this.start + this.rawUsedWidth; } isConsiderBoxes() { return this.avaliableWidth != this.length; } } export class RowFormattingInfo { get isFloatingIntersectRow() { return !!this.intersectsObjects.length; } get lastNonEmptyInterval() { return this.intervals[this.lastNonEmptyIntervalIndex]; } constructor(minY, height, outerHorizontalRowContentBounds, boundsOfAnchoredOblectsOnThisColumn, tableCell) { this.intervals = []; this.busyIntervals = []; this.currIndex = 0; this.minY = minY; this.height = Math.max(1, height); this.outerHorizontalRowContentBounds = outerHorizontalRowContentBounds; this.boundsOfAnchoredOblectsOnThisColumn = boundsOfAnchoredOblectsOnThisColumn; this.tableCell = tableCell; this.lastNonEmptyIntervalIndex = 0; } get currInterval() { return this.intervals[this.currIndex]; } indexOfFreeInterval(width) { return ListUtils.indexBy(this.intervals, (curr) => curr.avaliableWidth >= width, this.currIndex); } indexOfIntervalContainsPositon(pos) { const ind = ListUtils.indexBy(this.intervals, (curr) => curr.start > pos || pos < curr.end, this.currIndex + 1); return ind < 0 ? this.intervals.length - 1 : ind; } calculate(outerHorizontalRowContentBounds) { this.setIntersectObjects(); this.busyIntervals = IntervalAlgorithms.getMergedIntervals(ListUtils.map(this.intersectsObjects, (objBnds) => new FixedInterval(objBnds.x, objBnds.width)), true); const freeIntervals = this.calculateFreeIntervals(this.busyIntervals, outerHorizontalRowContentBounds ?? this.outerHorizontalRowContentBounds); if (freeIntervals.length) { this.intervals = ListUtils.map(freeIntervals, (curr) => new RowIntervalInfo(curr.start, curr.length)); return; } this.resetMinY(ListUtils.min); Log.print(LogSource.RowFormatter, "RowFormattingInfo.calculate ", `minY:${this.minY}, height:${this.height}, currIndex: ${this.currIndex} intervals:\n${Log.join("\n", ListUtils.map(this.intervals, (curr) => LogObjToStr.fixedInterval(curr)))}`); } calculateFreeIntervals(busyIntervals, outerHorizontalRowContentBounds) { const intervals = ListUtils.reducedMap(busyIntervals, val => IntervalAlgorithms.getIntersectionNonNullLength(val, outerHorizontalRowContentBounds)); if (!intervals.length) return [outerHorizontalRowContentBounds.clone()]; const lastIntervalEnd = ListUtils.last(intervals).end; const result = ListUtils.reducedMap(intervals, (curr, i) => FixedInterval.fromPositions(busyIntervals[i - 1].end, curr.start), 1); if (outerHorizontalRowContentBounds.start < busyIntervals[0].start) result.unshift(FixedInterval.fromPositions(outerHorizontalRowContentBounds.start, busyIntervals[0].start)); if (outerHorizontalRowContentBounds.end > lastIntervalEnd) result.push(FixedInterval.fromPositions(lastIntervalEnd, outerHorizontalRowContentBounds.end)); return result; } recalculate(outerHorizontalRowContentBounds) { const currInterval = this.currInterval; this.calculate(outerHorizontalRowContentBounds); const pos = currInterval.start + currInterval.rawUsedWidth; const index = this.findIntervalIndexByPosition(pos); const interval = this.intervals[index]; if (interval && interval.start < pos) { const avaliableWidth = interval.length - (pos - interval.start); interval.avaliableWidth = avaliableWidth; } this.currIndex = index; } findIntervalIndexByPosition(position) { let index = 0; while (index < this.intervals.length) { const interval = this.intervals[index]; if (position < interval.start || interval.start <= position && position < interval.end) break; index++; } return index; } canIncrementHeightTo(newHeight) { const contentBounds = ListUtils.map(this.intervals, (c) => new Rectangle(c.start, this.minY, c.usedWidth, newHeight)); const intersectedObjects = this.calcIntersectObjects(newHeight); return !ListUtils.unsafeAnyOf(intersectedObjects, (ancBound) => ListUtils.unsafeAnyOf(contentBounds, (contentBound) => RectangleUtils.getNonCollapsedIntersection(contentBound, ancBound))); } findNextYPos() { this.resetMinY(ListUtils.min); } findNextYPosWhatHasNeededSpace(requiredWidth) { while (this.isFloatingIntersectRow && !ListUtils.anyOf(this.intervals, i => i.avaliableWidth >= requiredWidth)) this.resetMinY(ListUtils.min); this.currIndex = 0; } setIntersectObjects() { this.intersectsObjects = this.calcIntersectObjects(this.height); } calcIntersectObjects(height) { const initRect = new Rectangle(this.outerHorizontalRowContentBounds.start, this.minY, this.outerHorizontalRowContentBounds.length, height); return ListUtils.reducedMap(this.boundsOfAnchoredOblectsOnThisColumn, (objInfo) => { if (this.tableCell !== objInfo.cell || this.isInfinityWidth) return null; const rect = Rectangle.getNonCollapsedIntersection(initRect, objInfo.bounds); if (!rect) return null; if (!objInfo.canPutTextAtLeft) { const right = rect.right; rect.x = initRect.x; rect.width = right - rect.x; } if (!objInfo.canPutTextAtRight) { rect.width = initRect.right - rect.x; } return rect; }); } resetMinY(getVal) { this.minY = getVal(this.intersectsObjects, a => a.bottom).bottom; this.calculate(); } }