/*
Редактор шрифтов UFN
Эта программа создана специально для программы Малик Эмулятор.
Copyright © 2019, 2022 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
общественной лицензии GNU.
Вы должны были получить копию Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package ru.malik.elaborarer.ufneditor;
import com.nokia.mid.ui.*;
import javax.microedition.lcdui.*;
import malik.emulator.fileformats.font.*;
import malik.emulator.fileformats.font.UnicodeRasterFont.*;
public final class GlyphEditor extends FontCustomItem
{
public static final int MODE_DRAW = 0;
public static final int MODE_MOVE = 1;
public static final int MODE_SIZE = 2;
public static final int MINIMUM_ZOOM = 4;
public static final int MAXIMUM_ZOOM = 16;
private static final int LABEL_NONE = 0;
private static final int LABEL_GLYPH_WIDTH = 1;
private static final int LABEL_FONT_HEIGTH = 2;
private static final int LABEL_FONT_CAPITAL = 3;
private static final int LABEL_FONT_SMALL = 4;
private static final int LABEL_FONT_BASE = 5;
private static final int COLOR_GRID = 0x66404040;
private static final int COLOR_AXIS = 0x9f000000;
private static final int COLOR_FRAME = 0x66004000;
private static final int COLOR_PIXEL = 0xbf000000;
private static final int COLOR_SMALL = 0x66008000;
private static final int COLOR_CAPITAL = 0x66006000;
private static final int COLOR_BORDER = 0xcc00bf00;
private static final Image LABELS;
static {
Image labels;
try
{
labels = Image.createImage("/labels.png");
}
catch(Exception e)
{
labels = Image.createImage(2, 5);
}
LABELS = labels;
}
private static boolean isPointerIn(int left, int top, int width, int height, int x, int y) {
return x >= left && x < left + width && y >= top && y < top + height;
}
private boolean opaque;
private boolean focused;
private int coords;
private int label;
private int zoom;
private int mode;
private Glyph glyph;
public GlyphEditor(UnicodeRasterFont font) {
super(font);
this.zoom = 8;
}
public void notifyGlyphChanged() {
repaint();
}
public void zoomIn() {
int zoom;
if((zoom = this.zoom) < MAXIMUM_ZOOM) this.zoom = zoom + 1;
invalidate();
}
public void zoomOut() {
int zoom;
if((zoom = this.zoom) > MINIMUM_ZOOM) this.zoom = zoom - 1;
invalidate();
}
public void setZoom(int zoom) {
if(zoom < MINIMUM_ZOOM) zoom = MINIMUM_ZOOM;
if(zoom > MAXIMUM_ZOOM) zoom = MAXIMUM_ZOOM;
this.zoom = zoom;
invalidate();
}
public void setMode(int mode) {
if(mode < MODE_DRAW || mode > MODE_SIZE)
{
throw new IllegalArgumentException("GlyphEditor.setMode: аргумент mode имеет недопустимое значение.");
}
this.mode = mode;
repaint();
}
public void setGlyph(Glyph glyph) {
if(glyph == null)
{
throw new NullPointerException("GlyphEditor.setGlyph: аргумент glyph равен нулевой ссылке.");
}
if(glyph.parentFont() != font)
{
throw new IllegalArgumentException("GlyphEditor.setGlyph: аргумент glyph не принадлежит заданному шрифту.");
}
this.glyph = glyph;
repaint();
}
public int getZoom() {
return zoom;
}
public int getMode() {
return mode;
}
public Glyph getGlyph() {
return glyph;
}
protected void paint(Graphics renderA, int contentWidth, int contentHeight) {
int step = zoom;
int clipL = renderA.getClipX();
int clipT = renderA.getClipY();
int clipR = clipL + renderA.getClipWidth() - 1;
int clipB = clipT + renderA.getClipHeight() - 1;
int glyphL = getGlyphX(clipL);
int glyphT = getGlyphY(clipT);
int glyphR = getGlyphX(clipR);
int glyphB = getGlyphY(clipB);
Glyph glyph = this.glyph;
DirectGraphics renderB = DirectUtils.getDirectGraphics(renderA);
UnicodeRasterFont font = this.font;
renderB.setARGBColor(COLOR_GRID);
for(int ex = getEditorX(glyphL) + step - 1, gx = glyphL; ex <= clipR && gx <= glyphR; ex += step, gx++) if(gx != -1) renderA.drawLine(ex, clipT, ex, clipB);
for(int ey = getEditorY(glyphT) + step - 1, gy = glyphT; ey <= clipB && gy >= glyphB; ey += step, gy--) if(gy != 0) renderA.drawLine(clipL, ey, clipR, ey);
// координатные оси
{
int ex = getEditorX(-1) + step - 1;
int ey = getEditorY(0) + step - 1;
renderB.setARGBColor(COLOR_AXIS);
if(ex >= clipL && ex <= clipR) renderA.drawLine(ex, clipT, ex, clipB);
if(ey >= clipT && ey <= clipB) renderA.drawLine(clipL, ey, clipR, ey);
}
if(glyph != null)
{
int fb = font.getBaselineHeight();
int fh = font.getHeight();
int gw = glyph.getWidth();
int gl;
int gr;
int gb = -fb - 1;
int gt = fh + gb;
int gbl;
int gbr;
int gbb;
int gbt;
int ex1 = getEditorX(0);
int ex2 = getEditorX(gw) - 1;
int ey1 = getEditorY(gt) - 1;
int ey2 = getEditorY(gb) - 1;
renderB.setARGBColor(COLOR_FRAME);
renderA.drawLine(ex1, ey1, ex2, ey1);
renderA.drawLine(ex1, ey2, ex2, ey2);
renderA.drawLine(ex2, ey1 + 1, ex2, ey2 - 1);
ey1 = getEditorY(font.getCapitalLettersHeight() - 1) - 1;
ey2 = getEditorY(font.getSmallLettersHeight() - 1) - 1;
renderB.setARGBColor(COLOR_CAPITAL);
renderA.drawLine(ex1, ey1, --ex2, ey1);
renderB.setARGBColor(COLOR_SMALL);
renderA.drawLine(ex1, ey2, ex2, ey2);
gl = gbl = glyph.getRawLeft();
gb = gbb = glyph.getRawBottom();
gr = gbr = glyph.getRawWidth() + gl - 1;
gt = gbt = glyph.getRawHeight() + gb - 1;
if(gbl < glyphL) gbl = glyphL;
if(gbb < glyphB) gbb = glyphB;
if(gbr > glyphR) gbr = glyphR;
if(gbt > glyphT) gbt = glyphT;
renderB.setARGBColor(COLOR_PIXEL);
for(int ex0 = getEditorX(gbl), ey = getEditorY(gbt), gy = gbt; gy >= gbb; ey += step, gy--) for(int ex = ex0, gx = gbl; gx <= gbr; ex += step, gx++) if(glyph.getPixel(gx, gy))
{
renderA.fillRect(ex, ey, step, step);
}
switch(mode)
{
case MODE_MOVE:
if(gr >= gl && gt >= gb)
{
ex1 = getEditorX(gl) - 1;
ey1 = getEditorY(gt) - 1;
ex2 = getEditorX(gr + 1) - 1;
ey2 = getEditorY(gb - 1) - 1;
renderB.setARGBColor(COLOR_BORDER);
renderA.drawLine(ex1 + 1, --ey1, ex2 - 1, ey1);
renderA.drawLine(ex1, ++ey1, ex2, ey1);
renderA.drawLine(ex1 - 1, ++ey1, ex2 + 1, ey1);
renderA.drawLine(ex1 - 1, --ey2, ex2 + 1, ey2);
renderA.drawLine(ex1, ++ey2, ex2, ey2);
renderA.drawLine(ex1 + 1, ++ey2, ex2 - 1, ey2);
ey1++;
ey2 -= 3;
renderA.drawLine(--ex1, ey1, ex1, ey2);
renderA.drawLine(++ex1, ey1, ex1, ey2);
renderA.drawLine(++ex1, ey1, ex1, ey2);
renderA.drawLine(--ex2, ey1, ex2, ey2);
renderA.drawLine(++ex2, ey1, ex2, ey2);
renderA.drawLine(++ex2, ey1, ex2, ey2);
}
break;
case MODE_SIZE:
int iy;
int lw;
int lh;
int ofs;
int li = label;
Image labels = LABELS;
lw = labels.getWidth() / 2;
lh = labels.getHeight() / 5;
ofs = -lh / 2 + step;
ex1 -= lw + 1;
renderA.drawRegion(labels, li == 1 ? lw : 0, iy = 0, lw, lh, 0, ex2 + 2, getEditorY(0) + ofs, 0);
renderA.drawRegion(labels, li == 2 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(fh - fb) + ofs, 0);
renderA.drawRegion(labels, li == 3 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(font.getCapitalLettersHeight()) + ofs, 0);
renderA.drawRegion(labels, li == 4 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(font.getSmallLettersHeight()) + ofs, 0);
renderA.drawRegion(labels, li == 5 ? lw : 0, iy += lh, lw, lh, 0, ex1, getEditorY(-fb) + ofs, 0);
break;
}
}
}
protected void keyReleased(int key) {
int zoom;
switch(key)
{
case '1':
if(mode != MODE_DRAW)
{
mode = MODE_DRAW;
super.notifyStateChanged();
repaint();
}
break;
case '2':
if(mode != MODE_MOVE)
{
mode = MODE_MOVE;
super.notifyStateChanged();
repaint();
}
break;
case '3':
if(mode != MODE_SIZE)
{
mode = MODE_SIZE;
super.notifyStateChanged();
repaint();
}
break;
case '*':
if((zoom = this.zoom) > MINIMUM_ZOOM)
{
this.zoom = zoom - 1;
super.notifyStateChanged();
invalidate();
}
break;
case '#':
if((zoom = this.zoom) < MAXIMUM_ZOOM)
{
this.zoom = zoom + 1;
super.notifyStateChanged();
invalidate();
}
break;
}
}
protected void pointerPressed(int x, int y) {
Glyph glyph;
if((glyph = this.glyph) != null)
{
int gx = getGlyphX(x);
int gy = getGlyphY(y);
switch(mode)
{
case MODE_DRAW:
glyph.putPixel(gx, gy, opaque = !glyph.getPixel(gx, gy));
repaint();
break;
case MODE_SIZE:
if((label = getLabelIndexAt(x, y)) == LABEL_NONE) glyph.putPixel(gx, gy, opaque = !glyph.getPixel(gx, gy));
repaint();
/* fall through */
case MODE_MOVE:
coords = gx & 0xffff | gy << 16;
break;
}
}
}
protected void pointerDragged(int x, int y) {
Glyph glyph;
if((glyph = this.glyph) != null)
{
int gx = getGlyphX(x);
int gy = getGlyphY(y);
int oldcoords;
int newcoords;
switch(mode)
{
case MODE_DRAW:
glyph.putPixel(gx, gy, opaque);
repaint();
break;
case MODE_MOVE:
if((oldcoords = coords) != (newcoords = coords = gx & 0xffff | gy << 16))
{
glyph.move((short) newcoords - (short) oldcoords, (newcoords >> 16) - (oldcoords >> 16));
repaint();
}
break;
case MODE_SIZE:
switch(label)
{
case LABEL_GLYPH_WIDTH:
if((oldcoords = (short) coords) != (newcoords = (short) (coords = gx & 0xffff | gy << 16)))
{
int width = glyph.getWidth() + (newcoords - oldcoords);
if(width > UnicodeRasterFont.MAX_COORDINATE) width = UnicodeRasterFont.MAX_COORDINATE;
if(width < 0) width = 0;
glyph.setWidth(width);
repaint();
}
break;
case LABEL_FONT_HEIGTH:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int fb;
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getHeight() + (newcoords - oldcoords);
if(height > (tmp = (fb = font.getBaselineHeight()) + (UnicodeRasterFont.MAX_COORDINATE + 1))) height = tmp;
if(height < (tmp = Math.max(font.getSmallLettersHeight(), font.getCapitalLettersHeight()) + fb)) height = tmp;
font.setHeight(height);
repaint();
}
break;
case LABEL_FONT_CAPITAL:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getCapitalLettersHeight() + (newcoords - oldcoords);
if(height > (tmp = font.getHeight() - font.getBaselineHeight())) height = tmp;
if(height < (tmp = font.getSmallLettersHeight())) height = tmp;
font.setCapitalLettersHeight(height);
repaint();
}
break;
case LABEL_FONT_SMALL:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getSmallLettersHeight() + (newcoords - oldcoords);
if(height > (tmp = font.getCapitalLettersHeight())) height = tmp;
if(height < 0) height = 0;
font.setSmallLettersHeight(height);
repaint();
}
break;
case LABEL_FONT_BASE:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int fh;
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getBaselineHeight() + (oldcoords - newcoords);
if(height > (tmp = Math.min((fh = font.getHeight()) - font.getCapitalLettersHeight(), -UnicodeRasterFont.MIN_COORDINATE))) height = tmp;
if(height < (tmp = Math.max(fh - (UnicodeRasterFont.MAX_COORDINATE + 1), 0))) height = tmp;
font.setBaselineHeight(height);
repaint();
}
break;
default:
glyph.putPixel(gx, gy, opaque);
repaint();
break;
}
break;
}
}
}
protected void pointerReleased(int x, int y) {
Glyph glyph;
if((glyph = this.glyph) != null)
{
int gx = getGlyphX(x);
int gy = getGlyphY(y);
int oldcoords;
int newcoords;
switch(mode)
{
case MODE_DRAW:
glyph.putPixel(gx, gy, opaque);
repaint();
break;
case MODE_MOVE:
if((oldcoords = coords) != (newcoords = gx & 0xffff | gy << 16))
{
coords = newcoords;
glyph.move((short) newcoords - (short) oldcoords, (newcoords >> 16) - (oldcoords >> 16));
repaint();
}
break;
case MODE_SIZE:
switch(label)
{
case LABEL_GLYPH_WIDTH:
if((oldcoords = (short) coords) != (newcoords = (short) (coords = gx & 0xffff | gy << 16)))
{
int width = glyph.getWidth() + (newcoords - oldcoords);
if(width > UnicodeRasterFont.MAX_COORDINATE) width = UnicodeRasterFont.MAX_COORDINATE;
if(width < 0) width = 0;
glyph.setWidth(width);
}
break;
case LABEL_FONT_HEIGTH:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int fb;
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getHeight() + (newcoords - oldcoords);
if(height > (tmp = (fb = font.getBaselineHeight()) + (UnicodeRasterFont.MAX_COORDINATE + 1))) height = tmp;
if(height < (tmp = Math.max(font.getSmallLettersHeight(), font.getCapitalLettersHeight()) + fb)) height = tmp;
font.setHeight(height);
}
break;
case LABEL_FONT_CAPITAL:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getCapitalLettersHeight() + (newcoords - oldcoords);
if(height > (tmp = font.getHeight() - font.getBaselineHeight())) height = tmp;
if(height < (tmp = font.getSmallLettersHeight())) height = tmp;
font.setCapitalLettersHeight(height);
}
break;
case LABEL_FONT_SMALL:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getSmallLettersHeight() + (newcoords - oldcoords);
if(height > (tmp = font.getCapitalLettersHeight())) height = tmp;
if(height < 0) height = 0;
font.setSmallLettersHeight(height);
}
break;
case LABEL_FONT_BASE:
if((oldcoords = coords >> 16) != (newcoords = (coords = gx & 0xffff | gy << 16) >> 16))
{
int fh;
int tmp;
int height;
UnicodeRasterFont font = this.font;
height = font.getBaselineHeight() + (oldcoords - newcoords);
if(height > (tmp = Math.min((fh = font.getHeight()) - font.getCapitalLettersHeight(), -UnicodeRasterFont.MIN_COORDINATE))) height = tmp;
if(height < (tmp = Math.max(fh - (UnicodeRasterFont.MAX_COORDINATE + 1), 0))) height = tmp;
font.setBaselineHeight(height);
}
break;
default:
glyph.putPixel(gx, gy, opaque);
break;
}
label = 0;
repaint();
break;
}
}
}
protected void traverseOut() {
focused = false;
}
protected boolean traverse(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
int l;
int t;
int w;
int h;
Glyph glyph;
UnicodeRasterFont font;
if(focused && direction != NONE) return false;
focused = true;
font = this.font;
if((glyph = this.glyph) == null)
{
l = getEditorX(0);
t = getEditorY(0);
w = 0;
h = 0;
} else
{
int zoom = this.zoom;
int fh = font.getHeight();
l = getEditorX(0);
t = getEditorY(fh - font.getBaselineHeight() - 1);
w = glyph.getWidth() * zoom;
h = fh * zoom;
}
visibleRectangle[0] = l - (viewportWidth - w) / 2;
visibleRectangle[1] = t - (viewportHeight - h) / 2;
visibleRectangle[2] = viewportWidth;
visibleRectangle[3] = viewportHeight;
return true;
}
protected int getMinContentWidth() {
return getEditorX(UnicodeRasterFont.MAX_COORDINATE + 1);
}
protected int getMinContentHeight() {
return getEditorY(UnicodeRasterFont.MIN_COORDINATE - 1);
}
private int getGlyphX(int editorX) {
return editorX / zoom + UnicodeRasterFont.MIN_COORDINATE;
}
private int getGlyphY(int editorY) {
return -editorY / zoom + UnicodeRasterFont.MAX_COORDINATE;
}
private int getEditorX(int glyphX) {
return (glyphX - UnicodeRasterFont.MIN_COORDINATE) * zoom;
}
private int getEditorY(int glyphY) {
return (UnicodeRasterFont.MAX_COORDINATE - glyphY) * zoom;
}
private int getLabelIndexAt(int x, int y) {
int gw;
int fh;
int fb;
int lw;
int lh;
int ex1;
int ex2;
int ofs;
Image labels = LABELS;
Glyph glyph = this.glyph;
UnicodeRasterFont font = this.font;
gw = glyph.getWidth();
fh = font.getHeight();
fb = font.getBaselineHeight();
lw = labels.getWidth() / 2;
lh = labels.getHeight() / 5;
ex1 = getEditorX(0) - lw - 1;
ex2 = getEditorX(gw);
ofs = -lh / 2 + zoom;
if(isPointerIn(ex1, getEditorY(-fb) + ofs, lw, lh, x, y)) return LABEL_FONT_BASE;
if(isPointerIn(ex1, getEditorY(font.getSmallLettersHeight()) + ofs, lw, lh, x, y)) return LABEL_FONT_SMALL;
if(isPointerIn(ex1, getEditorY(font.getCapitalLettersHeight()) + ofs, lw, lh, x, y)) return LABEL_FONT_CAPITAL;
if(isPointerIn(ex1, getEditorY(fh - fb) + ofs, lw, lh, x, y)) return LABEL_FONT_HEIGTH;
if(isPointerIn(ex2, getEditorY(0) + ofs, lw, lh, x, y)) return LABEL_GLYPH_WIDTH;
return LABEL_NONE;
}
}