public interface ITracer {

	/**
	 * <p>
	 * Called by Fontbox to trace a particular event to the tracer.
	 * </p>
	 * <p>
	 * Events triggered usually take the following parameter forms:
	 * <ol>
	 * <li>The method name being invoked (the source)</li>
	 * <li>The return argument (can be null) <i>or</i> the name of the phase
	 * being recorded</li>
	 * <li>Any other debugging parameters if the call is for a phase</li>
	 * </ol>
	 * See the location of invocation for more information about the details of
	 * the parameters passed.
	 * </p>
	 *
	 * @param params
	 *            The parameters from the invoked method
	 */
	public void trace(Object... params);

	/**
	 * <p>
	 * Called by Fontbox to trace a particular warning notification to the
	 * tracer.
	 * </p>
	 * <p>
	 * Events triggered usually take the following parameter forms:
	 * <ol>
	 * <li>The method name being invoked (the source)</li>
	 * <li>The reason for the warning message</li>
	 * <li>Any other debugging parameters where appropriate</li>
	 * </ol>
	 * See the location of invocation for more information about the details of
	 * the parameters passed.
	 * </p>
	 *
	 * @param params
	 *            The parameters from the invoked method
	 */
	public void warn(Object... params);

	/**
	 * <p>
	 * Called by Fontbox to ask if Fontbox assertions are enabled.
	 * </p>
	 *
	 * @return If Fontbox internal assertions are enabled
	 */
	public boolean enableAssertion();

}
 public class FormattedString {

	public final String string;
	public final TextFormat[] format;

	public FormattedString(String string) {
		this.string = string;
		format = new TextFormat[string.length()];
	}

	public FormattedString applyFormat(TextFormat format, int index) {
		this.format[index] = format;
		return this;
	}

}
 public class TextFormat implements Cloneable {

	public final EnumSet<DecorationStyle> decorations;
	public final GLFont font;
	public final ColorFormat color;

	public TextFormat(GLFont font) {
		this(font, EnumSet.noneOf(DecorationStyle.class), null);
	}

	public TextFormat(GLFont font, EnumSet<DecorationStyle> decorations) {
		this(font, decorations, null);
	}

	public TextFormat(GLFont font, ColorFormat color) {
		this(font, EnumSet.noneOf(DecorationStyle.class), color);
	}

	public TextFormat(GLFont font, EnumSet<DecorationStyle> decorations, ColorFormat color) {
		this.decorations = decorations;
		this.font = font;
		this.color = color;
	}

	@Override
	public TextFormat clone() {
		EnumSet<DecorationStyle> style = EnumSet.noneOf(DecorationStyle.class);
		style.addAll(decorations);
		return new TextFormat(font, style, (color != null) ? color.clone() : null);
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof TextFormat))
			return false;
		TextFormat that = (TextFormat) o;
		if (!font.equals(that.font) || !decorations.equals(that.decorations))
			return false;
		if ((that.color != null && color == null) || (that.color == null && color != null))
			return false;
		if (that.color != null && color != null)
			if (!color.equals(that.color))
				return false;
		return true;
	}
}
 public enum AlignmentMode {
	LEFT, CENTER, RIGHT, JUSTIFY;
}
 public enum FloatMode {
	NONE, LEFT, RIGHT;
}
 public class LayoutException extends Exception {

	/**
	 * Serializer version ID
	 */
	private static final long serialVersionUID = 1437904113154741479L;

	/**
	 * Initializes the layout exception
	 *
	 * @param reason
	 *            The reason for the exception
	 */
	public LayoutException(String reason) {
		super(reason);
	}

	/**
	 * Initializes the layout exception
	 *
	 * @param reason
	 *            The reason for the exception
	 * @param cause
	 *            The parent cause exception
	 */
	public LayoutException(String reason, Throwable cause) {
		super(reason, cause);
	}

}
 public class ObjectBounds {

	public int x, y;
	public int width, height;
	public FloatMode floating;

	public ObjectBounds(int x, int y, int width, int height, FloatMode floating) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.floating = floating;
	}

	public boolean floating() {
		return floating != FloatMode.NONE;
	}

	public FloatMode floatMode() {
		return floating;
	}

	public boolean intersects(ObjectBounds that) {
		boolean flag0 = (x < that.x + that.width && x + width > that.x);
		boolean flag1 = (y < that.y + that.height && y + height > that.y);
		return flag0 && flag1;
	}

	public boolean encloses(int x, int y) {
		boolean flag0 = (this.x <= x && this.x + width >= x);
		boolean flag1 = (this.y <= y && this.y + height >= y);
		return flag0 && flag1;
	}

	@Override
	public String toString() {
		return "[" + x + ", " + y + "] => [" + width + " x " + height + "]";
	}

	public boolean inside(ObjectBounds bounds) {
		return bounds.encloses(x, y) && bounds.encloses(x + width, y + height);
	}
}
 public class PageCursor {

	private int x, y;

	public PageCursor() {
		x = 0;
		y = 0;
	}

	public int x() {
		return x;
	}

	public int y() {
		return y;
	}

	public void left(int vx) {
		x = vx;
	}

	public void top(int vy) {
		y = vy;
	}

	public void pushLeft(int dx) {
		x += dx;
	}

	public void pushDown(int dy) {
		y += dy;
	}

	@Override
	public String toString() {
		return "C>{" + x + ", " + y + "}";
	}

}
 public class PageWriter {

	private ArrayList<Page> pages = new ArrayList<Page>();
	private ArrayList<PageCursor> cursors = new ArrayList<PageCursor>();
	private PageProperties attributes;
	private PageIndex index;
	private Object lock = new Object();
	private boolean closed = false;
	private int ptr = 0;

	public PageWriter(PageProperties attributes) {
		this.attributes = attributes;
		index = new PageIndex();
	}

	public void close() {
		synchronized (lock) {
			closed = true;
		}
	}

	private void checkOpen() throws IOException {
		synchronized (lock) {
			if (closed)
				throw new IOException("Writer closed!");
		}
	}

	public Page previous() throws IOException {
		synchronized (lock) {
			checkOpen();
			seek(-1);
			return pages.get(ptr);
		}
	}

	public Page next() throws IOException {
		synchronized (lock) {
			checkOpen();
			seek(1);
			return pages.get(ptr);
		}
	}

	public Page current() throws IOException {
		synchronized (lock) {
			checkOpen();
			seek(0);
			return pages.get(ptr);
		}
	}

	public boolean write(Element element) throws IOException {
		synchronized (lock) {
			checkOpen();
			Page what = current();
			if (element.bounds() == null)
				throw new IOException("Cannot write unbounded object to page.");
			Fontbox.doAssert(what.insidePage(element.bounds()), "Element outside page boundary.");
			Element intersect = what.intersectsElement(element.bounds());
			Fontbox.doAssert(intersect == null, "Element intersects existing element " + intersect + ": box "
					+ ((intersect != null && intersect.bounds() != null) ? intersect.bounds() : "<null>") + " and "
					+ element.bounds() + "!");

			if (element.identifier() != null)
				index.push(element.identifier(), ptr);

			what.push(element);

			PageCursor current = cursor();
			for (Element e : what.allElements()) {
				if (e.bounds().floating())
					continue;
				ObjectBounds bb = e.bounds();
				if (bb.y + bb.height + 1 > current.y())
					current.top(bb.y + bb.height + 1);
			}

			IntegerExclusionStream window = new IntegerExclusionStream(0, what.width);
			for (Element e : what.allElements()) {
				ObjectBounds bb = e.bounds();
				if (current.y() >= bb.y && bb.y + bb.height >= current.y())
					window.excludeRange(0, bb.x + bb.width);
			}
			current.left(window.largest());

			Fontbox.tracer().trace("PageWriter.write", "pushCursor", current);

			return true;
		}
	}

	public PageCursor cursor() throws IOException {
		synchronized (lock) {
			checkOpen();
			return cursors.get(ptr);
		}
	}

	private void seek(int which) throws IOException {
		synchronized (lock) {
			ptr += which;
			if (0 > ptr)
				ptr = 0;
			if (ptr > pages.size())
				ptr = pages.size();
			if (ptr == pages.size()) {
				pages.add(new Page(attributes.copy()));
				cursors.add(new PageCursor());
			}
		}
	}

	public ArrayList<Page> pages() {
		synchronized (lock) {
			if (!closed)
				return (ArrayList<Page>) pages.clone();
			return pages;
		}
	}

	public PageIndex index() throws IOException {
		synchronized (lock) {
			if (!closed)
				throw new IOException("Writing not finished!");
			return index;
		}
	}

	public ArrayList<PageCursor> cursors() {
		synchronized (lock) {
			if (!closed)
				return (ArrayList<PageCursor>) cursors.clone();
			return cursors;
		}
	}
}
 public class Line extends Element {
	/** The characters */
	public final char[] line;
	/** The character formats */
	public final TextFormat[] format;

	/** The line's ID */
	public String id;
	/** The size of the spacing between words */
	public final int space_size;

	/**
	 * Create a new line
	 *
	 * @param line
	 *            The line's text
	 * @param format
	 *            The text format map
	 * @param bounds
	 *            The location of the line
	 * @param space_size
	 *            The size of the spacing between words
	 */
	public Line(char[] line, TextFormat[] format, ObjectBounds bounds, int space_size) {
		setBounds(bounds);
		this.line = line;
		this.format = format;
		id = null;
		this.space_size = space_size;
	}

	/**
	 * Create a new line with an ID
	 *
	 * @param line
	 *            The line's text
	 * @param format
	 *            The text format map
	 * @param uid
	 *            The line's ID
	 * @param bounds
	 *            The location of the line
	 * @param space_size
	 *            The size of the spacing between words
	 */
	public Line(char[] line, TextFormat[] format, String uid, ObjectBounds bounds, int space_size) {
		this(line, format, bounds, space_size);
		id = uid;
	}

	@Override
	public void layout(ITracer trace, PageWriter writer) throws IOException, LayoutException {
		throw new LayoutException("Cannot layout Line type; Line already laid!");
	}

	@Override
	public boolean canUpdate() {
		return false;
	}

	@Override
	public void update() {
		/* No action required */
	}

	@Override
	public boolean canCompileRender() {
		return true;
	}

	private void safeSwitchToFont(GLFont font) throws RenderException {
		if (font.getTextureId() == -1)
			throw new RenderException("Font object not loaded!");
		GLFontMetrics metric = font.getMetric();
		if (metric == null)
			throw new RenderException("Font object not loaded!");
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, font.getTextureId());
		GL11.glScalef(font.getScale(), font.getScale(), 1.0f);
	}

	@Override
	public void render(BookGUI gui, int mx, int my, float frame) throws RenderException {
		float x = 0, y = 0;
		if (line.length == 0)
			return;
		GL11.glPushMatrix();
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

		TextFormat decorator = format[0];
		GL11.glPushMatrix();
		safeSwitchToFont(decorator.font);
		GL11.glTranslatef(bounds().x, bounds().y, 0);

		for (int i = 0; i < line.length; i++) {
			char c = line[i];
			if (c != ' ') {
				TextFormat aDecorator = format[i];
				if (aDecorator != null && !aDecorator.equals(decorator)) {
					if (aDecorator.font != decorator.font) {
						GL11.glPopMatrix();
						GL11.glPushMatrix();
						safeSwitchToFont(aDecorator.font);
						GL11.glTranslatef(bounds().x, bounds().y, 0);
					}
					decorator = aDecorator;
				}

				GLFontMetrics metric = decorator.font.getMetric();
				GLGlyphMetric glyph = metric.glyphs.get((int) c);
				if (glyph == null) // blank glyph?
					continue;

				if (decorator.color == null)
					GL11.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
				else
					GL11.glColor4f(decorator.color.redF(), decorator.color.greenF(), decorator.color.blueF(),
							decorator.color.alphaF());

				float tiltTop = 0.0f, tiltBottom = 0.0f;
				if (decorator.decorations.contains(DecorationStyle.ITALIC)) {
					tiltTop = -5.55f;
					tiltBottom = 5.55f;
				}

				renderGlyphInPlace(metric, glyph, x, y, tiltTop, tiltBottom);
				if (decorator.decorations.contains(DecorationStyle.BOLD))
					renderGlyphInPlace(metric, glyph, x + 0.5f, y + 0.5f, tiltTop, tiltBottom);

				x += glyph.width;
			} else
				x += space_size;
		}

		GL11.glPopMatrix();

		GL11.glDisable(GL11.GL_BLEND);
		GL11.glPopMatrix();
	}

	private void renderGlyphInPlace(GLFontMetrics metric, GLGlyphMetric glyph, float x, float y, float tiltTop,
			float tiltBottom) {
		double u = glyph.ux / metric.fontImageWidth;
		double v = (glyph.vy - glyph.ascent) / metric.fontImageHeight;
		double us = glyph.width / metric.fontImageWidth;
		double vs = glyph.height / metric.fontImageHeight;
		GL11.glBegin(GL11.GL_QUADS);

		GL11.glTexCoord2d(u, v + vs);
		GL11.glVertex3d(x + tiltTop, y + glyph.height, 1.0);

		GL11.glTexCoord2d(u + us, v + vs);
		GL11.glVertex3d(x + tiltTop + glyph.width, y + glyph.height, 1.0);

		GL11.glTexCoord2d(u + us, v);
		GL11.glVertex3d(x + tiltBottom + glyph.width, y, 1.0);
		GL11.glTexCoord2d(u, v);
		GL11.glVertex3d(x + tiltBottom, y, 1.0);
		GL11.glEnd();
	}

	@Override
	public void clicked(BookGUI gui, int mx, int my) {
		/* No action required */
	}

	@Override
	public void typed(BookGUI gui, char val, int code) {
		/* No action required */
	}

	@Override
	public String identifier() {
		return id;
	}
} public class LineWriter {

	/** The writer stream */
	private final PageWriter writer;
	/** The alignment writing in */
	private final AlignmentMode alignment;

	/** The list of words on the stack currently */
	private final ArrayList<String> words;
	/** The map of formatting objects on the stack currently */
	private final HashMap<Integer, TextFormat> format;
	/** The default formatting */
	private final TextFormat zeroFormat;

	/** The current computed bounds of the stack's words */
	private ObjectBounds bounds;
	/** The current size of the spaces between the stack's words */
	private int spaceSize;

	/**
	 * Construct a new line writing utility. The underlying stream and the
	 * writing font must be specified and cannot be null.
	 *
	 * @param writer
	 *            The underlying stream to operate on.
	 * @param defaultFormat
	 *            The default text formatting to use.
	 * @param alignment
	 *            The alignment to paginate in.
	 */
	public LineWriter(PageWriter writer, TextFormat defaultFormat, AlignmentMode alignment) {
		this.writer = writer;
		this.alignment = alignment;
		zeroFormat = defaultFormat;
		words = new ArrayList<String>();
		format = new HashMap<Integer, TextFormat>();
		format.put(0, zeroFormat.clone());
	}

	private void update() throws LayoutException, IOException {
		int width = 0, height = 0;

		Page page = writer.current();
		TextFormat activeFormat = format.get(0);

		int offset = 0;
		int wordsWidth = 0;
		for (String word : words) {
			char[] chars = word.toCharArray();
			for (char cz : chars) {
				if (format.get(offset) != null)
					activeFormat = format.get(offset);
				GLGlyphMetric cm = activeFormat.font.getMetric().glyphs.get((int) cz);
				if (cm == null)
					throw new LayoutException(String.format("Glyph %s not supported by font %s.", cz,
							activeFormat.font.getName()));
				wordsWidth += cm.width;
				if (cm.ascent > height)
					height = cm.ascent;
				offset++;
			}
			offset++;
		}

		int blankWidth = page.width - page.properties.margin_left - page.properties.margin_right - wordsWidth;
		spaceSize = page.properties.min_space_size;
		int x = writer.cursor().x(), y = writer.cursor().y();

		switch (alignment) {
		case CENTER:
			float halfBlank = blankWidth / 2.0f;
			x += (int) Math.floor(halfBlank);
			break;
		case JUSTIFY:
			float density = (float) wordsWidth / (float) page.width;
			if (density >= page.properties.min_line_density) {
				int extra_px_per_space = (int) Math.floor(blankWidth / words.size());
				if (extra_px_per_space > page.properties.min_space_size)
					spaceSize = extra_px_per_space;
			}
			break;
		case LEFT:
			/* Do nothing */
			break;
		case RIGHT:
			x += blankWidth;
			break;
		}

		width = wordsWidth + Math.max(words.size() - 2, 0) * spaceSize;
		bounds = new ObjectBounds(x, y, width, height, FloatMode.NONE);
	}

	/**
	 * Called to emit the stack's contents to a Line element. The contents of
	 * the stack are automatically cleared and zerored on invocation.
	 *
	 * @param uid
	 *            The line's ID, if any
	 *
	 * @return The formatted line. The stack, properties and other values
	 *         associated with generating the line are reset on the self object.
	 */
	public Line emit(String uid) {
		StringBuilder words = new StringBuilder();
		for (int i = 0; i < this.words.size(); i++) {
			String what = this.words.get(i);
			words.append(what);
			if (i < words.length() - 1)
				words.append(" ");
		}
		char[] glyphs = words.toString().toCharArray();
		TextFormat[] format = new TextFormat[glyphs.length];
		for (int i = 0; i < glyphs.length; i++)
			if (this.format.get(i) != null)
				format[i] = this.format.get(i);
		Line what = new Line(words.toString().toCharArray(), format, uid, bounds, spaceSize);
		bounds = null;
		spaceSize = 0;
		this.words.clear();
		this.format.clear();
		this.format.put(0, zeroFormat.clone());
		return what;
	}

	/**
	 * Get the current pending bounding box of the words on the writer.
	 *
	 * @return The pending bounding box of the words on the writer.
	 */
	public ObjectBounds pendingBounds() {
		return bounds;
	}

	/**
	 * Pushes the word onto the writer stack. The word is placed on the end of
	 * the stack and the dimensions of the stack are recomputed automatically.
	 *
	 * @param word
	 *            The word to place on the end of the stack
	 * @param format
	 *            The text formatting, if any
	 * @throws IOException
	 *             Any exception which occurs when reading from the page writing
	 *             stream underlying this writer
	 * @throws LayoutException
	 *             Any exception which occurs when updating the potentially
	 *             paginated text
	 */
	public void push(String word, TextFormat[] format) throws LayoutException, IOException {
		if (format != null) {
			if (word.length() != format.length)
				throw new LayoutException("Specified format doesn't bound word length");

			int offset = 0;
			for (String wd : words)
				offset += wd.length() + 1;

			for (int i = 0; i < format.length; i++) {
				TextFormat aformat = format[i];
				if (aformat != null)
					this.format.put(offset + i, aformat);
			}
		}
		words.add(word);
		update();
	}

	/**
	 * Removes the word from the end of the writer stack. The word removed is
	 * returned and then dimensions of the stack are recomputed automatically.
	 *
	 * @return The word which was removed from the end of the stack
	 * @throws IOException
	 *             Any exception which occurs when reading from the page writing
	 *             stream underlying this writer
	 * @throws LayoutException
	 *             Any exception which occurs when updating the potentially
	 *             paginated text
	 */
	public String pop() throws LayoutException, IOException {
		String word = words.remove(words.size() - 1);

		int offset = 0;
		for (String wd : words)
			offset += wd.length() + 1;
		Iterator<Entry<Integer, TextFormat>> itx = format.entrySet().iterator();
		while (itx.hasNext()) {
			Entry<Integer, TextFormat> etx = itx.next();
			if (etx.getKey() >= offset)
				itx.remove();
		}
		if (!format.containsKey(0))
			format.put(0, zeroFormat.clone());

		update();
		return word;
	}

	/**
	 * Get the size (number of elements) on the stack at the current time.
	 *
	 * @return The number of elements currently on the writer stack at the time
	 *         of invocation.
	 */
	public int size() {
		return words.size();
	}

}
 public class Page extends Container {

	/** The page layout properties container */
	public PageProperties properties;

	/** The list of static elements on the page */
	private ArrayList<Element> staticElements = new ArrayList<Element>();
	/** The list of dynamic elements on the page */
	private ArrayList<Element> dynamicElements = new ArrayList<Element>();

	/**
	 * Initialize a new Page with a specified set of page layout properties.
	 *
	 * @param properties
	 *            The page layout properties.
	 */
	public Page(PageProperties properties) {
		super(properties.width, properties.height);
		this.properties = properties;
	}

	public ArrayList<Element> allElements() {
		ArrayList<Element> all = new ArrayList<Element>();
		all.addAll(staticElements);
		all.addAll(dynamicElements);
		return all;
	}

	/**
	 * Get a list of all static elements on the page
	 *
	 * @return The list of static elements on the page
	 */
	public ArrayList<Element> staticElements() {
		return staticElements;
	}

	/**
	 * Get a list of all dynamic elements on the page
	 *
	 * @return The list of dynamic elements on the page
	 */
	public ArrayList<Element> dynamicElements() {
		return dynamicElements;
	}

	/**
	 * Push an element onto the page, unchecked.
	 *
	 * @param element
	 *            The element to push
	 */
	public void push(Element element) {
		if (!element.canCompileRender())
			dynamicElements.add(element);
		else
			staticElements.add(element);
	}

	/**
	 * Determine if the provided bounding box intersects with an existing
	 * element on the page. Returns true if an intersection occurs, false if
	 * not.
	 *
	 * @param bounds
	 *            The bounding box to check
	 * @return If an intersection occurs
	 */
	public Element intersectsElement(ObjectBounds bounds) {
		for (Element element : staticElements)
			if (element.bounds() != null && element.bounds().intersects(bounds))
				return element;
		return null;
	}

	/**
	 * Determine if the provided bounding box fits entirely on the page. Returns
	 * true if the bounding box fits inside the page, false if not.
	 *
	 * @param bounds
	 *            The bounding box to check
	 * @return If the bounding box fits inside the page
	 */
	public boolean insidePage(ObjectBounds bounds) {
		if (bounds.x < 0 || bounds.y < 0 || bounds.x > width || bounds.y > height)
			return false;
		if (bounds.x + bounds.width > width || bounds.y + bounds.height > height)
			return false;
		return true;
	}
} public abstract class BookGUI extends GuiScreen {

	/**
	 * The page-up mode.
	 *
	 * @author AfterLifeLochie
	 */
	public static enum UpMode {
		/** One-up (one page) mode */
		ONEUP(1),
		/** Two-up (two page) mode */
		TWOUP(2);

		/** The number of pages in this mode */
		public final int pages;

		UpMode(int pages) {
			this.pages = pages;
		}
	}

	/**
	 * Page layout container
	 *
	 * @author AfterLifeLochie
	 *
	 */
	public static class Layout {
		public int x, y;

		/**
		 * Create a new Layout container for rendering the page on screen.
		 *
		 * @param x
		 *            The x-coordinate to render at
		 * @param y
		 *            The y-coordinate to render at
		 */
		public Layout(int x, int y) {
			this.x = x;
			this.y = y;
		}
	}

	/** The renderer's UpMode */
	protected final UpMode mode;
	/** The page layout grid */
	protected final Layout[] layout;

	/** The list of pages */
	protected ArrayList<Page> pages;
	/** The list of cursors */
	protected ArrayList<PageCursor> cursors;
	/** The data index */
	protected PageIndex index;
	/** The current page pointer */
	protected int ptr = 0;

	/** The current opengl display list state */
	protected boolean useDisplayList = false;
	/** The current opengl buffer list */
	protected int[] glDisplayLists;
	/** The current buffer dirty state */
	protected boolean glBufferDirty[];

	/**
	 * <p>
	 * Create a new Book rendering context on top of the existing Minecraft GUI
	 * system. The book rendering properties are set through the constructor and
	 * control how many and where pages are rendered.
	 * </p>
	 *
	 * @param mode
	 *            The page mode, usually ONEUP or TWOUP.
	 * @param layout
	 *            The layout array which specifies where the pages should be
	 *            rendered. The number of elements in the array must match the
	 *            number of pages required by the UpMode specified.
	 */
	public BookGUI(UpMode mode, Layout[] layout) {
		if (layout == null)
			throw new IllegalArgumentException("Layout cannot be null!");
		if (mode == null)
			throw new IllegalArgumentException("Mode cannot be null!");
		if (layout.length != mode.pages)
			throw new IllegalArgumentException("Expected " + mode.pages + " pages for mode " + mode);
		this.mode = mode;
		this.layout = layout;
		prepareGraphics();
	}

	/**
	 * <p>
	 * Updates the pages currently being rendered.
	 * </p>
	 * <p>
	 * If the page read pointer is currently beyond the end of the new page
	 * count (ie, the number of pages has reduced), the pointer will be reset to
	 * the beginning of the book.
	 * </p>
	 *
	 * @param pages
	 *            The new list of pages
	 * @param index
	 *            The new page index
	 */
	public void changePages(ArrayList<Page> pages, PageIndex index) {
		if (ptr >= pages.size()) {
			ptr = 0;
			internalOnPageChanged(this, ptr);
		}
		this.pages = pages;
		this.index = index;
	}

	/**
	 * <p>
	 * Updates the cursors currently being rendered. Used for debugging only.
	 * </p>
	 * <p>
	 * If the cursor parameter is not null, the matching cursor for each page
	 * will be displayed. If the cursor parameter is null, no cursors will be
	 * rendered on the page.
	 * </p>
	 *
	 * @param cursors
	 *            The list of cursors, or null if no cursors should be rendered
	 */
	public void changeCursors(ArrayList<PageCursor> cursors) {
		this.cursors = cursors;
	}

	@Override
	public boolean doesGuiPauseGame() {
		return false;
	}

	@Override
	public void initGui() {
		super.initGui();
	}

	@Override
	public void onGuiClosed() {
		if (useDisplayList) {
			useDisplayList = false;
			GL11.glDeleteLists(glDisplayLists[0], glDisplayLists.length);
			for (int i = 0; i < glDisplayLists.length; i++)
				glDisplayLists[i] = -1;
		}
	}

	@Override
	public void updateScreen() {
		super.updateScreen();
	}

	@Override
	public void drawScreen(int mx, int my, float frames) {
		super.drawScreen(mx, my, frames);
		drawBackground(mx, my, frames);
		try {
			if (pages != null)
				for (int i = 0; i < mode.pages; i++) {
					Layout where = layout[i];
					int what = ptr + i;
					if (pages.size() <= what)
						break;
					Page page = pages.get(ptr + i);
					GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

					if (cursors != null && what < cursors.size()) {
						PageCursor cursor = cursors.get(what);
						GL11.glDisable(GL11.GL_TEXTURE_2D);
						GL11.glEnable(GL11.GL_BLEND);
						GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
						GL11.glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
						GLUtils.drawDefaultRect(where.x, where.y, page.width * 0.44f, page.height * 0.44f, zLevel);
						GL11.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
						GLUtils.drawDefaultRect(where.x + (cursor.x() * 0.44f), where.y + (cursor.y() * 0.44f), 1.0,
								1.0, zLevel);
						GL11.glDisable(GL11.GL_BLEND);
						GL11.glEnable(GL11.GL_TEXTURE_2D);
					}
					renderPage(i, page, where.x, where.y, zLevel, mx, my, frames);

				}
		} catch (RenderException err) {
			err.printStackTrace();
		}
		drawForeground(mx, my, frames);
	}

	/**
	 * Called when the current page is changed
	 *
	 * @param gui
	 *            The current GUI
	 * @param whatPtr
	 *            The new page pointer value
	 */
	public abstract void onPageChanged(BookGUI gui, int whatPtr);

	/**
	 * <p>
	 * Draw the background layer of the interface. You must leave the opengl
	 * state such that the layout (0, 0) will be drawn in the current place.
	 * </p>
	 *
	 * @param mx
	 *            The mouse x-coordinate
	 * @param my
	 *            The mouse y-coordinate
	 * @param frame
	 *            The partial frames rendered
	 */
	public abstract void drawBackground(int mx, int my, float frame);

	/**
	 * <p>
	 * Draw the foreground layer of the interface. The opengl state is such that
	 * the layout coordinates (0, 0) are in the top-left corner of the written
	 * text.
	 * </p>
	 *
	 * @param mx
	 *            The mouse x-coordinate
	 * @param my
	 *            The mouse y-coordinate
	 * @param frame
	 *            The partial frames rendered
	 */
	public abstract void drawForeground(int mx, int my, float frame);

	/**
	 * Called internally when the page is changed. Don't override this, use
	 * {@link #onPageChanged(BookGUI, int)} instead.
	 *
	 * @param gui
	 *            The book GUI
	 * @param whatPtr
	 *            The new page pointer
	 */
	protected void internalOnPageChanged(BookGUI gui, int whatPtr) {
		for (int i = 0; i < mode.pages; i++)
			glBufferDirty[i] = true;
		onPageChanged(gui, whatPtr);
	}

	/**
	 * Called internally to set up the display lists.
	 */
	protected void prepareGraphics() {
		try {
			Util.checkGLError();
		} catch (OpenGLException glex) {
			Fontbox.tracer().warn("BookGUI.prepareGraphics", "Bad OpenGL operation detected, check GL history!");
			glex.printStackTrace();
			return;
		}
		glDisplayLists = new int[mode.pages];
		glBufferDirty = new boolean[mode.pages];
		int glList = GL11.glGenLists(glDisplayLists.length);

		try {
			Util.checkGLError();
		} catch (OpenGLException glex) {
			Fontbox.tracer().warn("BookGUI.prepareGraphics",
					"Unable to allocate display-list buffers, using immediate mode.");
			return;
		}

		if (glList <= 0)
			Fontbox.tracer().warn("BookGUI.prepareGraphics", "No display-lists available, using immediate mode.");
		else {
			for (int i = 0; i < glDisplayLists.length; i++) {
				glDisplayLists[i] = glList + i;
				glBufferDirty[i] = true;
			}
			Fontbox.tracer()
			.trace("BookGUI.prepareGraphics", "Displaylist initialized.", glList, glDisplayLists.length);
			useDisplayList = true;
		}
	}

	/**
	 * Advance to the next page
	 */
	protected void next() {
		if (ptr + mode.pages < pages.size()) {
			ptr += mode.pages;
			internalOnPageChanged(this, ptr);
		}
	}

	/**
	 * Go to a page in the index; if the item doesn't exist, no navigation
	 * occurs
	 *
	 * @param id
	 *            The ID of the anchor to go to
	 */
	protected void go(String id) {
		int where = index.find(id);
		if (where != -1)
			go(where);
	}

	/**
	 * Go to a page
	 *
	 * @param where
	 *            The page pointer
	 */
	protected void go(int where) {
		where = where - (where % mode.pages);
		if (ptr != where && 0 <= where - mode.pages && where + mode.pages < pages.size()) {
			ptr = where;
			internalOnPageChanged(this, ptr);
		}
	}

	/**
	 * Reverse to the previous page
	 */
	protected void previous() {
		if (0 <= ptr - mode.pages) {
			ptr -= mode.pages;
			internalOnPageChanged(this, ptr);
		}
	}

	@Override
	protected void keyTyped(char val, int code) {
		super.keyTyped(val, code);
		if (code == Keyboard.KEY_LEFT)
			previous();
		if (code == Keyboard.KEY_RIGHT)
			next();
	}

	@Override
	protected void mouseClicked(int mx, int my, int button) {
		super.mouseClicked(mx, my, button);
		for (int i = 0; i < mode.pages; i++) {
			Layout where = layout[i];
			int which = ptr + i;
			if (pages.size() <= which)
				break;
			Page page = pages.get(ptr + i);
			int mouseX = mx - where.x, mouseY = my - where.y;
			if (mouseX >= 0 && mouseY >= 0 && mouseX <= page.width && mouseY <= page.height) {
				Element elem = DocumentProcessor.getElementAt(page, mouseX, mouseY);
				if (elem != null)
					elem.clicked(this, mouseX, mouseY);
			}
		}
	}

	@Override
	protected void mouseMovedOrUp(int mx, int my, int button) {
		super.mouseMovedOrUp(mx, my, button);
	}

	@Override
	protected void mouseClickMove(int mx, int my, int button, long ticks) {
		super.mouseClickMove(mx, my, button, ticks);
	}

	private void renderPage(int index, Page page, float x, float y, float z, int mx, int my, float frame)
			throws RenderException {
		if (!useDisplayList) {
			renderPageStaticsImmediate(index, page, x, y, z, mx, my, frame);
			renderPageDynamics(index, page, x, y, z, mx, my, frame);
		} else {
			renderPageStaticsBuffered(index, page, x, y, z, mx, my, frame);
			renderPageDynamics(index, page, x, y, z, mx, my, frame);
		}
	}

	private void renderPageDynamics(int index, Page page, float x, float y, float z, int mx, int my, float frame)
			throws RenderException {
		GL11.glPushMatrix();
		GL11.glTranslatef(x, y, z);
		rendeElementGroupImmediate(page.dynamicElements(), mx, my, frame);
		GL11.glPopMatrix();
	}

	private void renderPageStaticsBuffered(int index, Page page, float x, float y, float z, int mx, int my, float frame)
			throws RenderException {
		if (glBufferDirty[index]) {
			GL11.glNewList(glDisplayLists[index], GL11.GL_COMPILE);
			renderPageStaticsImmediate(index, page, x, y, z, mx, my, frame);
			GL11.glEndList();
			glBufferDirty[index] = false;
		}
		GL11.glCallList(glDisplayLists[index]);
	}

	private void renderPageStaticsImmediate(int index, Page page, float x, float y, float z, int mx, int my, float frame)
			throws RenderException {
		GL11.glPushMatrix();
		GL11.glTranslatef(x, y, z);
		rendeElementGroupImmediate(page.staticElements(), mx, my, frame);
		GL11.glPopMatrix();
	}

	private void rendeElementGroupImmediate(ArrayList<Element> elements, int mx, int my, float frame)
			throws RenderException {
		int count = elements.size();
		for (int i = 0; i < count; i++) {
			elements.get(i).render(this, mx, my, frame);
			if (cursors != null) {
				ObjectBounds bounds = elements.get(i).bounds();
				if (bounds != null) {
					GL11.glDisable(GL11.GL_TEXTURE_2D);
					GL11.glEnable(GL11.GL_BLEND);
					GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
					GL11.glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
					GL11.glPushMatrix();
					GL11.glTranslatef(bounds.x * 0.44f, bounds.y * 0.44f, 0);
					GLUtils.drawDefaultRect(0, 0, bounds.width * 0.44f, bounds.height * 0.44f, zLevel);
					GL11.glPopMatrix();
					GL11.glDisable(GL11.GL_BLEND);
					GL11.glEnable(GL11.GL_TEXTURE_2D);
				}
			}
		}
	}

}
 public class RenderException extends Exception {

	/**
	 * Serializer version ID
	 */
	private static final long serialVersionUID = -4995118231054148211L;

	/**
	 * Initializes the rendering exception
	 *
	 * @param reason
	 *            The reason for the exception
	 */
	public RenderException(String reason) {
		super(reason);
	}

	/**
	 * Initializes the rendering exception
	 *
	 * @param reason
	 *            The reason for the exception
	 * @param cause
	 *            The parent cause exception
	 */
	public RenderException(String reason, Throwable cause) {
		super(reason, cause);
	}

}
 public class StackedPushbackStringReader {

	/** Mutex lock. */
	protected Object lock;
	/** Map of all Characters in the stream */
	private ArrayList<Character> str;
	/** Pushback history stack */
	private Stack<Integer> pushback;
	/** Top of stream pointer */
	private int next = 0;

	/**
	 * Create a new StackedPushbackStringReader at the start of the string. The
	 * contents of the string are copied to a local buffer.
	 *
	 * @param s
	 *            The source string.
	 */
	public StackedPushbackStringReader(String s) {
		lock = this;
		str = new ArrayList<Character>();
		pushback = new Stack<Integer>();
		char[] chars = s.toCharArray();
		for (char c : chars)
			str.add(c);
	}

	/** Check to make sure that the stream has not been closed */
	private void ensureOpen() throws IOException {
		if (str == null)
			throw new IOException("Stream closed");
	}

	/**
	 * Get the next character on the stream, or 0 if no characters are remaining
	 * on the stream.
	 *
	 * @return A character or 0
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public char next() throws IOException {
		synchronized (lock) {
			ensureOpen();
			if (next >= str.size())
				return (char) 0;
			return str.get(next++);
		}
	}

	/**
	 * Pushes the current position onto the stack. If the stack is full, a
	 * pushback overflow will be returned.
	 *
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void pushPosition() throws IOException {
		synchronized (lock) {
			ensureOpen();
			if (pushback.size() > 64)
				throw new IOException("Pusback overflow!");
			pushback.push(next);
		}
	}

	/**
	 * Pops the previous position off the stack. If the stack is empty, a
	 * pushback underflow will be returned.
	 *
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void popPosition() throws IOException {
		synchronized (lock) {
			ensureOpen();
			if (pushback.size() == 0)
				throw new IOException("Pushback underflow!");
			next = pushback.pop();
		}
	}

	/**
	 * Returns the current position of the reader.
	 *
	 * @return The current position of the reader.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public int getPosition() throws IOException {
		synchronized (lock) {
			ensureOpen();
			return next;
		}
	}

	/**
	 * Sets the position of the reader.
	 *
	 * @param ns
	 *            A new position.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void setPosition(int ns) throws IOException {
		synchronized (lock) {
			ensureOpen();
			next = ns;
		}
	}

	/**
	 * Commits the current position of the reader. This pops the previous return
	 * position without restoring the pointer.
	 *
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void commitPosition() throws IOException {
		synchronized (lock) {
			ensureOpen();
			pushback.pop();
		}
	}

	/**
	 * Skips forward a number of characters.
	 *
	 * @param ns
	 *            How far forward to skip.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void skip(long ns) throws IOException {
		synchronized (lock) {
			ensureOpen();
			next += ns;
			if (0 > next)
				next = 0;
			if (next > str.size())
				next = str.size();
		}
	}

	/**
	 * Skips backwards a number of characters.
	 *
	 * @param ns
	 *            How far backwards to skip.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public void rewind(long ns) throws IOException {
		synchronized (lock) {
			ensureOpen();
			next -= ns;
			if (0 > next)
				next = 0;
			if (next > str.size())
				next = str.size();
		}
	}

	/**
	 * Ensures the stream is ready for use.
	 *
	 * @return If the stream is ready for use.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public boolean ready() throws IOException {
		synchronized (lock) {
			ensureOpen();
			return true;
		}
	}

	/**
	 * Determine the number of characters remaining in the read collection.
	 *
	 * @return The number of characters waiting to be read.
	 * @throws IOException
	 *             If the lock cannot be obtained or if the stream is not open,
	 *             an IOException will be thrown.
	 */
	public int available() throws IOException {
		synchronized (lock) {
			ensureOpen();
			return Math.max(0, str.size() - next);
		}
	}

	/**
	 * Closes the reader.
	 */
	public void close() {
		str = null;
	}

}
import java.io.IOException;
import java.util.HashMap;
import net.afterlifelochie.fontbox.api.ITracer;
import net.afterlifelochie.fontbox.data.FormattedString;
import net.afterlifelochie.fontbox.document.formatting.TextFormat;
import net.afterlifelochie.fontbox.document.property.AlignmentMode;
import net.afterlifelochie.fontbox.document.property.FloatMode;
import net.afterlifelochie.fontbox.layout.LayoutException;
import net.afterlifelochie.fontbox.layout.ObjectBounds;
import net.afterlifelochie.fontbox.layout.PageCursor;
import net.afterlifelochie.fontbox.layout.PageWriter;
import net.afterlifelochie.fontbox.layout.components.Line;
import net.afterlifelochie.fontbox.layout.components.LineWriter;
import net.afterlifelochie.fontbox.layout.components.Page;
import net.afterlifelochie.fontbox.render.BookGUI;
import net.afterlifelochie.fontbox.render.RenderException;
import net.afterlifelochie.io.StackedPushbackStringReader;

package net.afterlifelochie.fontbox.document;



/**
 * <p>
 * Document element class. Elements are used to in a Document to construct a
 * linked list of objects which are subsequently paginated and rendered.
 * </p>
 *
 * @author AfterLifeLochie
 *
 */
public abstract class Element {

	private ObjectBounds bounds;

	/**
	 * Get the bounds of the object
	 *
	 * @return The bounds of the object
	 */
	public ObjectBounds bounds() {
		return bounds;
	}

	/**
	 * Set the bounds of the object
	 *
	 * @param bb
	 *            The new bounds of the object
	 */
	public void setBounds(ObjectBounds bb) {
		bounds = bb;
	}

	/**
	 * <p>
	 * Called by the document generator to request this element fill in it's
	 * rendering-based properties. The element should place itself on the
	 * current page and update the writing cursor if required.
	 * </p>
	 *
	 * @param trace
	 *            The debugging tracer object
	 * @param writer
	 *            The current page writer
	 * @throws IOException
	 *             Any I/O exception which occurs when writing to the stream
	 * @throws LayoutException
	 *             Any exception which prevents the element from being written
	 *             to the writing stream
	 */

	public abstract void layout(ITracer trace, PageWriter writer) throws IOException, LayoutException;
