Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoelGeraci-Datalogics/fbe8e3f7eca9b6a5946d to your computer and use it in GitHub Desktop.
Save JoelGeraci-Datalogics/fbe8e3f7eca9b6a5946d to your computer and use it in GitHub Desktop.
Creates a new PDF with a 3D Annotation, in this sample a U3D file is used.
/*
* Copyright Datalogics, Inc. 2015
*/
package pdfjt.cookbook.annotations.richmedia;
import com.adobe.internal.io.LazyRandomAccessFileByteReader;
import com.adobe.pdfjt.core.cos.CosCloneMgr;
import com.adobe.pdfjt.core.cos.CosObject;
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.PDF3DCrossSection;
import com.adobe.pdfjt.pdf.multimedia.PDF3DDStream;
import com.adobe.pdfjt.pdf.multimedia.PDF3DLightingScheme;
import com.adobe.pdfjt.pdf.multimedia.PDF3DNode;
import com.adobe.pdfjt.pdf.multimedia.PDF3DNodeArray;
import com.adobe.pdfjt.pdf.multimedia.PDF3DSectionArray;
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_Advanced {
/*
* Read through the entire sample before changing these inputs; 3D views are
* model specific. This sample build upon the
* "InsertRichMediaAnnotation_U3D" sample by adding additional views that
* control node visibility, position, opacity, and other node attributes as
* well as showing how to add cross section views. To help see how the view
* setting affect the views, the JavaScript to play the animation has been
* removed though the coded to set the background image is still present.
*/
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 = "addAbstract.js";
private static String outputFilePath = "cookbook/Annotations/output/BrakeAssembly_Advanced.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.
*
* 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);
}
/*
* Advanced Views: Views can have node specific settings that can change
* a node's state, opacity and position in world space. This is
* controlled by the 3D Node Dictionary array. See 13.6.4.7 3D Node
* Dictionaries in the PDF Specification.
*
* For this view we'll use same matrix as "Front" view but zoom in a
* little, move the Caliper and turn one node transparent. We'll start
* by creating a view in the same way we did above.
*/
PDF3DVPDict pdf3DVPDict_MoveCaliper = PDF3DVPDict.newInstance(pdfDocument, ASName.k_P);
// Changing the FOV from 30 to 15 will put the camera close to the model
pdf3DVPDict_MoveCaliper.setFieldOfView(15);
pdf3DVPDict_MoveCaliper.setPerspactiveScale(ASName.k_Min);
ASString hiddenCaliperView = new ASString("Caliper Transparent");
PDF3DVDict pdf3DViewDict_MoveCaliper = PDF3DVDict.newInstance(pdfDocument, hiddenCaliperView);
pdf3DViewDict_MoveCaliper.setInternalName(hiddenCaliperView);
pdf3DViewDict_MoveCaliper.setExternalName(hiddenCaliperView);
pdf3DViewDict_MoveCaliper.setMatrixSource(ASName.k_M);
pdf3DViewDict_MoveCaliper.setCameraToWorld(new double[]{-1, 0, 0, 0, 0, 1, 0, 1, 0, 0, -2, 0 });
pdf3DViewDict_MoveCaliper.setP(pdf3DVPDict_MoveCaliper);
// We'll just reuse the background, lighting scheme and center of orbit from above
pdf3DViewDict_MoveCaliper.setBG(pdf3DVBGDict);
pdf3DViewDict_MoveCaliper.setLS(pdf3DLightingScheme);
pdf3DViewDict_MoveCaliper.setCenterOfOrbit(1);
/*
* Now we need to tell the viewer what to do when the user selects this
* view. The NR key specifies whether nodes specified in the NA array
* are returned to their original states (as specified in the 3D
* artwork) before applying the transformation matrices and opacity settings
* specified in the node dictionaries. If true, the artwork’s 3D node
* parameters are restored to their original states and then the
* dictionaries specified by the NA array shall be applied. If false,
* the dictionaries specified by the NAarray are applied to the
* current states of the nodes.
*/
pdf3DViewDict_MoveCaliper.setNAReturned(true);
/*
* Now we'll add the Node specific stuff. You'll need to know the exact
* node name of each node that you want to treat differently from the
* view settings created just above.
*
* First we are going to move the "Caliper" assembly away from the center along the x axis relative to it's parent.
*
* Then we set the transparency of the "Calipers" node, leaving it in place relative to the "Caliper" assembly.
*/
PDF3DNode pdf3DNode_Calipers = PDF3DNode.newInstance(pdfDocument, new ASString("Caliper"), 1, true, new double[]{1, 0, 0, 0, 1, 0, 0, 0, 1, .1, 0, 0 });
PDF3DNode pdf3DNode_Caliper = PDF3DNode.newInstance(pdfDocument, new ASString("Calipers"), .5, true, new double[]{1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 });
/*
* Then we create a NodeArray and add the nodes to it
*/
PDF3DNodeArray pdf3DNodeArray = PDF3DNodeArray.newInstance(pdfDocument);
pdf3DNodeArray.add(pdf3DNode_Calipers);
pdf3DNodeArray.add(pdf3DNode_Caliper);
pdf3DViewDict_MoveCaliper.setNA(pdf3DNodeArray);
/*
* Finally we add the new view to the views array.
*/
pdf3DViewArray.add(pdf3DViewDict_MoveCaliper);
/*
* For the cross section view, we'll use the default view that we
* created above and just add the cross section to it. We'll start by
* cloning the default view object that we created above.
*/
CosCloneMgr cosCloneMgr = new CosCloneMgr(pdfDocument.getCosDocument());
PDF3DVDict pdf3DVDict_CrossSection = PDF3DVDict.getInstance(cosCloneMgr.clone(pdf3DViewDictDefault.getCosObject()));
/*
* Next we modify the view name.
*/
pdf3DVDict_CrossSection.setExternalName(new ASString("Cross Section"));
pdf3DVDict_CrossSection.setInternalName(new ASString("Cross Section"));
/*
* Then we create a cross section. The array that gets passed in is the
* Orientation key, a three-element array specifying the orientation of the
* cutting plane in world space. Each value represents the
* orientation in relation to the X, Y, and Z axes, respectively (see
* 13.6.5, “Coordinate Systems for 3D”). Exactly one of the values must
* be null, indicating an initial state of the cutting plane that is
* perpendicular to the corresponding axis and clipping all geometry on
* the positive side of that axis. The other two values are numbers
* indicating the rotation of the plane, in degrees, around their
* corresponding axes. The order in which these rotations are applied
* matches the order in which the values appear in the array.
*/
PDF3DCrossSection pdf3DCrossSection = PDF3DCrossSection.newInstance(pdfDocument, new Object[]{0.0,0.0,CosObject.t_Null});
/*
* Now we set the offset of the plane to be just slightly below the center on the z axis.
*/
pdf3DCrossSection.setCenter(new double[] {0.0, 0.0, -0.01});
/*
* Now we set the colors to ones that match what Acrobat creates. This
* part is optional; the defaults look just as good.
*/
pdf3DCrossSection.setInteresectionColor(new double[] {1.0, 0.0, 0.0});
pdf3DCrossSection.setPlaneVisible(true);
pdf3DCrossSection.setPlaneOpacity(.35);
pdf3DCrossSection.setPlaneColor(new double[]{0.75, 0.86, 1.00});
/*
* The PDF Specification supports multiple cross sections per view.
* Though I've never seen a 3D PDF viewer capable of displaying more
* than one, we still need to add the cross section to an array of cross
* sections before adding the array to the view.
*/
PDF3DSectionArray pdf3DSectionArray = PDF3DSectionArray.newInstance(pdfDocument);
pdf3DSectionArray.add(pdf3DCrossSection);
pdf3DVDict_CrossSection.setSA(pdf3DSectionArray);
/*
* Then we can finally add the view to the array of views.
*/
pdf3DViewArray.add(pdf3DVDict_CrossSection);
// 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