croptable.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module table/tableselection/croptable
  7. */
  8. import { createEmptyTableCell, findAncestor, updateNumericAttribute } from '../commands/utils';
  9. import TableWalker from '../tablewalker';
  10. import { getColumnIndexes, getRowIndexes } from '../utils';
  11. /**
  12. * Returns a cropped table according to given dimensions.
  13. * To return a cropped table that starts at first row and first column and end in third row and column:
  14. *
  15. * const croppedTable = cropTable( table, 1, 1, 3, 3, tableUtils, writer );
  16. *
  17. * Calling the code above for the table below:
  18. *
  19. * 0 1 2 3 4 0 1 2
  20. * ┌───┬───┬───┬───┬───┐
  21. * 0 │ a │ b │ c │ d │ e │
  22. * ├───┴───┤ ├───┴───┤ ┌───┬───┬───┐
  23. * 1 │ f │ │ g │ │ │ │ g │ 0
  24. * ├───┬───┴───┼───┬───┤ will return: ├───┴───┼───┤
  25. * 2 │ h │ i │ j │ k │ │ i │ j │ 1
  26. * ├───┤ ├───┤ │ │ ├───┤
  27. * 3 │ l │ │ m │ │ │ │ m │ 2
  28. * ├───┼───┬───┤ ├───┤ └───────┴───┘
  29. * 4 │ n │ o │ p │ │ q │
  30. * └───┴───┴───┴───┴───┘
  31. *
  32. * @param {module:engine/model/element~Element} sourceTable
  33. * @param {Object} cropDimensions
  34. * @param {Number} cropDimensions.startRow
  35. * @param {Number} cropDimensions.startColumn
  36. * @param {Number} cropDimensions.endRow
  37. * @param {Number} cropDimensions.endColumn
  38. * @param {module:engine/model/writer~Writer} writer
  39. * @param {module:table/tableutils~TableUtils} tableUtils
  40. * @returns {module:engine/model/element~Element}
  41. */
  42. export function cropTableToDimensions( sourceTable, cropDimensions, writer, tableUtils ) {
  43. const { startRow, startColumn, endRow, endColumn } = cropDimensions;
  44. // Create empty table with empty rows equal to crop height.
  45. const croppedTable = writer.createElement( 'table' );
  46. const cropHeight = endRow - startRow + 1;
  47. for ( let i = 0; i < cropHeight; i++ ) {
  48. writer.insertElement( 'tableRow', croppedTable, 'end' );
  49. }
  50. const tableMap = [ ...new TableWalker( sourceTable, { startRow, endRow, includeSpanned: true } ) ];
  51. // Iterate over source table slots (including empty - spanned - ones).
  52. for ( const { row: sourceRow, column: sourceColumn, cell: tableCell, isSpanned } of tableMap ) {
  53. // Skip slots outside the cropped area.
  54. // Could use startColumn, endColumn. See: https://github.com/ckeditor/ckeditor5/issues/6785.
  55. if ( sourceColumn < startColumn || sourceColumn > endColumn ) {
  56. continue;
  57. }
  58. // Row index in cropped table.
  59. const cropRow = sourceRow - startRow;
  60. const row = croppedTable.getChild( cropRow );
  61. // For empty slots: fill the gap with empty table cell.
  62. if ( isSpanned ) {
  63. // TODO: Remove table utils usage. See: https://github.com/ckeditor/ckeditor5/issues/6785.
  64. const { row: anchorRow, column: anchorColumn } = tableUtils.getCellLocation( tableCell );
  65. // But fill the gap only if the spanning cell is anchored outside cropped area.
  66. // In the table from method jsdoc those cells are: "c" & "f".
  67. if ( anchorRow < startRow || anchorColumn < startColumn ) {
  68. createEmptyTableCell( writer, writer.createPositionAt( row, 'end' ) );
  69. }
  70. }
  71. // Otherwise clone the cell with all children and trim if it exceeds cropped area.
  72. else {
  73. const tableCellCopy = tableCell._clone( true );
  74. writer.append( tableCellCopy, row );
  75. // Crop end column/row is equal to crop width/height.
  76. const cropEndColumn = endColumn - startColumn + 1;
  77. const cropEndRow = cropHeight;
  78. // Column index in cropped table.
  79. const cropColumn = sourceColumn - startColumn;
  80. // Trim table if it exceeds cropped area.
  81. // In the table from method jsdoc those cells are: "g" & "m".
  82. trimTableCellIfNeeded( tableCellCopy, cropRow, cropColumn, cropEndColumn, cropEndRow, writer );
  83. }
  84. }
  85. // Adjust heading rows & columns in cropped table if crop selection includes headings parts.
  86. addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer );
  87. return croppedTable;
  88. }
  89. /**
  90. * Returns a cropped table from the selected table cells.
  91. *
  92. * This function is to be used with the table selection.
  93. *
  94. * tableSelection.setCellSelection( startCell, endCell );
  95. *
  96. * const croppedTable = cropTable( tableSelection.getSelectedTableCells(), tableUtils, writer );
  97. *
  98. * **Note**: This function is also used by {@link module:table/tableselection~TableSelection#getSelectionAsFragment}.
  99. *
  100. * @param {Iterable.<module:engine/model/element~Element>} selectedTableCellsIterator
  101. * @param {module:table/tableutils~TableUtils} tableUtils
  102. * @param {module:engine/model/writer~Writer} writer
  103. * @returns {module:engine/model/element~Element}
  104. */
  105. export function cropTableToSelection( selectedTableCellsIterator, tableUtils, writer ) {
  106. const selectedTableCells = Array.from( selectedTableCellsIterator );
  107. const { first: startColumn, last: endColumn } = getColumnIndexes( selectedTableCells );
  108. const { first: startRow, last: endRow } = getRowIndexes( selectedTableCells );
  109. const sourceTable = findAncestor( 'table', selectedTableCells[ 0 ] );
  110. const cropDimensions = {
  111. startRow,
  112. startColumn,
  113. endRow,
  114. endColumn
  115. };
  116. return cropTableToDimensions( sourceTable, cropDimensions, writer, tableUtils );
  117. }
  118. // Adjusts table cell dimensions to not exceed last row and last column.
  119. function trimTableCellIfNeeded( tableCell, cellRow, cellColumn, lastColumn, lastRow, writer ) {
  120. const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
  121. const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
  122. if ( cellColumn + colspan > lastColumn ) {
  123. const trimmedSpan = lastColumn - cellColumn;
  124. updateNumericAttribute( 'colspan', trimmedSpan, tableCell, writer, 1 );
  125. }
  126. if ( cellRow + rowspan > lastRow ) {
  127. const trimmedSpan = lastRow - cellRow;
  128. updateNumericAttribute( 'rowspan', trimmedSpan, tableCell, writer, 1 );
  129. }
  130. }
  131. // Sets proper heading attributes to a cropped table.
  132. function addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer ) {
  133. const headingRows = parseInt( sourceTable.getAttribute( 'headingRows' ) || 0 );
  134. if ( headingRows > 0 ) {
  135. const headingRowsInCrop = headingRows - startRow;
  136. updateNumericAttribute( 'headingRows', headingRowsInCrop, croppedTable, writer, 0 );
  137. }
  138. const headingColumns = parseInt( sourceTable.getAttribute( 'headingColumns' ) || 0 );
  139. if ( headingColumns > 0 ) {
  140. const headingColumnsInCrop = headingColumns - startColumn;
  141. updateNumericAttribute( 'headingColumns', headingColumnsInCrop, croppedTable, writer, 0 );
  142. }
  143. }