/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.jogl.awt;
import static com.jogamp.opengl.GL.GL_ARRAY_BUFFER;
import static com.jogamp.opengl.GL.GL_STATIC_DRAW;
import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.andork.jogl.BufferHelper;
import org.andork.jogl.Dumps;
import org.andork.jogl.JoglDrawContext;
import org.andork.jogl.JoglDrawable;
import org.andork.jogl.JoglManagedResource;
import org.andork.jogl.JoglResourceManager;
import com.jogamp.opengl.GL2ES2;
public class JoglText extends JoglManagedResource implements JoglDrawable {
public static class Builder {
private final float[] lrtb = new float[4];
private Map<SegmentKey, BufferHelper> buffers = new HashMap<SegmentKey, BufferHelper>();
private float[] dot = new float[3];
private float[] nextDot = new float[3];
private final float[] baseline = new float[3];
private final float[] ascent = new float[3];
private JoglTextProgram program;
public Builder() {
baseline[0] = 1;
ascent[1] = 1;
}
public Builder add(String text, GlyphCache cache, float... color) {
SegmentKey key = null;
BufferHelper buffer = null;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
GlyphPage page = cache.getPage(c);
if (key == null || key.page != page) {
key = new SegmentKey(page, color);
buffer = buffers.get(key);
if (buffer == null) {
buffer = new BufferHelper();
buffers.put(key, buffer);
}
}
page.getTexcoordBounds(c, lrtb);
float scale = 1f / page.metrics.getAscent();
float width = page.metrics.charWidth(c) * scale;
nextDot[0] = dot[0] + baseline[0] * width;
nextDot[1] = dot[1] + baseline[1] * width;
nextDot[2] = dot[2] + baseline[2] * width;
float ascentScale = page.metrics.getMaxAscent() * scale;
float descentScale = -page.metrics.getMaxDescent() * scale;
buffer.putAsFloats(dot[0] + ascent[0] * descentScale);
buffer.putAsFloats(dot[1] + ascent[1] * descentScale);
buffer.putAsFloats(dot[2] + ascent[2] * descentScale);
buffer.putAsFloats(lrtb[0], lrtb[3]);
buffer.putAsFloats(dot[0] + ascent[0] * ascentScale);
buffer.putAsFloats(dot[1] + ascent[1] * ascentScale);
buffer.putAsFloats(dot[2] + ascent[2] * ascentScale);
buffer.putAsFloats(lrtb[0], lrtb[2]);
buffer.putAsFloats(nextDot[0] + ascent[0] * descentScale);
buffer.putAsFloats(nextDot[1] + ascent[1] * descentScale);
buffer.putAsFloats(nextDot[2] + ascent[2] * descentScale);
buffer.putAsFloats(lrtb[1], lrtb[3]);
buffer.putAsFloats(dot[0] + ascent[0] * ascentScale);
buffer.putAsFloats(dot[1] + ascent[1] * ascentScale);
buffer.putAsFloats(dot[2] + ascent[2] * ascentScale);
buffer.putAsFloats(lrtb[0], lrtb[2]);
buffer.putAsFloats(nextDot[0] + ascent[0] * descentScale);
buffer.putAsFloats(nextDot[1] + ascent[1] * descentScale);
buffer.putAsFloats(nextDot[2] + ascent[2] * descentScale);
buffer.putAsFloats(lrtb[1], lrtb[3]);
buffer.putAsFloats(nextDot[0] + ascent[0] * ascentScale);
buffer.putAsFloats(nextDot[1] + ascent[1] * ascentScale);
buffer.putAsFloats(nextDot[2] + ascent[2] * ascentScale);
buffer.putAsFloats(lrtb[1], lrtb[2]);
float[] temp = dot;
dot = nextDot;
nextDot = temp;
}
return this;
}
public Builder ascent(float... ascent) {
System.arraycopy(ascent, 0, this.ascent, 0, 3);
return this;
}
public Builder baseline(float... baseline) {
System.arraycopy(baseline, 0, this.baseline, 0, 3);
return this;
}
public JoglText create(JoglResourceManager manager) {
Segment[] segments = new Segment[buffers.size()];
int k = 0;
for (Map.Entry<SegmentKey, BufferHelper> entry : buffers.entrySet()) {
FloatBuffer data = entry.getValue().toByteBuffer().asFloatBuffer();
Dumps.dumpBuffer(data, "%9.2f, ", 5);
segments[k++] = new Segment(data, entry.getKey().page, data.capacity() / 5, entry.getKey().color);
}
return new JoglText(manager, Arrays.asList(segments), program);
}
public Builder dot(float... dot) {
System.arraycopy(dot, 0, this.dot, 0, 3);
return this;
}
public Builder program(JoglTextProgram program) {
this.program = program;
return this;
}
}
public static class Segment {
private final FloatBuffer data;
private int buffer;
public final GlyphPage page;
public final int count;
public final float[] color;
private Segment(FloatBuffer data, GlyphPage page, int count, float[] color) {
this.data = data;
this.page = page;
this.count = count;
this.color = color;
}
public int buffer() {
return buffer;
}
}
private static class SegmentKey {
private final GlyphPage page;
private final float[] color;
public SegmentKey(GlyphPage page, float[] color) {
this.page = page;
this.color = Arrays.copyOf(color, 4);
}
@Override
public boolean equals(Object o) {
if (o instanceof SegmentKey) {
SegmentKey ps = (SegmentKey) o;
return ps.page == page && Arrays.equals(ps.color, color);
}
return false;
}
@Override
public int hashCode() {
return 31 * page.hashCode() ^ Arrays.hashCode(color);
}
}
public final List<Segment> segments;
private JoglTextProgram program;
public final float[] origin = new float[3];
private JoglText(JoglResourceManager manager, List<Segment> segments, JoglTextProgram program) {
super(manager);
this.segments = segments;
this.program = program;
}
@Override
protected void doDispose(GL2ES2 gl) {
if (!segments.isEmpty()) {
int[] temp = new int[segments.size()];
int k = 0;
for (Segment segment : segments) {
temp[k++] = segment.buffer;
segment.buffer = 0;
}
gl.glDeleteBuffers(k, temp, 0);
}
}
@Override
protected void doInit(GL2ES2 gl) {
if (!segments.isEmpty()) {
int[] temp = new int[segments.size()];
gl.glGenBuffers(segments.size(), temp, 0);
int k = 0;
for (Segment segment : segments) {
segment.buffer = temp[k++];
segment.data.position(0);
gl.glBindBuffer(GL_ARRAY_BUFFER, segment.buffer);
gl.glBufferData(GL_ARRAY_BUFFER, segment.data.capacity() * 4, segment.data, GL_STATIC_DRAW);
}
gl.glBindBuffer(GL_ARRAY_BUFFER, 0);
}
}
@Override
protected void doRelease() {
program.removeUser(this);
for (Segment segment : segments) {
segment.page.removeUser(this);
}
}
@Override
protected void doUse() {
program.addUser(this);
for (Segment segment : segments) {
segment.page.addUser(this);
}
}
@Override
public void draw(JoglDrawContext context, GL2ES2 gl, float[] m, float[] n) {
requireInitialized();
if (program != null) {
program.draw(this, context, gl, m, n);
}
}
public JoglTextProgram program() {
return program;
}
public JoglText program(JoglTextProgram program) {
if (this.program != program) {
if (isInUse()) {
if (this.program != null) {
this.program.removeUser(this);
}
if (program != null) {
program.addUser(this);
}
}
this.program = program;
}
return this;
}
}