Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions fastexcel-writer/src/main/java/org/dhatim/fastexcel/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2016 Dhatim.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dhatim.fastexcel;

/**
* Supported image types for embedding in worksheets.
*/
public enum ImageType {
PNG("png", "image/png", false),
JPEG("jpeg", "image/jpeg", false),
GIF("gif", "image/gif", false),
SVG("svg", "image/svg+xml", true);

private final String extension;
private final String contentType;
private final boolean vector;

ImageType(String extension, String contentType, boolean vector) {
this.extension = extension;
this.contentType = contentType;
this.vector = vector;
}

/**
* Check if this is a vector image format (e.g., SVG).
*
* @return true if vector format, false if raster
*/
public boolean isVector() {
return vector;
}

/**
* Get the file extension for this image type.
*
* @return File extension without the dot (e.g., "png", "jpeg")
*/
public String getExtension() {
return extension;
}

/**
* Get the MIME content type for this image type.
*
* @return MIME content type (e.g., "image/png")
*/
public String getContentType() {
return contentType;
}

/**
* Detect image type from byte array header.
*
* @param data Image bytes
* @return Detected ImageType
* @throws IllegalArgumentException if the image format is not supported or data is invalid
*/
public static ImageType fromBytes(byte[] data) {
if (data == null || data.length < 8) {
throw new IllegalArgumentException("Invalid image data: data is null or too short");
}
// PNG signature: 89 50 4E 47 0D 0A 1A 0A
if (data[0] == (byte) 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47
&& data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A) {
return PNG;
}
// JPEG signature: FF D8 FF
if (data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[2] == (byte) 0xFF) {
return JPEG;
}
// GIF signature: 47 49 46 38 (GIF8)
if (data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38) {
return GIF;
}
// SVG detection: look for <?xml or <svg in the beginning (text-based format)
String header = new String(data, 0, Math.min(data.length, 256), java.nio.charset.StandardCharsets.UTF_8);
if (header.contains("<svg") || (header.contains("<?xml") && header.contains("<svg"))) {
return SVG;
}
throw new IllegalArgumentException("Unsupported image format. Supported formats: PNG, JPEG, GIF, SVG");
}
}
158 changes: 158 additions & 0 deletions fastexcel-writer/src/main/java/org/dhatim/fastexcel/Picture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2016 Dhatim.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dhatim.fastexcel;

import java.io.IOException;

/**
* Represents an image embedded in a worksheet.
*/
public class Picture {

private final int id;
private final String name;
private final PictureAnchor anchor;
private final byte[] imageData;
private final ImageType imageType;
private final boolean lockAspectRatio;

// Relationship ID (set when writing)
private String relationshipId;

Picture(int id, String name, PictureAnchor anchor, byte[] imageData, ImageType imageType,
boolean lockAspectRatio) {
this.id = id;
this.name = name != null ? name : "Picture " + id;
this.anchor = anchor;
this.imageData = imageData;
this.imageType = imageType;
this.lockAspectRatio = lockAspectRatio;
}

void setRelationshipId(String relationshipId) {
this.relationshipId = relationshipId;
}

String getRelationshipId() {
return relationshipId;
}

int getId() {
return id;
}

byte[] getImageData() {
return imageData;
}

ImageType getImageType() {
return imageType;
}

PictureAnchor getAnchor() {
return anchor;
}

/**
* Get the name of this picture.
*
* @return Picture name
*/
public String getName() {
return name;
}

/**
* Write the picture element to the drawing XML.
*/
void write(Writer w) throws IOException {
if (anchor.isTwoCellAnchor()) {
writeTwoCellAnchor(w);
} else {
writeOneCellAnchor(w);
}
}

private void writeOneCellAnchor(Writer w) throws IOException {
w.append("<xdr:oneCellAnchor>");
anchor.writeFrom(w);
anchor.writeExt(w);
writePicElement(w);
w.append("<xdr:clientData/>");
w.append("</xdr:oneCellAnchor>");
}

private void writeTwoCellAnchor(Writer w) throws IOException {
w.append("<xdr:twoCellAnchor>");
anchor.writeFrom(w);
anchor.writeTo(w);
writePicElement(w);
w.append("<xdr:clientData/>");
w.append("</xdr:twoCellAnchor>");
}

private void writePicElement(Writer w) throws IOException {
w.append("<xdr:pic>");

// Non-visual properties
w.append("<xdr:nvPicPr>");
w.append("<xdr:cNvPr id=\"").append(id).append("\" name=\"");
w.appendEscaped(name);
w.append("\"/>");
w.append("<xdr:cNvPicPr>");
if (lockAspectRatio) {
w.append("<a:picLocks noChangeAspect=\"1\"/>");
}
w.append("</xdr:cNvPicPr>");
w.append("</xdr:nvPicPr>");

// Blip fill (image reference)
w.append("<xdr:blipFill>");
if (imageType == ImageType.SVG) {
// SVG uses extension element with svgBlip (Office 2016+)
w.append("<a:blip xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">");
w.append("<a:extLst>");
w.append("<a:ext uri=\"{96DAC541-7B7A-43D3-8B79-37D633B846F1}\">");
w.append("<asvg:svgBlip xmlns:asvg=\"http://schemas.microsoft.com/office/drawing/2016/SVG/main\" ");
w.append("r:embed=\"").append(relationshipId).append("\"/>");
w.append("</a:ext>");
w.append("</a:extLst>");
w.append("</a:blip>");
} else {
// Raster images (PNG, JPEG, GIF)
w.append("<a:blip xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" ");
w.append("r:embed=\"").append(relationshipId).append("\"/>");
}
w.append("<a:stretch><a:fillRect/></a:stretch>");
w.append("</xdr:blipFill>");

// Shape properties
w.append("<xdr:spPr>");
w.append("<a:xfrm>");
w.append("<a:off x=\"0\" y=\"0\"/>");
if (anchor.isTwoCellAnchor()) {
w.append("<a:ext cx=\"0\" cy=\"0\"/>");
} else {
w.append("<a:ext cx=\"").append(anchor.getWidthEmu()).append("\" cy=\"")
.append(anchor.getHeightEmu()).append("\"/>");
}
w.append("</a:xfrm>");
w.append("<a:prstGeom prst=\"rect\"><a:avLst/></a:prstGeom>");
w.append("</xdr:spPr>");

w.append("</xdr:pic>");
}
}
Loading