Skip to content

Commit

Permalink
🐛 Prevent breaking text before the first row
Browse files Browse the repository at this point in the history
The `layoutText` function returns a frame and optionally a remainder
containing the rows that did not fit into the available space.

If even the first row did not fit, this function would return an empty
frame and a remainder containing all the content. This would result in
rendering an empty frame. This empty frame is not only redundant, it
could even contain a misleading anchor.

This commit prevents this situation by preventing a break before the
first row of text.
  • Loading branch information
ralfstx committed Oct 3, 2023
1 parent f05eed6 commit 122ddfa
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [0.5.4] - Unreleased

### Fixed

- Text in a text block will no longer break before the first row, which
would result in an empty frame.

## [0.5.3] - 2023-09-28

### Fixed
Expand Down
6 changes: 4 additions & 2 deletions src/layout-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ function layoutText(block: TextBlock, box: Box, doc: Document) {
const { row, objects: rowObjects, remainder } = layoutResult;

if (row.height > remainingSpace.height) {
// This row doesn't fit in the remaining space. Break here if possible.
if (block.breakInside !== 'avoid') {
// This row doesn't fit in the remaining space. Break here if
// possible. Do not break before the first row to avoid returning
// and empty frame.
if (block.breakInside !== 'avoid' && rows.length) {
break;
}
}
Expand Down
29 changes: 26 additions & 3 deletions test/layout-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Box } from '../src/box.js';
import { Document } from '../src/document.js';
import { layoutTextContent } from '../src/layout-text.js';
import { paperSizes } from '../src/page-sizes.js';
import { fakeFont, range, span } from './test-utils.js';
import { extractTextRows, fakeFont, range, span } from './test-utils.js';

const { objectContaining } = expect;

Expand Down Expand Up @@ -248,8 +248,30 @@ describe('layout', () => {
const text = [span(longText, { fontSize: 20 })];
const block = { text };

const { remainder } = layoutTextContent(block, box, doc);
const { frame, remainder } = layoutTextContent(block, box, doc);

expect(extractTextRows(frame).join()).toMatch(/^foo.*foo$/);
expect(remainder).toEqual({
text: [
{
text: expect.stringMatching(/^foo.*foo$/),
attrs: expect.objectContaining({ fontSize: 20 }),
},
],
});
});

it('does not break text before the first line', () => {
box.height = 10;
const longText = range(100)
.map(() => 'foo')
.join(' ');
const text = [span(longText, { fontSize: 20 })];
const block = { text };

const { frame, remainder } = layoutTextContent(block, box, doc);

expect(extractTextRows(frame).join()).toMatch(/^foo.*foo$/);
expect(remainder).toEqual({
text: [
{
Expand All @@ -268,8 +290,9 @@ describe('layout', () => {
const text = [span(longText, { fontSize: 20 })];
const block = { text, breakInside: 'avoid' as const };

const { remainder } = layoutTextContent(block, box, doc);
const { frame, remainder } = layoutTextContent(block, box, doc);

expect(extractTextRows(frame).join()).toMatch(/^foo.*foo$/);
expect(remainder).toBeUndefined();
});
});
Expand Down
15 changes: 8 additions & 7 deletions test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,14 @@ export function fakePDFPage(document?: PDFDocument): PDFPage {
export function extractTextRows(frame: Partial<Frame>) {
const lines = [] as string[];
frame.children?.forEach((child) => {
child.objects?.forEach((obj) => {
if (obj.type === 'text') {
obj.rows.forEach((row) => {
lines.push(row.segments.map((s) => s.text).join(', '));
});
}
});
extractTextRows(child).forEach((line) => lines.push(line));
});
frame.objects?.forEach((obj) => {
if (obj.type === 'text') {
obj.rows.forEach((row) => {
lines.push(row.segments.map((s) => s.text).join(', '));
});
}
});
return lines;
}
Expand Down

0 comments on commit 122ddfa

Please sign in to comment.