unit beziertext;
interface
uses
Windows, Graphics, Math;
procedure TextAlongBezier(canvas: TCanvas;
bezierPts: array of TPoint; const s: string);
implementation
function DistanceBetween2Pts(pt1,pt2: TPoint): single;
begin
result := sqrt((pt1.X - pt2.X)*(pt1.X - pt2.X) +
(pt1.Y - pt2.Y)*(pt1.Y - pt2.Y));
end;
function GetPtAtDistAndAngleFromPt(pt: TPoint;
dist: integer; angle: single): TPoint;
begin
result.X := round(dist * cos(angle));
result.Y := -round(dist * sin(angle));
inc(result.X , pt.X);
inc(result.Y , pt.Y);
end;
function PtBetween2Pts(pt1, pt2: TPoint;
relativeDistFromPt1: single): TPoint;
begin
if pt2.X = pt1.X then
result.X := pt2.X else
result.X := pt1.X + round((pt2.X - pt1.X)*relativeDistFromPt1);
if pt2.Y = pt1.Y then
result.Y := pt2.Y else
result.Y := pt1.Y + round((pt2.Y - pt1.Y)*relativeDistFromPt1);
end;
function GetAnglePt2FromPt1(pt1, pt2: TPoint): single;
begin
dec(pt2.X,pt1.X);
dec(pt2.Y,pt1.Y);
with pt2 do
if X = 0 then
begin
result := pi/2;
if Y > 0 then result := 3*result;
end else
begin
result := arctan2(-Y,X);
if result < 0 then result := result + pi * 2;
end;
end;
procedure AngledCharOut(Canvas: TCanvas; pt: TPoint;
c: char; radians: single; offsetX, offsetY: integer);
var
lf: TLogFont;
OldFontHdl,NewFontHdl: HFont;
angle: integer;
begin
angle := round(radians * 180/pi);
if angle > 180 then angle := angle - 360;
if angle = 0 then angle := 1;
with Canvas do
begin
if GetObject(Font.Handle, SizeOf(lf), @lf) = 0 then exit;
lf.lfEscapement := Angle * 10;
lf.lfOrientation := Angle * 10;
lf.lfOutPrecision := OUT_TT_ONLY_PRECIS;
NewFontHdl := CreateFontIndirect(lf);
OldFontHdl := selectObject(handle,NewFontHdl);
if offsetX < 0 then
pt := GetPtAtDistAndAngleFromPt(pt, -offsetX, radians + Pi)
else if offsetX > 0 then
pt := GetPtAtDistAndAngleFromPt(pt, offsetX, radians);
if offsetY < 0 then
pt := GetPtAtDistAndAngleFromPt(pt, -offsetY, radians + pi/2)
else if offsetY > 0 then
pt := GetPtAtDistAndAngleFromPt(pt, offsetY, radians - pi/2);
TextOut(pt.x, pt.y, c);
selectObject(handle,OldFontHdl);
DeleteObject(NewFontHdl);
end;
end;
procedure TextAlongBezier(canvas: TCanvas;
bezierPts: array of TPoint; const s: string);
var
i, j, ptCnt, textLenPxls, textLenChars, vertOffset: integer;
currentInsertionDist, charWidthDiv2: integer;
pt: TPoint;
flatPts: array of TPoint;
types: array of byte;
distances: array of single;
dummyPtr: pointer;
angle, spcPxls, bezierLen, relativeDistFRomPt1: single;
charWidths: array[#32..#255] of integer;
begin
textLenChars := length(s);
if (textLenChars = 0) or (high(bezierPts) mod 3 <> 0) then exit;
with canvas do
begin
BeginPath(handle);
PolyBezier(bezierPts);
EndPath(handle);
FlattenPath(handle);
if not GetCharWidth32(handle,32,255, charWidths[#32]) then exit;
dummyPtr := nil;
ptCnt := GetPath(handle, dummyPtr, dummyPtr, 0);
if ptCnt < 1 then exit;
setLength(flatPts, ptCnt);
setLength(types, ptCnt);
setLength(distances, ptCnt);
GetPath(handle, flatPts[0], types[0], ptCnt);
distances[0] := 0;
bezierLen := 0;
for i := 1 to ptCnt -1 do
begin
bezierLen := bezierLen +
DistanceBetween2Pts(flatPts[i], flatPts[i-1]);
distances[i] := bezierLen;
end;
textLenPxls := 0;
for i := 1 to textLenChars do inc(textLenPxls, charWidths[s[i]]);
if textLenChars = 1 then
spcPxls := 0 else
spcPxls := (bezierLen - textLenPxls)/(textLenChars -1);
SetBkMode (handle, TRANSPARENT);
vertOffset := -trunc(2/3* TextHeight('Yy'));
j := 1;
currentInsertionDist := 0;
for i := 1 to textLenChars do
begin
charWidthDiv2 := charWidths[s[i]] div 2;
inc(currentInsertionDist, charWidthDiv2);
while (j < ptCnt -1) and (distances[j] < currentInsertionDist) do
inc(j);
if distances[j] = currentInsertionDist then
pt := flatPts[j]
else
begin
relativeDistFRomPt1 := (currentInsertionDist - distances[j-1]) /
(distances[j] - distances[j-1]);
pt := PtBetween2Pts(flatPts[j-1],flatPts[j],relativeDistFRomPt1);
end;
angle := GetAnglePt2FromPt1(flatPts[j-1], flatPts[j]);
AngledCharOut(canvas,pt,s[i], angle, -charWidthDiv2, vertOffset);
inc(currentInsertionDist,
charWidthDiv2 + trunc(spcPxls) + round(frac(spcPxls*i)));
end;
end;
end;
end.
|