All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.fop.render.ps.PSTextPainter Maven / Gradle / Ivy

Go to download

Apache FOP (Formatting Objects Processor) is the world's first print formatter driven by XSL formatting objects (XSL-FO) and the world's first output independent formatter. It is a Java application that reads a formatting object (FO) tree and renders the resulting pages to a specified output. Output formats currently supported include PDF, PCL, PS, AFP, TIFF, PNG, SVG, XML (area tree representation), Print, AWT and TXT. The primary output target is PDF.

There is a newer version: 2.9
Show newest version
/*
 * 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: PSTextPainter.java 820689 2009-10-01 15:36:10Z jeremias $ */

package org.apache.fop.render.ps;

import java.awt.BasicStroke;
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.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.util.Iterator;
import java.util.List;

import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.text.TextPaintInfo;
import org.apache.batik.gvt.text.TextSpanLayout;

import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.svg.NativeTextPainter;
import org.apache.fop.util.CharUtilities;

/**
 * Renders the attributed character iterator of a {@link 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 static final boolean DEBUG = false; private FontResourceCache fontResources; 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; } /** {@inheritDoc} */ protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { AttributedCharacterIterator runaci = textRun.getACI(); runaci.first(); TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); if (tpi == null || !tpi.visible) { return; } if ((tpi != null) && (tpi.composite != null)) { g2d.setComposite(tpi.composite); } //------------------------------------ TextSpanLayout layout = textRun.getLayout(); logTextRun(runaci, layout); CharSequence chars = collectCharacters(runaci); runaci.first(); //Reset ACI final PSGraphics2D ps = (PSGraphics2D)g2d; final PSGenerator gen = ps.getPSGenerator(); ps.preparePainting(); if (DEBUG) { log.debug("Text: " + chars); gen.commentln("%Text: " + chars); } GeneralPath debugShapes = null; if (DEBUG) { debugShapes = new GeneralPath(); } TextUtil textUtil = new TextUtil(gen); textUtil.setupFonts(runaci); if (!textUtil.hasFonts()) { //Draw using Java2D when no native fonts are available textRun.getLayout().draw(g2d); return; } gen.saveGraphicsState(); gen.concatMatrix(g2d.getTransform()); Shape imclip = g2d.getClip(); clip(ps, imclip); gen.writeln("BT"); //beginTextObject() AffineTransform localTransform = new AffineTransform(); Point2D prevPos = null; GVTGlyphVector gv = layout.getGlyphVector(); PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { char ch = chars.charAt(index); boolean visibleChar = gv.isGlyphVisible(index) || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); logCharacter(ch, layout, index, visibleChar); if (!visibleChar) { continue; } Point2D glyphPos = gv.getGlyphPosition(index); AffineTransform glyphTransform = gv.getGlyphTransform(index); if (log.isTraceEnabled()) { log.trace("pos " + glyphPos + ", transform " + glyphTransform); } if (DEBUG) { Shape sh = gv.getGlyphLogicalBounds(index); if (sh == null) { sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); } debugShapes.append(sh, false); } //Exact position of the glyph localTransform.setToIdentity(); localTransform.translate(glyphPos.getX(), glyphPos.getY()); if (glyphTransform != null) { localTransform.concatenate(glyphTransform); } localTransform.scale(1, -1); boolean flushCurrentRun = false; //Try to optimize by combining characters using the same font and on the same line. if (glyphTransform != null) { //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 Point2D relPos; 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; } //Select the actual character to paint char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); //Select (sub)font for character Font f = textUtil.selectFontForChar(paintChar); char mapped = f.mapChar(ch); boolean fontChanging = textUtil.isFontChanging(f, mapped); 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.addCharacter(paintChar, relPos); psRun.noteStartingTransformation(localTransform); //Change font if necessary if (fontChanging) { textUtil.setCurrentFont(f, mapped); } //Update last position prevPos = glyphPos; } psRun.paint(ps, textUtil, tpi); gen.writeln("ET"); //endTextObject() gen.restoreGraphicsState(); if (DEBUG) { //Paint debug shapes g2d.setStroke(new BasicStroke(0)); g2d.setColor(Color.LIGHT_GRAY); g2d.draw(debugShapes); } } private void applyColor(Paint paint, final PSGenerator gen) throws IOException { if (paint == null) { return; } else if (paint instanceof Color) { Color col = (Color)paint; gen.useColor(col); } else { log.warn("Paint not supported: " + paint.toString()); } } private PSResource getResourceForFont(Font f, String postfix) { String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); return this.fontResources.getPSResourceForFontKey(key); } private void clip(PSGraphics2D ps, 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"); } private class TextUtil { private PSGenerator gen; private Font[] fonts; private Font currentFont; private int currentEncoding = -1; public TextUtil(PSGenerator gen) { this.gen = gen; } public Font selectFontForChar(char ch) { for (int i = 0, c = fonts.length; i < c; i++) { if (fonts[i].hasChar(ch)) { return fonts[i]; } } return fonts[0]; //TODO Maybe fall back to painting with shapes } 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) { if (f != getCurrentFont()) { int encoding = mapped / 256; if (encoding != getCurrentFontEncoding()) { return true; //Font is changing } } return false; //Font is the same } public void selectFont(Font f, char mapped) throws IOException { int encoding = mapped / 256; String postfix = (encoding == 0 ? null : Integer.toString(encoding)); PSResource res = getResourceForFont(f, postfix); gen.useFont("/" + res.getName(), f.getFontSize() / 1000f); gen.getResourceTracker().notifyResourceUsageOnPage(res); } 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); } public void setupFonts(AttributedCharacterIterator runaci) { this.fonts = findFonts(runaci); } public boolean hasFonts() { return (fonts != null) && (fonts.length > 0); } } private class PSTextRun { private AffineTransform textTransform; private List relativePositions = new java.util.LinkedList(); private StringBuffer currentChars = new StringBuffer(); private int horizChanges = 0; private int vertChanges = 0; public void reset() { textTransform = null; currentChars.setLength(0); horizChanges = 0; vertChanges = 0; relativePositions.clear(); } public int getHorizRunLength() { if (this.vertChanges == 0 && getRunLength() > 0) { return getRunLength(); } return 0; } public void addCharacter(char paintChar, Point2D relPos) { addRelativePosition(relPos); currentChars.append(paintChar); } private void addRelativePosition(Point2D relPos) { if (getRunLength() > 0) { if (relPos.getX() != 0) { horizChanges++; } if (relPos.getY() != 0) { vertChanges++; } } relativePositions.add(relPos); } public void noteStartingTransformation(AffineTransform transform) { if (textTransform == null) { this.textTransform = new AffineTransform(transform); } } public int getRunLength() { return currentChars.length(); } private boolean isXShow() { return vertChanges == 0; } private boolean isYShow() { return horizChanges == 0; } public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi) throws IOException { if (getRunLength() > 0) { if (log.isDebugEnabled()) { log.debug("Text run: " + currentChars); } textUtil.writeTextMatrix(this.textTransform); if (isXShow()) { log.debug("Horizontal text: xshow"); paintXYShow(g2d, textUtil, tpi.fillPaint, true, false); } else if (isYShow()) { log.debug("Vertical text: yshow"); paintXYShow(g2d, textUtil, tpi.fillPaint, false, true); } else { log.debug("Arbitrary text: xyshow"); paintXYShow(g2d, textUtil, tpi.fillPaint, true, true); } boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null); if (stroke) { log.debug("Stroked glyph outlines"); paintStrokedGlyphs(g2d, textUtil, tpi.strokePaint, tpi.strokeStroke); } } } private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint, boolean x, boolean y) throws IOException { PSGenerator gen = textUtil.gen; char firstChar = this.currentChars.charAt(0); //Font only has to be setup up before the first character Font f = textUtil.selectFontForChar(firstChar); char mapped = f.mapChar(firstChar); textUtil.selectFont(f, mapped); textUtil.setCurrentFont(f, mapped); applyColor(paint, gen); StringBuffer sb = new StringBuffer(); sb.append('('); for (int i = 0, c = this.currentChars.length(); i < c; i++) { char ch = this.currentChars.charAt(i); mapped = f.mapChar(ch); PSGenerator.escapeChar(mapped, sb); } sb.append(')'); if (x || y) { sb.append("\n["); int idx = 0; Iterator iter = this.relativePositions.iterator(); while (iter.hasNext()) { Point2D pt = (Point2D)iter.next(); if (idx > 0) { if (x) { sb.append(format(gen, pt.getX())); } if (y) { if (x) { sb.append(' '); } sb.append(format(gen, -pt.getY())); } if (idx % 8 == 0) { sb.append('\n'); } else { sb.append(' '); } } idx++; } if (x) { sb.append('0'); } if (y) { if (x) { sb.append(' '); } sb.append('0'); } sb.append(']'); } sb.append(' '); if (x) { sb.append('x'); } if (y) { sb.append('y'); } sb.append("show"); // --> xshow, yshow or xyshow gen.writeln(sb.toString()); } private String format(PSGenerator gen, double coord) { if (Math.abs(coord) < 0.00001) { return "0"; } else { return gen.formatDouble5(coord); } } private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, Paint strokePaint, Stroke stroke) throws IOException { PSGenerator gen = textUtil.gen; applyColor(strokePaint, gen); PSGraphics2D.applyStroke(stroke, gen); Font f = null; Iterator iter = this.relativePositions.iterator(); iter.next(); Point2D pos = new Point2D.Double(0, 0); gen.writeln("0 0 M"); for (int i = 0, c = this.currentChars.length(); i < c; i++) { char ch = this.currentChars.charAt(0); if (i == 0) { //Font only has to be setup up before the first character f = textUtil.selectFontForChar(ch); } char mapped = f.mapChar(ch); if (i == 0) { textUtil.selectFont(f, mapped); textUtil.setCurrentFont(f, mapped); } mapped = f.mapChar(this.currentChars.charAt(i)); //add glyph outlines to current path char codepoint = (char)(mapped % 256); gen.write("(" + codepoint + ")"); gen.writeln(" false charpath"); if (iter.hasNext()) { //Position for the next character Point2D pt = (Point2D)iter.next(); pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); gen.writeln(gen.formatDouble5(pos.getX()) + " " + gen.formatDouble5(pos.getY()) + " M"); } } gen.writeln("stroke"); //paints all accumulated glyph outlines } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy