croptable.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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, updateNumericAttribute } from '../commands/utils';
  9. import TableWalker from '../tablewalker';
  10. /**
  11. * Returns a cropped table according to given dimensions.
  12. * To return a cropped table that starts at first row and first column and end in third row and column:
  13. *
  14. * const croppedTable = cropTableToDimensions( table, {
  15. * startRow: 1,
  16. * endRow: 1,
  17. * startColumn: 3,
  18. * endColumn: 3
  19. * }, writer );
  20. *
  21. * Calling the code above for the table below:
  22. *
  23. * 0 1 2 3 4 0 1 2
  24. * ┌───┬───┬───┬───┬───┐
  25. * 0 │ a │ b │ c │ d │ e │
  26. * ├───┴───┤ ├───┴───┤ ┌───┬───┬───┐
  27. * 1 │ f │ │ g │ │ │ │ g │ 0
  28. * ├───┬───┴───┼───┬───┤ will return: ├───┴───┼───┤
  29. * 2 │ h │ i │ j │ k │ │ i │ j │ 1
  30. * ├───┤ ├───┤ │ │ ├───┤
  31. * 3 │ l │ │ m │ │ │ │ m │ 2
  32. * ├───┼───┬───┤ ├───┤ └───────┴───┘
  33. * 4 │ n │ o │ p │ │ q │
  34. * └───┴───┴───┴───┴───┘
  35. *
  36. * @param {module:engine/model/element~Element} sourceTable
  37. * @param {Object} cropDimensions
  38. * @param {Number} cropDimensions.startRow
  39. * @param {Number} cropDimensions.startColumn
  40. * @param {Number} cropDimensions.endRow
  41. * @param {Number} cropDimensions.endColumn
  42. * @param {module:engine/model/writer~Writer} writer
  43. * @returns {module:engine/model/element~Element}
  44. */
  45. export function cropTableToDimensions( sourceTable, cropDimensions, writer ) {
  46. const { startRow, startColumn, endRow, endColumn } = cropDimensions;
  47. // Create empty table with empty rows equal to crop height.
  48. const croppedTable = writer.createElement( 'table' );
  49. const cropHeight = endRow - startRow + 1;
  50. for ( let i = 0; i < cropHeight; i++ ) {
  51. writer.insertElement( 'tableRow', croppedTable, 'end' );
  52. }
  53. const tableMap = [ ...new TableWalker( sourceTable, { startRow, endRow, startColumn, endColumn, includeAllSlots: true } ) ];
  54. // Iterate over source table slots (including empty - spanned - ones).
  55. for ( const { row: sourceRow, column: sourceColumn, cell: tableCell, isAnchor, cellAnchorRow, cellAnchorColumn } of tableMap ) {
  56. // Row index in cropped table.
  57. const rowInCroppedTable = sourceRow - startRow;
  58. const row = croppedTable.getChild( rowInCroppedTable );
  59. // For empty slots: fill the gap with empty table cell.
  60. if ( !isAnchor ) {
  61. // But fill the gap only if the spanning cell is anchored outside cropped area.
  62. // In the table from method jsdoc those cells are: "c" & "f".
  63. if ( cellAnchorRow < startRow || cellAnchorColumn < startColumn ) {
  64. createEmptyTableCell( writer, writer.createPositionAt( row, 'end' ) );
  65. }
  66. }
  67. // Otherwise clone the cell with all children and trim if it exceeds cropped area.
  68. else {
  69. const tableCellCopy = tableCell._clone( true );
  70. writer.append( tableCellCopy, row );
  71. // Trim table if it exceeds cropped area.
  72. // In the table from method jsdoc those cells are: "g" & "m".
  73. trimTableCellIfNeeded( tableCellCopy, sourceRow, sourceColumn, endRow, endColumn, writer );
  74. }
  75. }
  76. // Adjust heading rows & columns in cropped table if crop selection includes headings parts.
  77. addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer );
  78. return croppedTable;
  79. }
  80. /**
  81. * Adjusts table cell dimensions to not exceed limit row and column.
  82. *
  83. * If table cell width (or height) covers a column (or row) that is after a limit column (or row)
  84. * this method will trim "colspan" (or "rowspan") attribute so the table cell will fit in a defined limits.
  85. *
  86. * @param {module:engine/model/element~Element} tableCell
  87. * @param {Number} cellRow
  88. * @param {Number} cellColumn
  89. * @param {Number} limitRow
  90. * @param {Number} limitColumn
  91. * @param {module:engine/model/writer~Writer} writer
  92. */
  93. export function trimTableCellIfNeeded( tableCell, cellRow, cellColumn, limitRow, limitColumn, writer ) {
  94. const colspan = parseInt( tableCell.getAttribute( 'colspan' ) || 1 );
  95. const rowspan = parseInt( tableCell.getAttribute( 'rowspan' ) || 1 );
  96. const endColumn = cellColumn + colspan - 1;
  97. if ( endColumn > limitColumn ) {
  98. const trimmedSpan = limitColumn - cellColumn + 1;
  99. updateNumericAttribute( 'colspan', trimmedSpan, tableCell, writer, 1 );
  100. }
  101. const endRow = cellRow + rowspan - 1;
  102. if ( endRow > limitRow ) {
  103. const trimmedSpan = limitRow - cellRow + 1;
  104. updateNumericAttribute( 'rowspan', trimmedSpan, tableCell, writer, 1 );
  105. }
  106. }
  107. // Sets proper heading attributes to a cropped table.
  108. function addHeadingsToCroppedTable( croppedTable, sourceTable, startRow, startColumn, writer ) {
  109. const headingRows = parseInt( sourceTable.getAttribute( 'headingRows' ) || 0 );
  110. if ( headingRows > 0 ) {
  111. const headingRowsInCrop = headingRows - startRow;
  112. updateNumericAttribute( 'headingRows', headingRowsInCrop, croppedTable, writer, 0 );
  113. }
  114. const headingColumns = parseInt( sourceTable.getAttribute( 'headingColumns' ) || 0 );
  115. if ( headingColumns > 0 ) {
  116. const headingColumnsInCrop = headingColumns - startColumn;
  117. updateNumericAttribute( 'headingColumns', headingColumnsInCrop, croppedTable, writer, 0 );
  118. }
  119. }