I am trying to affix a digital signature on a pdf document.
I am able to affix the signature on the pdf but the signature is not visible. I tried a lot looked at so many codes (How to make a signature visible on a document) making signature visible but somehow not able to do .Here is the code which i am using to affix the signature on the pdf.
String inputPDFlocation = INPUT_PDF_BASEPATH + inputPDFfilename;
String outputPDFlocation = OUTPUT_PDF_BASEPATH + inputPDFfilename;
PdfReader reader = new PdfReader(inputPDFlocation); // input PDF
PdfStamper stamper = PdfStamper.createSignature(reader, new FileOutputStream(outputPDFlocation), '\0', null, true);
// Finding the size of the page, and determining the location of signature:
Rectangle cropBox = reader.getCropBox(1); // Gets the 1st page
float width = 78;
float height = 34;
Rectangle rectangle;
// Top left
rectangle = new Rectangle(cropBox.getLeft(), cropBox.getTop(height), cropBox.getLeft(width), cropBox.getTop());
// Creating the appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason(SIGNATURE_REASON);
sap.setLocation(SIGNATURE_LOCATION);
sap.setVisibleSignature(rectangle, 1, sap.getNewSigName()); // new Rectangle(36, 748, 144, 780)
ExternalDigest externalDigest = new BouncyCastleDigest();
int estimatedSize = 8192;
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(sap.getReason());
dic.setLocation(sap.getLocation());
dic.setSignatureCreator(sap.getSignatureCreator());
dic.setContact(sap.getContact());
// Adding a buffer between the signature generation and the certificate start valid time.
Calendar cal = sap.getSignDate();
cal.add(Calendar.MINUTE, 1);
dic.setDate(new PdfDate(cal)); // time-stamp will over-rule this
sap.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(estimatedSize * 2 + 2));
sap.preClose(exc);
String hashAlgorithm = DigestAlgorithms.SHA256;
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
//convert the byte to hex format method 1
StringBuffer sb = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
sb.append(Integer.toString((hash[i] & 0xff) + 0x100, 16).substring(1));
}
// 5. Setting Document hash
esign.setInput(sb.toString());
StringWriter eSignXML = new StringWriter();
try {
JAXBContext.newInstance(Esign.class).createMarshaller().marshal(esign, eSignXML);
} catch (Exception ex) {
logger.error("There was an error converting <Esign> XML to String: {}", ex.getMessage());
}
// 7. Get the digital signer, and sign the XML
DigitalSigner ds = getDigitalSigner();
if (ds == null) {
return eSignResponse;
}
String signedEsignXML = ds.signXML(eSignXML.toString(), true);
// 8. Make a request
String responseXML = null;
Response response = HTTPClient.postRequestWithXmlPayloadOverTLS(eSignConfig.get(URL_ESIGN_SERVER), null, null, signedEsignXML, false);
if (response != null) {
if (Response.Status.Family.familyOf(response.getStatus()) == Response.Status.Family.SUCCESSFUL) {
responseXML = response.getEntity().toString();
}
}
// 9. Parse the response
EsignResp esignResp = XMLUtil.parseXML(responseXML, EsignResp.class);
if (esignResp != null) {
if (esignResp.getStatus() != null && esignResp.getStatus().equalsIgnoreCase("1")) {
eSignResponse.setStatus(1);
// If response is a SUCCESS, affix the signature ...
String pkcs7Response = esignResp.getPkcs7Response();
CMSSignedData cms = new CMSSignedData(Base64.decodeBase64(pkcs7Response.getBytes()));
byte[] paddedSig = new byte[estimatedSize];
System.arraycopy(cms.getEncoded(), 0, paddedSig, 0, cms.getEncoded().length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
sap.close(dic2);
}
You use a 0x0 rectangle for the visualization:
// Finding the size of the page, and determining the location of signature:
Rectangle cropBox = reader.getCropBox(1); // Gets the 1st page
float width = 0;
float height = 0;
Rectangle rectangle;
// Top left
rectangle = new Rectangle(cropBox.getLeft(), cropBox.getTop(height), cropBox.getLeft(width), cropBox.getTop());
[...]
sap.setVisibleSignature(rectangle, 1, sap.getNewSigName()); // new Rectangle(36, 748, 144, 780)
Unless you choose positive values for width and height, you will never see a signature visualization.
Related
I'm trying to add an empty signature field to an existing digitally signed pdf (certify signature).
I have a workflow where many users will sign the document (approval signature), the document is created with "n" empty signature fields, one for each user, our application first apply a invisible certify signature, then each user can sign the document in respective field, but due to changes unexpected in the workflow, other users might want to sign, so we want to add the respective empty signature field and then apply the signature.
I tried to add the empty field (a table with a cell event) to the certified document but when I want to add it and associate the field, it breaks the signature, I cannot make it work correctly.
Here is the methods used to sign,add signature field, and set the signature field options.
I don't know what I'm doing wrong.
public static String sign(SignRequest signRequest, File certificate, File unsignedDocument, File image, File icon)
throws FileNotFoundException, IOException, DocumentException, StreamParsingException, OCSPException,
OperatorException, URISyntaxException, WriterException, GeneralSecurityException, FontFormatException {
SignatureType sigType = Optional
.ofNullable(SignatureType.get(signRequest.getSignatureProperties().getSignatureType()))
.orElse(SignatureType.APPROVAL_SIGNATURE);
File signedDocument = File.createTempFile("signed",".pdf");
char[] pass = signRequest.getKeyStore().getPassword().toCharArray();
// Load certificate chain
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance("PKCS12", provider.getName());
ks.load(new FileInputStream(certificate.getAbsolutePath()), pass);
String alias = getAliasFromKeyStore(ks);
PrivateKey pk = (PrivateKey) ks.getKey(alias, pass);
Certificate[] chain = ks.getCertificateChain(alias);
// Creating the reader and the stamper
PdfReader reader = new PdfReader(FileUtils.openInputStream(unsignedDocument));
FileOutputStream os = new FileOutputStream(signedDocument);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
PdfSignatureAppearance appearance = null;
// Certify o approval signature (approval is the default signature type)
switch (sigType) {
case CERTIFY_SIGNATURE:
if (reader.getAcroFields().getSignatureNames().size() <= 0) {
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
signRequest, image, icon, Boolean.TRUE);
} else {
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
signRequest, image, icon, Boolean.FALSE);
}
break;
case APPROVAL_SIGNATURE:
default:
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain, signRequest,
image, icon, Boolean.FALSE);
break;
}
// Adding LTV (optional)
OcspClient ocspClient = null;
List<CrlClient> crlList = null;
if (signRequest.getSignatureProperties().getLtv() == Boolean.TRUE) {
ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));
CrlClient crlClient = new CrlClientOnline(chain);
crlList = new ArrayList<CrlClient>();
crlList.add(crlClient);
}
// Adding timestamp (optional)
TSAClient tsaClient = null;
if (signRequest.getTimestamp() != null
&& StringUtils.isNotBlank(signRequest.getTimestamp().getUrl())) {
tsaClient = new TSAClientBouncyCastle(signRequest.getTimestamp().getUrl(),
signRequest.getTimestamp().getUser(), signRequest.getTimestamp().getPassword());
}
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, signtRequest.getSignatureProperties().getAlgorithm(),
provider.getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature
.signDetached(appearance, digest, pks, chain, crlList, ocspClient, tsaClient,
calculateEstimatedSize(chain, ocspClient, tsaClient, crlList, getEstimatedSizeBonus()), CryptoStandard.CMS);
return signedDocument.getAbsolutePath();
}
private static PdfSignatureAppearance setSignatureFieldOptions(PdfSignatureAppearance appearance, PdfReader reader, Certificate[] chain, SignRequest signRequest, File image, File icon, Boolean certifySignature) throws MalformedURLException, IOException, DocumentException {
SignatureProperties sigProperties = signRequest.getSignatureProperties();
SignatureField sigField = sigProperties.getSignatureField();
// Creating the appearance
appearance.setSignatureCreator(Constant.SIGNATURE_CREATOR);
Optional.ofNullable(sigProperties.getReason()).ifPresent(appearance::setReason);
Optional.ofNullable(sigProperties.getLocation()).ifPresent(appearance::setLocation);
if (certifySignature) {
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
} else {
appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
}
/**
* Signature Field Name
*/
BoundingBox box = sigProperties.getSignatureField().getBoundingBox();
String fieldName = sigField.getName();
int pageNumber = sigProperties.getSignatureField().getPage();
if (!sigField.isVisible()) {
if (StringUtils.isBlank(sigField.getName())) {
fieldName = generateFieldName();
appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
} else {
appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
}
} else {
Font font = FontFactory.getFont(Optional.ofNullable(sigField.getFontName()).orElse(BaseFont.HELVETICA),
Optional.ofNullable(sigField.getFontSize()).orElse(6));
Rectangle rect = null;
FieldPosition fieldPosition = null;
//ADD EMPTY FIELD
if (StringUtils.isBlank(sigField.getName()) && box != null) {
fieldName = generateFieldName();
rect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
box.getLowerLeftY() + box.getHeight());
appearance.setVisibleSignature(rect, pageNumber, fieldName);
////////////////////////////////// TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////
Rectangle documentRectangle = reader.getPageSize(pageNumber);
PdfStamper stamper = appearance.getStamper();
float pageMargin = 10;
float tableMargin = 15;
int numberOfFields = 1; // 1 sigField
float headerWidth = (documentRectangle.getWidth() - (pageMargin * 2));
// Table with signature field
PdfPTable table = new PdfPTable(1);
table.setTotalWidth(headerWidth - (tableMargin * 4));
table.setLockedWidth(Boolean.TRUE);
table.setWidthPercentage(100);
float posXTable = (pageMargin + (headerWidth - table.getTotalWidth()) / 2);
float posYTable = 400; // custom y position
int height = 70; // custom height
for (int i = 0; i < numberOfFields; i++) {
String sigFieldName = String.format(Constant.SIGNATURE_FIELD_PREFIX + "%s", (i + 1));
table.addCell(createSignatureFieldCell(stamper, sigFieldName, height, pageNumber));
}
table.writeSelectedRows(0, -1, posXTable, posYTable, stamper.getOverContent(pageNumber));
////////////////////////////////// END TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////
} else {
//APPLY SIGNATURE TO EXISTING EMPTY FIELD
List<FieldPosition> acroFields = reader.getAcroFields().getFieldPositions(sigField.getName());
fieldPosition = acroFields.get(0);
appearance.setVisibleSignature(fieldName);
}
// --------------------------- Custom signature appearance ---------------------
PdfTemplate t = appearance.getLayer(2);
Rectangle sigRect = null;
if (fieldPosition != null) {
sigRect = fieldPosition.position;
} else {
sigRect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
box.getLowerLeftY() + box.getHeight());
}
// Left rectangle
Rectangle leftRect = new Rectangle(0, 0, (sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
ColumnText ct1 = new ColumnText(t);
ct1.setSimpleColumn(leftRect);
Image im1 = Image.getInstance(icon.getAbsolutePath());
float ratio1 = leftRect.getHeight() / im1.getHeight();
im1.scaleToFit(im1.getWidth() * ratio1, im1.getHeight() * ratio1);
Paragraph p = createParagraph("Digital sign", font, Constant.PARAGRAPH_LEADING, Constant.MARGIN * 9);
ct1.addElement(new Chunk(im1, Constant.MARGIN * 10, 0));
ct1.addElement(p);
ct1.go();
// Middle rectangle
Rectangle middleRect = new Rectangle((sigRect.getWidth() / 5), 0,
(leftRect.getWidth() + sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
ColumnText ct2 = new ColumnText(t);
ct2.setSimpleColumn(middleRect);
if (visibleSignatureImage != null) {
Image im2 = Image.getInstance(image.getAbsolutePath());
float ratio2 = sigRect.getHeight() / im2.getHeight();
im2.scaleToFit(im2.getWidth() * ratio2, im2.getHeight() * ratio2);
ct2.addElement(new Chunk(im2, 0, 0));
ct2.go();
}
// TextFields
List<TextField> textFields = fillSignatureFieldText(chain, sigProperties, font);
// Right rectangle - Names
Rectangle rightRectNames = new Rectangle(
(Constant.MARGIN * 5 + leftRect.getWidth() + middleRect.getWidth()), 0,
(leftRect.getWidth() + middleRect.getWidth() + sigRect.getWidth() / 4),
sigRect.getHeight() - Constant.MARGIN);
ColumnText ct31 = new ColumnText(t);
ct31.setSimpleColumn(rightRectNames);
List<Paragraph> paragraphsNames = textFields.stream()
.map(e -> createParagraph(e.getName(), font, Constant.PARAGRAPH_LEADING, 0))
.collect(Collectors.toList());
paragraphsNames.forEach(ct31::addElement);
ct31.go();
// Right rectangle - Values
Rectangle rightRectValues = new Rectangle(
(Constant.MARGIN * 4 + leftRect.getWidth() + middleRect.getWidth() + rightRectNames.getWidth()), 0,
sigRect.getWidth(), (sigRect.getHeight() - Constant.MARGIN));
ColumnText ct32 = new ColumnText(t);
ct32.setSimpleColumn(rightRectValues);
List<Paragraph> paragraphsValues = textFields.stream()
.map(e -> createParagraph(e.getValue(), font, Constant.PARAGRAPH_LEADING, 0))
.collect(Collectors.toList());
paragraphsValues.forEach(ct32::addElement);
ct32.go();
// --------------------------- Custom signature appearance ---------------------
}
return appearance;
}
//this is used to first create the empty fields
protected static PdfPCell createSignatureFieldCell(PdfWriter writer, String name, int height) {
PdfPCell cell = new PdfPCell();
cell.setMinimumHeight(height);
cell.setBackgroundColor(BaseColor.WHITE);
PdfFormField field = PdfFormField.createSignature(writer);
field.setFieldName(name);
field.setFlags(PdfAnnotation.FLAGS_PRINT);
cell.setCellEvent(new MySignatureFieldEvent(field, null, 0));
return cell;
}
//this is used to try to add the extra empty field to signed document
protected static PdfPCell createSignatureFieldCell(PdfStamper stamper, String name, int height, int pageNumber) {
PdfPCell cell = new PdfPCell();
cell.setMinimumHeight(height);
cell.setBackgroundColor(BaseColor.WHITE);
PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
field.setFieldName(name);
field.setFlags(PdfAnnotation.FLAGS_PRINT);
cell.setCellEvent(new MySignatureFieldEvent(field, stamper, pageNumber));
return cell;
}
public static class MySignatureFieldEvent implements PdfPCellEvent {
public PdfFormField field;
public PdfStamper stamper;
public int pageField;
public MySignatureFieldEvent(PdfFormField field, PdfStamper stamper, int pageField) {
this.field = field;
this.stamper = stamper;
this.pageField = pageField;
}
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
PdfWriter writer = canvases[0].getPdfWriter();
field.setPage();
field.setWidget(position, PdfAnnotation.HIGHLIGHT_INVERT);
if (stamper == null) {
writer.addAnnotation(field);
}else {
stamper.addAnnotation(field, pageField);
}
}
}
First of all, originally Adobe interpreted the certification levels as described in this answer. In particular, if a document has a certification signature, then adding new fields is forbidden, even signature fields.
This strictness appears to have been lost along the way but may again be applied any time after updates, in particular after the recently published certification attacks on pdf-insecurity.org exploited this relaxed option.
That been said, though, what you never are allowed to do is changing the static page content! In your code, though, you add the additional signature fields by adding a table (with events) to the document. This table will change the static page content.
Thus, try to only add a new signature field.
I'm trying to read a multi page tiff and blackout some content and then write it again.
Code :-
public void blackOut(File file, File outputTiff, String compressionType) throws IOException
{
ImageReader reader = getImageReader(file);
int pageCount = reader.getNumImages(true);
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(TIFF_FORMAT);
if (!writers.hasNext())
{
throw new RuntimeException(JAI_IMAGE_WRITER_MESSAGE);
}
ImageWriter writer = writers.next();
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
if (compressionType != null)
{
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
tiffWriteParam.setCompressionType(compressionType);
}
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(tiffWriteParam);
try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputTiff))
{
writer.setOutput(ios);
int dpiX = 300;
int dpiY = 300;
for (int i = 0; i < pageCount; i++)
{
BufferedImage bufferedImage = reader.read(i);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(10));
g2d.fillRect(X, Y, Width, Height);
g2d.dispose();
IIOImage iioImage = new IIOImage(bufferedImage, null, null);
ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromRenderedImage(iioImage.getRenderedImage());
ImageWriteParam param = writer.getDefaultWriteParam();
IIOMetadata imageMetadata = writer.getDefaultImageMetadata(imageType, param);
imageMetadata = setDPIViaAPI(imageMetadata, dpiX, dpiY);
iioImage.setMetadata(imageMetadata);
if (i == 0)
{
IIOImage firstIioImage = iioImage;
writer.write(streamMetadata, firstIioImage, tiffWriteParam);
}
else
{
writer.writeInsert(i, iioImage, tiffWriteParam);
}
}
}
finally
{
writer.dispose();
}
}
private static IIOMetadata setDPIViaAPI(IIOMetadata imageMetadata, int dpiX, int dpiY)
throws IIOInvalidTreeException
{
// Derive the TIFFDirectory from the metadata.
TIFFDirectory dir = TIFFDirectory.createFromMetadata(imageMetadata);
// Get {X,Y}Resolution tags.
BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
TIFFTag tagXRes = base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION);
TIFFTag tagYRes = base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
// Create {X,Y}Resolution fields.
TIFFField fieldXRes = new TIFFField(tagXRes, TIFFTag.TIFF_RATIONAL,
1, new long[][] {{dpiX, 1}});
TIFFField fieldYRes = new TIFFField(tagYRes, TIFFTag.TIFF_RATIONAL,
1, new long[][] {{dpiY, 1}});
// Append {X,Y}Resolution fields to directory.
dir.addTIFFField(fieldXRes);
dir.addTIFFField(fieldYRes);
// Convert to metadata object.
IIOMetadata metadata = dir.getAsMetadata();
// Add other metadata.
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
horiz.setAttribute("value", Double.toString(25.4f / dpiX));
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
vert.setAttribute("value", Double.toString(25.4f / dpiY));
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
dim.appendChild(horiz);
dim.appendChild(vert);
root.appendChild(dim);
metadata.mergeTree("javax_imageio_1.0", root);
return metadata;
}
while writing the file again I'm getting the file double (sometimes triple) the size of actual and that too after applying the compression 'LZW'.
Any way that I can get the same file size as previous?
Thanks.
The MediaBox coordinate of my PDF file is (-8,-8), now I want to set it (0,0).
I tried to set it directly, but the contents of the file were offset.
So I want to change the MediaBox coordinates and move the content as well.
Here's the itextshare code(c#). I'm glad to be able to solve it with Java itext.
using (PdfReader pdfReader = new PdfReader(#"MediaBoxZero.pdf"))
{
using (PdfStamper stamper = new PdfStamper(pdfReader, new FileStream(#"MediaBoxZero_Result.pdf", FileMode.Create)))
{
var mediaBox = pdfReader.GetBoxSize(1, "media");
PdfArray mediaBoxN = new PdfArray();
mediaBoxN.Add(new float[] { 0, 0, mediaBox.Width, mediaBox.Height });
for (int curPageNum = 1; curPageNum <= pdfReader.NumberOfPages; ++curPageNum)
{
PdfDictionary pagedict = pdfReader.GetPageN(curPageNum);
pagedict.Put(PdfName.MEDIABOX, mediaBoxN);
}
}
}
I tried affine transformation, but it didn't work. Affine transformation should only work when generating new PDFs, and I wanted to edit existing PDFs.
using (PdfReader pdfReader = new PdfReader(#"MediaBoxZero.pdf"))
{
using (PdfStamper stamper = new PdfStamper(pdfReader, new FileStream(#"MediaBoxZero_Result.pdf", FileMode.Create)))
{
PdfContentByte pb = stamper.GetOverContent(1);
AffineTransform at = new AffineTransform();
at.Translate(100,0);
pb.Transform(at);
pb.ConcatCTM(at);
//var mediaBox = pdfReader.GetBoxSize(1, "media");
//PdfArray mediaBoxN = new PdfArray();
//mediaBoxN.Add(new float[] { 0, 0, mediaBox.Width, mediaBox.Height });
//for (int curPageNum = 1; curPageNum <= pdfReader.NumberOfPages; ++curPageNum)
//{
// PdfDictionary pagedict = pdfReader.GetPageN(curPageNum);
// foreach (var item in pagedict.GetEnumerator())
// {
// }
// pagedict.Put(PdfName.MEDIABOX, mediaBoxN);
//}
}
}
}
You can shift the page contents like this with iTextSharp to match the MediaBox change:
using (PdfReader pdfReader = new PdfReader(SOURCE_PDF))
{
for (int i = 1; i <= pdfReader.NumberOfPages; i++)
{
Rectangle mediaBox = pdfReader.GetPageSize(i);
if (mediaBox.Left == 0 && mediaBox.Bottom == 0)
continue;
PdfDictionary pageDict = pdfReader.GetPageN(i);
pageDict.Put(PdfName.MEDIABOX, new PdfArray { new PdfNumber(0), new PdfNumber(0),
new PdfNumber(mediaBox.Width), new PdfNumber(mediaBox.Height) });
Rectangle cropBox = pdfReader.GetBoxSize(i, "crop");
if (cropBox != null)
{
pageDict.Put(PdfName.CROPBOX, new PdfArray { new PdfNumber(cropBox.Left - mediaBox.Left),
new PdfNumber(cropBox.Bottom-mediaBox.Bottom), new PdfNumber(cropBox.Right - mediaBox.Left),
new PdfNumber(cropBox.Top - mediaBox.Bottom) });
}
using (MemoryStream stream = new MemoryStream())
{
string translation = String.Format(CultureInfo.InvariantCulture, "1 0 0 1 {0} {1} cm\n", -mediaBox.Left, -mediaBox.Bottom);
byte[] translationBytes = Encoding.ASCII.GetBytes(translation);
stream.Write(translationBytes, 0, translationBytes.Length);
byte[] contentBytes = pdfReader.GetPageContent(i);
stream.Write(contentBytes, 0, contentBytes.Length);
pdfReader.SetPageContent(i, stream.ToArray());
}
}
using (FileStream fileStream = new FileStream(#"MediaBox-normalized.pdf", FileMode.Create))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, fileStream))
{
}
}
Some remarks:
You cannot simply manipulate the UnderContent of the respective page because iText attempts to prevent changes to the graphics state there (like a change of the current transformation matrix) from bleeding through to the existing content. Thus, here we update the page content on the byte level.
The code updates the CropBox (if set) alongside the MediaBox. Strictly speaking it should also update the other boxes known to PDF pages.
The code ignores annotations. If your PDFs have annotations, you have to also move the annotation Rects and other annotation properties in userspace coordinates, like QuadPoints, Vertices, etc.
I´m trying to save a BufferedImage as TIFF. This works fine, but it's taking too long per TIFF, up to 5 sec. 5 sec might not sound like much, but if you want to save up to 20 images it´s too long.
Here is my code:
public void convertToTif() throws ConvertionFailedException {
final int dpi = 300;
try {
ImageOutputStream ios = null;
TIFFImageWriterSpi tiffspi = new TIFFImageWriterSpi();
ImageWriter writer = tiffspi.createWriterInstance();
// setup writer
ios = ImageIO.createImageOutputStream(this.tifFile);
writer.setOutput(ios);
BufferedImage tiffImage = create1BitTif(this.imageToConvert);
// get new Metadata
ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromBufferedImageType(tiffImage.getType());
IIOMetadata imageMetadata = writer.getDefaultImageMetadata(imageType, null);
TIFFDirectory dir = TIFFDirectory.createFromMetadata(imageMetadata);
BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
TIFFTag tagXRes = base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION);
TIFFTag tagYRes = base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
TIFFTag tagNewSubfile = base.getTag(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
TIFFTag tagBitsPerSample = base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
TIFFTag tagResolutionUnit = base.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
// set X DPI
long[][] X_DPI = new long[][] { { dpi, 1 } };
TIFFField xDPI = new TIFFField(tagXRes, TIFFTag.TIFF_RATIONAL, 1, X_DPI);
// set Y DPI
long[][] Y_DPI = new long[][] { { dpi, 1 } };
TIFFField yDPI = new TIFFField(tagYRes, TIFFTag.TIFF_RATIONAL, 1, Y_DPI);
// setzt Photometric von BlackIsZero auf WhiteIsZero
TIFFField photometricInterpretationField = new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION), BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO);
// setzt NewSubfileType auf 0
TIFFField newSubfile = new TIFFField(tagNewSubfile, 0);
// setzt BitsPerSample auf 1
TIFFField bitsPerSample = new TIFFField(tagBitsPerSample, 1);
// setzt ResolutionUnit auf 2
TIFFField resolutionUnit = new TIFFField(tagResolutionUnit, 2);
dir.addTIFFField(xDPI);
dir.addTIFFField(yDPI);
dir.addTIFFField(photometricInterpretationField);
dir.addTIFFField(newSubfile);
dir.addTIFFField(bitsPerSample);
dir.addTIFFField(resolutionUnit);
TIFFImageWriteParam param = (TIFFImageWriteParam) writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setTIFFCompressor(new TIFFT6Compressor());
param.setCompressionType("CCITT T.6");
param.setCompressionQuality(0.0f);
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(null);
String tiffMetadataFormatName = streamMetadata.getNativeMetadataFormatName();
IIOMetadataNode newTree = new IIOMetadataNode(tiffMetadataFormatName);
// create little endian
IIOMetadataNode endianNode = new IIOMetadataNode("ByteOrder");
endianNode.setAttribute("value", "LITTLE_ENDIAN");
newTree.appendChild(endianNode);
streamMetadata.setFromTree(tiffMetadataFormatName, newTree);
writer.prepareWriteSequence(streamMetadata);
writer.writeToSequence(new IIOImage(tiffImage, null, dir.getAsMetadata()), param);
ios.close();
} catch (IOException e) {
throw new ConvertionFailedException(e);
}
}
I am trying to write a pdf conversion, that will take a pdf containing 1-up portrait pages, and create a new document, but merge every 2 pages into one 2-up landscape page
ie.
the following code will scale down the content 50%, but I cant figure out how to make the new page landscape, while injecting the other page as portrait, and injecting into the top left, and right of centre
public static void main(String[] args) throws IOException, DocumentException, COSVisitorException {
scalePages("c:/pdf/in.pdf", "c:/pdf/out" + new Date().getTime() + ".pdf", 0.50f);
}
public static void scalePages(String inFile, String outFile, float scale ) throws IOException, COSVisitorException {
PDDocument doc1 = null;
try {
doc1 = PDDocument.load( inFile );
List allPages = doc1.getDocumentCatalog().getAllPages();
for( int i=0; i<allPages.size(); i++ ) {
PDPage page1 = (PDPage)allPages.get(i );
PDRectangle mediaBox = page1.getMediaBox();
float oldX = mediaBox.getUpperRightX();
float newX = oldX * scale;
float oldY = mediaBox.getUpperRightY();
float newY = oldY * scale;
mediaBox.setUpperRightX(newX);
mediaBox.setUpperRightY(newY);
PDFStreamParser parser = new PDFStreamParser(page1.getContents());
parser.parse();
List tokens = parser.getTokens();
tokens.add(0,new COSFloat(scale));
tokens.add(1,new COSInteger(0));
tokens.add(2,new COSInteger(0));
tokens.add(3,new COSFloat(scale));
tokens.add(4,new COSInteger(0));
tokens.add(5,new COSInteger(0));
tokens.add(6,PDFOperator.getOperator("cm"));
PDStream newContents = new PDStream( doc1 );
ContentStreamWriter writer = new ContentStreamWriter( newContents.createOutputStream() );
writer.writeTokens( tokens );
newContents.addCompression();
page1.setContents(newContents);
//page1.setRotation(90);
mediaBox.setUpperRightX(oldX);
mediaBox.setUpperRightY(oldY);
}
doc1.save( outFile );
} finally {
if( doc1 != null ) {
doc1.close();
}
}
}
so the result is as follows
any pointers would be greatly appreciated
Here is an example that "stitches" two one page PDFs side-by-side and saves result in a new file using PDFBox. Enjoy!
function void generateSideBySidePDF() {
File pdf1File = new File(FILE1_PATH);
File pdf2File = new File(FILE2_PATH);
File outPdfFile = new File(OUTFILE_PATH);
PDDocument pdf1 = null;
PDDocument pdf2 = null;
PDDocument outPdf = null;
try {
pdf1 = PDDocument.load(pdf1File);
pdf2 = PDDocument.load(pdf2File);
outPdf = new PDDocument();
// Create output PDF frame
PDRectangle pdf1Frame = pdf1.getPage(0).getCropBox();
PDRectangle pdf2Frame = pdf2.getPage(0).getCropBox();
PDRectangle outPdfFrame = new PDRectangle(pdf1Frame.getWidth()+pdf2Frame.getWidth(), Math.max(pdf1Frame.getHeight(), pdf2Frame.getHeight()));
// Create output page with calculated frame and add it to the document
COSDictionary dict = new COSDictionary();
dict.setItem(COSName.TYPE, COSName.PAGE);
dict.setItem(COSName.MEDIA_BOX, outPdfFrame);
dict.setItem(COSName.CROP_BOX, outPdfFrame);
dict.setItem(COSName.ART_BOX, outPdfFrame);
PDPage outPdfPage = new PDPage(dict);
outPdf.addPage(outPdfPage);
// Source PDF pages has to be imported as form XObjects to be able to insert them at a specific point in the output page
LayerUtility layerUtility = new LayerUtility(outPdf);
PDFormXObject formPdf1 = layerUtility.importPageAsForm(pdf1, 0);
PDFormXObject formPdf2 = layerUtility.importPageAsForm(pdf2, 0);
// Add form objects to output page
AffineTransform afLeft = new AffineTransform();
layerUtility.appendFormAsLayer(outPdfPage, formPdf1, afLeft, "left");
AffineTransform afRight = AffineTransform.getTranslateInstance(pdf1Frame.getWidth(), 0.0);
layerUtility.appendFormAsLayer(outPdfPage, formPdf2, afRight, "right");
outPdf.save(outPdfFile);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (pdf1 != null) pdf1.close();
if (pdf2 != null) pdf2.close();
if (outPdf != null) outPdf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I ended up doing this with itext
private String createTwoUp(String originalPdfFile) throws IOException, DocumentException {
String newFilename = FilenameUtils.getBaseName(originalPdfFile) + "_2up." + FilenameUtils.getExtension(originalPdfFile);
newFilename = FilenameUtils.concat(getPdfFileFolder(), newFilename);
PdfReader reader = new PdfReader(originalPdfFile);
Document doc = new Document(new RectangleReadOnly(842f, 595f), 0, 0, 0, 0);
PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(newFilename));
doc.open();
int totalPages = reader.getNumberOfPages();
for (int i = 1; i <= totalPages; i = i + 2) {
doc.newPage();
PdfContentByte cb = writer.getDirectContent();
PdfImportedPage page = writer.getImportedPage(reader, i); // page #1
float documentWidth = doc.getPageSize().getWidth() / 2;
float documentHeight = doc.getPageSize().getHeight();
if (i > 1) {
documentHeight = documentHeight - 65f;
}
float pageWidth = page.getWidth();
float pageHeight = page.getHeight();
float widthScale = documentWidth / pageWidth;
float heightScale = documentHeight / pageHeight;
float scale = Math.min(widthScale, heightScale);
//float offsetX = 50f;
float offsetX = (documentWidth - (pageWidth * scale)) / 2;
float offsetY = 0f;
cb.addTemplate(page, scale, 0, 0, scale, offsetX, offsetY);
if (i+1 <= totalPages) {
PdfImportedPage page2 = writer.getImportedPage(reader, i+1); // page #2
pageWidth = page.getWidth();
pageHeight = page.getHeight();
widthScale = documentWidth / pageWidth;
heightScale = documentHeight / pageHeight;
scale = Math.min(widthScale, heightScale);
offsetX = ((documentWidth - (pageWidth * scale)) / 2) + documentWidth;
cb.addTemplate(page2, scale, 0, 0, scale, offsetX, offsetY);
}
}
doc.close();
return newFilename;
}