Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 54 additions & 22 deletions src/node/elements/paragraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,33 +107,43 @@ export default class Paragraph {
// * that fit into the same row:
const groupedPartiallyLinedChildren = partiallyLinedChildren.reduce(
(result, currentElement, currentIndex, array) => {
if (!result) {
result = []

// * If this is the very beginning, we start a new line:
if (!result.length) {
result = [[currentElement]];
this._debug._ && console.log('%c➡️ ◼️ start the first line:', 'font-weight: bold; color: yellow; background-color: #808080;', currentElement);
return result;
}

const currentLine = result.at(-1);

// * If BR is encountered, we start a new empty line:
if(this._DOM.getElementTagName(currentElement) === 'BR' ) {
if (!result.length) result.push([]);
result.at(-1).push(currentElement);
result.push([]); // => will be: result.at(-1).length === 0;
this._debug._ && console.log('br; push:', currentElement);
currentLine.push(currentElement);
result.push([]); // => will be: currentLine.length === 0;
this._debug._ && console.log('↩️ (BR) add to line last element:', currentElement);
return result;
}

// * If this is the beginning, or if a new line:
if(!result.length || this._node.isLineChanged(result.at(-1).at(-1), currentElement)) {
// * If the last element was BR, we end current line and start a new one:
if(currentLine.length === 0) {
this._debug._ && console.log('⬆️ add to line 1st element:', currentElement);
currentLine.push(currentElement);
return result;
}

const isVerticalDrop = this._node.isVerticalDrop(currentLine.at(-1), currentElement);

// * If this is a new line:
if(isVerticalDrop) {
result.push([currentElement]);
this._debug._ && console.log('◼️ start new line:', currentElement);
this._debug._ && console.log('%c➡️ ◼️ start new line with current:', 'font-weight: bold; color: yellow; background-color: #808080;', currentElement);
return result;
}

// TODO: isLineChanged vs isLineKept: можно сделать else? они противоположны
if(
result.at(-1).length === 0 // the last element was BR
|| (result.length && this._node.isLineKept(result.at(-1).at(-1), currentElement))
) {
this._debug._ && console.log('⬆ add to line:', currentElement);
result.at(-1).push(currentElement);
if((!isVerticalDrop)) {
this._debug._ && console.log('⬆️ add to line:', currentElement);
currentLine.push(currentElement);
return result;
}

Expand Down Expand Up @@ -195,8 +205,11 @@ export default class Paragraph {
newLine = arr[0];
newLine.setAttribute('role', '🚫');
this.strictAssert(arr.length == 0, 'The string cannot be empty (_splitComplexTextBlockIntoLines)')
} else if (arr.length == 1) {
newLine = arr[0];
// } else if (arr.length == 1) {
// newLine = arr[0];
// * Wrap every split line in textGroup to stabilize measurements:
// * each line gets a block-level wrapper, while inline flow is preserved inside the group,
// * keeping the original visual appearance.`
} else {
const group = this._node.createTextGroup();
newLine = group;
Expand Down Expand Up @@ -368,15 +381,34 @@ export default class Paragraph {
const cashInlineLineHeight = wrapper.style.lineHeight;
wrapper.style.lineHeight = 2;

// Cache geometry for this single measurement pass.
// We read layout from the browser only once per element and reuse it in comparisons/logs.
const rectCache = new WeakMap();
const getRectCached = (element) => {
if (!element) return null;
const cached = rectCache.get(element);
if (cached) return cached;
const rect = this._DOM.getElementBCR(element);
rectCache.set(element, rect);
return rect;
};

// Line start detection in Firefox is unreliable via offsetTop for inline fragments.
// Compute it from DOMRect:
const getTopCached = (element) => getRectCached(element)?.top;
const getBottomCached = (element) => getRectCached(element)?.bottom;

// Split the splittedItem into lines.
// Let's find the elements that start a new line.

const newLineStartNumbers = wrappedWordsArray.reduce(
(result, currentWord, currentIndex) => {
const prevTop = (currentIndex > 0) ? wrappedWordsArray[currentIndex - 1].offsetTop : undefined;
const prevHth = (currentIndex > 0) ? wrappedWordsArray[currentIndex - 1].offsetHeight : undefined;
const currTop = currentWord.offsetTop;
if (currentIndex > 0 && (prevTop + prevHth) <= currTop) {
const prevWord = currentIndex > 0 ? wrappedWordsArray[currentIndex - 1] : null;
// * prevBottom <= currTop means the next token starts a new line.
const prevBottom = currentIndex > 0 ? getBottomCached(prevWord) : undefined;
const currTop = getTopCached(currentWord);
const isNewLine = (currentIndex > 0) ? (prevBottom <= currTop) : false;
if (isNewLine) {
result.push(currentIndex);
}
return result;
Expand Down
34 changes: 8 additions & 26 deletions src/node/modules/positioning.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,35 +78,17 @@ export function isLastChildOfLastChild(element, rootElement) {
/**
* @this {Node}
*/
export function isLineChanged(current, next) {
// * (-1): Browser rounding fix (when converting mm to pixels).
const delta = this._DOM.getElementOffsetTop(next)
- this._DOM.getElementOffsetBottom(current);
export function isVerticalDrop(first, second) {
// * (-1): Browser subpixel rounding fix.
const firstBottom = this._DOM.getElementOffsetBottom(first);
const secondTop = this._DOM.getElementOffsetTop(second);
const delta = secondTop - firstBottom;
const vert = delta > (-2);
// const gor = this.getElementLeft(current) + this.getElementWidth(current) > this.getElementLeft(next);
return vert;
}

// TODO: isLineChanged vs isLineKept: можно сделать else? они противоположны
/**
* @this {Node}
*/
export function isLineKept(current, next) {
// * (-1): Browser rounding fix (when converting mm to pixels).
const currentBottom = this._DOM.getElementOffsetBottom(current);
const nextTop = this._DOM.getElementOffsetTop(next);
const delta = currentBottom - nextTop;
const vert = delta >= 2;
_isDebug(this) && console.group('isLineKept?')
_isDebug(this) && console.log(
'\n',
vert,
'\n',
'\n currentBottom', currentBottom, [current],
'\n nextTop', nextTop, [next],
_isDebug(this) && console.log('%c isVerticalDrop?', "font-weight:bold", vert,
'\n delta', delta,
'\n firstBottom', firstBottom, [first],
'\n secondTop', secondTop, [second],
);
_isDebug(this) && console.groupEnd('isLineKept?')
return vert;
}

Expand Down
24 changes: 1 addition & 23 deletions src/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,17 +302,6 @@ ${SELECTOR.cleanBottomCut} {

_serviceElementsStyle() {


// * - Firefox and inconsistent values of offset top for inline element
// * - Visually, the string fits, but the inline baseline gap below the string
// * causes a compensator + assertions.
// * 'display: inline-block' removes spaces between parts of the string,
const _makeInlineBlock = 'display: inline-block';
// * but it should leave the text inline in media print,
// * and inside text group.
const _keepInline = 'display: inline';


const screen = `
.null {
display: inline;
Expand Down Expand Up @@ -347,20 +336,12 @@ ${SELECTOR.textGroup} {
display: block;
}

${SELECTOR.textLine} {
${_makeInlineBlock};
}

${SELECTOR.textGroup} ${SELECTOR.textLine} {
${_keepInline};
}

${SELECTOR.complexTextBlock} {
display: block;
}

${SELECTOR.complexTextBlock} ${SELECTOR.complexTextBlock} {
${_keepInline};
display: inline;
}

${SELECTOR.printPageBreak} {
Expand All @@ -382,9 +363,6 @@ ${SELECTOR.printForcedPageBreak} {
break-after: page;
}

${SELECTOR.textLine} {
${_keepInline};
}
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# 6 lines are divided into 4 groups (2 lines first and last form a group)
lines_1_2 = '//html2pdf4doc-text-group[@data-child="0"]'
lines_3 = '//html2pdf4doc-text-group[@data-child="1"]'
lines_4 = '//span[@data-child="2"]'
lines_4 = '//html2pdf4doc-text-group[@data-child="2"]'
lines_5_6 = '//html2pdf4doc-text-group[@data-child="3"]'


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@

# On Windows / MacOS / Linux, different fonts and text are split differently.
# Therefore, here we only check the case with a standalone inline wrapper `<tt>`.
# We are checking the structure here when `inline_parent` is included in `complex-text-block`,
# We are checking the structure here when `inline_parent`(wrapped in group_wrapper),
# is included in `complex-text-block`,
# and in turn contains text in service wrappers `text-node` and `text-line`.
parent = '/div[@data-testid="test-block"]'
ctb = '/html2pdf4doc-complex-text-block'
inline_parent_part = '/tt[@data-child]'
# and each line is wrapped into a group
group_wrapper = '/html2pdf4doc-text-group[@data-child]'
inline_parent_part = '/tt'
inner_service_blocks = '/html2pdf4doc-text-node/html2pdf4doc-text-line'
tester = parent + ctb + inline_parent_part + inner_service_blocks
tester = parent + ctb + group_wrapper + inline_parent_part + inner_service_blocks

class Test(BaseCase):
def __init__(self, *args, **kwargs):
Expand Down
Loading