{
    UFonts содержит класс для работы с экранными шрифтами, которые поддерживают
    Уникод (.ufn). Поддерживается: получение базовой информации о шрифте
    (размер, начертание), вывод отдельных символов и символьных строк.

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

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

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

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

unit UFonts;

{$MODE DELPHI}

interface

uses
    Lang,
    IOStreams,
    FileIO,
    Images;

{%region public }
type
    UnicodeFont = class(_Object)
    strict private
        style: int;
        height: int;
        baseLineHeight: int;
        underlinePos: int;
        strikeoutPos: int;
        linesWidth: int;
        codesCount: int;
        widths: byte_Array1d;
        codes: int_Array1d;
        offsets: int_Array1d;
        stream: DataInput;
        name: UnicodeString;
        function indexOfCharacter(code: int): int;
        procedure seek(pos: long);
    public
        constructor create(const fontName, fileName: AnsiString); overload;
        constructor create(const fontName, fileName: UnicodeString); overload;
        function isCharacterExists(code: int): boolean; virtual;
        function isBold(): boolean; virtual;
        function isItalic(): boolean; virtual;
        function isEmpty(): boolean; virtual;
        function getStyle(): int; virtual;
        function getHeight(): int; virtual;
        function getBaseLineHeight(): int; virtual;
        function getName(): UnicodeString; virtual;
        function getCharacterWidth(code: int): int; virtual;
        function getCharactersWidth(const chars: AnsiString): int; overload; virtual;
        function getCharactersWidth(const chars: UnicodeString): int; overload; virtual;
        function getCharactersWidth(const chars: int_Array1d): int; overload; virtual;
        function getCharactersWidth(const chars: int_Array1d; offset, length: int): int; overload; virtual;
        function drawCharacter(listener: GraphicListener; code: int; x, y, argb: int; underline, strikeout: boolean): int; virtual;
        function drawCharacters(listener: GraphicListener; const chars: AnsiString; x, y, argb: int; underline, strikeout: boolean): int; overload; virtual;
        function drawCharacters(listener: GraphicListener; const chars: UnicodeString; x, y, argb: int; underline, strikeout: boolean): int; overload; virtual;
        function drawCharacters(listener: GraphicListener; const chars: int_Array1d; x, y, argb: int; underline, strikeout: boolean): int; overload; virtual;
        function drawCharacters(listener: GraphicListener; const chars: int_Array1d; offset, length: int; x, y, argb: int; underline, strikeout: boolean): int; overload; virtual;
    end;
{%endregion}

implementation

{%region UnicodeFont }
    function UnicodeFont.indexOfCharacter(code: int): int;
    var
        left: int;
        right: int;
        c: int;
        r: int;
    begin
        result := -1;
        left := 0;
        right := codesCount - 1;
        while left <= right do begin
            c := (left + right) shr 1;
            r := code - codes[c];
            if r > 0 then
                left := c + 1
            else if r < 0 then
                right := c - 1
            else begin
                result := c;
                break;
            end;
        end;
    end;

    procedure UnicodeFont.seek(pos: long);
    var
        stream: Input;
    begin
        stream := self.stream;
        stream.seek(pos - stream.position());
    end;

    constructor UnicodeFont.create(const fontName, fileName: AnsiString);
    begin
        create(toUTF16String(fontName), toUTF16String(fileName));
    end;

    constructor UnicodeFont.create(const fontName, fileName: UnicodeString);
    var
        i: int;
        codesCount: int;
        widths: byte_Array1d;
        codes: int_Array1d;
        offsets: int_Array1d;
        f: FileInputStream;
        stream: DataInput;
    begin
        inherited create();
        f := FileInputStream.create(fileName);
        if f.isInvalidHandle() then begin
            f.free();
            exit;
        end;
        stream := DataInputStream.create(f);
        if stream.readInt() <> $55464e54 then begin
            exit;
        end;
        self.style := stream.readUnsignedByte() and $03;
        self.height := stream.readUnsignedByte();
        self.baseLineHeight := stream.readUnsignedByte();
        stream.seek(2);
        self.underlinePos := stream.readByte();
        self.strikeoutPos := stream.readByte();
        self.linesWidth := stream.readUnsignedByte();
        codesCount := stream.readIntLE();
        if (codesCount < 0) or (self.height = 0) then begin
            self.codesCount := 0;
            exit;
        end;
        self.codesCount := codesCount;
        if self.linesWidth = 0 then begin
            self.linesWidth := 1;
        end;
        widths := byte_Array1d_create(codesCount);
        codes := int_Array1d_create(codesCount);
        offsets := int_Array1d_create(codesCount);
        for i := 0 to codesCount - 1 do begin
            codes[i] := stream.readIntLE();
            offsets[i] := stream.readIntLE();
        end;
        self.widths := widths;
        self.codes := codes;
        self.offsets := offsets;
        self.stream := stream;
        if System.length(fontName) > 0 then begin
            self.name := fontName;
        end else begin
            self.name := 'Default';
        end;
        for i := 0 to codesCount - 1 do begin
            seek(zeroExtend(offsets[i]));
            widths[i] := byte(stream.readByte());
        end;
    end;

    function UnicodeFont.isCharacterExists(code: int): boolean;
    begin
        if stream <> nil then begin
            result := indexOfCharacter(code) >= 0;
        end else begin
            result := false;
        end;
    end;

    function UnicodeFont.isBold(): boolean;
    begin
        result := (style and $01) <> 0;
    end;

    function UnicodeFont.isItalic(): boolean;
    begin
        result := (style and $02) <> 0;
    end;

    function UnicodeFont.isEmpty(): boolean;
    begin
        result := codesCount = 0;
    end;

    function UnicodeFont.getStyle(): int;
    begin
        result := style;
    end;

    function UnicodeFont.getHeight(): int;
    begin
        result := height;
    end;

    function UnicodeFont.getBaseLineHeight(): int;
    begin
        result := baseLineHeight;
    end;

    function UnicodeFont.getName(): UnicodeString;
    begin
        result := name;
    end;

    function UnicodeFont.getCharacterWidth(code: int): int;
    var
        index: int;
    begin
        index := indexOfCharacter(code);
        if index < 0 then begin
            index := indexOfCharacter(0);
        end;
        if index >= 0 then begin
            result := widths[index] and $ff;
        end else begin
            result := 0;
        end;
    end;

    function UnicodeFont.getCharactersWidth(const chars: AnsiString): int;
    begin
        result := getCharactersWidth(getCharCodes(chars));
    end;

    function UnicodeFont.getCharactersWidth(const chars: UnicodeString): int;
    begin
        result := getCharactersWidth(getCharCodes(chars));
    end;

    function UnicodeFont.getCharactersWidth(const chars: int_Array1d): int;
    begin
        result := getCharactersWidth(chars, 0, System.length(chars));
    end;

    function UnicodeFont.getCharactersWidth(const chars: int_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := offset + length;
        len := System.length(chars);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('UnicodeFont.getCharactersWidth: индекс элемента массива выходит из диапазона.');
        end;
        result := 0;
        for i := offset to lim - 1 do begin
            inc(result, getCharacterWidth(chars[i]));
        end;
    end;

    function UnicodeFont.drawCharacter(listener: GraphicListener; code: int; x, y, argb: int; underline, strikeout: boolean): int;
    var
        b: int;
        i: int;
        j: int;
        ix: int;
        iy: int;
        iw: int;
        ih: int;
        xl: int;
        yl: int;
        pos: int;
        bytes: int;
        index: int;
        image: byte_Array1d;
        stream: DataInput;
    begin
        stream := self.stream;
        if (listener = nil) or (stream = nil) then begin
            result := 0;
            exit;
        end;
        index := indexOfCharacter(code);
        if index < 0 then begin
            index := indexOfCharacter(0);
            if index < 0 then begin
                result := 0;
                exit;
            end;
        end;
        result := widths[index] and $ff;
        seek(zeroExtend(offsets[index] + 1));
        ix := stream.readByte();
        iy := stream.readByte();
        iw := stream.readUnsignedByte();
        ih := stream.readUnsignedByte();
        bytes := iw div 8;
        if (iw and $07) <> 0 then begin
            inc(bytes);
        end;
        image := byte_Array1d_create(bytes * ih);
        pos := 0;
        b := 0;
        for j := 1 to ih do for i := 0 to iw - 1 do begin
            if (i and $07) = 0 then begin
                b := stream.readUnsignedByte();
                image[pos] := b;
                inc(pos);
            end else begin
                b := b shr 1;
            end;
            if (b and 1) <> 0 then begin
                listener.putPixel(x + ix + i, y - iy - j, argb);
            end;
        end;
        if not underline and not strikeout then begin
            exit;
        end;
        for j := 0 to linesWidth - 1 do begin
            if underline then begin
                yl := underlinePos - j;
                for xl := 0 to result - 1 do begin
                    if (xl >= ix) and (xl < ix + iw) and (yl >= iy) and (yl < iy + ih) then begin
                        index := (((yl - iy) * bytes) shl 3) + (xl - ix);
                        if (image[index shr 3] and (1 shl (index and $07))) = 0 then begin
                            listener.putPixel(x + xl, y - yl - 1, argb);
                        end;
                    end else begin
                        listener.putPixel(x + xl, y - yl - 1, argb);
                    end;
                end;
            end;
            if strikeout then begin
                yl := strikeoutPos - j;
                for xl := 0 to result - 1 do begin
                    if (xl >= ix) and (xl < ix + iw) and (yl >= iy) and (yl < iy + ih) then begin
                        index := (((yl - iy) * bytes) shl 3) + (xl - ix);
                        if (image[index shr 3] and (1 shl (index and $07))) = 0 then begin
                            listener.putPixel(x + xl, y - yl - 1, argb);
                        end;
                    end else begin
                        listener.putPixel(x + xl, y - yl - 1, argb);
                    end;
                end;
            end;
        end;
    end;

    function UnicodeFont.drawCharacters(listener: GraphicListener; const chars: AnsiString; x, y, argb: int; underline, strikeout: boolean): int;
    begin
        result := drawCharacters(listener, getCharCodes(chars), x, y, argb, underline, strikeout);
    end;

    function UnicodeFont.drawCharacters(listener: GraphicListener; const chars: UnicodeString; x, y, argb: int; underline, strikeout: boolean): int;
    begin
        result := drawCharacters(listener, getCharCodes(chars), x, y, argb, underline, strikeout);
    end;

    function UnicodeFont.drawCharacters(listener: GraphicListener; const chars: int_Array1d; x, y, argb: int; underline, strikeout: boolean): int;
    begin
        result := drawCharacters(listener, chars, 0, System.length(chars), x, y, argb, underline, strikeout);
    end;

    function UnicodeFont.drawCharacters(listener: GraphicListener; const chars: int_Array1d; offset, length: int; x, y, argb: int; underline, strikeout: boolean): int;
    var
        lim: int;
        len: int;
        i: int;
        w: int;
    begin
        lim := offset + length;
        len := System.length(chars);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('UnicodeFont.drawCharacters: индекс элемента массива выходит из диапазона.');
        end;
        result := 0;
        for i := offset to lim - 1 do begin
            w := drawCharacter(listener, chars[i], x, y, argb, underline, strikeout);
            inc(x, w);
            inc(result, w);
        end;
    end;
{%endregion}

end.

