Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JoelGeraci-Datalogics/85682856c2acb034c787 to your computer and use it in GitHub Desktop.
Save JoelGeraci-Datalogics/85682856c2acb034c787 to your computer and use it in GitHub Desktop.
Inserts a 3D model in a PDF page using the Rich Media Annotation.
/*
* Copyright Datalogics, Inc. 2016
*/
package pdfjt.cookbook.annotations.richmedia;
import com.adobe.internal.io.LazyRandomAccessFileByteReader;
import com.adobe.pdfjt.core.types.ASName;
import com.adobe.pdfjt.core.types.ASRectangle;
import com.adobe.pdfjt.core.types.ASString;
import com.adobe.pdfjt.pdf.document.PDFDocument;
import com.adobe.pdfjt.pdf.document.PDFOpenOptions;
import com.adobe.pdfjt.pdf.document.PDFResources;
import com.adobe.pdfjt.pdf.document.PDFStream;
import com.adobe.pdfjt.pdf.graphics.PDFRectangle;
import com.adobe.pdfjt.pdf.graphics.xobject.PDFXObjectForm;
import com.adobe.pdfjt.pdf.graphics.xobject.PDFXObjectImage;
import com.adobe.pdfjt.pdf.interactive.annotation.PDFAnnotation3D;
import com.adobe.pdfjt.pdf.interactive.annotation.PDFAppearance;
import com.adobe.pdfjt.pdf.multimedia.PDF3DADict;
import com.adobe.pdfjt.pdf.multimedia.PDF3DDStream;
import com.adobe.pdfjt.pdf.multimedia.PDF3DLightingScheme;
import com.adobe.pdfjt.pdf.multimedia.PDF3DVBGDict;
import com.adobe.pdfjt.pdf.multimedia.PDF3DVDict;
import com.adobe.pdfjt.pdf.multimedia.PDF3DVPDict;
import com.adobe.pdfjt.pdf.multimedia.PDF3DViewArray;
import com.adobe.pdfjt.pdf.page.PDFPage;
import com.adobe.pdfjt.services.imageconversion.ImageManager;
import com.adobe.pdfjt.services.xobjhandler.XObjectUseOptions;
import pdfjt.util.SampleFileServices;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
/**
* Creates a new PDF with a 3D Annotation, in this sample a U3D file
* is used.
*/
public class InsertRichMediaAnnotation_U3D {
/*
* Read through the entire sample before changing these inputs; 3D views are model specific.
*/
private static final String inputPath = "http://dev.datalogics.com/cookbook/annotations/richmedia/";
private static final String inputU3DModel = "BrakeAssembly.u3d";
private static final String inputU3DPoster = "BrakeAssemblyPoster.jpg";
private static final String inputBackground = "abstract.jpg";
private static final String inputU3DJavaScript = "BrakeAssembly.js";
private static String outputFilePath = "cookbook/Annotations/output/BrakeAssembly.pdf";
public static void main(String[] args) throws Exception {
// Create a new document, with a landscape letter-sized pdfPage.
ASRectangle pageRectangle = new ASRectangle(0, 0, ASRectangle.US_LETTER.height(), ASRectangle.US_LETTER.width());
PDFDocument pdfDocument = PDFDocument.newInstance(pageRectangle, PDFOpenOptions.newInstance());
/*
* The 3D model will be inside a Rich Media Annotation on the first page
* so we create that. I want a 1/2 inch border around the annotation so I set up that rectangle.
*/
PDFPage pdfPage = pdfDocument.requirePages().getPage(0);
PDFRectangle annotationLocation = PDFRectangle.newInstance(pdfDocument,
36,
36,
pdfPage.getMediaBox().width()-36,
pdfPage.getMediaBox().height()-36);
/*
* Create the 3D annotation, position it and add it to the page.
*/
PDFAnnotation3D pdfAnnotation3D = PDFAnnotation3D.newInstance(pdfDocument);
pdfAnnotation3D.setRect(annotationLocation);
/*
* Set the 3D annotation's label
*/
pdfAnnotation3D.setContents("3D Model");
pdfPage.addAnnotation(pdfAnnotation3D);
/*
* You're going to want to have a copy of the PDF Specification handy
* for the rest of this. See Section 13.6 "3D Artwork".
*
* The first step is to read the U3D file into the 3D Annotation as a
* stream.
*/
URL urlU3D = new URL(inputPath+inputU3DModel);
File localFileU3D = new File(inputU3DModel);
FileUtils.copyURLToFile(urlU3D, localFileU3D);
LazyRandomAccessFileByteReader readerU3D = new LazyRandomAccessFileByteReader(localFileU3D);
PDF3DDStream pdf3DStream = PDF3DDStream.newInstance(pdfDocument, PDF3DDStream.k_U3D);
pdf3DStream.setStreamData(readerU3D);
pdfAnnotation3D.set3DDictOrStream(pdf3DStream);
/*
* While we're working with the PDF3DDStream, we'll add some JavaScript
* to the 3D Annotation that will load and run when the 3D Annotation
* initializes. This particular JavaScript will play and animation that
* is already present in the model. It will play the animation forward
* and then reverse the animation continuously.
*/
URL urlJS = new URL(inputPath+inputU3DJavaScript);
File localFileJS = new File(inputU3DJavaScript);
FileUtils.copyURLToFile(urlJS, localFileJS);
LazyRandomAccessFileByteReader readerJS = new LazyRandomAccessFileByteReader(localFileJS);
PDFStream jsStream = PDFStream.newInstance(pdfDocument);
jsStream.setStreamData(readerJS);
pdf3DStream.setOnInstantiate(jsStream);
/*
* Next we set when the 3D Annotation will be activated (enabled) and deactivated (disabled).
* To make this section easier to read, I've set up some ASNames matching the Acrobat UI.
*/
ASName enableWhenTheContentIsClicked = ASName.k_XA;
ASName enableWhenThePageContainingTheContentIsOpened = ASName.k_PO;
ASName enableWhenThePageContainingTheContentIsVisible = ASName.k_PV;
ASName disableWhenDisableContentIsSelectedFromTheContextMenu = ASName.k_XA;
ASName disableWhenThePageContainingTheContentIsClosed = ASName.k_PC;
ASName disableWhenThePageContainingTheContentIsNotVisible = ASName.k_PI;
/*
* Now we can create and set the 3DA (Activation) Dictionary. See table
* 299 of the PDF Specification.
*/
PDF3DADict pdf3DADict = PDF3DADict.newInstance(pdfDocument);
pdfAnnotation3D.set3DA(pdf3DADict);
pdf3DADict.setActivation(enableWhenTheContentIsClicked);
pdf3DADict.setDeactivation(disableWhenThePageContainingTheContentIsNotVisible);
/*
* Next we tell the viewer if we want it to open the model tree
* automatically when the 3D Annotation is activated.
*/
pdf3DADict.setNP(true);
/*
* Next we set up the 3D view dictionary for the default view. The
* settings below mimic what Adobe Acrobat does when importing a U3D
* model that has no default lighting scheme. It also adds the default
* background color that Acrobat would add. Like with Acrobat, this view
* will NOT appear in the list of model views and cannot be addressed
* via JavaScript. See the Advanced 3D sample for that.
*
* Start by creating the default view and add it to the annotation. This
* Object corresponds to the 3DV dictionary. See table 298 of the PDF
* Specification.
*/
ASString defaultViewName = new ASString("Default");
PDF3DVDict pdf3DViewDictDefault = PDF3DVDict.newInstance(pdfDocument, defaultViewName);
pdf3DViewDictDefault.setInternalName(defaultViewName);
pdfAnnotation3D.set3DView(pdf3DViewDictDefault);
/*
* Next create and add the lighting scheme dictionary to the view. See
* table 304 of the PDF Specification.
*/
PDF3DLightingScheme pdf3DLightingScheme = PDF3DLightingScheme.newInstance(pdfDocument, PDF3DLightingScheme.k_Headlamp);
pdf3DLightingScheme.setType(ASName.create("3DLightingScheme"));
pdf3DViewDictDefault.setLS(pdf3DLightingScheme);
/*
* Create and add the background color to the view.
*/
PDF3DVBGDict pdf3DVBGDict = PDF3DVBGDict.newInstance(pdfDocument);
pdf3DVBGDict.setColor(new double[] {0.375, 0.375, 0.375});
pdf3DViewDictDefault.setBG(pdf3DVBGDict);
/*
* Create and add an Appearance (Poster) to the 3D Annotation. This is what
* displays when the annotation is not active or when the file is being
* displayed in a viewer that can't show 3D. Normally, you'd use a U3D
* parser to create an image but for the purposes of this sample, I've
* done that for you.
*/
BufferedImage bufferedImage = ImageIO.read(new URL(inputPath+inputU3DPoster).openStream());
PDFXObjectForm pdfXObjectForm = ImageManager.getXObjPDFImage(pdfDocument, bufferedImage, new XObjectUseOptions());
PDFAppearance pdfAppearance = PDFAppearance.newInstance(pdfDocument);
pdfAppearance.setNormalAppearance(pdfXObjectForm);
pdfAnnotation3D.setAppearance(pdfAppearance);
/*
* Add resources
*/
BufferedImage bufferedImageBG = ImageIO.read(new URL(inputPath+inputBackground).openStream());
PDFXObjectImage pdfXObjectImageBG = ImageManager.getPDFImage(bufferedImageBG, pdfDocument);
ArrayList<Object> resourcesList = new ArrayList<Object>();
resourcesList.add(inputBackground);
resourcesList.add(pdfXObjectImageBG);
PDFResources pdfResources = PDFResources.newInstance(pdfDocument);
pdfResources.setDictionaryArrayValue(ASName.create("Names"), resourcesList);
pdf3DStream.setResources(pdfResources);
/*
* At this point, you've got a functioning 3D Annotation but this is as
* far as you can go without knowing more about the U3D geometry of your
* specific model. If you are unfamiliar with how to work with 3D models
* or don't have access to a U3D tool set... stop... back away slowly...
* and don't make any sudden movements... otherwise, keep reading.
*
* In order to make the "Home" button in the 3D toolbar function
* properly in the viewer, you need add camera settings specific to the
* model in question. Specifically, you'll need a 12-element 3D
* transformation matrix that specifies the position and orientation of
* the camera in world coordinates. It's actually a 4x4 matrix but only
* the first three columns of the matrix change; so the
* matrix is expressed in PDF as an array of 12 numbers. See "Section
* 13.6.5 Coordinate Systems for 3D" in the PDF Specification for further
* details.
*
* Normally, you'd get this information by using a U3D parser and doing
* a lot of math. But for the purposes of this sample, I've already done
* that for you. These numbers are specific to the input U3D supplied
* above. Don't expect them to work for any other models.
*/
double[] cameraMatrixDefault = {
-0.382683, 0.923880, -0,
0.180240, 0.074658, 0.980785,
0.906127, 0.375330, -0.195090,
-1.032850, -0.419857, 0.200171 };
pdf3DViewDictDefault.setMatrixSource(ASName.k_M);
pdf3DViewDictDefault.setCameraToWorld(cameraMatrixDefault);
pdf3DViewDictDefault.setCenterOfOrbit(1.171060);
/*
* Create and add a Projection Dictionary to the default view
*/
PDF3DVPDict pdf3DVPDict = PDF3DVPDict.newInstance(pdfDocument, ASName.k_P);
pdf3DVPDict.setFieldOfView(30);
pdf3DVPDict.setPerspactiveScale(ASName.k_Min);
pdf3DViewDictDefault.setP(pdf3DVPDict);
/*
* With these additional settings added to the default view, the "Home"
* button on the 3D toolbar will now be functional but we can also add
* additional views. The following section will add the equivalent of
* the Acrobat generated default views for Left, Top, Front, Right,
* Bottom, and Back. However, the first thing we are going to do is make
* the Default view available to the JavaScript for Acrobat 3D
* Annotations API by creating a view array and adding the default view
* to it. This is also where we'll add the additional views. First we
* need to set it's external name; this is the name that appears in the
* list of views. Because in Acrobat the default view is represented on
* the 3D toolbar, I'm using the name "Home" for the default view.
* However, this view will be addressed by JavaScript using it's internal
* name "default" as we set above.
*/
pdf3DViewDictDefault.setExternalName(new ASString("Home"));
PDF3DViewArray pdf3DViewArray = PDF3DViewArray.newInstance(pdfDocument);
pdf3DViewArray.add(pdf3DViewDictDefault);
pdf3DStream.setPresetViewArtwork(pdf3DViewArray);
/*
* Now we can add the other views. Again, the camera matrix would be
* derived using another tool specific to U3D.
*/
Map<ASString, double[]> viewMap = new HashMap<ASString, double[]>();
viewMap.put(new ASString("Left"), new double[]{0, 1, 0, 0, 0, 1, 1, 0, 0, -2, 0, 0 });
viewMap.put(new ASString("Top"), new double[]{-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 2 });
viewMap.put(new ASString("Front"), new double[]{-1, 0, 0, 0, 0, 1, 0, 1, 0, 0, -2, 0 });
viewMap.put(new ASString("Right"), new double[]{0, -1, 0, 0, 0, 1, -1, 0, 0, 1, 0, 0 });
viewMap.put(new ASString("Bottom"), new double[]{-1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, -2});
viewMap.put(new ASString("Back"), new double[]{1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 2, 0 });
for (Map.Entry<ASString, double[]> entry : viewMap.entrySet()) {
ASString viewName = entry.getKey();
PDF3DVDict pdf3DViewDict = PDF3DVDict.newInstance(pdfDocument, viewName);
pdf3DViewDict.setInternalName(viewName);
pdf3DViewDict.setExternalName(viewName);
pdf3DViewDict.setMatrixSource(ASName.k_M);
pdf3DViewDict.setCameraToWorld(entry.getValue());
pdf3DViewDict.setCenterOfOrbit(1);
/* Reuse the background and lighting scheme dictionaries color from above.
*
*/
pdf3DViewDict.setBG(pdf3DVBGDict);
pdf3DViewDict.setLS(pdf3DLightingScheme);
pdf3DViewDict.setP(pdf3DVPDict);
// Add the view
pdf3DViewArray.add(pdf3DViewDict);
}
// Save out the PDF Document just created.
SampleFileServices.savePDFDocument(outputFilePath, pdfDocument);
System.out.println("Successful output to " + outputFilePath);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment