import java.awt.Color;
import java.awt.Graphics2D;
+import java.awt.Point;
import java.awt.RenderingHints;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.apache.fop.render.Graphics2DAdapter;
import org.apache.fop.render.Graphics2DImagePainter;
import org.apache.fop.render.RendererContext.RendererContextWrapper;
* @return the generated BufferedImage
*/
protected BufferedImage paintToBufferedImage(Graphics2DImagePainter painter,
- RendererContextWrapper context, int resolution, boolean gray) {
+ RendererContextWrapper context, int resolution, boolean gray, boolean withAlpha) {
int bmw = UnitConv.mpt2px(context.getWidth(), resolution);
int bmh = UnitConv.mpt2px(context.getHeight(), resolution);
BufferedImage bi;
if (gray) {
- bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_BYTE_GRAY);
+ if (withAlpha) {
+ bi = createGrayBufferedImageWithAlpha(bmw, bmh);
+ } else {
+ bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_BYTE_GRAY);
+ }
} else {
- bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_INT_ARGB);
+ if (withAlpha) {
+ bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_INT_ARGB);
+ } else {
+ bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_INT_RGB);
+ }
}
Graphics2D g2d = bi.createGraphics();
try {
g2d.setBackground(Color.white);
g2d.setColor(Color.black);
- g2d.clearRect(0, 0, bmw, bmh);
+ if (!withAlpha) {
+ g2d.clearRect(0, 0, bmw, bmh);
+ }
double sx = (double)bmw / context.getWidth();
double sy = (double)bmh / context.getHeight();
g2d.scale(sx, sy);
return bi;
}
+ private static BufferedImage createGrayBufferedImageWithAlpha(int width, int height) {
+ BufferedImage bi;
+ boolean alphaPremultiplied = true;
+ int bands = 2;
+ int[] bits = new int[bands];
+ for (int i = 0; i < bands; i++) {
+ bits[i] = 8;
+ }
+ ColorModel cm = new ComponentColorModel(
+ ColorSpace.getInstance(ColorSpace.CS_GRAY),
+ bits,
+ true, alphaPremultiplied,
+ Transparency.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE);
+ WritableRaster wr = Raster.createInterleavedRaster(
+ DataBuffer.TYPE_BYTE,
+ width, height, bands,
+ new Point(0, 0));
+ bi = new BufferedImage(cm, wr, alphaPremultiplied, null);
+ return bi;
+ }
+
/**
* Sets rendering hints on the Graphics2D created for painting to a BufferedImage. Subclasses
* can modify the settings to customize the behaviour.
//Paint to a BufferedImage
int resolution = (int)Math.round(context.getUserAgent().getTargetResolution());
- BufferedImage bi = paintToBufferedImage(painter, wrappedContext, resolution, false);
+ BufferedImage bi = paintToBufferedImage(painter, wrappedContext, resolution, false, false);
afp.drawBufferedImage(bi, resolution, x, y, width, height);
}
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ByteLookupTable;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
+import java.awt.image.LookupOp;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
+import org.apache.xmlgraphics.image.GraphicsUtil;
+
/**
* This class provides methods for generating PCL print files.
*/
private OutputStream out;
+ private boolean currentSourceTransparency = true;
+ private boolean currentPatternTransparency = true;
+
+ private int maxBitmapResolution = PCL_RESOLUTIONS[PCL_RESOLUTIONS.length - 1];
+
/**
* Main constructor.
* @param out the OutputStream to write the PCL stream to
this.out = out;
}
+ /**
+ * Main constructor.
+ * @param out the OutputStream to write the PCL stream to
+ * @param maxResolution the maximum resolution to encode bitmap images at
+ */
+ public PCLGenerator(OutputStream out, int maxResolution) {
+ this(out);
+ boolean found = false;
+ for (int i = 0; i < PCL_RESOLUTIONS.length; i++) {
+ if (PCL_RESOLUTIONS[i] == maxResolution) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException("Illegal value for maximum resolution!");
+ }
+ this.maxBitmapResolution = maxResolution;
+ }
+
/** @return the OutputStream that this generator writes to */
public OutputStream getOutputStream() {
return this.out;
}
+ /** @return the maximum resolution to encode bitmap images at */
+ public int getMaximumBitmapResolution() {
+ return this.maxBitmapResolution;
+ }
+
/**
* Writes a PCL escape command to the output stream.
* @param cmd the command (without the ESCAPE character)
}
}
+ /**
+ * Pushes the current cursor position on a stack (stack size: max 20 entries)
+ * @throws IOException In case of an I/O error
+ */
+ public void pushCursorPos() throws IOException {
+ writeCommand("&f0S");
+ }
+
+ /**
+ * Pops the current cursor position from the stack.
+ * @throws IOException In case of an I/O error
+ */
+ public void popCursorPos() throws IOException {
+ writeCommand("&f1S");
+ }
+
/**
* Changes the current print direction while maintaining the current cursor position.
* @param rotate the rotation angle (counterclockwise), one of 0, 90, 180 and 270.
setPatternTransparencyMode(true);
}
+ /**
+ * Sets the source transparency mode.
+ * @param transparent true if transparent, false for opaque
+ * @throws IOException In case of an I/O error
+ */
+ public void setSourceTransparencyMode(boolean transparent) throws IOException {
+ setTransparencyMode(transparent, currentPatternTransparency);
+ }
+
/**
* Sets the pattern transparency mode.
* @param transparent true if transparent, false for opaque
* @throws IOException In case of an I/O error
*/
public void setPatternTransparencyMode(boolean transparent) throws IOException {
- if (transparent) {
- writeCommand("*v0O");
- } else {
- writeCommand("*v1O");
+ setTransparencyMode(currentSourceTransparency, transparent);
+ }
+
+ /**
+ * Sets the transparency modes.
+ * @param source source transparency: true if transparent, false for opaque
+ * @param pattern pattern transparency: true if transparent, false for opaque
+ * @throws IOException In case of an I/O error
+ */
+ public void setTransparencyMode(boolean source, boolean pattern) throws IOException {
+ if (source != currentSourceTransparency && pattern != currentPatternTransparency) {
+ writeCommand("*v" + (source ? '0' : '1') + "n" + (pattern ? '0' : '1') + "O");
+ } else if (source != currentSourceTransparency) {
+ writeCommand("*v" + (source ? '0' : '1') + "N");
+ } else if (pattern != currentPatternTransparency) {
+ writeCommand("*v" + (pattern ? '0' : '1') + "O");
}
+ this.currentSourceTransparency = source;
+ this.currentPatternTransparency = pattern;
}
/**
* @throws IOException In case of an I/O error
*/
public void selectCurrentPattern(int patternID, int pattern) throws IOException {
- writeCommand("*c" + patternID + "G");
+ if (pattern > 1) {
+ writeCommand("*c" + patternID + "G");
+ }
writeCommand("*v" + pattern + "T");
}
* @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600)
*/
private int calculatePCLResolution(int resolution, boolean increased) {
+ int choice = -1;
for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) {
if (resolution > PCL_RESOLUTIONS[i]) {
int idx = i + 1;
} else if (idx < PCL_RESOLUTIONS.length - 1) {
idx += increased ? 1 : 0;
}
- return PCL_RESOLUTIONS[idx];
+ choice = idx;
+ break;
+ //return PCL_RESOLUTIONS[idx];
}
}
- return PCL_RESOLUTIONS[increased ? 2 : 0];
+ if (choice < 0) {
+ choice = (increased ? 2 : 0);
+ }
+ while (choice > 0 && PCL_RESOLUTIONS[choice] > getMaximumBitmapResolution()) {
+ choice--;
+ }
+ return PCL_RESOLUTIONS[choice];
}
private boolean isValidPCLResolution(int resolution) {
}
}
+ //Threshold table to convert an alpha channel (8-bit) into a clip mask (1-bit)
+ private static final byte[] THRESHOLD_TABLE = new byte[256];
+ static { // Initialize the arrays
+ for (int i = 0; i < 256; i++) {
+ THRESHOLD_TABLE[i] = (byte) ((i < 240) ? 255 : 0);
+ }
+ }
+
+ private RenderedImage getMask(RenderedImage img, Dimension targetDim) {
+ ColorModel cm = img.getColorModel();
+ if (cm.hasAlpha()) {
+ BufferedImage alpha = new BufferedImage(img.getWidth(), img.getHeight(),
+ BufferedImage.TYPE_BYTE_GRAY);
+ Raster raster = img.getData();
+ GraphicsUtil.copyBand(raster, cm.getNumColorComponents(), alpha.getRaster(), 0);
+
+ BufferedImageOp op1 = new LookupOp(new ByteLookupTable(0, THRESHOLD_TABLE), null);
+ BufferedImage alphat = op1.filter(alpha, null);
+
+ BufferedImage mask;
+ if (true) {
+ mask = new BufferedImage(targetDim.width, targetDim.height,
+ BufferedImage.TYPE_BYTE_BINARY);
+ } else {
+ byte[] arr = {(byte)0, (byte)0xff};
+ ColorModel colorModel = new IndexColorModel(1, 2, arr, arr, arr);
+ WritableRaster wraster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
+ targetDim.width, targetDim.height, 1, 1, null);
+ mask = new BufferedImage(colorModel, wraster, false, null);
+ }
+
+ Graphics2D g2d = mask.createGraphics();
+ try {
+ AffineTransform at = new AffineTransform();
+ double sx = targetDim.getWidth() / img.getWidth();
+ double sy = targetDim.getHeight() / img.getHeight();
+ at.scale(sx, sy);
+ g2d.drawRenderedImage(alphat, at);
+ } finally {
+ g2d.dispose();
+ }
+ /*
+ try {
+ BatchDiffer.saveAsPNG(alpha, new java.io.File("D:/out-alpha.png"));
+ BatchDiffer.saveAsPNG(mask, new java.io.File("D:/out-mask.png"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }*/
+ return mask;
+ } else {
+ return null;
+ }
+ }
+
/**
* Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome
* (1-bit) bitmap image.
Dimension orgDim = new Dimension(img.getWidth(), img.getHeight());
Dimension effDim = getAdjustedDimension(orgDim, resolution, effResolution);
boolean scaled = !orgDim.equals(effDim);
+
+ //Transparency mask disabled. Doesn't work reliably
+ final boolean transparencyDisabled = true;
+ RenderedImage mask = (transparencyDisabled ? null : getMask(img, effDim));
+ if (mask != null) {
+ pushCursorPos();
+ selectCurrentPattern(0, 1); //Solid white
+ setTransparencyMode(true, true);
+ paintMonochromeBitmap(mask, effResolution);
+ popCursorPos();
+ }
+
BufferedImage src = null;
if (img instanceof BufferedImage && !scaled) {
- if (!isGrayscaleImage(img)) {
+ if (!isGrayscaleImage(img) || img.getColorModel().hasAlpha()) {
src = new BufferedImage(effDim.width, effDim.height,
BufferedImage.TYPE_BYTE_GRAY);
ColorConvertOp op = new ColorConvertOp(
MonochromeBitmapConverter converter = createMonochromeBitmapConverter();
converter.setHint("quality", "false");
- long start = System.currentTimeMillis();
BufferedImage buf = (BufferedImage)converter.convertToMonochrome(src);
- long duration = System.currentTimeMillis() - start;
- System.out.println(duration + " ms");
RenderedImage red = buf;
+ selectCurrentPattern(0, 0); //Solid black
+ setTransparencyMode(mask != null, true);
paintMonochromeBitmap(red, effResolution);
} else {
int effResolution = calculatePCLResolution(resolution);
+ setSourceTransparencyMode(false);
+ selectCurrentPattern(0, 0); //Solid black
paintMonochromeBitmap(img, effResolution);
}
}
/**
* Graphics2D implementation implementing PCL and HP GL/2.
+ * Note: This class cannot be used stand-alone to create full PCL documents.
*/
public class PCLGraphics2D extends AbstractGraphics2D {
boolean paintAsBitmap = pclContext.paintAsBitmap();
if (!paintAsBitmap) {
ByteArrayOutputStream baout = new ByteArrayOutputStream();
- PCLGenerator tempGen = new PCLGenerator(baout);
+ PCLGenerator tempGen = new PCLGenerator(baout, gen.getMaximumBitmapResolution());
try {
GraphicContext ctx = (GraphicContext)pcl.getGraphicContext().clone();
if (!painted) {
//Fallback solution: Paint to a BufferedImage
int resolution = (int)Math.round(context.getUserAgent().getTargetResolution());
- BufferedImage bi = paintToBufferedImage(painter, pclContext, resolution, true);
+ BufferedImage bi = paintToBufferedImage(painter, pclContext, resolution, true, false);
pcl.setCursorPos(x, y);
gen.paintBitmap(bi, resolution);
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
+import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.fo.extensions.ExtensionElementMapping;
import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontInfo;
import org.apache.fop.image.EPSImage;
import org.apache.fop.image.FopImage;
import org.apache.fop.image.ImageFactory;
import org.apache.fop.render.PrintRenderer;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.RendererContextConstants;
+import org.apache.fop.render.java2d.FontMetricsMapper;
+import org.apache.fop.render.java2d.FontSetup;
import org.apache.fop.render.java2d.Java2DRenderer;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.util.QName;
}
}
+ /**
+ * @see org.apache.fop.render.Renderer#setupFontInfo(org.apache.fop.fonts.FontInfo)
+ */
+ public void setupFontInfo(FontInfo inFontInfo) {
+ //Don't call super.setupFontInfo() here!
+ //The PCLRenderer uses the Java2D FontSetup which needs a special font setup
+ //create a temp Image to test font metrics on
+ fontInfo = inFontInfo;
+ BufferedImage fontImage = new BufferedImage(100, 100,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = fontImage.createGraphics();
+ //The next line is important to get accurate font metrics!
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+ RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ FontSetup.setup(fontInfo, g);
+ }
+
/**
* Central exception handler for I/O exceptions.
* @param ioe IOException to handle
return this.graphicContext;
}
+ /** @return the target resolution */
+ protected int getResolution() {
+ int resolution = (int)Math.round(userAgent.getTargetResolution());
+ if (resolution <= 300) {
+ return 300;
+ } else {
+ return 600;
+ }
+ }
+
/**
* Sets the current font (NOTE: Hard-coded font mappings ATM!)
* @param name the font name (internal F* names for now)
* @param size the font size
+ * @return true if the font can be mapped to PCL
* @throws IOException if an I/O problem occurs
*/
- public void setFont(String name, float size) throws IOException {
+ public boolean setFont(String name, float size) throws IOException {
int fontcode = 0;
if (name.length() > 1 && name.charAt(0) == 'F') {
try {
gen.writeCommand("(s1p" + formattedSize + "v0s0b45101T");
break;
default:
- gen.writeCommand("(0N");
- gen.writeCommand("(s" + formattedSize + "V");
- break;
+ //gen.writeCommand("(0N");
+ //gen.writeCommand("(s" + formattedSize + "V");
+ return false;
}
+ return true;
}
/** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */
public void startRenderer(OutputStream outputStream) throws IOException {
log.debug("Rendering areas to PCL...");
this.out = outputStream;
- this.gen = new PCLGenerator(out);
+ this.gen = new PCLGenerator(out, getResolution());
gen.universalEndOfLanguage();
- gen.writeText("@PJL JOB NAME = \"" + userAgent.getTitle() + "\"\n");
+ gen.writeText("@PJL COMMENT Produced by " + userAgent.getProducer() + "\n");
+ if (userAgent.getTitle() != null) {
+ gen.writeText("@PJL JOB NAME = \"" + userAgent.getTitle() + "\"\n");
+ }
+ gen.writeText("@PJL SET RESOLUTION = " + getResolution() + "\n");
gen.writeText("@PJL ENTER LANGUAGE = PCL\n");
gen.resetPrinter();
}
gen.clearHorizontalMargins();
gen.setTopMargin(0);
gen.setVMI(0);
- gen.setUnitOfMeasure(600);
- gen.setRasterGraphicsResolution(600);
+ gen.setUnitOfMeasure(getResolution());
+ gen.setRasterGraphicsResolution(getResolution());
}
/** Saves the current graphics state on the stack. */
/**
* @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
*/
- protected void renderText(TextArea area) {
- //renderInlineAreaBackAndBorders(area);
- String fontname = getInternalFontNameForArea(area);
- int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE);
+ protected void renderText(final TextArea text) {
+ renderInlineAreaBackAndBorders(text);
+
+ String fontname = getInternalFontNameForArea(text);
+ int fontsize = text.getTraitAsInteger(Trait.FONT_SIZE);
//Determine position
- //int saveIP = currentIPPosition;
- //int saveBP = currentBPPosition;
- int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
- int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
+ int saveIP = currentIPPosition;
+ int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
+ int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
try {
- setFont(fontname, fontsize);
- Color col = (Color)area.getTrait(Trait.COLOR);
- //this.currentFill = col;
- if (col != null) {
- //useColor(ct);
- gen.setPatternTransparencyMode(false);
- gen.selectCurrentPattern(gen.convertToPCLShade(col), 2);
+ final Color col = (Color)text.getTrait(Trait.COLOR);
+ boolean pclFont = setFont(fontname, fontsize);
+ if (pclFont) {
+ //this.currentFill = col;
+ if (col != null) {
+ //useColor(ct);
+ gen.setTransparencyMode(true, false);
+ gen.selectCurrentPattern(gen.convertToPCLShade(col), 2);
+ }
+
+ saveGraphicsState();
+ graphicContext.translate(rx, bl);
+ setCursorPos(0, 0);
+ gen.setTransparencyMode(true, true);
+
+ super.renderText(text); //Updates IPD and renders words and spaces
+ restoreGraphicsState();
+ } else {
+ //Use Java2D to paint different fonts via bitmap
+ final Font font = getFontFromArea(text);
+ final int baseline = text.getBaselineOffset();
+
+ //for cursive fonts, so the text isn't clipped
+ int extraWidth = font.getFontSize() / 3;
+
+ Graphics2DAdapter g2a = getGraphics2DAdapter();
+ final Rectangle paintRect = new Rectangle(
+ rx, currentBPPosition + text.getOffset(),
+ text.getIPD() + extraWidth, text.getBPD());
+ RendererContext rc = createRendererContext(paintRect.x, paintRect.y,
+ paintRect.width, paintRect.height, null);
+ Map atts = new java.util.HashMap();
+ atts.put(new QName(ExtensionElementMapping.URI, null, "conversion-mode"), "bitmap");
+ rc.setProperty(RendererContextConstants.FOREIGN_ATTRIBUTES, atts);
+
+ Graphics2DImagePainter painter = new Graphics2DImagePainter() {
+
+ public void paint(Graphics2D g2d, Rectangle2D area) {
+ FontMetricsMapper mapper = (FontMetricsMapper)fontInfo.getMetricsFor(
+ font.getFontName());
+ g2d.setFont(mapper.getFont(font.getFontSize()));
+ g2d.translate(0, baseline);
+ g2d.scale(1000, 1000);
+ g2d.setColor(col);
+ Java2DRenderer.renderText(text, g2d, font);
+ }
+
+ public Dimension getImageSize() {
+ return paintRect.getSize();
+ }
+
+ };
+ g2a.paintImage(painter, rc,
+ paintRect.x, paintRect.y, paintRect.width, paintRect.height);
+ currentIPPosition = saveIP + text.getAllocIPD();
}
-
- saveGraphicsState();
- graphicContext.translate(rx, bl);
- setCursorPos(0, 0);
-
- super.renderText(area); //Updates IPD
//renderTextDecoration(tf, fontsize, area, bl, rx);
- restoreGraphicsState();
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
protected void fillRect(float x, float y, float width, float height) {
try {
setCursorPos(x * 1000, y * 1000);
- gen.fillRect((int)width * 1000, (int)height * 1000,
+ gen.fillRect((int)(width * 1000), (int)(height * 1000),
this.currentFillColor);
} catch (IOException ioe) {
handleIOTrouble(ioe);
Font font = getFontFromArea(textArea);
int tws = (space.isAdjustable()
- ? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
+ ? textArea.getTextWordSpaceAdjust()
+ 2 * textArea.getTextLetterSpaceAdjust()
: 0);
float width = borderRect.width;
float height = borderRect.height;
if (bpsBefore != null) {
- int borderWidth = (int) Math.round((bpsBefore.width / 1000f));
+ float borderWidth = bpsBefore.width / 1000f;
updateFillColor(bpsBefore.color);
- fillRect((int) startx, (int) starty, (int) width,
- borderWidth);
+ fillRect(startx, starty, width, borderWidth);
}
if (bpsAfter != null) {
- int borderWidth = (int) Math.round((bpsAfter.width / 1000f));
+ float borderWidth = bpsAfter.width / 1000f;
updateFillColor(bpsAfter.color);
- fillRect((int) startx,
- (int) (starty + height - borderWidth), (int) width,
- borderWidth);
+ fillRect(startx, (starty + height - borderWidth),
+ width, borderWidth);
}
if (bpsStart != null) {
- int borderWidth = (int) Math.round((bpsStart.width / 1000f));
+ float borderWidth = bpsStart.width / 1000f;
updateFillColor(bpsStart.color);
- fillRect((int) startx, (int) starty, borderWidth,
- (int) height);
+ fillRect(startx, starty, borderWidth, height);
}
if (bpsEnd != null) {
- int borderWidth = (int) Math.round((bpsEnd.width / 1000f));
+ float borderWidth = bpsEnd.width / 1000f;
updateFillColor(bpsEnd.color);
- fillRect((int) (startx + width - borderWidth),
- (int) starty, borderWidth, (int) height);
+ fillRect((startx + width - borderWidth), starty, borderWidth, height);
}
}