/* * Copyright 2006-2012 ICEsoft Technologies Inc. * * Licensed 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. */ package org.icepdf.core.pobjects.graphics; import org.icepdf.core.pobjects.fonts.FontFile; import org.icepdf.core.pobjects.graphics.text.GlyphText; import org.icepdf.core.util.Defs; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.geom.AffineTransform; import java.util.ArrayList; /** * <p>This class represents text which will be rendered to the a graphics context. * This class was created to act as a wrapper for painting text using the Phelphs * font library as well as painting using java.awt.Font.</p> * <p/> * <p>Objects of this type are created by the content parser when "TJ" or "Tj" * operands are encountered in a page's content stream. Each TextSprite object * is comprised of a list of CharSprites which can be painted by the Shapes * class at render time.</p> * * @since 2.0 */ public class TextSprite { // ability to turn off optimized drawing for text. private final static boolean OPTIMIZED_DRAWING_ENABLED = Defs.booleanProperty("org.icepdf.core.text.optimized", true); // child GlyphText objects private ArrayList<GlyphText> glyphTexts; // text bounds, including all child Glyph sprites, in glyph space // this bound is used during painting to respect painting clip. Rectangle2D.Float bounds; // space reference for where glyph private AffineTransform graphicStateTransform; // stroke color private Color strokeColor; // the write private int rmode; // Font used to paint text private FontFile font; /** * <p>Creates a new TextSprit object.</p> * * @param font font used when painting glyphs. * @param size size of the font in user space */ public TextSprite(FontFile font, int size, AffineTransform graphicStateTransform) { glyphTexts = new ArrayList<GlyphText>(size); // all glyphs in text share this ctm this.graphicStateTransform = graphicStateTransform; this.font = font; bounds = new Rectangle2D.Float(); } /** * <p>Adds a new text char to the TextSprite which will pe painted at x, y under * the current CTM</p> * * @param cid cid to paint. * @param unicode unicode represention of cid. * @param x x-coordinate to paint. * @param y y-coordinate to paint. * @param width width of cid from font. */ public GlyphText addText(String cid, String unicode, float x, float y, float width) { // keep track of the text total bound, important for shapes painting. // IMPORTANT: where working in Java Coordinates with any of the Font bounds float w = width;//(float)stringBounds.getWidth(); float h = (float) (font.getAscent() + font.getDescent()); if (h <= 0.0f) { h = (float) (font.getMaxCharBounds().getHeight()); } if (w <= 0.0f) { w = (float) font.getMaxCharBounds().getWidth(); } // zero height will not intersect with clip rectangle // todo: test if this can occur, might be legacy code from old bug... if (h <= 0.0f) { h = 1.0f; } if (w <= 0.0f) { w = 1.0f; } Rectangle2D.Float glyphBounds = new Rectangle2D.Float(x, y - (float) font.getAscent(), w, h); // add bounds to total text bounds. bounds.add(glyphBounds); // create glyph and normalize bounds. GlyphText glyphText = new GlyphText(x, y, glyphBounds, cid, unicode); glyphText.normalizeToUserSpace(graphicStateTransform); glyphTexts.add(glyphText); return glyphText; } /** * Gets the character bounds of each glyph found in the TextSprite. * @return bounds in PDF coordinates of character bounds */ public ArrayList<GlyphText> getGlyphSprites() { return glyphTexts; } public AffineTransform getGraphicStateTransform() { return graphicStateTransform; } /** * Set the graphic state transorm on all child sprites, This is used for * xForm object parsing and text selection. There is no need to do this * outside of the context parser. * @param graphicStateTransform */ public void setGraphicStateTransform(AffineTransform graphicStateTransform) { this.graphicStateTransform = graphicStateTransform; for (GlyphText sprite : glyphTexts){ sprite.normalizeToUserSpace(this.graphicStateTransform); } } /** * <p>Set the rmode for all the characters being in this object. Rmode can * have the following values:</p> * <ul> * <li>0 - Fill text.</li> * <li>1 - Stroke text. </li> * <li>2 - fill, then stroke text. </li> * <li>3 - Neither fill nor stroke text (invisible). </li> * <li>4 - Fill text and add to path for clipping. </li> * <li>5 - Stroke text and add to path for clipping. </li> * <li>6 - Fill, then stroke text and add to path for clipping. </li> * <li>7 - Add text to path for clipping.</li> * </ul> * * @param rmode valid rmode from 0-7 */ public void setRMode(int rmode) { if (rmode >= 0) { this.rmode = rmode; } } public String toString() { StringBuilder text = new StringBuilder(glyphTexts.size()); for (GlyphText glyphText : glyphTexts){ text.append(glyphText.getUnicode()); } return text.toString(); } public void setStrokeColor(Color color) { strokeColor = color; } /** * Getst the bounds of the text that makes up this sprite. The bounds * are defined PDF space and are relative to the current CTM. * @return */ public Rectangle2D.Float getBounds(){ return bounds; } /** * <p>Paints all the character elements in this TextSprite to the graphics * context</p> * * @param g graphics context to which the characters will be painted to. */ public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // draw bounding box. // drawBoundBox(g2d); for (GlyphText glyphText : glyphTexts){ // paint glyph font.drawEstring(g2d, glyphText.getCid(), glyphText.getX(), glyphText.getY(), FontFile.LAYOUT_NONE, rmode, strokeColor); // debug glyph box // draw glyph box // drawGyphBox(g2d, glyphText); } } /* private void drawBoundBox(Graphics2D gg) { // draw the characters GeneralPath charOutline; Color oldColor = gg.getColor(); Stroke oldStroke = gg.getStroke(); double scale = gg.getTransform().getScaleX(); scale = 1.0f / scale; if (scale <= 0) { scale = 1; } gg.setStroke(new BasicStroke((float) (scale))); gg.setColor(Color.blue); charOutline = new GeneralPath(bounds); gg.draw(charOutline); gg.setColor(oldColor); gg.setStroke(oldStroke); } */ /* private void drawGyphBox(Graphics2D gg, GlyphText glyphSprite) { // draw the characters GeneralPath charOutline; Color oldColor = gg.getColor(); Stroke oldStroke = gg.getStroke(); double scale = gg.getTransform().getScaleX(); scale = 1.0f / scale; if (scale <= 0) { scale = 1; } gg.setStroke(new BasicStroke((float) (scale))); gg.setColor(Color.red); charOutline = new GeneralPath(glyphSprite.getGeneralPath()); gg.draw(charOutline); gg.setColor(oldColor); gg.setStroke(oldStroke); } */ /** * Tests if the interior of the <code>TextSprite</code> bounds intersects the * interior of a specified <code>shape</code>. * * @param shape shape to calculate intersection against * @return true, if <code>TextSprite</code> bounds intersects <code>shape</code>; * otherwise; false. */ public boolean intersects(Shape shape) { // return shape.intersects(bounds.toJava2dCoordinates()); return !OPTIMIZED_DRAWING_ENABLED || shape.intersects(bounds); } /** * Dispose this TextSprite Object. */ public void dispose() { glyphTexts.clear(); glyphTexts.trimToSize(); strokeColor = null; font = null; } }