/* * 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; } }