/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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 General Public License for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.print.layout;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Point2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.Properties;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.compiere.model.MQuery;
import org.compiere.print.MPrintFormatItem;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.Msg;
import org.compiere.util.NamePair;
import org.compiere.util.Util;
/**
* String Print ELement.
* The input can be multiple lines. The first tab is expanded.
*
* @author Jorg Janke
* @version $Id: StringElement.java,v 1.2 2006/07/30 00:53:02 jjanke Exp $
*/
public class StringElement extends PrintElement
{
/**
* generated serial id
*/
private static final long serialVersionUID = 239112399504036626L;
/**
* Standard Field Constructor.
* Created in LayoutEngine.
* @param inText text
* @param font font
* @param paint paint
* @param ID optional ID (null if document)
* @param translateText if true, check for optional text translation
*/
public StringElement (String inText, Font font, Paint paint, NamePair ID, boolean translateText)
{
super();
if (log.isLoggable(Level.FINEST)) log.finest("Text=" + inText + ", ID=" + ID + ", Translate=" + translateText);
m_font = font;
m_paint = paint;
if (translateText)
{
int count = Util.getCount(inText, '@');
if (count > 0 && count % 2 == 0)
{
m_originalString = inText;
// Translate it to get rough space (not correct context) = may be too small
inText = Msg.parseTranslation(Env.getCtx(), m_originalString);
}
}
m_ID = ID;
String[] lines = Pattern.compile("\n", Pattern.MULTILINE).split(inText);
m_string_paper = new AttributedString[lines.length];
m_string_view = new AttributedString[lines.length];
for (int i = 0; i < lines.length; i++)
{
String line = Util.removeCRLF (lines[i]);
m_string_paper[i] = new AttributedString(line);
if (line.length() == 0)
continue;
if (log.isLoggable(Level.FINEST)) log.finest(" - line=" + i + " - " + line);
m_string_paper[i].addAttribute(TextAttribute.FONT, font);
m_string_paper[i].addAttribute(TextAttribute.FOREGROUND, paint);
if (m_ID != null && i == 0) // first line only - create special Attributed String
{
m_string_view[i] = new AttributedString(line);
m_string_view[i].addAttribute(TextAttribute.FONT, font);
int endIndex = line.length();
m_string_view[i].addAttribute(TextAttribute.FOREGROUND, LINK_COLOR);
m_string_view[i].addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL, 0, endIndex);
}
else
m_string_view[i] = m_string_paper[i];
}
// Load Image
waitForLoad(LayoutEngine.IMAGE_TRUE);
waitForLoad(LayoutEngine.IMAGE_FALSE);
} // StringElement
/**
* Attributed String Constructor
* @param string attributed string
*/
public StringElement(AttributedString string)
{
super();
m_string_paper = new AttributedString[] {string};
m_string_view = m_string_paper;
} // StringElement
/**
* Field Constructor.
* Created in LayoutEngine.
* @param content text or boolean
* @param font font
* @param paint paint
* @param ID optional ID (null if document)
* @param label optional label
* @param labelSuffix optional label suffix
*/
public StringElement (Object content, Font font, Paint paint, NamePair ID, String label, String labelSuffix)
{
super();
if (log.isLoggable(Level.FINEST)) log.finest("Label=" + label + "|" + labelSuffix
+ ", Content=" + content + ", ID=" + ID);
m_font = font;
m_paint = paint;
int startIndex = 0;
int endOffset = 0;
StringBuilder text = new StringBuilder();
if (label != null && label.length() > 0)
{
text.append(label).append(" ");
startIndex = label.length() + 1;
}
if (content instanceof Boolean)
m_check = (Boolean)content;
else
text.append(content);
if (labelSuffix != null && labelSuffix.length() > 0)
{
text.append(labelSuffix);
endOffset = labelSuffix.length();
}
m_ID = ID;
String[] lines = Pattern.compile("\n", Pattern.MULTILINE).split(text);
m_string_paper = new AttributedString[lines.length];
m_string_view = new AttributedString[lines.length];
for (int i = 0; i < lines.length; i++)
{
String line = Util.removeCRLF (lines[i]);
m_string_paper[i] = new AttributedString(line);
if (line.length() == 0)
continue;
if (log.isLoggable(Level.FINEST)) log.finest(" - line=" + i + " - " + line);
m_string_paper[i].addAttribute(TextAttribute.FONT, font);
m_string_paper[i].addAttribute(TextAttribute.FOREGROUND, paint);
if (m_ID != null && i == 0) // first line only - create special Attributed String
{
m_string_view[i] = new AttributedString(line);
m_string_view[i].addAttribute(TextAttribute.FONT, font);
m_string_view[i].addAttribute(TextAttribute.FOREGROUND, paint);
int endIndex = line.length() - endOffset;
if (endIndex > startIndex)
{
m_string_view[i].addAttribute (TextAttribute.FOREGROUND, LINK_COLOR, startIndex, endIndex);
m_string_view[i].addAttribute (TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL, startIndex, endIndex);
}
}
else
m_string_view[i] = m_string_paper[i];
}
} // StringElement
/** Attributed String - Viewer */
private AttributedString[] m_string_view = null;
/** Attributed String - Printer */
private AttributedString[] m_string_paper = null;
/** Before translated String */
private String m_originalString = null;
/** Font used */
private Font m_font = null;
/** Paint used */
private Paint m_paint = null;
/** Optional ID of String */
private NamePair m_ID = null;
/** Optional CheckBox */
private Boolean m_check = null;
/**
* Get optional ID
* @return ID or null
*/
public NamePair getID()
{
return m_ID;
} // getID
/**
* Get Original String
* @return original (may be null)
*/
public String getOriginalString()
{
return m_originalString;
} // getOrig
/**
* Translate Content if required.
* If content is translated, the element needs to stay in the bounds
* of the originally calculated size and need to align the field.
* @param ctx context
*/
public void translate(Properties ctx)
{
if (m_originalString == null)
return;
String inText = Msg.parseTranslation(ctx, m_originalString);
// log.fine( "StringElement.translate", inText);
String[] lines = Pattern.compile("\n", Pattern.MULTILINE).split(inText);
m_string_paper = new AttributedString[lines.length];
for (int i = 0; i < lines.length; i++)
{
String line = Util.removeCRLF (lines[i]);
m_string_paper[i] = new AttributedString(line);
if (line.length() > 0)
{
m_string_paper[i].addAttribute(TextAttribute.FONT, m_font);
m_string_paper[i].addAttribute(TextAttribute.FOREGROUND, m_paint);
}
}
m_string_view = m_string_paper;
} // translate
/**
* Layout and Calculate Size.
* Set p_width and p_height.
* @return Size
*/
protected boolean calculateSize()
{
if (p_sizeCalculated)
return true;
//
FontRenderContext frc = new FontRenderContext(null, true, true);
TextLayout layout = null;
p_height = 0f;
p_width = 0f;
// No Limit
if (p_maxWidth == 0f && p_maxHeight == 0f)
{
for (int i = 0; i < m_string_paper.length; i++)
{
AttributedCharacterIterator iter = m_string_paper[i].getIterator();
if (iter.getBeginIndex() == iter.getEndIndex())
continue;
// Check for Tab (just first)
int tabPos = -1;
for (char c = iter.first(); c != CharacterIterator.DONE && tabPos == -1; c = iter.next())
{
if (c == '\t')
tabPos = iter.getIndex();
}
if (tabPos == -1)
{
layout = new TextLayout (iter, frc);
p_height += layout.getAscent() + layout.getDescent() + layout.getLeading();
if (p_width < layout.getAdvance())
p_width = layout.getAdvance();
}
else // with tab
{
LineBreakMeasurer measurer = new LineBreakMeasurer(iter, frc);
layout = measurer.nextLayout(9999, tabPos, false);
p_height += layout.getAscent() + layout.getDescent() + layout.getLeading();
float width = getTabPos (0, layout.getAdvance());
layout = measurer.nextLayout(9999, iter.getEndIndex(), true);
width += layout.getAdvance();
if (p_width < width)
p_width = width;
}
} // for all strings
// Add CheckBox Size
if (m_check != null)
{
p_width += LayoutEngine.IMAGE_SIZE.width;
if (p_height < LayoutEngine.IMAGE_SIZE.height)
p_height = LayoutEngine.IMAGE_SIZE.height;
}
}
// Size Limits
else
{
p_width = p_maxWidth;
for (int i = 0; i < m_string_paper.length; i++)
{
AttributedCharacterIterator iter = m_string_paper[i].getIterator();
if (iter.getBeginIndex() == iter.getEndIndex())
continue;
LineBreakMeasurer measurer = new LineBreakMeasurer(iter, frc);
while (measurer.getPosition() < iter.getEndIndex())
{
// no need to expand tab space for limited space
layout = measurer.nextLayout(p_maxWidth);
float lineHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
if (p_maxHeight == -1f && i == 0) // one line only
p_maxHeight = lineHeight;
if (p_maxHeight == 0f || (p_height + lineHeight) <= p_maxHeight)
p_height += lineHeight;
}
} // for all strings
// Add CheckBox Size
if (m_check != null)
{
// p_width += LayoutEngine.IMAGE_SIZE.width;
if (p_height < LayoutEngine.IMAGE_SIZE.height)
p_height = LayoutEngine.IMAGE_SIZE.height;
}
}
// Enlarge Size when aligned and max size is given
if (p_FieldAlignmentType != null)
{
//boolean changed = false;
if (p_height < p_maxHeight)
{
p_height = p_maxHeight;
//changed = true;
}
if (p_width < p_maxWidth)
{
p_width = p_maxWidth;
//changed = true;
}
}
return true;
} // calculateSize
/**
* Get Drill Down Query
* @param relativePoint point to find print element
* @param pageNo page number (ignored)
* @return drill down query or null
*/
public MQuery getDrillDown (Point relativePoint, int pageNo)
{
if (m_ID != null && getBounds().contains(relativePoint))
{
if (log.isLoggable(Level.FINE)) log.fine(toString());
String columnName = MQuery.getZoomColumnName(m_ID.getName());
String tableName = MQuery.getZoomTableName(columnName);
Object code = m_ID.getID();
if (m_ID instanceof KeyNamePair)
code = Integer.valueOf(((KeyNamePair)m_ID).getKey());
//
MQuery query = new MQuery(tableName);
query.addRestriction(columnName, MQuery.EQUAL, code);
return query;
}
return null;
} // getDrillDown
/**
* Get Drill Across Query
* @param relativePoint relative Point
* @param pageNo page number (ignored)
* @return null - not implemented
*/
public MQuery getDrillAcross (Point relativePoint, int pageNo)
{
return null;
} // getDrillAcross
/**
* Paint/Print.
* Calculate actual Size.
* The text is printed in the topmost left position - i.e. the leading is below the line.
* @param g2D Graphics
* @param pageStart top left Location of page
* @param pageNo page number for multi page support (0 = header/footer) - ignored
* @param ctx print context
* @param isView true if online view (IDs are links)
*/
public void paint (Graphics2D g2D, int pageNo, Point2D pageStart, Properties ctx, boolean isView)
{
Point2D.Double location = getAbsoluteLocation(pageStart);
//
if (m_originalString != null)
translate(ctx);
AttributedString aString = null;
AttributedCharacterIterator iter = null;
float xPos = (float)location.x;
float yPos = (float)location.y;
float yPen = 0f;
float height = 0f;
float width = 0f;
// for all lines
for (int i = 0; i < m_string_paper.length; i++)
{
// Get Text
if (isView)
{
if (m_string_view[i] == null)
continue;
aString = m_string_view[i];
}
else
{
if (m_string_paper[i] == null)
continue;
aString = m_string_paper[i];
}
iter = aString.getIterator();
// Zero Length
if (iter.getBeginIndex() == iter.getEndIndex())
continue;
// Check for Tab (just first) and 16 bit characters
int tabPos = -1;
boolean is8Bit = true;
for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
{
if (c == '\t' && tabPos == -1)
tabPos = iter.getIndex();
if (c > 255)
is8Bit = false;
}
TextLayout layout = null;
float xPen = xPos;
// No Limit
if (p_maxWidth == 0f)
{
if (tabPos == -1)
{
layout = new TextLayout (iter, g2D.getFontRenderContext());
yPen = yPos + layout.getAscent();
boolean fastDraw = LayoutEngine.s_FASTDRAW;
if (fastDraw && !isView && !is8Bit)
fastDraw = false;
if ( fastDraw )
{
g2D.setFont(m_font);
g2D.setPaint(m_paint);
g2D.drawString(iter, xPen, yPen);
}
else
layout.draw(g2D, xPen, yPen);
//
yPos += layout.getAscent() + layout.getDescent() + layout.getLeading();
if (width < layout.getAdvance())
width = layout.getAdvance();
}
else // we have a tab
{
LineBreakMeasurer measurer = new LineBreakMeasurer(iter, g2D.getFontRenderContext());
layout = measurer.nextLayout(9999, tabPos, false);
float lineHeight_1 = layout.getAscent() + layout.getDescent() + layout.getLeading();
yPen = yPos + layout.getAscent();
layout.draw(g2D, xPen, yPen); // first part before tab
xPen = getTabPos (xPos, layout.getAdvance());
float lineWidth = xPen - xPos;
layout = measurer.nextLayout(9999);//, iter.getEndIndex(), true);
float lineHeight_2 = layout.getAscent() + layout.getDescent() + layout.getLeading();
layout.draw(g2D, xPen, yPen); // second part after tab
//
yPos += Math.max(lineHeight_1, lineHeight_2);
lineWidth += layout.getAdvance();
if (width < lineWidth)
width = lineWidth;
}
}
// Size Limits
else
{
boolean fastDraw = LayoutEngine.s_FASTDRAW;
if (fastDraw && !isView && !is8Bit)
fastDraw = false;
LineBreakMeasurer measurer = new LineBreakMeasurer(iter, g2D.getFontRenderContext());
while (measurer.getPosition() < iter.getEndIndex())
{
if (tabPos == -1)
{
layout = measurer.nextLayout(p_maxWidth);
if (measurer.getPosition() < iter.getEndIndex())
fastDraw = false;
}
else // tab
{
fastDraw = false;
layout = measurer.nextLayout(p_maxWidth, tabPos, false);
}
// Line Height
float lineHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
if (p_maxHeight == -1f && i == 0) // one line only
p_maxHeight = lineHeight;
// If we have height left over
if (p_maxHeight == 0f || (height + lineHeight) <= p_maxHeight)
{
yPen = (float)location.y + height + layout.getAscent();
// Tab in Text
if (tabPos != -1)
{
layout.draw(g2D, xPen, yPen); // first part before tab
xPen = getTabPos (xPos, layout.getAdvance());
layout = measurer.nextLayout(p_width, iter.getEndIndex(), true);
tabPos = -1; // reset (just one tab)
}
else if ((MPrintFormatItem.FIELDALIGNMENTTYPE_TrailingRight.equals(p_FieldAlignmentType) && layout.isLeftToRight())
|| (MPrintFormatItem.FIELDALIGNMENTTYPE_LeadingLeft.equals(p_FieldAlignmentType) && !layout.isLeftToRight()))
xPen += p_maxWidth - layout.getAdvance();
else if (MPrintFormatItem.FIELDALIGNMENTTYPE_Center.equals(p_FieldAlignmentType))
xPen += (p_maxWidth - layout.getAdvance()) / 2;
else if (MPrintFormatItem.FIELDALIGNMENTTYPE_Block.equals(p_FieldAlignmentType) && measurer.getPosition() < iter.getEndIndex())
{
layout = layout.getJustifiedLayout(p_maxWidth);
fastDraw = false;
}
if (fastDraw)
{
g2D.setFont(m_font);
g2D.setPaint(m_paint);
g2D.drawString(iter, xPen, yPen);
height += lineHeight;
break;
}
else
{
layout.draw(g2D, xPen, yPen);
}
height += lineHeight;
}
}
width = p_maxWidth;
} // size limits
} // for all strings
if (m_check != null)
{
int x = (int)(location.x + width + 1);
int y = (int)(location.y);
g2D.drawImage(m_check.booleanValue() ? LayoutEngine.IMAGE_TRUE : LayoutEngine.IMAGE_FALSE, x, y, this);
}
} // paint
/**
* Get Tab Position.
* The Tab position is relative to the string itself, not the absolute
* position; i.e. to have the same tab position on a page, strings need
* to start at the same position.
* The Tab is rounded up to the next 30 dividable position.
* @param xPos starting x position
* @param length length of segment
* @return new x Position (xPos + length + tabSpace)
*/
private float getTabPos (float xPos, float length)
{
float retValue = xPos + length;
int iLength = (int)Math.ceil(length);
int tabSpace = iLength % 30;
retValue += (30 - tabSpace);
return retValue;
} // getTabPos
/**
* String Representation
* @return info
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("StringElement[");
sb.append("Bounds=").append(getBounds())
.append(",Height=").append(p_height).append("(").append(p_maxHeight)
.append("),Width=").append(p_width).append("(").append(p_maxHeight)
.append("),PageLocation=").append(p_pageLocation).append(" - ");
for (int i = 0; i < m_string_paper.length; i++)
{
if (m_string_paper.length > 1)
sb.append(Env.NL).append(i).append(":");
AttributedCharacterIterator iter = m_string_paper[i].getIterator();
for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
sb.append(c);
}
if (m_ID != null)
sb.append(",ID=(").append(m_ID.toStringX()).append(")");
sb.append("]");
return sb.toString();
} // toString
} // StringElement