GR32_Text_LCL_Win.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. unit GR32_Text_LCL_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 Vectorial Polygon Rasterizer 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. * Contributor(s):
  31. *
  32. * ***** END LICENSE BLOCK ***** *)
  33. interface
  34. {$I GR32.inc}
  35. uses
  36. Windows, Types, GR32, GR32_Paths;
  37. procedure TextToPath(Font: HFONT; Path: TCustomPath;
  38. const ARect: TFloatRect; const Text: string; Flags: Cardinal); overload;
  39. function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string;
  40. Flags: Cardinal): TFloatRect; overload;
  41. function MeasureText(Font: HFONT; const ARect: TFloatRect; const Text: string;
  42. Flags: Cardinal): TFloatRect;
  43. type
  44. TTextHinting = (thNone, thNoHorz, thHinting);
  45. procedure SetHinting(Value: TTextHinting);
  46. function GetHinting: TTextHinting;
  47. const
  48. DT_LEFT = 0; //See also Window's DrawText() flags ...
  49. DT_CENTER = 1; //http://msdn.microsoft.com/en-us/library/ms901121.aspx
  50. DT_RIGHT = 2;
  51. DT_WORDBREAK = $10;
  52. DT_VCENTER = 4;
  53. DT_BOTTOM = 8;
  54. DT_SINGLELINE = $20;
  55. DT_JUSTIFY = 3; //Graphics32 additions ...
  56. DT_HORZ_ALIGN_MASK = 3;
  57. implementation
  58. uses
  59. GR32_LowLevel;
  60. var
  61. UseHinting: Boolean;
  62. HorzStretch: Integer; // stretching factor when calling GetGlyphOutline()
  63. HorzStretch_Inv: single;
  64. VertFlip_mat2: tmat2;
  65. const
  66. GGO_UNHINTED = $0100;
  67. GGODefaultFlags: array [Boolean] of Integer = (GGO_NATIVE or GGO_UNHINTED, GGO_NATIVE);
  68. TT_PRIM_CSPLINE = 3;
  69. MaxSingle = 3.4e+38;
  70. function PointFXtoPointF(const Point: tagPointFX): TFloatPoint;
  71. begin
  72. Result.X := Point.X.Value + Point.X.Fract * FixedToFloat;
  73. Result.Y := Point.Y.Value + Point.Y.Fract * FixedToFloat;
  74. end;
  75. function GlyphOutlineToPath(Handle: HDC; Path: TCustomPath;
  76. DstX, MaxX, DstY: Single;
  77. const Glyph: Integer; out Metrics: TGlyphMetrics): Boolean;
  78. var
  79. I, K, S: Integer;
  80. Res: DWORD;
  81. GlyphMemPtr, BufferPtr: PTTPolygonHeader;
  82. CurvePtr: PTTPolyCurve;
  83. P1, P2, P3: TFloatPoint;
  84. begin
  85. Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics,
  86. 0, nil, VertFlip_mat2);
  87. Result := DstX + Metrics.gmCellIncX <= MaxX;
  88. if not Result or not Assigned(Path) then Exit;
  89. GetMem(GlyphMemPtr, Res);
  90. BufferPtr := GlyphMemPtr;
  91. Res := GetGlyphOutline(Handle, Glyph, GGODefaultFlags[UseHinting], Metrics,
  92. Res, BufferPtr, VertFlip_mat2);
  93. if (Res = GDI_ERROR) or (BufferPtr^.dwType <> TT_POLYGON_TYPE) then
  94. begin
  95. FreeMem(GlyphMemPtr);
  96. Exit;
  97. end;
  98. while Res > 0 do
  99. begin
  100. S := BufferPtr.cb - SizeOf(TTTPolygonHeader);
  101. PtrUInt(CurvePtr) := PtrUInt(BufferPtr) + SizeOf(TTTPolygonHeader);
  102. P1 := PointFXtoPointF(BufferPtr.pfxStart);
  103. Path.MoveTo(P1.X + DstX, P1.Y + DstY);
  104. while S > 0 do
  105. begin
  106. case CurvePtr.wType of
  107. TT_PRIM_LINE:
  108. for I := 0 to CurvePtr.cpfx - 1 do
  109. begin
  110. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  111. Path.LineTo(P1.X + DstX, P1.Y + DstY);
  112. end;
  113. TT_PRIM_QSPLINE:
  114. begin
  115. for I := 0 to CurvePtr.cpfx - 2 do
  116. begin
  117. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  118. if I < CurvePtr.cpfx - 2 then
  119. with PointFXtoPointF(CurvePtr.apfx[I + 1]) do
  120. begin
  121. P2.x := (P1.x + x) * 0.5;
  122. P2.y := (P1.y + y) * 0.5;
  123. end
  124. else
  125. P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
  126. Path.ConicTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY);
  127. end;
  128. end;
  129. TT_PRIM_CSPLINE:
  130. begin
  131. I := 0;
  132. while I < CurvePtr.cpfx - 2 do
  133. begin
  134. P1 := PointFXtoPointF(CurvePtr.apfx[I]);
  135. P2 := PointFXtoPointF(CurvePtr.apfx[I + 1]);
  136. P3 := PointFXtoPointF(CurvePtr.apfx[I + 2]);
  137. Path.CurveTo(P1.X + DstX, P1.Y + DstY, P2.X + DstX, P2.Y + DstY,
  138. P3.X + DstX, P3.Y + DstY);
  139. Inc(I, 2);
  140. end;
  141. end;
  142. end;
  143. K := (CurvePtr.cpfx - 1) * SizeOf(TPointFX) + SizeOf(TTPolyCurve);
  144. Dec(S, K);
  145. Inc(PtrInt(CurvePtr), K);
  146. end;
  147. Path.EndPath(True);
  148. Dec(Res, BufferPtr.cb);
  149. Inc(PtrInt(BufferPtr), BufferPtr.cb);
  150. end;
  151. FreeMem(GlyphMemPtr);
  152. end;
  153. procedure InternalTextToPath(DC: HDC; Path: TCustomPath; const ARect: TFloatRect;
  154. const Text: string; Flags: Cardinal);
  155. const
  156. CHAR_CR = 10;
  157. CHAR_NL = 13;
  158. CHAR_SP = 32;
  159. var
  160. GlyphMetrics: TGlyphMetrics;
  161. TextMetric: TTextMetric;
  162. I, J, TextLen, SpcCount, SpcX, LineStart: Integer;
  163. CharValue: Integer;
  164. CharOffsets: TArrayOfInteger;
  165. X, Y, XMax, YMax, MaxRight: Single;
  166. S: string;
  167. TextPath: TFlattenedPath;
  168. OwnedPath: TFlattenedPath;
  169. {$IFDEF USEKERNING}
  170. LastCharValue: Integer;
  171. KerningPairs: PKerningPairArray;
  172. KerningPairCount: Integer;
  173. {$ENDIF}
  174. procedure AlignTextCenter(CurrentI: Integer);
  175. var
  176. M, N, PathStart, PathEnd: Integer;
  177. Delta: TFloat;
  178. begin
  179. Delta := Round((ARect.Right * HorzStretch - X - 1) * 0.5);
  180. PathStart := CharOffsets[LineStart];
  181. PathEnd := CharOffsets[CurrentI];
  182. for M := PathStart to PathEnd - 1 do
  183. for N := 0 to High(TextPath.Path[M]) do
  184. TextPath.Path[M][N].X := TextPath.Path[M][N].X + Delta;
  185. end;
  186. procedure AlignTextRight(CurrentI: Integer);
  187. var
  188. M, N, PathStart, PathEnd: Integer;
  189. Delta: TFloat;
  190. begin
  191. Delta := Round(ARect.Right * HorzStretch - X - 1);
  192. PathStart := CharOffsets[LineStart];
  193. PathEnd := CharOffsets[CurrentI];
  194. for M := PathStart to PathEnd - 1 do
  195. for N := 0 to High(TextPath.Path[M]) do
  196. TextPath.Path[M][N].X := TextPath.Path[M][N].X + Delta;
  197. end;
  198. procedure AlignTextJustify(CurrentI: Integer);
  199. var
  200. L, M, N, PathStart, PathEnd: Integer;
  201. SpcDelta, SpcDeltaInc: TFloat;
  202. begin
  203. if (SpcCount < 1) or (Ord(Text[CurrentI]) = CHAR_CR) then
  204. Exit;
  205. SpcDelta := (ARect.Right * HorzStretch - X - 1) / SpcCount;
  206. SpcDeltaInc := SpcDelta;
  207. L := LineStart;
  208. //Trim leading spaces ...
  209. while (L < CurrentI) and (Ord(Text[L]) = CHAR_SP) do Inc(L);
  210. //Now find first space char in line ...
  211. while (L < CurrentI) and (Ord(Text[L]) <> CHAR_SP) do Inc(L);
  212. PathStart := CharOffsets[L - 1];
  213. repeat
  214. M := L + 1;
  215. while (M < CurrentI) and (Ord(Text[M]) <> CHAR_SP) do Inc(M);
  216. PathEnd := CharOffsets[M];
  217. L := M;
  218. for M := PathStart to PathEnd - 1 do
  219. for N := 0 to High(TextPath.Path[M]) do
  220. TextPath.Path[M][N].X := TextPath.Path[M][N].X + SpcDeltaInc;
  221. SpcDeltaInc := SpcDeltaInc + SpcDelta;
  222. PathStart := PathEnd;
  223. until L >= CurrentI;
  224. end;
  225. procedure NewLine(CurrentI: Integer);
  226. begin
  227. if (Flags and DT_SINGLELINE <> 0) then Exit;
  228. if assigned(TextPath) then
  229. case (Flags and DT_HORZ_ALIGN_MASK) of
  230. DT_CENTER : AlignTextCenter(CurrentI);
  231. DT_RIGHT : AlignTextRight(CurrentI);
  232. DT_JUSTIFY: AlignTextJustify(CurrentI);
  233. end;
  234. X := ARect.Left * HorzStretch;
  235. Y := Y + TextMetric.tmHeight;
  236. LineStart := CurrentI;
  237. SpcCount := 0;
  238. end;
  239. function MeasureTextX(const S: string): Integer;
  240. var
  241. I: Integer;
  242. begin
  243. Result := 0;
  244. for I := 1 to Length(S) do
  245. begin
  246. CharValue := Ord(S[I]);
  247. GetGlyphOutline(DC, CharValue,
  248. GGODefaultFlags[UseHinting], GlyphMetrics, 0, nil, VertFlip_mat2);
  249. Inc(Result, GlyphMetrics.gmCellIncX);
  250. end;
  251. end;
  252. function NeedsNewLine(X: Single): boolean;
  253. begin
  254. Result := X > ARect.Right * HorzStretch;
  255. end;
  256. procedure AddSpace;
  257. begin
  258. Inc(SpcCount);
  259. X := X + SpcX;
  260. end;
  261. begin
  262. {$IFDEF USEKERNING}
  263. KerningPairs := nil;
  264. KerningPairCount := GetKerningPairs(DC, 0, nil);
  265. if GetLastError <> 0 then
  266. RaiseLastOSError;
  267. if KerningPairCount > 0 then
  268. begin
  269. GetMem(KerningPairs, KerningPairCount * SizeOf(TKerningPair));
  270. GetKerningPairs(DC, KerningPairCount, PKerningPair(KerningPairs));
  271. end;
  272. LastCharValue := 0;
  273. {$ENDIF}
  274. SpcCount := 0;
  275. LineStart := 0;
  276. OwnedPath := nil;
  277. if (Path <> nil) then
  278. begin
  279. if (Path is TFlattenedPath) then
  280. begin
  281. TextPath := TFlattenedPath(Path);
  282. TextPath.Clear;
  283. end
  284. else
  285. begin
  286. OwnedPath := TFlattenedPath.Create;
  287. TextPath := OwnedPath;
  288. end
  289. end else
  290. TextPath := nil;
  291. GetTextMetrics(DC, TextMetric);
  292. TextLen := Length(Text);
  293. X := ARect.Left * HorzStretch;
  294. Y := ARect.Top + TextMetric.tmAscent;
  295. XMax := X;
  296. if not Assigned(Path) or (ARect.Right = ARect.Left) then
  297. MaxRight := MaxSingle //either measuring text or unbounded text
  298. else
  299. MaxRight := ARect.Right * HorzStretch;
  300. SetLength(CharOffsets, TextLen +1);
  301. CharOffsets[0] := 0;
  302. GetGlyphOutline(DC, CHAR_SP, GGODefaultFlags[UseHinting],
  303. GlyphMetrics, 0, nil, VertFlip_mat2);
  304. SpcX := GlyphMetrics.gmCellIncX;
  305. if (Flags and DT_SINGLELINE <> 0) then
  306. begin
  307. //ignore justify when forcing singleline ...
  308. if (Flags and DT_JUSTIFY = DT_JUSTIFY) then
  309. Flags := Flags and not DT_JUSTIFY;
  310. //ignore wordbreak when forcing singleline ...
  311. if (Flags and DT_WORDBREAK = DT_WORDBREAK) then
  312. Flags := Flags and not DT_WORDBREAK;
  313. MaxRight := MaxSingle;
  314. end;
  315. // Batch whole path construction so we can be sure that the path isn't rendered
  316. // while we're still modifying it.
  317. if (TextPath <> nil) then
  318. TextPath.BeginUpdate;
  319. for I := 1 to TextLen do
  320. begin
  321. CharValue := Ord(Text[I]);
  322. if CharValue <= 32 then
  323. begin
  324. if (Flags and DT_SINGLELINE = DT_SINGLELINE) then
  325. CharValue := CHAR_SP;
  326. if Assigned(TextPath) then
  327. CharOffsets[I] := Length(TextPath.Path);
  328. case CharValue of
  329. CHAR_CR: NewLine(I);
  330. CHAR_NL: ;
  331. CHAR_SP:
  332. begin
  333. if Flags and DT_WORDBREAK = DT_WORDBREAK then
  334. begin
  335. J := I + 1;
  336. while (J <= TextLen) and
  337. ([Ord(Text[J])] * [CHAR_CR, CHAR_NL, CHAR_SP] = []) do
  338. Inc(J);
  339. S := Copy(Text, I, J - I);
  340. if NeedsNewLine(X + MeasureTextX(S)) then
  341. NewLine(I) else
  342. AddSpace;
  343. end else
  344. begin
  345. if NeedsNewLine(X + SpcX) then
  346. NewLine(I)
  347. else
  348. AddSpace;
  349. end;
  350. end;
  351. end;
  352. end
  353. else
  354. begin
  355. if GlyphOutlineToPath(DC, TextPath,
  356. X, MaxRight, Y, CharValue, GlyphMetrics) then
  357. begin
  358. if Assigned(TextPath) then
  359. CharOffsets[I] := Length(TextPath.Path);
  360. end else
  361. begin
  362. if Ord(Text[I -1]) = CHAR_SP then
  363. begin
  364. //this only happens without DT_WORDBREAK
  365. X := X - SpcX;
  366. Dec(SpcCount);
  367. end;
  368. //the current glyph doesn't fit so a word must be split since
  369. //it fills more than a whole line ...
  370. NewLine(I - 1);
  371. if not GlyphOutlineToPath(DC, TextPath,
  372. X, MaxRight, Y, CharValue, GlyphMetrics) then Break;
  373. if Assigned(TextPath) then
  374. CharOffsets[I] := Length(TextPath.Path);
  375. end;
  376. X := X + GlyphMetrics.gmCellIncX;
  377. {$IFDEF USEKERNING}
  378. for J := 0 to KerningPairCount - 1 do
  379. begin
  380. if (KerningPairs^[J].wFirst = LastCharValue) and
  381. (KerningPairs^[J].wSecond = CharValue) then
  382. X := X + KerningPairs^[J].iKernAmount;
  383. end;
  384. LastCharValue := CharValue;
  385. {$ENDIF}
  386. if X > XMax then XMax := X;
  387. end;
  388. end;
  389. if [(Flags and DT_HORZ_ALIGN_MASK)] * [DT_CENTER, DT_RIGHT] <> [] then
  390. NewLine(TextLen);
  391. YMax := Y + TextMetric.tmHeight - TextMetric.tmAscent;
  392. //reverse HorzStretch (if any) ...
  393. if (HorzStretch <> 1) and assigned(TextPath) then
  394. for I := 0 to High(TextPath.Path) do
  395. for J := 0 to High(TextPath.Path[I]) do
  396. TextPath.Path[I][J].X := TextPath.Path[I][J].X * HorzStretch_Inv;
  397. XMax := XMax * HorzStretch_Inv;
  398. X := ARect.Right - XMax;
  399. Y := ARect.Bottom - YMax;
  400. if Flags and (DT_VCENTER or DT_BOTTOM) <> 0 then
  401. begin
  402. if Flags and DT_VCENTER <> 0 then
  403. Y := Y * 0.5;
  404. if assigned(TextPath) then
  405. for I := 0 to High(TextPath.Path) do
  406. for J := 0 to High(TextPath.Path[I]) do
  407. TextPath.Path[I][J].Y := TextPath.Path[I][J].Y + Y;
  408. end;
  409. {$IFDEF USEKERNING}
  410. if Assigned(KerningPairs) then
  411. FreeMem(KerningPairs);
  412. {$ENDIF}
  413. if (Path <> nil) then
  414. begin
  415. TextPath.EndPath; // TODO : Why is this needed?
  416. if (Path <> TextPath) then
  417. Path.Assign(TextPath);
  418. TextPath.EndUpdate;
  419. OwnedPath.Free;
  420. end;
  421. end;
  422. procedure TextToPath(Font: HFONT; Path: TCustomPath; const ARect: TFloatRect;
  423. const Text: string; Flags: Cardinal); overload;
  424. var
  425. DC: HDC;
  426. SavedFont: HFONT;
  427. begin
  428. DC := GetDC(0);
  429. try
  430. SavedFont := SelectObject(DC, Font);
  431. InternalTextToPath(DC, Path, ARect, Text, Flags);
  432. SelectObject(DC, SavedFont);
  433. finally
  434. ReleaseDC(0, DC);
  435. end;
  436. end;
  437. function MeasureTextDC(DC: HDC; const ARect: TFloatRect; const Text: string;
  438. Flags: Cardinal): TFloatRect;
  439. begin
  440. Result := ARect;
  441. InternalTextToPath(DC, nil, Result, Text, Flags);
  442. Result.Left := Round(Result.Left);
  443. Result.Top := Round(Result.Top);
  444. Result.Right := Round(Result.Right);
  445. Result.Bottom := Round(Result.Bottom);
  446. end;
  447. function MeasureText(Font: HFONT; const ARect: TFloatRect;
  448. const Text: string; Flags: Cardinal): TFloatRect;
  449. var
  450. DC: HDC;
  451. SavedFont: HFONT;
  452. begin
  453. DC := GetDC(0);
  454. try
  455. SavedFont := SelectObject(DC, Font);
  456. Result := MeasureTextDC(DC, ARect, Text, Flags);
  457. SelectObject(DC, SavedFont);
  458. finally
  459. ReleaseDC(0, DC);
  460. end;
  461. end;
  462. procedure SetHinting(Value: TTextHinting);
  463. begin
  464. UseHinting := Value <> thNone;
  465. if (Value = thNoHorz) then
  466. HorzStretch := 16 else
  467. HorzStretch := 1;
  468. HorzStretch_Inv := 1 / HorzStretch;
  469. FillChar(VertFlip_mat2, SizeOf(VertFlip_mat2), 0);
  470. VertFlip_mat2.eM11.value := HorzStretch;
  471. VertFlip_mat2.eM22.value := -1; //reversed Y axis
  472. end;
  473. function GetHinting: TTextHinting;
  474. begin
  475. if HorzStretch <> 1 then Result := thNoHorz
  476. else if UseHinting then Result := thHinting
  477. else Result := thNone;
  478. end;
  479. procedure InitHinting;
  480. begin
  481. {$IFDEF NOHORIZONTALHINTING}
  482. SetHinting(thNoHorz);
  483. {$ELSE}
  484. {$IFDEF NOHINTING}
  485. SetHinting(thNone);
  486. {$ELSE}
  487. SetHinting(thHinting);
  488. {$ENDIF};
  489. {$ENDIF}
  490. end;
  491. initialization
  492. InitHinting;
  493. end.