25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

826 lines
24 KiB

  1. unit utsTextBlock;
  2. {$IFDEF FPC}
  3. {$mode objfpc}{$H+}
  4. {$ENDIF}
  5. interface
  6. uses
  7. Classes, SysUtils,
  8. utsUtils, utsTypes, utsFont, utsCharCache, utsContext;
  9. type
  10. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  11. TtsLineItemType = (
  12. tsItemTypeUnknown,
  13. tsItemTypeFont,
  14. tsItemTypeColor,
  15. tsItemTypeText,
  16. tsItemTypeSpace,
  17. tsItemTypeLineBreak,
  18. tsItemTypeTab,
  19. tsItemTypeSpacing);
  20. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  21. TtsLineFlag = (
  22. tsLastItemIsSpace, // is set if the last item was a space item
  23. tsMetaValid, // is set if the line meta data is valid
  24. tsAutoLineBreak // is set if the linebreak was set automatically
  25. );
  26. TtsLineFlags = set of TtsLineFlag;
  27. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  28. PtsLineItem = ^TtsLineItem;
  29. TtsLineItem = packed record
  30. Next: PtsLineItem;
  31. Prev: PtsLineItem;
  32. ItemType: TtsLineItemType;
  33. case TtsLineItemType of
  34. tsItemTypeFont: (
  35. Font: TtsFont
  36. );
  37. tsItemTypeColor: (
  38. Color: TtsColor4f;
  39. );
  40. tsItemTypeText, tsItemTypeSpace: (
  41. Text: PWideChar; // text of this item
  42. TextWidth: Integer; // width of text (in pixel)
  43. );
  44. tsItemTypeSpacing: (
  45. Spacing: Integer;
  46. );
  47. tsItemTypeTab: (
  48. TabWidth: Integer; // with of tab (in pixel)
  49. );
  50. end;
  51. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  52. PtsBlockLine = ^TtsBlockLine;
  53. TtsBlockLine = packed record
  54. Next: PtsBlockLine;
  55. First: PtsLineItem;
  56. Last: PtsLineItem;
  57. Flags: TtsLineFlags;
  58. meta: packed record
  59. Width: Integer; // absolut width of this line
  60. Height: Integer; // absolute height of this line
  61. Spacing: Integer; // spacing between lines
  62. Ascent: Integer; // text ascent
  63. SpaceCount: Integer; // number of words in this line
  64. end;
  65. end;
  66. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  67. TtsBlockRenderer = class(TtsRenderRefGenerator)
  68. private
  69. fCharCache: TtsCharCache;
  70. protected
  71. property CharCache: TtsCharCache read fCharCache;
  72. procedure BeginRender; virtual; abstract;
  73. procedure EndRender; virtual; abstract;
  74. function GetDrawPos: TtsPosition; virtual; abstract;
  75. procedure SetDrawPos(const aValue: TtsPosition); virtual; abstract;
  76. procedure MoveDrawPos(const aOffset: TtsPosition); virtual; abstract;
  77. procedure SetColor(const aValue: TtsColor4f); virtual; abstract;
  78. procedure Render(const aRenderRef: TtsRenderRef; const aForcedWidth: Integer = 0); virtual; abstract;
  79. public
  80. constructor Create(const aContext: TtsContext; const aFormat: TtsFormat);
  81. destructor Destroy; override;
  82. end;
  83. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  84. TtsTextBlock = class(TtsRefManager)
  85. private
  86. fRenderer: TtsBlockRenderer;
  87. fTop: Integer;
  88. fLeft: Integer;
  89. fWidth: Integer;
  90. fHeight: Integer;
  91. fFlags: TtsBlockFlags;
  92. fVertAlign: TtsVertAlignment;
  93. fHorzAlign: TtsHorzAlignment;
  94. fClipping: TtsClipping;
  95. fCurrentChars: TtsChars;
  96. fCurrentColor: TtsColor4f;
  97. fCurrentFont: TtsFont;
  98. fFirstLine: PtsBlockLine;
  99. fLastLine: PtsBlockLine;
  100. function GetRect: TtsRect;
  101. function PushLineItem(const aItem: PtsLineItem): Boolean;
  102. procedure PushSpacing(const aWidth: Integer);
  103. procedure PushNewLine;
  104. procedure FreeLineItem(var aItem: PtsLineItem);
  105. procedure FreeLineItems(var aItem: PtsLineItem);
  106. procedure FreeLines(var aItem: PtsBlockLine);
  107. function SplitText(aText: PWideChar): PtsLineItem;
  108. function SplitIntoLines(aItem: PtsLineItem): Boolean;
  109. procedure TrimSpaces(const aLine: PtsBlockLine);
  110. procedure UpdateLineMeta(const aLine: PtsBlockLine);
  111. public
  112. procedure ChangeFont(const aFont: TtsFont);
  113. procedure ChangeColor(const aColor: TtsColor4f);
  114. public
  115. property Rect: TtsRect read GetRect;
  116. property Width: Integer read fWidth;
  117. property Height: Integer read fHeight;
  118. property Flags: TtsBlockFlags read fFlags;
  119. property Top: Integer read fTop write fTop;
  120. property Left: Integer read fLeft write fLeft;
  121. property VertAlign: TtsVertAlignment read fVertAlign write fVertAlign;
  122. property HorzAlign: TtsHorzAlignment read fHorzAlign write fHorzAlign;
  123. property Clipping: TtsClipping read fClipping write fClipping;
  124. property CurrentColor: TtsColor4f read fCurrentColor write ChangeColor;
  125. property CurrentFont: TtsFont read fCurrentFont write ChangeFont;
  126. function GetActualBlockHeight: Integer;
  127. procedure TextOutA(const aText: PAnsiChar);
  128. procedure TextOutW(const aText: PWideChar);
  129. function GetTextWidthA(const aText: PAnsiChar): Integer;
  130. function GetTextWidthW(const aText: PWideChar): Integer;
  131. procedure Render;
  132. constructor Create(const aRenderer: TtsBlockRenderer; const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags);
  133. destructor Destroy; override;
  134. end;
  135. implementation
  136. uses
  137. math,
  138. utsChar;
  139. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  140. //TtsBlockRenderer//////////////////////////////////////////////////////////////////////////////////////////////////////
  141. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  142. constructor TtsBlockRenderer.Create(const aContext: TtsContext; const aFormat: TtsFormat);
  143. begin
  144. inherited Create(aContext, aFormat);
  145. fCharCache := TtsCharCache.Create(self);
  146. end;
  147. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  148. destructor TtsBlockRenderer.Destroy;
  149. begin
  150. FreeAndNil(fCharCache);
  151. inherited Destroy;
  152. end;
  153. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  154. //TtsTextBlock//////////////////////////////////////////////////////////////////////////////////////////////////////////
  155. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  156. function TtsTextBlock.GetRect: TtsRect;
  157. begin
  158. result.Left := fLeft;
  159. result.Top := fTop;
  160. result.Right := fLeft + fWidth;
  161. result.Bottom := fTop + fHeight;
  162. end;
  163. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  164. function TtsTextBlock.PushLineItem(const aItem: PtsLineItem): Boolean;
  165. begin
  166. result := false;
  167. if not Assigned(fLastLine) then
  168. PushNewLine;
  169. if not Assigned(fLastLine^.First) and
  170. (aItem^.ItemType in [tsItemTypeSpace, tsItemTypeSpacing]) then
  171. exit; // di not add line space or line spacing if line is empty
  172. if Assigned(fLastLine^.Last) then begin
  173. aItem^.Prev := fLastLine^.Last;
  174. aItem^.Next := nil;
  175. fLastLine^.Last^.Next := aItem;
  176. fLastLine^.Last := aItem;
  177. end;
  178. if not Assigned(fLastLine^.First) then begin
  179. fLastLine^.First := aItem;
  180. fLastLine^.Last := aItem;
  181. end;
  182. case aItem^.ItemType of
  183. tsItemTypeSpace, tsItemTypeText:
  184. fLastLine^.meta.Width := fLastLine^.meta.Width + aItem^.TextWidth;
  185. tsItemTypeSpacing:
  186. fLastLine^.meta.Width := fLastLine^.meta.Width + aItem^.Spacing;
  187. tsItemTypeTab:
  188. fLastLine^.meta.Width := fLastLine^.meta.Width + aItem^.TabWidth;
  189. end;
  190. result := true;
  191. end;
  192. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  193. procedure TtsTextBlock.PushSpacing(const aWidth: Integer);
  194. var
  195. p: PtsLineItem;
  196. begin
  197. if (aWidth <= 0) then
  198. exit;
  199. new(p);
  200. FillChar(p^, SizeOf(p^), #0);
  201. p^.ItemType := tsItemTypeSpacing;
  202. p^.Spacing := aWidth;
  203. PushLineItem(p);
  204. end;
  205. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  206. procedure TtsTextBlock.PushNewLine;
  207. var
  208. p: PtsBlockLine;
  209. begin
  210. TrimSpaces(fLastLine);
  211. new(p);
  212. FillChar(p^, SizeOf(p^), #0);
  213. UpdateLineMeta(p);
  214. if Assigned(fLastLine) then begin
  215. fLastLine^.Next := p;
  216. fLastLine := p;
  217. end;
  218. if not Assigned(fFirstLine) then begin
  219. fFirstLine := p;
  220. fLastLine := p;
  221. end;
  222. end;
  223. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  224. procedure TtsTextBlock.FreeLineItem(var aItem: PtsLineItem);
  225. begin
  226. if Assigned(aItem^.Prev) then
  227. aItem^.Prev^.Next := aItem^.Next;
  228. if Assigned(aItem^.Next) then
  229. aItem^.Next^.Prev := aItem^.Prev;
  230. case aItem^.ItemType of
  231. tsItemTypeText, tsItemTypeSpace:
  232. tsStrDispose(aItem^.Text);
  233. end;
  234. Dispose(aItem);
  235. aItem := nil;
  236. end;
  237. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  238. procedure TtsTextBlock.FreeLineItems(var aItem: PtsLineItem);
  239. var
  240. p: PtsLineItem;
  241. begin
  242. while Assigned(aItem) do begin
  243. p := aItem;
  244. aItem := aItem^.Next;
  245. FreeLineItem(p);
  246. end;
  247. end;
  248. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  249. procedure TtsTextBlock.FreeLines(var aItem: PtsBlockLine);
  250. var
  251. p: PtsBlockLine;
  252. begin
  253. while Assigned(aItem) do begin
  254. p := aItem;
  255. aItem := aItem^.Next;
  256. FreeLineItems(p^.First);
  257. p^.Last := nil;
  258. Dispose(p);
  259. end;
  260. end;
  261. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  262. function TtsTextBlock.SplitText(aText: PWideChar): PtsLineItem;
  263. var
  264. TextBegin: PWideChar;
  265. TextLength: Integer;
  266. State: TtsLineItemType;
  267. LastItem: PtsLineItem;
  268. procedure AddItem(const aItem: PtsLineItem);
  269. begin
  270. if Assigned(result) then begin
  271. LastItem^.Next := aItem;
  272. aItem^.Prev := LastItem;
  273. aItem^.Next := nil;
  274. LastItem := aItem;
  275. end;
  276. if not Assigned(result) then begin
  277. result := aItem;
  278. LastItem := aItem;
  279. end;
  280. end;
  281. procedure ExtractWord;
  282. var
  283. p: PtsLineItem;
  284. Text: PWideChar;
  285. begin
  286. if (State = tsItemTypeUnknown) then
  287. exit;
  288. new(p);
  289. FillChar(p^, SizeOf(p^), #0);
  290. p^.ItemType := State;
  291. case State of
  292. tsItemTypeText, tsItemTypeSpace: begin
  293. p^.Text := tsStrAlloc(TextLength);
  294. Text := p^.Text;
  295. while (TextBegin <> aText) do begin
  296. Text^ := TextBegin^;
  297. inc(Text, 1);
  298. inc(TextBegin, 1);
  299. end;
  300. AddItem(p);
  301. end;
  302. tsItemTypeLineBreak, tsItemTypeTab: begin
  303. AddItem(p);
  304. end;
  305. else
  306. Dispose(p);
  307. end;
  308. TextBegin := aText;
  309. TextLength := 0;
  310. end;
  311. begin
  312. result := nil;
  313. LastItem := nil;
  314. TextBegin := aText;
  315. TextLength := 0;
  316. State := tsItemTypeUnknown;
  317. if not Assigned(aText) then
  318. exit;
  319. while (aText^ <> #0) do begin
  320. case aText^ of
  321. // line breaks
  322. #$000D, #$000A: begin
  323. if (State <> tsItemTypeLineBreak) then begin
  324. ExtractWord;
  325. State := tsItemTypeLineBreak;
  326. end else if (TextBegin^ <> #13) or (aText^ <> #10) or (TextBegin + 1 < aText) then
  327. ExtractWord;
  328. end;
  329. // spaces
  330. #$0020: begin
  331. if (State <> tsItemTypeSpace) then
  332. ExtractWord;
  333. State := tsItemTypeSpace;
  334. end;
  335. // tabulator
  336. #$0009: begin
  337. ExtractWord;
  338. State := tsItemTypeTab;
  339. end;
  340. else
  341. if (State <> tsItemTypeText) then
  342. ExtractWord;
  343. State := tsItemTypeText;
  344. end;
  345. inc(aText, 1);
  346. inc(TextLength, 1);
  347. end;
  348. if (TextBegin <> aText) then
  349. ExtractWord;
  350. end;
  351. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  352. function TtsTextBlock.SplitIntoLines(aItem: PtsLineItem): Boolean;
  353. var
  354. p: PtsLineItem;
  355. tab: Integer;
  356. begin
  357. result := false;
  358. if not Assigned(fCurrentFont) then
  359. exit;
  360. result := true;
  361. while Assigned(aItem) do begin
  362. p := aItem;
  363. aItem := aItem^.Next;
  364. p^.Next := nil;
  365. p^.Prev := nil;
  366. if not Assigned(fLastLine) then
  367. PushNewLine;
  368. case p^.ItemType of
  369. tsItemTypeText, tsItemTypeSpace: begin
  370. // increment word counter
  371. if (p^.ItemType = tsItemTypeSpace) then begin
  372. if not (tsLastItemIsSpace in fLastLine^.Flags) then
  373. inc(fLastLine^.meta.SpaceCount, 1);
  374. Include(fLastLine^.Flags, tsLastItemIsSpace);
  375. end else
  376. Exclude(fLastLine^.Flags, tsLastItemIsSpace);
  377. // update and check line width
  378. p^.TextWidth := GetTextWidthW(p^.Text);
  379. if (tsBlockFlagWordWrap in fFlags) and
  380. (fLastLine^.meta.Width + p^.TextWidth > fWidth) then
  381. begin
  382. if (fLastLine^.meta.Width = 0) then begin
  383. if not PushLineItem(p) then // if is first word, than add anyway
  384. FreeLineItem(p);
  385. p := nil;
  386. end;
  387. include(fLastLine^.Flags, tsAutoLineBreak);
  388. PushNewLine;
  389. end;
  390. // add item
  391. if Assigned(p) then begin
  392. if not PushLineItem(p) then
  393. FreeLineItem(p);
  394. PushSpacing(fCurrentFont.CharSpacing);
  395. end;
  396. end;
  397. tsItemTypeLineBreak: begin
  398. if not PushLineItem(p) then
  399. FreeLineItem(p);
  400. PushNewLine;
  401. end;
  402. tsItemTypeTab: begin
  403. tab := fCurrentFont.TabWidth * fCurrentFont.Metric.Size;
  404. p^.TabWidth := (1 + fLastLine^.meta.Width div tab) * tab - fLastLine^.meta.Width;
  405. if not PushLineItem(p) then
  406. FreeLineItem(p);
  407. end;
  408. else
  409. raise EtsException.Create('unexpected line item');
  410. end;
  411. end;
  412. end;
  413. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  414. procedure TtsTextBlock.TrimSpaces(const aLine: PtsBlockLine);
  415. procedure Trim(var aItem: PtsLineItem; const aMoveNext: Boolean);
  416. var
  417. tmp, p: PtsLineItem;
  418. IsFirst: Boolean;
  419. begin
  420. IsFirst := true;
  421. p := aItem;
  422. while Assigned(p) do begin
  423. tmp := p;
  424. if aMoveNext then
  425. p := p^.Next
  426. else
  427. p := p^.Prev;
  428. case tmp^.ItemType of
  429. tsItemTypeText: begin //done
  430. break;
  431. end;
  432. tsItemTypeSpace,
  433. tsItemTypeSpacing: begin
  434. // update line meta
  435. if (tmp^.ItemType = tsItemTypeSpace) then begin
  436. aLine^.meta.Width := aLine^.meta.Width - tmp^.TextWidth;
  437. dec(aLine^.meta.SpaceCount, 1);
  438. end else
  439. aLine^.meta.Width := aLine^.meta.Width - tmp^.Spacing;
  440. FreeLineItem(tmp);
  441. if IsFirst then
  442. aItem := p;
  443. end;
  444. else
  445. IsFirst := false;
  446. end;
  447. end;
  448. end;
  449. begin
  450. if not Assigned(aLine) then
  451. exit;
  452. Trim(aLine^.First, true);
  453. Trim(aLine^.Last, false);
  454. end;
  455. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  456. procedure TtsTextBlock.UpdateLineMeta(const aLine: PtsBlockLine);
  457. var
  458. metric: TtsTextMetric;
  459. begin
  460. if not Assigned(fCurrentFont) or
  461. not Assigned(aLine) then
  462. exit;
  463. fCurrentFont.GetTextMetric(metric);
  464. if (tsMetaValid in aLine^.Flags) then begin
  465. aLine^.meta.Height := max(
  466. aLine^.meta.Height,
  467. metric.LineHeight);
  468. aLine^.meta.Spacing := max(
  469. aLine^.meta.Spacing,
  470. metric.LineSpacing);
  471. aLine^.meta.Ascent := max(
  472. aLine^.meta.Ascent,
  473. metric.Ascent);
  474. end else begin
  475. Include(aLine^.Flags, tsMetaValid);
  476. aLine^.meta.Height := metric.LineHeight;
  477. aLine^.meta.Spacing := metric.LineSpacing;
  478. aLine^.meta.Ascent := metric.Ascent;
  479. end;
  480. end;
  481. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  482. procedure TtsTextBlock.ChangeFont(const aFont: TtsFont);
  483. var
  484. p: PtsLineItem;
  485. begin
  486. if not Assigned(aFont) then
  487. exit;
  488. New(p);
  489. FillChar(p^, SizeOf(p^), #0);
  490. fCurrentFont := aFont;
  491. fCurrentChars := fRenderer.fCharCache.Chars[fCurrentFont];
  492. p^.ItemType := tsItemTypeFont;
  493. p^.Font := fCurrentFont;
  494. PushLineItem(p);
  495. UpdateLineMeta(fLastLine);
  496. end;
  497. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  498. procedure TtsTextBlock.ChangeColor(const aColor: TtsColor4f);
  499. var
  500. p: PtsLineItem;
  501. begin
  502. New(p);
  503. FillChar(p^, SizeOf(p^), #0);
  504. fCurrentColor := aColor;
  505. p^.ItemType := tsItemTypeColor;
  506. p^.Color := fCurrentColor;
  507. PushLineItem(p);
  508. end;
  509. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  510. function TtsTextBlock.GetActualBlockHeight: Integer;
  511. var
  512. line: PtsBlockLine;
  513. begin
  514. result := 0;
  515. line := fFirstLine;
  516. while Assigned(line) do begin
  517. result := result + line^.meta.Height;
  518. line := line^.Next;
  519. end;
  520. end;
  521. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  522. procedure TtsTextBlock.TextOutA(const aText: PAnsiChar);
  523. var
  524. tmp: PWideChar;
  525. begin
  526. tmp := fRenderer.Context.AnsiToWide(aText);
  527. try
  528. TextOutW(tmp);
  529. finally
  530. tsStrDispose(tmp);
  531. end;
  532. end;
  533. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  534. procedure TtsTextBlock.TextOutW(const aText: PWideChar);
  535. var
  536. p: PtsLineItem;
  537. begin
  538. p := SplitText(aText);
  539. if not SplitIntoLines(p) then
  540. FreeLineItems(p);
  541. end;
  542. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  543. function TtsTextBlock.GetTextWidthA(const aText: PAnsiChar): Integer;
  544. begin
  545. result := 0;
  546. if Assigned(fCurrentChars) then
  547. result := fCurrentChars.GetTextWidthA(aText);
  548. end;
  549. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  550. function TtsTextBlock.GetTextWidthW(const aText: PWideChar): Integer;
  551. begin
  552. result := 0;
  553. if Assigned(fCurrentChars) then
  554. result := fCurrentChars.GetTextWidthW(aText);
  555. end;
  556. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  557. procedure TtsTextBlock.Render;
  558. var
  559. c: PWideChar;
  560. pos: TtsPosition;
  561. x, y, tmp, tab: Integer;
  562. ExtraSpaceTotal, ExtraSpaceActual: Single;
  563. r: TtsRect;
  564. line: PtsBlockLine;
  565. item: PtsLineItem;
  566. font: TtsFont;
  567. chars: TtsChars;
  568. char: TtsChar;
  569. metric: TtsTextMetric;
  570. draw: Boolean;
  571. procedure DrawItem;
  572. begin
  573. case item^.ItemType of
  574. tsItemTypeFont: begin
  575. font := item^.Font;
  576. font.GetTextMetric(metric);
  577. chars := fRenderer.fCharCache.Chars[font];
  578. end;
  579. tsItemTypeColor: begin
  580. fRenderer.SetColor(item^.Color);
  581. end;
  582. tsItemTypeText: begin
  583. if draw and Assigned(font) then begin
  584. c := item^.Text;
  585. while (c^ <> #0) do begin
  586. if Assigned(chars) then begin
  587. char := chars.AddChar(c^);
  588. if Assigned(char) then begin
  589. fRenderer.MoveDrawPos(tsPosition(0, -metric.BaseLineOffset));
  590. fRenderer.Render(char.RenderRef);
  591. fRenderer.MoveDrawPos(tsPosition(char.GlyphMetric.Advance + font.CharSpacing, metric.BaseLineOffset));
  592. end;
  593. end;
  594. inc(c);
  595. end;
  596. end;
  597. end;
  598. tsItemTypeSpace: begin
  599. if draw and Assigned(font) then begin
  600. ExtraSpaceActual := ExtraSpaceActual + ExtraSpaceTotal;
  601. c := item^.Text;
  602. while (c^ <> #0) do begin
  603. if Assigned(chars) then begin
  604. char := chars.AddChar(c^);
  605. if Assigned(char) then begin
  606. if (font.Metric.Style * [tsStyleUnderline, tsStyleStrikeout] <> []) then begin
  607. fRenderer.MoveDrawPos(tsPosition(0, -metric.BaseLineOffset));
  608. fRenderer.Render(char.RenderRef);
  609. fRenderer.MoveDrawPos(tsPosition(char.GlyphMetric.Advance + font.CharSpacing, metric.BaseLineOffset));
  610. end else begin
  611. fRenderer.MoveDrawPos(tsPosition(char.GlyphMetric.Advance + font.CharSpacing, 0));
  612. end;
  613. end;
  614. end;
  615. inc(c);
  616. end;
  617. tmp := Trunc(ExtraSpaceActual);
  618. ExtraSpaceActual := ExtraSpaceActual - tmp;
  619. if (font.Metric.Style * [tsStyleUnderline, tsStyleStrikeout] <> []) then begin
  620. if Assigned(chars) then begin
  621. char := chars.AddChar(#0);
  622. if Assigned(char) then
  623. fRenderer.Render(char.RenderRef, tmp);
  624. // TODO draw lines; maybe with a temporary created fake char or something like an empty char?
  625. end;
  626. end;
  627. fRenderer.MoveDrawPos(tsPosition(tmp, 0));
  628. end;
  629. end;
  630. tsItemTypeLineBreak: begin
  631. // because this should be the last item in a line, we have nothing to do here
  632. end;
  633. tsItemTypeTab: begin
  634. // get current x pos and round it to TabWidth
  635. pos := fRenderer.GetDrawPos;
  636. tab := font.TabWidth * font.Metric.Size;
  637. if (tab = 0) then
  638. tab := 1;
  639. pos.x := Left + (1 + (pos.x - Left) div tab) * tab;
  640. fRenderer.SetDrawPos(pos);
  641. end;
  642. tsItemTypeSpacing: begin
  643. fRenderer.MoveDrawPos(tsPosition(item^.Spacing, 0));
  644. end;
  645. end;
  646. end;
  647. procedure DrawLine;
  648. begin
  649. // check vertical clipping
  650. case Clipping of
  651. tsClipCharBorder, tsClipWordBorder:
  652. draw := (y + line^.meta.Height >= r.Top) and (y <= r.Bottom);
  653. tsClipCharComplete, tsClipWordComplete:
  654. draw := (y >= r.Top) and (y + line^.meta.Height <= r.Bottom);
  655. else
  656. draw := true;
  657. end;
  658. // check horizontal alignment
  659. x := r.Left;
  660. ExtraSpaceTotal := 0;
  661. ExtraSpaceActual := 0;
  662. case HorzAlign of
  663. tsHorzAlignCenter:
  664. x := r.Left + (Width div 2) - (line^.meta.Width div 2);
  665. tsHorzAlignRight:
  666. x := r.Right - line^.meta.Width;
  667. tsHorzAlignJustify:
  668. if (tsAutoLineBreak in line^.Flags) and (line^.meta.SpaceCount > 0) then
  669. ExtraSpaceTotal := (Width - line^.meta.Width) / line^.meta.SpaceCount;
  670. end;
  671. if draw then
  672. fRenderer.SetDrawPos(tsPosition(x, y + line^.meta.Ascent));
  673. inc(y, line^.meta.Height + line^.meta.Spacing);
  674. item := line^.First;
  675. while Assigned(item) do begin
  676. DrawItem;
  677. item := item^.Next;
  678. end;
  679. end;
  680. begin
  681. fRenderer.BeginRender;
  682. try
  683. // init variables
  684. y := Top;
  685. r := Rect;
  686. font := nil;
  687. line := fFirstLine;
  688. // check vertical alignment
  689. case VertAlign of
  690. tsVertAlignCenter:
  691. y := y + (Height div 2 - GetActualBlockHeight div 2);
  692. tsVertAlignBottom:
  693. y := y + (Height - GetActualBlockHeight);
  694. end;
  695. while Assigned(line) do begin
  696. DrawLine;
  697. line := line^.Next;
  698. end;
  699. finally
  700. fRenderer.EndRender;
  701. end;
  702. end;
  703. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  704. constructor TtsTextBlock.Create(const aRenderer: TtsBlockRenderer; const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags);
  705. begin
  706. inherited Create(aRenderer);
  707. fRenderer := aRenderer;
  708. fTop := aTop;
  709. fLeft := aLeft;
  710. fWidth := aWidth;
  711. fHeight := aHeight;
  712. fFlags := aFlags;
  713. end;
  714. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  715. destructor TtsTextBlock.Destroy;
  716. begin
  717. FreeLines(fFirstLine);
  718. fLastLine := nil;
  719. inherited Destroy;
  720. end;
  721. end.