/*
    Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
    и других спецификаций для функционирования компактных приложений на языке
    Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Это свободная программа: вы можете перераспространять ее и/или изменять
    ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
*/

package malik.emulator.fileformats.font;

import java.io.*;
import java.util.*;
import malik.emulator.fileformats.*;
import malik.emulator.io.cloud.*;
import malik.emulator.util.*;

public final class UnicodeRasterFont extends Object implements DataCodec
{
    static final int MAX_SIGNED_VALUE = +0x7f;
    static final int MIN_SIGNED_VALUE = -0x80;
    static final int MAX_UNSIGNED_VALUE = 0xff;
    static final int MIN_UNSIGNED_VALUE = 0x00;
    public static final int MAX_COORDINATE = +0x7f;
    public static final int MIN_COORDINATE = -0x7f;
    public static final long SIGNATURE = 0x55464e54L;

    static boolean isEmptyCol(int width, int height, byte[] pixels, int col) {
        for(int scan = bytesNeeded(width), mask = 1 << (col & 7), i = col >> 3, row = height; row-- > 0; i += scan) if((pixels[i] & mask) != 0) return false;
        return true;
    }

    static boolean isEmptyRow(int width, int height, byte[] pixels, int row) {
        for(int scan = bytesNeeded(width), mend = maskEnding(width), base = scan * row, c = scan + base - 1, i = base; i <= c; i++)
        {
            int mask = i < c ? 0xff : mend;
            if((pixels[i] & mask) != 0) return false;
        }
        return true;
    }

    static boolean isEmptyGlyph(int width, int height, byte[] pixels) {
        for(int scan = bytesNeeded(width), mend = maskEnding(width), len = scan * height, c = scan - 1, i = 0; i < len; i++)
        {
            int mask;
            if(i < c)
            {
                mask = 0xff;
            } else
            {
                mask = mend;
                c += scan;
            }
            if((pixels[i] & mask) != 0) return false;
        }
        return true;
    }

    static boolean pixelsMatches(int width, int height, byte[] pixels1, byte[] pixels2) {
        for(int scan = bytesNeeded(width), mend = maskEnding(width), len = scan * height, c = scan - 1, i = 0; i < len; i++)
        {
            int mask;
            if(i < c)
            {
                mask = 0xff;
            } else
            {
                mask = mend;
                c += scan;
            }
            if((pixels1[i] & mask) != (pixels2[i] & mask)) return false;
        }
        return true;
    }

    static int pixelsHashCode(int width, int height, byte[] pixels) {
        int result = 0;
        for(int scan = bytesNeeded(width), mend = maskEnding(width), len = scan * height, c = scan - 1, i = 0; i < len; i++)
        {
            int mask;
            if(i < c)
            {
                mask = 0xff;
            } else
            {
                mask = mend;
                c += scan;
            }
            result ^= (pixels[i] & mask) << (i << 3);
        }
        return result;
    }

    static int madeRectangle(int left, int bottom, int width, int height) {
        return left << 24 | (bottom & 0xff) << 16 | (width & 0xff) << 8 | (height & 0xff);
    }

    static int bytesNeeded(int width, int height) {
        return (width + (-width & 7) >> 3) * height;
    }

    static int bytesNeeded(int width) {
        return width + (-width & 7) >> 3;
    }

    static int toCanonical(int rectangle) {
        return (rectangle & 0xff00) == 0 || (rectangle & 0x00ff) == 0 ? 0 : rectangle;
    }

    static int maskEnding(int width) {
        return (width &= 7) > 0 ? (1 << width) - 1 : 0xff;
    }

    static int indexOf(int characterCode, Glyph[] glyphs, int count) {
        for(int result, limit1 = 0, limit2 = count - 1; limit1 <= limit2; )
        {
            int guess;
            if((guess = glyphs[result = (limit1 + limit2) >> 1].getCharacterCode() - characterCode) < 0)
            {
                limit1 = result + 1;
            }
            else if(guess > 0)
            {
                limit2 = result - 1;
            }
            else
            {
                return result;
            }
        }
        return -1;
    }

    static int indexOf(Glyph glyph, Glyph[] glyphs, int count) {
        return Array.findb(glyphs, count - 1, glyph);
    }

    public class Glyph extends Object implements DataCodec
    {
        boolean removed;
        int characterCode;
        int offset;
        int width;
        int rectangle;
        byte[] pixels;

        Glyph(boolean removed, int characterCode, int width, int rectangle, byte[] pixels) {
            this.removed = removed;
            this.characterCode = characterCode;
            this.width = width;
            this.rectangle = rectangle;
            this.pixels = pixels;
        }

        public boolean equals(Object anot) {
            boolean result;
            if(anot == this) return true;
            if(!(anot instanceof Glyph)) return false;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                Glyph g;
                synchronized((g = (Glyph) anot).monitor())
                {
                    int r;
                    result = width == g.width && (r = rectangle) == g.rectangle && pixelsMatches(r >> 8 & 0xff, r & 0xff, pixels, g.pixels);
                }
            }
            return result;
        }

        public int hashCode() {
            int result;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                int r;
                result = (width << 12) ^ (r = rectangle) ^ pixelsHashCode(r >> 8 & 0xff, r & 0xff, pixels);
            }
            return result;
        }

        public void loadFromInputStream(InputStream stream) throws IOException {
            loadFromDataStream(new ExtendedDataInputStream(stream));
        }

        public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
            synchronized((UnicodeRasterFont.this).monitor)
            {
                int r;
                width = stream.readUnsignedByte();
                rectangle = r = toCanonical(stream.readInt());
                stream.readFully(madeLarger(r), 0, bytesNeeded(r >> 8 & 0xff, r & 0xff));
                if(r != 0) trim();
            }
        }

        public void saveToOutputStream(OutputStream stream) throws IOException {
            saveToDataStream(new ExtendedDataOutputStream(stream));
        }

        public void saveToDataStream(ExtendedDataOutputStream stream) throws IOException {
            synchronized((UnicodeRasterFont.this).monitor)
            {
                int r;
                stream.writeByte(width);
                stream.writeInt(r = rectangle);
                stream.write(pixels, 0, bytesNeeded(r >> 8 & 0xff, r & 0xff));
            }
        }

        public void clear() {
            synchronized((UnicodeRasterFont.this).monitor)
            {
                width = 0;
                rectangle = 0;
            }
        }

        public boolean isEmpty() {
            boolean result;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                result = (width | rectangle) == 0;
            }
            return result;
        }

        public void paste(Glyph source) {
            if(source == null)
            {
                throw new NullPointerException("Glyph.paste: аргумент source равен нулевой ссылке.");
            }
            synchronized((UnicodeRasterFont.this).monitor)
            {
                synchronized(source.monitor())
                {
                    int r;
                    width = source.width;
                    rectangle = r = source.rectangle;
                    Array.copy(source.pixels, 0, madeLarger(r), 0, bytesNeeded(r >> 8 & 0xff, r & 0xff));
                }
            }
        }

        public void move(int deltaX, int deltaY) {
            if(deltaX == 0 && deltaY == 0) return;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                int r = rectangle;
                int maxX = (MAX_COORDINATE + 1) - (r >> 8 & 0xff);
                int maxY = (MAX_COORDINATE + 1) - (r & 0xff);
                int left = (r >> 24) + deltaX;
                int bottom = (byte) (r >> 16) + deltaY;
                if(left < MIN_COORDINATE) left = MIN_COORDINATE;
                if(left > maxX) left = maxX;
                if(bottom < MIN_COORDINATE) bottom = MIN_COORDINATE;
                if(bottom > maxY) bottom = maxY;
                rectangle = toCanonical(r & 0xffff | left << 24 | (bottom & 0xff) << 16);
            }
        }

        public void putPixel(int x, int y, boolean opaque) {
            if(x < MIN_COORDINATE || x > MAX_COORDINATE || y < MIN_COORDINATE || y > MAX_COORDINATE) return;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                label0:
                {
                    int r;
                    int ax;
                    int ay;
                    int left = (r = rectangle) >> 24;
                    int bottom = (byte) (r >> 16);
                    int width = r >> 8 & 0xff;
                    int height = r & 0xff;
                    int right = left + width - 1;
                    int top = bottom + height - 1;
                    int scan;
                    if(!opaque)
                    {
                        if(x >= left && x <= right && y >= bottom && y <= top)
                        {
                            ax = x - left;
                            ay = y - bottom;
                            scan = bytesNeeded(width);
                            pixels[ay * scan + (ax >> 3)] &= ~(1 << (ax & 7));
                            if(x == left || x == right || y == bottom || y == top) trim();
                        }
                        break label0;
                    }
                    if((r & 0xffff) == 0)
                    {
                        madeLarger(rectangle = x << 24 | (y & 0xff) << 16 | 0x0101)[0] = 1;
                        break label0;
                    }
                    if(x >= left && x <= right && y >= bottom && y <= top)
                    {
                        ax = x - left;
                        ay = y - bottom;
                        scan = bytesNeeded(width);
                        pixels[ay * scan + (ax >> 3)] |= (1 << (ax & 7));
                        break label0;
                    }
                    ax = x - ((r = expand(x < left ? x - left : x > right ? x - right : 0, y < bottom ? y - bottom : y > top ? y - top : 0)) >> 24);
                    ay = y - ((byte) (r >> 16));
                    scan = bytesNeeded(r >> 8 & 0xff);
                    pixels[ay * scan + (ax >> 3)] |= (1 << (ax & 7));
                }
            }
        }

        public void setCharacterCode(int characterCode) {
            int error = 0;
            UnicodeRasterFont parent;
            synchronized((parent = UnicodeRasterFont.this).monitor)
            {
                label0:
                {
                    int count;
                    int newIndex;
                    int oldIndex;
                    int oldCharacterCode;
                    Glyph[] glyphs;
                    if(characterCode == (oldCharacterCode = this.characterCode)) break label0;
                    if(removed)
                    {
                        this.characterCode = characterCode;
                        break label0;
                    }
                    if(indexOf(characterCode, glyphs = parent.glyphs, count = parent.getGlyphsCount()) >= 0)
                    {
                        error = 1;
                        break label0;
                    }
                    newIndex = count;
                    oldIndex = indexOf(oldCharacterCode, glyphs, count);
                    for(int i = 0; i < count; i++) if(glyphs[i].characterCode > characterCode)
                    {
                        newIndex = i;
                        break;
                    }
                    if(newIndex < oldIndex)
                    {
                        Array.copy(glyphs, newIndex, glyphs, newIndex + 1, oldIndex - newIndex);
                    }
                    else if(newIndex > oldIndex && --newIndex > oldIndex)
                    {
                        Array.copy(glyphs, oldIndex + 1, glyphs, oldIndex, newIndex - oldIndex);
                    }
                    glyphs[newIndex] = this;
                    this.characterCode = characterCode;
                }
            }
            if(error == 1)
            {
                throw new CharacterCodeExistsException(
                    (new StringBuilder()).append("Glyph.setCharacterCode: глиф с кодом ").append(characterCode).append(" уже существует в родительском шрифте.").toString()
                );
            }
        }

        public void setWidth(int width) {
            if(width < MIN_UNSIGNED_VALUE) width = MIN_UNSIGNED_VALUE;
            if(width > MAX_UNSIGNED_VALUE) width = MAX_UNSIGNED_VALUE;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                this.width = width;
            }
        }

        public void getPixels(int[] pixels, int offset, int scanlength, int pixel) {
            int error;
            if(pixels == null)
            {
                throw new NullPointerException("Glyph.getPixels: аргумент pixels равен нулевой ссылке.");
            }
            error = 0;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                label0:
                {
                    int r;
                    int width = (r = rectangle) >> 8 & 0xff;
                    int height = r & 0xff;
                    int pixelsOffset;
                    int pixelsLength;
                    byte[] p;
                    if(scanlength > -width && scanlength < width)
                    {
                        error = 1;
                        break label0;
                    }
                    if(scanlength >= 0)
                    {
                        pixelsOffset = offset;
                        pixelsLength = width + (height - 1) * scanlength;
                    } else
                    {
                        pixelsOffset = offset + (height - 1) * scanlength;
                        pixelsLength = width + offset - pixelsOffset;
                    }
                    if(!Array.isBoundValid(pixels.length, pixelsOffset, pixelsLength))
                    {
                        error = 2;
                        break label0;
                    }
                    p = this.pixels;
                    for(int scan = bytesNeeded(width), step = -scanlength - width, offs = offset + scanlength * (height - 1), y = 0; y < height; offs += step, y++)
                    {
                        for(int x = 0; x < width; offs++, x++) if((p[y * scan + (x >> 3)] & (1 << (x & 7))) != 0) pixels[offs] = pixel;
                    }
                }
            }
            switch(error)
            {
            case 1:
                throw new IllegalArgumentException("Glyph.getPixels: аргумент scanlength выходит из диапазона.");
            case 2:
                throw new ArrayIndexOutOfBoundsException("Glyph.getPixels: индекс массива выходит из диапазона.");
            }
        }

        public boolean getPixel(int x, int y) {
            boolean result;
            synchronized((UnicodeRasterFont.this).monitor)
            {
                int r;
                int left = (r = rectangle) >> 24;
                int bottom = (byte) (r >> 16);
                int width = r >> 8 & 0xff;
                int right = left + width;
                int top = bottom + (r & 0xff);
                if(x < left || x >= right || y < bottom || y >= top)
                {
                    result = false;
                } else
                {
                    int scan;
                    x -= left;
                    y -= bottom;
                    scan = bytesNeeded(width);
                    result = (pixels[y * scan + (x >> 3)] & (1 << (x & 7))) != 0;
                }
            }
            return result;
        }

        public int getCharacterCode() {
            return characterCode;
        }

        public int getWidth() {
            return width;
        }

        public int getRawLeft() {
            return rectangle >> 24;
        }

        public int getRawBottom() {
            return (byte) (rectangle >> 16);
        }

        public int getRawWidth() {
            return rectangle >> 8 & 0xff;
        }

        public int getRawHeight() {
            return rectangle & 0xff;
        }

        public Glyph copy() {
            int c;
            int w;
            int r;
            byte[] p;
            UnicodeRasterFont parent;
            synchronized((parent = UnicodeRasterFont.this).monitor)
            {
                int len = bytesNeeded((r = rectangle) >> 8 & 0xff, r & 0xff);
                c = characterCode;
                w = width;
                Array.copy(pixels, 0, p = new byte[len < 15 ? 15 : len + 1], 0, len);
            }
            return parent.new Glyph(true, c, w, r, p);
        }

        public UnicodeRasterFont parentFont() {
            return removed ? null : UnicodeRasterFont.this;
        }

        final void trim() {
            int r;
            int count;
            int left = (r = rectangle) >> 24;
            int bottom = (byte) (r >> 16);
            int width = r >> 8 & 0xff;
            int height = r & 0xff;
            int scan = bytesNeeded(width);
            byte[] pixels = this.pixels;

            /* глиф пуст? */
            if(isEmptyGlyph(width, height, pixels))
            {
                rectangle = 0;
                return;
            }

            /* пустые строки пикселов на вершине глифа? */
            count = 0;
            for(int row = height; row-- > 0; count++) if(!isEmptyRow(width, height, pixels, row)) break;
            height -= count;

            /* пустые строки пикселов на дне глифа? */
            count = 0;
            for(int row = 0; row < height; count++, row++) if(!isEmptyRow(width, height, pixels, row)) break;
            if(count > 0)
            {
                bottom += count;
                height -= count;
                Array.copy(pixels, count * scan, pixels, 0, height * scan);
            }

            /* пустые столбцы пикселов на правой стороне глифа? */
            count = 0;
            for(int col = width; col-- > 0; count++) if(!isEmptyCol(width, height, pixels, col)) break;
            if(count > 0)
            {
                int newWidth;
                int newScan = bytesNeeded(newWidth = width - count);
                if(scan > newScan) for(int index = scan, newIndex = newScan, row = height; row-- > 1; index += scan, newIndex += newScan) Array.copy(pixels, index, pixels, newIndex, newScan);
                width = newWidth;
                scan = newScan;
            }

            /* пустые столбцы пикселов на левой стороне глифа? */
            count = 0;
            for(int col = 0; col < width; count++, col++) if(!isEmptyCol(width, height, pixels, col)) break;
            if(count > 0)
            {
                int newWidth;
                int newScan = bytesNeeded(newWidth = width - count);
                if((count & 7) > 0)
                {
                    int ofs0 = count >> 3;
                    int ofs1 = 1 + ofs0;
                    int bits1 = count & 7;
                    int bits0 = 8 - bits1;
                    int mask1 = (1 << bits1) - 1;
                    int mask0 = 0xff - mask1;
                    for(int mend = maskEnding(newWidth), c = newScan - 1, f0 = ofs0, f1 = ofs1, to = 0, row = height; row-- > 0; f0 += scan, f1 += scan, to += scan) for(int i = 0; i <= c; i++)
                    {
                        int mask = i < c ? 0xff : mend;
                        pixels[to + i] = (byte) (((pixels[f0 + i] & mask0) >> bits1 | (pixels[f1 + i] & mask1) << bits0) & mask);
                    }
                } else
                {
                    int delta = count >> 3;
                    Array.copy(pixels, delta, pixels, 0, scan * height - delta);
                }
                if(scan > newScan) for(int index = scan, newIndex = newScan, row = height; row-- > 1; index += scan, newIndex += newScan) Array.copy(pixels, index, pixels, newIndex, newScan);
                left += count;
                width = newWidth;
            }

            /* обновление прямоугольника глифа */
            rectangle = madeRectangle(left, bottom, width, height);
        }

        final int expand(int expandX, int expandY) {
            int r;
            int count;
            int left = (r = rectangle) >> 24;
            int bottom = (byte) (r >> 16);
            int width = r >> 8 & 0xff;
            int height = r & 0xff;
            int scan = bytesNeeded(width);
            byte[] pixels = madeLarger(width + (expandX >= 0 ? expandX : -expandX), height + (expandY >= 0 ? expandY : -expandY));

            /* добавление пустых столбцов пикселов на левую сторону глифа */
            if(expandX < 0)
            {
                int newWidth;
                int newScan = bytesNeeded(newWidth = width + (count = -expandX));
                if(scan < newScan) for(int index = scan * (height - 1), newIndex = newScan * (height - 1), row = height; row-- > 1; index -= scan, newIndex -= newScan)
                {
                    Array.copy(pixels, index, pixels, newIndex, scan);
                }
                if((count & 7) > 0)
                {
                    int len = count >> 3;
                    int bits0 = count & 7;
                    int bits1 = 8 - bits0;
                    int mask1 = (1 << bits1) - 1;
                    int mask0 = 0xff - mask1;
                    for(int mend = maskEnding(newWidth), c = newScan - len - 2, f0 = 0, f1 = 1, to = len + 1, row = height; row-- > 0; f0 += newScan, f1 += newScan, to += newScan)
                    {
                        int mask;
                        for(int i = c; i >= 0; i--)
                        {
                            mask = i < c ? 0xff : mend;
                            pixels[to + i] = (byte) (((pixels[f0 + i] & mask0) >> bits1 | (pixels[f1 + i] & mask1) << bits0) & mask);
                        }
                        mask = -1 < c ? 0xff : mend;
                        pixels[to - 1] = (byte) (((pixels[f0] & mask1) << bits0) & mask);
                        Array.fill(pixels, f0, len, 0);
                    }
                } else
                {
                    int delta = count >> 3;
                    Array.copy(pixels, 0, pixels, delta, newScan * height - delta);
                    for(int index = 0, row = height; row-- > 0; index += newScan) Array.fill(pixels, index, delta, 0);
                }
                left += expandX;
                width = newWidth;
                scan = newScan;
            }

            /* добавление пустых столбцов пикселов на правую сторону глифа */
            if(expandX > 0)
            {
                int newWidth;
                int newScan = bytesNeeded(newWidth = width + expandX);
                if(scan < newScan) for(int index = scan * (height - 1), newIndex = newScan * (height - 1), row = height; row-- > 1; index -= scan, newIndex -= newScan)
                {
                    Array.copy(pixels, index, pixels, newIndex, scan);
                }
                for(int mend = maskEnding(width), c = scan - 1, index = scan, len = newScan - scan, row = height; row-- > 0; c += newScan, index += newScan)
                {
                    pixels[c] &= mend;
                    Array.fill(pixels, index, len, 0);
                }
                width = newWidth;
                scan = newScan;
            }

            /* добавление пустых строк пикселов на дно глифа */
            if(expandY < 0)
            {
                Array.copy(pixels, 0, pixels, count = -expandY * scan, height * scan);
                Array.fill(pixels, 0, count, 0);
                bottom += expandY;
                height -= expandY;
            }

            /* добавление пустых строк пикселов на вершину глифа */
            if(expandY > 0)
            {
                Array.fill(pixels, height * scan, expandY * scan, 0);
                height += expandY;
            }

            /* обновление прямоугольника глифа */
            return rectangle = madeRectangle(left, bottom, width, height);
        }

        final int getDataSize() {
            int r;
            return bytesNeeded((r = rectangle) >> 8 & 0xff, r & 0xff) + 5;
        }

        final byte[] madeLarger(int rectangle) {
            int len;
            int length = bytesNeeded(rectangle >> 8 & 0xff, rectangle & 0xff) + 1;
            byte[] result = pixels;
            if(length > (len = result.length))
            {
                int newLength = (len << 1) + 1;
                int resLength = length >= newLength ? length : newLength;
                Array.copy(result, 0, result = pixels = new byte[resLength], 0, len);
            }
            return result;
        }

        final byte[] madeLarger(int width, int height) {
            int len;
            int length = bytesNeeded(width, height) + 1;
            byte[] result = pixels;
            if(length > (len = result.length))
            {
                int newLength = (len << 1) + 1;
                int resLength = length >= newLength ? length : newLength;
                Array.copy(result, 0, result = pixels = new byte[resLength], 0, len);
            }
            return result;
        }

        final Object monitor() {
            return (UnicodeRasterFont.this).monitor;
        }
    }

    private class Enumerator extends Object implements Enumeration
    {
        private int index;

        public Enumerator() {
        }

        public boolean hasMoreElements() {
            return index < (UnicodeRasterFont.this).count;
        }

        public Object nextElement() {
            int error = 0;
            Glyph result;
            UnicodeRasterFont parent;
            synchronized((parent = UnicodeRasterFont.this).monitor)
            {
                label0:
                {
                    int i;
                    if((i = index) >= parent.count)
                    {
                        error = 1;
                        result = null;
                        break label0;
                    }
                    result = parent.glyphs[i++];
                    index = i;
                }
            }
            if(error == 1)
            {
                throw new NoSuchElementException("Enumeration.nextElement: элементов больше не осталось.");
            }
            return result;
        }
    }

    int count;
    long attributes;
    Glyph[] glyphs;
    String lastFileName;
    final Object monitor;

    public UnicodeRasterFont() {
        this.glyphs = new Glyph[15];
        this.monitor = new Object();
    }

    public void loadFromInputStream(InputStream stream) throws IOException {
        loadFromDataStream(new ExtendedDataInputStream(stream));
    }

    public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
        int error;
        if(!stream.markSupported())
        {
            throw new InvalidStreamException("UnicodeRasterFont.loadFromDataStream: поток ввода данных не поддерживает методы mark и reset.");
        }
        stream.mark(Integer.MAX_VALUE);
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int len = count;
                Glyph[] g = glyphs;
                InputStream src;
                for(int i = len; i-- > 0; g[i].removed = true);
                Array.fill(g, count = 0, len, null);
                attributes = stream.readLong();
                if((len = stream.readIntLE()) < 0)
                {
                    error = 1;
                    break label0;
                }
                if(g.length < (count = len)) glyphs = g = new Glyph[len + 15];
                for(int i = 0; i < len; i++)
                {
                    int characterCode;
                    long position;
                    stream.reset();
                    stream.skip(((long) i << 3) + 12L);
                    characterCode = stream.readIntLE();
                    position = (long) stream.readIntLE() & 0x00000000ffffffffL;
                    stream.reset();
                    stream.skip(position - 4L);
                    (g[i] = this.new Glyph(false, characterCode, 0, 0, new byte[15])).loadFromDataStream(stream);
                }
                if((src = stream.getSourceStream()) instanceof HandleInputStream) lastFileName = ((HandleInputStream) src).getFileName();
            }
        }
        if(error == 1)
        {
            throw new InvalidDataFormatException("UnicodeRasterFont.loadFromDataStream: неправильный формат файла шрифта UFN.");
        }
    }

    public void saveToOutputStream(OutputStream stream) throws IOException {
        saveToDataStream(new ExtendedDataOutputStream(stream));
    }

    public void saveToDataStream(ExtendedDataOutputStream stream) throws IOException {
        synchronized(monitor)
        {
            int len = count;
            int offset;
            int startoffset = offset = 16 + (len << 3);
            Glyph[] g = glyphs;
            OutputStream dst;
            label0: for(int i = 0; i < len; i++)
            {
                int w;
                int r;
                int rw;
                int rh;
                byte[] p;
                Glyph curr;
                w = (curr = g[i]).getWidth();
                rw = (r = curr.rectangle) >> 8 & 0xff;
                rh = r & 0xff;
                p = curr.pixels;
                for(int j = 0; j < i; j++)
                {
                    Glyph jgl = g[j];
                    if(w == jgl.getWidth() && r == jgl.rectangle && pixelsMatches(rw, rh, p, jgl.pixels))
                    {
                        curr.offset = jgl.offset;
                        continue label0;
                    }
                }
                curr.offset = offset;
                offset += curr.getDataSize();
            }
            offset = startoffset;
            stream.writeInt((int) SIGNATURE);
            stream.writeLong(attributes);
            stream.writeIntLE(len);
            for(int i = 0; i < len; i++)
            {
                Glyph curr = g[i];
                stream.writeIntLE(curr.getCharacterCode());
                stream.writeIntLE(curr.offset);
            }
            for(int i = 0; i < len; i++)
            {
                Glyph curr = g[i];
                if(offset == curr.offset)
                {
                    curr.saveToDataStream(stream);
                    offset += curr.getDataSize();
                }
            }
            if((dst = stream.getDestinationStream()) instanceof HandleOutputStream) lastFileName = ((HandleOutputStream) dst).getFileName();
        }
    }

    public void clear() {
        synchronized(monitor)
        {
            int len = count;
            Glyph[] g = glyphs;
            for(int i = len; i-- > 0; g[i].removed = true);
            Array.fill(g, count = 0, len, null);
            attributes = 0L;
        }
    }

    public boolean isEmpty() {
        boolean result;
        synchronized(monitor)
        {
            result = ((long) count | attributes) == 0L;
        }
        return result;
    }

    public void removeGlyph(int characterCode) {
        synchronized(monitor)
        {
            int len;
            int index;
            Glyph[] g;
            if((index = indexOf(characterCode, g = glyphs, len = count)) >= 0)
            {
                g[index].removed = true;
                if(index < --len) Array.copy(g, index + 1, g, index, len - index);
                g[count = len] = null;
            }
        }
    }

    public void removeGlyph(Glyph character) {
        synchronized(monitor)
        {
            int len;
            int index;
            Glyph[] g;
            if((index = indexOf(character, g = glyphs, len = count)) >= 0)
            {
                g[index].removed = true;
                if(index < --len) Array.copy(g, index + 1, g, index, len - index);
                g[count = len] = null;
            }
        }
    }

    public void setBold(boolean bold) {
        synchronized(monitor)
        {
            attributes = bold ? attributes | (1L << 56) : attributes & ~(1L << 56);
        }
    }

    public void setItalic(boolean italic) {
        synchronized(monitor)
        {
            attributes = italic ? attributes | (1L << 57) : attributes & ~(1L << 57);
        }
    }

    public void setHeight(int height) {
        if(height < MIN_UNSIGNED_VALUE) height = MIN_UNSIGNED_VALUE;
        if(height > MAX_UNSIGNED_VALUE) height = MAX_UNSIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 48) | (long) height << 48;
        }
    }

    public void setBaselineHeight(int baselineHeight) {
        if(baselineHeight < MIN_UNSIGNED_VALUE) baselineHeight = MIN_UNSIGNED_VALUE;
        if(baselineHeight > MAX_UNSIGNED_VALUE) baselineHeight = MAX_UNSIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 40) | (long) baselineHeight << 40;
        }
    }

    public void setSmallLettersHeight(int smallLettersHeight) {
        if(smallLettersHeight < MIN_UNSIGNED_VALUE) smallLettersHeight = MIN_UNSIGNED_VALUE;
        if(smallLettersHeight > MAX_UNSIGNED_VALUE) smallLettersHeight = MAX_UNSIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 32) | (long) smallLettersHeight << 32;
        }
    }

    public void setCapitalLettersHeight(int capitalLettersHeight) {
        if(capitalLettersHeight < MIN_UNSIGNED_VALUE) capitalLettersHeight = MIN_UNSIGNED_VALUE;
        if(capitalLettersHeight > MAX_UNSIGNED_VALUE) capitalLettersHeight = MAX_UNSIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 24) | (long) capitalLettersHeight << 24;
        }
    }

    public void setStrikeoutLinePosition(int strikeoutLinePosition) {
        if(strikeoutLinePosition < MIN_SIGNED_VALUE) strikeoutLinePosition = MIN_SIGNED_VALUE;
        if(strikeoutLinePosition > MAX_SIGNED_VALUE) strikeoutLinePosition = MAX_SIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 8) | (long) (strikeoutLinePosition & 0xff) << 8;
        }
    }

    public void setUnderscoreLinePosition(int underscoreLinePosition) {
        if(underscoreLinePosition < MIN_SIGNED_VALUE) underscoreLinePosition = MIN_SIGNED_VALUE;
        if(underscoreLinePosition > MAX_SIGNED_VALUE) underscoreLinePosition = MAX_SIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~(0xffL << 16) | (long) (underscoreLinePosition & 0xff) << 16;
        }
    }

    public void setLinesWidth(int linesWidth) {
        if(linesWidth < MIN_UNSIGNED_VALUE) linesWidth = MIN_UNSIGNED_VALUE;
        if(linesWidth > MAX_UNSIGNED_VALUE) linesWidth = MAX_UNSIGNED_VALUE;
        synchronized(monitor)
        {
            attributes = attributes & ~0xffL | (long) linesWidth;
        }
    }

    public boolean isBold() {
        return (attributes & (1L << 56)) != 0L;
    }

    public boolean isItalic() {
        return (attributes & (1L << 57)) != 0L;
    }

    public int getHeight() {
        return (int) (attributes >> 48) & 0xff;
    }

    public int getBaselineHeight() {
        return (int) (attributes >> 40) & 0xff;
    }

    public int getSmallLettersHeight() {
        return (int) (attributes >> 32) & 0xff;
    }

    public int getCapitalLettersHeight() {
        return (int) (attributes >> 24) & 0xff;
    }

    public int getStrikeoutLinePosition() {
        return (byte) (attributes >> 8);
    }

    public int getUnderscoreLinePosition() {
        return (byte) (attributes >> 16);
    }

    public int getLinesWidth() {
        int result;
        return (result = (int) attributes & 0xff) == 0 ? 1 : result;
    }

    public int getGlyphsCount() {
        return count;
    }

    public String getLastFileName() {
        return lastFileName;
    }

    public Glyph getGlyph(int characterCode) {
        Glyph result;
        synchronized(monitor)
        {
            int index;
            Glyph[] g;
            result = (index = indexOf(characterCode, g = glyphs, count)) >= 0 ? g[index] : null;
        }
        return result;
    }

    public Glyph addGlyph(int characterCode) {
        int error = 0;
        Glyph result;
        synchronized(monitor)
        {
            label0:
            {
                int len;
                Glyph[] g;
                if(indexOf(characterCode, g = glyphs, len = count) >= 0)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                if(len == g.length) Array.copy(g, 0, g = glyphs = new Glyph[(len << 1) + 1], 0, len);
                result = this.new Glyph(false, characterCode, 0, 0, new byte[15]);
                for(int i = 0; i < len; i++) if(g[i].getCharacterCode() > characterCode)
                {
                    Array.copy(g, i, g, i + 1, len++ - i);
                    g[i] = result;
                    count = len;
                    break label0;
                }
                g[len++] = result;
                count = len;
            }
        }
        if(error == 1)
        {
            throw new CharacterCodeExistsException((new StringBuilder()).append("UnicodeRasterFont.addGlyph: глиф с кодом ").append(characterCode).append(" уже существует в этом шрифте.").toString());
        }
        return result;
    }

    public Enumeration glyphs() {
        return this.new Enumerator();
    }
}
