Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T1145596: DataGrid - When exporting to PDF a row is moved to the next page if the row's height is bigger than the page height. #23909

Merged
merged 12 commits into from
Feb 27, 2023
2 changes: 1 addition & 1 deletion js/exporter/jspdf/common/pdf_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ function applyRtl(doc, rectsByPages, options) {
});
}

export { calculateRowHeight, calculateTextHeight, calculateTargetRectWidth, getTextLines, getPageWidth, getPageHeight, applyWordWrap, toPdfUnit, applyRtl };
export { calculateRowHeight, calculateTextHeight, calculateTargetRectWidth, getTextDimensions, getTextLines, getPageWidth, getPageHeight, applyWordWrap, toPdfUnit, applyRtl };
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { getTextLines, getTextDimensions, calculateTextHeight } from '../pdf_utils';

function createMultiCellRect(rect, text, marginTop) {
return {
...rect,
sourceCellInfo: {
...rect.sourceCellInfo,
text
},
y: marginTop,
};
}

export const createOnSplitMultiPageRow = (
doc,
options,
headerHeight,
maxBottomRight,
) =>
(isFirstPage, pageRects) => {
const currentPageRects = [];
const nextPageRects = [];
let maxCurrentPageHeight = 0;
let maxNextPageHeight = 0;

pageRects.forEach((rect) => {
const { w, sourceCellInfo } = rect;
const additionalHeight = (!isFirstPage && options.repeatHeaders)
? headerHeight
: headerHeight + options.topLeft.y;
const heightOfOneLine = getTextDimensions(doc, sourceCellInfo.text, sourceCellInfo.font).h;
const paddingHeight = sourceCellInfo.padding.top + sourceCellInfo.padding.bottom;
const fullPageHeight = (maxBottomRight.y - additionalHeight - paddingHeight - options.margin.top);
const possibleLinesCount = Math.floor(fullPageHeight / (heightOfOneLine * doc.getLineHeightFactor()));
const allLines = getTextLines(doc, sourceCellInfo.text,
sourceCellInfo.font, {
wordWrapEnabled: sourceCellInfo.wordWrapEnabled,
targetRectWidth: w
});

if(possibleLinesCount < allLines.length) {
const currentPageText = allLines.slice(0, possibleLinesCount).join('\n');
const currentPageHeight = calculateTextHeight(doc, currentPageText, sourceCellInfo.font, {
wordWrapEnabled: sourceCellInfo.wordWrapEnabled,
targetRectWidth: w
});
maxCurrentPageHeight = Math.max(maxCurrentPageHeight, currentPageHeight + paddingHeight);
maxNextPageHeight = rect.h - currentPageHeight;
currentPageRects.push(createMultiCellRect(rect, currentPageText, options.margin.top));
nextPageRects.push(createMultiCellRect(rect, allLines.slice(possibleLinesCount).join('\n'), options.margin.top));
} else {
const currentPageHeight = calculateTextHeight(doc, sourceCellInfo.text, sourceCellInfo.font, {
wordWrapEnabled: sourceCellInfo.wordWrapEnabled,
targetRectWidth: w
});
maxCurrentPageHeight = Math.max(maxCurrentPageHeight, currentPageHeight + paddingHeight);
maxNextPageHeight = Math.max(maxNextPageHeight, currentPageHeight + paddingHeight);
currentPageRects.push(createMultiCellRect(rect, sourceCellInfo.text, options.margin.top));
nextPageRects.push(createMultiCellRect(rect, '', options.margin.top));
}
});

currentPageRects.forEach((rect) => rect.h = maxCurrentPageHeight);
nextPageRects.forEach((rect) => rect.h = maxNextPageHeight);

return [currentPageRects, nextPageRects];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const isHeader = (rect) => rect?.sourceCellInfo.gridCell.rowType === 'header';
const spitMultiPageRows = (
rectsToPatch,
isCurrentPageContainsOnlyHeader,
firstRectYAdjustment,
splitMultiPageRowFunc,
checkIsFitToPageFunc,
) => {
let [newPageRects, remainPageRects] = splitMultiPageRowFunc(isCurrentPageContainsOnlyHeader, rectsToPatch);
const newPageRectsArray = [
isCurrentPageContainsOnlyHeader
? newPageRects.map((rect) => ({ ...rect, y: firstRectYAdjustment }))
: newPageRects
];
while(!checkIsFitToPageFunc(false, remainPageRects[0].h)) {
[newPageRects, remainPageRects] = splitMultiPageRowFunc(false, remainPageRects);
newPageRectsArray.push(newPageRects);
}
return [newPageRectsArray, remainPageRects];
};
const patchRects = (
rectsToSplit,
rectsToPatch,
remainPageRects,
) => {
rectsToPatch.forEach((rect, rectIndex) => {
rect.sourceCellInfo.text = remainPageRects[rectIndex].sourceCellInfo.text;
rect.h = remainPageRects[rectIndex].h;
});

const untouchedRowIdx = rectsToSplit.indexOf(rectsToPatch[rectsToPatch.length - 1]) + 1;
if(untouchedRowIdx >= rectsToSplit.length) {
return;
}

const delta = rectsToSplit[untouchedRowIdx].y - (rectsToPatch[0].y + remainPageRects[0].h);
for(let idx = untouchedRowIdx; idx < rectsToSplit.length; idx++) {
rectsToSplit[idx].y = rectsToSplit[idx].y - delta;
}
};
export const checkPageContainsOnlyHeader = (
pageRects,
isFirstPage,
) => isFirstPage && isHeader(pageRects[pageRects.length - 1]);
export const getMultiPageRowPages = (
currentPageRects,
rectsToSplit,
isCurrentPageContainsOnlyHeader,
splitMultiPageRowFunc,
checkIsFitToPageFunc,
) => {
if(!splitMultiPageRowFunc) {
return [];
}

const currentPageLastRect = currentPageRects[currentPageRects.length - 1];
const nextPageFirstRect = rectsToSplit[currentPageRects.length];
if(!nextPageFirstRect || isHeader(nextPageFirstRect)) {
return [];
}

const isRectsFitsToPage = checkIsFitToPageFunc(isCurrentPageContainsOnlyHeader, nextPageFirstRect.h);
if(isRectsFitsToPage && !isCurrentPageContainsOnlyHeader) {
return [];
}

const rectsToPatch = rectsToSplit.filter(({ y }) => y === nextPageFirstRect.y);
const firstRectYAdjustment = currentPageLastRect.y + currentPageLastRect.h;

const [multiPageRowPages, remainPageRects] = spitMultiPageRows(
rectsToPatch,
isCurrentPageContainsOnlyHeader,
firstRectYAdjustment,
splitMultiPageRowFunc,
checkIsFitToPageFunc
);

patchRects(rectsToSplit, rectsToPatch, remainPageRects);

return multiPageRowPages;
};
44 changes: 33 additions & 11 deletions js/exporter/jspdf/common/rows_splitting.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { isDefined } from '../../../core/utils/type';
import { getPageWidth, getPageHeight } from './pdf_utils';

import { roundToThreeDecimals } from './draw_utils';

import { getMultiPageRowPages, checkPageContainsOnlyHeader } from './rows_spliting_utils/get_multipage_row_pages';
import { createOnSplitMultiPageRow } from './rows_spliting_utils/create_on_split_multipage_row';

function convertToCellsArray(rows) {
return [].concat.apply([],
rows.map(rowInfo => {
Expand All @@ -28,8 +32,8 @@ function splitByPages(doc, rowsInfo, options, onSeparateRectHorizontally, onSepa
const headerHeight = headerRows.reduce((accumulator, row) => { return accumulator + row.height; }, 0);

const verticallyPages = splitRectsByPages(convertToCellsArray(rowsInfo), options.margin.top, 'y', 'h',
(pagesLength, currentCoordinate) => {
const additionalHeight = (pagesLength > 0 && options.repeatHeaders)
(isFirstPage, currentCoordinate) => {
const additionalHeight = (!isFirstPage && options.repeatHeaders)
? headerHeight
: 0;
return roundToThreeDecimals(currentCoordinate + additionalHeight) <= roundToThreeDecimals(maxBottomRight.y);
Expand All @@ -54,8 +58,9 @@ function splitByPages(doc, rowsInfo, options, onSeparateRectHorizontally, onSepa

currentPageRects.push(args.topRect);
rectsToSplit.push(args.bottomRect);
});

},
createOnSplitMultiPageRow(doc, options, headerHeight, maxBottomRight)
);
if(options.repeatHeaders) {
for(let i = 1; i < verticallyPages.length; i++) {
verticallyPages[i].forEach(rect => rect.y += headerHeight);
Expand Down Expand Up @@ -102,22 +107,21 @@ function splitByPages(doc, rowsInfo, options, onSeparateRectHorizontally, onSepa
pageIndex += 1;
}
}

return verticallyPages.map(rects => {
return rects.map(rect => Object.assign({}, rect.sourceCellInfo, { _rect: rect }));
});
}


function splitRectsByPages(rects, marginValue, coordinate, dimension, checkPredicate, onSeparateCallback) {
function splitRectsByPages(rects, marginValue, coordinate, dimension, isFitToPage, onSeparateCallback, onSplitMultiPageRow) {
MikeVitik marked this conversation as resolved.
Show resolved Hide resolved
const pages = [];
const rectsToSplit = [...rects];
const isFitToPageForMultiPageRow = (isFirstPage, rectHeight) => (isFitToPage(isFirstPage, rectHeight + marginValue));
MikeVitik marked this conversation as resolved.
Show resolved Hide resolved

while(rectsToSplit.length > 0) {
let currentPageMaxRectCoordinate = 0;
const currentPageRects = rectsToSplit.filter(rect => {
const currentRectCoordinate = rect[coordinate] + rect[dimension];
if(checkPredicate(pages.length, currentRectCoordinate)) {
if(isFitToPage(pages.length === 0, currentRectCoordinate)) {
if(currentPageMaxRectCoordinate <= currentRectCoordinate) {
currentPageMaxRectCoordinate = currentRectCoordinate;
}
Expand All @@ -127,6 +131,15 @@ function splitRectsByPages(rects, marginValue, coordinate, dimension, checkPredi
}
});

const isCurrentPageContainsOnlyHeader = checkPageContainsOnlyHeader(currentPageRects, pages.length === 0);
const multiPageRowPages = getMultiPageRowPages(
currentPageRects,
rectsToSplit,
isCurrentPageContainsOnlyHeader,
onSplitMultiPageRow,
isFitToPageForMultiPageRow,
);

const rectsToSeparate = rectsToSplit.filter(rect => {
// Check cells that have 'coordinate' less than 'currentPageMaxRectCoordinate'
const currentRectLeft = rect[coordinate];
Expand All @@ -135,7 +148,6 @@ function splitRectsByPages(rects, marginValue, coordinate, dimension, checkPredi
return true;
}
});

MikeVitik marked this conversation as resolved.
Show resolved Hide resolved
rectsToSeparate.forEach(rect => {
onSeparateCallback(rect, currentPageMaxRectCoordinate, currentPageRects, rectsToSplit);
const index = rectsToSplit.indexOf(rect);
Expand All @@ -150,22 +162,32 @@ function splitRectsByPages(rects, marginValue, coordinate, dimension, checkPredi
rectsToSplit.splice(index, 1);
}
});

rectsToSplit.forEach(rect => {
rect[coordinate] = isDefined(currentPageMaxRectCoordinate)
? (rect[coordinate] - currentPageMaxRectCoordinate + marginValue)
: rect[coordinate];
});

if(currentPageRects.length > 0) {
const firstPageContainsHeaderAndMultiPageRow = isCurrentPageContainsOnlyHeader && multiPageRowPages.length > 0;
if(firstPageContainsHeaderAndMultiPageRow) {
const [firstPage, ...restOfPages] = multiPageRowPages;
pages.push([...currentPageRects, ...firstPage]);
pages.push(...restOfPages);
} else if(currentPageRects.length > 0) {
pages.push(currentPageRects);
pages.push(...multiPageRowPages);
} else if(multiPageRowPages.length > 0) {
pages.push(...multiPageRowPages);
pages.push(rectsToSplit);
} else {
pages.push(rectsToSplit);
break;
}

}

return pages;
}

export { splitByPages };

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ QUnit.testStart(() => {

import './jspdfParts/jspdf.dataGrid.customDrawCell.tests.js';
import './jspdfParts/jspdf.dataGrid.splitting.tests.js';
import './jspdfParts/jspdf.dataGrid.splittingMultipageRow.tests.js';
import './jspdfParts/jspdf.dataGrid.measureUnits.tests.js';
import './jspdfParts/jspdf.dataGrid.columnDataTypes.tests.js';
import './jspdfParts/jspdf.dataGrid.columnDataFormats.tests.js';
Expand Down
Loading