lazarus-ccr/components/chemtext/source/chemtext.pas
wp_xxyyzz 1f45709f7c chemtext: Less hints and warnings
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8122 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2021-10-27 13:52:00 +00:00

477 lines
12 KiB
ObjectPascal

(*******************************************************************************
chemtext.pas
Motivated by chemtxt written by Patrick Spanel (Patrik.Spanel@jh-inst.cas.cz)
Download his version from
http://torry.net/vcl/science/packs/ChemText12.zip
or
http://delphi.icm.edu.pl/ftp/d10free/chemtxt.zip
Adapted to Lazarus and extended by Werner Pamler
License:
LGPL with linking exception (like Lazarus)
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
for details about the license.
*******************************************************************************)
unit chemtext;
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
interface
uses
LclIntf, LCLType, LCLVersion, Types, SysUtils, Classes, Graphics, StdCtrls;
const
DEFAULT_SMALLFONT_SIZE = 67;
DEFAULT_SUBSCRIPT_OFFSET = 50;
DEFAULT_SUPERSCRIPT_OFFSET = 12;
var
SmallFontSizePercent: Integer = DEFAULT_SMALLFONT_SIZE;
SubscriptFontOffsetPercent: Integer = DEFAULT_SUBSCRIPT_OFFSET;
SuperscriptFontOffsetPercent: Integer = DEFAULT_SUPERSCRIPT_OFFSET;
type
TChemArrow = (
caASCIISingle, caASCIIDouble, caUTF8, caUTF8Single, caUTF8Double, caUTF8Half
);
TChemLabel = class(TCustomLabel)
private
FArrow: TChemArrow;
procedure SetArrow(const AValue: TChemArrow);
protected
procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer;
{%H-}WithThemeSpace: Boolean); override;
procedure CalculateSize(out NeededWidth, NeededHeight: Integer);
procedure DoMeasureTextPosition(var TextTop: integer;
var TextLeft: integer); {$IF LCL_FullVersion < 2010000} override; {$IFEND}
public
constructor Create(AOwner: TComponent); override;
procedure Paint; override;
published
property Arrow: TChemArrow read FArrow write SetArrow default caASCIISingle;
property Align;
property Alignment;
property Anchors;
property AutoSize;
// property BidiMode;
property BorderSpacing;
property Caption;
property Color;
property Constraints;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property FocusControl;
property Font;
property Layout;
// property ParentBidiMode;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
// property ShowAccelChar;
property ShowHint;
property Transparent;
property Visible;
// property WordWrap;
property OnChangeBounds;
property OnClick;
property OnContextPopup;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnStartDrag;
// property OptimalFill;
end;
{ The following rotuines can be used in an event handler, for example in
OnDrawDataCell of DBGrid:
procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);
begin
if Assigned(Field) then
ChemTextOut((Sender as TDBGrid).Canvas, Rect.Left, Rect.Top, Field.DisplayText);
end;
}
function ChemTextOut(ACanvas: TCanvas; X, Y: integer;
const AText:String; Arrow: TChemArrow = caAsciiSingle; Measure: Boolean = false): TSize;
function ChemTextHeight(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): Integer;
function ChemTextWidth(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): Integer;
function ChemTextExtent(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): TSize;
procedure Register;
implementation
{$R chemtext.res}
uses
Themes, Math, LazUtf8;
type
TArrowDir = (adLeft, adRight, adBoth);
const
ARROW_LINE: array[boolean] of char = ('-', '=');
ESCAPE_CHAR = '\';
SAVE_CURRENT_POS = '@';
RETURN_TO_POS = '|';
SUPERSCRIPT_CHAR = '^';
SUBSCRIPT_CHAR = '_';
HYDRATE_DOT = #$E2#$80#$A2;
function ChemTextHeight(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): Integer;
var
ex: TSize;
begin
ex := ChemTextExtent(ACanvas, AText, Arrow);
Result := ex.CY;
end;
function ChemTextWidth(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): Integer;
var
ex: TSize;
begin
ex := ChemTextExtent(ACanvas, AText, Arrow);
Result := ex.CX;
end;
function ChemTextExtent(ACanvas: TCanvas; const AText: String;
Arrow: TChemArrow = caAsciiSingle): TSize;
begin
Result := ChemTextOut(ACanvas, 0, 0, AText, Arrow, true);
end;
function ChemTextOut(ACanvas: TCanvas; X, Y:integer; const AText: String;
Arrow: TChemArrow = caAsciiSingle; Measure: Boolean = false): TSize;
var
lTextHeight: Integer;
procedure DrawSub(var x: Integer; y: Integer; const s: String);
var
h: Integer;
yoff: Integer;
begin
h := ACanvas.Font.Height;
try
ACanvas.Font.Height := MulDiv(h, SmallFontSizePercent, 100);
yoff := abs(MulDiv(h, SubscriptFontOffsetPercent, 100));
if not Measure then
ACanvas.TextOut(x, y + yoff, s);
x := x + ACanvas.TextWidth(s);
lTextHeight := Max(lTextHeight, yoff + ACanvas.TextHeight('0'));
finally
ACanvas.Font.Height := h;
end;
end;
procedure DrawSup(var x: Integer; y: Integer; const s: String);
var
h: Integer;
yoff: Integer;
begin
h := ACanvas.Font.Height;
try
ACanvas.Font.Height := MulDiv(h, SmallFontSizePercent, 100);
yoff := abs(MulDiv(h, SuperscriptFontOffsetPercent, 100));
if not Measure then
ACanvas.TextOut(x, y - yoff, s);
inc(x, ACanvas.TextWidth(s));
finally
ACanvas.Font.Height := h;
end;
end;
procedure DrawNormal(var x: Integer; y: Integer; const s: String);
begin
if not Measure then
ACanvas.TextOut(x, y, s);
inc(x, ACanvas.TextWidth(s));
end;
procedure DrawArrow(var x: Integer; y: Integer; ADir: TArrowDir;
ALen: Integer);
const
ARROWS: array[TChemArrow, TArrowDir] of string = (
('<%s', '%s>', '<%s>'), // caAsciiSingle
('<%s', '%s>', '<%s>'), // caAsciiDouble
(#$E2#$86#$90, #$E2#$86#$92, #$E2#$87#$8C), // caUTF8 ← → ⇌
(#$E2#$86#$90, #$E2#$86#$92, #$E2#$86#$94), // caUTF8Single ← → ↔
(#$E2#$87#$90, #$E2#$87#$92, #$E2#$87#$94), // caUTF8Double ⇐ ⇒ ⇔ ⇔
(#$E2#$86#$BD, #$E2#$87#$80, #$E2#$87#$8C) // caUTF8Half ↽ ↼ ⇌
);
var
i: Integer;
s: String = '';
begin
if Arrow in [caASCIISingle, caASCIIDouble] then
begin
SetLength(s, ALen);
for i:=1 to ALen do s[i] := ARROW_LINE[Arrow=caAsciiDouble];
s := Format(ARROWS[Arrow, ADir], [s]);
end else
s := ARROWS[Arrow, ADir];
if not Measure then
ACanvas.TextOut(x, y, s);
inc(x, ACanvas.TextWidth(s));
end;
var
x0: Integer;
i, j, n: integer;
s: string;
subNos: boolean; // "subscript numbers"
escaping: Boolean;
savedX: Integer;
begin
Result := Size(0, 0);
if AText = '' then
exit;
with ACanvas do begin
if Font.Size = 0 then
Font.Size := GetFontData(Font.Reference.Handle).Height;
lTextHeight := TextHeight('Tg');
x0 := X;
subNos := false;
escaping := false;
i := 1;
while i <= Length(AText) do begin
if escaping then
begin
DrawNormal(X, Y, AText[i]);
escaping := false;
end else
case AText[i] of
'0'..'9':
begin
s := AText[i];
j := i+1;
while (j <= Length(AText)) and (AText[j] in ['0'..'9']) do
inc(j);
s := Copy(AText, i, j-i);
if subNos then
DrawSub(X, Y, s)
else
DrawNormal(X, Y, s);
i := j-1;
subNos := false;
end;
'<':
begin
j := i+1;
while (j <= Length(AText)) and (AText[j] in ['-', '=']) do
inc(j);
if (AText[j] = '>') then
DrawArrow(X, Y, adBoth, j-i-1)
else begin
DrawArrow(X, Y, adLeft, j-i-1);
dec(j);
end;
i := j;
subNos := false;
end;
'+':
begin
if (i > 1) and (AText[i-1] in ['A'..'Z','a'..'z','0'..'9','+',')']) then
DrawSup(X, Y, '+')
else
DrawNormal(X, Y, '+');
subNos := false;
end;
'-':
begin
begin
j := i+1;
while (j <= Length(AText)) and (AText[j] = '-') do inc(j);
if (j <= Length(AText)) and (AText[j] = '>') then // Arrow
begin
DrawArrow(X, y, adRight, j-i);
i := j;
end else // superscript -
DrawSup(X, Y, '-');
end;
subNos := false;
end;
'.':
begin
subNos := false;
DrawNormal(X, Y, HYDRATE_DOT);
end;
ESCAPE_CHAR:
escaping := true;
SAVE_CURRENT_POS:
savedX := X;
RETURN_TO_POS:
X := savedX;
SUPERSCRIPT_CHAR:
begin
n := UTF8CodePointSize(@AText[i+1]);
DrawSup(X, Y, copy(AText, i+1, n));
inc(i, n);
end;
SUBSCRIPT_CHAR:
begin
n := UTF8CodePointSize(@AText[i+1]);
DrawSub(X, Y, copy(AText, i+1, n));
inc(i, n);
end;
else
begin
j := i+1;
while (j <= Length(AText)) and
not (AText[j] in ['0'..'9', '+', '-', '<', '.', ESCAPE_CHAR, SAVE_CURRENT_POS, RETURN_TO_POS, SUPERSCRIPT_CHAR, SUBSCRIPT_CHAR])
do
inc(j);
s := Copy(AText, i, j-i);
DrawNormal(X, Y, s);
i := j-1;
subNos := AText[i] in ['A'..'Z', 'a'..'z', ')'];
// In these cases a subsequent number will be subscripted.
end;
end;
inc(i);
end;
end;
Result.CX := X - x0;
Result.CY := lTextHeight;
end;
{ TChemText }
constructor TChemLabel.Create(AOwner: TComponent);
begin
inherited;
end;
procedure TChemLabel.CalculatePreferredSize(
var PreferredWidth, PreferredHeight: integer; WithThemeSpace: Boolean);
begin
CalculateSize(PreferredWidth, PreferredHeight);
end;
procedure TChemlabel.CalculateSize(out NeededWidth, NeededHeight: Integer);
var
ex: TSize;
begin
Canvas.Font := Font;
ex := ChemTextExtent(Canvas, Caption, FArrow);
NeededWidth := ex.CX;
NeededHeight := ex.CY;
end;
procedure TChemLabel.DoMeasureTextPosition(var TextTop: integer;
var TextLeft: integer);
var
lTextHeight: integer;
lTextWidth: integer;
begin
TextLeft := 0;
TextTop := 0;
if (Alignment <> taLeftJustify) or (Layout <> tlTop) then begin
CalculateSize(lTextWidth, lTextHeight);
case Alignment of
taCenter : TextLeft := (Width - lTextWidth) div 2;
taRightJustify : TextLeft := Width - lTextWidth;
end;
case Layout of
tlCenter : TextTop := (Height - lTextHeight) div 2;
tlBottom : TextTop := Height - lTextHeight;
end;
end;
end;
procedure TChemLabel.Paint;
var
textTop: Integer = 0;
textLeft: Integer = 0;
oldFontColor: TColor;
labelText: String;
begin
if not Transparent then
begin
Canvas.Brush.Color := Self.Color;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(ClientRect);
end;
Canvas.Brush.Style := bsClear;
Canvas.Font := Font;
labelText := Caption;
DoMeasureTextPosition(textTop, textLeft);
oldFontColor := Font.Color;
if not IsEnabled then
if ThemeServices.ThemesEnabled then
Canvas.Font.Color := clGrayText
else
begin
Canvas.Font.Color := clBtnHighlight;
ChemTextOut(Canvas, textLeft + 1, textTop + 1, labelText, FArrow);
Canvas.Font.Color := clBtnShadow;
end;
ChemTextOut(Canvas, textLeft, textTop, labelText, FArrow);
Canvas.Font.Color := oldFontColor;
end;
procedure TChemLabel.SetArrow(const AValue: TChemArrow);
begin
if AValue = FArrow then
exit;
FArrow := AValue;
Invalidate;
end;
procedure Register;
begin
RegisterComponents('Misc', [TChemlabel]);
end;
end.