/**
* ===========================================
* LibLayout : a free Java layouting library
* ===========================================
*
* Project Info: http://reporting.pentaho.org/liblayout/
*
* (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library 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.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------
* $Id: RenderableText.java 6653 2008-12-02 14:53:40Z tmorgner $
* ------------
* (C) Copyright 2006-2007, by Pentaho Corporation.
*/
package org.jfree.layouting.renderer.model;
import org.jfree.layouting.renderer.text.ExtendedBaselineInfo;
import org.jfree.layouting.renderer.text.Glyph;
import org.pentaho.reporting.libraries.fonts.text.breaks.BreakOpportunityProducer;
import org.pentaho.reporting.libraries.fonts.text.Spacing;
/**
* The renderable text is a text chunk, enriched with layouting information,
* such as break opportunities, character sizes, kerning information and spacing
* information.
* <p/>
* Text is given as codepoints. Break opportunities are given as integer values,
* where zero forbids breaking, and higher values denote better breaks. Spacing
* and glyph sizes and kerning is given in micro-points; Spacing is the 'added'
* space between codepoints if text-justification is enabled.
* <p/>
* The text is computed as grapheme clusters; this means that several unicode
* codepoints may result in a single /virtual/ glyph/codepoint/character.
* (Example: 'A' + accent symbols). If the font supports Lithurges, these
* lithurges may also be represented as a single grapheme cluster (and thus
* behave unbreakable).
* <p/>
* Grapheme clusters with more than one unicode char have the size of that char
* added to the first codepoint, all subsequence codepoints of the same cluster
* have a size/kerning/etc of zero and are unbreakable.
* <p/>
* This text chunk is perfectly suitable for horizontal text, going either from
* left-to-right or right-to-left. (Breaking mixed text is up to the
* textfactory).
*
* @author Thomas Morgner
*/
public class RenderableText extends RenderNode
{
private Glyph[] glyphs;
private int offset;
private int length;
private boolean ltr;
private int script;
private long minimumWidth;
private long preferredWidth;
private boolean forceLinebreak;
private ExtendedBaselineInfo baselineInfo;
protected RenderableText()
{
}
public RenderableText(final ExtendedBaselineInfo baselineInfo,
final Glyph[] glyphs,
final int offset,
final int length,
final int script,
final boolean forceLinebreak)
{
initialize(glyphs, offset, length, baselineInfo, script, forceLinebreak);
}
protected void initialize(final Glyph[] glyphs,
final int offset,
final int length,
final ExtendedBaselineInfo baselineInfo,
final int script, final boolean forceLinebreak)
{
if (glyphs == null)
{
throw new NullPointerException();
}
if (glyphs.length < offset + length)
{
throw new IllegalArgumentException();
}
this.baselineInfo = baselineInfo;
this.ltr = true; // this depends on the script value
this.script = script;
this.glyphs = glyphs;
this.offset = offset;
this.length = length;
this.forceLinebreak = forceLinebreak;
// Major axis: All child boxes are placed from left-to-right
setMajorAxis(HORIZONTAL_AXIS);
// Minor: The childs might be aligned on their position (shifted up or down)
setMinorAxis(VERTICAL_AXIS);
long wordMinChunkWidth = 0;
long wordMinWidth = 0;
long wordPrefWidth = 0;
long wordMaxWidth = 0;
// long heightAbove = 0;
// long heightBelow = 0;
long minimumChunkWidth = 0;
final int lastPos = Math.min(glyphs.length, offset + length);
for (int i = offset; i < lastPos; i++)
{
final Glyph glyph = glyphs[i];
final Spacing spacing = glyph.getSpacing();
// heightAbove = Math.max(glyph.getBaseLine(), heightAbove);
// heightBelow = Math.max(glyph.getHeight() - glyph.getBaseLine(), heightBelow);
final int kerning = glyph.getKerning();
final int width = glyph.getWidth();
if (glyph.getBreakWeight() <= BreakOpportunityProducer.BREAK_CHAR)
{
// for the layouting, we avoid inner-word breaks
// Later, when we have to force breaks, we may take inner-breaks into
// account.
wordMinChunkWidth += width + spacing.getMinimum() - kerning;
wordMinWidth += width + spacing.getMinimum() - kerning;
wordPrefWidth += width + spacing.getOptimum() - kerning;
wordMaxWidth += width + spacing.getMaximum() - kerning;
}
else
{
wordMinChunkWidth += width + spacing.getMinimum() - kerning;
wordMinWidth += width + spacing.getMinimum() - kerning;
wordPrefWidth += width + spacing.getOptimum() - kerning;
wordMaxWidth += width + spacing.getMaximum() - kerning;
minimumChunkWidth = Math.max(minimumChunkWidth, wordMinChunkWidth);
wordMinWidth = 0;
// Paranoid sanity checks: The word- and linebreaks should have been
// replaced by other definitions in the text factory.
if (glyph.getBreakWeight() == BreakOpportunityProducer.BREAK_LINE)
{
throw new IllegalStateException("A renderable text cannot and must " +
"not contain linebreaks.");
}
}
}
minimumChunkWidth = Math.max(minimumChunkWidth, wordMinChunkWidth);
minimumWidth = Math.max(minimumWidth, wordMinWidth);
preferredWidth = Math.max(preferredWidth, wordPrefWidth);
final long maximumWidth = Math.max(minimumWidth, wordMaxWidth);
setMaximumBoxWidth(maximumWidth);
setMinimumChunkWidth(minimumChunkWidth);
setIcmMetricsFinished(true);
}
public boolean isForceLinebreak()
{
return forceLinebreak;
}
public boolean isLtr()
{
return ltr;
}
public Glyph[] getGlyphs()
{
return glyphs;
}
public int getOffset()
{
return offset;
}
public int getLength()
{
return length;
}
//
// public BreakAfterEnum getBreakAfterAllowed(final int axis)
// {
// if (axis == getMinorAxis())
// {
// return BreakAfterEnum.BREAK_ALLOW;
// }
//
// if (isForceLinebreak())
// {
// return BreakAfterEnum.BREAK_ALLOW;
// }
//
// if (glyphs.length > 0)
// {
// final int breakWeight = glyphs[glyphs.length - 1].getBreakWeight();
// if (breakWeight != BreakOpportunityProducer.BREAK_NEVER)
// {
// return BreakAfterEnum.BREAK_ALLOW;
// }
// }
// return BreakAfterEnum.BREAK_DISALLOW;
// }
public String getRawText()
{
final Glyph[] gs = getGlyphs();
final int length = getLength();
final StringBuffer b = new StringBuffer();
for (int i = getOffset(); i < length; i++)
{
final Glyph g = gs[i];
b.append((char) (0xffff & g.getCodepoint()));
final int[] extraCPs = g.getExtraChars();
for (int j = 0; j < extraCPs.length; j++)
{
final int extraCP = extraCPs[j];
b.append(extraCP);
}
}
return b.toString();
}
public boolean isEmpty()
{
return length == 0;
}
public boolean isDiscardable()
{
if (forceLinebreak)
{
return false;
}
return glyphs.length == 0;
}
/**
* Returns the baseline info for the given node. This can be null, if the node
* does not have any baseline info.
*
* @return
*/
public ExtendedBaselineInfo getBaselineInfo()
{
return baselineInfo;
}
public int getScript()
{
return script;
}
public long getMinimumWidth()
{
return minimumWidth;
}
public long getPreferredWidth()
{
return preferredWidth;
}
//
// /**
// * Splits the text along its major axis (horizontal for roman text) at the
// * given position (relative to the element's origin!)
// *
// * Split sizes are relative to the text's minimum size.
// *
// * @param position the split position.
// * @return the split elements.
// */
// public RenderableText[] split (long position)
// {
//// final Glyph[] glyphs,
//// final int offset,
//// final int length,
//
// int breakPos = offset;
// long size = 0;
// final int maxPos = (offset + length);
// for (int i = offset; i < maxPos; i++)
// {
// Glyph glyph = glyphs[i];
// size += glyph.getWidth();
// size += glyph.getSpacing().getMinimum();
//
// if (size >= position)
// {
// breakPos = i;
// break;
// }
// }
//
// final RenderableText[] result = new RenderableText[2];
// if (breakPos == offset || breakPos == maxPos)
// {
// result[0] = (RenderableText) derive(false);
// result[1] = null;
// return result;
// }
//
// result[0] = new RenderableText(baselineInfo, glyphs,
// offset, breakPos - offset, script, false);
// result[1] = new RenderableText(baselineInfo, glyphs,
// breakPos, length - (breakPos - offset), script, forceLinebreak);
// return result;
// }
}