Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created July 25, 2020 17:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/84a7bd082351ebcf19d97a1efa14b882 to your computer and use it in GitHub Desktop.
Save bennadel/84a7bd082351ebcf19d97a1efa14b882 to your computer and use it in GitHub Desktop.
Using Apache POI 3.17 To Save InVision Prototypes As Interactive PowerPoints In Lucee CFML 5.3.6.61
[
{
"id": 1,
"name": "Step 1",
"clientFilename": "step-1.png",
"width": 600,
"height": 531,
"hotspots": [
{
"x": 370,
"y": 89,
"width": 33,
"height": 30,
"targetScreenID": 1
},
{
"x": 406,
"y": 89,
"width": 33,
"height": 30,
"targetScreenID": 2
},
// ....
]
},
// ...
]
<cfscript>
// In Lucee CFML, when we call the createObject() method to instantiate Java objects,
// we can pass in an array of JAR files / directories from which Lucee will load the
// desired classes. In this demo, usage of this Array will be encapsulated within the
// javaNew() User Defined Function (UDF) at the bottom.
poiJarPaths = [
expandPath( "../poi-3.17/" ),
expandPath( "../poi-3.17/lib" ),
expandPath( "../poi-3.17/ooxml-lib" )
];
// Create some POI Class definitions that we'll need to reference below.
PictureType = javaNew( "org.apache.poi.sl.usermodel.PictureData$PictureType" );
SlideLayout = javaNew( "org.apache.poi.xslf.usermodel.SlideLayout" );
ShapeType = javaNew( "org.apache.poi.sl.usermodel.ShapeType" );
TextAlign = javaNew( "org.apache.poi.sl.usermodel.TextParagraph$TextAlign" );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Read in our InVision prototype data (this would normally come from the database).
screens = deserializeJson( fileRead( "./data.json" ) );
// In a PowerPoint (PPT) / Keynote presentation, all of the slides need to be the
// same size. As such, we're going to find the max dimensions of all the screens so
// that we can choose slide dimensions that can accommodate the largest screen.
maxScreenWidth = getMaxProperty( screens, "width" );
maxScreenHeight = getMaxProperty( screens, "height" );
// When calculating the slide dimensions, we're going to add some margin around each
// image which will give us some wiggle room and give us a place to add a Title.
slideWidth = ( maxScreenWidth + 30 + 30 );
slideHeight = ( maxScreenHeight + 70 + 30 );
// Create our PowerPoint slide-show with the given dimensions.
slideShow = javaNew( "org.apache.poi.xslf.usermodel.XMLSlideShow" )
.init()
;
slideShow.setPageSize(
javaNew( "java.awt.Dimension" )
.init( slideWidth, slideHeight )
);
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// When creating the PowerPoint slides, we have to perform TWO PASSES: one pass to
// create the individual slides and then another pass to add the hotspots / inter-
// slide hyperlinks. We have to do this in two passes because the hyperlinks take
// Slide instances as their targets.
// -- PASS ONE -- Create the slides and map screen IDs to Slide instances.
screenToSlideMap = {};
// Each slide is going to use the Title layout, which gives us a single placeholder
// into which we will set the Screen name.
titleOnlyLayout = slideShow.getSlideMasters()
.first()
.getLayout( SlideLayout.TITLE_ONLY )
;
for ( screen in screens ) {
// Create the slide and map it to the screenID at the same time.
slide
= screenToSlideMap[ screen.id ]
= slideShow.createSlide( titleOnlyLayout )
;
// Set the title and place it at the top-left of the slide.
title = slide.getPlaceholder( 0 );
title.clearText();
titleText = title.addNewTextParagraph();
titleText.setTextAlign( TextAlign.LEFT );
textRun = titleText.addNewTextRun();
textRun.setText( screen.name );
textRun.setFontSize( 22 );
title.setAnchor(
javaNew( "java.awt.Rectangle" )
.init( 30, 22, maxScreenWidth, 30 )
);
// In a PowerPoint presentation, the images are added to the slide show
// separately from their inclusion into a slide. This way, one picture can be
// reused (I assume) on several different slides. So, we're going to first add
// the screen binary to the slideshow.
// --
// NOTE: In a more variable environment, we would have to pass a different
// picture type (PNG, JPEG, GIF, etc). However, in this controlled demo, I know
// that all of the images are PNGs.
pictureData = slideShow.addPicture(
fileReadBinary( "./images/" & screen.clientFilename ),
pictureType.PNG
);
// Then, we're going to paint the screen images as shape on the slide, positioned
// below the title.
pictureShape = slide.createPicture( pictureData );
pictureShape.setAnchor(
javaNew( "java.awt.Rectangle" )
.init( 30, 70, screen.width, screen.height )
);
}
// -- PASS TWO -- Add the hotspots that link to existing Slides.
// Now that we've mapped all the screens to Slide instances, we can go back over
// the slides and add the hotspots (each of which links to another slide).
for ( screen in screens ) {
for ( hotspot in screen.hotspots ) {
// Get the Slide references for which slide we're on and which Slide we're
// about to link-to.
slide = screenToSlideMap[ screen.id ];
targetSlide = screenToSlideMap[ hotspot.targetScreenID ];
// Draw the hotspot shape on the slide.
hotspotShape = slide.createAutoShape();
hotspotShape.setShapeType( ShapeType.ROUND_RECT );
hotspotShape.setFillColor( colorNew( 0, 185, 255, 50 ) );
hotspotShape.setLineColor( colorNew( 0, 185, 255, 255 ) );
hotspotShape.setLineWidth( 2 );
hotspotShape.setAnchor(
javaNew( "java.awt.Rectangle" )
.init(
( hotspot.x + 30 ),
( hotspot.y + 70 ),
hotspot.width,
hotspot.height
)
);
// Link the hotspot shape to the target slide.
hotspotLink = hotspotShape.createHyperlink();
hotspotLink.linkToSlide( targetSlide );
}
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Now that we've generated the PowerPoint presentation from the InVision prototype,
// we need to save it to disk.
outputFile = javaNew( "java.io.File" )
.init( expandPath( "./prototype.ppt" ) )
;
outputFileStream = javaNew( "java.io.FileOutputStream" )
.init( outputFile )
;
try {
slideShow.write( outputFileStream );
echo( "Your InVision prototype has been generated as PPT!" );
} finally {
outputFileStream.close();
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I create a new Color object from the given RGBA values with ranges 0-255.
*
* @red255 I am the red channel (0-255).
* @green255 I am the green channel (0-255).
* @blue255 I am the blue channel (0-255).
* @alpha255 I am the alpha channel (0-255).
*/
public any function colorNew(
required numeric red255,
required numeric green255,
required numeric blue255,
required numeric alpha255
) {
var color = javaNew( "java.awt.Color" )
.init(
( red255 / 255 ),
( green255 / 255 ),
( blue255 / 255 ),
( alpha255 / 255 )
)
;
return( color );
}
/**
* I get the max value for the given property within the given collection.
*
* @collection I am the collection being inspected.
* @collectionProperty I am the property being evaluated.
*/
public numeric function getMaxProperty(
required array collection,
required string collectionProperty
) {
var maxValue = 0;
for ( var item in collection ) {
maxValue = max( maxValue, item[ collectionProperty ] );
}
return( maxValue );
}
/**
* I am a short-hand for Java class creation that will use the POI JAR paths for
* classes that appear to be POI-related.
*
* @className I am the Java class being created.
*/
public any function javaNew( required string className ) {
if ( className.find( "org.apache.poi." ) == 1 ) {
return( createObject( "java", className, poiJarPaths ) );
} else {
return( createObject( "java", className ) );
}
}
</cfscript>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment