GR32_Text_VCL_D2D.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. unit GR32_Text_VCL_D2D;
  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. * Christian-W. Budde ([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. {$include GR32.inc}
  35. uses
  36. Windows, Types, Math, D2D1, GR32, GR32_Paths;
  37. procedure TextToPath(Font: HFONT; const FontHeight: Integer; Path: TCustomPath;
  38. const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0);
  39. function TextToPolyPolygon(Font: HFONT; const FontHeight: Integer;
  40. const ARect: TFloatRect; const Text: string;
  41. Flags: Cardinal = 0): TArrayOfArrayOfFloatPoint;
  42. function MeasureTextDC(DC: HDC; const FontHeight: Integer;
  43. const ARect: TFloatRect; const Text: string;
  44. Flags: Cardinal = 0): TFloatRect; overload;
  45. function MeasureText(Font: HFONT; const FontHeight: Integer;
  46. const ARect: TFloatRect; const Text: string;
  47. Flags: Cardinal = 0): TFloatRect;
  48. type
  49. TTextGeometrySink = class(TInterfacedObject, ID2D1SimplifiedGeometrySink, ID2D1GeometrySink)
  50. private
  51. FPath: TCustomPath;
  52. FDstX, FDstY: TFloat;
  53. public
  54. constructor Create(Path: TCustomPath; DstX, DstY: TFloat);
  55. procedure SetFillMode(fillMode: D2D1_FILL_MODE); stdcall;
  56. procedure SetSegmentFlags(vertexFlags: D2D1_PATH_SEGMENT); stdcall;
  57. procedure BeginFigure(startPoint: D2D1_POINT_2F;
  58. figureBegin: D2D1_FIGURE_BEGIN); stdcall;
  59. procedure AddLines(points: PD2D1Point2F; pointsCount: UINT); stdcall;
  60. procedure AddBeziers(beziers: PD2D1BezierSegment;
  61. beziersCount: UINT); stdcall;
  62. procedure EndFigure(figureEnd: D2D1_FIGURE_END); stdcall;
  63. function Close: HResult; stdcall;
  64. procedure AddLine(point: D2D1_POINT_2F); stdcall;
  65. procedure AddBezier(const bezier: D2D1_BEZIER_SEGMENT); stdcall;
  66. procedure AddQuadraticBezier(const bezier: D2D1_QUADRATIC_BEZIER_SEGMENT); stdcall;
  67. procedure AddQuadraticBeziers(beziers: PD2D1QuadraticBezierSegment;
  68. beziersCount: UINT); stdcall;
  69. procedure AddArc(const arc: D2D1_ARC_SEGMENT); stdcall;
  70. end;
  71. const
  72. DT_LEFT = 0; //See also Window's DrawText() flags ...
  73. DT_CENTER = 1; //http://msdn.microsoft.com/en-us/library/ms901121.aspx
  74. DT_RIGHT = 2;
  75. DT_VCENTER = 4;
  76. DT_BOTTOM = 8;
  77. DT_WORDBREAK = $10;
  78. DT_SINGLELINE = $20;
  79. DT_NOCLIP = $100;
  80. DT_JUSTIFY = 3; //Graphics32 additions ...
  81. DT_HORZ_ALIGN_MASK = 3;
  82. implementation
  83. uses
  84. {$IFDEF USESTACKALLOC}
  85. GR32_LowLevel,
  86. {$ENDIF}
  87. ComObj,
  88. SysUtils;
  89. type
  90. IDWriteFontFaceFixed = interface(IUnknown)
  91. [SID_IDWriteFontFace]
  92. function GetType: DWRITE_FONT_FACE_TYPE; stdcall;
  93. function GetFiles(var numberOfFiles: Cardinal;
  94. out fontFiles: IDWriteFontFile): HResult; stdcall;
  95. function GetIndex: UINT32; stdcall;
  96. function GetSimulations: DWRITE_FONT_SIMULATIONS; stdcall;
  97. function IsSymbolFont: BOOL; stdcall;
  98. procedure GetMetrics(var fontFaceMetrics: TDwriteFontMetrics); stdcall;
  99. function GetGlyphCount: UINT16; stdcall;
  100. function GetDesignGlyphMetrics(glyphIndices: PWord; glyphCount: Cardinal;
  101. glyphMetrics: PDwriteGlyphMetrics; isSideways: BOOL = False): HResult; stdcall;
  102. function GetGlyphIndices(var codePoints: Cardinal; codePointCount: Cardinal;
  103. var glyphIndices: Word): HResult; stdcall;
  104. function TryGetFontTable(openTypeTableTag: Cardinal; var tableData: Pointer;
  105. var tableSize: Cardinal; var tableContext: Pointer;
  106. var exists: BOOL): HResult; stdcall;
  107. procedure ReleaseFontTable(tableContext: Pointer); stdcall;
  108. function GetGlyphRunOutline(emSize: Single; const glyphIndices: PWord;
  109. const glyphAdvances: PSingle; const glyphOffsets: PDwriteGlyphOffset;
  110. glyphCount: Cardinal; isSideways: BOOL; isRightToLeft: BOOL;
  111. geometrySink: IDWriteGeometrySink): HResult; stdcall;
  112. function GetRecommendedRenderingMode(emSize: Single; pixelsPerDip: Single;
  113. measuringMode: TDWriteMeasuringMode;
  114. var renderingParams: IDWriteRenderingParams;
  115. var renderingMode: TDWriteRenderingMode): HResult; stdcall;
  116. function GetGdiCompatibleMetrics(emSize: Single; pixelsPerDip: Single;
  117. transform: PDwriteMatrix; var fontFaceMetrics: DWRITE_FONT_METRICS): HResult; stdcall;
  118. function GetGDICompatibleGlyphMetrics(emSize: Single; pixelsPerDip: Single;
  119. transform: PDwriteMatrix; useGdiNatural: BOOL;
  120. glyphIndicies: PWord; glpyhCount: Cardinal;
  121. out glyphMetrics: TDwriteGlyphMetrics; isSideways: BOOL = FALSE): HResult; stdcall;
  122. end;
  123. const
  124. MaxSingle = 3.4e+38;
  125. { TTextGeometrySink }
  126. constructor TTextGeometrySink.Create(Path: TCustomPath; DstX, DstY: TFloat);
  127. begin
  128. FPath := Path;
  129. FDstX := DstX;
  130. FDstY := DstY;
  131. end;
  132. function D2D_POINT_2F_to_TFloatPoint(Value: D2D_POINT_2F): TFloatPoint;
  133. begin
  134. Result.X := Value.x;
  135. Result.Y := Value.Y;
  136. end;
  137. procedure TTextGeometrySink.AddArc(const arc: D2D1_ARC_SEGMENT);
  138. begin
  139. // FPath.Arc(D2D_POINT_2F_to_TFloatPoint(arc.point), arc.rotationAngle, arc.);
  140. end;
  141. procedure TTextGeometrySink.AddBezier(const bezier: D2D1_BEZIER_SEGMENT);
  142. begin
  143. FPath.CurveTo(
  144. FDstX + bezier.point1.x, FDstY + bezier.point1.y,
  145. FDstX + bezier.point2.x, FDstY + bezier.point2.y,
  146. FDstX + bezier.point3.x, FDstY + bezier.point3.y);
  147. end;
  148. procedure TTextGeometrySink.AddBeziers(beziers: PD2D1BezierSegment;
  149. beziersCount: UINT);
  150. var
  151. Index: Integer;
  152. begin
  153. for Index := 0 to beziersCount - 1 do
  154. begin
  155. FPath.CurveTo(
  156. FDstX + beziers.point1.x, FDstY + beziers.point1.y,
  157. FDstX + beziers.point2.x, FDstY + beziers.point2.y,
  158. FDstX + beziers.point3.x, FDstY + beziers.point3.y);
  159. Inc(Beziers);
  160. end;
  161. end;
  162. procedure TTextGeometrySink.AddLine(point: D2D1_POINT_2F);
  163. begin
  164. FPath.LineTo(FDstX + point.x, FDstY + point.y);
  165. end;
  166. procedure TTextGeometrySink.AddLines(points: PD2D1Point2F; pointsCount: UINT);
  167. var
  168. Index: Integer;
  169. begin
  170. for Index := 0 to pointsCount - 1 do
  171. begin
  172. FPath.LineTo(FDstX + points^.x, FDstY + points^.Y);
  173. Inc(points);
  174. end;
  175. end;
  176. procedure TTextGeometrySink.AddQuadraticBezier(
  177. const bezier: D2D1_QUADRATIC_BEZIER_SEGMENT);
  178. begin
  179. FPath.CurveTo(
  180. FDstX + bezier.point1.x, FDstY + bezier.point1.y,
  181. FDstX + bezier.point2.x, FDstY + bezier.point2.y);
  182. end;
  183. procedure TTextGeometrySink.AddQuadraticBeziers(
  184. beziers: PD2D1QuadraticBezierSegment; beziersCount: UINT);
  185. var
  186. Index: Integer;
  187. begin
  188. for Index := 0 to beziersCount - 1 do
  189. begin
  190. FPath.CurveTo(
  191. FDstX + beziers^.point1.x, FDstY + beziers^.point1.y,
  192. FDstX + beziers^.point2.x, FDstY + beziers^.point2.y);
  193. Inc(Beziers);
  194. end;
  195. end;
  196. procedure TTextGeometrySink.BeginFigure(startPoint: D2D1_POINT_2F;
  197. figureBegin: D2D1_FIGURE_BEGIN);
  198. begin
  199. FPath.MoveTo(FDstX + startPoint.x, FDstY + startPoint.Y);
  200. end;
  201. function TTextGeometrySink.Close: HResult;
  202. begin
  203. Result := S_OK;
  204. end;
  205. procedure TTextGeometrySink.EndFigure(figureEnd: D2D1_FIGURE_END);
  206. begin
  207. FPath.EndPath(True);
  208. end;
  209. procedure TTextGeometrySink.SetFillMode(fillMode: D2D1_FILL_MODE);
  210. begin
  211. end;
  212. procedure TTextGeometrySink.SetSegmentFlags(vertexFlags: D2D1_PATH_SEGMENT);
  213. begin
  214. end;
  215. var
  216. SingletonD2DFactory: ID2D1Factory;
  217. function D2DFactory(FactoryType: TD2D1FactoryType = D2D1_FACTORY_TYPE_SINGLE_THREADED;
  218. FactoryOptions: PD2D1FactoryOptions = nil): ID2D1Factory;
  219. var
  220. LD2DFactory: ID2D1Factory;
  221. begin
  222. if SingletonD2DFactory = nil then
  223. begin
  224. D2D1CreateFactory(FactoryType, IID_ID2D1Factory, FactoryOptions, LD2DFactory);
  225. if InterlockedCompareExchangePointer(Pointer(SingletonD2DFactory), Pointer(LD2DFactory), nil) = nil then
  226. LD2DFactory._AddRef;
  227. end;
  228. Result := SingletonD2DFactory;
  229. end;
  230. var
  231. SingletonDWriteFactory: IDWriteFactory;
  232. function DWriteFactory(FactoryType: TDWriteFactoryType = DWRITE_FACTORY_TYPE_SHARED): IDWriteFactory;
  233. var
  234. LDWriteFactory: IDWriteFactory;
  235. begin
  236. if SingletonDWriteFactory = nil then
  237. begin
  238. DWriteCreateFactory(FactoryType, IID_IDWriteFactory, IUnknown(LDWriteFactory));
  239. if InterlockedCompareExchangePointer(Pointer(SingletonDWriteFactory), Pointer(LDWriteFactory), nil) = nil then
  240. LDWriteFactory._AddRef;
  241. end;
  242. Result := SingletonDWriteFactory;
  243. end;
  244. procedure InternalTextToPath(DC: HDC; FontHeight: Integer;
  245. Path: TCustomPath; const ARect: TFloatRect;
  246. const Text: string; Flags: Cardinal = 0);
  247. const
  248. CHAR_CR = 10;
  249. CHAR_NL = 13;
  250. CHAR_SP = 32;
  251. var
  252. I, J, TextLen, SpcCount, SpcX, LineStart: Integer;
  253. CharValue: Integer;
  254. CharAdvance: TFloat;
  255. CharOffsets: TArrayOfInteger;
  256. CharWidths: TArrayOfInteger;
  257. X, Y, XMax, YMax, MaxRight: Single;
  258. S: string;
  259. // UseTempPath: Boolean;
  260. TextPath: TFlattenedPath;
  261. OwnedPath: TFlattenedPath;
  262. EmSize, PixelPerDip: Single;
  263. GDIInterop: IDWriteGdiInterop;
  264. Metrics: TDwriteFontMetrics;
  265. GlyphMetrics: TDwriteGlyphMetrics;
  266. GlyphIndex: Word;
  267. TextGeometrySink: TTextGeometrySink;
  268. FontFace: IDWriteFontFace;
  269. HR: HRESULT;
  270. CurrentChar: Word;
  271. procedure AlignTextCenter(CurrentI: Integer);
  272. var
  273. w, M, N, PathStart, PathEnd, CharStart, CharEnd: Integer;
  274. Delta: TFloat;
  275. i: Integer;
  276. MinX, MaxX: Single;
  277. begin
  278. Delta := Round(((ARect.Right - ARect.Left) - X - 1) * 0.5);
  279. PathStart := CharOffsets[LineStart];
  280. PathEnd := CharOffsets[CurrentI] - 1;
  281. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  282. begin
  283. MinX := ARect.Left + Delta;
  284. MaxX := ARect.Right + Delta;
  285. CharStart := LineStart;
  286. CharEnd := CurrentI;
  287. w := Round(Delta);
  288. for i := LineStart to CurrentI - 1 do
  289. begin
  290. if w < Arect.Left then
  291. begin
  292. CharStart := i + 1;
  293. MinX := w + CharWidths[i];
  294. end;
  295. w := w + CharWidths[i];
  296. if w <= ARect.Right then
  297. begin
  298. CharEnd := i + 1;
  299. MaxX := w;
  300. end;
  301. end;
  302. if (Flags and DT_WORDBREAK <> 0) then
  303. begin
  304. if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
  305. while (Text[CharStart] <> ' ') and (CharStart < CharEnd) do
  306. Inc(CharStart);
  307. if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
  308. while (Text[CharEnd] <> ' ') and (CharEnd > CharStart) do
  309. Dec(CharEnd);
  310. MinX:= Round(Delta);
  311. for i := 0 to CharStart - 1 do
  312. MinX := MinX + CharWidths[i];
  313. MaxX := Round(Delta);
  314. for i := 0 to CharEnd - 1 do
  315. MaxX := MaxX + CharWidths[i];
  316. end;
  317. PathStart := CharOffsets[CharStart];
  318. PathEnd := CharOffsets[CharEnd] - 1;
  319. for M := 0 to PathStart - 1 do
  320. SetLength(TextPath.Path[M], 0);
  321. for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
  322. SetLength(TextPath.Path[M], 0);
  323. Delta := Delta + (((MinX - ARect.Left) + (ARect.Right - MaxX)) * 0.5) - MinX;
  324. end;
  325. for M := PathStart to PathEnd do
  326. for N := 0 to High(TextPath.Path[M]) do
  327. TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
  328. end;
  329. procedure AlignTextRight(CurrentI: Integer);
  330. var
  331. w, i, M, N, PathStart, PathEnd, CharStart: Integer;
  332. Delta: TFloat;
  333. begin
  334. Delta := Round(ARect.Right - X - 1);
  335. PathStart := CharOffsets[LineStart];
  336. PathEnd := CharOffsets[CurrentI] - 1;
  337. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  338. begin
  339. CharStart := LineStart;
  340. w := 0;
  341. for i := LineStart to CurrentI - 1 do
  342. begin
  343. if w + Delta < Arect.Left then
  344. CharStart:= i + 1;
  345. w := w + CharWidths[i];
  346. end;
  347. if (Flags and DT_WORDBREAK <> 0) then
  348. if (CharStart > LineStart) and (Text[CharStart] <> ' ') then
  349. while (Text[CharStart] <> ' ') and (CharStart < CurrentI) do
  350. Inc(CharStart);
  351. PathStart := CharOffsets[CharStart];
  352. for M := 0 to PathStart - 1 do
  353. SetLength(TextPath.Path[M], 0);
  354. end;
  355. for M := PathStart to PathEnd do
  356. for N := 0 to High(TextPath.Path[M]) do
  357. TextPath.Path[M, N].X := TextPath.Path[M, N].X + Delta;
  358. end;
  359. procedure AlignTextLeft(CurrentI: Integer);
  360. var
  361. w, i, M, PathEnd, CharEnd: Integer;
  362. begin
  363. if (Flags and DT_SINGLELINE <> 0) and (Flags and DT_NOCLIP <> DT_NOCLIP) then
  364. begin
  365. CharEnd := LineStart;
  366. w := 0;
  367. for i := LineStart to CurrentI - 1 do
  368. begin
  369. w := w + CharWidths[i];
  370. if w <= (ARect.Right - ARect.Left) then
  371. CharEnd:= i + 1;
  372. end;
  373. if (Flags and DT_WORDBREAK <> 0) then
  374. if (CharEnd < CurrentI) and (Text[CharEnd] <> ' ') then
  375. while (Text[CharEnd] <> ' ') and (CharEnd > LineStart) do
  376. Dec(CharEnd);
  377. PathEnd := CharOffsets[CharEnd] - 1;
  378. for M := PathEnd + 1 to CharOffsets[CurrentI] - 1 do
  379. SetLength(TextPath.Path[M], 0);
  380. end;
  381. end;
  382. procedure AlignTextJustify(CurrentI: Integer);
  383. var
  384. L, M, N, PathStart, PathEnd: Integer;
  385. SpcDelta, SpcDeltaInc: TFloat;
  386. begin
  387. if (SpcCount < 1) or (Ord(Text[CurrentI]) = CHAR_CR) then
  388. Exit;
  389. SpcDelta := (ARect.Right - X - 1) / SpcCount;
  390. SpcDeltaInc := SpcDelta;
  391. L := LineStart;
  392. // Trim leading spaces ...
  393. while (L < CurrentI) and (Ord(Text[L]) = CHAR_SP) do Inc(L);
  394. // Now find first space char in line ...
  395. while (L < CurrentI) and (Ord(Text[L]) <> CHAR_SP) do Inc(L);
  396. PathStart := CharOffsets[L - 1];
  397. repeat
  398. M := L + 1;
  399. while (M < CurrentI) and (Ord(Text[M]) <> CHAR_SP) do Inc(M);
  400. PathEnd := CharOffsets[M];
  401. L := M;
  402. for M := PathStart to PathEnd - 1 do
  403. for N := 0 to High(TextPath.Path[M]) do
  404. TextPath.Path[M, N].X := TextPath.Path[M, N].X + SpcDeltaInc;
  405. SpcDeltaInc := SpcDeltaInc + SpcDelta;
  406. PathStart := PathEnd;
  407. until L >= CurrentI;
  408. end;
  409. procedure AlignLine(CurrentI: Integer);
  410. begin
  411. if Assigned(TextPath) and (Length(TextPath.Path) > 0) then
  412. case (Flags and DT_HORZ_ALIGN_MASK) of
  413. DT_LEFT : AlignTextLeft(CurrentI);
  414. DT_CENTER : AlignTextCenter(CurrentI);
  415. DT_RIGHT : AlignTextRight(CurrentI);
  416. DT_JUSTIFY: AlignTextJustify(CurrentI);
  417. end;
  418. end;
  419. procedure AddSpace;
  420. begin
  421. Inc(SpcCount);
  422. X := X + SpcX;
  423. end;
  424. procedure NewLine(CurrentI: Integer);
  425. begin
  426. if (Flags and DT_SINGLELINE <> 0) then
  427. begin
  428. AddSpace;
  429. Exit;
  430. end;
  431. AlignLine(CurrentI);
  432. X := ARect.Left;
  433. Y := Y + (Metrics.ascent + Metrics.descent) / Metrics.designUnitsPerEm * EmSize * PixelPerDip; // was tmHeight
  434. LineStart := CurrentI;
  435. SpcCount := 0;
  436. end;
  437. function MeasureTextX(const S: string): Integer;
  438. var
  439. I: Integer;
  440. begin
  441. Result := 0;
  442. for I := 1 to Length(S) do
  443. begin
  444. CharValue := Ord(S[I]);
  445. IDWriteFontFaceFixed(FontFace).GetGDICompatibleGlyphMetrics(EmSize,
  446. PixelPerDip, nil, True, @CharValue, 1, GlyphMetrics);
  447. Inc(Result, Round(GlyphMetrics.advanceWidth / Metrics.designUnitsPerEm * EmSize * PixelPerDip));
  448. end;
  449. end;
  450. function NeedsNewLine(X: Single): Boolean;
  451. begin
  452. Result := (ARect.Right > ARect.Left) and (X > ARect.Right);
  453. end;
  454. begin
  455. // get interoperability layer
  456. HR := DWriteFactory.GetGdiInterop(GDIInterop);
  457. OleCheck(HR);
  458. // get font face from GDI
  459. HR := GDIInterop.CreateFontFaceFromHdc(DC, FontFace);
  460. OleCheck(HR);
  461. FontFace.GetMetrics(Metrics);
  462. EmSize := FontHeight * 96 / 72;
  463. PixelPerDip := 1;
  464. SpcCount := 0;
  465. LineStart := 0;
  466. OwnedPath := nil;
  467. if (Path <> nil) then
  468. begin
  469. if (Path is TFlattenedPath) then
  470. begin
  471. TextPath := TFlattenedPath(Path);
  472. TextPath.Clear;
  473. end
  474. else
  475. begin
  476. OwnedPath := TFlattenedPath.Create;
  477. TextPath := OwnedPath;
  478. end
  479. end else
  480. TextPath := nil;
  481. TextLen := Length(Text);
  482. X := ARect.Left;
  483. Y := ARect.Top + Metrics.ascent / Metrics.designUnitsPerEm * EmSize * PixelPerDip;
  484. XMax := X;
  485. if not Assigned(Path) or (ARect.Right = ARect.Left) then
  486. MaxRight := MaxSingle //either measuring Text or unbounded Text
  487. else
  488. MaxRight := ARect.Right;
  489. SetLength(CharOffsets, TextLen + 1);
  490. CharOffsets[0] := 0;
  491. SetLength(CharWidths, TextLen);
  492. CurrentChar := CHAR_SP;
  493. IDWriteFontFaceFixed(FontFace).GetGDICompatibleGlyphMetrics(EmSize,
  494. PixelPerDip, nil, True, PWORD(@CurrentChar), 1, GlyphMetrics);
  495. SpcX := Round(GlyphMetrics.advanceWidth / Metrics.designUnitsPerEm * EmSize * PixelPerDip); // was gmCellIncX
  496. if (Flags and DT_SINGLELINE <> 0) or (ARect.Left = ARect.Right) then
  497. begin
  498. // ignore justify when forcing singleline ...
  499. if (Flags and DT_JUSTIFY = DT_JUSTIFY) then
  500. Flags := Flags and not DT_JUSTIFY;
  501. // ignore wordbreak when forcing singleline ...
  502. //if (Flags and DT_WORDBREAK = DT_WORDBREAK) then
  503. // Flags := Flags and not DT_WORDBREAK;
  504. MaxRight := MaxSingle;
  505. end;
  506. // Batch whole path construction so we can be sure that the path isn't rendered
  507. // while we're still modifying it.
  508. if (TextPath <> nil) then
  509. TextPath.BeginUpdate;
  510. for I := 1 to TextLen do
  511. begin
  512. CharValue := Ord(Text[I]);
  513. if CharValue <= 32 then
  514. begin
  515. if (Flags and DT_SINGLELINE = DT_SINGLELINE) then
  516. CharValue := CHAR_SP;
  517. if Assigned(TextPath) then
  518. CharOffsets[I] := Length(TextPath.Path);
  519. CharWidths[i - 1]:= SpcX;
  520. case CharValue of
  521. CHAR_CR: NewLine(I);
  522. CHAR_NL: ;
  523. CHAR_SP:
  524. begin
  525. if Flags and DT_WORDBREAK = DT_WORDBREAK then
  526. begin
  527. J := I + 1;
  528. while (J <= TextLen) and
  529. ([Ord(Text[J])] * [CHAR_CR, CHAR_NL, CHAR_SP] = []) do
  530. Inc(J);
  531. S := Copy(Text, I, J - I);
  532. if NeedsNewLine(X + MeasureTextX(S)) then
  533. NewLine(I) else
  534. AddSpace;
  535. end else
  536. begin
  537. if NeedsNewLine(X + SpcX) then
  538. NewLine(I)
  539. else
  540. AddSpace;
  541. end;
  542. end;
  543. end;
  544. end
  545. else
  546. begin
  547. HR := FontFace.GetGlyphIndices(Cardinal(CharValue), 1, GlyphIndex);
  548. OleCheck(HR);
  549. HR := IDWriteFontFaceFixed(FontFace).GetGDICompatibleGlyphMetrics(EmSize,
  550. PixelPerDip, nil, True, @GlyphIndex, 1, GlyphMetrics);
  551. OleCheck(HR);
  552. CharAdvance := GlyphMetrics.advanceWidth / Metrics.designUnitsPerEm * EmSize * PixelPerDip;
  553. if X + CharAdvance <= MaxRight then
  554. begin
  555. TextGeometrySink := TTextGeometrySink.Create(TextPath, X, Y);
  556. try
  557. HR := FontFace.GetGlyphRunOutline(EmSize, @GlyphIndex, nil, nil, 1,
  558. False, False, TextGeometrySink);
  559. OleCheck(HR);
  560. finally
  561. TextGeometrySink.Free;
  562. end;
  563. if Assigned(TextPath) then
  564. CharOffsets[I] := Length(TextPath.Path);
  565. CharWidths[I - 1] := Round(CharAdvance);
  566. end
  567. else
  568. begin
  569. if Ord(Text[I - 1]) = CHAR_SP then
  570. begin
  571. // this only happens without DT_WORDBREAK
  572. X := X - SpcX;
  573. Dec(SpcCount);
  574. end;
  575. // the current glyph doesn't fit so a word must be split since
  576. // it fills more than a whole line ...
  577. NewLine(I - 1);
  578. TextGeometrySink := TTextGeometrySink.Create(TextPath, X, Y);
  579. try
  580. HR := FontFace.GetGlyphRunOutline(EmSize, @GlyphIndex, nil, nil, 1,
  581. False, False, TextGeometrySink);
  582. OleCheck(HR);
  583. finally
  584. TextGeometrySink.Free;
  585. end;
  586. if Assigned(TextPath) then
  587. CharOffsets[I] := Length(TextPath.Path);
  588. CharWidths[I - 1] := Round(CharAdvance);
  589. end;
  590. X := X + CharAdvance;
  591. if X > XMax then XMax := X;
  592. end;
  593. end;
  594. if [(Flags and DT_HORZ_ALIGN_MASK)] * [DT_LEFT, DT_CENTER, DT_RIGHT] <> [] then
  595. AlignLine(TextLen);
  596. YMax := Y + Metrics.descent / Metrics.designUnitsPerEm * EmSize * PixelPerDip;
  597. X := ARect.Right - XMax;
  598. Y := ARect.Bottom - YMax;
  599. if Flags and (DT_VCENTER or DT_BOTTOM) <> 0 then
  600. begin
  601. if Flags and DT_VCENTER <> 0 then
  602. Y := Y * 0.5;
  603. if Assigned(TextPath) then
  604. for I := 0 to High(TextPath.Path) do
  605. for J := 0 to High(TextPath.Path[I]) do
  606. TextPath.Path[I, J].Y := TextPath.Path[I, J].Y + Y;
  607. end;
  608. if (Path <> nil) then
  609. begin
  610. TextPath.EndPath; // TODO : Why is this needed?
  611. if (Path <> TextPath) then
  612. Path.Assign(TextPath);
  613. TextPath.EndUpdate;
  614. OwnedPath.Free;
  615. end;
  616. end;
  617. procedure TextToPath(Font: HFONT; const FontHeight: Integer; Path: TCustomPath;
  618. const ARect: TFloatRect; const Text: string; Flags: Cardinal = 0);
  619. var
  620. DC: HDC;
  621. SavedFont: HFONT;
  622. begin
  623. DC := GetDC(0);
  624. try
  625. SavedFont := SelectObject(DC, Font);
  626. InternalTextToPath(DC, FontHeight, Path, ARect, Text, Flags);
  627. SelectObject(DC, SavedFont);
  628. finally
  629. ReleaseDC(0, DC);
  630. end;
  631. end;
  632. function TextToPolyPolygon(Font: HFONT; const FontHeight: Integer;
  633. const ARect: TFloatRect; const Text: string;
  634. Flags: Cardinal = 0): TArrayOfArrayOfFloatPoint;
  635. var
  636. Path: TFlattenedPath;
  637. begin
  638. Path := TFlattenedPath.Create;
  639. try
  640. TextToPath(Font, FontHeight, Path, ARect, Text, Flags);
  641. Result := Path.Path;
  642. finally
  643. Path.Free;
  644. end;
  645. end;
  646. function MeasureTextDC(DC: HDC; const FontHeight: Integer; const ARect: TFloatRect;
  647. const Text: string; Flags: Cardinal): TFloatRect;
  648. begin
  649. Result := ARect;
  650. InternalTextToPath(DC, FontHeight, nil, Result, Text, Flags);
  651. Result.Left := Round(Result.Left);
  652. Result.Top := Round(Result.Top);
  653. Result.Right := Round(Result.Right);
  654. Result.Bottom := Round(Result.Bottom);
  655. end;
  656. function MeasureText(Font: HFONT; const FontHeight: Integer;
  657. const ARect: TFloatRect; const Text: string; Flags: Cardinal): TFloatRect;
  658. var
  659. DC: HDC;
  660. SavedFont: HFONT;
  661. begin
  662. DC := GetDC(0);
  663. try
  664. SavedFont := SelectObject(DC, Font);
  665. Result := MeasureTextDC(DC, FontHeight, ARect, Text, Flags);
  666. SelectObject(DC, SavedFont);
  667. finally
  668. ReleaseDC(0, DC);
  669. end;
  670. end;
  671. end.