/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.internal;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import com.lowagie.text.Chunk;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.ColumnText;
import com.lowagie.text.pdf.PdfAction;
import com.lowagie.text.pdf.PdfAnnotation;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfDestination;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfObject;
import com.lowagie.text.pdf.PdfOutline;
import com.lowagie.text.pdf.PdfString;
import com.lowagie.text.pdf.PdfTextArray;
import com.lowagie.text.pdf.PdfWriter;
import static java.text.AttributedCharacterIterator.Attribute;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.LocalImageContainer;
import org.pentaho.reporting.engine.classic.core.URLImageContainer;
import org.pentaho.reporting.engine.classic.core.imagemap.ImageMap;
import org.pentaho.reporting.engine.classic.core.imagemap.ImageMapEntry;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.PhysicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableComplexText;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility;
import org.pentaho.reporting.engine.classic.core.layout.process.text.RichTextSpec;
import org.pentaho.reporting.engine.classic.core.layout.text.Glyph;
import org.pentaho.reporting.engine.classic.core.layout.text.GlyphList;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.graphics.internal.LogicalPageDrawable;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextDirection;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
import org.pentaho.reporting.engine.classic.core.util.TypedMapWrapper;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.util.DebugLog;
import org.pentaho.reporting.libraries.base.util.LFUMap;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.base.util.WaitingImageObserver;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointBuffer;
import org.pentaho.reporting.libraries.fonts.itext.BaseFontFontMetrics;
import org.pentaho.reporting.libraries.fonts.registry.FontNativeContext;
import org.pentaho.reporting.libraries.fonts.text.Spacing;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
import org.pentaho.reporting.libraries.xmlns.LibXmlInfo;
/**
* Creation-Date: 17.07.2007, 18:41:46
*
* @author Thomas Morgner
*/
public class PdfLogicalPageDrawable extends LogicalPageDrawable
{
private static final Log logger = LogFactory.getLog(PdfLogicalPageDrawable.class);
protected static class PdfTextSpec extends TextSpec
{
private BaseFontFontMetrics fontMetrics;
private PdfContentByte contentByte;
protected PdfTextSpec(final StyleSheet layoutContext,
final PdfOutputProcessorMetaData metaData,
final PdfGraphics2D g2,
final BaseFontFontMetrics fontMetrics,
final PdfContentByte cb)
{
super(layoutContext, metaData, g2);
if (fontMetrics == null)
{
throw new NullPointerException();
}
if (cb == null)
{
throw new NullPointerException();
}
this.fontMetrics = fontMetrics;
this.contentByte = cb;
}
public BaseFontFontMetrics getFontMetrics()
{
return fontMetrics;
}
public PdfContentByte getContentByte()
{
return contentByte;
}
public void close()
{
contentByte.endText();
//super.close(); // we do not dispose the graphics as we are working on the original object
}
}
private static final float ITALIC_ANGLE = 0.21256f;
private PdfWriter writer;
private float globalHeight;
private boolean globalEmbed;
private LFUMap<ResourceKey, com.lowagie.text.Image> imageCache;
private char version;
private PdfImageHandler imageHandler;
public PdfLogicalPageDrawable(final PdfWriter writer,
final LFUMap<ResourceKey, com.lowagie.text.Image> imageCache,
final char version)
{
if (writer == null)
{
throw new NullPointerException();
}
if (imageCache == null)
{
throw new NullPointerException();
}
this.writer = writer;
this.imageCache = imageCache;
this.version = version;
}
public void init(final LogicalPageBox rootBox,
final OutputProcessorMetaData metaData,
final ResourceManager resourceManager)
{
throw new UnsupportedOperationException();
}
public void init(final LogicalPageBox rootBox,
final PdfOutputProcessorMetaData metaData,
final ResourceManager resourceManager,
final PhysicalPageBox page)
{
super.init(rootBox, metaData, resourceManager);
if (page != null)
{
this.globalHeight = (float)
StrictGeomUtility.toExternalValue(page.getHeight() - page.getImageableY() + page.getGlobalY());
}
else
{
this.globalHeight = rootBox.getPageHeight();
}
this.globalEmbed = getMetaData().isFeatureSupported(OutputProcessorFeature.EMBED_ALL_FONTS);
this.imageHandler = new PdfImageHandler(metaData, resourceManager, imageCache);
}
public PdfOutputProcessorMetaData getMetaData()
{
return (PdfOutputProcessorMetaData) super.getMetaData();
}
/**
* Draws the object.
*
* @param graphics the graphics device.
* @param area the area inside which the object should be drawn.
*/
public void draw(final Graphics2D graphics, final Rectangle2D area)
{
super.draw(graphics, area);
}
protected void processLinksAndAnchors(final RenderNode box)
{
final StyleSheet styleSheet = box.getStyleSheet();
if (drawPdfScript(box) == false)
{
final String target = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TARGET);
final String title = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_TITLE);
if (target != null || title != null)
{
final String window = (String) styleSheet.getStyleProperty(ElementStyleKeys.HREF_WINDOW);
drawHyperlink(box, target, window, title);
}
}
final String anchor = (String) styleSheet.getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
if (anchor != null)
{
drawAnchor(box);
}
final String bookmark = (String) styleSheet.getStyleProperty(BandStyleKeys.BOOKMARK);
if (bookmark != null)
{
drawBookmark(box, bookmark);
}
}
protected boolean drawPdfScript(final RenderNode box)
{
final Object attribute = box.getAttributes().getAttribute
(AttributeNames.Pdf.NAMESPACE, AttributeNames.Pdf.SCRIPT_ACTION);
if (attribute == null)
{
return false;
}
final String attributeText = String.valueOf(attribute);
final PdfAction action = PdfAction.javaScript(attributeText, writer, false);
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final float leftX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX()));
final float rightX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX() + box.getWidth()));
final float lowerY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY() + box.getHeight()));
final float upperY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));
final PdfAnnotation annotation = new PdfAnnotation(writer, leftX, lowerY, rightX, upperY, action);
writer.addAnnotation(annotation);
return true;
}
protected void drawAnchor(final RenderNode content)
{
if (content.isNodeVisible(getDrawArea()) == false)
{
return;
}
final String anchorName = (String) content.getStyleSheet().getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
if (anchorName == null)
{
return;
}
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final float upperY = translateX + (float) (globalHeight - StrictGeomUtility.toExternalValue(content.getY()));
final float leftX = (float) (StrictGeomUtility.toExternalValue(content.getX()));
final PdfDestination dest = new PdfDestination(PdfDestination.FIT, leftX, upperY, 0);
writer.getDirectContent().localDestination(anchorName, dest);
}
protected void drawBookmark(final RenderNode box, final String bookmark)
{
if (box.isNodeVisible(getDrawArea()) == false)
{
return;
}
final PdfOutline root = writer.getDirectContent().getRootOutline();
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final float upperY = translateX + (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));
final float leftX = (float) (StrictGeomUtility.toExternalValue(box.getX()));
final PdfDestination dest = new PdfDestination(PdfDestination.FIT, leftX, upperY, 0);
new PdfOutline(root, dest, bookmark);
// destination will always point to the 'current' page
// todo: Make this a hierarchy ..
}
protected void drawHyperlink(final RenderNode box, final String target, final String window, final String title)
{
if (box.isNodeVisible(getDrawArea()) == false)
{
return;
}
final PdfAction action = createActionForLink(target);
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final float leftX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX()));
final float rightX = translateX + (float) (StrictGeomUtility.toExternalValue(box.getX() + box.getWidth()));
final float lowerY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY() + box.getHeight()));
final float upperY = (float) (globalHeight - StrictGeomUtility.toExternalValue(box.getY()));
if (action != null)
{
final PdfAnnotation annotation = new PdfAnnotation(writer, leftX, lowerY, rightX, upperY, action);
writer.addAnnotation(annotation);
}
else if (StringUtils.isEmpty(title) == false)
{
final Rectangle rect = new Rectangle(leftX, lowerY, rightX, upperY);
final PdfAnnotation commentAnnotation = PdfAnnotation.createText(writer, rect, "Tooltip", title, false, null);
commentAnnotation.setAppearance(PdfAnnotation.APPEARANCE_NORMAL,
writer.getDirectContent().createAppearance(rect.getWidth(), rect.getHeight()));
writer.addAnnotation(commentAnnotation);
}
}
private PdfAction createActionForLink(final String target)
{
if (StringUtils.isEmpty(target))
{
return null;
}
final PdfAction action = new PdfAction();
if (target.startsWith("#"))
{
// its a local link ..
action.put(PdfName.S, PdfName.GOTO);
action.put(PdfName.D, new PdfString(target.substring(1)));
}
else
{
action.put(PdfName.S, PdfName.URI);
action.put(PdfName.URI, new PdfString(target));
}
return action;
}
protected void drawText(final RenderableText renderableText, final long contentX2)
{
if (renderableText.getLength() == 0)
{
return;
}
final long posX = renderableText.getX();
final long posY = renderableText.getY();
final float x1 = (float) (StrictGeomUtility.toExternalValue(posX));
final PdfContentByte cb;
PdfTextSpec textSpec = (PdfTextSpec) getTextSpec();
if (textSpec == null)
{
final StyleSheet layoutContext = renderableText.getStyleSheet();
// The code below may be weird, but at least it is predictable weird.
final String fontName = getMetaData().getNormalizedFontFamilyName
((String) layoutContext.getStyleProperty(TextStyleKeys.FONT));
final String encoding = (String) layoutContext.getStyleProperty(TextStyleKeys.FONTENCODING);
final float fontSize = (float) layoutContext.getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 10);
final boolean embed = globalEmbed || layoutContext.getBooleanStyleProperty(TextStyleKeys.EMBEDDED_FONT);
final boolean bold = layoutContext.getBooleanStyleProperty(TextStyleKeys.BOLD);
final boolean italics = layoutContext.getBooleanStyleProperty(TextStyleKeys.ITALIC);
final BaseFontFontMetrics fontMetrics = getMetaData().getBaseFontFontMetrics
(fontName, fontSize, bold, italics, encoding, embed, false);
final PdfGraphics2D g2 = (PdfGraphics2D) getGraphics();
final Color cssColor = (Color) layoutContext.getStyleProperty(ElementStyleKeys.PAINT);
g2.setPaint(cssColor);
g2.setFillPaint();
g2.setStrokePaint();
//final float translateY = (float) affineTransform.getTranslateY();
cb = g2.getRawContentByte();
textSpec = new PdfTextSpec(layoutContext, getMetaData(), g2, fontMetrics, cb);
setTextSpec(textSpec);
cb.beginText();
cb.setFontAndSize(fontMetrics.getBaseFont(), fontSize);
}
else
{
cb = textSpec.getContentByte();
}
final BaseFontFontMetrics baseFontRecord = textSpec.getFontMetrics();
final BaseFont baseFont = baseFontRecord.getBaseFont();
final float ascent = baseFont.getFontDescriptor(BaseFont.BBOXURY, textSpec.getFontSize());
final float y2 = (float) (StrictGeomUtility.toExternalValue(posY) + ascent);
final float y = globalHeight - y2;
final AffineTransform affineTransform = textSpec.getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final FontNativeContext nativeContext = baseFontRecord.getNativeContext();
if (baseFontRecord.isTrueTypeFont() && textSpec.isBold() && nativeContext.isNativeBold() == false)
{
final float strokeWidth = textSpec.getFontSize() / 30.0f; // right from iText ...
if (strokeWidth == 1)
{
cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
}
else
{
cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE);
cb.setLineWidth(strokeWidth);
}
}
else
{
cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
}
// if the font does not declare to be italics already, emulate it ..
if (baseFontRecord.isTrueTypeFont() && textSpec.isItalics() && nativeContext.isNativeItalics() == false)
{
final float italicAngle =
baseFont.getFontDescriptor(BaseFont.ITALICANGLE, textSpec.getFontSize());
if (italicAngle == 0)
{
// italics requested, but the font itself does not supply italics gylphs.
cb.setTextMatrix(1, 0, PdfLogicalPageDrawable.ITALIC_ANGLE, 1, x1 + translateX, y);
}
else
{
cb.setTextMatrix(x1 + translateX, y);
}
}
else
{
cb.setTextMatrix(x1 + translateX, y);
}
final OutputProcessorMetaData metaData = getMetaData();
final GlyphList gs = renderableText.getGlyphs();
final int offset = renderableText.getOffset();
final CodePointBuffer codePointBuffer = getCodePointBuffer();
if (metaData.isFeatureSupported(OutputProcessorFeature.FAST_FONTRENDERING) &&
isNormalTextSpacing(renderableText))
{
final int maxLength = renderableText.computeMaximumTextSize(contentX2);
final String text = gs.getText(renderableText.getOffset(), maxLength, codePointBuffer);
cb.showText(text);
}
else
{
final PdfTextArray textArray = new PdfTextArray();
final StringBuilder buffer = new StringBuilder(gs.getSize());
final int maxPos = offset + renderableText.computeMaximumTextSize(contentX2);
for (int i = offset; i < maxPos; i++)
{
final Glyph g = gs.getGlyph(i);
final Spacing spacing = g.getSpacing();
if (i != offset)
{
final float optimum = (float) StrictGeomUtility.toFontMetricsValue(spacing.getMinimum());
if (optimum != 0)
{
textArray.add(buffer.toString());
textArray.add(-optimum / textSpec.getFontSize());
buffer.setLength(0);
}
}
final String text = gs.getGlyphAsString(i, codePointBuffer);
buffer.append(text);
}
if (buffer.length() > 0)
{
textArray.add(buffer.toString());
}
cb.showText(textArray);
}
}
private ParagraphRenderBox paragraphContext;
protected void processParagraphChilds(final ParagraphRenderBox box)
{
try
{
paragraphContext = box;
super.processParagraphChilds(box);
}
finally
{
paragraphContext = null;
}
}
private float computeLeadingSafetyMargin(final RenderableComplexText node)
{
if (paragraphContext == null)
{
return -SAFETY_MARGIN;
}
ElementAlignment alignment;
if (node.getNext() == null)
{
alignment = paragraphContext.getLastLineAlignment();
}
else
{
alignment = paragraphContext.getTextAlignment();
}
if (ElementAlignment.LEFT.equals(alignment))
{
return 0;
}
else if (ElementAlignment.RIGHT.equals(alignment))
{
return -SAFETY_MARGIN * 2;
}
else if (ElementAlignment.CENTER.equals(alignment))
{
return -SAFETY_MARGIN;
}
else
{
if (computeRunDirection(node) == PdfWriter.RUN_DIRECTION_RTL)
{
return -SAFETY_MARGIN * 2;
}
else
{
return 0f;
}
}
}
private static final float SAFETY_MARGIN = 0.1f;
private float computeTrailingSafetyMargin(final RenderableComplexText node)
{
if (paragraphContext == null)
{
return SAFETY_MARGIN;
}
ElementAlignment alignment;
if (node.getNext() == null)
{
alignment = paragraphContext.getLastLineAlignment();
}
else
{
alignment = paragraphContext.getTextAlignment();
}
if (ElementAlignment.LEFT.equals(alignment))
{
return SAFETY_MARGIN * 2;
}
else if (ElementAlignment.RIGHT.equals(alignment))
{
return 0;
}
else if (ElementAlignment.CENTER.equals(alignment))
{
return SAFETY_MARGIN;
}
else
{
if (computeRunDirection(node) == PdfWriter.RUN_DIRECTION_RTL)
{
return 0f;
}
else
{
return SAFETY_MARGIN * 2;
}
}
}
private int mapAlignment(final RenderableComplexText node)
{
ElementAlignment alignment;
if (node.getNext() == null)
{
alignment = paragraphContext.getLastLineAlignment();
}
else
{
alignment = paragraphContext.getTextAlignment();
}
if (ElementAlignment.LEFT.equals(alignment))
{
return Element.ALIGN_LEFT;
}
else if (ElementAlignment.RIGHT.equals(alignment))
{
return Element.ALIGN_RIGHT;
}
else if (ElementAlignment.CENTER.equals(alignment))
{
return Element.ALIGN_CENTER;
}
else
{
return Element.ALIGN_JUSTIFIED;
}
}
protected void drawComplexText(final RenderableComplexText node, final Graphics2D g2)
{
try
{
final Phrase p = createPhrase(node);
final ColumnConfig cc = createColumnText(node);
final PdfGraphics2D pg2 = (PdfGraphics2D) getGraphics();
final PdfContentByte cb = pg2.getRawContentByte();
ColumnText ct = cc.reconfigure(cb, p);
ct.setText(p);
if (ct.go(false) == ColumnText.NO_MORE_COLUMN)
{
throw new InvalidReportStateException
("iText signaled an error when printing text. Failing to prevent silent data-loss: Width=" +
ct.getFilledWidth());
}
}
catch (DocumentException e)
{
throw new InvalidReportStateException(e);
}
}
private static class ColumnConfig
{
private float llx;
private float lly;
private float urx;
private float ury;
private float leading;
private int alignment;
private int runDirection;
private ColumnConfig(final float llx,
final float lly,
final float urx,
final float ury,
final float leading,
final int alignment,
final int runDirection)
{
this.llx = llx;
this.lly = lly;
this.urx = urx;
this.ury = ury;
this.leading = leading;
this.alignment = alignment;
this.runDirection = runDirection;
}
public ColumnText reconfigure(PdfContentByte cb, Phrase p)
{
float filledWidth = ColumnText.getWidth(p, runDirection, 0) + 0.5f;
ColumnText ct = new ColumnText(cb);
ct.setRunDirection(runDirection);
if (alignment == Element.ALIGN_LEFT)
{
ct.setSimpleColumn(llx, lly, llx + filledWidth, ury, leading, alignment);
}
else if (alignment == Element.ALIGN_RIGHT)
{
ct.setSimpleColumn(urx - filledWidth, lly, urx, ury, leading, alignment);
}
else if (alignment == Element.ALIGN_CENTER)
{
float delta = ((urx - llx) - filledWidth) / 2;
ct.setSimpleColumn(urx + delta, lly, urx - delta, ury, leading, alignment);
}
else {
ct.setSimpleColumn(llx, lly, urx, ury, leading, alignment);
}
return ct;
}
}
private ColumnConfig createColumnText(final RenderableComplexText node)
{
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final float width = (float) StrictGeomUtility.toExternalValue(node.getWidth());
final float nodeX = (float) StrictGeomUtility.toExternalValue(node.getX());
float marginLeft = width * computeLeadingSafetyMargin(node);
final float llx = translateX + nodeX + marginLeft;
float marginRight = width * computeTrailingSafetyMargin(node);
final float urx = translateX + nodeX + width + marginRight;
final float y2 = (float) (StrictGeomUtility.toExternalValue(node.getY()));
final float lly = globalHeight - y2;
final float ury = (float) (lly - 1.2 * StrictGeomUtility.toExternalValue(node.getHeight()));
final ColumnConfig ct = new ColumnConfig(llx, lly, urx, ury,
node.getParagraphFontMetrics().getAscent(), mapAlignment(node), computeRunDirection(node));
return ct;
}
private Phrase createPhrase(final RenderableComplexText node)
{
Phrase p = new Phrase();
RichTextSpec text = node.getRichText();
for (RichTextSpec.StyledChunk c : text.getStyleChunks())
{
TypedMapWrapper<Attribute, Object> attributes = new TypedMapWrapper<Attribute, Object>(c.getAttributes());
final Number size = attributes.get(TextAttribute.SIZE, 10f, Number.class);
final PdfTextSpec pdfTextSpec = computeFont(c);
final int style = computeStyle(attributes, pdfTextSpec);
final Color paint = (Color) c.getStyleSheet().getStyleProperty(ElementStyleKeys.PAINT);
// add chunks
BaseFont baseFont = pdfTextSpec.getFontMetrics().getBaseFont();
Font font = new Font(baseFont, size.floatValue(), style, paint);
if (c.getOriginatingTextNode() instanceof RenderableReplacedContentBox)
{
RenderableReplacedContentBox content = (RenderableReplacedContentBox) c.getOriginatingTextNode();
com.lowagie.text.Image image = imageHandler.createImage(content);
if (image != null)
{
Chunk chunk = new Chunk(image, 0, 0);
// chunk.setFont(font);
p.add(chunk);
}
}
else
{
String textToPrint = c.getText();
Chunk chunk = new Chunk(textToPrint, font);
p.add(chunk);
}
}
return p;
}
private int computeRunDirection(final RenderableComplexText node)
{
Object styleProperty = node.getStyleSheet().getStyleProperty(TextStyleKeys.DIRECTION, TextDirection.LTR);
if (TextDirection.RTL.equals(styleProperty))
{
return PdfWriter.RUN_DIRECTION_RTL;
}
else
{
return PdfWriter.RUN_DIRECTION_LTR;
}
}
private PdfTextSpec computeFont(final RichTextSpec.StyledChunk c)
{
final StyleSheet layoutContext = c.getStyleSheet();
final PdfGraphics2D g2 = (PdfGraphics2D) getGraphics();
final PdfContentByte cb = g2.getRawContentByte();
final TypedMapWrapper<Attribute, Object> attributes = new TypedMapWrapper<Attribute, Object>(c.getAttributes());
final String fontName = attributes.get(TextAttribute.FAMILY, String.class);
final Number fontSize = attributes.get(TextAttribute.SIZE, 10f, Number.class);
final String encoding = (String) layoutContext.getStyleProperty(TextStyleKeys.FONTENCODING);
final boolean embed = globalEmbed || layoutContext.getBooleanStyleProperty(TextStyleKeys.EMBEDDED_FONT);
final Float weightRaw = attributes.get(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR, Float.class);
final Float italicsRaw = attributes.get(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, Float.class);
final BaseFontFontMetrics fontMetrics = getMetaData().getBaseFontFontMetrics
(fontName, fontSize.doubleValue(), weightRaw >= TextAttribute.WEIGHT_DEMIBOLD,
italicsRaw >= TextAttribute.POSTURE_OBLIQUE, encoding, embed, false);
return new PdfTextSpec(layoutContext, getMetaData(), g2, fontMetrics, cb);
}
private int computeStyle(final TypedMapWrapper<Attribute, Object> attributes,
final PdfTextSpec pdfTextSpec)
{
final Float weight = attributes.get(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR, Float.class);
final Float italics = attributes.get(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, Float.class);
final boolean underlined = attributes.exists(TextAttribute.UNDERLINE);
final boolean strikethrough = attributes.exists(TextAttribute.STRIKETHROUGH);
FontNativeContext nativeContext = pdfTextSpec.getFontMetrics().getNativeContext();
int style = 0;
if (nativeContext.isNativeBold() == false && weight >= TextAttribute.WEIGHT_DEMIBOLD)
{
style |= Font.BOLD;
}
if (nativeContext.isNativeItalics() == false && italics >= TextAttribute.POSTURE_OBLIQUE)
{
style |= Font.ITALIC;
}
if (underlined)
{
style |= Font.UNDERLINE;
}
if (strikethrough)
{
style |= Font.STRIKETHRU;
}
return style;
}
protected boolean startInlineBox(final InlineRenderBox box)
{
if (box.getStaticBoxLayoutProperties().isVisible() == false)
{
return false;
}
if (box.isBoxVisible(getDrawArea()) == false)
{
return false;
}
return super.startInlineBox(box);
}
protected void finishInlineBox(final InlineRenderBox box)
{
if (box.getStaticBoxLayoutProperties().isVisible() == false)
{
return;
}
if (box.isBoxVisible(getDrawArea()) == false)
{
return;
}
super.finishInlineBox(box);
}
protected void drawReplacedContent(final RenderableReplacedContentBox content)
{
final Graphics2D g2 = getGraphics();
final Object o = content.getContent().getRawObject();
if (o instanceof DrawableWrapper)
{
final DrawableWrapper drawableWrapper = (DrawableWrapper) o;
if (drawDrawable(content, g2, drawableWrapper))
{
drawImageMap(content);
}
return;
}
if (o instanceof Image)
{
if (drawImage(content, (Image) o))
{
drawImageMap(content);
}
return;
}
if (o instanceof URLImageContainer)
{
final URLImageContainer imageContainer = (URLImageContainer) o;
com.lowagie.text.Image instance = imageHandler.createImage(imageContainer);
if (instance != null)
{
try
{
ResourceManager resourceManager = getResourceManager();
ResourceKey resource = imageContainer.getResourceKey();
final Resource imageWrapped = resourceManager.create(resource, null, Image.class);
final Image image = (Image) imageWrapped.getResource();
if (drawImage(content, image, instance))
{
drawImageMap(content);
}
return;
}
catch (Exception e)
{
PdfLogicalPageDrawable.logger.info("URL-image cannot be rendered, as the image was not loadable.", e);
}
}
}
if (o instanceof LocalImageContainer)
{
final LocalImageContainer imageContainer = (LocalImageContainer) o;
final Image image = imageContainer.getImage();
if (drawImage(content, image))
{
drawImageMap(content);
}
}
else
{
PdfLogicalPageDrawable.logger.debug("Unable to handle " + o);
}
}
protected void drawImageMap(final RenderableReplacedContentBox content)
{
if (version < '6')
{
return;
}
final ImageMap imageMap = RenderUtility.extractImageMap(content);
// only generate a image map, if the user does not specify their own onw via the override.
// Of course, they would have to provide the map by other means as well.
if (imageMap == null)
{
return;
}
final ImageMapEntry[] imageMapEntries = imageMap.getMapEntries();
for (int i = 0; i < imageMapEntries.length; i++)
{
final ImageMapEntry imageMapEntry = imageMapEntries[i];
final String link = imageMapEntry.getAttribute(LibXmlInfo.XHTML_NAMESPACE, "href");
final String tooltip = imageMapEntry.getAttribute(LibXmlInfo.XHTML_NAMESPACE, "title");
if (StringUtils.isEmpty(tooltip))
{
continue;
}
final AffineTransform affineTransform = getGraphics().getTransform();
final float translateX = (float) affineTransform.getTranslateX();
final int x = (int) (translateX + StrictGeomUtility.toExternalValue(content.getX()));
final int y = (int) StrictGeomUtility.toExternalValue(content.getY());
final float[] translatedCoords = translateCoordinates(imageMapEntry.getAreaCoordinates(), x, y);
final PolygonAnnotation polygonAnnotation = new PolygonAnnotation(writer, translatedCoords);
polygonAnnotation.put(PdfName.CONTENTS, new PdfString(tooltip, PdfObject.TEXT_UNICODE));
writer.addAnnotation(polygonAnnotation);
}
}
private float[] translateCoordinates(final float[] coords, final float dx, final float dy)
{
final float[] floats = coords.clone();
if (floats.length % 2 != 0)
{
throw new IllegalArgumentException("Corrdinates are not x/y pairs");
}
for (int i = 0; i < floats.length; i += 2)
{
floats[i] += dx;
floats[i + 1] = globalHeight - floats[i + 1] + dy;
}
return floats;
}
protected boolean drawImage(final RenderableReplacedContentBox content,
final Image image,
final com.lowagie.text.Image itextImage)
{
final StyleSheet layoutContext = content.getStyleSheet();
final boolean shouldScale = layoutContext.getBooleanStyleProperty(ElementStyleKeys.SCALE);
final int x = (int) StrictGeomUtility.toExternalValue(content.getX());
final int y = (int) StrictGeomUtility.toExternalValue(content.getY());
final int width = (int) StrictGeomUtility.toExternalValue(content.getWidth());
final int height = (int) StrictGeomUtility.toExternalValue(content.getHeight());
if (width == 0 || height == 0)
{
PdfLogicalPageDrawable.logger.debug("Error: Image area is empty: " + content);
return false;
}
final WaitingImageObserver obs = new WaitingImageObserver(image);
obs.waitImageLoaded();
final int imageWidth = image.getWidth(obs);
final int imageHeight = image.getHeight(obs);
if (imageWidth < 1 || imageHeight < 1)
{
return false;
}
final Rectangle2D.Double drawAreaBounds = new Rectangle2D.Double(x, y, width, height);
final AffineTransform scaleTransform;
final Graphics2D g2;
if (shouldScale == false)
{
double deviceScaleFactor = 1;
final double devResolution = getMetaData().getNumericFeatureValue(OutputProcessorFeature.DEVICE_RESOLUTION);
if (getMetaData().isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING))
{
if (devResolution != 72.0 && devResolution > 0)
{
// Need to scale the device to its native resolution before attempting to draw the image..
deviceScaleFactor = (72.0 / devResolution);
}
}
final int clipWidth = Math.min(width, (int) Math.ceil(deviceScaleFactor * imageWidth));
final int clipHeight = Math.min(height, (int) Math.ceil(deviceScaleFactor * imageHeight));
final ElementAlignment horizontalAlignment =
(ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
final ElementAlignment verticalAlignment =
(ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
final int alignmentX = (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
final int alignmentY = (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);
g2 = (Graphics2D) getGraphics().create();
g2.clip(drawAreaBounds);
g2.translate(x, y);
g2.translate(alignmentX, alignmentY);
g2.clip(new Rectangle2D.Float(0, 0, clipWidth, clipHeight));
g2.scale(deviceScaleFactor, deviceScaleFactor);
scaleTransform = null;
}
else
{
g2 = (Graphics2D) getGraphics().create();
g2.clip(drawAreaBounds);
g2.translate(x, y);
g2.clip(new Rectangle2D.Float(0, 0, width, height));
final double scaleX;
final double scaleY;
final boolean keepAspectRatio = layoutContext.getBooleanStyleProperty(ElementStyleKeys.KEEP_ASPECT_RATIO);
if (keepAspectRatio)
{
final double scaleFactor = Math.min(width / (double) imageWidth, height / (double) imageHeight);
scaleX = scaleFactor;
scaleY = scaleFactor;
}
else
{
scaleX = width / (double) imageWidth;
scaleY = height / (double) imageHeight;
}
final int clipWidth = (int) (scaleX * imageWidth);
final int clipHeight = (int) (scaleY * imageHeight);
final ElementAlignment horizontalAlignment =
(ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.ALIGNMENT);
final ElementAlignment verticalAlignment =
(ElementAlignment) layoutContext.getStyleProperty(ElementStyleKeys.VALIGNMENT);
final int alignmentX = (int) RenderUtility.computeHorizontalAlignment(horizontalAlignment, width, clipWidth);
final int alignmentY = (int) RenderUtility.computeVerticalAlignment(verticalAlignment, height, clipHeight);
g2.translate(alignmentX, alignmentY);
scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
}
final PdfGraphics2D pdfGraphics2D = (PdfGraphics2D) g2;
pdfGraphics2D.drawPdfImage(itextImage, image, scaleTransform, null);
g2.dispose();
return true;
}
}