devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
272 lines (271 loc) • 15.7 kB
JavaScript
import { UnitConverter } from '@devexpress/utils/lib/class/unit-converter';
import { Errors } from '@devexpress/utils/lib/errors';
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
import { ListUtils } from '@devexpress/utils/lib/utils/list';
import { LayoutBox, LayoutBoxType } from '../../../layout/main-structures/layout-boxes/layout-box';
import { LayoutNumberingListBox } from '../../../layout/main-structures/layout-boxes/layout-numbering-list-box';
import { ListNumberAlignment } from '../../../model/numbering-lists/list-level-properties';
import { ParagraphFirstLineIndent } from '../../../model/paragraph/paragraph-properties';
import { BoxWrap } from '../../box/box-wrap';
import { RowFormattingInfo } from './row-formatting-info';
import { RowHeightCalculator } from './row-height-calculator';
import { RichUtils } from '../../../model/rich-utils';
import { NumberingHelper } from '../../../model/numbering-lists/numbering-helper';
import { NumberingType } from '../../../model/numbering-lists/numbering-list';
export class RowSizesManager {
constructor(rowFormatter, outerHorizontalRowContentBounds, minY, rowSpacingBeforeApplier, boundsOfAnchoredOblectsOnThisColumn, isFirstRowInParagraph) {
this.rowFormatter = rowFormatter;
this.row = this.rowFormatter.row;
this.row.x = outerHorizontalRowContentBounds.start;
this.row.width = outerHorizontalRowContentBounds.length;
this.rowStartPos = this.rowFormatter.currBox.rowOffset;
this.heightCalculator = new RowHeightCalculator(this.rowFormatter, rowSpacingBeforeApplier);
if (isFirstRowInParagraph && this.rowFormatter.paragraph.isInList())
this.addNumberingListBoxes();
const tableCell = this.rowFormatter.paragraph.getTableCell();
this.rowFormattingInfo = new RowFormattingInfo(minY, this.heightCalculator.currState.getFullRowHeight(), outerHorizontalRowContentBounds, boundsOfAnchoredOblectsOnThisColumn, tableCell);
this.rowFormattingInfo.calculate();
}
addNumberingListBoxes() {
const { paragraph, result: { paragraphIndex }, numberingListCountersManager, manager: formatterManager } = this.rowFormatter;
const counters = numberingListCountersManager.calculateCounters(paragraphIndex);
let markText = paragraph.getNumberingListTextCore(counters);
let markProperties = paragraph.getNumerationCharacterProperties();
if (formatterManager.browserUtils.MacOSPlatform &&
markText === RichUtils.specialCharacters.MSWordBulletMark &&
markProperties.fontInfo.name === 'Symbol') {
const listTemplate = NumberingHelper.getNumberingListTemplate(formatterManager.model, NumberingType.Bullet);
const level = listTemplate.levels[paragraph.getListLevelIndex()];
const levelFontInfo = level.getCharacterProperties().fontInfo;
markProperties = markProperties.clone();
markProperties.fontInfo = levelFontInfo.clone();
markProperties.fontInfo.measurer = levelFontInfo.measurer;
markText = level.getListLevelProperties().displayFormatString;
}
this.row.numberingListBox = new LayoutNumberingListBox(markProperties, markProperties.getLayoutColorInfo(formatterManager.model.colorProvider), markText, paragraph.getNumberingListSeparatorChar(), formatterManager.model.cache.mergedCharacterPropertiesCache, paragraph.getListLevel().getListLevelProperties().alignment, formatterManager.model.cache.fontInfoCache);
LayoutBox.initializeWithMeasurer([new BoxWrap(this.row.numberingListBox, null)], formatterManager.measurer, formatterManager.innerClientProperties.showHiddenSymbols);
const boxes = [this.row.numberingListBox.textBox];
const separatorBox = this.row.numberingListBox.separatorBox;
if (separatorBox)
boxes.push(separatorBox);
for (let box of boxes)
this.heightCalculator.applyState(this.heightCalculator.getState(box));
}
addFullWord(boxes) {
const wordWidth = ListUtils.accumulate(boxes, 0, (width, box) => width + box.width);
const currIntervalEndPos = this.rowFormattingInfo.currInterval.end;
let indexOfFreeInterval = this.rowFormattingInfo.indexOfFreeInterval(wordWidth);
if (indexOfFreeInterval < 0)
return new AddFullWordResult(false, wordWidth);
const oldHeightState = this.heightCalculator.currState;
ListUtils.forEach(boxes, (box) => this.heightCalculator.applyState(this.heightCalculator.getState(box)));
const newHeightState = this.heightCalculator.currState;
const newHeight = newHeightState.getFullRowHeight();
this.rowFormattingInfo.intervals[indexOfFreeInterval].avaliableWidth -= wordWidth;
if (newHeight != this.rowFormattingInfo.height && !this.rowFormattingInfo.canIncrementHeightTo(newHeight)) {
this.rowFormattingInfo.height = newHeight;
this.heightCalculator.currState = oldHeightState;
this.restartAllRow();
return null;
}
this.heightCalculator.applyState(newHeightState);
if (this.rowFormattingInfo.currIndex != indexOfFreeInterval)
this.finishLogicalRow(indexOfFreeInterval, currIntervalEndPos);
let x = this.rowFormattingInfo.currInterval.startOfFreeSpace - wordWidth;
for (let box of boxes) {
box.x = x;
x += box.width;
this.row.boxes.push(box);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
}
return new AddFullWordResult(true);
}
addWordByChars(boxes) {
let isAddAtLeastOneChar = true;
const viewsSettings = this.rowFormatter.manager.innerClientProperties.viewsSettings;
for (let box of boxes) {
if (this.rowFormattingInfo.currInterval.avaliableWidth >= box.width) {
box.x = this.rowFormattingInfo.currInterval.startOfFreeSpace;
this.rowFormattingInfo.currInterval.avaliableWidth -= box.width;
this.row.boxes.push(box);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.heightCalculator.applyState(this.heightCalculator.getState(box));
}
else {
const newBox = box.splitByWidth(this.rowFormatter.manager.measurer, this.rowFormattingInfo.currInterval.avaliableWidth, isAddAtLeastOneChar);
if (newBox) {
newBox.x = this.rowFormattingInfo.currInterval.startOfFreeSpace;
this.row.boxes.push(newBox);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.heightCalculator.applyState(this.heightCalculator.getState(newBox));
return newBox.getEndPosition();
}
return box.rowOffset;
}
isAddAtLeastOneChar = false;
}
if (viewsSettings.isSimpleView)
return boxes[boxes.length - 1].getEndPosition();
throw new Error(Errors.InternalException);
}
addTabBox(box) {
this.row.boxes.push(box);
this.rowFormattingInfo.currInterval.avaliableWidth -= box.width;
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.rowFormatter.setBoxInfo(true);
}
addBox() {
const currIntervalEndPos = this.rowFormattingInfo.currInterval.end;
const box = this.rowFormatter.currBox;
const indexOfFreeInterval = this.rowFormattingInfo.indexOfFreeInterval(box.width);
if (indexOfFreeInterval < 0) {
if (this.row.isEmpty()) {
if (this.rowFormattingInfo.isFloatingIntersectRow) {
this.rowFormattingInfo.findNextYPos();
this.rowFormattingInfo.currIndex = 0;
this.addBox();
}
else {
box.x = this.rowFormattingInfo.intervals[0].startOfFreeSpace;
this.rowFormattingInfo.intervals[0].avaliableWidth -= Math.min(box.width, this.rowFormattingInfo.intervals[0].avaliableWidth);
this.addBoxIgnoreWidth();
}
}
else
this.rowFormatter.finishRow();
return;
}
this.rowFormattingInfo.intervals[indexOfFreeInterval].avaliableWidth -= box.width;
const newHeightState = this.heightCalculator.getState(box);
const newHeight = newHeightState.getFullRowHeight();
if (newHeight != this.rowFormattingInfo.height && !this.rowFormattingInfo.canIncrementHeightTo(newHeight)) {
this.rowFormattingInfo.intervals[indexOfFreeInterval].avaliableWidth += box.width;
this.rowFormattingInfo.height = newHeight;
this.restartAllRow();
return;
}
this.rowFormattingInfo.height = newHeight;
this.heightCalculator.applyState(newHeightState);
if (this.rowFormattingInfo.currIndex != indexOfFreeInterval)
this.finishLogicalRow(indexOfFreeInterval, currIntervalEndPos);
box.x = this.rowFormattingInfo.currInterval.startOfFreeSpace - box.width;
this.row.boxes.push(box);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.rowFormatter.setBoxInfo(true);
}
addBoxIgnoreWidth() {
const box = this.rowFormatter.currBox;
this.row.boxes.push(box);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.heightCalculator.applyState(this.heightCalculator.getState(box));
this.rowFormatter.setBoxInfo(true);
}
anywayAddBox() {
const box = this.rowFormatter.currBox;
box.x = this.rowFormattingInfo.currInterval.startOfFreeSpace;
this.rowFormattingInfo.currInterval.avaliableWidth -= box.width;
if (this.rowFormattingInfo.currInterval.avaliableWidth < 0)
this.rowFormattingInfo.currInterval.avaliableWidth = 0;
this.row.boxes.push(box);
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
this.rowFormatter.setBoxInfo(true);
}
restartAllRow(deleteAnchoredObjects = true) {
this.rowFormatter.setPosition(this.rowStartPos, false, true);
this.rowFormatter.setBoxInfo(false);
this.rowFormattingInfo.calculate();
this.rowFormattingInfo.currIndex = 0;
this.rowFormatter.result.startRowFormatting(deleteAnchoredObjects);
this.rowFormatter.wordHolder.restart();
this.rowFormatter.tabInfo.restart();
}
finishLogicalRow(nextIndex, prevRowEndPos) {
if (this.rowFormattingInfo.currInterval.isConsiderBoxes())
this.rowFormatter.result.finishLogicalRow(prevRowEndPos);
this.rowFormattingInfo.currIndex = nextIndex;
}
finishRow() {
this.row.y = this.rowFormattingInfo.minY;
if (this.heightCalculator.setFinalRowParams() || this.row.containsSpacesOnly() ||
this.rowFormattingInfo.canIncrementHeightTo(this.heightCalculator.currState.getFullRowHeight())) {
this.rowFormatter.result.finishRow();
return true;
}
this.rowFormattingInfo.height = this.heightCalculator.currState.getFullRowHeight();
this.restartAllRow();
return false;
}
addNumberingBoxes() {
return !!this.row.boxes.length || !this.row.numberingListBox || this.tryPlaceNumberingBoxes();
}
getTabEndPos(isFirstIteration, textBoxEndPos) {
const customTabPos = this.rowFormatter.tabInfo.getNextCustomTabPosition(textBoxEndPos);
if (this.rowFormatter.paragraphProps.firstLineIndentType == ParagraphFirstLineIndent.Hanging) {
const rightBound = this.rowFormatter.paragraphHorizontalBounds.start +
UnitConverter.twipsToPixelsF(this.rowFormatter.paragraphProps.leftIndent);
if (customTabPos && isFirstIteration && rightBound > textBoxEndPos &&
FixedInterval.fromPositions(textBoxEndPos, rightBound).contains(customTabPos.position))
return customTabPos.position;
return rightBound < textBoxEndPos ? this.rowFormatter.tabInfo.getNextDefaultTabPosition(textBoxEndPos) : rightBound;
}
else
return customTabPos ? customTabPos.position : this.rowFormatter.tabInfo.getNextDefaultTabPosition(textBoxEndPos);
}
tryPlaceNumberingBoxes() {
const numberingListBox = this.row.numberingListBox;
const textBox = numberingListBox.textBox;
const offset = this.calculateNumberingListBoxOffset(numberingListBox.alignment, textBox.width);
const startPos = this.rowFormattingInfo.currInterval.startOfFreeSpace - offset;
textBox.x = startPos;
let totalWidth = textBox.width;
let separatorWidthDependsOnIntervalIndex = false;
const separatorBox = numberingListBox.separatorBox;
if (separatorBox) {
const textBoxEndPos = startPos + this.row.numberingListBox.textBox.width;
if (separatorBox.getType() == LayoutBoxType.TabSpace) {
const tabEndPosition = this.getTabEndPos(this.rowFormattingInfo.currIndex == 0, textBoxEndPos);
separatorBox.width = tabEndPosition - textBoxEndPos;
separatorWidthDependsOnIntervalIndex = true;
}
separatorBox.x = textBoxEndPos;
totalWidth += separatorBox.width;
}
if (this.rowFormattingInfo.isFloatingIntersectRow) {
const indexOfFreeInterval = this.rowFormattingInfo.indexOfFreeInterval(totalWidth);
if (indexOfFreeInterval < 0) {
this.rowFormattingInfo.findNextYPos();
this.rowFormattingInfo.currIndex = 0;
this.restartAllRow();
return false;
}
if (this.rowFormattingInfo.currIndex != indexOfFreeInterval) {
const currIntervalEndPos = this.rowFormattingInfo.currInterval ? this.rowFormattingInfo.currInterval.end : -1;
if (separatorWidthDependsOnIntervalIndex) {
this.finishLogicalRow(this.rowFormattingInfo.currIndex + 1, currIntervalEndPos);
return this.tryPlaceNumberingBoxes();
}
else
this.finishLogicalRow(indexOfFreeInterval, currIntervalEndPos);
}
}
this.row.x -= offset;
this.rowFormattingInfo.currInterval.start -= offset;
this.rowFormattingInfo.currInterval.avaliableWidth -= totalWidth;
this.rowFormattingInfo.lastNonEmptyIntervalIndex = this.rowFormattingInfo.currIndex;
return true;
}
calculateNumberingListBoxOffset(alignment, boxWidth) {
let offset = 0;
if (alignment == ListNumberAlignment.Center)
offset = boxWidth / 2;
else if (alignment == ListNumberAlignment.Right)
offset = boxWidth;
return offset;
}
}
export class AddFullWordResult {
constructor(isSuccess, requiredWidth) {
this.isSuccess = isSuccess;
this.requiredWidth = requiredWidth;
}
}