Skip to content

Commit 3fe3321

Browse files
authored
Merge pull request #21334 from calixteman/merge_images
Allow inserting an image as a new page when editing a PDF (bug 2032967)
2 parents 3e76bfd + 600986b commit 3fe3321

12 files changed

Lines changed: 971 additions & 153 deletions

File tree

src/core/annotation.js

Lines changed: 23 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
BASELINE_FACTOR,
2626
BBOX_INIT,
2727
F32_BBOX_INIT,
28-
FeatureTest,
2928
info,
3029
isArrayEqual,
3130
LINE_DESCENT_FACTOR,
@@ -62,7 +61,6 @@ import {
6261
} from "./default_appearance.js";
6362
import { DateFormats, TimeFormats } from "../shared/scripting_utils.js";
6463
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
65-
import { Stream, StringStream } from "./stream.js";
6664
import {
6765
stringToAsciiOrUTF16BE,
6866
stringToPDFString,
@@ -72,11 +70,13 @@ import { BaseStream } from "./base_stream.js";
7270
import { bidi } from "./bidi.js";
7371
import { Catalog } from "./catalog.js";
7472
import { ColorSpaceUtils } from "./colorspace_utils.js";
73+
import { createImage } from "./editor/pdf_images.js";
7574
import { FileSpec } from "./file_spec.js";
7675
import { JpegStream } from "./jpeg_stream.js";
7776
import { ObjectLoader } from "./object_loader.js";
7877
import { OperatorList } from "./operator_list.js";
7978
import { parseMarkedContentProps } from "./evaluator_utils.js";
79+
import { StringStream } from "./stream.js";
8080
import { XFAFactory } from "./xfa/factory.js";
8181

8282
class AnnotationFactory {
@@ -351,7 +351,7 @@ class AnnotationFactory {
351351
continue;
352352
}
353353
imagePromises ||= new Map();
354-
imagePromises.set(bitmapId, StampAnnotation.createImage(bitmap, xref));
354+
imagePromises.set(bitmapId, createImage(bitmap, xref));
355355
}
356356

357357
return imagePromises;
@@ -427,7 +427,10 @@ class AnnotationFactory {
427427
changes.put(imageRef, {
428428
data: imageStream,
429429
});
430-
image.imageStream = image.smaskStream = null;
430+
image.imageStream = null;
431+
image.imageRenderStream = null;
432+
image.smaskStream = null;
433+
image.smaskRenderStream = null;
431434
}
432435
promises.push(
433436
StampAnnotation.createNewAnnotation(xref, annotation, changes, {
@@ -522,12 +525,23 @@ class AnnotationFactory {
522525
? await imagePromises?.get(annotation.bitmapId)
523526
: null;
524527
if (image?.imageStream) {
525-
const { imageStream, smaskStream } = image;
526-
if (smaskStream) {
527-
imageStream.dict.set("SMask", smaskStream);
528+
const {
529+
imageStream,
530+
imageRenderStream,
531+
smaskStream,
532+
smaskRenderStream,
533+
} = image;
534+
const imageRef =
535+
imageRenderStream ||
536+
new JpegStream(imageStream, imageStream.length);
537+
if (smaskStream || smaskRenderStream) {
538+
imageRef.dict.set("SMask", smaskRenderStream || smaskStream);
528539
}
529-
image.imageRef = new JpegStream(imageStream, imageStream.length);
530-
image.imageStream = image.smaskStream = null;
540+
image.imageRef = imageRef;
541+
image.imageStream = null;
542+
image.imageRenderStream = null;
543+
image.smaskStream = null;
544+
image.smaskRenderStream = null;
531545
}
532546
promises.push(
533547
StampAnnotation.createNewPrintAnnotation(
@@ -5097,82 +5111,6 @@ class StampAnnotation extends MarkupAnnotation {
50975111
return !modifiedIds?.has(this.data.id);
50985112
}
50995113

5100-
static async createImage(bitmap, xref) {
5101-
// TODO: when printing, we could have a specific internal colorspace
5102-
// (e.g. something like DeviceRGBA) in order avoid any conversion (i.e. no
5103-
// jpeg, no rgba to rgb conversion, etc...)
5104-
5105-
const { width, height } = bitmap;
5106-
const canvas = new OffscreenCanvas(width, height);
5107-
const ctx = canvas.getContext("2d", { alpha: true });
5108-
5109-
// Draw the image and get the data in order to extract the transparency.
5110-
ctx.drawImage(bitmap, 0, 0);
5111-
const data = ctx.getImageData(0, 0, width, height).data;
5112-
const buf32 = new Uint32Array(data.buffer);
5113-
const hasAlpha = buf32.some(
5114-
FeatureTest.isLittleEndian
5115-
? x => x >>> 24 !== 0xff
5116-
: x => (x & 0xff) !== 0xff
5117-
);
5118-
5119-
if (hasAlpha) {
5120-
// Redraw the image on a white background in order to remove the thin gray
5121-
// line which can appear when exporting to jpeg.
5122-
ctx.fillStyle = "white";
5123-
ctx.fillRect(0, 0, width, height);
5124-
ctx.drawImage(bitmap, 0, 0);
5125-
}
5126-
5127-
const jpegBytesPromise = canvas
5128-
.convertToBlob({ type: "image/jpeg", quality: 1 })
5129-
.then(blob => blob.bytes());
5130-
5131-
const xobjectName = Name.get("XObject");
5132-
const imageName = Name.get("Image");
5133-
const image = new Dict(xref);
5134-
image.set("Type", xobjectName);
5135-
image.set("Subtype", imageName);
5136-
image.set("BitsPerComponent", 8);
5137-
image.setIfName("ColorSpace", "DeviceRGB");
5138-
image.setIfName("Filter", "DCTDecode");
5139-
image.set("BBox", [0, 0, width, height]);
5140-
image.set("Width", width);
5141-
image.set("Height", height);
5142-
5143-
let smaskStream = null;
5144-
if (hasAlpha) {
5145-
const alphaBuffer = new Uint8Array(buf32.length);
5146-
if (FeatureTest.isLittleEndian) {
5147-
for (let i = 0, ii = buf32.length; i < ii; i++) {
5148-
alphaBuffer[i] = buf32[i] >>> 24;
5149-
}
5150-
} else {
5151-
for (let i = 0, ii = buf32.length; i < ii; i++) {
5152-
alphaBuffer[i] = buf32[i] & 0xff;
5153-
}
5154-
}
5155-
5156-
const smask = new Dict(xref);
5157-
smask.set("Type", xobjectName);
5158-
smask.set("Subtype", imageName);
5159-
smask.set("BitsPerComponent", 8);
5160-
smask.setIfName("ColorSpace", "DeviceGray");
5161-
smask.set("Width", width);
5162-
smask.set("Height", height);
5163-
5164-
smaskStream = new Stream(alphaBuffer, 0, 0, smask);
5165-
}
5166-
const imageStream = new Stream(await jpegBytesPromise, 0, 0, image);
5167-
5168-
return {
5169-
imageStream,
5170-
smaskStream,
5171-
width,
5172-
height,
5173-
};
5174-
}
5175-
51765114
static createNewDict(annotation, xref, { apRef, ap }) {
51775115
const { date, oldAnnotation, rect, rotation, user } = annotation;
51785116
const stamp = oldAnnotation || new Dict(xref);

src/core/document.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2154,4 +2154,4 @@ class PDFDocument {
21542154
}
21552155
}
21562156

2157-
export { Page, PDFDocument };
2157+
export { LETTER_SIZE_MEDIABOX, Page, PDFDocument };

0 commit comments

Comments
 (0)