matrixFont/source/symbol.pas
riva 8f95708c07 refactoring & formatting:
- symbol.pas:
    class TSymbol       -> TMatrixChar
    type  TSymbolField  -> TCharCanvas
    type  TPSymbolField -> TPCharCanvas
    field FSymbol       -> FCharCanvas
    prop  Symbol        -> CharCanvas
    proc  LoadSymbol    -> LoadChar

 - font.pas:
    class TFont         -> TMatrixFont
    type  TSymbolField  -> TCharCanvas
    type  TFontSet      -> TMxCharArray
    field FSymbol       -> FCharArray

 - fm_gen.pas:
    var   FontSet       -> mxFont
2024-05-31 15:10:05 +03:00

1231 lines
36 KiB
ObjectPascal
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

unit symbol;
{$mode objfpc}{$H+}
interface
uses
Classes, Graphics, SysUtils, Clipbrd, LazUTF8, LazFileUtils,
LCLType, GraphUtil, Math,
u_encodings, u_utilities, u_helpers;
const
EXCHANGE_BUFFER_TYPE_ID = 'matrixFontApp'; // ID буфера обмена
CHAR_IMPORT_FORMATS = 'png,jpg,jpeg,bmp,pnm,pgm,ppm,pbm,tif,tiff,ico';
type
TNumberView = (nvHEX, nvBIN, nvDEC);
TFontType = (ftMONOSPACE, ftPROPORTIONAL);
TEmptyBit = (emBIT_0, emBIT_1);
TCharCanvas = array of array of Boolean;
TPCharCanvas = ^TCharCanvas;
//TPBitmap = ^TBitmap;
TCanOptimize = (coUp, coDown, coLeft, coRight);
TDirection = (dirUp, dirDown, dirLeft, dirRight);
TBorder = (brUp, brDown, brLeft, brRight);
TMirror = (mrHorizontal, mrVertical);
TClipboardAction = (cbCut, cbCopy, cbPaste);
TPasteMode = (pmNorm, pmOr, pmXor, pmAnd);
TPixelAction = (paSet, paClear, paInvert, paNone);
{ TMatrixChar }
TMatrixChar = class
private
// ================================ Поля ===================================
FCharCanvas: TCharCanvas; // холст символа
// поля внешнего вида и параметров символа
FWidth: Integer; // поле - ширина символа в пикселях
FHeight: Integer; // поле - высота символа в пикселях
FHeightInPixels: Integer; // поле - высота изображения символа в пикселях
FWidthInPixels: Integer; // поле - ширина изображения символа в пикселях
FGridStep: Integer; // поле - шаг сетки в пикселях
FGridThickness: Integer; // поле - толщина линий сетки
FGridColor: Integer; // поле - цвет сетки
FBackgroundColor: Integer; // поле - цвет фона
FActiveColor: Integer; // поле - цвет нарисованного элемента
FShowGrid: Boolean; // поле - флаг видимости сетки
FGridChessBackground: Boolean; // поле - флаг отображения сетки в виде шахматного фона
FCurrentPixel: TPoint; // поле - координаты текущего нарисованного пикселя
// поля модификации символа
FShiftRollover: Boolean; // поле - флаг циклического режима сдвига
// поля истории изменений
FHistory: array of TCharCanvas; // поле - массив для истории изменений
FHistoryPosition: Integer; // поле - текущее изменение символа
FHistoryEmpty: Boolean; // поле - флаг пустоты истории изменений
FHistoryNoRedo: Boolean; // поле - флаг невозможности отмены
// поля для работы с буфером обмена
FCopyBufferEmpty: Boolean; // поле - флаг заполненности буфера обмена
// ========================= Внутренние методы =============================
// Методы чтения и записи свойств
function GetCopyBufferEmpty: Boolean;
procedure SetGridThickness(AValue: Integer);
procedure SetWidth(AWidth: Integer);
procedure SetHeight(AHeight: Integer);
procedure SetGridStep(AGridStep: Integer);
//==========================================================================
public
// =================================== Методы ==============================
// работа с пикселем (установка/очистка/инверсия)
procedure PixelAction(AX, AY: Integer; AAction: TPixelAction);
// очистить символ
procedure Clear;
// инвертировать изображение символа
procedure Invert;
// отобразить символ
procedure Mirror(MirrorDirection: TMirror);
// сдвиг символа
procedure Shift(Direction: TDirection);
// прижать символ к краю
procedure Snap(Border: TBorder);
// центрирование символа
procedure Center(AVertical: Boolean);
// поворот символа
procedure Rotate(AClockWise: Boolean);
// вывести изображение символа в битмап
procedure Draw(bmp: TBitmap);
// вывести изображение предпросмотра в битмап
procedure DrawPreview(bmp: TBitmap; Transparency: Boolean = True; ColorBG: TColor =
$FFFFFF; ColorActive: TColor = 0);
// генерировать код символа
function GenerateCode(fnScanColsFirst, fnScanColsToRight, fnScanRowsToDown,
fnNumbersInversion: Boolean; fnNumbersView: TNumberView; fnEmptyBits: TEmptyBit;
fnFontType: TFontType; fnNumbersBits: Integer): String;
// очистить историю изменений
procedure ClearChanges;
// сохранить текущую правку символа в историю
procedure SaveChange;
// отменить одну правку с конца истории
procedure UndoChange;
// повторить отмененную ранее правку
procedure RedoChange;
// увеличение масштаба изображения символа (+10%)
procedure ZoomIn;
// уменьшение масштаба изображения символа (-10%)
procedure ZoomOut;
// масштаб изображения символа: вписанный в заданную область
procedure ZoomFitToArea(Width, Height: Integer);
// импорт символа из системного шрифта для растеризации
procedure Import(Font: TFont; Index: Integer; AEncoding: String);
// импорт изображения символа из файла PNG
procedure ImportImage(AFilename: String; ATreshold: Byte = 128);
// получение ширины символа
function GetCharWidth: Integer;
// получение высоты символа
function GetCharHeight: Integer;
// загрузка символа целиком
procedure LoadChar(ASymbol: TPCharCanvas);
// изменение размеров холста символа
procedure ChangeSize(Up, Down, Left, Right: Integer; Crop: Boolean);
// определение возможности усечь символ: результат - кол-во пустых строк/стоблцов
function CanOptimize(Direction: TCanOptimize): Integer;
// операции с буфером обмена
procedure ClipboardAction(Action: TClipboardAction; Mode: TPasteMode = pmNorm);
//==========================================================================
//======================== Конструкторы и деструкторы ======================
constructor Create;
destructor Destroy; override;
//==========================================================================
// =========================== Свойства ====================================
// свойства внешнего вида и параметров символа
// ширина символа в пикселях
property Width: Integer read FWidth write SetWidth;
// высота символа в пикселях
property Height: Integer read FHeight write SetHeight;
// ширина изображения символа в пикселях
property WidthInPixels: Integer read FWidthInPixels;
// высота изображения символа в пикселях
property HeightInPixels: Integer read FHeightInPixels;
// шаг сетки в пикселях
property GridStep: Integer read FGridStep write SetGridStep;
// толщина линий сетки
property GridThickness: Integer read FGridThickness write SetGridThickness;
// цвет сетки
property GridColor: Integer read FGridColor write FGridColor;
// флаг отображения сетки в виде шахматного фона
property GridChessBackground: Boolean read FGridChessBackground write FGridChessBackground;
// цвет фона
property BackgroundColor: Integer read FBackgroundColor write FBackgroundColor;
// цвет нарисованного элемента
property ActiveColor: Integer read FActiveColor write FActiveColor;
// флаг видимости сетки
property ShowGrid: Boolean read FShowGrid write FShowGrid;
// координаты текущего нарисованного пикселя
property CurrentPixel: TPoint read FCurrentPixel;
//--------------------------------------------------------------------------
// свойства истории изменений
// флаг пустоты истории изменений
property HistoryEmpty: Boolean read FHistoryEmpty;
// флаг невозможности отмены
property HistoryNoRedo: Boolean read FHistoryNoRedo;
//--------------------------------------------------------------------------
// флаг заполненности буфера обмена
property CopyBufferEmpty: Boolean read GetCopyBufferEmpty;
//--------------------------------------------------------------------------
// свойства модификации символа
// флаг циклического режима сдвига
property ShiftRollover: Boolean read FShiftRollover write FShiftRollover;
//--------------------------------------------------------------------------
property CharCanvas: TCharCanvas read FCharCanvas;
end;
implementation
{ TMatrixChar }
procedure TMatrixChar.SetWidth(AWidth: Integer);
begin
FWidth := AWidth;
SetLength(FCharCanvas, FWidth, FHeight);
SetGridStep(FGridStep);
end;
function TMatrixChar.GetCopyBufferEmpty: Boolean;
begin
try
FCopyBufferEmpty := Clipboard.FindFormatID(EXCHANGE_BUFFER_TYPE_ID) = 0;
except
FCopyBufferEmpty := True;
end;
Result := FCopyBufferEmpty;
end;
procedure TMatrixChar.SetGridThickness(AValue: Integer);
begin
FGridThickness := AValue;
SetGridStep(FGridStep);
end;
procedure TMatrixChar.SetHeight(AHeight: Integer);
begin
FHeight := AHeight;
SetLength(FCharCanvas, FWidth, FHeight);
SetGridStep(FGridStep);
end;
procedure TMatrixChar.SetGridStep(AGridStep: Integer);
begin
FGridStep := AGridStep;
FWidthInPixels := FWidth * FGridStep + FGridThickness;
FHeightInPixels := FHeight * FGridStep + FGridThickness;
end;
// работа с пикселем (установка/очистка/инверсия)
procedure TMatrixChar.PixelAction(AX, AY: Integer; AAction: TPixelAction);
var
state: Boolean;
begin
state := AAction = paSet;
if (AX >= 0) and (AY >= 0) and (AX < FWidth) and (AY < FHeight) then
FCharCanvas[AX, AY] := state or (AAction = paInvert) and not FCharCanvas[AX, AY];
end;
// очистить символ
procedure TMatrixChar.Clear;
var
w, h: Integer;
begin
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
FCharCanvas[w, h] := False;
end;
// инвертировать изображение символа
procedure TMatrixChar.Invert;
var
w, h: Integer;
begin
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
FCharCanvas[w, h] := not FCharCanvas[w, h];
end;
// отобразить символ
procedure TMatrixChar.Mirror(MirrorDirection: TMirror);
var
w, h: Integer;
a: Boolean;
begin
case MirrorDirection of
// отобразить символ горизонтально
mrHorizontal:
for h := 0 to FHeight - 1 do
for w := 0 to (FWidth - 1) div 2 do
begin
a := FCharCanvas[w, h];
FCharCanvas[w, h] := FCharCanvas[FWidth - 1 - w, h];
FCharCanvas[FWidth - 1 - w, h] := a;
end;
// отобразить символ вертикально
mrVertical:
for h := 0 to (FHeight - 1) div 2 do
for w := 0 to FWidth - 1 do
begin
a := FCharCanvas[w, h];
FCharCanvas[w, h] := FCharCanvas[w, FHeight - 1 - h];
FCharCanvas[w, FHeight - 1 - h] := a;
end;
end;
end;
// сдвиг символа
procedure TMatrixChar.Shift(Direction: TDirection);
var
w, h: Integer;
a: array of Boolean;
begin
case Direction of
// сдвиг символа вверх
dirUp:
begin
SetLength(a, FWidth);
for w := 0 to FWidth - 1 do
begin
a[w] := FCharCanvas[w, 0];
for h := 0 to FHeight - 2 do
FCharCanvas[w, h] := FCharCanvas[w, h + 1];
if FShiftRollover then
FCharCanvas[w, FHeight - 1] := a[w]
else
FCharCanvas[w, FHeight - 1] := False;
end;
end;
// сдвиг символа вниз
dirDown:
begin
SetLength(a, FWidth);
for w := 0 to FWidth - 1 do
begin
a[w] := FCharCanvas[w, FHeight - 1];
for h := FHeight - 1 downto 1 do
FCharCanvas[w, h] := FCharCanvas[w, h - 1];
if FShiftRollover then
FCharCanvas[w, 0] := a[w]
else
FCharCanvas[w, 0] := False;
end;
end;
// сдвиг символа влево
dirLeft:
begin
SetLength(a, FHeight);
for h := 0 to FHeight - 1 do
begin
a[h] := FCharCanvas[0, h];
for w := 0 to FWidth - 2 do
FCharCanvas[w, h] := FCharCanvas[w + 1, h];
if FShiftRollover then
FCharCanvas[FWidth - 1, h] := a[h]
else
FCharCanvas[FWidth - 1, h] := False;
end;
end;
// сдвиг символа вправо
dirRight:
begin
SetLength(a, FHeight);
for h := 0 to FHeight - 1 do
begin
a[h] := FCharCanvas[FWidth - 1, h];
for w := FWidth - 1 downto 1 do
FCharCanvas[w, h] := FCharCanvas[w - 1, h];
if FShiftRollover then
FCharCanvas[0, h] := a[h]
else
FCharCanvas[0, h] := False;
end;
end;
end;
a := nil;
end;
// прижать символ к краю
procedure TMatrixChar.Snap(Border: TBorder);
var
w, h: Integer;
empty: Boolean;
begin
case Border of
// прижать символ к верхнему краю
brUp:
for h := 0 to FHeight - 1 do
begin
empty := True;
for w := 0 to FWidth - 1 do
if FCharCanvas[w, 0] = True then
begin
empty := False;
break;
end;
if empty then
Shift(dirUp)
else
break;
end;
// прижать символ к нижнему краю
brDown:
for h := 0 to FHeight - 1 do
begin
empty := True;
for w := 0 to FWidth - 1 do
if FCharCanvas[w, FHeight - 1] = True then
begin
empty := False;
break;
end;
if empty then
Shift(dirDown)
else
break;
end;
// прижать символ к левому краю
brLeft:
for w := 0 to FWidth - 1 do
begin
empty := True;
for h := 0 to FHeight - 1 do
if FCharCanvas[0, h] = True then
begin
empty := False;
break;
end;
if empty then
Shift(dirLeft)
else
break;
end;
// прижать символ к правому краю
brRight:
for w := 0 to FWidth - 1 do
begin
empty := True;
for h := 0 to FHeight - 1 do
if FCharCanvas[FWidth - 1, h] = True then
begin
empty := False;
break;
end;
if empty then
Shift(dirRight)
else
break;
end;
end;
end;
// центрирование символа
procedure TMatrixChar.Center(AVertical: Boolean);
var
i, steps: Integer;
begin
if AVertical then
begin
Snap(brUp);
steps := (FHeight - GetCharHeight) div 2;
for i := 0 to steps - 1 do
Shift(dirDown);
end
else
begin
Snap(brLeft);
steps := (FWidth - GetCharWidth) div 2;
for i := 0 to steps - 1 do
Shift(dirRight);
end;
end;
// поворот символа
procedure TMatrixChar.Rotate(AClockWise: Boolean);
var
s, h, w: Integer;
tmp: TCharCanvas;
begin
s := Min(FWidth, FHeight);
SetLength(tmp, s, s);
s -= 1;
// rotate to temp var
for h := 0 to s do
for w := 0 to s do
tmp[
AClockWise.Select(s - h, h),
AClockWise.Select(w, s - w)] := FCharCanvas[w, h];
// copy from temp var
Clear;
for h := 0 to s do
for w := 0 to s do
FCharCanvas[w, h] := tmp[w, h];
end;
// вывести изображение символа в битмап
procedure TMatrixChar.Draw(bmp: TBitmap);
var
start_x, start_y, end_x, end_y: Integer;
w, h: Integer;
begin
with bmp.Canvas do
begin
Brush.Color := FBackgroundColor;
Clear;
Clear;
end;
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
with bmp.Canvas do
begin
start_x := FGridThickness div 2 + 1 + w * FGridStep;
start_y := FGridThickness div 2 + 1 + h * FGridStep;
end_x := start_x + FGridStep;
end_y := start_y + FGridStep;
if FCharCanvas[w, h] then
Brush.Color := FActiveColor
else
if FShowGrid and FGridChessBackground and ((w + h) mod 2 = 0) then
Brush.Color := FGridColor
else
Brush.Color := FBackgroundColor;
FillRect(start_x - 1, start_y - 1, end_x, end_y);
if not FGridChessBackground and (FGridThickness < 1) then
FGridThickness := 1;
if FShowGrid and (FGridThickness > 0) then
begin
Pen.JoinStyle := pjsMiter; // квадратные углы концов линий фигур
Pen.Width := FGridThickness;
Pen.Color := FGridColor;
Rectangle(start_x - 1, start_y - 1, end_x, end_y);
end;
end;
end;
// вывести изображение предпросмотра в битмап
procedure TMatrixChar.DrawPreview(bmp: TBitmap; Transparency: Boolean = True;
ColorBG: TColor = $FFFFFF; ColorActive: TColor = 0);
var
w, h: Integer;
begin
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
with bmp.Canvas do
begin
if FCharCanvas[w, h] then
Brush.Color := ColorActive
else
Brush.Color := ColorBG;
Pixels[w, h] := Brush.Color;
end;
if Transparency then
begin
bmp.TransparentColor := ColorBG;
bmp.Transparent := True;
end;
end;
// генерировать код символа
function TMatrixChar.GenerateCode(
fnScanColsFirst, // поле - флаг очередности сканирования: столбцы-строки
fnScanColsToRight, // поле - флаг направления сканирования столбцов
fnScanRowsToDown, // поле - флаг направления сканирования строк
fnNumbersInversion: Boolean; // поле - битовая инверсия представления выходных чисел
fnNumbersView: TNumberView; // поле - настройка представления выходных чисел
fnEmptyBits: TEmptyBit; // поле - настройка заполнения пустых разрядов
fnFontType: TFontType; // поле - тип шрифта
fnNumbersBits: Integer // поле - разрядность выходных чисел
): String;
function create_number(stb: String; fnNView: TNumberView): String;
var
i, max: Integer;
number: QWord;
begin
max := trunc(fnNumbersBits / 10 * 3);
number := StrToQWord('%' + stb);
case fnNView of
nvBIN:
Result := '0b' + stb;
nvHEX:
Result := '0x' + IntToHex(number, fnNumbersBits div 4);
nvDEC:
begin
Result := IntToStr(number);
if number > 0 then
number := max - trunc(ln(number) / ln(10))
else
number := max;
if fnNView = nvDEC then
for i := 1 to number do
Result := ' ' + Result;
end;
end;
end;
var
ch: Char;
x, y: Integer;
x_end, y_end: Integer;
w_st, h_st: Integer;
str_binary: String;
str: String = '';
bit1, bit0: Char;
element: Boolean;
begin
if fnScanColsFirst then
begin
x_end := FWidth;
y_end := FHeight;
end
else
begin
x_end := FHeight;
y_end := FWidth;
end;
if fnScanColsToRight then
w_st := 0 // сканирование столбцов слева направо
else
w_st := FWidth - 1; // сканирование столбцов справа налево
if fnScanRowsToDown then
h_st := 0 // сканирование строк снизу вверх
else
h_st := FHeight - 1; // сканирование строк сверху вниз
bit0 := '0';
bit1 := '1';
if fnNumbersInversion then
begin
bit0 := '1';
bit1 := '0';
end;
for x := 0 to x_end - 1 do
begin
str_binary := '';
if (Length(str) > 0) then
str := str + ', ';
for y := 0 to y_end - 1 do
begin
if fnScanColsFirst then
element := FCharCanvas[abs(w_st - x), abs(h_st - y)]
else
element := FCharCanvas[abs(w_st - y), abs(h_st - x)];
if element then
str_binary := bit1 + str_binary
else
str_binary := bit0 + str_binary;
if (y_end > fnNumbersBits) and ((y + 1) mod fnNumbersBits = 0) and
(y + 1 <> y_end) then
begin
str := str + create_number(str_binary, fnNumbersView) + ', ';
str_binary := '';
end;
end;
Inc(y);
while (y mod fnNumbersBits <> 0) do // дополнение пустых разрядов
begin
if fnEmptyBits = emBIT_0 then
ch := '0'
else
ch := '1';
//str_binary := str_binary + ch; // побитовое выравнивание по левой стороне
str_binary := ch + str_binary; // побитовое выравнивание по правой стороне
Inc(y);
end;
str := str + create_number(str_binary, fnNumbersView);
end;
if fnFontType = ftPROPORTIONAL then
Result := create_number(binStr(GetCharWidth, fnNumbersBits), nvDEC) + ', /*N*/ ' + str
else
Result := str;
end;
// очистить историю изменений
procedure TMatrixChar.ClearChanges;
begin
FHistoryPosition := 1;
SaveChange;
FHistoryEmpty := True;
FHistoryNoRedo := True;
end;
// сохранить текущую правку символа в историю
procedure TMatrixChar.SaveChange;
begin
FHistoryPosition := FHistoryPosition + 1;
SetLength(FHistory, FHistoryPosition);
FHistory[FHistoryPosition - 1] := FCharCanvas;
FHistoryEmpty := False;
FHistoryNoRedo := True;
SetLength(FCharCanvas, FWidth, FHeight);
end;
// отменить одну правку с конца истории
procedure TMatrixChar.UndoChange;
begin
if FHistoryEmpty then Exit;
FHistoryPosition := FHistoryPosition - 1;
FCharCanvas := FHistory[FHistoryPosition - 1];
if FHistoryPosition = 2 then
FHistoryEmpty := True;
FHistoryNoRedo := False;
SetLength(FCharCanvas, FWidth, FHeight);
end;
// повторить отмененную ранее правку
procedure TMatrixChar.RedoChange;
begin
if FHistoryNoRedo then Exit;
if FHistoryPosition < high(FHistory) + 1 then
begin
FHistoryEmpty := False;
FCharCanvas := FHistory[FHistoryPosition];
FHistoryPosition := FHistoryPosition + 1;
FHistoryNoRedo := FHistoryPosition = (high(FHistory) + 1);
end;
SetLength(FCharCanvas, FWidth, FHeight);
end;
// увеличение масштаба изображения символа (+10%)
procedure TMatrixChar.ZoomIn;
var
tmp: Integer;
begin
tmp := round(FGridStep * 1.1);
if tmp = FGridStep then
SetGridStep(FGridStep + 1)
else
if FGridStep < 150 then
SetGridStep(tmp);
end;
// уменьшение масштаба изображения символа (-10%)
procedure TMatrixChar.ZoomOut;
var
tmp: Integer;
begin
tmp := round(FGridStep / 1.1);
if tmp = FGridStep then
Dec(tmp);
if tmp < FGridThickness + 1 then
SetGridStep(FGridThickness + 1)
else
SetGridStep(tmp);
end;
// масштаб изображения символа: вписанный в заданную область
procedure TMatrixChar.ZoomFitToArea(Width, Height: Integer);
begin
Width := Width - FGridThickness;
Height := Height - FGridThickness;
FWidthInPixels := Width div FWidth; // FWidthInPixels as temp var
FHeightInPixels := Height div FHeight; // FHeightInPixels as temp var
if FWidthInPixels < FHeightInPixels then
SetGridStep(FWidthInPixels)
else
SetGridStep(FHeightInPixels);
end;
// импорт символа из системного шрифта для растеризации
procedure TMatrixChar.Import(Font: TFont; Index: Integer; AEncoding: String);
var
tmp: TBitmap;
w, h: Integer;
begin
tmp := TBitmap.Create;
tmp.Canvas.Font := Font;
tmp.Width := FWidth;
tmp.Height := FHeight;
with tmp.Canvas do
begin
Brush.Color := 1;
Clear;
Clear;
Pen.Color := 0;
Font.Quality := fqNonAntialiased;
TextOut(1, 0, EncodingToUTF8(Char(Index), AEncoding));
// растеризация символа во внутренний формат
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
FCharCanvas[w, h] := Pixels[w, h] = 0;
end;
FreeAndNil(tmp);
end;
// импорт изображения символа из файла PNG
procedure TMatrixChar.ImportImage(AFilename: String; ATreshold: Byte);
var
tmp: TPicture;
w, h, mw, mh: Integer;
begin
if FileExistsUTF8(AFilename) and
FileExtCheck(AFilename, CHAR_IMPORT_FORMATS) then
begin
tmp := TPicture.Create;
tmp.LoadFromFile(AFilename);
mw := FWidth - 1;
mh := FHeight - 1;
if tmp.Width < mw then mw := tmp.Width - 1;
if tmp.Height < mh then mh := tmp.Height - 1;
// растеризация символа во внутренний формат
for h := 0 to mh do
for w := 0 to mw do
FCharCanvas[w, h] := ColorToGray(tmp.Bitmap.Canvas.Pixels[w, h]) < ATreshold;
FreeAndNil(tmp);
end;
end;
// получение ширины символа
function TMatrixChar.GetCharWidth: Integer;
var
x, y, w, tmp: Integer;
empty: Boolean;
begin
w := 0;
tmp := 0;
for x := 0 to FWidth - 1 do
begin
empty := True;
for y := 0 to FHeight - 1 do
if FCharCanvas[x, y] then
begin
empty := False;
break;
end;
Inc(tmp);
if not empty then
w := tmp;
end;
if w = 0 then
w := FWidth div 2 + 1;
Result := w;
end;
// получение высоты символа
function TMatrixChar.GetCharHeight: Integer;
var
y, x, h, tmp: Integer;
empty: Boolean;
begin
h := 0;
tmp := 0;
for y := 0 to FHeight - 1 do
begin
empty := True;
for x := 0 to FWidth - 1 do
if FCharCanvas[x, y] then
begin
empty := False;
break;
end;
Inc(tmp);
if not empty then
h := tmp;
end;
if h = 0 then
h := FHeight div 2 + 1;
Result := h;
end;
// загрузка символа целиком (вызывается обычно после создания символа)
procedure TMatrixChar.LoadChar(ASymbol: TPCharCanvas);
begin
FCharCanvas := ASymbol^;
SetLength(FCharCanvas, FWidth, FHeight);
SetLength(FHistory, FHistoryPosition);
FHistory[FHistoryPosition - 1] := FCharCanvas;
SetLength(FCharCanvas, FWidth, FHeight);
end;
// изменение размеров холста символа
procedure TMatrixChar.ChangeSize(Up, Down, Left, Right: Integer; Crop: Boolean);
var
h, w: Integer;
tmp: TCharCanvas;
begin
// временная копия символа
SetLength(tmp, FWidth, FHeight);
tmp := FCharCanvas;
Up := abs(Up);
Down := abs(Down);
Left := abs(Left);
Right := abs(Right);
if Crop then
// обрезка символа
begin
for h := 0 to FHeight - 1 - Down - Up do
for w := 0 to FWidth - 1 - Right - Left do
CharCanvas[w, h] := tmp[w + Left, h + Up];
h := FHeight - Up - Down;
w := FWidth - Left - Right;
SetHeight((h < 1).Select(1, h));
SetWidth((w < 1).Select(1, w));
end
else
// расширение символа
begin
SetHeight(FHeight + Up + Down);
SetWidth(FWidth + Left + Right);
// очистка фона добавленной области
for h := 0 to FHeight - 1 do
for w := 0 to FWidth - 1 do
CharCanvas[w, h] := False;
for h := 0 to FHeight - 1 - Down - Up do
for w := 0 to FWidth - 1 - Right - Left do
CharCanvas[w + Left, h + Up] := tmp[w, h];
end;
end;
// определение возможности усечь символ: результат - кол-во пустых строк/стоблцов
function TMatrixChar.CanOptimize(Direction: TCanOptimize): Integer;
var
w, h, Count: Integer;
exit_: Boolean;
begin
Count := 0;
case Direction of
// кол-во пустых строк сверху
coUp:
if FHeight > 1 then
for h := 0 to FHeight - 1 do
begin
exit_ := False;
for w := 0 to FWidth - 1 do
if FCharCanvas[w, h] = True then
begin
exit_ := True;
break;
end;
if exit_ then
break
else
Inc(Count);
end;
// кол-во пустых строк снизу
coDown:
if FHeight > 1 then
for h := FHeight - 1 downto 0 do
begin
exit_ := False;
for w := 0 to FWidth - 1 do
if FCharCanvas[w, h] = True then
begin
exit_ := True;
break;
end;
if exit_ then
break
else
Inc(Count);
end;
// кол-во пустых столбцов слева
coLeft:
if FWidth > 1 then
for w := 0 to FWidth - 1 do
begin
exit_ := False;
for h := 0 to FHeight - 1 do
if FCharCanvas[w, h] = True then
begin
exit_ := True;
break;
end;
if exit_ then
break
else
Inc(Count);
end;
// кол-во пустых столбцов справа
coRight:
if FWidth > 1 then
for w := FWidth - 1 downto 0 do
begin
exit_ := False;
for h := 0 to FHeight - 1 do
if FCharCanvas[w, h] = True then
begin
exit_ := True;
break;
end;
if exit_ then
break
else
Inc(Count);
end;
end;
Result := Count;
end;
// операции с буфером обмена
procedure TMatrixChar.ClipboardAction(Action: TClipboardAction; Mode: TPasteMode);
var
h, w, cw, ch: Integer;
pixel: Boolean;
Stream: TMemoryStream;
cb_fmt: TClipboardFormat;
begin
// копирование символа
if (Action = cbCopy) or (Action = cbCut) then
with Stream do
try
if GetCopyBufferEmpty then
cb_fmt := RegisterClipboardFormat(EXCHANGE_BUFFER_TYPE_ID)
else
cb_fmt := Clipboard.FindFormatID(EXCHANGE_BUFFER_TYPE_ID);
Stream := TMemoryStream.Create;
Position := 0;
WriteByte(FWidth);
WriteByte(FHeight);
for h := 0 to Height - 1 do
for w := 0 to Width - 1 do
if FCharCanvas[w, h] then
WriteByte(1)
else
WriteByte(0);
Clipboard.AddFormat(cb_fmt, Stream);
FCopyBufferEmpty := False;
finally
FreeAndNil(Stream);
end;
// вырезание символа: копировать + очистить
if Action = cbCut then
Clear;
// вставка символа
if Action = cbPaste then
with Stream do
begin
try
cb_fmt := Clipboard.FindFormatID(EXCHANGE_BUFFER_TYPE_ID);
except
cb_fmt := 0;
end;
if cb_fmt <> 0 then
try
Stream := TMemoryStream.Create;
if Clipboard.GetFormat(cb_fmt, Stream) then
begin
Position := 0;
cw := ReadByte;
ch := ReadByte;
for h := 0 to ch - 1 do
for w := 0 to cw - 1 do
begin
pixel := (ReadByte = 1);
if (w < FWidth) and (h < FHeight) then
case Mode of
pmNorm:
FCharCanvas[w, h] := pixel;
pmOr:
FCharCanvas[w, h] := FCharCanvas[w, h] or pixel;
pmXor:
FCharCanvas[w, h] := FCharCanvas[w, h] xor pixel;
pmAnd:
FCharCanvas[w, h] := FCharCanvas[w, h] and pixel;
end;
end;
end;
finally
FreeAndNil(Stream);
end;
end;
end;
constructor TMatrixChar.Create;
begin
FHeight := 1;
FWidth := 1;
FHeightInPixels := 3;
FWidthInPixels := 3;
FGridStep := 40;
FGridThickness := 1;
FGridChessBackground := False;
FGridColor := $BBBBBB;
FBackgroundColor := $FFFFFF;
FActiveColor := $000000;
FShowGrid := True;
FShiftRollover := True;
FHistoryPosition := 1;
SetLength(FCharCanvas, FWidth, FHeight);
Clear;
SaveChange;
FHistoryEmpty := True;
FHistoryNoRedo := True;
GetCopyBufferEmpty;
end;
destructor TMatrixChar.Destroy;
begin
FCharCanvas := nil;
inherited; // Эквивалентно: inherited Destroy;
end;
end.