
org.docbook.extensions.xslt20.ImageIntrinsics Maven / Gradle / Ivy
package org.docbook.extensions.xslt20;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.awt.Toolkit;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.lang.Thread;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.nwalsh.annotations.SaxonExtensionFunction;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.StaticProperty;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.value.Int64Value;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.iter.ArrayIterator;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.type.BuiltInAtomicType;
/**
* Saxon extension to obtain the intrinsic size of images.
*
* This class provides a
* Saxon
* extension to find the intrinsic size of images.
*
* The function attempts to load the image with the AWT Image toolkit. If that fails,
* it searches for a bounding box (present in, e.g., EPS and PDF images).
*
*
If the image can be loaded or a bounding box is found, the width and height
* of the image are returned.
*
*
Copyright © 2002-2015 Norman Walsh.
*
* @author Norman Walsh
* [email protected]
*/
@SaxonExtensionFunction
public class ImageIntrinsics extends ExtensionFunctionDefinition {
private static final StructuredQName qName =
new StructuredQName("", "http://docbook.org/extensions/xslt20", "image-properties");
private static final Pattern dimPatn = Pattern.compile("^(\\d+(\\.\\d*)?)(.*)$");
@Override
public StructuredQName getFunctionQName() {
return qName;
}
@Override
public int getMinimumNumberOfArguments() {
return 1;
}
@Override
public int getMaximumNumberOfArguments() {
return 1;
}
@Override
public SequenceType[] getArgumentTypes() {
return new SequenceType[]{SequenceType.SINGLE_STRING};
}
@Override
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
return SequenceType.makeSequenceType(BuiltInAtomicType.INTEGER, StaticProperty.ALLOWS_ZERO_OR_MORE);
}
public ExtensionFunctionCall makeCallExpression() {
return new IntrinsicsCall();
}
private class IntrinsicsCall extends ExtensionFunctionCall implements ImageObserver {
boolean imageLoaded = false;
boolean imageFailed = false;
Image image = null;
int width = -1;
int depth = -1;
public SequenceIterator call(SequenceIterator[] arguments, XPathContext context) throws XPathException {
String imageFn = ((StringValue) arguments[0].next()).getStringValue();
imageLoaded = false;
imageFailed = false;
image = null;
width = -1;
depth = -1;
System.setProperty("java.awt.headless","true");
try {
URL url = new URL(imageFn);
image = Toolkit.getDefaultToolkit().getImage (url);
} catch (MalformedURLException mue) {
image = Toolkit.getDefaultToolkit().getImage (imageFn);
}
width = image.getWidth(this);
depth = image.getHeight(this);
while (!imageFailed && (width == -1 || depth == -1)) {
try {
java.lang.Thread.currentThread().sleep(50);
} catch (Exception e) {
// nop;
}
width = image.getWidth(this);
depth = image.getHeight(this);
}
image.flush();
if ((width == -1 || depth == -1) && imageFailed) {
// Maybe it's an EPS or PDF?
// FIXME: this code is crude
BufferedReader ir = null;
String line = null;
int lineLimit = 100;
try {
ir = new BufferedReader(new FileReader(new File(imageFn)));
line = ir.readLine();
if (line != null && line.startsWith("%PDF-")) {
// We've got a PDF!
while (lineLimit > 0 && line != null) {
lineLimit--;
if (line.startsWith("/CropBox [")) {
line = line.substring(10);
if (line.indexOf("]") >= 0) {
line = line.substring(0, line.indexOf("]"));
}
parseBox(line);
lineLimit = 0;
}
line = ir.readLine();
}
} else if (line != null
&& line.startsWith("%!")
&& line.indexOf(" EPSF-") > 0) {
// We've got an EPS!
while (lineLimit > 0 && line != null) {
lineLimit--;
if (line.startsWith("%%BoundingBox: ")) {
line = line.substring(15);
parseBox(line);
lineLimit = 0;
}
line = ir.readLine();
}
} else if (line != null
&& (line.startsWith(" 0 && line != null) {
lineLimit--;
if (line.contains("width=") && width == -1) {
int pos = line.indexOf("width=");
String ex = line.substring(pos+7);
int sqpos = ex.indexOf("'");
int dqpos = ex.indexOf("\"");
pos = sqpos < dqpos && sqpos >= 0 ? sqpos : dqpos;
width = convertUnits(ex.substring(0, pos));
}
if (line.contains("height=") && depth == -1) {
int pos = line.indexOf("height=");
String ex = line.substring(pos+8);
int sqpos = ex.indexOf("'");
int dqpos = ex.indexOf("\"");
pos = sqpos < dqpos && sqpos >= 0 ? sqpos : dqpos;
depth = convertUnits(ex.substring(0, pos));
}
if (width >= 0 && depth >= 0) {
lineLimit = 0;
}
line = ir.readLine();
}
} else {
System.err.println("Failed to interpret image: " + imageFn);
}
} catch (Exception e) {
System.err.println("Failed to load image: " + imageFn);
width = -1;
depth = -1;
}
if (ir != null) {
try {
ir.close();
} catch (Exception e) {
// nop;
}
}
}
if (width >= 0) {
Int64Value[] props = { new Int64Value(width), new Int64Value(depth) };
return new ArrayIterator(props);
} else {
return EmptyIterator.getInstance();
}
}
private void parseBox(String line) {
int [] corners = new int [4];
int count = 0;
StringTokenizer st = new StringTokenizer(line);
while (count < 4 && st.hasMoreTokens()) {
try {
corners[count++] = Integer.parseInt(st.nextToken());
} catch (Exception e) {
// nop;
}
}
width = corners[2] - corners[0];
depth = corners[3] - corners[1];
}
public boolean imageUpdate(Image img, int infoflags,
int x, int y, int width, int height) {
if (((infoflags & ImageObserver.ERROR) == ImageObserver.ERROR)
|| ((infoflags & ImageObserver.ABORT) == ImageObserver.ABORT)) {
imageFailed = true;
return false;
}
// I really only care about the width and height, but if I return false as
// soon as those are available, the BufferedInputStream behind the loader
// gets closed too early.
if ((infoflags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) {
return false;
} else {
return true;
}
}
public int convertUnits(String dim) {
Matcher matcher = dimPatn.matcher(dim);
if (matcher.matches()) {
String magnitude = matcher.group(1);
String units = matcher.group(3);
Double d = Double.parseDouble(magnitude);
d = d * unitsScale(units);
return d.intValue();
} else {
if (dim.matches("^\\d+(\\.\\d*)?$")) {
Double d = Double.parseDouble(dim);
return d.intValue();
} else {
throw new UnsupportedOperationException("Cannot parse " + dim + " as a dimension");
}
}
}
public double unitsScale(String units) {
// N.B. The actual numbers aren't that important because SVG can scale
if ("pt".equals(units)) {
return 96.0 / 72.0;
} else if ("in".equals(units)) {
return 96.0;
} else if ("cm".equals(units)) {
return 96.0 / 2.54;
} else if ("mm".equals(units)) {
return 96.0 / 25.4;
} else if ("px".equals(units)) {
return 1;
} else {
return 1;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy