GR32.Text.Win.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. unit GR32.Text.Win;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is Delphi/Windows text vectorization utilities for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Mattias Andersson <[email protected]>
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2012
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * ***** END LICENSE BLOCK ***** *)
  31. interface
  32. {$include GR32.inc}
  33. //------------------------------------------------------------------------------
  34. //
  35. // This unit should be considered internal to Graphics32.
  36. //
  37. // Use the corresponding functions in the backend instead.
  38. //
  39. //------------------------------------------------------------------------------
  40. uses
  41. Windows, Types,
  42. GR32,
  43. GR32_Paths,
  44. GR32.Text.Types;
  45. //------------------------------------------------------------------------------
  46. //
  47. // Text functions for Windows
  48. //
  49. //------------------------------------------------------------------------------
  50. type
  51. TextToolsWin = record
  52. class procedure TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0); static;
  53. class function TextToPolyPolygon(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TArrayOfArrayOfFloatPoint; static;
  54. class function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TFloatRect; static;
  55. class function MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0): TFloatRect; static;
  56. class procedure SetHinting(Value: TTextHinting); static;
  57. class function GetHinting: TTextHinting; static;
  58. end;
  59. //------------------------------------------------------------------------------
  60. //------------------------------------------------------------------------------
  61. //------------------------------------------------------------------------------
  62. implementation
  63. uses
  64. {$IFDEF USESTACKALLOC}
  65. GR32_LowLevel,
  66. {$ENDIF}
  67. SysUtils;
  68. var
  69. UseHinting: Boolean;
  70. HorzStretch: Integer; // stretching factor when calling GetGlyphOutline()
  71. HorzStretch_Inv: Single;
  72. VertFlip_mat2: TMat2;
  73. const
  74. GGO_UNHINTED = $0100;
  75. GGODefaultFlags: array [Boolean] of Integer = (GGO_NATIVE or GGO_UNHINTED, GGO_NATIVE);
  76. TT_PRIM_CSPLINE = 3;
  77. MaxSingle = 3.4e+38;
  78. type
  79. TKerningPairArray = array [0..0] of TKerningPair;
  80. //------------------------------------------------------------------------------
  81. // import GetKerningPairs from gdi32 library
  82. function GetKerningPairs(DC: HDC; Count: DWORD; P: PKerningPair): DWORD; stdcall; external gdi32 name 'GetKerningPairs';
  83. //------------------------------------------------------------------------------
  84. function PointFXtoPointF(const Point: tagPointFX): TFloatPoint; {$IFDEF UseInlining} inline; {$ENDIF}
  85. begin
  86. Result.X := Point.X.Value + Point.X.Fract * FixedToFloat;
  87. Result.Y := Point.Y.Value + Point.Y.Fract * FixedToFloat;
  88. end;
  89. //------------------------------------------------------------------------------
  90. {$IFDEF USESTACKALLOC}
  91. {$W+}
  92. {$ENDIF}
  93. function GlyphOutlineToPath(Handle: HDC; Path: TCustomPath; DstX, MaxX, DstY: Single; const Glyph: Integer; out Metrics: TGlyphMetrics): Boolean;
  94. var
  95. I, K, S: Integer;
  96. Res: DWORD;
  97. GlyphMemPtr, BufferPtr: PTTPolygonHeader;
  98. CurvePtr: PTTPolyCurve;
  99. P1, P2, P3: TFloatPoint;
  100. begin
  101. Result := False;
  102. if (Path = nil) then
  103. Exit;
  104. Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics, 0, nil, VertFlip_mat2);
  105. if (Res = GDI_ERROR) or (DstX + Metrics.gmCellIncX > MaxX) then
  106. Exit;
  107. {$IFDEF USESTACKALLOC}
  108. GlyphMemPtr := StackAlloc(Res);
  109. {$ELSE}
  110. GetMem(GlyphMemPtr, Res);
  111. {$ENDIF}
  112. try
  113. BufferPtr := GlyphMemPtr;
  114. Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics, Res, BufferPtr, VertFlip_mat2);
  115. if (Res = GDI_ERROR) or (BufferPtr.dwType <> TT_POLYGON_TYPE) then
  116. Exit;
  117. // Batch each glyph so we're sure that the polygons are rendered as a whole (no pun...)
  118. // and not as individual independent polygons.
  119. // We're doing this here for completeness but since the path will also be batched at
  120. // an outer level it isn't really necessary here.
  121. Path.BeginUpdate;
  122. while (Res > 0) do
  123. begin
  124. S := BufferPtr.cb - SizeOf(TTTPolygonHeader);
  125. PByte(CurvePtr) := PByte(BufferPtr) + SizeOf(TTTPolygonHeader);
  126. P1 := PointFXtoPointF(BufferPtr.pfxStart);
  127. Path.MoveTo(P1.X + DstX, P1.Y + DstY);
  128. while (S > 0) do
  129. begin
  130. case CurvePtr.wType of
  131. TT_PRIM_LINE:
  132. for I := 0 to CurvePtr.cpfx - 1 do
  133. begin
  134. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  135. Path.LineTo(P1.X + DstX, P1.Y + DstY);
  136. end;
  137. TT_PRIM_QSPLINE:
  138. begin
  139. for I := 0 to CurvePtr.cpfx - 2 do
  140. begin
  141. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  142. P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
  143. if (I < CurvePtr.cpfx - 2) then
  144. begin
  145. P2.x := (P1.x + P2.x) * 0.5;
  146. P2.y := (P1.y + P2.y) * 0.5;
  147. end;
  148. Path.ConicTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY);
  149. end;
  150. end;
  151. TT_PRIM_CSPLINE:
  152. begin
  153. I := 0;
  154. while (I < CurvePtr.cpfx - 2) do
  155. begin
  156. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  157. P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
  158. P3 := PointFXtoPointF(CurvePtr.apfx[I + 2]);
  159. Path.CurveTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY, P3.X + DstX, P3.Y + DstY);
  160. Inc(I, 2);
  161. end;
  162. end;
  163. end;
  164. K := (CurvePtr.cpfx - 1) * SizeOf(TPointFX) + SizeOf(TTPolyCurve);
  165. Dec(S, K);
  166. Inc(PByte(CurvePtr), K);
  167. end;
  168. Path.EndPath(True);
  169. Dec(Res, BufferPtr.cb);
  170. Inc(PByte(BufferPtr), BufferPtr.cb);
  171. end;
  172. Path.EndUpdate;
  173. finally
  174. {$IFDEF USESTACKALLOC}
  175. StackFree(GlyphMemPtr);
  176. {$ELSE}
  177. FreeMem(GlyphMemPtr);
  178. {$ENDIF}
  179. end;
  180. Result := True;
  181. end;
  182. {$IFDEF USESTACKALLOC}
  183. {$W-}
  184. {$ENDIF}
  185. //------------------------------------------------------------------------------
  186. procedure InternalTextToPath(DC: HDC; Path: TCustomPath; var ARect: TFloatRect; const Text: string; Flags: Cardinal);
  187. const
  188. CHAR_CR = 10;
  189. CHAR_NL = 13;
  190. CHAR_SP = 32;
  191. var
  192. GlyphMetrics: TGlyphMetrics;
  193. TextMetric: TTextMetric;
  194. I, J, TextLen, SpcCount, SpcX, LineStart: Integer;
  195. CharValue: Integer;
  196. CharOffsets: TArrayOfInteger;
  197. CharWidths: TArrayOfInteger;
  198. X, Y, XMax, YMax, MaxRight: Single;
  199. S: string;
  200. TextPath: TFlattenedPath;
  201. OwnedPath: TFlattenedPath;
  202. {$IFDEF USEKERNING}
  203. NextCharValue: Integer;
  204. KerningPairs: PKerningPairArray;
  205. KerningPairCount: Integer;
  206. {$ENDIF}
  207. procedure AlignTextCenter(CurrentI: Integer);
  208. var
  209. w, M, N, PathStart, PathEnd, CharStart, CharEnd: Integer;
  210. Delta: TFloat;
  211. i: Integer;
  212. MinX, MaxX: Single;
  213. begin
  214. Delta := Round(((ARect.Right - ARect.Left) * HorzStretch - X - 1) * 0.5);
  215. PathStart := CharOffsets[LineStart];
  216. PathEnd := CharOffsets[CurrentI] - 1;
  217. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  218. begin
  219. MinX := ARect.Left + Delta;
  220. MaxX := ARect.Right + Delta;
  221. CharStart := LineStart;
  222. CharEnd := CurrentI;
  223. w := Round(Delta);
  224. for i := LineStart to CurrentI - 1 do
  225. begin
  226. if w < Arect.Left then
  227. begin
  228. CharStart := i + 1;
  229. MinX := w + CharWidths[i];
  230. end;
  231. w := w + CharWidths[i];
  232. if w <= ARect.Right then
  233. begin
  234. CharEnd := i + 1;
  235. MaxX := w;
  236. end;
  237. end;
  238. if (Flags and DT_WORDBREAK <> 0) then
  239. begin
  240. if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
  241. while (Text[CharStart] <> ' ') and (CharStart < CharEnd) do
  242. Inc(CharStart);
  243. if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
  244. while (Text[CharEnd] <> ' ') and (CharEnd > CharStart) do
  245. Dec(CharEnd);
  246. MinX := Round(Delta);
  247. for i := 0 to CharStart - 1 do
  248. MinX := MinX + CharWidths[i];
  249. MaxX := Round(Delta);
  250. for i := 0 to CharEnd - 1 do
  251. MaxX := MaxX + CharWidths[i];
  252. end;
  253. PathStart := CharOffsets[CharStart];
  254. PathEnd := CharOffsets[CharEnd] - 1;
  255. for M := 0 to PathStart - 1 do
  256. SetLength(TextPath.Path[M], 0);
  257. for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
  258. SetLength(TextPath.Path[M], 0);
  259. Delta := Delta + (((MinX - ARect.Left) + (ARect.Right - MaxX)) * 0.5) - MinX;
  260. end;
  261. for M := PathStart to PathEnd do
  262. for N := 0 to High(TextPath.Path[M]) do
  263. TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
  264. end;
  265. procedure AlignTextRight(CurrentI: Integer);
  266. var
  267. w, i, M, N, PathStart, PathEnd, CharStart: Integer;
  268. Delta: TFloat;
  269. begin
  270. Delta := Round(ARect.Right * HorzStretch - X - 1);
  271. PathStart := CharOffsets[LineStart];
  272. PathEnd := CharOffsets[CurrentI] - 1;
  273. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  274. begin
  275. CharStart := LineStart;
  276. w := 0;
  277. for i := LineStart to CurrentI - 1 do
  278. begin
  279. if w + Delta < Arect.Left then
  280. CharStart := i + 1;
  281. w := w + CharWidths[i];
  282. end;
  283. if (Flags and DT_WORDBREAK <> 0) then
  284. if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
  285. while (Text[CharStart] <> ' ') and (CharStart < CurrentI) do
  286. Inc(CharStart);
  287. PathStart := CharOffsets[CharStart];
  288. for M := 0 to PathStart - 1 do
  289. SetLength(TextPath.Path[M], 0);
  290. end;
  291. for M := PathStart to PathEnd do
  292. for N := 0 to High(TextPath.Path[M]) do
  293. TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
  294. end;
  295. procedure AlignTextLeft(CurrentI: Integer);
  296. var
  297. w, i, M, PathEnd, CharEnd: Integer;
  298. begin
  299. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  300. begin
  301. CharEnd := LineStart;
  302. w := 0;
  303. for i := LineStart to CurrentI - 1 do
  304. begin
  305. w := w + CharWidths[i];
  306. if w <= (ARect.Right - ARect.Left) then
  307. CharEnd := i + 1;
  308. end;
  309. if (Flags and DT_WORDBREAK <> 0) then
  310. if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
  311. while (Text[CharEnd] <> ' ') and (CharEnd > LineStart) do
  312. Dec(CharEnd);
  313. PathEnd := CharOffsets[CharEnd] - 1;
  314. for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
  315. SetLength(TextPath.Path[M], 0);
  316. end;
  317. end;
  318. procedure AlignTextJustify(CurrentI: Integer);
  319. var
  320. L, M, N, PathStart, PathEnd: Integer;
  321. SpcDelta, SpcDeltaInc: TFloat;
  322. begin
  323. if (SpcCount < 1) or (Ord(Text[CurrentI]) = CHAR_CR) then
  324. Exit;
  325. SpcDelta := (ARect.Right * HorzStretch - X - 1) / SpcCount;
  326. SpcDeltaInc := SpcDelta;
  327. L := LineStart;
  328. // Trim leading spaces ...
  329. while (L < CurrentI) and (Ord(Text[L]) = CHAR_SP) do
  330. Inc(L);
  331. // Now find first space char in line ...
  332. while (L < CurrentI) and (Ord(Text[L]) <> CHAR_SP) do
  333. Inc(L);
  334. PathStart := CharOffsets[L - 1];
  335. repeat
  336. M := L + 1;
  337. while (M < CurrentI) and (Ord(Text[M]) <> CHAR_SP) do
  338. Inc(M);
  339. PathEnd := CharOffsets[M];
  340. L := M;
  341. for M := PathStart to PathEnd - 1 do
  342. for N := 0 to High(TextPath.Path[M]) do
  343. TextPath.Path[M, N].X := TextPath.Path[M, N].X + SpcDeltaInc;
  344. SpcDeltaInc := SpcDeltaInc + SpcDelta;
  345. PathStart := PathEnd;
  346. until (L >= CurrentI);
  347. end;
  348. procedure AlignLine(CurrentI: Integer);
  349. begin
  350. if (TextPath <> nil) and (Length(TextPath.Path) > 0) then
  351. case (Flags and DT_HORZ_ALIGN_MASK) of
  352. DT_LEFT : AlignTextLeft(CurrentI);
  353. DT_CENTER : AlignTextCenter(CurrentI);
  354. DT_RIGHT : AlignTextRight(CurrentI);
  355. DT_JUSTIFY: AlignTextJustify(CurrentI);
  356. end;
  357. end;
  358. procedure AddSpace;
  359. begin
  360. Inc(SpcCount);
  361. X := X + SpcX;
  362. end;
  363. procedure NewLine(CurrentI: Integer);
  364. begin
  365. if (Flags and DT_SINGLELINE <> 0) then
  366. begin
  367. AddSpace;
  368. Exit;
  369. end;
  370. AlignLine(CurrentI);
  371. X := ARect.Left * HorzStretch;
  372. Y := Y + TextMetric.tmHeight;
  373. LineStart := CurrentI;
  374. SpcCount := 0;
  375. end;
  376. function MeasureTextX(const S: string): Integer;
  377. var
  378. I: Integer;
  379. begin
  380. Result := 0;
  381. for I := 1 to Length(S) do
  382. begin
  383. CharValue := Ord(S[I]);
  384. if (GetGlyphOutline(DC, CharValue, GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2) = GDI_ERROR) then
  385. RaiseLastOSError;
  386. Inc(Result, GlyphMetrics.gmCellIncX);
  387. end;
  388. end;
  389. function NeedsNewLine(X: Single): Boolean;
  390. begin
  391. Result := (ARect.Right > ARect.Left) and (X > ARect.Right * HorzStretch);
  392. end;
  393. begin
  394. SpcCount := 0;
  395. LineStart := 0;
  396. OwnedPath := nil;
  397. try
  398. if (Path <> nil) then
  399. begin
  400. if (Path is TFlattenedPath) then
  401. begin
  402. TextPath := TFlattenedPath(Path);
  403. TextPath.Clear;
  404. end
  405. else
  406. begin
  407. OwnedPath := TFlattenedPath.Create;
  408. TextPath := OwnedPath;
  409. end
  410. end else
  411. TextPath := nil;
  412. GetTextMetrics(DC, TextMetric);
  413. TextLen := Length(Text);
  414. X := ARect.Left * HorzStretch;
  415. Y := ARect.Top + TextMetric.tmAscent;
  416. XMax := X;
  417. if (Path = nil) or (ARect.Right = ARect.Left) then
  418. MaxRight := MaxSingle //either measuring Text or unbounded Text
  419. else
  420. MaxRight := ARect.Right * HorzStretch;
  421. SetLength(CharOffsets, TextLen + 1);
  422. CharOffsets[0] := 0;
  423. SetLength(CharWidths, TextLen);
  424. if (GetGlyphOutline(DC, CHAR_SP, GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2) = GDI_ERROR) then
  425. RaiseLastOSError;
  426. SpcX := GlyphMetrics.gmCellIncX;
  427. if (Flags and DT_SINGLELINE <> 0) or (ARect.Left = ARect.Right) then
  428. begin
  429. // ignore justify when forcing singleline ...
  430. if (Flags and DT_JUSTIFY = DT_JUSTIFY) then
  431. Flags := Flags and not DT_JUSTIFY;
  432. // ignore wordbreak when forcing singleline ...
  433. //if (Flags and DT_WORDBREAK = DT_WORDBREAK) then
  434. // Flags := Flags and not DT_WORDBREAK;
  435. MaxRight := MaxSingle;
  436. end;
  437. {$IFDEF USEKERNING}
  438. KerningPairs := nil;
  439. try
  440. KerningPairCount := GetKerningPairs(DC, 0, nil);
  441. if GetLastError <> 0 then
  442. RaiseLastOSError;
  443. if KerningPairCount > 0 then
  444. begin
  445. GetMem(KerningPairs, KerningPairCount * SizeOf(TKerningPair));
  446. GetKerningPairs(DC, KerningPairCount, PKerningPair(KerningPairs));
  447. end;
  448. {$ENDIF}
  449. // Batch whole path construction so we can be sure that the path isn't rendered
  450. // while we're still modifying it.
  451. if (TextPath <> nil) then
  452. TextPath.BeginUpdate;
  453. for I := 1 to TextLen do
  454. begin
  455. CharValue := Ord(Text[I]);
  456. if CharValue <= 32 then
  457. begin
  458. if (Flags and DT_SINGLELINE = DT_SINGLELINE) then
  459. CharValue := CHAR_SP;
  460. if (TextPath <> nil) then
  461. // Save path list offset of first path of current glyph
  462. CharOffsets[I] := Length(TextPath.Path);
  463. CharWidths[i - 1] := SpcX;
  464. case CharValue of
  465. CHAR_CR: NewLine(I);
  466. CHAR_NL: ;
  467. CHAR_SP:
  468. begin
  469. if Flags and DT_WORDBREAK = DT_WORDBREAK then
  470. begin
  471. J := I + 1;
  472. while (J <= TextLen) and ([Ord(Text[J])] * [CHAR_CR, CHAR_NL, CHAR_SP] = []) do
  473. Inc(J);
  474. S := Copy(Text, I, J - I);
  475. if NeedsNewLine(X + MeasureTextX(S)) then
  476. NewLine(I)
  477. else
  478. AddSpace;
  479. end else
  480. begin
  481. if NeedsNewLine(X + SpcX) then
  482. NewLine(I)
  483. else
  484. AddSpace;
  485. end;
  486. end;
  487. end;
  488. end
  489. else
  490. begin
  491. if GlyphOutlineToPath(DC, TextPath, X, MaxRight, Y, CharValue, GlyphMetrics) then
  492. begin
  493. if (TextPath <> nil) then
  494. // Save path list offset of first path of current glyph
  495. CharOffsets[I] := Length(TextPath.Path);
  496. CharWidths[I - 1]:= GlyphMetrics.gmCellIncX;
  497. end else
  498. begin
  499. if (Ord(Text[I - 1]) = CHAR_SP) then
  500. begin
  501. // this only happens without DT_WORDBREAK
  502. X := X - SpcX;
  503. Dec(SpcCount);
  504. end;
  505. // the current glyph doesn't fit so a word must be split since
  506. // it fills more than a whole line ...
  507. NewLine(I - 1);
  508. if not GlyphOutlineToPath(DC, TextPath, X, MaxRight, Y, CharValue, GlyphMetrics) then
  509. Break;
  510. if (TextPath <> nil) then
  511. // Save path list offset of first path of current glyph
  512. CharOffsets[I] := Length(TextPath.Path);
  513. CharWidths[I - 1]:= GlyphMetrics.gmCellIncX;
  514. end;
  515. X := X + GlyphMetrics.gmCellIncX;
  516. {$IFDEF USEKERNING}
  517. if i < TextLen then NextCharValue := Ord(Text[i + 1]);
  518. for J := 0 to KerningPairCount - 1 do
  519. begin
  520. if (KerningPairs^[J].wFirst = CharValue) and
  521. (KerningPairs^[J].wSecond = NextCharValue) then
  522. begin
  523. X := X + KerningPairs^[J].iKernAmount;
  524. break;
  525. end;
  526. end;
  527. {$ENDIF}
  528. if (X > XMax) then
  529. XMax := X;
  530. end;
  531. end;
  532. {$IFDEF USEKERNING}
  533. finally
  534. if (KerningPairs <> nil) then
  535. FreeMem(KerningPairs);
  536. end;
  537. {$ENDIF}
  538. if [(Flags and DT_HORZ_ALIGN_MASK)] * [DT_LEFT, DT_CENTER, DT_RIGHT] <> [] then
  539. AlignLine(TextLen);
  540. YMax := Y + TextMetric.tmHeight - TextMetric.tmAscent;
  541. // Reverse HorzStretch (if any) ...
  542. if (HorzStretch <> 1) and (TextPath <> nil) then
  543. for I := 0 to High(TextPath.Path) do
  544. for J := 0 to High(TextPath.Path[I]) do
  545. TextPath.Path[I, J].X := TextPath.Path[I, J].X * HorzStretch_Inv;
  546. XMax := XMax * HorzStretch_Inv;
  547. X := ARect.Right - XMax;
  548. Y := ARect.Bottom - YMax;
  549. case (Flags and DT_HORZ_ALIGN_MASK) of
  550. DT_LEFT : ARect := FloatRect(ARect.Left, ARect.Top, XMax, YMax);
  551. DT_CENTER : ARect := FloatRect(ARect.Left + X * 0.5, ARect.Top, XMax + X * 0.5, YMax);
  552. DT_RIGHT : ARect := FloatRect(ARect.Left + X, ARect.Top, ARect.Right, YMax);
  553. DT_JUSTIFY: ARect := FloatRect(ARect.Left, ARect.Top, ARect.Right, YMax);
  554. end;
  555. if (Flags and (DT_VCENTER or DT_BOTTOM) <> 0) then
  556. begin
  557. if (Flags and DT_VCENTER <> 0) then
  558. Y := Y * 0.5;
  559. if (TextPath <> nil) then
  560. for I := 0 to High(TextPath.Path) do
  561. for J := 0 to High(TextPath.Path[I]) do
  562. TextPath.Path[I, J].Y := TextPath.Path[I, J].Y + Y;
  563. GR32.OffsetRect(ARect, 0, Y);
  564. end;
  565. if (Path <> nil) then
  566. begin
  567. TextPath.EndPath; // TODO : Why is this needed?
  568. if (Path <> TextPath) then
  569. Path.Assign(TextPath);
  570. TextPath.EndUpdate;
  571. end;
  572. finally
  573. OwnedPath.Free;
  574. end;
  575. end;
  576. //------------------------------------------------------------------------------
  577. class procedure TextToolsWin.TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect; const Text: string; Flags: Cardinal);
  578. var
  579. DC: HDC;
  580. SavedFont: HFONT;
  581. R: TFloatRect;
  582. begin
  583. DC := GetDC(0);
  584. try
  585. SavedFont := SelectObject(DC, Font);
  586. R := ARect;
  587. InternalTextToPath(DC, Path, R, Text, Flags);
  588. SelectObject(DC, SavedFont);
  589. finally
  590. ReleaseDC(0, DC);
  591. end;
  592. end;
  593. //------------------------------------------------------------------------------
  594. class function TextToolsWin.TextToPolyPolygon(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TArrayOfArrayOfFloatPoint;
  595. var
  596. Path: TFlattenedPath;
  597. begin
  598. Path := TFlattenedPath.Create;
  599. try
  600. TextToPath(Font, Path, ARect, Text, Flags);
  601. Result := Path.Path;
  602. finally
  603. Path.Free;
  604. end;
  605. end;
  606. //------------------------------------------------------------------------------
  607. class function TextToolsWin.MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TFloatRect;
  608. begin
  609. Result := ARect;
  610. InternalTextToPath(DC, nil, Result, Text, Flags);
  611. end;
  612. //------------------------------------------------------------------------------
  613. class function TextToolsWin.MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: string; Flags: Cardinal): TFloatRect;
  614. var
  615. DC: HDC;
  616. SavedFont: HFONT;
  617. begin
  618. DC := GetDC(0);
  619. try
  620. SavedFont := SelectObject(DC, Font);
  621. Result := MeasureTextDC(DC, ARect, Text, Flags);
  622. SelectObject(DC, SavedFont);
  623. finally
  624. ReleaseDC(0, DC);
  625. end;
  626. end;
  627. //------------------------------------------------------------------------------
  628. class procedure TextToolsWin.SetHinting(Value: TTextHinting);
  629. begin
  630. UseHinting := (Value <> thNone);
  631. if (Value = thNoHorz) then
  632. HorzStretch := 16
  633. else
  634. HorzStretch := 1;
  635. HorzStretch_Inv := 1 / HorzStretch;
  636. VertFlip_mat2 := Default(TMat2);
  637. VertFlip_mat2.eM11.value := HorzStretch;
  638. VertFlip_mat2.eM22.value := -1; // Reversed Y axis
  639. end;
  640. class function TextToolsWin.GetHinting: TTextHinting;
  641. begin
  642. if (HorzStretch <> 1) then
  643. Result := thNoHorz
  644. else
  645. if UseHinting then
  646. Result := thHinting
  647. else
  648. Result := thNone;
  649. end;
  650. //------------------------------------------------------------------------------
  651. procedure InitHinting;
  652. begin
  653. {$if defined(NOHORIZONTALHINTING)}
  654. TextToolsWin.SetHinting(thNoHorz);
  655. {$elseif defined(NOHINTING)}
  656. TextToolsWin.SetHinting(thNone);
  657. {$else}
  658. TextToolsWin.SetHinting(thHinting);
  659. {$ifend}
  660. end;
  661. //------------------------------------------------------------------------------
  662. //------------------------------------------------------------------------------
  663. //------------------------------------------------------------------------------
  664. initialization
  665. InitHinting;
  666. end.