Support Jpeg Exif Orientation (#1482)
Co-authored-by: Franklin Strube <fstrube@morevang.com> Co-authored-by: Luiz Américo Pereira Câmara <blikblum@users.noreply.github.com>
@ -5,6 +5,8 @@
|
||||
- Add support for PDF/A-1b, PDF/A-1a, PDF/A-2b, PDF/A-2a, PDF/A-3b, PDF/A-3a
|
||||
- Update crypto-js to v4.2.0 (properly fix security issue)
|
||||
|
||||
- Add support for EXIF orientation on JPEG images (#626 and #1353)
|
||||
|
||||
### [v0.13.0] - 2021-10-24
|
||||
|
||||
- Add tiling pattern support
|
||||
|
||||
@ -7,21 +7,22 @@ rendered at the current point in the text flow (below the last line of text).
|
||||
Otherwise, it is positioned absolutely at the specified point. The image will
|
||||
be scaled according to the following options.
|
||||
|
||||
* Neither `width` or `height` provided - image is rendered at full size
|
||||
* `width` provided but not `height` - image is scaled proportionally to fit in the provided `width`
|
||||
* `height` provided but not `width` - image is scaled proportionally to fit in the provided `height`
|
||||
* Both `width` and `height` provided - image is stretched to the dimensions provided
|
||||
* `scale` factor provided - image is scaled proportionally by the provided scale factor
|
||||
* `fit` array provided - image is scaled proportionally to fit within the passed width and height
|
||||
* `cover` array provided - image is scaled proportionally to completely cover the rectangle defined by the passed width and height
|
||||
* `link` - a URL to link this image to (shortcut to create an annotation)
|
||||
* `goTo` - go to anchor (shortcut to create an annotation)
|
||||
* `destination` - create anchor to this image
|
||||
- Neither `width` or `height` provided - image is rendered at full size
|
||||
- `width` provided but not `height` - image is scaled proportionally to fit in the provided `width`
|
||||
- `height` provided but not `width` - image is scaled proportionally to fit in the provided `height`
|
||||
- Both `width` and `height` provided - image is stretched to the dimensions provided
|
||||
- `scale` factor provided - image is scaled proportionally by the provided scale factor
|
||||
- `fit` array provided - image is scaled proportionally to fit within the passed width and height
|
||||
- `cover` array provided - image is scaled proportionally to completely cover the rectangle defined by the passed width and height
|
||||
- `link` - a URL to link this image to (shortcut to create an annotation)
|
||||
- `goTo` - go to anchor (shortcut to create an annotation)
|
||||
- `destination` - create anchor to this image
|
||||
- `ignoreOrientation` - (true/false) ignore JPEG EXIF orientation. By default, images with JPEG EXIF orientation are properly rotated and/or flipped. Defaults to `false`, unless `ignoreOrientation` option set to `true` when creating the `PDFDocument` object (e.g. `new PDFDocument({ignoreOrientation: true})`)
|
||||
|
||||
When a `fit` or `cover` array is provided, PDFKit accepts these additional options:
|
||||
|
||||
* `align` - horizontally align the image, the possible values are `'left'`, `'center'` and `'right'`
|
||||
* `valign` - vertically align the image, the possible values are `'top'`, `'center'` and `'bottom'`
|
||||
- `align` - horizontally align the image, the possible values are `'left'`, `'center'` and `'right'`
|
||||
- `valign` - vertically align the image, the possible values are `'top'`, `'center'` and `'bottom'`
|
||||
|
||||
Here is an example showing some of these options.
|
||||
|
||||
@ -48,11 +49,11 @@ Here is an example showing some of these options.
|
||||
.rect(430, 15, 100, 100).stroke()
|
||||
.text('Centered', 430, 0);
|
||||
|
||||
* * *
|
||||
---
|
||||
|
||||
This example produces the following output:
|
||||
|
||||

|
||||

|
||||
|
||||
That is all there is to adding images to your PDF documents with PDFKit. Now
|
||||
let's look at adding outlines.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import exif from 'jpeg-exif';
|
||||
|
||||
const MARKERS = [
|
||||
0xffc0,
|
||||
0xffc1,
|
||||
@ -31,6 +33,9 @@ class JPEG {
|
||||
throw 'SOI not found in JPEG';
|
||||
}
|
||||
|
||||
// Parse the EXIF orientation
|
||||
this.orientation = exif.fromBuffer(this.data).Orientation || 1;
|
||||
|
||||
let pos = 2;
|
||||
while (pos < this.data.length) {
|
||||
marker = this.data.readUInt16BE(pos);
|
||||
|
||||
@ -7,12 +7,17 @@ export default {
|
||||
},
|
||||
|
||||
image(src, x, y, options = {}) {
|
||||
let bh, bp, bw, image, ip, left, left1;
|
||||
let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
|
||||
if (typeof x === 'object') {
|
||||
options = x;
|
||||
x = null;
|
||||
}
|
||||
|
||||
// Ignore orientation based on document options or image options
|
||||
const ignoreOrientation =
|
||||
options.ignoreOrientation ||
|
||||
(options.ignoreOrientation !== false && this.options.ignoreOrientation);
|
||||
|
||||
x = (left = x != null ? x : options.x) != null ? left : this.x;
|
||||
y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
|
||||
|
||||
@ -36,24 +41,31 @@ export default {
|
||||
this.page.xobjects[image.label] = image.obj;
|
||||
}
|
||||
|
||||
let w = options.width || image.width;
|
||||
let h = options.height || image.height;
|
||||
let { width, height } = image;
|
||||
|
||||
// If EXIF orientation calls for it, swap width and height
|
||||
if (!ignoreOrientation && image.orientation > 4) {
|
||||
[width, height] = [height, width];
|
||||
}
|
||||
|
||||
let w = options.width || width;
|
||||
let h = options.height || height;
|
||||
|
||||
if (options.width && !options.height) {
|
||||
const wp = w / image.width;
|
||||
w = image.width * wp;
|
||||
h = image.height * wp;
|
||||
const wp = w / width;
|
||||
w = width * wp;
|
||||
h = height * wp;
|
||||
} else if (options.height && !options.width) {
|
||||
const hp = h / image.height;
|
||||
w = image.width * hp;
|
||||
h = image.height * hp;
|
||||
const hp = h / height;
|
||||
w = width * hp;
|
||||
h = height * hp;
|
||||
} else if (options.scale) {
|
||||
w = image.width * options.scale;
|
||||
h = image.height * options.scale;
|
||||
w = width * options.scale;
|
||||
h = height * options.scale;
|
||||
} else if (options.fit) {
|
||||
[bw, bh] = options.fit;
|
||||
bp = bw / bh;
|
||||
ip = image.width / image.height;
|
||||
ip = width / height;
|
||||
if (ip > bp) {
|
||||
w = bw;
|
||||
h = bw / ip;
|
||||
@ -64,7 +76,7 @@ export default {
|
||||
} else if (options.cover) {
|
||||
[bw, bh] = options.cover;
|
||||
bp = bw / bh;
|
||||
ip = image.width / image.height;
|
||||
ip = width / height;
|
||||
if (ip > bp) {
|
||||
h = bh;
|
||||
w = bh * ip;
|
||||
@ -88,6 +100,91 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreOrientation) {
|
||||
switch (image.orientation) {
|
||||
// No orientation (need to flip image, though, because of the default transform matrix on the document)
|
||||
default:
|
||||
case 1:
|
||||
h = -h;
|
||||
y -= h;
|
||||
|
||||
rotateAngle = 0;
|
||||
break;
|
||||
// Flip Horizontal
|
||||
case 2:
|
||||
w = -w;
|
||||
h = -h;
|
||||
x -= w;
|
||||
y -= h;
|
||||
|
||||
rotateAngle = 0;
|
||||
break;
|
||||
// Rotate 180 degrees
|
||||
case 3:
|
||||
originX = x;
|
||||
originY = y;
|
||||
|
||||
h = -h;
|
||||
x -= w;
|
||||
|
||||
rotateAngle = 180;
|
||||
break;
|
||||
// Flip vertical
|
||||
case 4:
|
||||
// Do nothing, image will be flipped
|
||||
|
||||
break;
|
||||
// Flip horizontally and rotate 270 degrees CW
|
||||
case 5:
|
||||
originX = x;
|
||||
originY = y;
|
||||
|
||||
[w, h] = [h, w];
|
||||
y -= h;
|
||||
|
||||
rotateAngle = 90;
|
||||
break;
|
||||
// Rotate 90 degrees CW
|
||||
case 6:
|
||||
originX = x;
|
||||
originY = y;
|
||||
|
||||
[w, h] = [h, w];
|
||||
h = -h;
|
||||
|
||||
rotateAngle = 90;
|
||||
break;
|
||||
// Flip horizontally and rotate 90 degrees CW
|
||||
case 7:
|
||||
originX = x;
|
||||
originY = y;
|
||||
|
||||
[w, h] = [h, w];
|
||||
h = -h;
|
||||
w = -w;
|
||||
x -= w;
|
||||
|
||||
rotateAngle = 90;
|
||||
break;
|
||||
// Rotate 270 degrees CW
|
||||
case 8:
|
||||
originX = x;
|
||||
originY = y;
|
||||
|
||||
[w, h] = [h, w];
|
||||
h = -h;
|
||||
x -= w;
|
||||
y -= h;
|
||||
|
||||
rotateAngle = -90;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
h = -h;
|
||||
y -= h;
|
||||
rotateAngle = 0;
|
||||
}
|
||||
|
||||
// create link annotations if the link option is given
|
||||
if (options.link != null) {
|
||||
this.link(x, y, w, h, options.link);
|
||||
@ -105,7 +202,14 @@ export default {
|
||||
}
|
||||
|
||||
this.save();
|
||||
this.transform(w, 0, 0, -h, x, y + h);
|
||||
|
||||
if (rotateAngle) {
|
||||
this.rotate(rotateAngle, {
|
||||
origin: [originX, originY]
|
||||
});
|
||||
}
|
||||
|
||||
this.transform(w, 0, 0, h, x, y);
|
||||
this.addContent(`/${image.label} Do`);
|
||||
this.restore();
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0",
|
||||
"fontkit": "^1.8.1",
|
||||
"jpeg-exif": "^1.1.4",
|
||||
"linebreak": "^1.0.2",
|
||||
"png-js": "^1.0.0"
|
||||
},
|
||||
|
||||
@ -11,7 +11,8 @@ const external = [
|
||||
'linebreak',
|
||||
'png-js',
|
||||
'crypto-js',
|
||||
'saslprep'
|
||||
'saslprep',
|
||||
'jpeg-exif',
|
||||
];
|
||||
|
||||
export default [
|
||||
|
||||
BIN
tests/images/orientation-1.jpeg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
tests/images/orientation-2.jpeg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
tests/images/orientation-3.jpeg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
tests/images/orientation-4.jpeg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
tests/images/orientation-5.jpeg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
tests/images/orientation-6.jpeg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
tests/images/orientation-7.jpeg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
tests/images/orientation-8.jpeg
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 772 KiB |
|
After Width: | Height: | Size: 786 KiB |
687
tests/visual/images.spec.js
Normal file
@ -0,0 +1,687 @@
|
||||
import { runDocTest } from './helpers';
|
||||
|
||||
describe('images', function() {
|
||||
test('orientation', function() {
|
||||
return runDocTest(function(doc) {
|
||||
doc.font('tests/fonts/Roboto-Regular.ttf');
|
||||
doc.fill('black');
|
||||
doc.fillColor('black');
|
||||
doc.fillOpacity(undefined);
|
||||
doc.fontSize(12);
|
||||
|
||||
doc.text(
|
||||
'EXIF orientation data may be present on some JPEG images.There are 8 exif orientation values:',
|
||||
40,
|
||||
10,
|
||||
{
|
||||
lineBreak: false
|
||||
}
|
||||
);
|
||||
|
||||
doc.text('1 - No orientation needed', 40, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 40, 44.0625, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 320, 44.0625, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('2 - Flip horizonatal', 40, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 40, 138.125, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 320, 138.125, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('3 - Rotate 180 degrees', 40, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 40, 232.1875, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 320, 232.1875, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('4 - Flip vertically', 40, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 40, 326.25, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 320, 326.25, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('5 - Flip horizontally and rotate 270 degrees CW', 40, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 40, 420.3125, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 320, 420.3125, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('6 - Rotate 90 degrees CW', 40, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 40, 514.375, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 320, 514.375, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('7 - Flip horizontally and rotate 90 degrees CW', 40, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 40, 608.4375, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 320, 608.4375, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('8 - Rotate 270 degrees CW', 40, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 40, 702.5, {
|
||||
height: 80,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 320, 702.5, {
|
||||
height: 80
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('orientation - with cover and alignment', function() {
|
||||
return runDocTest(function(doc) {
|
||||
let options = {
|
||||
align: 'center',
|
||||
cover: [60, 60],
|
||||
valign: 'center'
|
||||
};
|
||||
|
||||
doc.font('tests/fonts/Roboto-Regular.ttf');
|
||||
doc.fill('black');
|
||||
doc.fillColor('black');
|
||||
doc.fillOpacity(undefined);
|
||||
doc.fontSize(12);
|
||||
|
||||
doc.text(
|
||||
'Images with EXIF orientation should properly align with fit/cover options:',
|
||||
40,
|
||||
10,
|
||||
{
|
||||
lineBreak: false
|
||||
}
|
||||
);
|
||||
|
||||
// Orientation 1
|
||||
doc.text('1 - No orientation needed', 40, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 40, 44.0625, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 44.0625, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 320, 44.0625, options);
|
||||
|
||||
doc.rect(320, 44.0625, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 2
|
||||
doc.text('2 - Flip horizonatal', 40, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 40, 138.125, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 138.125, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 320, 138.125, options);
|
||||
|
||||
doc.rect(320, 138.125, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 3
|
||||
doc.text('3 - Rotate 180 degrees', 40, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 40, 232.1875, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 232.1875, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 320, 232.1875, options);
|
||||
|
||||
doc.rect(320, 232.1875, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 4
|
||||
doc.text('4 - Flip vertically', 40, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 40, 326.25, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 326.25, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 320, 326.25, options);
|
||||
|
||||
doc.rect(320, 326.25, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 5
|
||||
doc.text('5 - Flip horizontally and rotate 270 degrees CW', 40, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 40, 420.3125, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 420.3125, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 320, 420.3125, options);
|
||||
|
||||
doc.rect(320, 420.3125, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 6
|
||||
doc.text('6 - Rotate 90 degrees CW', 40, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 40, 514.375, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 514.375, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 320, 514.375, options);
|
||||
|
||||
doc.rect(320, 514.375, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 7
|
||||
doc.text('7 - Flip horizontally and rotate 90 degrees CW', 40, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 40, 608.4375, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 608.4375, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 320, 608.4375, options);
|
||||
|
||||
doc.rect(320, 608.4375, 60, 60).stroke('red');
|
||||
|
||||
// Orientation 8
|
||||
doc.text('8 - Rotate 270 degrees CW', 40, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 40, 702.5, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 702.5, 60, 60).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 320, 702.5, options);
|
||||
|
||||
doc.rect(320, 702.5, 60, 60).stroke('red');
|
||||
});
|
||||
});
|
||||
|
||||
test('orientation - with fit and alignment', function() {
|
||||
return runDocTest(function(doc) {
|
||||
let options = {
|
||||
align: 'center',
|
||||
fit: [80, 80],
|
||||
valign: 'center'
|
||||
};
|
||||
|
||||
doc.font('tests/fonts/Roboto-Regular.ttf');
|
||||
doc.fill('black');
|
||||
doc.fillColor('black');
|
||||
doc.fillOpacity(undefined);
|
||||
doc.fontSize(12);
|
||||
|
||||
doc.text(
|
||||
'Images with EXIF orientation should properly align with fit/cover options:',
|
||||
40,
|
||||
10,
|
||||
{
|
||||
lineBreak: false
|
||||
}
|
||||
);
|
||||
|
||||
// Orientation 1
|
||||
doc.text('1 - No orientation needed', 40, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 40, 44.0625, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 44.0625, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 320, 44.0625, options);
|
||||
|
||||
doc.rect(320, 44.0625, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 2
|
||||
doc.text('2 - Flip horizonatal', 40, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 40, 138.125, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 138.125, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 320, 138.125, options);
|
||||
|
||||
doc.rect(320, 138.125, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 3
|
||||
doc.text('3 - Rotate 180 degrees', 40, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 40, 232.1875, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 232.1875, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 320, 232.1875, options);
|
||||
|
||||
doc.rect(320, 232.1875, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 4
|
||||
doc.text('4 - Flip vertically', 40, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 40, 326.25, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 326.25, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 320, 326.25, options);
|
||||
|
||||
doc.rect(320, 326.25, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 5
|
||||
doc.text('5 - Flip horizontally and rotate 270 degrees CW', 40, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 40, 420.3125, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 420.3125, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 320, 420.3125, options);
|
||||
|
||||
doc.rect(320, 420.3125, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 6
|
||||
doc.text('6 - Rotate 90 degrees CW', 40, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 40, 514.375, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 514.375, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 320, 514.375, options);
|
||||
|
||||
doc.rect(320, 514.375, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 7
|
||||
doc.text('7 - Flip horizontally and rotate 90 degrees CW', 40, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 40, 608.4375, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 608.4375, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 320, 608.4375, options);
|
||||
|
||||
doc.rect(320, 608.4375, 80, 80).stroke('red');
|
||||
|
||||
// Orientation 8
|
||||
doc.text('8 - Rotate 270 degrees CW', 40, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 40, 702.5, {
|
||||
...options,
|
||||
ignoreOrientation: true
|
||||
});
|
||||
|
||||
doc.rect(40, 702.5, 80, 80).stroke('red');
|
||||
|
||||
doc.text('(output)', 320, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 320, 702.5, options);
|
||||
|
||||
doc.rect(320, 702.5, 80, 80).stroke('red');
|
||||
});
|
||||
});
|
||||
|
||||
test('orientation - document option', function() {
|
||||
let options = {
|
||||
ignoreOrientation: true
|
||||
};
|
||||
|
||||
return runDocTest(options, function(doc) {
|
||||
doc.font('tests/fonts/Roboto-Regular.ttf');
|
||||
doc.fill('black');
|
||||
doc.fillColor('black');
|
||||
doc.fillOpacity(undefined);
|
||||
doc.fontSize(12);
|
||||
|
||||
doc.text(
|
||||
'EXIF orientation support can be enabled on the entire PDFDocument:',
|
||||
40,
|
||||
10,
|
||||
{
|
||||
lineBreak: false
|
||||
}
|
||||
);
|
||||
|
||||
doc.text('1 - No orientation needed', 40, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 40, 44.0625, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 30, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-1.jpeg', 320, 44.0625, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('2 - Flip horizonatal', 40, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 40, 138.125, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 124.0625, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-2.jpeg', 320, 138.125, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('3 - Rotate 180 degrees', 40, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 40, 232.1875, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 218.125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-3.jpeg', 320, 232.1875, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('4 - Flip vertically', 40, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 40, 326.25, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 312.1875, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-4.jpeg', 320, 326.25, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('5 - Flip horizontally and rotate 270 degrees CW', 40, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 40, 420.3125, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 406.25, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-5.jpeg', 320, 420.3125, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('6 - Rotate 90 degrees CW', 40, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 40, 514.375, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 500.3125, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-6.jpeg', 320, 514.375, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('7 - Flip horizontally and rotate 90 degrees CW', 40, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 40, 608.4375, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 594.375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-7.jpeg', 320, 608.4375, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
|
||||
doc.text('8 - Rotate 270 degrees CW', 40, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 40, 702.5, {
|
||||
height: 80
|
||||
});
|
||||
|
||||
doc.text('(output)', 320, 688.4375, {
|
||||
lineBreak: false
|
||||
});
|
||||
|
||||
doc.image('tests/images/orientation-8.jpeg', 320, 702.5, {
|
||||
height: 80,
|
||||
ignoreOrientation: false
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -4976,6 +4976,11 @@ jest@^29.4.3:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^29.4.3"
|
||||
|
||||
jpeg-exif@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-exif/-/jpeg-exif-1.1.4.tgz#781a65b6cd74f62cb1c493511020f8d3577a1c2b"
|
||||
integrity sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==
|
||||
|
||||
js-stringify@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db"
|
||||
|
||||