blob: 6640f9b802d611c5dc106c512e7fc02b91646adb [file] [log] [blame]
/*
* 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.pdf;
import java.awt.geom.AffineTransform;
/**
* Utility class for generating PDF text objects. It needs to be subclassed to add writing
* functionality (see {@link #write(String)}).
*/
public abstract class PDFTextUtil {
/** The number of decimal places. */
private static final int DEC = 8;
/** PDF text rendering mode: Fill text */
public static final int TR_FILL = 0;
/** PDF text rendering mode: Stroke text */
public static final int TR_STROKE = 1;
/** PDF text rendering mode: Fill, then stroke text */
public static final int TR_FILL_STROKE = 2;
/** PDF text rendering mode: Neither fill nor stroke text (invisible) */
public static final int TR_INVISIBLE = 3;
/** PDF text rendering mode: Fill text and add to path for clipping */
public static final int TR_FILL_CLIP = 4;
/** PDF text rendering mode: Stroke text and add to path for clipping */
public static final int TR_STROKE_CLIP = 5;
/** PDF text rendering mode: Fill, then stroke text and add to path for clipping */
public static final int TR_FILL_STROKE_CLIP = 6;
/** PDF text rendering mode: Add text to path for clipping */
public static final int TR_CLIP = 7;
private boolean inTextObject = false;
private String startText;
private String endText;
private boolean useMultiByte;
private StringBuffer bufTJ;
private int textRenderingMode = TR_FILL;
private String currentFontName;
private double currentFontSize;
/**
* Main constructor.
*/
public PDFTextUtil() {
//nop
}
/**
* Writes PDF code.
* @param code the PDF code to write
*/
protected abstract void write(String code);
private void writeAffineTransform(AffineTransform at, StringBuffer sb) {
double[] lt = new double[6];
at.getMatrix(lt);
sb.append(PDFNumber.doubleOut(lt[0], DEC)).append(" ");
sb.append(PDFNumber.doubleOut(lt[1], DEC)).append(" ");
sb.append(PDFNumber.doubleOut(lt[2], DEC)).append(" ");
sb.append(PDFNumber.doubleOut(lt[3], DEC)).append(" ");
sb.append(PDFNumber.doubleOut(lt[4], DEC)).append(" ");
sb.append(PDFNumber.doubleOut(lt[5], DEC));
}
private void writeChar(char ch, StringBuffer sb) {
if (!useMultiByte) {
if (ch < 32 || ch > 127) {
sb.append("\\").append(Integer.toOctalString((int)ch));
} else {
switch (ch) {
case '(':
case ')':
case '\\':
sb.append("\\");
break;
default:
}
sb.append(ch);
}
} else {
sb.append(PDFText.toUnicodeHex(ch));
}
}
private void checkInTextObject() {
if (!inTextObject) {
throw new IllegalStateException("Not in text object");
}
}
/**
* Indicates whether we are in a text object or not.
* @return true if we are in a text object
*/
public boolean isInTextObject() {
return inTextObject;
}
/**
* Called when a new text object should be started. Be sure to call setFont() before
* issuing any text painting commands.
*/
public void beginTextObject() {
if (inTextObject) {
throw new IllegalStateException("Already in text object");
}
write("BT\n");
this.inTextObject = true;
}
/**
* Called when a text object should be ended.
*/
public void endTextObject() {
checkInTextObject();
write("ET\n");
this.inTextObject = false;
initValues();
}
/**
* Resets the state fields.
*/
protected void initValues() {
this.currentFontName = null;
this.currentFontSize = 0.0;
this.textRenderingMode = TR_FILL;
}
/**
* Creates a "q" command, pushing a copy of the entire graphics state onto the stack.
*/
public void saveGraphicsState() {
write("q\n");
}
/**
* Creates a "Q" command, restoring the entire graphics state to its former value by popping
* it from the stack.
*/
public void restoreGraphicsState() {
write("Q\n");
}
/**
* Creates a "cm" command.
* @param at the transformation matrix
*/
public void concatMatrix(AffineTransform at) {
if (!at.isIdentity()) {
writeTJ();
StringBuffer sb = new StringBuffer();
writeAffineTransform(at, sb);
sb.append(" cm\n");
write(sb.toString());
}
}
/**
* Writes a "Tf" command, setting a new current font.
* @param fontName the name of the font to select
* @param fontSize the font size (in points)
*/
public void writeTf(String fontName, double fontSize) {
checkInTextObject();
write("/" + fontName + " " + PDFNumber.doubleOut(fontSize) + " Tf\n");
this.startText = useMultiByte ? "<" : "(";
this.endText = useMultiByte ? ">" : ")";
}
/**
* Updates the current font. This method only writes a "Tf" if the current font changes.
* @param fontName the name of the font to select
* @param fontSize the font size (in points)
* @param multiByte true indicates the font is a multi-byte font, false means single-byte
*/
public void updateTf(String fontName, double fontSize, boolean multiByte) {
checkInTextObject();
if (!fontName.equals(this.currentFontName) || (fontSize != this.currentFontSize)) {
writeTJ();
this.currentFontName = fontName;
this.currentFontSize = fontSize;
this.useMultiByte = multiByte;
writeTf(fontName, fontSize);
}
}
/**
* Sets the text rendering mode.
* @param mode the rendering mode (value 0 to 7, see PDF Spec, constants: TR_*)
*/
public void setTextRenderingMode(int mode) {
if (mode < 0 || mode > 7) {
throw new IllegalArgumentException(
"Illegal value for text rendering mode. Expected: 0-7");
}
if (mode != this.textRenderingMode) {
writeTJ();
this.textRenderingMode = mode;
write(this.textRenderingMode + " Tr\n");
}
}
/**
* Sets the text rendering mode.
* @param fill true if the text should be filled
* @param stroke true if the text should be stroked
* @param addToClip true if the path should be added for clipping
*/
public void setTextRenderingMode(boolean fill, boolean stroke, boolean addToClip) {
int mode;
if (fill) {
mode = (stroke ? 2 : 0);
} else {
mode = (stroke ? 1 : 3);
}
if (addToClip) {
mode += 4;
}
setTextRenderingMode(mode);
}
/**
* Writes a "Tm" command, setting a new text transformation matrix.
* @param localTransform the new text transformation matrix
*/
public void writeTextMatrix(AffineTransform localTransform) {
StringBuffer sb = new StringBuffer();
writeAffineTransform(localTransform, sb);
sb.append(" Tm ");
write(sb.toString());
}
/**
* Writes a char to the "TJ-Buffer".
* @param codepoint the mapped character (code point/character code)
*/
public void writeTJMappedChar(char codepoint) {
if (bufTJ == null) {
bufTJ = new StringBuffer();
}
if (bufTJ.length() == 0) {
bufTJ.append("[").append(startText);
}
writeChar(codepoint, bufTJ);
}
/**
* Writes a glyph adjust value to the "TJ-Buffer".
* @param adjust the glyph adjust value in thousands of text unit space.
*/
public void adjustGlyphTJ(double adjust) {
if (bufTJ == null) {
bufTJ = new StringBuffer();
}
if (bufTJ.length() > 0) {
bufTJ.append(endText).append(" ");
}
if (bufTJ.length() == 0) {
bufTJ.append("[");
}
bufTJ.append(PDFNumber.doubleOut(adjust, DEC - 4));
bufTJ.append(" ");
bufTJ.append(startText);
}
/**
* Writes a "TJ" command, writing out the accumulated buffer with the characters and glyph
* positioning values. The buffer is reset afterwards.
*/
public void writeTJ() {
if (isInString()) {
bufTJ.append(endText).append("] TJ\n");
write(bufTJ.toString());
bufTJ.setLength(0);
}
}
private boolean isInString() {
return bufTJ != null && bufTJ.length() > 0;
}
}