org.apache.fop.render.ps.PSPainter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The 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: PSPainter.java 1885366 2021-01-11 15:00:20Z ssteiner $ */
package org.apache.fop.render.ps;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.Map;
import org.w3c.dom.Document;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.image.loader.ImageException;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageProcessingHints;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.RenderingContext;
import org.apache.fop.render.intermediate.AbstractIFPainter;
import org.apache.fop.render.intermediate.BorderPainter;
import org.apache.fop.render.intermediate.GraphicsPainter;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
import org.apache.fop.util.HexEncoder;
/**
* IFPainter implementation that produces PostScript.
*/
public class PSPainter extends AbstractIFPainter {
/** logging instance */
private static Log log = LogFactory.getLog(PSPainter.class);
private final GraphicsPainter graphicsPainter;
private BorderPainter borderPainter;
private boolean inTextMode;
/**
* Default constructor.
* @param documentHandler the parent document handler
*/
public PSPainter(PSDocumentHandler documentHandler) {
this(documentHandler, IFState.create());
}
protected PSPainter(PSDocumentHandler documentHandler, IFState state) {
super(documentHandler);
this.graphicsPainter = new PSGraphicsPainter(getGenerator());
this.borderPainter = new BorderPainter(graphicsPainter);
this.state = state;
}
private PSGenerator getGenerator() {
return getDocumentHandler().getGenerator();
}
/** {@inheritDoc} */
public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
throws IFException {
try {
PSGenerator generator = getGenerator();
saveGraphicsState();
generator.concatMatrix(toPoints(transform));
} catch (IOException ioe) {
throw new IFException("I/O error in startViewport()", ioe);
}
if (clipRect != null) {
clipRect(clipRect);
}
}
/** {@inheritDoc} */
public void endViewport() throws IFException {
try {
restoreGraphicsState();
} catch (IOException ioe) {
throw new IFException("I/O error in endViewport()", ioe);
}
}
/** {@inheritDoc} */
public void startGroup(AffineTransform transform, String layer) throws IFException {
try {
PSGenerator generator = getGenerator();
saveGraphicsState();
generator.concatMatrix(toPoints(transform));
} catch (IOException ioe) {
throw new IFException("I/O error in startGroup()", ioe);
}
}
/** {@inheritDoc} */
public void endGroup() throws IFException {
try {
restoreGraphicsState();
} catch (IOException ioe) {
throw new IFException("I/O error in endGroup()", ioe);
}
}
/** {@inheritDoc} */
protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) {
Map hints = super.createDefaultImageProcessingHints(sessionContext);
//PostScript doesn't support alpha channels
hints.put(ImageProcessingHints.TRANSPARENCY_INTENT,
ImageProcessingHints.TRANSPARENCY_INTENT_IGNORE);
//TODO We might want to support image masks in the future.
return hints;
}
/** {@inheritDoc} */
protected RenderingContext createRenderingContext() {
PSRenderingContext psContext = new PSRenderingContext(
getUserAgent(), getGenerator(), getFontInfo());
return psContext;
}
/** {@inheritDoc} */
protected void drawImageUsingImageHandler(ImageInfo info, Rectangle rect)
throws ImageException, IOException {
if (!getDocumentHandler().getPSUtil().isOptimizeResources()
|| PSImageUtils.isImageInlined(info,
(PSRenderingContext)createRenderingContext())) {
super.drawImageUsingImageHandler(info, rect);
} else {
if (log.isDebugEnabled()) {
log.debug("Image " + info + " is embedded as a form later");
}
//Don't load image at this time, just put a form placeholder in the stream
PSResource form = getDocumentHandler().getFormForImage(info.getOriginalURI());
PSImageUtils.drawForm(form, info, rect, getGenerator());
}
}
/** {@inheritDoc} */
public void drawImage(String uri, Rectangle rect) throws IFException {
try {
endTextObject();
} catch (IOException ioe) {
throw new IFException("I/O error in drawImage()", ioe);
}
drawImageUsingURI(uri, rect);
}
/** {@inheritDoc} */
public void drawImage(Document doc, Rectangle rect) throws IFException {
try {
endTextObject();
} catch (IOException ioe) {
throw new IFException("I/O error in drawImage()", ioe);
}
drawImageUsingDocument(doc, rect);
}
/** {@inheritDoc} */
public void clipRect(Rectangle rect) throws IFException {
try {
PSGenerator generator = getGenerator();
endTextObject();
generator.defineRect(rect.x / 1000.0, rect.y / 1000.0,
rect.width / 1000.0, rect.height / 1000.0);
generator.writeln(generator.mapCommand("clip") + " " + generator.mapCommand("newpath"));
} catch (IOException ioe) {
throw new IFException("I/O error in clipRect()", ioe);
}
}
/** {@inheritDoc} */
public void clipBackground(Rectangle rect,
BorderProps bpsBefore, BorderProps bpsAfter,
BorderProps bpsStart, BorderProps bpsEnd) throws IFException {
try {
borderPainter.clipBackground(rect,
bpsBefore, bpsAfter, bpsStart, bpsEnd);
} catch (IOException ioe) {
throw new IFException("I/O error while clipping background", ioe);
}
}
/** {@inheritDoc} */
public void fillRect(Rectangle rect, Paint fill) throws IFException {
if (fill == null) {
return;
}
if (rect.width != 0 && rect.height != 0) {
try {
endTextObject();
PSGenerator generator = getGenerator();
if (fill != null) {
if (fill instanceof Color) {
generator.useColor((Color)fill);
} else {
throw new UnsupportedOperationException("Non-Color paints NYI");
}
}
generator.defineRect(rect.x / 1000.0, rect.y / 1000.0,
rect.width / 1000.0, rect.height / 1000.0);
generator.writeln(generator.mapCommand("fill"));
} catch (IOException ioe) {
throw new IFException("I/O error in fillRect()", ioe);
}
}
}
/** {@inheritDoc} */
public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom,
BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException {
if (top != null || bottom != null || left != null || right != null) {
try {
endTextObject();
if (getDocumentHandler().getPSUtil().getRenderingMode() == PSRenderingMode.SIZE
&& hasOnlySolidBorders(top, bottom, left, right)) {
super.drawBorderRect(rect, top, bottom, left, right, innerBackgroundColor);
} else {
this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor);
}
} catch (IOException ioe) {
throw new IFException("I/O error in drawBorderRect()", ioe);
}
}
}
/** {@inheritDoc} */
public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
throws IFException {
try {
endTextObject();
this.graphicsPainter.drawLine(start, end, width, color, style);
} catch (IOException ioe) {
throw new IFException("I/O error in drawLine()", ioe);
}
}
private Typeface getTypeface(String fontName) {
if (fontName == null) {
throw new NullPointerException("fontName must not be null");
}
Typeface tf = getFontInfo().getFonts().get(fontName);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
}
return tf;
}
/**
* Saves the graphics state of the rendering engine.
* @throws IOException if an I/O error occurs
*/
protected void saveGraphicsState() throws IOException {
endTextObject();
getGenerator().saveGraphicsState();
}
/**
* Restores the last graphics state of the rendering engine.
* @throws IOException if an I/O error occurs
*/
protected void restoreGraphicsState() throws IOException {
endTextObject();
getGenerator().restoreGraphicsState();
}
/**
* Indicates the beginning of a text object.
* @throws IOException if an I/O error occurs
*/
protected void beginTextObject() throws IOException {
if (!inTextMode) {
PSGenerator generator = getGenerator();
generator.saveGraphicsState();
generator.writeln("BT");
inTextMode = true;
}
}
/**
* Indicates the end of a text object.
* @throws IOException if an I/O error occurs
*/
protected void endTextObject() throws IOException {
if (inTextMode) {
inTextMode = false;
PSGenerator generator = getGenerator();
generator.writeln("ET");
generator.restoreGraphicsState();
}
}
private String formatMptAsPt(PSGenerator gen, int value) {
return gen.formatDouble(value / 1000.0);
}
/* Disabled: performance experiment (incomplete)
private static final String ZEROS = "0.00";
private String formatMptAsPt1(int value) {
String s = Integer.toString(value);
int len = s.length();
StringBuffer sb = new StringBuffer();
if (len < 4) {
sb.append(ZEROS.substring(0, 5 - len));
sb.append(s);
} else {
int dec = len - 3;
sb.append(s.substring(0, dec));
sb.append('.');
sb.append(s.substring(dec));
}
return sb.toString();
}*/
/** {@inheritDoc} */
public void drawText(int x, int y, int letterSpacing, int wordSpacing,
int[][] dp, String text) throws IFException {
try {
//Do not draw text if font-size is 0 as it creates an invalid PostScript file
if (state.getFontSize() == 0) {
return;
}
PSGenerator generator = getGenerator();
generator.useColor(state.getTextColor());
FontTriplet triplet = new FontTriplet(state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
String fontKey = getFontKey(triplet);
Typeface typeface = getTypeface(fontKey);
if (typeface instanceof MultiByteFont && ((MultiByteFont) typeface).hasSVG()) {
drawSVGText((MultiByteFont) typeface, triplet, x, y, text, state);
return;
}
beginTextObject();
//TODO Ignored: state.getFontVariant()
//TODO Opportunity for font caching if font state is more heavily used
int sizeMillipoints = state.getFontSize();
// This assumes that *all* CIDFonts use a /ToUnicode mapping
SingleByteFont singleByteFont = null;
if (typeface instanceof SingleByteFont) {
singleByteFont = (SingleByteFont)typeface;
}
Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints);
PSFontResource res = getDocumentHandler().getPSResourceForFontKey(fontKey);
boolean isOpenTypeFont = typeface instanceof MultiByteFont && ((MultiByteFont)typeface).isOTFFile();
useFont(fontKey, sizeMillipoints, isOpenTypeFont);
if (dp != null && dp[0] != null) {
x += dp[0][0];
y -= dp[0][1];
}
generator.writeln("1 0 0 -1 " + formatMptAsPt(generator, x)
+ " " + formatMptAsPt(generator, y) + " Tm");
int textLen = text.length();
int start = 0;
if (singleByteFont != null) {
//Analyze string and split up in order to paint in different sub-fonts/encodings
int currentEncoding = -1;
for (int i = 0; i < textLen; i++) {
char c = text.charAt(i);
char mapped = typeface.mapChar(c);
int encoding = mapped / 256;
if (currentEncoding != encoding) {
if (i > 0) {
writeText(text, start, i - start,
letterSpacing, wordSpacing, dp, font, typeface, false);
}
if (encoding == 0) {
useFont(fontKey, sizeMillipoints, false);
} else {
useFont(fontKey + "_" + Integer.toString(encoding), sizeMillipoints, false);
}
currentEncoding = encoding;
start = i;
}
}
} else {
if (typeface instanceof MultiByteFont && ((MultiByteFont)typeface).isOTFFile()) {
//Analyze string and split up in order to paint in different sub-fonts/encodings
int curEncoding = 0;
for (int i = start; i < textLen; i++) {
char orgChar = text.charAt(i);
MultiByteFont mbFont = (MultiByteFont)typeface;
mbFont.mapChar(orgChar);
int origGlyphIdx = mbFont.findGlyphIndex(orgChar);
int newGlyphIdx = mbFont.getUsedGlyphs().get(origGlyphIdx);
int encoding = newGlyphIdx / 256;
if (encoding != curEncoding) {
if (i != 0) {
writeText(text, start, i - start, letterSpacing, wordSpacing, dp, font, typeface,
true);
start = i;
}
generator.useFont("/" + res.getName() + "." + encoding, sizeMillipoints / 1000f);
curEncoding = encoding;
}
}
} else {
useFont(fontKey, sizeMillipoints, false);
}
}
writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, typeface,
typeface instanceof MultiByteFont);
} catch (IOException ioe) {
throw new IFException("I/O error in drawText()", ioe);
}
}
private void writeText(String text, int start, int len,
int letterSpacing, int wordSpacing, int[][] dp,
Font font, Typeface tf, boolean multiByte) throws IOException {
PSGenerator generator = getGenerator();
int end = start + len;
int initialSize = len;
initialSize += initialSize / 2;
boolean hasLetterSpacing = (letterSpacing != 0);
boolean needTJ = false;
int lineStart = 0;
StringBuffer accText = new StringBuffer(initialSize);
StringBuffer sb = new StringBuffer(initialSize);
boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile();
for (int i = start; i < end; i++) {
int orgChar = text.charAt(i);
int ch;
int cw;
int xGlyphAdjust = 0;
int yGlyphAdjust = 0;
if (CharUtilities.isFixedWidthSpace(orgChar)) {
//Fixed width space are rendered as spaces so copy/paste works in a reader
ch = font.mapChar(CharUtilities.SPACE);
cw = font.getCharWidth(orgChar);
xGlyphAdjust = font.getCharWidth(ch) - cw;
} else {
if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
xGlyphAdjust -= wordSpacing;
}
// surrogate pairs have to be merged in a single code point
if (CharUtilities.containsSurrogatePairAt(text, i)) {
orgChar = Character.toCodePoint((char) orgChar, text.charAt(++i));
}
ch = font.mapCodePoint(orgChar);
}
if (dp != null && i < dp.length && dp[i] != null) {
// get x advancement adjust
xGlyphAdjust -= dp[i][2] - dp[i][0];
yGlyphAdjust += dp[i][3] - dp[i][1];
}
if (dp != null && i < dp.length - 1 && dp[i + 1] != null) {
// get x placement adjust for next glyph
xGlyphAdjust -= dp[i + 1][0];
yGlyphAdjust += dp[i + 1][1];
}
if (!multiByte || isOTF) {
char codepoint = (char)(ch % 256);
if (isOTF) {
accText.append(HexEncoder.encode(codepoint, 2));
} else {
PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
}
} else {
accText.append(HexEncoder.encode(ch));
}
if (xGlyphAdjust != 0 || yGlyphAdjust != 0) {
needTJ = true;
if (sb.length() == 0) {
sb.append('['); //Need to start TJ
}
if (accText.length() > 0) {
if ((sb.length() - lineStart + accText.length()) > 200) {
sb.append(PSGenerator.LF);
lineStart = sb.length();
}
lineStart = writePostScriptString(sb, accText, multiByte, lineStart);
sb.append(' ');
accText.setLength(0); //reset accumulated text
}
if (yGlyphAdjust == 0) {
sb.append(Integer.toString(xGlyphAdjust)).append(' ');
} else {
sb.append('[');
sb.append(Integer.toString(yGlyphAdjust)).append(' ');
sb.append(Integer.toString(xGlyphAdjust)).append(']').append(' ');
}
}
}
if (needTJ) {
if (accText.length() > 0) {
if ((sb.length() - lineStart + accText.length()) > 200) {
sb.append(PSGenerator.LF);
}
writePostScriptString(sb, accText, multiByte);
}
if (hasLetterSpacing) {
sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ");
} else {
sb.append("] TJ");
}
} else {
writePostScriptString(sb, accText, multiByte);
if (hasLetterSpacing) {
StringBuffer spb = new StringBuffer();
spb.append(formatMptAsPt(generator, letterSpacing))
.append(" 0 ");
sb.insert(0, spb.toString());
sb.append(" " + generator.mapCommand("ashow"));
} else {
sb.append(" " + generator.mapCommand("show"));
}
}
generator.writeln(sb.toString());
}
private void writePostScriptString(StringBuffer buffer, StringBuffer string,
boolean multiByte) {
writePostScriptString(buffer, string, multiByte, 0);
}
private int writePostScriptString(StringBuffer buffer, StringBuffer string, boolean multiByte,
int lineStart) {
buffer.append(multiByte ? '<' : '(');
int l = string.length();
int index = 0;
int maxCol = 200;
buffer.append(string.substring(index, Math.min(index + maxCol, l)));
index += maxCol;
while (index < l) {
if (!multiByte) {
buffer.append('\\');
}
buffer.append(PSGenerator.LF);
lineStart = buffer.length();
buffer.append(string.substring(index, Math.min(index + maxCol, l)));
index += maxCol;
}
buffer.append(multiByte ? '>' : ')');
return lineStart;
}
private void useFont(String key, int size, boolean otf) throws IOException {
PSFontResource res = getDocumentHandler().getPSResourceForFontKey(key);
PSGenerator generator = getGenerator();
String name = "/" + res.getName();
if (otf) {
name += ".0";
}
generator.useFont(name, size / 1000f);
res.notifyResourceUsageOnPage(generator.getResourceTracker());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy