Create a non-buffered java.awt.Image from an SVG file - java

I have some existing code which looks a lot like the solution on Swing & Batik: Create an ImageIcon from an SVG file?
But the destination for my image is PDF, and it bugs me that when you zoom into the PDF, you see pixels. If the source and destination data are both vector graphics, it should be possible to render directly.
The library we're using (iText) takes a java.awt.Image, but I can't seem to figure out how to get a java.awt.Image which renders an SVG. Does Batik have some way to do that?

Well, here's what I ended up doing. java.awt.Image was certainly a dead-end. There was a solution in the form of wrapping up a PdfTemplate in an ImgTemplate so that it could be used as an iText Image.
(I had to have it in something which knew its size, because it's being used in a table and the layout would go completely crazy otherwise. An Image seems to know this.)
public class SvgHelper {
private final SAXSVGDocumentFactory factory;
private final GVTBuilder builder;
private final BridgeContext bridgeContext;
public SvgHelper() {
factory = new SAXSVGDocumentFactory(
XMLResourceDescriptor.getXMLParserClassName());
UserAgent userAgent = new UserAgentAdapter();
DocumentLoader loader = new DocumentLoader(userAgent);
bridgeContext = new BridgeContext(userAgent, loader);
bridgeContext.setDynamicState(BridgeContext.STATIC);
builder = new GVTBuilder();
}
public Image createSvgImage(PdfContentByte contentByte, URL resource,
float maxPointWidth, float maxPointHeight) {
Image image = drawUnscaledSvg(contentByte, resource);
image.scaleToFit(maxPointWidth, maxPointHeight);
return image;
}
public Image drawUnscaledSvg(PdfContentByte contentByte, URL resource) {
GraphicsNode imageGraphics;
try {
SVGDocument imageDocument = factory.createSVGDocument(resource.toString());
imageGraphics = builder.build(bridgeContext, imageDocument);
} catch (IOException e) {
throw new RuntimeException("Couldn't load SVG resource", e);
}
float width = (float) imageGraphics.getBounds().getWidth();
float height = (float) imageGraphics.getBounds().getHeight();
PdfTemplate template = contentByte.createTemplate(width, height);
Graphics2D graphics = template.createGraphics(width, height);
try {
// SVGs can have their corner at coordinates other than (0,0).
Rectangle2D bounds = imageGraphics.getBounds();
//TODO: Is this in the right coordinate space even?
graphics.translate(-bounds.getX(), -bounds.getY());
imageGraphics.paint(graphics);
return new ImgTemplate(template);
} catch (BadElementException e) {
throw new RuntimeException("Couldn't generate PDF from SVG", e);
} finally {
graphics.dispose();
}
}
}

Related

Springboot: Directly Upload the Image to Azure BlobClient without writing to OS first

I have generated an image thumbnail which I'm trying to store in azure. BlobClient need the data to be of type "BinaryData" so I wrapped it as:
myBlobClient.upload(BinaryData.fromObject(thumbnailImage));
The code snippet is this:
RenderedImage thumbnailImage = CommonUtils.generateImageThumbnail(newFilePath + '?' + thumbnailFileSasToken);
String thumbnailFileName = CommonUtils.removeFileExtension(fileName) + "_preview." + extension;
String thumbnailFilePath = "./tmp/" + thumbnailFileName;
File tempFile = new File(thumbnailFilePath);
assert thumbnailImage != null;
ImageIO.write(thumbnailImage, extension, tempFile);
if (item == null) {
sourceBlobName = String.format("%s/%s", savedOrder.getOrderNo(), thumbnailFileName);
} else {
sourceBlobName = String.format("%s/%s/%s", savedOrder.getOrderNo(), item.getItemNo(), thumbnailFileName);
}
BlobClient myBlobClient = fileService.getBlobClient(sourceBlobName);
myBlobClient.upload(BinaryData.fromStream(new FileInputStream(tempFile)));
return myBlobClient;
Helper Methods:
public static BufferedImage convertToBufferedImage(Image image)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(null), image.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
public static RenderedImage generateImageThumbnail(String filePath) {
try {
BufferedImage image = ImageIO.read(new URL(filePath).openStream());
Image scaledImage = image.getScaledInstance(300, 300, Image.SCALE_DEFAULT);
return convertToBufferedImage(scaledImage);
} catch(IOException ex){
log.error(ex.getMessage());
}
return null;
}
The exception I get on the line myBlobClient.upload is:
java.io.UncheckedIOException:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid
type definition for type sun.awt.image.IntegerInterleavedRaster:
Failed to construct BeanSerializer for [simple type, class
sun.awt.image.IntegerInterleavedRaster]:
(java.lang.IllegalArgumentException) Failed to call setAccess() on
Field 'sampleModel' due to
java.lang.reflect.InaccessibleObjectException, problem: Unable to
make field protected java.awt.image.SampleModel
java.awt.image.Raster.sampleModel accessible: module java.desktop does
not "opens java.awt.image" to unnamed module #4fb3ee4e (through
reference chain: java.awt.image.BufferedImage["raster"])
Update
So I found a way to write the file first and convert it into InputStream which then uploads. Is there a way to bypass writing the file first and directly upload it?
New Attempt
String thumbnailName = CommonUtils.removeFileExtension(fileName) + "_preview." + extension;
String thumbnailPath = "./tmp/" + thumbnailName;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File tempFile = new File(thumbnailPath);
if (imageThumbnail != null) {
ImageIO.write(imageThumbnail, extension, baos);
}
sourceBlobName = item == null ? String.format("%s/%s", savedOrder.getOrderNo(), thumbnailName)
: String.format("%s/%s/%s", savedOrder.getOrderNo(), item.getItemNo(), thumbnailName);
BlobClient myBlobClient = fileService.getBlobClient(sourceBlobName);
myBlobClient.upload(BinaryData.fromObject(baos));
return myBlobClient;

How to duplicate a shape apache POI?

I need to duplicate the same shape in a slide using Apache POI (XSLF) ppt.
I can do the something like this below code ?
static void cloneShape(XMLSlideShow slideShow, int slideNumber, String textBoxId) {
Optional<XSLFShape> textBoxopt = getShapesByName(slideShow, slideNumber, textBoxId).stream().findFirst();
XSLFAutoShape shapeToBeCloned = (XSLFAutoShape) textBoxopt.get();
XSLFShapeContainer slide = slideShow.getSlides().get(slideNumber);
XSLFAutoShape shape1 = slide.createAutoShape(***shapeToBeCloned***);
There is not any clone method for XSLFShapes. And even if it would, then there is not any method to add a cloned XSLFShape to the XSSFSheet (slide). There is XSSFSheet.addShape(XSLFShape shape) but this does nothing but throwing UnsupportedOperationException. I love the sense of humor of the apache poi developers.
So if one want copy a shape of a slide, then one only is able using the underlying objects. The class org.apache.xmlbeans.XmlObject provides a copy method which makes a deep copy of the XML. Then that copy needs to be added into the shape tree of the slide. Then the shape tree of the slide needs to be new initialized. After that the high level object of the shape can be got from XSSFSheet.getShapes(). Unfortunately most of the needed methods are not public. So reflection needs to be used.
Following code shows one way to do this. It simply clones all shapes except group shapes and graphical object frame shapes in each slide of the given PPTIn.pptx.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.sl.usermodel.*;
import org.apache.poi.xslf.usermodel.*;
import java.util.List;
import java.util.ArrayList;
public class PowerPointCloneShape {
static List<XSLFShape> getShapesByName(XMLSlideShow slideShow, String shapeName) {
List<XSLFShape> shapes = new ArrayList<XSLFShape>();
for (XSLFSlide slide : slideShow.getSlides()) {
for (XSLFShape shape : slide.getShapes()) {
//System.out.println(shape.getShapeName());
if (shapeName.equals(shape.getShapeName())) {
shapes.add(shape);
}
}
}
return shapes;
}
static List<XSLFShape> getShapes(XMLSlideShow slideShow) {
List<XSLFShape> shapes = new ArrayList<XSLFShape>();
for (XSLFSlide slide : slideShow.getSlides()) {
for (XSLFShape shape : slide.getShapes()) {
shapes.add(shape);
}
}
return shapes;
}
// method to new initialize drawing and shapes in sheet from updated shape tree
static void initDrawingAndShapes(XSLFSheet sheet) throws Exception {
java.lang.reflect.Field _drawing = XSLFSheet.class.getDeclaredField("_drawing");
_drawing.setAccessible(true);
_drawing.set(sheet, null);
java.lang.reflect.Field _shapes = XSLFSheet.class.getDeclaredField("_shapes");
_shapes.setAccessible(true);
_shapes.set(sheet, null);
java.lang.reflect.Method initDrawingAndShapes = XSLFSheet.class.getDeclaredMethod("initDrawingAndShapes");
initDrawingAndShapes.setAccessible(true);
initDrawingAndShapes.invoke(sheet);
}
// method to allocate the next shape ID in sheet
static int allocateShapeId(XSLFSheet sheet) throws Exception {
java.lang.reflect.Method allocateShapeId = XSLFSheet.class.getDeclaredMethod("allocateShapeId");
allocateShapeId.setAccessible(true);
int nextId = (int)allocateShapeId.invoke(sheet);
return nextId;
}
// method to get the shape tree of sheet
static org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape getSpTree(XSLFSheet sheet) throws Exception {
java.lang.reflect.Field _spTree = XSLFSheet.class.getDeclaredField("_spTree");
_spTree.setAccessible(true);
org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = (org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape)_spTree.get(sheet);
return spTree;
}
// method to clone a shape contained in a sheet
static XSLFShape cloneShape(XSLFShape shape) throws Exception {
// first clone low level XML
org.apache.xmlbeans.XmlObject xmlObject = shape.getXmlObject();
org.apache.xmlbeans.XmlObject xmlClone = xmlObject.copy();
//System.out.println(xmlClone.getClass().getName());
// then create high level clone shapes
if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTShape) { // simple shape
org.openxmlformats.schemas.presentationml.x2006.main.CTShape ctShapeClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTShape)xmlClone;
// get sheet
XSLFSheet sheet = shape.getSheet();
// set new ID
int nextId = allocateShapeId(sheet);
ctShapeClone.getNvSpPr().getCNvPr().setId(nextId);
// add into the shape tree of sheet
org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
spTree.addNewSp();
spTree.setSpArray​(spTree.sizeOfSpArray()-1, ctShapeClone);
// new initialize drawing and shapes in sheet
initDrawingAndShapes(sheet);
// get clone
XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
return clone;
} else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTPicture) { // picture shape
org.openxmlformats.schemas.presentationml.x2006.main.CTPicture ctPictureClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTPicture)xmlClone;
XSLFSheet sheet = shape.getSheet();
int nextId = allocateShapeId(sheet);
ctPictureClone.getNvPicPr().getCNvPr().setId(nextId);
org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
spTree.addNewPic();
spTree.setPicArray​(spTree.sizeOfPicArray()-1, ctPictureClone);
initDrawingAndShapes(sheet);
XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
return clone;
} else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTConnector) { // connector shape
org.openxmlformats.schemas.presentationml.x2006.main.CTConnector ctConnectorClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTConnector)xmlClone;
XSLFSheet sheet = shape.getSheet();
int nextId = allocateShapeId(sheet);
ctConnectorClone.getNvCxnSpPr().getCNvPr().setId(nextId);
org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
spTree.addNewCxnSp();
spTree.setCxnSpArray​(spTree.sizeOfCxnSpArray()-1, ctConnectorClone);
initDrawingAndShapes(sheet);
XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
// connector has connecting points which also simple are cloned but would must be new adjusted
return clone;
} else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape) { // group shape
// cloning is not that simple
} else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame) { // graphical object frame shape (table, chart, diagram, ...)
// cloning is not that simple
}
return null;
}
public static void main(String args[]) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTIn.pptx"));
//List<XSLFShape> shapes = getShapesByName(slideShow, "Textbox 1");
List<XSLFShape> shapes = getShapes(slideShow);
System.out.println(shapes);
//if (shapes.size() > 0 ) {
//XSLFShape shape = shapes.get(0);
for (XSLFShape shape : shapes) {
System.out.println("source: " + shape);
XSLFShape clone = cloneShape(shape);
System.out.println("clone: " + clone);
if (clone instanceof PlaceableShape) {
if (!clone.isPlaceholder() || clone.getPlaceholder() == Placeholder.CONTENT) { // do not change anchor of placeholders except content
PlaceableShape placeableShape = (PlaceableShape)clone;
java.awt.geom.Rectangle2D anchor = shape.getAnchor();
placeableShape.setAnchor(new java.awt.geom.Rectangle2D.Double(anchor.getX()+100, anchor.getY()+100, anchor.getWidth(), anchor.getHeight()));
//System.out.println(clone.getAnchor());
}
}
if (clone instanceof XSLFTextShape) {
XSLFTextShape textShape = (XSLFTextShape)clone;
if (textShape.getTextParagraphs().size() > 0 && textShape.getTextParagraphs().get(0).getTextRuns().size() > 0) {
textShape.getTextParagraphs().get(0).getTextRuns().get(0).setText("new text");
} else {
textShape.setText("new text");
}
//System.out.println(textShape.getText());
}
}
FileOutputStream out = new FileOutputStream("./PPTOut.pptx");
slideShow.write(out);
out.close();
slideShow.close();
}
}
If after cloning you got warning shape id X has been already used,
try clear shapeIds before calling initDrawingAndShapes:
static void clearShapeIds(XSLFSheet sheet) {
Field _shapeIds = XSLFSheet.class.getDeclaredField("shapeIds");
_shapeIds.setAccessible(true);
((SparseBitSet) _shapeIds.get(sheet)).clear();
}

How to use iText get pdf rendered with multiple fonts

I'm using iText to parse html to pdf with both english and chinese characters. I'm using
// for pdf rendering
compile group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.1'
// for pdf rendering
compile group: 'com.itextpdf.tool', name: 'xmlworker', version: '5.5.13.1'
I've already make it possible to get chinese characters parsing not a problem with dependency
// for chinese font in pdf rendering
compile group: 'com.itextpdf', name: 'itext-asian', version: '5.2.0'
and customized font provider
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont bfChinese = null;
try {
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
LOG.error("Not found STSong-Light,maybe com.itextpdf.itext-asian dependency problem");
}
return new Font(bfChinese, size, style);
}
}
and pdf rendring code
public static File html2Pdf(String html, String fileName) {
try {
String path = buildPath(fileName);
// step 1
Document document = new Document(PageSize.A4);
document.setMargins(20, 20, 0, 0);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(path));
// step 3
document.open();
// step 4
InputStream cssInput = null;
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8)), cssInput, new StSongProvider());
// step 5
document.close();
LOG.info("PDF file: {} rendering successfully", path);
return new File(path);
} catch (IOException ex) {
// do something
} catch (DocumentException ex) {
// do something
}
}
But the english characters in the result pdf is not that beautiful without a proper font (all characters are using STSong-Light font). I want to get pdf rendered with chinese characters using STSong-Light and english characters using some fonts that iText supported originally such as Times-Roman.
I found this SO thread makes building a document possible with multiple fonts using FontSelector. But how to make it compatible with pdf creation process? The XMLWorkerHelper.getInstance().parseXHtml api only accept a FontProvider as parameter. Any ideas about this?
The solution is do something on the customized font provider, make it not return only one font, but return font depends on the html cell font-family attribute.
public class StSongProvider extends XMLWorkerFontProvider {
private static final Logger LOG = LoggerFactory.getLogger(StSongProvider.class);
public StSongProvider() {
super(null, null);
}
#Override
public Font getFont(final String fontName, String encoding, float size, final int style) {
BaseFont font = null;
try {
if (StringUtils.equals(fontName, "STSong-Light")) {
font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} else {
font = BaseFont.createFont(FontFactory.TIMES_ROMAN, FontFactory.defaultEncoding, true);
}
} catch (Exception e) {
// do something
}
return new Font(font, size, style);
}
}
With above font provider, and set the style="font-family:STSong-Light attribute of a html cell contains chinese characters to format them, and other english characters will be well formatted with TIMES_ROMAN;

Play! 2 with Java on heroku, save file temporary

At the very begining - sorry for my english.
I'm developing a play-application in java and deploy it to heroku.
I like to create a picture (a QRCode to be precisely), store it temporary and display it on the next page.
I do know about herokus ephemeral filesystem, but if I understand right, on the cedar stack, I am able to create files wherever I like, as long as it's ok, that they won't be stored for a long time. The app just needs to generate a QR, I scan it and the file may be deleted.
It seems as if the file is not created. Any ideas of how I can manage to temporary save and show my QRs?
Controller
public class Application extends Controller {
private static String workingDirectory = "public/images/";
public static Result qrCode() {
String msg = "I am a QR-String";
BufferedImage image = (BufferedImage) QR.stringToImage(msg);
String imgPath = workingDirectory+"posQR.png";
try{
File outputfile = new File(imgPath);
ImageIO.write(image,"png",outputfile);
}catch(IOException e){
e.printStackTrace();
}
return ok(views.html.qrCode.render());
}
}
View qrCode
<img src="#routes.Assets.at("images/posQR.png")">
Edit 1
Stored the image as tempFile an pass it to the view.
On heroku an local the view contains the exact absolute path, but the image won't load.
Any ideas left?
Controller
public class Application extends Controller {
private static String workingDirectory = "public/images/";
public static Result qrCode() {
String msg = "I am a QR-String";
BufferedImage image = (BufferedImage) QR.stringToImage(msg);
File outputfile = null;
String imgPath = workingDirectory+"posQR.png";
try{
outputfile = File.createTempFile("posQR",".png");
ImageIO.write(image,"png",outputfile);
}catch(IOException e){
e.printStackTrace();
}
return ok(views.html.qrCode.render(outputfile.getAbsolutePath()));
}
View qrCode
#(qrPath: String)
...
<img id="qr" src=#qrPath>
Does workingDirectory end with file separator?
String imgPath = workingDirectory+"posQR.png";
This helped me: Play! framework 2.0: How to display multiple image?
Finaly I did it. The trick was not to do src=path but src=getImage(path). Still strange, but now it works.
routes
GET /tmp/*filepath controllers.Application.getImage(filepath: String)
Application
public class Application extends Controller {
public static Result qrCode() {
String msg = "I am a QR-String";
BufferedImage image = (BufferedImage) QR.stringToImage(msg);
File outputfile = null;
try{
outputfile = File.createTempFile("posQR",".png");
ImageIO.write(image,"png",outputfile);
}catch(IOException e){
e.printStackTrace();
}
return ok(views.html.qrCode.render(outputfile.getAbsolutePath()));
}
...
public static Result getImage(String imgPath){
return ok(new File(imgPath));
}
}
view qrCode
#(qrPath: String)
...
<img src="#routes.Application.getImage(qrPath)"/>
Thanks for your help :D

Render image from servlet in flyingsaucer generated pdf

I'm using flyingsaucer to render an xhtml document to pdf through a servlet which returns the generated pdf document. The xhtml document features an image which is requested from another servlet. The image servlet checks who is logged in before returning the appropriate image. The code below shows how the image is requested:
<img height="140" width="140" src="http://localhost:8080/myapp/servlet/DisplayPic" />
My problem is that the http request for the image is from the pdf renderer and not the logged in user so the image servlet doesn't know who's logged in and therefore the desired image is not returned.
I'm currently using the code below to render the xhtml document:
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);
I need to either maintain the user's session when the image servlet is requested or provide the renderer with the image to use for that specific xhtml element. I think the latter can be done using a ReplacedElementFactory but I haven't been able to dig out any example code that can help me.
I've got this working very nicely now. Here's the code.
In my xhtml document i have:
<div class="profile_picture" style="display:block;width:140px;height:140px;" />
(I'm using a div element instead of img as the factory is only used for block level elements)
I render my document using:
ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setReplacedElementFactory(new ProfileImageReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);
And i have my own ReplacedElementFactory as below:
public class ProfileImageReplacedElementFactory implements ReplacedElementFactory {
private final ReplacedElementFactory superFactory;
public ProfileImageReplacedElementFactory(ReplacedElementFactory superFactory) {
this.superFactory = superFactory;
}
#Override
public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox,
UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
Element element = blockBox.getElement();
if (element == null) {
return null;
}
String nodeName = element.getNodeName();
String className = element.getAttribute("class");
if ("div".equals(nodeName) && className.contains("profile_picture")) {
InputStream input = null;
try {
input = ...;
byte[] bytes = IOUtils.toByteArray(input);
Image image = Image.getInstance(bytes);
FSImage fsImage = new ITextFSImage(image);
if (fsImage != null) {
if ((cssWidth != -1) || (cssHeight != -1)) {
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
} catch (IOException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} catch (BadElementException e) {
getLogger().error(ExceptionUtils.getStackTrace(e));
} finally {
IOUtils.closeQuietly(input);
}
}
return superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}
#Override
public void reset() {
superFactory.reset();
}
#Override
public void remove(Element e) {
superFactory.remove(e);
}
#Override
public void setFormSubmissionListener(FormSubmissionListener listener) {
superFactory.setFormSubmissionListener(listener);
}
}

Categories