/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package jp.sourceforge.dvibrowser.dvicore.font;

import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.api.BinaryDevice;

// not thread-safe.

public final class PackedGlyphRasterizer
{
  private PkGlyph glyph;
  private byte [] buf;
  private int dynF, dynG, dynH;
  private boolean highNyb;
  private int offset;

  public void begin(PkGlyph glyph)
  {
    this.glyph = glyph;
    buf = glyph.raster();
    dynF = glyph.dynF();
    dynG = ((13-dynF) << 4) + dynF + 1;
    dynH = ((dynF+1)<<4) - dynF - 1; // 15 * (dynF + 1)
    offset = 0;
    highNyb = true;
  }

  public void end()
  {
    glyph = null;
    buf = null;
  }

  private int getNybble()
  throws DviException
  {
    if (highNyb) {
      highNyb = false;
      return ((buf[offset] >> 4) & 15);
    } else {
      highNyb = true;
      return (buf[offset++] & 15);
    }
  }

  private int repeat = 0;

  private int getPackedNumInternal(boolean recursed)
  throws DviException
  {
    int i = getNybble();
    if (i == 0) {
      int j;
      do {
        j = getNybble();
        i++;
      } while (j == 0);
      while (i-- > 0) {
        j = (j << 4) | getNybble();
      }
      return j - 16 + dynG;
    } else {
      if (i <= dynF) {
        return i;
      } else if (i < 14) { // dynF < i < 14
        return (i << 4) + getNybble() - dynH;
      } else {
        if (recursed)
          throw new IllegalStateException
            ("too many levels of recursion");
        if (i == 14) {
          repeat = getPackedNumInternal(true);
        } else // i == 15
          repeat = 1;
        return getPackedNumInternal(true);
      }
    }
  }

  public int getPackedNum()
  throws DviException
  {
    return getPackedNumInternal(false);
  }

  public void rasterizeTo(final BinaryDevice out)
  throws DviException 
  {
    boolean paintFlag = glyph.turnOn();
    int w = glyph.width();
    int h = glyph.height();
    if (w <= 0 || h <= 0)
      return;
    if (out.beginRaster(w, h)) {
      out.beginLine();
      while (h > 0) {
        int count = getPackedNum();
        while (count > 0) {
          if (count < w) {
            out.putBits(count, paintFlag);
            w -= count;
            break;
          } else {
            out.putBits(w, paintFlag);
            out.endLine(repeat);
            count -= w;
            h -= repeat + 1;
            w = glyph.width();
            repeat = 0;
            out.beginLine();
          }
        }
        paintFlag = !paintFlag;
      }
    }
    out.endRaster();
  }
}
