package com.glview.hwui.font;
import com.glview.hwui.packer.Packer;
import com.glview.hwui.packer.PackerRect;
public class ColumnBasePacker implements Packer {
private final static int CACHE_BLOCK_ROUNDING_SIZE = 4;
int mWidth, mHeight;
CacheBlock mCacheBlocks;
public ColumnBasePacker(int width, int height) {
mWidth = width;
mHeight = height;
mCacheBlocks = new CacheBlock(0, 0,
mWidth, mHeight);
}
@Override
public PackerRect insert(int width, int height) {
if (height > mHeight) {
return null;
}
// roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
// This columns for glyphs that are close but not necessarily exactly the same size. It trades
// off the loss of a few pixels for some glyphs against the ability to store more glyphs
// of varying sizes in one block.
int roundedUpW = (width + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
CacheBlock cacheBlock = mCacheBlocks;
int retOriginX = 0, retOriginY = 0;
while (cacheBlock != null) {
// Store glyph in this block iff: it fits the block's remaining space and:
// it's the remainder space (mY == 0) or there's only enough height for this one glyph
// or it's within ROUNDING_SIZE of the block width
if (roundedUpW <= cacheBlock.mWidth && height <= cacheBlock.mHeight &&
(cacheBlock.mY == 0 ||
(cacheBlock.mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
if (cacheBlock.mHeight - height < height) {
// Only enough space for this glyph - don't bother rounding up the width
roundedUpW = width;
}
retOriginX = cacheBlock.mX;
retOriginY = cacheBlock.mY;
// If this is the remainder space, create a new cache block for this column. Otherwise,
// adjust the info about this column.
if (cacheBlock.mY == 0) {
int oldX = cacheBlock.mX;
// Adjust remainder space dimensions
cacheBlock.mWidth -= roundedUpW;
cacheBlock.mX += roundedUpW;
if (mHeight - height >= height) {
// There's enough height left over to create a new CacheBlock
CacheBlock newBlock = new CacheBlock(oldX, height,
roundedUpW, mHeight - height);
mCacheBlocks = CacheBlock.insertBlock(mCacheBlocks, newBlock);
}
} else {
// Insert into current column and adjust column dimensions
cacheBlock.mY += height;
cacheBlock.mHeight -= height;
}
if (cacheBlock.mHeight < Math.min(height, width)) {
// If remaining space in this block is too small to be useful, remove it
mCacheBlocks = CacheBlock.removeBlock(mCacheBlocks, cacheBlock);
}
return new PackerRect(retOriginX, retOriginY, retOriginX + width, retOriginY + height);
}
cacheBlock = cacheBlock.mNext;
}
return null;
}
@Override
public void reset() {
mCacheBlocks = new CacheBlock(0, 0,
mWidth, mHeight);
}
@Override
public String dump() {
StringBuffer sb = new StringBuffer();
CacheBlock currBlock = mCacheBlocks;
while (currBlock != null) {
sb.append("Block x=");
sb.append(currBlock.mX);
sb.append(" y=");
sb.append(currBlock.mY);
sb.append(" w=");
sb.append(currBlock.mWidth);
sb.append(" h=");
sb.append(currBlock.mHeight);
sb.append("\n");
currBlock = currBlock.mNext;
}
return sb.toString();
}
static class CacheBlock {
int mX;
int mY;
int mWidth;
int mHeight;
CacheBlock mNext;
CacheBlock mPrev;
public CacheBlock(int x, int y, int width, int height) {
mX = x;
mY = y;
mWidth = width;
mHeight = height;
}
static CacheBlock insertBlock(CacheBlock head, CacheBlock newBlock) {
CacheBlock currBlock = head;
CacheBlock prevBlock = null;
while (currBlock != null && currBlock.mY != 0) {
if (newBlock.mWidth < currBlock.mWidth) {
newBlock.mNext = currBlock;
newBlock.mPrev = prevBlock;
currBlock.mPrev = newBlock;
if (prevBlock != null) {
prevBlock.mNext = newBlock;
return head;
} else {
return newBlock;
}
}
prevBlock = currBlock;
currBlock = currBlock.mNext;
}
// new block larger than all others - insert at end (but before the remainder space, if there)
newBlock.mNext = currBlock;
newBlock.mPrev = prevBlock;
if (currBlock != null) {
currBlock.mPrev = newBlock;
}
if (prevBlock != null) {
prevBlock.mNext = newBlock;
return head;
} else {
return newBlock;
}
}
static CacheBlock removeBlock(CacheBlock head, CacheBlock blockToRemove) {
CacheBlock newHead = head;
CacheBlock nextBlock = blockToRemove.mNext;
CacheBlock prevBlock = blockToRemove.mPrev;
if (prevBlock != null) {
prevBlock.mNext = nextBlock;
} else {
newHead = nextBlock;
}
if (nextBlock != null) {
nextBlock.mPrev = prevBlock;
}
return newHead;
}
}
}