123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- unit GR32.Text.Win;
- (* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1 or LGPL 2.1 with linking exception
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * Alternatively, the contents of this file may be used under the terms of the
- * Free Pascal modified version of the GNU Lesser General Public License
- * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
- * of this license are applicable instead of those above.
- * Please see the file LICENSE.txt for additional information concerning this
- * license.
- *
- * The Original Code is Delphi/Windows text vectorization utilities for Graphics32
- *
- * The Initial Developer of the Original Code is
- * Mattias Andersson <[email protected]>
- *
- * Portions created by the Initial Developer are Copyright (C) 2012
- * the Initial Developer. All Rights Reserved.
- *
- * ***** END LICENSE BLOCK ***** *)
- interface
- {$include GR32.inc}
- //------------------------------------------------------------------------------
- //
- // This unit should be considered internal to Graphics32.
- //
- // Use the corresponding functions in the backend instead.
- //
- //------------------------------------------------------------------------------
- uses
- Windows, Types,
- GR32,
- GR32_Paths,
- GR32.Text.Types;
- //------------------------------------------------------------------------------
- //
- // Text functions for Windows
- //
- //------------------------------------------------------------------------------
- type
- TextToolsWin = record
- class procedure TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0); static;
- class function TextToPolyPolygon(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TArrayOfArrayOfFloatPoint; static;
- class function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TFloatRect; static;
- class function MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TFloatRect; static;
- class procedure SetHinting(Value: TTextHinting); static;
- class function GetHinting: TTextHinting; static;
- end;
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- implementation
- uses
- {$IFDEF USESTACKALLOC}
- GR32_LowLevel,
- {$ENDIF}
- SysUtils;
- var
- UseHinting: Boolean;
- HorzStretch: Integer; // stretching factor when calling GetGlyphOutline()
- HorzStretch_Inv: Single;
- VertFlip_mat2: TMat2;
- const
- GGO_UNHINTED = $0100;
- GGODefaultFlags: array [Boolean] of Integer = (GGO_NATIVE or GGO_UNHINTED, GGO_NATIVE);
- TT_PRIM_CSPLINE = 3;
- MaxSingle = 3.4e+38;
- type
- TKerningPairArray = array [0..0] of TKerningPair;
- //------------------------------------------------------------------------------
- // import GetKerningPairs from gdi32 library
- function GetKerningPairs(DC: HDC; Count: DWORD; P: PKerningPair): DWORD; stdcall; external gdi32 name 'GetKerningPairs';
- //------------------------------------------------------------------------------
- function PointFXtoPointF(const Point: tagPointFX): TFloatPoint; {$IFDEF UseInlining} inline; {$ENDIF}
- begin
- Result.X := Point.X.Value + Point.X.Fract * FixedToFloat;
- Result.Y := Point.Y.Value + Point.Y.Fract * FixedToFloat;
- end;
- //------------------------------------------------------------------------------
- {$IFDEF USESTACKALLOC}
- {$W+}
- {$ENDIF}
- function GlyphOutlineToPath(Handle: HDC; Path: TCustomPath; DstX, MaxX, DstY: Single; const Glyph: Integer; out Metrics: TGlyphMetrics): Boolean;
- var
- I, K, S: Integer;
- Res: DWORD;
- GlyphMemPtr, BufferPtr: PTTPolygonHeader;
- CurvePtr: PTTPolyCurve;
- P1, P2, P3: TFloatPoint;
- begin
- Result := False;
- if (Path = nil) then
- Exit;
- Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics, 0, nil, VertFlip_mat2);
- if (Res = GDI_ERROR) or (DstX + Metrics.gmCellIncX > MaxX) then
- Exit;
- {$IFDEF USESTACKALLOC}
- GlyphMemPtr := StackAlloc(Res);
- {$ELSE}
- GetMem(GlyphMemPtr, Res);
- {$ENDIF}
- try
- BufferPtr := GlyphMemPtr;
- Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics, Res, BufferPtr, VertFlip_mat2);
- if (Res = GDI_ERROR) or (BufferPtr.dwType <> TT_POLYGON_TYPE) then
- Exit;
- // Batch each glyph so we're sure that the polygons are rendered as a whole (no pun...)
- // and not as individual independent polygons.
- // We're doing this here for completeness but since the path will also be batched at
- // an outer level it isn't really necessary here.
- Path.BeginUpdate;
- while (Res > 0) do
- begin
- S := BufferPtr.cb - SizeOf(TTTPolygonHeader);
- PByte(CurvePtr) := PByte(BufferPtr) + SizeOf(TTTPolygonHeader);
- P1 := PointFXtoPointF(BufferPtr.pfxStart);
- Path.MoveTo(P1.X + DstX, P1.Y + DstY);
- while (S > 0) do
- begin
- case CurvePtr.wType of
- TT_PRIM_LINE:
- for I := 0 to CurvePtr.cpfx - 1 do
- begin
- P1 := PointFXtoPointF(CurvePtr.apfx[I]);
- Path.LineTo(P1.X + DstX, P1.Y + DstY);
- end;
- TT_PRIM_QSPLINE:
- begin
- for I := 0 to CurvePtr.cpfx - 2 do
- begin
- P1 := PointFXtoPointF(CurvePtr.apfx[I]);
- P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
- if (I < CurvePtr.cpfx - 2) then
- begin
- P2.x := (P1.x + P2.x) * 0.5;
- P2.y := (P1.y + P2.y) * 0.5;
- end;
- Path.ConicTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY);
- end;
- end;
- TT_PRIM_CSPLINE:
- begin
- I := 0;
- while (I < CurvePtr.cpfx - 2) do
- begin
- P1 := PointFXtoPointF(CurvePtr.apfx[I]);
- P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
- P3 := PointFXtoPointF(CurvePtr.apfx[I + 2]);
- Path.CurveTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY, P3.X + DstX, P3.Y + DstY);
- Inc(I, 2);
- end;
- end;
- end;
- K := (CurvePtr.cpfx - 1) * SizeOf(TPointFX) + SizeOf(TTPolyCurve);
- Dec(S, K);
- Inc(PByte(CurvePtr), K);
- end;
- Path.EndPath(True);
- Dec(Res, BufferPtr.cb);
- Inc(PByte(BufferPtr), BufferPtr.cb);
- end;
- Path.EndUpdate;
- finally
- {$IFDEF USESTACKALLOC}
- StackFree(GlyphMemPtr);
- {$ELSE}
- FreeMem(GlyphMemPtr);
- {$ENDIF}
- end;
- Result := True;
- end;
- {$IFDEF USESTACKALLOC}
- {$W-}
- {$ENDIF}
- //------------------------------------------------------------------------------
- procedure InternalTextToPath(DC: HDC; Path: TCustomPath; var ARect: TFloatRect; const Text: string; Flags: Cardinal);
- const
- CHAR_CR = 10;
- CHAR_NL = 13;
- CHAR_SP = 32;
- var
- GlyphMetrics: TGlyphMetrics;
- TextMetric: TTextMetric;
- I, J, TextLen, SpcCount, SpcX, LineStart: Integer;
- CharValue: Integer;
- CharOffsets: TArrayOfInteger;
- CharWidths: TArrayOfInteger;
- X, Y, XMax, YMax, MaxRight: Single;
- S: string;
- TextPath: TFlattenedPath;
- OwnedPath: TFlattenedPath;
- {$IFDEF USEKERNING}
- NextCharValue: Integer;
- KerningPairs: PKerningPairArray;
- KerningPairCount: Integer;
- {$ENDIF}
- procedure AlignTextCenter(CurrentI: Integer);
- var
- w, M, N, PathStart, PathEnd, CharStart, CharEnd: Integer;
- Delta: TFloat;
- i: Integer;
- MinX, MaxX: Single;
- begin
- Delta := Round(((ARect.Right - ARect.Left) * HorzStretch - X - 1) * 0.5);
- PathStart := CharOffsets[LineStart];
- PathEnd := CharOffsets[CurrentI] - 1;
- if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
- begin
- MinX := ARect.Left + Delta;
- MaxX := ARect.Right + Delta;
- CharStart := LineStart;
- CharEnd := CurrentI;
- w := Round(Delta);
- for i := LineStart to CurrentI - 1 do
- begin
- if w < Arect.Left then
- begin
- CharStart := i + 1;
- MinX := w + CharWidths[i];
- end;
- w := w + CharWidths[i];
- if w <= ARect.Right then
- begin
- CharEnd := i + 1;
- MaxX := w;
- end;
- end;
- if (Flags and DT_WORDBREAK <> 0) then
- begin
- if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
- while (Text[CharStart] <> ' ') and (CharStart < CharEnd) do
- Inc(CharStart);
- if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
- while (Text[CharEnd] <> ' ') and (CharEnd > CharStart) do
- Dec(CharEnd);
- MinX := Round(Delta);
- for i := 0 to CharStart - 1 do
- MinX := MinX + CharWidths[i];
- MaxX := Round(Delta);
- for i := 0 to CharEnd - 1 do
- MaxX := MaxX + CharWidths[i];
- end;
- PathStart := CharOffsets[CharStart];
- PathEnd := CharOffsets[CharEnd] - 1;
- for M := 0 to PathStart - 1 do
- SetLength(TextPath.Path[M], 0);
- for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
- SetLength(TextPath.Path[M], 0);
- Delta := Delta + (((MinX - ARect.Left) + (ARect.Right - MaxX)) * 0.5) - MinX;
- end;
- for M := PathStart to PathEnd do
- for N := 0 to High(TextPath.Path[M]) do
- TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
- end;
- procedure AlignTextRight(CurrentI: Integer);
- var
- w, i, M, N, PathStart, PathEnd, CharStart: Integer;
- Delta: TFloat;
- begin
- Delta := Round(ARect.Right * HorzStretch - X - 1);
- PathStart := CharOffsets[LineStart];
- PathEnd := CharOffsets[CurrentI] - 1;
- if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
- begin
- CharStart := LineStart;
- w := 0;
- for i := LineStart to CurrentI - 1 do
- begin
- if w + Delta < Arect.Left then
- CharStart := i + 1;
- w := w + CharWidths[i];
- end;
- if (Flags and DT_WORDBREAK <> 0) then
- if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
- while (Text[CharStart] <> ' ') and (CharStart < CurrentI) do
- Inc(CharStart);
- PathStart := CharOffsets[CharStart];
- for M := 0 to PathStart - 1 do
- SetLength(TextPath.Path[M], 0);
- end;
- for M := PathStart to PathEnd do
- for N := 0 to High(TextPath.Path[M]) do
- TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
- end;
- procedure AlignTextLeft(CurrentI: Integer);
- var
- w, i, M, PathEnd, CharEnd: Integer;
- begin
- if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
- begin
- CharEnd := LineStart;
- w := 0;
- for i := LineStart to CurrentI - 1 do
- begin
- w := w + CharWidths[i];
- if w <= (ARect.Right - ARect.Left) then
- CharEnd := i + 1;
- end;
- if (Flags and DT_WORDBREAK <> 0) then
- if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
- while (Text[CharEnd] <> ' ') and (CharEnd > LineStart) do
- Dec(CharEnd);
- PathEnd := CharOffsets[CharEnd] - 1;
- for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
- SetLength(TextPath.Path[M], 0);
- end;
- end;
- procedure AlignTextJustify(CurrentI: Integer);
- var
- L, M, N, PathStart, PathEnd: Integer;
- SpcDelta, SpcDeltaInc: TFloat;
- begin
- if (SpcCount < 1) or (Ord(Text[CurrentI]) = CHAR_CR) then
- Exit;
- SpcDelta := (ARect.Right * HorzStretch - X - 1) / SpcCount;
- SpcDeltaInc := SpcDelta;
- L := LineStart;
- // Trim leading spaces ...
- while (L < CurrentI) and (Ord(Text[L]) = CHAR_SP) do
- Inc(L);
- // Now find first space char in line ...
- while (L < CurrentI) and (Ord(Text[L]) <> CHAR_SP) do
- Inc(L);
- PathStart := CharOffsets[L - 1];
- repeat
- M := L + 1;
- while (M < CurrentI) and (Ord(Text[M]) <> CHAR_SP) do
- Inc(M);
- PathEnd := CharOffsets[M];
- L := M;
- for M := PathStart to PathEnd - 1 do
- for N := 0 to High(TextPath.Path[M]) do
- TextPath.Path[M, N].X := TextPath.Path[M, N].X + SpcDeltaInc;
- SpcDeltaInc := SpcDeltaInc + SpcDelta;
- PathStart := PathEnd;
- until (L >= CurrentI);
- end;
- procedure AlignLine(CurrentI: Integer);
- begin
- if (TextPath <> nil) and (Length(TextPath.Path) > 0) then
- case (Flags and DT_HORZ_ALIGN_MASK) of
- DT_LEFT : AlignTextLeft(CurrentI);
- DT_CENTER : AlignTextCenter(CurrentI);
- DT_RIGHT : AlignTextRight(CurrentI);
- DT_JUSTIFY: AlignTextJustify(CurrentI);
- end;
- end;
- procedure AddSpace;
- begin
- Inc(SpcCount);
- X := X + SpcX;
- end;
- procedure NewLine(CurrentI: Integer);
- begin
- if (Flags and DT_SINGLELINE <> 0) then
- begin
- AddSpace;
- Exit;
- end;
- AlignLine(CurrentI);
- X := ARect.Left * HorzStretch;
- Y := Y + TextMetric.tmHeight;
- LineStart := CurrentI;
- SpcCount := 0;
- end;
- function MeasureTextX(const S: string): Integer;
- var
- I: Integer;
- begin
- Result := 0;
- for I := 1 to Length(S) do
- begin
- CharValue := Ord(S[I]);
- if (GetGlyphOutline(DC, CharValue, GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2) = GDI_ERROR) then
- RaiseLastOSError;
- Inc(Result, GlyphMetrics.gmCellIncX);
- end;
- end;
- function NeedsNewLine(X: Single): Boolean;
- begin
- Result := (ARect.Right > ARect.Left) and (X > ARect.Right * HorzStretch);
- end;
- begin
- SpcCount := 0;
- LineStart := 0;
- OwnedPath := nil;
- try
- if (Path <> nil) then
- begin
- if (Path is TFlattenedPath) then
- begin
- TextPath := TFlattenedPath(Path);
- TextPath.Clear;
- end
- else
- begin
- OwnedPath := TFlattenedPath.Create;
- TextPath := OwnedPath;
- end
- end else
- TextPath := nil;
- GetTextMetrics(DC, TextMetric);
- TextLen := Length(Text);
- X := ARect.Left * HorzStretch;
- Y := ARect.Top + TextMetric.tmAscent;
- XMax := X;
- if (Path = nil) or (ARect.Right = ARect.Left) then
- MaxRight := MaxSingle //either measuring Text or unbounded Text
- else
- MaxRight := ARect.Right * HorzStretch;
- SetLength(CharOffsets, TextLen + 1);
- CharOffsets[0] := 0;
- SetLength(CharWidths, TextLen);
- if (GetGlyphOutline(DC, CHAR_SP, GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2) = GDI_ERROR) then
- RaiseLastOSError;
- SpcX := GlyphMetrics.gmCellIncX;
- if (Flags and DT_SINGLELINE <> 0) or (ARect.Left = ARect.Right) then
- begin
- // ignore justify when forcing singleline ...
- if (Flags and DT_JUSTIFY = DT_JUSTIFY) then
- Flags := Flags and not DT_JUSTIFY;
- // ignore wordbreak when forcing singleline ...
- //if (Flags and DT_WORDBREAK = DT_WORDBREAK) then
- // Flags := Flags and not DT_WORDBREAK;
- MaxRight := MaxSingle;
- end;
- {$IFDEF USEKERNING}
- KerningPairs := nil;
- try
- KerningPairCount := GetKerningPairs(DC, 0, nil);
- if GetLastError <> 0 then
- RaiseLastOSError;
- if KerningPairCount > 0 then
- begin
- GetMem(KerningPairs, KerningPairCount * SizeOf(TKerningPair));
- GetKerningPairs(DC, KerningPairCount, PKerningPair(KerningPairs));
- end;
- {$ENDIF}
- // Batch whole path construction so we can be sure that the path isn't rendered
- // while we're still modifying it.
- if (TextPath <> nil) then
- TextPath.BeginUpdate;
- for I := 1 to TextLen do
- begin
- CharValue := Ord(Text[I]);
- if CharValue <= 32 then
- begin
- if (Flags and DT_SINGLELINE = DT_SINGLELINE) then
- CharValue := CHAR_SP;
- if (TextPath <> nil) then
- // Save path list offset of first path of current glyph
- CharOffsets[I] := Length(TextPath.Path);
- CharWidths[i - 1] := SpcX;
- case CharValue of
- CHAR_CR: NewLine(I);
- CHAR_NL: ;
- CHAR_SP:
- begin
- if Flags and DT_WORDBREAK = DT_WORDBREAK then
- begin
- J := I + 1;
- while (J <= TextLen) and ([Ord(Text[J])] * [CHAR_CR, CHAR_NL, CHAR_SP] = []) do
- Inc(J);
- S := Copy(Text, I, J - I);
- if NeedsNewLine(X + MeasureTextX(S)) then
- NewLine(I)
- else
- AddSpace;
- end else
- begin
- if NeedsNewLine(X + SpcX) then
- NewLine(I)
- else
- AddSpace;
- end;
- end;
- end;
- end
- else
- begin
- if GlyphOutlineToPath(DC, TextPath, X, MaxRight, Y, CharValue, GlyphMetrics) then
- begin
- if (TextPath <> nil) then
- // Save path list offset of first path of current glyph
- CharOffsets[I] := Length(TextPath.Path);
- CharWidths[I - 1]:= GlyphMetrics.gmCellIncX;
- end else
- begin
- if (Ord(Text[I - 1]) = CHAR_SP) then
- begin
- // this only happens without DT_WORDBREAK
- X := X - SpcX;
- Dec(SpcCount);
- end;
- // the current glyph doesn't fit so a word must be split since
- // it fills more than a whole line ...
- NewLine(I - 1);
- if not GlyphOutlineToPath(DC, TextPath, X, MaxRight, Y, CharValue, GlyphMetrics) then
- Break;
- if (TextPath <> nil) then
- // Save path list offset of first path of current glyph
- CharOffsets[I] := Length(TextPath.Path);
- CharWidths[I - 1]:= GlyphMetrics.gmCellIncX;
- end;
- X := X + GlyphMetrics.gmCellIncX;
- {$IFDEF USEKERNING}
- if i < TextLen then NextCharValue := Ord(Text[i + 1]);
- for J := 0 to KerningPairCount - 1 do
- begin
- if (KerningPairs^[J].wFirst = CharValue) and
- (KerningPairs^[J].wSecond = NextCharValue) then
- begin
- X := X + KerningPairs^[J].iKernAmount;
- break;
- end;
- end;
- {$ENDIF}
- if (X > XMax) then
- XMax := X;
- end;
- end;
- {$IFDEF USEKERNING}
- finally
- if (KerningPairs <> nil) then
- FreeMem(KerningPairs);
- end;
- {$ENDIF}
- if [(Flags and DT_HORZ_ALIGN_MASK)] * [DT_LEFT, DT_CENTER, DT_RIGHT] <> [] then
- AlignLine(TextLen);
- YMax := Y + TextMetric.tmHeight - TextMetric.tmAscent;
- // Reverse HorzStretch (if any) ...
- if (HorzStretch <> 1) and (TextPath <> nil) then
- for I := 0 to High(TextPath.Path) do
- for J := 0 to High(TextPath.Path[I]) do
- TextPath.Path[I, J].X := TextPath.Path[I, J].X * HorzStretch_Inv;
- XMax := XMax * HorzStretch_Inv;
- X := ARect.Right - XMax;
- Y := ARect.Bottom - YMax;
- case (Flags and DT_HORZ_ALIGN_MASK) of
- DT_LEFT : ARect := FloatRect(ARect.Left, ARect.Top, XMax, YMax);
- DT_CENTER : ARect := FloatRect(ARect.Left + X * 0.5, ARect.Top, XMax + X * 0.5, YMax);
- DT_RIGHT : ARect := FloatRect(ARect.Left + X, ARect.Top, ARect.Right, YMax);
- DT_JUSTIFY: ARect := FloatRect(ARect.Left, ARect.Top, ARect.Right, YMax);
- end;
- if (Flags and (DT_VCENTER or DT_BOTTOM) <> 0) then
- begin
- if (Flags and DT_VCENTER <> 0) then
- Y := Y * 0.5;
- if (TextPath <> nil) then
- for I := 0 to High(TextPath.Path) do
- for J := 0 to High(TextPath.Path[I]) do
- TextPath.Path[I, J].Y := TextPath.Path[I, J].Y + Y;
- GR32.OffsetRect(ARect, 0, Y);
- end;
- if (Path <> nil) then
- begin
- TextPath.EndPath; // TODO : Why is this needed?
- if (Path <> TextPath) then
- Path.Assign(TextPath);
- TextPath.EndUpdate;
- end;
- finally
- OwnedPath.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- class procedure TextToolsWin.TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect; const Text: string; Flags: Cardinal);
- var
- DC: HDC;
- SavedFont: HFONT;
- R: TFloatRect;
- begin
- DC := GetDC(0);
- try
- SavedFont := SelectObject(DC, Font);
- R := ARect;
- InternalTextToPath(DC, Path, R, Text, Flags);
- SelectObject(DC, SavedFont);
- finally
- ReleaseDC(0, DC);
- end;
- end;
- //------------------------------------------------------------------------------
- class function TextToolsWin.TextToPolyPolygon(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TArrayOfArrayOfFloatPoint;
- var
- Path: TFlattenedPath;
- begin
- Path := TFlattenedPath.Create;
- try
- TextToPath(Font, Path, ARect, Text, Flags);
- Result := Path.Path;
- finally
- Path.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- class function TextToolsWin.MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TFloatRect;
- begin
- Result := ARect;
- InternalTextToPath(DC, nil, Result, Text, Flags);
- end;
- //------------------------------------------------------------------------------
- class function TextToolsWin.MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TFloatRect;
- var
- DC: HDC;
- SavedFont: HFONT;
- begin
- DC := GetDC(0);
- try
- SavedFont := SelectObject(DC, Font);
- Result := MeasureTextDC(DC, ARect, Text, Flags);
- SelectObject(DC, SavedFont);
- finally
- ReleaseDC(0, DC);
- end;
- end;
- //------------------------------------------------------------------------------
- class procedure TextToolsWin.SetHinting(Value: TTextHinting);
- begin
- UseHinting := (Value <> thNone);
- if (Value = thNoHorz) then
- HorzStretch := 16
- else
- HorzStretch := 1;
- HorzStretch_Inv := 1 / HorzStretch;
- VertFlip_mat2 := Default(TMat2);
- VertFlip_mat2.eM11.value := HorzStretch;
- VertFlip_mat2.eM22.value := -1; // Reversed Y axis
- end;
- class function TextToolsWin.GetHinting: TTextHinting;
- begin
- if (HorzStretch <> 1) then
- Result := thNoHorz
- else
- if UseHinting then
- Result := thHinting
- else
- Result := thNone;
- end;
- //------------------------------------------------------------------------------
- procedure InitHinting;
- begin
- {$if defined(NOHORIZONTALHINTING)}
- TextToolsWin.SetHinting(thNoHorz);
- {$elseif defined(NOHINTING)}
- TextToolsWin.SetHinting(thNone);
- {$else}
- TextToolsWin.SetHinting(thHinting);
- {$ifend}
- end;
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- initialization
- InitHinting;
- end.
|