UNPKG

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
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; } }