Added a new rendering mode "bitmap" (in addition to "speed" and "bitmap") that uses Java2DPainter for the whole page and paints a single bitmap (much like most PCL drivers operate). Quality: "speed" < "quality" < "bitmap", Performance: "speed" > "quality" > "bitmap" (at high page complexity "bitmap" might actually be better), File Size: "speed" < "quality" < "bitmap" Increased bitmap encoding performance by up to 100% (by optimizing the Java code and by supporting the "zeroed row" PCL command) git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@709119 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_0
@@ -21,6 +21,7 @@ package org.apache.fop.render.intermediate; | |||
import java.awt.Color; | |||
import java.awt.Dimension; | |||
import java.awt.Point; | |||
import java.awt.Rectangle; | |||
import java.awt.geom.AffineTransform; | |||
import java.io.FileNotFoundException; | |||
@@ -47,6 +48,8 @@ import org.apache.fop.events.ResourceEventProducer; | |||
import org.apache.fop.render.ImageHandler; | |||
import org.apache.fop.render.ImageHandlerRegistry; | |||
import org.apache.fop.render.RenderingContext; | |||
import org.apache.fop.traits.BorderProps; | |||
import org.apache.fop.traits.RuleStyle; | |||
/** | |||
* Abstract base class for IFPainter implementations. | |||
@@ -195,8 +198,8 @@ public abstract class AbstractIFPainter implements IFPainter { | |||
+ effImage.getInfo() + " (" + effImage.getClass().getName() + ")"); | |||
} | |||
if (log.isDebugEnabled()) { | |||
log.debug("Using ImageHandler: " + handler.getClass().getName()); | |||
if (log.isTraceEnabled()) { | |||
log.trace("Using ImageHandler: " + handler.getClass().getName()); | |||
} | |||
//TODO foreign attributes | |||
@@ -261,6 +264,67 @@ public abstract class AbstractIFPainter implements IFPainter { | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, | |||
BorderProps start, BorderProps end) throws IFException { | |||
if (before != null) { | |||
Rectangle b = new Rectangle( | |||
rect.x, rect.y, | |||
rect.width, before.width); | |||
fillRect(b, before.color); | |||
} | |||
if (end != null) { | |||
Rectangle b = new Rectangle( | |||
rect.x + rect.width - end.width, rect.y, | |||
end.width, rect.height); | |||
fillRect(b, end.color); | |||
} | |||
if (after != null) { | |||
Rectangle b = new Rectangle( | |||
rect.x, rect.y + rect.height - after.width, | |||
rect.width, after.width); | |||
fillRect(b, after.color); | |||
} | |||
if (start != null) { | |||
Rectangle b = new Rectangle( | |||
rect.x, rect.y, | |||
start.width, rect.height); | |||
fillRect(b, start.color); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) | |||
throws IFException { | |||
Rectangle rect = getLineBoundingBox(start, end, width); | |||
fillRect(rect, color); | |||
} | |||
/** | |||
* Calculates the bounding box for a line. Currently, only horizontal and vertical lines | |||
* are needed and supported. | |||
* @param start the starting point of the line (coordinates in mpt) | |||
* @param end the ending point of the line (coordinates in mpt) | |||
* @param width the line width (in mpt) | |||
* @return the bounding box (coordinates in mpt) | |||
*/ | |||
protected Rectangle getLineBoundingBox(Point start, Point end, int width) { | |||
if (start.y == end.y) { | |||
int topy = start.y - width / 2; | |||
return new Rectangle( | |||
start.x, topy, | |||
end.x - start.x, width); | |||
} else if (start.x == end.y) { | |||
int leftx = start.x - width / 2; | |||
return new Rectangle( | |||
leftx, start.x, | |||
width, end.y - start.y); | |||
} else { | |||
throw new IllegalArgumentException( | |||
"Only horizontal or vertical lines are supported at the moment."); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void setFont(String family, String style, Integer weight, String variant, Integer size, | |||
Color color) throws IFException { |
@@ -32,9 +32,13 @@ import java.awt.image.RenderedImage; | |||
public class DefaultMonochromeBitmapConverter implements | |||
MonochromeBitmapConverter { | |||
private boolean quality = false; | |||
/** {@inheritDoc} */ | |||
public void setHint(String name, String value) { | |||
//ignore, not supported | |||
if ("quality".equalsIgnoreCase(name)) { | |||
quality = "true".equalsIgnoreCase(value); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@@ -42,8 +46,16 @@ public class DefaultMonochromeBitmapConverter implements | |||
BufferedImage buf = new BufferedImage(img.getWidth(), img.getHeight(), | |||
BufferedImage.TYPE_BYTE_BINARY); | |||
RenderingHints hints = new RenderingHints(null); | |||
//This hint doesn't seem to make a difference :-( | |||
hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); | |||
//These hints don't seem to make a difference :-( Not seeing any dithering on Sun Java. | |||
hints.put(RenderingHints.KEY_DITHERING, | |||
RenderingHints.VALUE_DITHER_ENABLE); | |||
if (quality) { | |||
hints.put(RenderingHints.KEY_RENDERING, | |||
RenderingHints.VALUE_RENDER_QUALITY); | |||
hints.put(RenderingHints.KEY_COLOR_RENDERING, | |||
RenderingHints.VALUE_COLOR_RENDER_QUALITY); | |||
} | |||
ColorConvertOp op = new ColorConvertOp( | |||
ColorSpace.getInstance(ColorSpace.CS_GRAY), hints); | |||
op.filter(img, buf); |
@@ -19,18 +19,27 @@ | |||
package org.apache.fop.render.pcl; | |||
import java.awt.Color; | |||
import java.awt.Dimension; | |||
import java.awt.Graphics2D; | |||
import java.awt.Rectangle; | |||
import java.awt.RenderingHints; | |||
import java.awt.image.BufferedImage; | |||
import java.io.IOException; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.xmlgraphics.util.UnitConv; | |||
import org.apache.fop.apps.FOUserAgent; | |||
import org.apache.fop.apps.FopFactoryConfigurator; | |||
import org.apache.fop.apps.MimeConstants; | |||
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; | |||
import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; | |||
import org.apache.fop.render.intermediate.IFException; | |||
import org.apache.fop.render.intermediate.IFPainter; | |||
import org.apache.fop.render.java2d.Java2DPainter; | |||
/** | |||
* {@code IFDocumentHandler} implementation that produces PCL 5. | |||
@@ -54,6 +63,10 @@ public class PCLDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
/** contains the pageHeight of the last printed page */ | |||
private long pageHeight = 0; | |||
/** the current page image (only set when all-bitmap painting is activated) */ | |||
private BufferedImage currentImage; | |||
/** | |||
* Default constructor. | |||
*/ | |||
@@ -190,12 +203,74 @@ public class PCLDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
/** {@inheritDoc} */ | |||
public IFPainter startPageContent() throws IFException { | |||
return new PCLPainter(this, this.currentPageDefinition); | |||
if (pclUtil.getRenderingMode() == PCLRenderingMode.BITMAP) { | |||
return createAllBitmapPainter(); | |||
} else { | |||
return new PCLPainter(this, this.currentPageDefinition); | |||
} | |||
} | |||
private IFPainter createAllBitmapPainter() { | |||
double scale = gen.getMaximumBitmapResolution() | |||
/ FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION; | |||
Rectangle printArea = this.currentPageDefinition.getLogicalPageRect(); | |||
int bitmapWidth = (int)Math.ceil( | |||
UnitConv.mpt2px(printArea.width, gen.getMaximumBitmapResolution())); | |||
int bitmapHeight = (int)Math.ceil( | |||
UnitConv.mpt2px(printArea.height, gen.getMaximumBitmapResolution())); | |||
this.currentImage = createBufferedImage(bitmapWidth, bitmapHeight); | |||
Graphics2D graphics2D = this.currentImage.createGraphics(); | |||
if (!PCLGenerator.isJAIAvailable()) { | |||
RenderingHints hints = new RenderingHints(null); | |||
//These hints don't seem to make a difference :-( Not seeing any dithering on Sun Java. | |||
hints.put(RenderingHints.KEY_DITHERING, | |||
RenderingHints.VALUE_DITHER_ENABLE); | |||
graphics2D.addRenderingHints(hints); | |||
} | |||
//Ensure white page background | |||
graphics2D.setBackground(Color.WHITE); | |||
graphics2D.clearRect(0, 0, bitmapWidth, bitmapHeight); | |||
graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, | |||
RenderingHints.VALUE_FRACTIONALMETRICS_ON); | |||
graphics2D.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, | |||
RenderingHints.VALUE_STROKE_PURE); | |||
graphics2D.scale(scale / 1000f, scale / 1000f); | |||
graphics2D.translate(-printArea.x, -printArea.y); | |||
return new Java2DPainter(graphics2D, getUserAgent(), getFontInfo()); | |||
} | |||
private BufferedImage createBufferedImage(int bitmapWidth, int bitmapHeight) { | |||
int bitmapType; | |||
if (PCLGenerator.isJAIAvailable()) { | |||
//TYPE_BYTE_GRAY was used to work around the lack of dithering when using | |||
//TYPE_BYTE_BINARY. Adding RenderingHints didn't help. | |||
bitmapType = BufferedImage.TYPE_BYTE_GRAY; | |||
//bitmapType = BufferedImage.TYPE_INT_RGB; //Use to enable Batik gradients | |||
} else { | |||
bitmapType = BufferedImage.TYPE_BYTE_BINARY; | |||
} | |||
return new BufferedImage( | |||
bitmapWidth, bitmapHeight, bitmapType); | |||
} | |||
/** {@inheritDoc} */ | |||
public void endPageContent() throws IFException { | |||
//nop | |||
if (this.currentImage != null) { | |||
try { | |||
//ImageWriterUtil.saveAsPNG(this.currentImage, new java.io.File("D:/page.png")); | |||
Rectangle printArea = this.currentPageDefinition.getLogicalPageRect(); | |||
gen.setCursorPos(0, 0); | |||
gen.paintBitmap(this.currentImage, printArea.getSize(), true); | |||
} catch (IOException ioe) { | |||
throw new IFException("I/O error while encoding page image", ioe); | |||
} finally { | |||
this.currentImage = null; | |||
} | |||
} | |||
} | |||
/** {@inheritDoc} */ |
@@ -30,8 +30,10 @@ import java.awt.image.ByteLookupTable; | |||
import java.awt.image.ColorConvertOp; | |||
import java.awt.image.ColorModel; | |||
import java.awt.image.DataBuffer; | |||
import java.awt.image.DataBufferByte; | |||
import java.awt.image.IndexColorModel; | |||
import java.awt.image.LookupOp; | |||
import java.awt.image.MultiPixelPackedSampleModel; | |||
import java.awt.image.Raster; | |||
import java.awt.image.RenderedImage; | |||
import java.awt.image.WritableRaster; | |||
@@ -604,6 +606,26 @@ public class PCLGenerator { | |||
return (img.getColorModel().getNumColorComponents() == 1); | |||
} | |||
private static int jaiAvailable = -1; //no synchronization necessary, not critical | |||
/** | |||
* Indicates whether JAI is available. JAI has shown to be reliable when dithering a | |||
* grayscale or color image to monochrome bitmaps (1-bit). | |||
* @return true if JAI is available | |||
*/ | |||
public static boolean isJAIAvailable() { | |||
if (jaiAvailable < 0) { | |||
try { | |||
String clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter"; | |||
Class.forName(clName); | |||
jaiAvailable = 1; | |||
} catch (ClassNotFoundException cnfe) { | |||
jaiAvailable = 0; | |||
} | |||
} | |||
return (jaiAvailable > 0); | |||
} | |||
private MonochromeBitmapConverter createMonochromeBitmapConverter() { | |||
MonochromeBitmapConverter converter = null; | |||
try { | |||
@@ -839,6 +861,16 @@ public class PCLGenerator { | |||
g2d.clearRect(0, 0, effDim.width, effDim.height); | |||
} | |||
private int toGray(int rgb) { | |||
// see http://www.jguru.com/faq/view.jsp?EID=221919 | |||
double greyVal = 0.072169d * (rgb & 0xff); | |||
rgb >>= 8; | |||
greyVal += 0.715160d * (rgb & 0xff); | |||
rgb >>= 8; | |||
greyVal += 0.212671d * (rgb & 0xff); | |||
return (int)greyVal; | |||
} | |||
/** | |||
* Paint a bitmap at the current cursor position. The bitmap must be a monochrome | |||
* (1-bit) bitmap image. | |||
@@ -850,79 +882,159 @@ public class PCLGenerator { | |||
if (!isValidPCLResolution(resolution)) { | |||
throw new IllegalArgumentException("Invalid PCL resolution: " + resolution); | |||
} | |||
setRasterGraphicsResolution(resolution); | |||
writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A"); | |||
Raster raster = img.getData(); | |||
boolean monochrome = isMonochromeImage(img); | |||
if (!monochrome) { | |||
throw new IllegalArgumentException("img must be a monochrome image"); | |||
} | |||
int x = 0; | |||
int y = 0; | |||
int imgw = img.getWidth(); | |||
int imgh = img.getHeight(); | |||
int bytewidth = (imgw / 8); | |||
if ((imgw % 8) != 0) { | |||
bytewidth++; | |||
} | |||
byte ib; | |||
byte[] rle = new byte[bytewidth * 2]; //compressed (RLE) | |||
byte[] uncompressed = new byte[bytewidth]; //uncompressed | |||
int lastcount = -1; | |||
byte lastbyte = 0; | |||
int rlewidth = 0; | |||
setRasterGraphicsResolution(resolution); | |||
writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A"); | |||
Raster raster = img.getData(); | |||
Encoder encoder = new Encoder(img); | |||
// Transfer graphics data | |||
for (y = 0; y < imgh; y++) { | |||
ib = 0; | |||
for (x = 0; x < imgw; x++) { | |||
int sample = raster.getSample(x, y, 0); | |||
//Set image bit for black | |||
if ((sample == 0)) { | |||
ib |= (1 << (7 - (x % 8))); | |||
} | |||
//RLE encoding | |||
if ((x % 8) == 7 || ((x + 1) == imgw)) { | |||
if (rlewidth < bytewidth) { | |||
if (lastcount >= 0) { | |||
if (ib == lastbyte) { | |||
lastcount++; | |||
} else { | |||
rle[rlewidth++] = (byte)(lastcount & 0xFF); | |||
rle[rlewidth++] = lastbyte; | |||
lastbyte = ib; | |||
lastcount = 0; | |||
} | |||
int imgw = img.getWidth(); | |||
IndexColorModel cm = (IndexColorModel)img.getColorModel(); | |||
if (cm.getTransferType() == DataBuffer.TYPE_BYTE) { | |||
DataBufferByte dataBuffer = (DataBufferByte)raster.getDataBuffer(); | |||
MultiPixelPackedSampleModel packedSampleModel = new MultiPixelPackedSampleModel( | |||
DataBuffer.TYPE_BYTE, img.getWidth(), img.getHeight(), 1); | |||
if (img.getSampleModel().equals(packedSampleModel) | |||
&& dataBuffer.getNumBanks() == 1) { | |||
//Optimized packed encoding | |||
byte[] buf = dataBuffer.getData(); | |||
int scanlineStride = packedSampleModel.getScanlineStride(); | |||
int idx = 0; | |||
int c0 = toGray(cm.getRGB(0)); | |||
int c1 = toGray(cm.getRGB(1)); | |||
boolean zeroIsWhite = c0 > c1; | |||
for (int y = 0, maxy = img.getHeight(); y < maxy; y++) { | |||
for (int x = 0, maxx = scanlineStride; x < maxx; x++) { | |||
if (zeroIsWhite) { | |||
encoder.add8Bits(buf[idx]); | |||
} else { | |||
lastbyte = ib; | |||
lastcount = 0; | |||
} | |||
if (lastcount == 255 || ((x + 1) == imgw)) { | |||
rle[rlewidth++] = (byte)(lastcount & 0xFF); | |||
rle[rlewidth++] = lastbyte; | |||
lastbyte = 0; | |||
lastcount = -1; | |||
encoder.add8Bits((byte)~buf[idx]); | |||
} | |||
idx++; | |||
} | |||
uncompressed[x / 8] = ib; | |||
ib = 0; | |||
encoder.endLine(); | |||
} | |||
} else { | |||
//Optimized non-packed encoding | |||
for (int y = 0, maxy = img.getHeight(); y < maxy; y++) { | |||
byte[] line = (byte[])raster.getDataElements(0, y, imgw, 1, null); | |||
for (int x = 0, maxx = imgw; x < maxx; x++) { | |||
encoder.addBit(line[x] == 0); | |||
} | |||
encoder.endLine(); | |||
} | |||
} | |||
} else { | |||
//Safe but slow fallback | |||
for (int y = 0, maxy = img.getHeight(); y < maxy; y++) { | |||
for (int x = 0, maxx = imgw; x < maxx; x++) { | |||
int sample = raster.getSample(x, y, 0); | |||
encoder.addBit(sample == 0); | |||
} | |||
encoder.endLine(); | |||
} | |||
} | |||
// End raster graphics | |||
writeCommand("*rB"); | |||
} | |||
private class Encoder { | |||
private int imgw; | |||
private int bytewidth; | |||
private byte[] rle; //compressed (RLE) | |||
private byte[] uncompressed; //uncompressed | |||
private int lastcount = -1; | |||
private byte lastbyte = 0; | |||
private int rlewidth = 0; | |||
private byte ib = 0; //current image bits | |||
private int x = 0; | |||
private boolean zeroRow = true; | |||
public Encoder(RenderedImage img) { | |||
imgw = img.getWidth(); | |||
bytewidth = (imgw / 8); | |||
if ((imgw % 8) != 0) { | |||
bytewidth++; | |||
} | |||
rle = new byte[bytewidth * 2]; | |||
uncompressed = new byte[bytewidth]; | |||
} | |||
public void addBit(boolean bit) { | |||
//Set image bit for black | |||
if (bit) { | |||
ib |= 1; | |||
} | |||
//RLE encoding | |||
if ((x % 8) == 7 || ((x + 1) == imgw)) { | |||
finishedByte(); | |||
} else { | |||
ib <<= 1; | |||
} | |||
x++; | |||
} | |||
public void add8Bits(byte b) { | |||
ib = b; | |||
finishedByte(); | |||
x += 8; | |||
} | |||
private void finishedByte() { | |||
if (rlewidth < bytewidth) { | |||
if (lastcount >= 0) { | |||
if (ib == lastbyte) { | |||
lastcount++; | |||
} else { | |||
rle[rlewidth++] = (byte)(lastcount & 0xFF); | |||
rle[rlewidth++] = lastbyte; | |||
lastbyte = ib; | |||
lastcount = 0; | |||
} | |||
} else { | |||
lastbyte = ib; | |||
lastcount = 0; | |||
} | |||
if (lastcount == 255 || ((x + 1) == imgw)) { | |||
rle[rlewidth++] = (byte)(lastcount & 0xFF); | |||
rle[rlewidth++] = lastbyte; | |||
lastbyte = 0; | |||
lastcount = -1; | |||
} | |||
} | |||
uncompressed[x / 8] = ib; | |||
if (ib != 0) { | |||
zeroRow = false; | |||
} | |||
ib = 0; | |||
} | |||
public void endLine() throws IOException { | |||
if (zeroRow) { | |||
writeCommand("*b1Y"); | |||
} else if (rlewidth < bytewidth) { | |||
writeCommand("*b1m" + rlewidth + "W"); | |||
this.out.write(rle, 0, rlewidth); | |||
out.write(rle, 0, rlewidth); | |||
} else { | |||
writeCommand("*b0m" + bytewidth + "W"); | |||
this.out.write(uncompressed); | |||
out.write(uncompressed); | |||
} | |||
lastcount = -1; | |||
rlewidth = 0; | |||
ib = 0; | |||
x = 0; | |||
zeroRow = true; | |||
} | |||
// End raster graphics | |||
writeCommand("*rB"); | |||
} | |||
} |
@@ -114,6 +114,22 @@ public class PCLPageDefinition { | |||
return null; | |||
} | |||
/** | |||
* Returns a page definition based on a page format. | |||
* @param name the name of the page format (ex. "A4" or "Letter") | |||
* @return the page definition or null if no match was found | |||
*/ | |||
public static PCLPageDefinition getPageDefinition(String name) { | |||
Iterator iter = pageDefinitions.iterator(); | |||
while (iter.hasNext()) { | |||
PCLPageDefinition def = (PCLPageDefinition)iter.next(); | |||
if (def.getName().equalsIgnoreCase(name)) { | |||
return def; | |||
} | |||
} | |||
return null; | |||
} | |||
/** @return the default page definition (letter) */ | |||
public static PCLPageDefinition getDefaultPageDefinition() { | |||
return defaultPageDefinition; |
@@ -66,7 +66,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
/** logging instance */ | |||
private static Log log = LogFactory.getLog(PCLPainter.class); | |||
private final boolean DEBUG = false; | |||
private static final boolean DEBUG = false; | |||
private PCLDocumentHandler parent; | |||
@@ -173,10 +173,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
/** {@inheritDoc} */ | |||
public void clipRect(Rectangle rect) throws IFException { | |||
//PCL cannot clip (only HP GL/2 can) | |||
/* | |||
generator.endTextObject(); | |||
generator.clipRect(rect); | |||
*/ | |||
//If you need clipping support, switch to RenderingMode.BITMAP. | |||
} | |||
/** {@inheritDoc} */ | |||
@@ -203,23 +200,97 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
} | |||
/** {@inheritDoc} */ | |||
public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, | |||
BorderProps start, BorderProps end) throws IFException { | |||
public void drawBorderRect(final Rectangle rect, | |||
final BorderProps before, final BorderProps after, | |||
final BorderProps start, final BorderProps end) throws IFException { | |||
if (getPCLUtil().getRenderingMode() == PCLRenderingMode.SPEED) { | |||
super.drawBorderRect(rect, before, after, start, end); | |||
return; | |||
} | |||
if (before != null || after != null || start != null || end != null) { | |||
/* | |||
generator.endTextObject(); | |||
this.borderPainter.drawBorders(rect, before, after, start, end); | |||
*/ | |||
final Rectangle boundingBox = rect; | |||
final Dimension dim = boundingBox.getSize(); | |||
Graphics2DImagePainter painter = new Graphics2DImagePainter() { | |||
public void paint(Graphics2D g2d, Rectangle2D area) { | |||
g2d.translate(-rect.x, -rect.y); | |||
Java2DPainter painter = new Java2DPainter(g2d, | |||
getUserAgent(), parent.getFontInfo(), state); | |||
try { | |||
painter.drawBorderRect(rect, before, after, start, end); | |||
} catch (IFException e) { | |||
//This should never happen with the Java2DPainter | |||
throw new RuntimeException("Unexpected error while painting borders", e); | |||
} | |||
} | |||
public Dimension getImageSize() { | |||
return dim.getSize(); | |||
} | |||
}; | |||
paintMarksAsBitmap(painter, boundingBox); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) | |||
public void drawLine(final Point start, final Point end, | |||
final int width, final Color color, final RuleStyle style) | |||
throws IFException { | |||
/* | |||
generator.endTextObject(); | |||
this.borderPainter.drawLine(start, end, width, color, style); | |||
*/ | |||
if (getPCLUtil().getRenderingMode() == PCLRenderingMode.SPEED) { | |||
super.drawLine(start, end, width, color, style); | |||
return; | |||
} | |||
final Rectangle boundingBox = getLineBoundingBox(start, end, width); | |||
final Dimension dim = boundingBox.getSize(); | |||
Graphics2DImagePainter painter = new Graphics2DImagePainter() { | |||
public void paint(Graphics2D g2d, Rectangle2D area) { | |||
g2d.translate(-boundingBox.x, -boundingBox.y); | |||
Java2DPainter painter = new Java2DPainter(g2d, | |||
getUserAgent(), parent.getFontInfo(), state); | |||
try { | |||
painter.drawLine(start, end, width, color, style); | |||
} catch (IFException e) { | |||
//This should never happen with the Java2DPainter | |||
throw new RuntimeException("Unexpected error while painting a line", e); | |||
} | |||
} | |||
public Dimension getImageSize() { | |||
return dim.getSize(); | |||
} | |||
}; | |||
paintMarksAsBitmap(painter, boundingBox); | |||
} | |||
private void paintMarksAsBitmap(Graphics2DImagePainter painter, Rectangle boundingBox) | |||
throws IFException { | |||
ImageInfo info = new ImageInfo(null, null); | |||
ImageSize size = new ImageSize(); | |||
size.setSizeInMillipoints(boundingBox.width, boundingBox.height); | |||
info.setSize(size); | |||
ImageGraphics2D img = new ImageGraphics2D(info, painter); | |||
Map hints = new java.util.HashMap(); | |||
hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT, | |||
ImageProcessingHints.BITMAP_TYPE_INTENT_GRAY); | |||
PCLRenderingContext context = (PCLRenderingContext)createRenderingContext(); | |||
context.setSourceTransparencyEnabled(true); | |||
try { | |||
drawImage(img, boundingBox, context, true, hints); | |||
} catch (IOException ioe) { | |||
throw new IFException( | |||
"I/O error while painting marks using a bitmap", ioe); | |||
} catch (ImageException ie) { | |||
throw new IFException( | |||
"Error while painting marks using a bitmap", ie); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@@ -246,8 +317,6 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
} | |||
} catch (IOException ioe) { | |||
throw new IFException("I/O error in drawText()", ioe); | |||
} catch (ImageException ime) { | |||
throw new IFException("Image processing error in drawText()", ime); | |||
} | |||
} | |||
@@ -304,7 +373,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
private static final double SAFETY_MARGIN_FACTOR = 0.05; | |||
private Rectangle getTextBoundingRect(int x, int y, int[] dx, int[] dy, String text, | |||
private Rectangle getTextBoundingBox(int x, int y, int[] dx, int[] dy, String text, | |||
Font font, FontMetricsMapper metrics) { | |||
int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000; | |||
int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative | |||
@@ -339,11 +408,9 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
} | |||
private void drawTextAsBitmap(final int x, final int y, final int[] dx, final int[] dy, | |||
final String text, FontTriplet triplet) throws IOException, ImageException { | |||
final String text, FontTriplet triplet) throws IFException { | |||
//Use Java2D to paint different fonts via bitmap | |||
final Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize()); | |||
//final Font font = getFontFromArea(text); | |||
//final int baseline = text.getBaselineOffset(); | |||
//for cursive fonts, so the text isn't clipped | |||
final FontMetricsMapper mapper = (FontMetricsMapper)parent.getFontInfo().getMetricsFor( | |||
@@ -354,14 +421,9 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize()); | |||
final int baselineOffset = maxAscent + safetyMargin; | |||
final Rectangle boundingRect = getTextBoundingRect(x, y, dx, dy, text, font, mapper); | |||
final Rectangle boundingBox = getTextBoundingBox(x, y, dx, dy, text, font, mapper); | |||
final Dimension dim = boundingBox.getSize(); | |||
Map atts = new java.util.HashMap(); | |||
atts.put(CONV_MODE, "bitmap"); | |||
atts.put(SRC_TRANSPARENCY, "true"); | |||
//rc.setProperty(RendererContextConstants.FOREIGN_ATTRIBUTES, atts); | |||
final Dimension dim = boundingRect.getSize(); | |||
Graphics2DImagePainter painter = new Graphics2DImagePainter() { | |||
public void paint(Graphics2D g2d, Rectangle2D area) { | |||
@@ -394,19 +456,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { | |||
} | |||
}; | |||
ImageInfo info = new ImageInfo(null, null); | |||
ImageSize size = new ImageSize(); | |||
size.setSizeInMillipoints(boundingRect.width, boundingRect.height); | |||
info.setSize(size); | |||
ImageGraphics2D img = new ImageGraphics2D(info, painter); | |||
Rectangle rect = boundingRect; | |||
Map hints = new java.util.HashMap(); | |||
hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT, | |||
ImageProcessingHints.BITMAP_TYPE_INTENT_GRAY); | |||
PCLRenderingContext context = (PCLRenderingContext)createRenderingContext(); | |||
context.setSourceTransparencyEnabled(true); | |||
drawImage(img, rect, context, true, hints); | |||
paintMarksAsBitmap(painter, boundingBox); | |||
} | |||
/** Saves the current graphics state on the stack. */ |
@@ -164,7 +164,8 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants { | |||
* @param qualityBeforeSpeed true if quality is more important than speed | |||
*/ | |||
public void setQualityBeforeSpeed(boolean qualityBeforeSpeed) { | |||
pclUtil.setQualityBeforeSpeed(qualityBeforeSpeed); | |||
pclUtil.setRenderingMode(qualityBeforeSpeed | |||
? PCLRenderingMode.QUALITY : PCLRenderingMode.SPEED); | |||
} | |||
/** | |||
@@ -1187,10 +1188,10 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants { | |||
if (bpsBefore == null && bpsAfter == null && bpsStart == null && bpsEnd == null) { | |||
return; //no borders to paint | |||
} | |||
if (pclUtil.isQualityBeforeSpeed()) { | |||
drawQualityBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); | |||
} else { | |||
if (PCLRenderingMode.SPEED == pclUtil.getRenderingMode()) { | |||
drawFastBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); | |||
} else { | |||
drawQualityBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); | |||
} | |||
} | |||
@@ -75,13 +75,11 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator | |||
private void configure(Configuration cfg, PCLRenderingUtil pclUtil) throws FOPException { | |||
String rendering = cfg.getChild("rendering").getValue(null); | |||
if ("quality".equalsIgnoreCase(rendering)) { | |||
pclUtil.setQualityBeforeSpeed(true); | |||
} else if ("speed".equalsIgnoreCase(rendering)) { | |||
pclUtil.setQualityBeforeSpeed(false); | |||
} else if (rendering != null) { | |||
try { | |||
pclUtil.setRenderingMode(PCLRenderingMode.valueOf(rendering)); | |||
} catch (IllegalArgumentException e) { | |||
throw new FOPException( | |||
"Valid values for 'rendering' are 'quality' and 'speed'. Value found: " | |||
"Valid values for 'rendering' are 'quality', 'speed' and 'bitmap'. Value found: " | |||
+ rendering); | |||
} | |||
@@ -0,0 +1,82 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||
* contributor license agreements. See the NOTICE file distributed with | |||
* this work for additional information regarding copyright ownership. | |||
* The ASF licenses this file to You 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. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.render.pcl; | |||
import java.io.ObjectStreamException; | |||
import java.io.Serializable; | |||
/** | |||
* Enumeration class for PCL rendering modes. | |||
*/ | |||
public final class PCLRenderingMode implements Serializable { | |||
private static final long serialVersionUID = 6359884255324755026L; | |||
/** "Quality" rendering (mixed native and bitmap for improved quality) */ | |||
public static final PCLRenderingMode QUALITY = new PCLRenderingMode("quality"); | |||
/** "Speed" rendering (maximum speed with native rendering, reduced visual quality) */ | |||
public static final PCLRenderingMode SPEED = new PCLRenderingMode("speed"); | |||
/** | |||
* "Bitmap" rendering (pages are painted entirely as bitmaps, maximum quality, | |||
* reduced performance) | |||
*/ | |||
public static final PCLRenderingMode BITMAP = new PCLRenderingMode("bitmap"); | |||
private String name; | |||
/** | |||
* Constructor to add a new named item. | |||
* @param name Name of the item. | |||
*/ | |||
private PCLRenderingMode(String name) { | |||
this.name = name; | |||
} | |||
/** @return the name of the enum */ | |||
public String getName() { | |||
return this.name; | |||
} | |||
/** | |||
* Returns the enumeration/singleton object based on its name. | |||
* @param name the name of the enumeration value | |||
* @return the enumeration object | |||
*/ | |||
public static PCLRenderingMode valueOf(String name) { | |||
if (QUALITY.getName().equalsIgnoreCase(name)) { | |||
return QUALITY; | |||
} else if (SPEED.getName().equalsIgnoreCase(name)) { | |||
return SPEED; | |||
} else if (BITMAP.getName().equalsIgnoreCase(name)) { | |||
return BITMAP; | |||
} else { | |||
throw new IllegalArgumentException("Illegal value for enumeration: " + name); | |||
} | |||
} | |||
private Object readResolve() throws ObjectStreamException { | |||
return valueOf(getName()); | |||
} | |||
/** {@inheritDoc} */ | |||
public String toString() { | |||
return "PCLRenderingMode:" + name; | |||
} | |||
} |
@@ -42,10 +42,10 @@ public class PCLRenderingUtil { | |||
private FOUserAgent userAgent; | |||
/** | |||
* Controls whether appearance is more important than speed. False can cause some FO feature | |||
* Controls whether appearance is more important than speed. "SPEED" can cause some FO feature | |||
* to be ignored (like the advanced borders). | |||
*/ | |||
private boolean qualityBeforeSpeed = false; | |||
private PCLRenderingMode renderingMode = PCLRenderingMode.SPEED; | |||
/** | |||
* Controls whether all text should be painted as text. This is a fallback setting in case | |||
@@ -84,18 +84,18 @@ public class PCLRenderingUtil { | |||
/** | |||
* Configures the renderer to trade speed for quality if desired. One example here is the way | |||
* that borders are rendered. | |||
* @param qualityBeforeSpeed true if quality is more important than speed | |||
* @param mode one of the {@code PCLRenderingMode}.* constants | |||
*/ | |||
public void setQualityBeforeSpeed(boolean qualityBeforeSpeed) { | |||
this.qualityBeforeSpeed = qualityBeforeSpeed; | |||
public void setRenderingMode(PCLRenderingMode mode) { | |||
this.renderingMode = mode; | |||
} | |||
/** | |||
* Indicates whether quality is more important than speed. | |||
* @return true if quality is favored over speed | |||
* Returns the selected rendering mode. | |||
* @return the rendering mode | |||
*/ | |||
public boolean isQualityBeforeSpeed() { | |||
return this.qualityBeforeSpeed; | |||
public PCLRenderingMode getRenderingMode() { | |||
return this.renderingMode; | |||
} | |||
/** |