/*
* 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.layout.text;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointBuffer;
import org.pentaho.reporting.libraries.fonts.text.Spacing;
public final class GlyphList {
protected static final int EXTRA_GLYPH_INFO = 7;
private static class VirtualGlyph implements Glyph {
private int index;
private int spacingIndex;
private int extraGlyphCount;
private int[] glyphData;
private GlyphList parent;
private VirtualGlyph( final GlyphList parent ) {
this.parent = parent;
}
public void update( final int glyphDataIndex, final int spacingIndex ) {
this.index = glyphDataIndex;
this.spacingIndex = spacingIndex;
this.glyphData = parent.getGlyphSequenceData();
this.extraGlyphCount = glyphData[index];
}
public int getClassification() {
return glyphData[index + 2];
}
public int[] getExtraChars() {
if ( extraGlyphCount == 0 ) {
return GlyphList.EMPTY_INTS;
}
final int[] retal = new int[extraGlyphCount];
System.arraycopy( glyphData, index + GlyphList.EXTRA_GLYPH_INFO + 1, retal, 0, extraGlyphCount );
return retal;
}
public int getBaseLine() {
return glyphData[index + 5];
}
public int getCodepoint() {
return glyphData[index + GlyphList.EXTRA_GLYPH_INFO];
}
public int getBreakWeight() {
return glyphData[index + 1];
}
public Spacing getSpacing() {
final Spacing[] spacings1 = parent.getSpacings();
return spacings1[spacingIndex];
}
public int getWidth() {
return glyphData[index + 3];
}
public int getHeight() {
return glyphData[index + 4];
}
public int getKerning() {
return glyphData[index + 6];
}
}
protected static final int[] EMPTY_INTS = new int[0];
private int[] glyphSequenceData;
private Spacing[] spacings;
private int[] glyphIndices;
private int glyphSequenceFill;
private int glyphIncrement;
private int size;
private int spacerIncrement;
private VirtualGlyph virtualGlyph;
private boolean locked;
private StringBuilder stringBuilder;
private GlyphList() {
this.virtualGlyph = new VirtualGlyph( this );
}
public GlyphList( final int spacerIncrement ) {
// this is a good enough default for all common unicode languages.
this( spacerIncrement * ( GlyphList.EXTRA_GLYPH_INFO + 1 ), spacerIncrement );
}
public GlyphList( final int glyphIncrement, final int spacerIncrement ) {
this.glyphIncrement = glyphIncrement;
this.spacerIncrement = spacerIncrement;
this.virtualGlyph = new VirtualGlyph( this );
this.glyphIndices = new int[spacerIncrement];
this.spacings = new Spacing[spacerIncrement];
this.glyphSequenceData = new int[glyphIncrement];
}
protected int[] getGlyphSequenceData() {
return glyphSequenceData;
}
protected Spacing[] getSpacings() {
return spacings;
}
/**
* Ensures, that the list backend can store at least <code>c</code> elements. This method does nothing, if the new
* capacity is less than the current capacity.
*
* @param capacity
* the new capacity of the list.
*/
private void ensureGlyphCapacity( final int capacity ) {
if ( glyphSequenceData.length <= capacity ) {
final int[] newData = new int[Math.max( glyphSequenceData.length + glyphIncrement, capacity + 1 )];
System.arraycopy( glyphSequenceData, 0, newData, 0, glyphSequenceFill );
glyphSequenceData = newData;
}
}
private void ensureSpacerCapacity( final int capacity ) {
if ( spacings.length <= capacity ) {
final Spacing[] newData = new Spacing[Math.max( spacings.length + spacerIncrement, capacity + 1 )];
System.arraycopy( spacings, 0, newData, 0, size );
spacings = newData;
final int[] newIndexData = new int[Math.max( glyphIndices.length + spacerIncrement, capacity + 1 )];
System.arraycopy( glyphIndices, 0, newIndexData, 0, size );
glyphIndices = newIndexData;
}
}
public void addGlyphData( final int[] rawCodepoints, final int rawCodePointOffset, final int rawCodePointLength,
final int breakWeight, final int classification, final Spacing spacing, final int width, final int height,
final int baseLine, final int kerning ) {
if ( locked ) {
throw new IllegalStateException();
}
ensureGlyphCapacity( glyphSequenceFill + rawCodePointLength + GlyphList.EXTRA_GLYPH_INFO );
ensureSpacerCapacity( size + 1 );
final int glyphSequenceFill = this.glyphSequenceFill;
glyphSequenceData[glyphSequenceFill] = rawCodePointLength - 1;
glyphSequenceData[glyphSequenceFill + 1] = breakWeight;
glyphSequenceData[glyphSequenceFill + 2] = classification;
glyphSequenceData[glyphSequenceFill + 3] = width;
glyphSequenceData[glyphSequenceFill + 4] = height;
glyphSequenceData[glyphSequenceFill + 5] = baseLine;
glyphSequenceData[glyphSequenceFill + 6] = kerning;
if ( rawCodePointLength == 1 ) {
glyphSequenceData[glyphSequenceFill + GlyphList.EXTRA_GLYPH_INFO] = rawCodepoints[rawCodePointOffset];
} else {
System.arraycopy( rawCodepoints, rawCodePointOffset, glyphSequenceData, glyphSequenceFill
+ GlyphList.EXTRA_GLYPH_INFO, rawCodePointLength );
}
this.glyphSequenceFill = glyphSequenceFill + GlyphList.EXTRA_GLYPH_INFO + rawCodePointLength;
glyphIndices[size] = glyphSequenceFill;
spacings[size] = spacing;
size += 1;
}
public Glyph getGlyph( final int index ) {
if ( index >= size ) {
throw new IndexOutOfBoundsException();
}
if ( index < 0 ) {
throw new IndexOutOfBoundsException();
}
virtualGlyph.update( glyphIndices[index], index );
return virtualGlyph;
}
public int getSize() {
return size;
}
public GlyphList lock() {
final GlyphList retval = new GlyphList();
retval.spacerIncrement = 0;
retval.glyphIncrement = 0;
retval.locked = true;
retval.glyphSequenceFill = glyphSequenceFill;
retval.glyphSequenceData = new int[glyphSequenceFill];
System.arraycopy( glyphSequenceData, 0, retval.glyphSequenceData, 0, glyphSequenceFill );
retval.size = size;
retval.spacings = new Spacing[size];
retval.glyphIndices = new int[size];
System.arraycopy( spacings, 0, retval.spacings, 0, size );
System.arraycopy( glyphIndices, 0, retval.glyphIndices, 0, size );
return retval;
}
public void clear() {
size = 0;
glyphSequenceFill = 0;
}
public void ensureSize( final int size ) {
ensureSpacerCapacity( size );
ensureGlyphCapacity( size * 8 );
}
public boolean isEmpty() {
return size == 0;
}
public String getText( final int offset, final int length, final CodePointBuffer codePointBuffer ) {
if ( length == 0 ) {
return "";
}
codePointBuffer.setCursor( 0 );
final StringBuilder cps = create();
final int maxPos = offset + length;
for ( int i = offset; i < maxPos; i++ ) {
final int glyphIndex = glyphIndices[i];
final int glyphDataStart = glyphIndex + GlyphList.EXTRA_GLYPH_INFO;
final int glyphDataEnd = glyphDataStart + glyphSequenceData[glyphIndex] + 1;
for ( int g = glyphDataStart; g < glyphDataEnd; g++ ) {
encodeStringIncrementally( cps, glyphSequenceData[g] );
}
}
final String retval = cps.toString();
cps.delete( 0, cps.length() );
return retval;
}
private StringBuilder create() {
if ( stringBuilder == null ) {
stringBuilder = new StringBuilder();
}
stringBuilder.delete( 0, stringBuilder.length() );
return stringBuilder;
}
private void encodeStringIncrementally( StringBuilder stringBuffer, int codePoint ) {
if ( codePoint < 0x10000 ) {
stringBuffer.append( (char) codePoint );
} else {
// oh, no, we have to decode ...
// compute the weird replacement mode chars ..
final int derivedSourceItem = codePoint - 0x10000;
final int highWord = 0xD800 | ( ( derivedSourceItem & 0xFFC00 ) >> 10 );
final int lowWord = 0xDC00 | ( derivedSourceItem & 0x3FF );
stringBuffer.append( (char) highWord );
stringBuffer.append( (char) lowWord );
}
}
public String getGlyphAsString( final int index, final CodePointBuffer codePointBuffer ) {
codePointBuffer.setCursor( 0 );
final StringBuilder cps = create();
final int glyphIndex = glyphIndices[index];
final int glyphDataStart = glyphIndex + GlyphList.EXTRA_GLYPH_INFO;
final int glyphDataEnd = glyphDataStart + glyphSequenceData[glyphIndex] + 1;
for ( int g = glyphDataStart; g < glyphDataEnd; g++ ) {
encodeStringIncrementally( cps, glyphSequenceData[g] );
}
final String retval = cps.toString();
cps.delete( 0, cps.length() );
return retval;
}
public String toString() {
return "GlyphList={text='" + getText( 0, size, new CodePointBuffer( size ) ) + "'}";
}
}