Created
January 25, 2016 18:28
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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