devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
278 lines (277 loc) • 16.5 kB
JavaScript
import { Errors } from '@devexpress/utils/lib/errors';
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 { NumberMapUtils } from '@devexpress/utils/lib/utils/map/number';
import { SearchUtils } from '@devexpress/utils/lib/utils/search';
import { LayoutPositionCreatorConflictFlags, LayoutPositionMainSubDocumentCreator } from '../../layout-engine/layout-position-creator';
import { DocumentLayoutDetailsLevel } from '../../layout/document-layout-details-level';
import { LayoutAndModelPositions } from '../../layout/layout-position';
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 { HeaderFooterInvalidatorHelper } from './header-footer-invalidator-helper';
import { RemoveContentHelper } from './remove-content-helper';
export class LayoutInvalidator {
get model() { return this.manager.model; }
get layout() { return this.manager.layout; }
get mainSubDoc() { return this.model.mainSubDocument; }
constructor(manager) {
this.manager = manager;
}
onContentInserted(subDocumentId, logPosition, length, restartFromParagraphStart) {
const subDocument = this.model.subDocuments[subDocumentId];
const pages = this.layout.pages;
if (pages.length == 0)
return;
if (!subDocument.isMain()) {
Log.print(LogSource.LayoutFormatterInvalidator, "onContentInserted(header\\footer\\textBox)", `subDocument.id:${subDocument.id}, logPosition:${logPosition}, length:${length}, restartFromParagraphStart:${restartFromParagraphStart}`);
this.otherSubDocChanged();
return;
}
const interval = new FixedInterval(logPosition, length);
if (length > 0)
this.contentOfMainSubDocumentInsertedOrDeleted(interval, restartFromParagraphStart, (lp) => this.advanceForward(lp, length));
else if (length < 0)
this.contentOfMainSubDocumentInsertedOrDeleted(interval, restartFromParagraphStart, (lp) => RemoveContentHelper.deleteInterval(this.layout, lp, interval, this.manager.changesManager.getPageChanges()));
else
throw new Error(Errors.InternalException);
}
contentOfMainSubDocumentInsertedOrDeleted(interval, restartFromParagraphStart, changeLayout) {
Log.print(LogSource.LayoutFormatterInvalidator, "contentInserted", `subDocument.id:${0}, logPosition:${interval.start}, length:${interval.length}, restartFromParagraphStart:${restartFromParagraphStart}`);
const positions = [];
if (restartFromParagraphStart)
this.addRestartFromParagraph(positions, interval.start);
const lp = this.findLayoutPositionInAllLayout(this.mainSubDoc, interval.start, DocumentLayoutDetailsLevel.Row, false, true);
const isValid = lp.page.isValid;
lp.page.invalidate();
positions.push(this.prevRowPositions(lp));
changeLayout(lp);
if (!isValid) {
const poss = ListUtils.last(positions);
poss.modelPosition = poss.layoutPosition.page.getPosition();
poss.layoutPosition.initByIndexes(poss.layoutPosition.pageIndex, 0, 0, 0);
poss.layoutPosition.applyObjectsAsMainSubDocument(this.layout, -1);
}
this.callRestart(positions);
}
onTableChanged(subDocumentId, tableIndex, tableNestedLevel) {
const subDocument = this.model.subDocuments[subDocumentId];
const topLevelTable = subDocument.tables[tableIndex].getTopLevelParent();
const interval = topLevelTable.interval;
if (tableNestedLevel > 0) {
this.onIntervalChanged(subDocumentId, interval);
return;
}
if (!subDocument.isMain()) {
Log.print(LogSource.LayoutFormatterInvalidator, "onIntervalChanged(header\\footer)", `subDocument.id:${subDocument.id}, interval:${LogObjToStr.fixedInterval(interval)}`);
this.otherSubDocChanged();
return;
}
const lp = this.findLayoutPositionInAllLayout(subDocument, interval.start, DocumentLayoutDetailsLevel.Row, false, true);
const layoutTableColumn = lp.column.tablesInfo.find(t => t.logicInfo.grid.table === topLevelTable);
let pageIndex = lp.pageIndex;
if (layoutTableColumn && layoutTableColumn.logicInfo.pageIndexFromWhichTableWasMoved !== null) {
pageIndex = layoutTableColumn.logicInfo.pageIndexFromWhichTableWasMoved;
}
this.invalidatePagesByEndPosition(pageIndex, interval.end);
this.manager.restartManager.restartFromPage(pageIndex, interval.start, false);
}
onIntervalChanged(subDocumentId, interval) {
const subDocument = this.model.subDocuments[subDocumentId];
if (interval.length == 0 || !this.layout.pages.length)
return;
if (!subDocument.isMain()) {
Log.print(LogSource.LayoutFormatterInvalidator, "onIntervalChanged(header\\footer)", `subDocument.id:${subDocument.id}, interval:${LogObjToStr.fixedInterval(interval)}`);
this.otherSubDocChanged();
return;
}
Log.print(LogSource.LayoutFormatterInvalidator, "onIntervalChanged", `subDocument.id:${subDocument.id}, interval:${LogObjToStr.fixedInterval(interval)}`);
const lp = this.findLayoutPositionInAllLayout(subDocument, interval.start, DocumentLayoutDetailsLevel.Row, false, true);
this.invalidatePagesByEndPosition(lp.pageIndex, interval.end);
this.manager.restartManager.restartFromPage(lp.pageIndex, interval.start, false);
}
onChangedSections(startSectionIndex, endSectionIndex) {
Log.print(LogSource.LayoutFormatterInvalidator, "onChangedSection", `sectionIndex:${startSectionIndex}-${endSectionIndex}`);
const startSection = this.model.sections[startSectionIndex];
const endSection = this.model.sections[endSectionIndex];
const pages = this.layout.pages;
const sectionStartPos = startSection.startLogPosition.value;
const pageIndexStart = LayoutInvalidator.getSectionFirstPageIndex(pages, this.layout.validPageCount, startSection.startLogPosition.value);
const secEndPos = endSection.getEndPosition();
for (let pageIndex = pageIndexStart, page; (page = pages[pageIndex]) && page.getPosition() <= secEndPos; pageIndex++)
page.invalidate();
this.manager.restartManager.restartFromPage(pageIndexStart, sectionStartPos, true);
}
onListLevelChanged(newState) {
Log.print(LogSource.LayoutFormatterInvalidator, "onListLevelChanged newState\n", LogObjToStr.historyItemState(LogObjToStr.historyItemListLevelStateObject, newState, "\t", "\n"));
for (let obj of newState.objects) {
NumberMapUtils.forEach(this.model.subDocuments, (subDocument) => {
const abstractNumberingListIndex = obj.isAbstractNumberingList ?
obj.numberingListIndex :
subDocument.documentModel.numberingLists[obj.numberingListIndex].abstractNumberingListIndex;
const listLevelIndex = obj.listLevelIndex;
const intervals = [];
for (let paragraph of subDocument.paragraphs) {
if (paragraph.getAbstractNumberingListIndex() === abstractNumberingListIndex &&
paragraph.getListLevelIndex() === listLevelIndex)
intervals.push(paragraph.interval);
}
if (intervals.length > 0) {
const mergedIntervals = IntervalAlgorithms.getMergedIntervals(intervals, true);
for (let interval of mergedIntervals)
this.onIntervalChanged(subDocument.id, interval);
}
});
}
}
onHeaderFooterIndexChanged(sectionIndex, type) {
const headerFooterInvalidatorHelper = new HeaderFooterInvalidatorHelper(this.model, this.layout, type);
headerFooterInvalidatorHelper.initBySectionIndex(sectionIndex);
const firstPage = this.layout.pages[headerFooterInvalidatorHelper.startPageIndex];
if (!firstPage)
return;
ListUtils.forEach(this.layout.pages, (page) => page.invalidate(), headerFooterInvalidatorHelper.startPageIndex, headerFooterInvalidatorHelper.endPageIndex);
this.manager.restartManager.restartFromPage(headerFooterInvalidatorHelper.startPageIndex, firstPage.getPosition(), true);
}
onPagesChanged(startPageIndex, endPageIndex) {
const firstPage = this.layout.pages[startPageIndex];
if (!firstPage)
return;
ListUtils.forEach(this.layout.pages, (page) => page.invalidate(), startPageIndex, endPageIndex);
this.manager.restartManager.restartFromPage(startPageIndex, firstPage.getPosition(), true);
}
onChangedAllLayout() {
Log.print(LogSource.LayoutFormatterInvalidator, "onChangedAllLayout", "");
this.manager.restartManager.restartAllLayout();
}
prevRowPositions(layoutPos) {
const prevLP = layoutPos.clone();
prevLP.advanceToPrevRow(this.layout);
const lp = prevLP.row.tableCellInfo && (!layoutPos.row.tableCellInfo ||
layoutPos.row.tableCellInfo.parentRow.parentTable.getTopLevelColumn() !=
prevLP.row.tableCellInfo.parentRow.parentTable.getTopLevelColumn()) ?
layoutPos :
prevLP;
return new LayoutAndModelPositions(lp, this.getStartModelPositionOfRow(lp));
}
callRestart(positions) {
const minMax = ListUtils.minMax(positions, a => a.modelPosition);
const minPageIndex = minMax.minElement.layoutPosition.pageIndex;
ListUtils.forEach(this.layout.pages, (page) => page.invalidate(), minPageIndex, minMax.maxElement.layoutPosition.pageIndex + 1);
if (!this.layout.pages[minPageIndex])
return;
this.manager.restartManager.restartFromPage(minPageIndex, minMax.minElement.modelPosition, false);
}
advanceForward(layoutPos, length) {
ListUtils.forEach(this.layout.pages, (page) => page.setAbsolutePosition(page.getPosition() + length), layoutPos.pageIndex + 1);
this.moveRowsToRight(layoutPos, length);
this.moveColumnsToRight(layoutPos, length);
this.movePageAreasToRight(layoutPos, length);
}
moveRowsToRight(layoutPosition, offset) {
const rows = layoutPosition.column.rows;
for (let rowIndex = layoutPosition.rowIndex + 1, row; row = rows[rowIndex]; rowIndex++)
row.columnOffset += offset;
}
moveColumnsToRight(layoutPosition, offset) {
const columns = layoutPosition.pageArea.columns;
for (let columnIndex = layoutPosition.columnIndex + 1, column; column = columns[columnIndex]; columnIndex++)
column.pageAreaOffset += offset;
}
movePageAreasToRight(layoutPosition, offset) {
const pageAreas = layoutPosition.page.mainSubDocumentPageAreas;
ListUtils.forEach(pageAreas, (pa) => pa.pageOffset += offset, layoutPosition.pageAreaIndex + 1);
}
otherSubDocChanged() {
ListUtils.forEach(this.layout.pages, (p) => p.invalidate());
this.manager.restartManager.restartFromPage(0, 0, true);
}
static getSectionFirstPageIndex(pages, validPageCount, startSectionPos) {
if (!validPageCount)
return 0;
const firstPageIndex = Math.max(0, SearchUtils.normedInterpolationIndexOf(pages, (p) => p.getPosition(), startSectionPos, 0, validPageCount - 1));
return pages[firstPageIndex].getPosition() < startSectionPos ? Math.min(firstPageIndex + 1, validPageCount) : firstPageIndex;
}
invalidatePagesByEndPosition(startPageIndex, intervalEndPos) {
this.invalidatePages(startPageIndex, (page) => intervalEndPos < page.getEndPosition());
}
invalidatePages(startPageIndex, endCondition) {
const pages = this.layout.pages;
for (let pageIndex = startPageIndex, page; page = pages[pageIndex]; pageIndex++) {
page.invalidate();
if (endCondition(page))
break;
}
}
findLayoutPositionInAllLayout(subDocument, pos, detailsLevel, endRowConflictFlags, middleRowConflictFlags) {
const layout = this.layout;
const realValidPageCount = layout.validPageCount;
const realIsFullyFormatted = layout.isFullyFormatted;
layout.validPageCount = layout.pages.length;
layout.isFullyFormatted = true;
const res = new LayoutPositionMainSubDocumentCreator(layout, subDocument, pos, detailsLevel, true)
.create(new LayoutPositionCreatorConflictFlags().setDefault(endRowConflictFlags), new LayoutPositionCreatorConflictFlags().setDefault(middleRowConflictFlags));
layout.validPageCount = realValidPageCount;
layout.isFullyFormatted = realIsFullyFormatted;
return res;
}
getStartModelPositionOfRow(lp) {
var _a;
const prevLP = lp.clone();
if (prevLP.advanceToPrevRow(this.layout)) {
const prevLPTblCellInfo = prevLP.row.tableCellInfo;
if (!prevLPTblCellInfo)
return prevLP.getLogPosition(DocumentLayoutDetailsLevel.Row) + prevLP.row.getLastBox().getEndPosition();
const curLPTblCellInfo = (_a = lp.row) === null || _a === void 0 ? void 0 : _a.tableCellInfo;
const curTopLevelColumn = curLPTblCellInfo ? curLPTblCellInfo.parentRow.parentTable.getTopLevelColumn() : null;
const prevTopLevelColumn = prevLPTblCellInfo ? prevLPTblCellInfo.parentRow.parentTable.getTopLevelColumn() : null;
if (!curLPTblCellInfo || curTopLevelColumn.logicInfo != prevTopLevelColumn.logicInfo)
return prevTopLevelColumn.logicInfo.grid.table.getEndPosition();
return prevTopLevelColumn.logicInfo.grid.table.getStartPosition();
}
return 0;
}
extendByMultipageTables(pageIndex, minPosition, forceRestartFullPage) {
const subDocument = this.model.mainSubDocument;
if (!subDocument.tables.length)
return -1;
const page = this.layout.pages[pageIndex];
if (!page)
return -1;
const table = this.isPageStartWithMultipageTable(page);
if (!table)
return -1;
return minPosition < table.getEndPosition() || forceRestartFullPage ? table.getStartPosition() : table.getEndPosition();
}
isPageStartWithMultipageTable(page) {
if (!page)
return null;
const fstPa = page.mainSubDocumentPageAreas[0];
if (!fstPa)
return null;
const fstColumn = fstPa.columns[0];
if (!fstColumn)
return null;
const fstRow = fstColumn.rows[0];
if (!fstRow)
return null;
const tableCellInfo = fstRow.tableCellInfo;
return LayoutInvalidator.isLayoutRowRowStartWithMultipageTable(fstRow) ?
tableCellInfo.parentRow.parentTable.getTopLevelColumn().logicInfo.grid.table :
null;
}
static isLayoutRowRowStartWithMultipageTable(layoutRow) {
const tableCellInfo = layoutRow.tableCellInfo;
return this.isTableCellIsPartOfMultipageTable(tableCellInfo);
}
static isTableCellIsPartOfMultipageTable(tableCellInfo) {
return !!tableCellInfo && (tableCellInfo.parentRow.rowIndex != 0 || tableCellInfo.parentRow.parentTable.isBoundWithPrev() || this.isTableCellIsPartOfMultipageTable(tableCellInfo.parentRow.parentTable.parentCell));
}
addRestartFromParagraph(positions, logPosition) {
const parStartPos = this.mainSubDoc.getParagraphByPosition(logPosition).startLogPosition.value;
const layPosParStart = this.findLayoutPositionInAllLayout(this.mainSubDoc, parStartPos, DocumentLayoutDetailsLevel.Row, false, true);
layPosParStart.page.invalidate();
positions.push(this.prevRowPositions(layPosParStart));
}
}