I don't know whether it is the best approach, but it works. There is an unsolved problem on SetNextLabel procedure which causes an access violation at design time.
It can set Columns and Lines like in a newspaper and can send text to the NextLabel. Several labels can be concatenated, each one sending SurplusText to the next one.
You need to set MaxLines and Columns properties at design time and Text property at run time.
unit columnlabel;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, StdCtrls, Controls, Windows, LResources, StrUtils;
type
TColumnLabel = class (TCustomLabel)
private
FCompleteJustification :boolean;
FNextLabel :TCustomLabel;
FSurplusText,
FJustifiedText,
FNormalText :string;
FMaxLines,
FColumns,
FLines,
FMaxLineWidth :integer;
function MyWrapText(AText :string; MaxCol :integer): string;
function Justify(AText :string; AColumns :integer) :string;
function GetText :string;
function GetMaxLines :integer;
procedure SetColumns(AValue :integer);
procedure SetText(AValue :TCaption);
procedure SetNextLabel(ALabel :TCustomLabel);
procedure SetMaxLines(AValue :integer);
public
constructor Create(AOwner :TComponent);override;
property Lines :integer read FLines;
published
property Text :TCaption read GetText write SetText;
property Columns :integer read FColumns write SetColumns;
property MaxLines :integer read GetMaxLines write SetMaxLines;
property NextLabel :TCustomLabel read FNextLabel write SetNextLabel;
property CompleteJustification :boolean read FCompleteJustification write FCompleteJustification;
property MaxLineWidth :integer read FMaxLineWidth write FMaxLineWidth;
property Align;
property Alignment;
property Anchors;
property BidiMode;
property BorderSpacing;
property Color;
property Constraints;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property Font;
//property Layout;
property ParentBidiMode;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Transparent;
property Visible;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseEnter;
property OnMouseLeave;
property OnChangeBounds;
property OnContextPopup;
property OnResize;
property OnStartDrag;
//property OptimalFill;
end;
procedure Register;
implementation
function TColumnLabel.MyWrapText(AText :string; MaxCol :integer): string;
const
NewLine = #13#10;
LastNewLineChar = #10;
var
i, j, n :integer;
s, ss :string;
begin
Result := AText;
if Length(AText) <= MaxCol then Exit;
ss := '';
repeat
s := Copy2SymbDel(AText, LastNewLineChar);
s := Trim(s);
j := 0;
n := 0;
while j <= Length(s) do
begin
inc(j);
if (j mod MaxCol) = 0 then
begin
i := j;
while s[i] <> ' ' do
Dec(i);
if Pos(' ', s) > 0 then
begin
System.Insert(NewLine, s, i + 1);
Inc(n, Length(NewLine));
inc(j, 2);
end
else
begin
System.Insert(NewLine, s, j);
Inc(n, Length(NewLine));
inc(j, 2);
end;
end;
end;
ss := ss + s + NewLine;
until AText = '';
Result := Trim(ss);
end;
function TColumnLabel.Justify(AText :string; AColumns :integer) :string;
var
s, ss, a, sss, s4, wrappedtext :string;
maxwidth, l, n :integer;
ByteSet :set of byte;
begin
fsurplustext := '';
n := 0;
ss := '';
if tag = 1 then
begin
repeat
inc(n);
s := Copy2SymbDel(AText, #10);
s := Trim(s);
if n > FMaxLines then
FSurplusText := FSurplusText + Trim(s) + #13#10
else
ss := ss + Trim(s) + #13#10;
until AText = '';
if Assigned(FNextLabel) then
if FNextLabel is TColumnLabel then
TColumnLabel(FNextLabel).Text := FSurplusText
else
FNextLabel.Caption := FSurplusText;
Result := Trim(ss);
Exit;
end;
ByteSet := [];
while pos(#13#10#13#10, atext) > 0 do
atext := ansireplacestr(atext, #13#10#13#10, '*');
while pos(#13#10, atext) > 0 do
atext := ansireplacestr(atext, #13#10, ' ');
while pos('*', atext) > 0 do
atext := ansireplacestr(atext, '*', #13#10#13#10);
while pos(' ', AText) > 0 do
AText := AnsiReplaceStr(AText, ' ', ' ');
wrappedtext := MyWrapText(AText, AColumns);
s4 := '';
MaxWidth := 0;
s := wrappedtext;
n := 0;
repeat
Inc(n);
ss := Copy2SymbDel(s, #10);
ss := Trim(ss);
if ss = '' then Include(ByteSet, n);
if (Canvas.TextWidth(ss) > MaxWidth) then
MaxWidth := Canvas.TextWidth(ss);
until s = '';
Include(ByteSet, n + 1);
s := wrappedtext;
n := 0;
repeat
Inc(n);
ss := Copy2SymbDel(s, #10);
ss := Trim(ss);
a := ' ';
sss := Trim(ss);
l := Length(sss);
if not((n + 1) in ByteSet) or FCompleteJustification then
while (Canvas.TextWidth(Trim(sss)) <= MaxWidth - Canvas.TextWidth(' ')) do
begin
if Trim(sss) = '' then Break;
if Pos(' ', sss) = 0 then Break;
l := RPosEx(a, sss, l - 1);
if (l > 0) then
begin
System.Insert(' ', sss, l);
end
else
begin
l := Length(sss);
a := a + ' ';
end;
end;
if n > FMaxLines then
FSurplusText := FSurplusText + Trim(sss) + #13#10
else
s4 := s4 + Trim(sss) + #13#10;
until s = '';
FLines := n;
if Assigned(FNextLabel) then
if FNextLabel is TColumnLabel then
TColumnLabel(FNextLabel).Text := FSurplusText
else
FNextLabel.Caption := FSurplusText;
Result := Trim(s4);
end;
constructor TColumnLabel.Create(AOwner :TComponent);
begin
inherited Create(AOwner);
FColumns := 40;
Text := Name;
MaxLines := 30;
FLines := 1;
CompleteJustification := False;
OptimalFill := False;
AutoSize := True;
MaxLineWidth := 200;
FSurplusText := '';
end;
procedure TColumnLabel.SetNextLabel(ALabel :TCustomLabel);
begin
if (FNextLabel = ALabel)
or (FNextLabel = Self) then Exit;
if FNextLabel <> nil then
FNextLabel.RemoveFreeNotification(Self);
FNextLabel := ALabel;
if fnextlabel <> nil then
begin
FNextLabel.Font.Name := font.name;
FNextLabel.Font.Size := font.size;
FNextLabel.Tag := 1;
end;
if ALabel <> nil then
FNextLabel.FreeNotification(Self);
end;
procedure TColumnLabel.SetColumns(AValue :integer);
begin
if FColumns <> AValue then
begin
FColumns := AValue;
SetText(FNormalText);
end;
end;
function TColumnLabel.GetText :string;
begin
Result := Caption;
end;
procedure TColumnLabel.SetText(AValue :TCaption);
begin
if FNormalText = aValue then Exit;
FNormalText := AValue;
if FColumns > 0 then
FJustifiedText := Justify(FNormalText, FColumns)
else
FJustifiedText := FNormalText;
Caption := FJustifiedText;
end;
function TColumnLabel.GetMaxLines :integer;
begin
Result := FMaxLines;
end;
procedure TColumnLabel.SetMaxLines(AValue :integer);
begin
if FMaxLines <> AValue then
FMaxLines := AValue;
SetText(FNormalText);
end;
procedure Register;
begin
RegisterComponents('Misc',[TColumnLabel]);
end;
initialization
{$I columnlabel.lrs}
end.