/* * 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.ps; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Point2D.Double; import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.apache.batik.gvt.text.TextPaintInfo; import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontMetrics; import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.svg.NativeTextPainter; import org.apache.fop.util.HexEncoder; /** * Renders the attributed character iterator of a {@link org.apache.batik.gvt.TextNode TextNode}. * This class draws the text directly using PostScript text operators so * the text is not drawn using shapes which makes the PS files larger. *
* The text runs are split into smaller text runs that can be bundles in single
* calls of the xshow, yshow or xyshow operators. For outline text, the charpath
* operator is used.
*/
public class PSTextPainter extends NativeTextPainter {
private FontResourceCache fontResources;
private PSGraphics2D ps;
private PSGenerator gen;
private TextUtil textUtil;
private boolean flushCurrentRun;
private PSTextRun psRun;
private Double relPos;
private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
/**
* Create a new PS text painter with the given font information.
* @param fontInfo the font collection
*/
public PSTextPainter(FontInfo fontInfo) {
super(fontInfo);
this.fontResources = new FontResourceCache(fontInfo);
}
/** {@inheritDoc} */
protected boolean isSupported(Graphics2D g2d) {
return g2d instanceof PSGraphics2D;
}
@Override
protected void preparePainting(Graphics2D g2d) {
ps = (PSGraphics2D) g2d;
gen = ps.getPSGenerator();
ps.preparePainting();
}
@Override
protected void saveGraphicsState() throws IOException {
gen.saveGraphicsState();
}
@Override
protected void restoreGraphicsState() throws IOException {
gen.restoreGraphicsState();
}
@Override
protected void setInitialTransform(AffineTransform transform) throws IOException {
gen.concatMatrix(transform);
}
private PSFontResource getResourceForFont(Font f, String postfix) {
String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName());
return this.fontResources.getFontResourceForFontKey(key);
}
@Override
protected void clip(Shape shape) throws IOException {
if (shape == null) {
return;
}
ps.getPSGenerator().writeln("newpath");
PathIterator iter = shape.getPathIterator(IDENTITY_TRANSFORM);
ps.processPathIterator(iter);
ps.getPSGenerator().writeln("clip");
}
@Override
protected void beginTextObject() throws IOException {
gen.writeln("BT");
textUtil = new TextUtil();
psRun = new PSTextRun(); //Used to split a text run into smaller runs
}
@Override
protected void endTextObject() throws IOException {
psRun.paint(ps, textUtil, tpi);
gen.writeln("ET");
}
@Override
protected void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean reposition) {
flushCurrentRun = false;
//Try to optimize by combining characters using the same font and on the same line.
if (reposition) {
//Happens for text-on-a-path
flushCurrentRun = true;
}
if (psRun.getRunLength() >= 128) {
//Don't let a run get too long
flushCurrentRun = true;
}
//Note the position of the glyph relative to the previous one
if (prevPos == null) {
relPos = new Point2D.Double(0, 0);
} else {
relPos = new Point2D.Double(
glyphPos.getX() - prevPos.getX(),
glyphPos.getY() - prevPos.getY());
}
if (psRun.vertChanges == 0
&& psRun.getHorizRunLength() > 2
&& relPos.getY() != 0) {
//new line
flushCurrentRun = true;
}
}
@Override
protected void writeGlyph(char glyph, AffineTransform localTransform) throws IOException {
boolean fontChanging = textUtil.isFontChanging(font, glyph);
if (fontChanging) {
flushCurrentRun = true;
}
if (flushCurrentRun) {
//Paint the current run and reset for the next run
psRun.paint(ps, textUtil, tpi);
psRun.reset();
}
//Track current run
psRun.addGlyph(glyph, relPos);
psRun.noteStartingTransformation(localTransform);
//Change font if necessary
if (fontChanging) {
textUtil.setCurrentFont(font, glyph);
}
}
private class TextUtil {
private Font currentFont;
private int currentEncoding = -1;
public boolean isMultiByte(Font f) {
FontMetrics metrics = f.getFontMetrics();
boolean multiByte = metrics instanceof MultiByteFont || metrics instanceof LazyFont
&& ((LazyFont) metrics).getRealFont() instanceof MultiByteFont;
return multiByte;
}
public void writeTextMatrix(AffineTransform transform) throws IOException {
double[] matrix = new double[6];
transform.getMatrix(matrix);
gen.writeln(gen.formatDouble5(matrix[0]) + " "
+ gen.formatDouble5(matrix[1]) + " "
+ gen.formatDouble5(matrix[2]) + " "
+ gen.formatDouble5(matrix[3]) + " "
+ gen.formatDouble5(matrix[4]) + " "
+ gen.formatDouble5(matrix[5]) + " Tm");
}
public boolean isFontChanging(Font f, char mapped) {
// this is only applicable for single byte fonts
if (!isMultiByte(f)) {
if (f != getCurrentFont()) {
return true;
}
if (mapped / 256 != getCurrentFontEncoding()) {
return true;
}
}
return false; //Font is the same
}
public void selectFont(Font f, char mapped) throws IOException {
int encoding = mapped / 256;
String postfix = (!isMultiByte(f) && encoding > 0 ? Integer.toString(encoding) : null);
PSFontResource res = getResourceForFont(f, postfix);
gen.useFont("/" + res.getName(), f.getFontSize() / 1000f);
res.notifyResourceUsageOnPage(gen.getResourceTracker());
}
public Font getCurrentFont() {
return this.currentFont;
}
public int getCurrentFontEncoding() {
return this.currentEncoding;
}
public void setCurrentFont(Font font, int encoding) {
this.currentFont = font;
this.currentEncoding = encoding;
}
public void setCurrentFont(Font font, char mapped) {
int encoding = mapped / 256;
setCurrentFont(font, encoding);
}
}
private class PSTextRun {
private AffineTransform textTransform;
private List