devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
294 lines (293 loc) • 18.7 kB
JavaScript
import { TableCellConditionalFormattingHistoryItem, TableRowConditionalFormattingHistoryItem } from '../history/items/tables/change-table-cell-history-items';
import { TablePosition } from './main-structures/table';
import { TablePropertiesMergerStyleColumnBandSize, TablePropertiesMergerStyleRowBandSize } from './properties-mergers/table-properties-merger';
import { ConditionalTableStyleFormatting, TableCellMergingState, TableLookTypes } from './secondary-structures/table-base-structures';
import { TableRowGridAfterHistoryItem, TableRowGridBeforeHistoryItem, TableRowHeightHistoryItem, } from '../history/items/tables/table-row-properties-history-items';
import { TableHeightUnit, TableWidthUnit, TableWidthUnitType } from './secondary-structures/table-units';
import { TableCellColumnSpanHistoryItem, TableCellPreferredWidthHistoryItem, TableCellVerticalMergingHistoryItem, } from '../history/items/tables/table-cell-properties-history-items';
export class TableCellUtils {
static getCellIndexByColumnIndex(row, startColumnIndex) {
let columnIndex = row.gridBefore;
for (let i = 0, cell; cell = row.cells[i]; i++) {
if (startColumnIndex >= columnIndex && startColumnIndex < columnIndex + cell.columnSpan)
return i;
columnIndex += cell.columnSpan;
}
return -1;
}
static getCellIndexByEndColumnIndex(row, endColumnIndex) {
let cellIndexByColumnIndex = this.getCellIndexByColumnIndex(row, endColumnIndex);
if (cellIndexByColumnIndex < 0)
return -1;
let cellByColumnIndex = row.cells[cellIndexByColumnIndex];
if (this.getStartColumnIndex(cellByColumnIndex) + cellByColumnIndex.columnSpan - 1 <= endColumnIndex)
return cellIndexByColumnIndex;
if (cellIndexByColumnIndex != 0)
return cellIndexByColumnIndex - 1;
return -1;
}
static getStartColumnIndex(cell) {
let columnIndex = cell.parentRow.gridBefore;
let row = cell.parentRow;
for (let i = 0, currentCell; currentCell = row.cells[i]; i++) {
if (currentCell === cell)
break;
columnIndex += currentCell.columnSpan;
}
return columnIndex;
}
static getEndColumnIndex(cell) {
return this.getStartColumnIndex(cell) + cell.columnSpan - 1;
}
static getColumnCount(table) {
let row = table.rows[0];
let result = row.gridBefore + row.gridAfter;
for (let cellIndex = 0, cell; cell = row.cells[cellIndex]; cellIndex++) {
result += cell.columnSpan;
}
return result;
}
static getCellIndicesByColumnsRange(row, interval) {
let indices = [];
let startColumnIndex = interval.start;
while (startColumnIndex < interval.end) {
let cellIndex = this.getCellIndexByColumnIndex(row, startColumnIndex);
let cell = row.cells[cellIndex];
if (!cell)
return indices;
indices.push(cellIndex);
startColumnIndex += startColumnIndex - this.getStartColumnIndex(cell) + cell.columnSpan;
}
return indices;
}
static getAbsoluteCellIndexInRow(row, columnIndex) {
if (!row.cells.length)
throw new Error("Empty row");
columnIndex -= row.gridBefore;
let cellIndex = 0;
let cellsCount = row.cells.length;
while (columnIndex > 0 && cellIndex < cellsCount) {
let currentCell = row.cells[cellIndex];
columnIndex -= currentCell.columnSpan;
if (columnIndex >= 0)
cellIndex++;
}
return cellIndex;
}
static getVerticalSpanCellPositions(restartCellPosition, patternCellStartColumnIndex) {
let positions = [];
positions.push(restartCellPosition);
if (restartCellPosition.cell.verticalMerging !== TableCellMergingState.Restart)
return positions;
let table = restartCellPosition.table;
for (let rowIndex = restartCellPosition.rowIndex + 1, nextRow; nextRow = table.rows[rowIndex]; rowIndex++) {
let nextRowCellIndex = this.getCellIndexByColumnIndex(nextRow, patternCellStartColumnIndex);
let nextCell = nextRow.cells[nextRowCellIndex];
if (nextCell && nextCell.verticalMerging === TableCellMergingState.Continue)
positions.push(TablePosition.createAndInit(table, rowIndex, nextRowCellIndex));
else
break;
}
return positions;
}
static getSameTableCells(firstCell, lastCell) {
const rightOrder = firstCell.parentRow.parentTable.nestedLevel >= lastCell.parentRow.parentTable.nestedLevel;
let topLevelCell = rightOrder ? firstCell : lastCell;
let lowLevelCell = rightOrder ? lastCell : firstCell;
while (topLevelCell.parentRow.parentTable.nestedLevel > lowLevelCell.parentRow.parentTable.nestedLevel)
topLevelCell = topLevelCell.parentRow.parentTable.parentCell;
while (true) {
if (topLevelCell.parentRow.parentTable === lowLevelCell.parentRow.parentTable)
return {
firstCell: rightOrder ? topLevelCell : lowLevelCell,
lastCell: rightOrder ? lowLevelCell : topLevelCell
};
topLevelCell = topLevelCell.parentRow.parentTable.parentCell;
lowLevelCell = lowLevelCell.parentRow.parentTable.parentCell;
if (!topLevelCell || !lowLevelCell)
return null;
}
}
static splitTableCellsVerticallyCore(processor, subDocument, position, rowsCount, columnsCount, inputPosition) {
const { table, cell, row, cellIndex } = position;
const { modelManipulator } = processor.modelManager;
if (cell.verticalMerging === TableCellMergingState.Restart) {
this.splitMergedCellsVertically(processor, subDocument, position, columnsCount, rowsCount);
return;
}
this.insertRows(processor, subDocument, position, rowsCount);
const startIndex = cellIndex;
const endIndex = cellIndex + columnsCount - 1;
for (let i = 0, tableCell; tableCell = row.cells[i]; i++) {
if (i < startIndex || i > endIndex) {
const columnIndex = this.getStartColumnIndex(tableCell);
const cellPosition = TablePosition.createAndInit(table, position.rowIndex, i);
const mergedCellPosition = this.getVerticalSpanCellPositions(cellPosition, columnIndex)[0];
const restartRowIndex = mergedCellPosition.rowIndex;
const continuationRowIndex = rowsCount + position.rowIndex - 2;
for (let i = continuationRowIndex; i >= restartRowIndex; i--) {
const mergedCellIndex = this.getCellIndexByColumnIndex(table.rows[i], columnIndex);
const rowPosition = TablePosition.createAndInit(table, i, mergedCellIndex);
modelManipulator.table.mergeTwoTableCellsVertically(subDocument, rowPosition, inputPosition);
}
modelManipulator.table.normalizeRows(subDocument, table);
}
}
}
static insertRows(processor, subDocument, position, rowsCount) {
const { history, modelManipulator } = processor.modelManager;
const rowHeight = position.row.height;
const newRowHeight = TableHeightUnit.create(rowHeight.value / rowsCount, rowHeight.type);
const historyItem = new TableRowHeightHistoryItem(modelManipulator, subDocument, position.table.index, position.rowIndex, newRowHeight);
history.addAndRedo(historyItem);
for (let i = 1; i < rowsCount; i++)
modelManipulator.table.insertRowBelow(subDocument, position.table, position.rowIndex);
}
static splitMergedCellsVertically(processor, subDocument, position, columnsCount, rowsCount) {
const endIndex = position.cellIndex + columnsCount - 1;
for (let cellIndex = position.cellIndex; cellIndex <= endIndex; cellIndex++) {
const newTablePosition = TablePosition.createAndInit(position.table, position.rowIndex, cellIndex);
this.splitMergedCellsVerticallyCore(processor, subDocument, newTablePosition, rowsCount);
}
}
static splitMergedCellsVerticallyCore(processor, subDocument, position, rowsCount) {
const { history, modelManipulator } = processor.modelManager;
const columnIndex = this.getStartColumnIndex(position.cell);
const mergedCellsPositions = this.getVerticalSpanCellPositions(position, columnIndex);
if (mergedCellsPositions.length === rowsCount) {
for (let i = 0, mergedCellsPosition; mergedCellsPosition = mergedCellsPositions[i]; i++)
history.addAndRedo(new TableCellVerticalMergingHistoryItem(modelManipulator, subDocument, position.table.index, mergedCellsPosition.rowIndex, mergedCellsPosition.cellIndex, TableCellMergingState.None));
}
else {
const totalRowsCount = mergedCellsPositions.length / rowsCount;
for (let i = 0, mergedCellsPosition; mergedCellsPosition = mergedCellsPositions[i]; i++) {
if (i % totalRowsCount == 0)
history.addAndRedo(new TableCellVerticalMergingHistoryItem(modelManipulator, subDocument, position.table.index, mergedCellsPosition.rowIndex, mergedCellsPosition.cellIndex, TableCellMergingState.Restart));
}
}
}
static splitTableCellsHorizontallyCore(processor, subDocument, position, columnsCount, inputPosition) {
const { history, modelManipulator } = processor.modelManager;
const columnIndex = this.getStartColumnIndex(position.cell);
const verticalSpanPositions = this.getVerticalSpanCellPositions(position, columnIndex);
const spanDelta = columnsCount - position.cell.columnSpan;
const oldPatternCellWidth = position.cell.preferredWidth;
if (oldPatternCellWidth.type !== TableWidthUnitType.Nil && oldPatternCellWidth.type !== TableWidthUnitType.Auto) {
for (let i = verticalSpanPositions.length - 1; i >= 0; i--) {
const cellPosition = verticalSpanPositions[i];
const cellWidth = cellPosition.cell.preferredWidth;
if (cellWidth.type !== TableWidthUnitType.Nil && cellWidth.type !== TableWidthUnitType.Auto)
history.addAndRedo(new TableCellPreferredWidthHistoryItem(modelManipulator, subDocument, cellPosition.table.index, cellPosition.rowIndex, cellPosition.cellIndex, TableWidthUnit.create(cellWidth.value / columnsCount, cellWidth.type)));
if (cellPosition.cell.columnSpan > 1)
history.addAndRedo(new TableCellColumnSpanHistoryItem(modelManipulator, subDocument, cellPosition.table.index, cellPosition.rowIndex, cellPosition.cellIndex, Math.max(1, cellPosition.cell.columnSpan - (columnsCount - 1))));
}
}
for (let i = 1; i < columnsCount; i++)
modelManipulator.table.insertCellToTheRight(subDocument, position.table, position.rowIndex, position.cellIndex, inputPosition, false, false);
if (spanDelta > 0)
this.normalizeColumnSpansAfterSplitHorizontally(processor, subDocument, verticalSpanPositions, columnIndex, spanDelta);
}
static normalizeColumnSpansAfterSplitHorizontally(processor, subDocument, verticalSpanPositions, columnIndex, newColumnsCount) {
const { history, modelManipulator } = processor.modelManager;
const table = verticalSpanPositions[0].table;
const startRowIndex = verticalSpanPositions[0].rowIndex;
const endRowIndex = startRowIndex + verticalSpanPositions.length - 1;
for (let rowIndex = 0, row; row = table.rows[rowIndex]; rowIndex++) {
if (rowIndex >= startRowIndex && rowIndex <= endRowIndex)
continue;
const cellIndex = this.getCellIndexByColumnIndex(row, columnIndex);
const cell = row.cells[cellIndex];
if (!cell) {
if (row.gridBefore >= columnIndex)
history.addAndRedo(new TableRowGridBeforeHistoryItem(modelManipulator, subDocument, table.index, rowIndex, row.gridBefore + newColumnsCount));
else
history.addAndRedo(new TableRowGridAfterHistoryItem(modelManipulator, subDocument, table.index, rowIndex, row.gridAfter + newColumnsCount));
}
else
history.addAndRedo(new TableCellColumnSpanHistoryItem(modelManipulator, subDocument, table.index, rowIndex, cellIndex, cell.columnSpan + newColumnsCount));
}
}
}
export class TableConditionalFormattingCalculator {
static updateTable(control, table, subDocument) {
let tableStyleColumnBandSize = new TablePropertiesMergerStyleColumnBandSize()
.getProperty(table.properties, table.style, ConditionalTableStyleFormatting.WholeTable, control.model.defaultTableProperties);
let tableStyleRowBandSize = new TablePropertiesMergerStyleRowBandSize()
.getProperty(table.properties, table.style, ConditionalTableStyleFormatting.WholeTable, control.model.defaultTableProperties);
for (let rowIndex = 0, row; row = table.rows[rowIndex]; rowIndex++) {
let rowConditionalFormatting = this.getRowConditionalFormatting(table.lookTypes, tableStyleRowBandSize, table, rowIndex);
if (row.conditionalFormatting !== rowConditionalFormatting)
control.history.addAndRedo(new TableRowConditionalFormattingHistoryItem(control.modelManipulator, subDocument, table.index, rowIndex, rowConditionalFormatting));
for (let cellIndex = 0, cell; cell = row.cells[cellIndex]; cellIndex++) {
let cellConditionalFormatting = rowConditionalFormatting | this.getCellConditionalFormatting(table.lookTypes, tableStyleColumnBandSize, table, rowIndex, cellIndex);
if (cell.conditionalFormatting !== cellConditionalFormatting)
control.history.addAndRedo(new TableCellConditionalFormattingHistoryItem(control.modelManipulator, subDocument, table.index, rowIndex, cellIndex, cellConditionalFormatting));
}
}
}
static updateTableWithoutHistory(model, table) {
let tableStyleColumnBandSize = new TablePropertiesMergerStyleColumnBandSize()
.getProperty(table.properties, table.style, ConditionalTableStyleFormatting.WholeTable, model.defaultTableProperties);
let tableStyleRowBandSize = new TablePropertiesMergerStyleRowBandSize()
.getProperty(table.properties, table.style, ConditionalTableStyleFormatting.WholeTable, model.defaultTableProperties);
for (let rowIndex = 0, row; row = table.rows[rowIndex]; rowIndex++) {
row.conditionalFormatting = this.getRowConditionalFormatting(table.lookTypes, tableStyleRowBandSize, table, rowIndex);
for (let cellIndex = 0, cell; cell = row.cells[cellIndex]; cellIndex++)
cell.conditionalFormatting = row.conditionalFormatting |
TableConditionalFormattingCalculator.getCellConditionalFormatting(table.lookTypes, tableStyleColumnBandSize, table, rowIndex, cellIndex);
}
}
static getRowConditionalFormatting(tableLook, tableStyleRowBandSize, table, rowIndex) {
let result = ConditionalTableStyleFormatting.WholeTable;
if (tableLook & TableLookTypes.ApplyFirstRow) {
if (rowIndex === 0)
result |= ConditionalTableStyleFormatting.FirstRow;
}
if (tableLook & TableLookTypes.ApplyLastRow) {
if (rowIndex === table.rows.length - 1)
result |= ConditionalTableStyleFormatting.LastRow;
}
if (!(tableLook & TableLookTypes.DoNotApplyRowBanding) && !(result & ConditionalTableStyleFormatting.FirstRow || result & ConditionalTableStyleFormatting.LastRow)) {
if (tableLook & TableLookTypes.ApplyFirstRow)
rowIndex--;
if (Math.floor(rowIndex / tableStyleRowBandSize) % 2 == 0)
result |= ConditionalTableStyleFormatting.OddRowBanding;
else
result |= ConditionalTableStyleFormatting.EvenRowBanding;
}
return result;
}
static getCellConditionalFormatting(tableLook, tableStyleColumnBandSize, table, rowIndex, cellIndex) {
let result = ConditionalTableStyleFormatting.WholeTable;
let row = table.rows[rowIndex];
if (tableLook & TableLookTypes.ApplyFirstColumn) {
if (cellIndex === 0)
result |= ConditionalTableStyleFormatting.FirstColumn;
}
if (tableLook & TableLookTypes.ApplyLastColumn) {
if (cellIndex === row.cells.length - 1)
result |= ConditionalTableStyleFormatting.LastColumn;
}
if (tableLook & TableLookTypes.ApplyFirstRow && rowIndex === 0) {
if (tableLook & TableLookTypes.ApplyFirstColumn && cellIndex === 0)
result |= ConditionalTableStyleFormatting.TopLeftCell;
if (tableLook & TableLookTypes.ApplyLastColumn && cellIndex === row.cells.length - 1)
result |= ConditionalTableStyleFormatting.TopRightCell;
}
else if (tableLook & TableLookTypes.ApplyLastRow && rowIndex === table.rows.length - 1) {
if (tableLook & TableLookTypes.ApplyFirstColumn && cellIndex === 0)
result |= ConditionalTableStyleFormatting.BottomLeftCell;
if (tableLook & TableLookTypes.ApplyLastColumn && cellIndex === row.cells.length - 1)
result |= ConditionalTableStyleFormatting.BottomRightCell;
}
if (!(tableLook & TableLookTypes.DoNotApplyColumnBanding) && !(result & ConditionalTableStyleFormatting.FirstColumn || result & ConditionalTableStyleFormatting.LastColumn)) {
if (tableLook & TableLookTypes.ApplyFirstColumn)
cellIndex--;
if (Math.floor(cellIndex / tableStyleColumnBandSize) % 2 == 0)
result |= ConditionalTableStyleFormatting.OddColumnBanding;
else
result |= ConditionalTableStyleFormatting.EvenColumnBanding;
}
return result;
}
}