refactoring, numbering plan
This commit is contained in:
1 parent
b5279434b2
commit
3b04039aa9
47 files changed
+3485
-953
No files matched your search
@@ -0,0 +1,133 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { degrees, PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { TextOverlayError } from '../../src/lib/pdf-errors';
|
||||
import {
|
||||
TEXT_OVERLAY_MARGIN,
|
||||
drawTextOverlay,
|
||||
getRotatedBoxPlacement
|
||||
} from '../../src/lib/pdf-text-overlay';
|
||||
import { getPageText, getPageTextAngle, getPageTextMatrix } from '../e2e/helpers';
|
||||
|
||||
const PAGE_WIDTH = 420;
|
||||
const PAGE_HEIGHT = 594;
|
||||
const MARGIN = 28;
|
||||
|
||||
/**
|
||||
* Maps a point from default user space to the displayed page. Viewers rotate the
|
||||
* content clockwise by `/Rotate`, so this is the forward transformation.
|
||||
*/
|
||||
const toVisualPoint = (
|
||||
point: { x: number; y: number },
|
||||
rotation: number,
|
||||
width: number,
|
||||
height: number
|
||||
) => {
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
return { x: point.y, y: width - point.x };
|
||||
case 180:
|
||||
return { x: width - point.x, y: height - point.y };
|
||||
case 270:
|
||||
return { x: height - point.y, y: point.x };
|
||||
default:
|
||||
return point;
|
||||
}
|
||||
};
|
||||
|
||||
const createPageWithMark = async (rotation: number) => {
|
||||
const document = await PDFDocument.create();
|
||||
const font = await document.embedFont(StandardFonts.Helvetica);
|
||||
const page = document.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
||||
page.setRotation(degrees(rotation));
|
||||
drawTextOverlay(page, {
|
||||
font,
|
||||
text: 'Bl. 1',
|
||||
size: 11,
|
||||
color: rgb(0.1, 0.1, 0.1),
|
||||
position: 'bottom-center',
|
||||
margin: MARGIN,
|
||||
compensatePageRotation: true
|
||||
});
|
||||
|
||||
return PDFDocument.load(await document.save());
|
||||
};
|
||||
|
||||
for (const rotation of [0, 90, 180, 270]) {
|
||||
test(`bottom-center mark stays at the visual bottom on a /Rotate ${rotation} page`, async () => {
|
||||
const output = await createPageWithMark(rotation);
|
||||
const matrix = getPageTextMatrix(output, 0);
|
||||
const visualWidth = rotation % 180 === 0 ? PAGE_WIDTH : PAGE_HEIGHT;
|
||||
const origin = toVisualPoint({ x: matrix.x, y: matrix.y }, rotation, PAGE_WIDTH, PAGE_HEIGHT);
|
||||
|
||||
// Horizontally centred on the displayed page.
|
||||
expect(Math.abs(origin.x - visualWidth / 2)).toBeLessThan(30);
|
||||
// Close to the bottom margin (the baseline sits a few points above it).
|
||||
expect(origin.y).toBeGreaterThan(MARGIN - 2);
|
||||
expect(origin.y).toBeLessThan(MARGIN + 20);
|
||||
// The glyphs read horizontally: the drawn angle cancels the page rotation.
|
||||
expect(Math.round(getPageTextAngle(matrix))).toBe(rotation);
|
||||
expect(getPageText(output, 0)).toBe('Bl. 1');
|
||||
});
|
||||
}
|
||||
|
||||
test('a mark that does not fit raises a TextOverlayError instead of shrinking forever', async () => {
|
||||
const document = await PDFDocument.create();
|
||||
const font = await document.embedFont(StandardFonts.Helvetica);
|
||||
const page = document.addPage([120, 160]);
|
||||
|
||||
expect(() =>
|
||||
drawTextOverlay(page, {
|
||||
font,
|
||||
text: 'Ein sehr langer Stempeltext, der nicht auf die Seite passt',
|
||||
size: 18,
|
||||
color: rgb(0, 0, 0),
|
||||
position: 'bottom-center',
|
||||
margin: MARGIN,
|
||||
framed: true
|
||||
})
|
||||
).toThrow(TextOverlayError);
|
||||
});
|
||||
|
||||
test('characters the standard font cannot encode are reported as such', async () => {
|
||||
const document = await PDFDocument.create();
|
||||
const font = await document.embedFont(StandardFonts.Helvetica);
|
||||
const page = document.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
||||
|
||||
let thrown: unknown;
|
||||
try {
|
||||
drawTextOverlay(page, {
|
||||
font,
|
||||
text: 'Beschluss \u{1F600}',
|
||||
size: 11,
|
||||
color: rgb(0, 0, 0),
|
||||
position: 'top-right',
|
||||
margin: MARGIN
|
||||
});
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
|
||||
expect(thrown).toBeInstanceOf(TextOverlayError);
|
||||
expect((thrown as Error).message).toContain('nicht dargestellt werden');
|
||||
});
|
||||
|
||||
test('a rotated mark keeps its bounding box inside the page', async () => {
|
||||
const boxWidth = 200;
|
||||
const boxHeight = 40;
|
||||
|
||||
for (const angle of [0, 45, 90, 135]) {
|
||||
const placement = getRotatedBoxPlacement(
|
||||
PAGE_WIDTH,
|
||||
PAGE_HEIGHT,
|
||||
boxWidth,
|
||||
boxHeight,
|
||||
'bottom-center',
|
||||
angle,
|
||||
TEXT_OVERLAY_MARGIN
|
||||
);
|
||||
|
||||
expect(placement.x).toBeGreaterThanOrEqual(TEXT_OVERLAY_MARGIN - 0.001);
|
||||
expect(placement.y).toBeGreaterThanOrEqual(TEXT_OVERLAY_MARGIN - 0.001);
|
||||
expect(placement).not.toHaveProperty('cosine');
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user