選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

2174 行
66 KiB

  1. unit utsTextSuite;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, contnrs, math, syncobjs,
  6. utsTypes, utsUtils;
  7. type
  8. TtsImage = class;
  9. TtsFont = class;
  10. TtsFontGenerator = class;
  11. TtsRenderer = class;
  12. TtsContext = class;
  13. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  14. TtsKernel1DItem = packed record
  15. Offset: Integer;
  16. Value: Single;
  17. DataOffset: Integer;
  18. end;
  19. TtsKernel1D = class
  20. public
  21. Size: Integer;
  22. Items: array of TtsKernel1DItem;
  23. ItemCount: Integer;
  24. constructor Create(const aRadius, aStrength: Single);
  25. end;
  26. TtsImageFunc = procedure(const aImage: TtsImage; X, Y: Integer; var aPixel: TtsColor4f; aArgs: Pointer);
  27. TtsImage = class(TObject)
  28. private
  29. fWidth: Integer;
  30. fHeight: Integer;
  31. fFormat: TtsFormat;
  32. fData: Pointer;
  33. fHasScanlines: Boolean;
  34. fScanlines: array of Pointer;
  35. function GetScanline(const aIndex: Integer): Pointer;
  36. function GetIsEmpty: Boolean;
  37. procedure SetData(const aData: Pointer; const aFormat: TtsFormat = tsFormatEmpty; const aWidth: Integer = 0; const aHeight: Integer = 0);
  38. procedure UpdateScanlines;
  39. public
  40. property IsEmpty: Boolean read GetIsEmpty;
  41. property Width: Integer read fWidth;
  42. property Height: Integer read fHeight;
  43. property Format: TtsFormat read fFormat;
  44. property Data: Pointer read fData;
  45. property Scanline[const aIndex: Integer]: Pointer read GetScanline;
  46. function GetPixelAt(const x, y: Integer; out aColor: TtsColor4f): Boolean;
  47. procedure Assign(const aImage: TtsImage);
  48. procedure CreateEmpty(const aFormat: TtsFormat; const aWidth, aHeight: Integer);
  49. procedure LoadFromFunc(const aFunc: TtsImageFunc; const aArgs: Pointer);
  50. procedure Resize(const aNewWidth, aNewHeight, X, Y: Integer);
  51. procedure FindMinMax(out aRect: TtsRect);
  52. procedure FillColor(const aColor: TtsColor4f; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  53. procedure FillPattern(const aPattern: TtsImage; X, Y: Integer; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  54. procedure BlendImage(const aImage: TtsImage; const X, Y: Integer);
  55. procedure Blur(const aHorzKernel, aVertKernel: TtsKernel1D; const aChannelMask: TtsColorChannels);
  56. procedure AddResizingBorder;
  57. constructor Create;
  58. destructor Destroy; override;
  59. end;
  60. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  61. TtsCharRenderRef = class(TObject);
  62. TtsChar = class(TObject)
  63. private
  64. fCharCode: WideChar;
  65. fGlyphOrigin: TtsPosition;
  66. fGlyphRect: TtsRect;
  67. fAdvance: Integer;
  68. fRenderRef: TtsCharRenderRef;
  69. public
  70. property CharCode: WideChar read fCharCode;
  71. property GlyphOrigin: TtsPosition read fGlyphOrigin write fGlyphOrigin;
  72. property GlyphRect: TtsRect read fGlyphRect write fGlyphRect;
  73. property Advance: Integer read fAdvance write fAdvance;
  74. property RenderRef: TtsCharRenderRef read fRenderRef write fRenderRef;
  75. constructor Create(const aCharCode: WideChar);
  76. end;
  77. TtsFontCharArray = packed record
  78. Chars: array [Byte] of TtsChar;
  79. CharCount: Byte;
  80. end;
  81. PtsFontCharArray = ^TtsFontCharArray;
  82. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  83. TtsFont = class(TObject)
  84. private
  85. fRenderer: TtsRenderer;
  86. fCreator: TtsFontGenerator;
  87. fProperties: TtsFontProperties;
  88. fCharSpacing: Integer;
  89. fTabWidth: Integer;
  90. fLineSpacing: Single;
  91. fChars: array[Byte] of PtsFontCharArray;
  92. fCreateChars: Boolean;
  93. function HasChar(const aCharCode: WideChar): Boolean;
  94. function GetChar(const aCharCode: WideChar): TtsChar;
  95. function GetCharCreate(const aCharCode: WideChar): TtsChar;
  96. procedure AddChar(const aCharCode: WideChar; const aChar: TtsChar); overload;
  97. protected
  98. constructor Create(const aRenderer: TtsRenderer; const aCreator: TtsFontGenerator; const aProperties: TtsFontProperties);
  99. public
  100. property CreateChars: Boolean read fCreateChars write fCreateChars;
  101. property Char[const aCharCode: WideChar]: TtsChar read GetChar;
  102. property Renderer: TtsRenderer read fRenderer;
  103. property Properties: TtsFontProperties read fProperties;
  104. property CharSpacing: Integer read fCharSpacing write fCharSpacing;
  105. property TabWidth: Integer read fTabWidth write fTabWidth;
  106. property LineSpacing: Single read fLineSpacing write fLineSpacing;
  107. function AddChar(const aCharCode: WideChar): TtsChar; overload;
  108. procedure AddCharRange(const aCharCodeBeg, aCharCodeEnd: WideChar);
  109. procedure RemoveChar(const aCharCode: WideChar);
  110. procedure ClearChars;
  111. function GetTextWidthW(aText: PWideChar): Integer;
  112. procedure GetTextMetric(out aMetric: TtsTextMetric);
  113. destructor Destroy; override;
  114. end;
  115. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  116. TtsPostProcessStepRange = record
  117. StartChar: WideChar;
  118. EndChar: WideChar;
  119. end;
  120. PtsPostProcessStepRange = ^TtsPostProcessStepRange;
  121. TtsFontProcessStepUsage = (
  122. tsUsageInclude,
  123. tsUsageExclude);
  124. TtsPostProcessStep = class(TObject)
  125. private
  126. fIncludeCharRange: TList;
  127. fExcludeCharRange: TList;
  128. procedure ClearList(const aList: TList);
  129. public
  130. function IsInRange(const aCharCode: WideChar): Boolean;
  131. procedure Execute(const aChar: TtsChar; const aCharImage: TtsImage); virtual; abstract;
  132. procedure AddUsageRange(const aUsage: TtsFontProcessStepUsage; const aStartChar, aEndChar: WideChar);
  133. procedure AddUsageChars(const aUsage: TtsFontProcessStepUsage; aChars: PWideChar);
  134. procedure ClearIncludeRange;
  135. procedure ClearExcludeRange;
  136. constructor Create;
  137. destructor Destroy; override;
  138. end;
  139. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  140. TtsFontGenerator = class(TObject)
  141. private
  142. fPostProcessSteps: TObjectList;
  143. function GetPostProcessStepCount: Integer;
  144. function GetPostProcessStep(const aIndex: Integer): TtsPostProcessStep;
  145. procedure DrawLine(const aChar: TtsChar; const aCharImage: TtsImage; aLinePosition, aLineSize: Integer);
  146. procedure DoPostProcess(const aChar: TtsChar; const aCharImage: TtsImage);
  147. protected
  148. function GetGlyphMetrics(const aFont: TtsFont; const aCharCode: WideChar; out aGlyphOrigin, aGlyphSize: TtsPosition; out aAdvance: Integer): Boolean; virtual; abstract;
  149. procedure GetCharImage(const aFont: TtsFont; const aCharCode: WideChar; const aCharImage: TtsImage); virtual; abstract;
  150. public
  151. property PostProcessStepCount: Integer read GetPostProcessStepCount;
  152. property PostProcessStep[const aIndex: Integer]: TtsPostProcessStep read GetPostProcessStep;
  153. function GenerateChar(const aCharCode: WideChar; const aFont: TtsFont; const aRenderer: TtsRenderer): TtsChar;
  154. function AddPostProcessStep(const aStep: TtsPostProcessStep): TtsPostProcessStep;
  155. function InsertPostProcessStep(const aIndex: Integer; const aStep: TtsPostProcessStep): TtsPostProcessStep;
  156. procedure DeletePostProcessStep(const aIndex: Integer);
  157. procedure ClearPostProcessSteps;
  158. constructor Create;
  159. destructor Destroy; override;
  160. end;
  161. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  162. TtsLineItemType = (
  163. tsItemTypeUnknown,
  164. tsItemTypeFont,
  165. tsItemTypeColor,
  166. tsItemTypeText,
  167. tsItemTypeSpace,
  168. tsItemTypeLineBreak,
  169. tsItemTypeTab,
  170. tsItemTypeSpacing);
  171. PtsLineItem = ^TtsLineItem;
  172. TtsLineItem = packed record
  173. Next: PtsLineItem;
  174. Prev: PtsLineItem;
  175. ItemType: TtsLineItemType;
  176. case TtsLineItemType of
  177. tsItemTypeFont: (
  178. Font: TtsFont
  179. );
  180. tsItemTypeColor: (
  181. Color: TtsColor4f;
  182. );
  183. tsItemTypeText, tsItemTypeSpace: (
  184. Text: PWideChar; // text of this item
  185. TextWidth: Integer; // width of text (in pixel)
  186. );
  187. tsItemTypeSpacing: (
  188. Spacing: Integer;
  189. );
  190. end;
  191. TtsLineFlag = (
  192. tsLastItemIsSpace, // is set if the last item was a space item
  193. tsMetaValid // is set if the line meta data is valid
  194. );
  195. TtsLineFlags = set of TtsLineFlag;
  196. PtsBlockLine = ^TtsBlockLine;
  197. TtsBlockLine = packed record
  198. Next: PtsBlockLine;
  199. First: PtsLineItem;
  200. Last: PtsLineItem;
  201. Flags: TtsLineFlags;
  202. meta: packed record
  203. Width: Integer; // absolut width of this line
  204. Height: Integer; // absolute height of this line
  205. Spacing: Integer; // spacing between lines
  206. Ascent: Integer; // text ascent
  207. SpaceCount: Integer; // number of words in this line
  208. end;
  209. end;
  210. TtsBlockFlag = (
  211. tsBlockFlagWordWrap
  212. );
  213. TtsBlockFlags = set of TtsBlockFlag;
  214. TtsClipping = (
  215. tsClipNone, // no clipping
  216. tsClipWordBorder, // draw all words that have at least one pixel inside the box
  217. tsClipCharBorder, // draw all chars that have at least one pixel inside the box
  218. tsClipWordComplete, // draw all words that are completly inside the box
  219. tsClipCharComplete // draw all chars that are completly inside the box
  220. );
  221. TtsTextBlock = class(TObject)
  222. private
  223. fRenderer: TtsRenderer;
  224. fTop: Integer;
  225. fLeft: Integer;
  226. fWidth: Integer;
  227. fHeight: Integer;
  228. fFlags: TtsBlockFlags;
  229. fVertAlign: TtsVertAlignment;
  230. fHorzAlign: TtsHorzAlignment;
  231. fClipping: TtsClipping;
  232. fCurrentColor: TtsColor4f;
  233. fCurrentFont: TtsFont;
  234. fFirstLine: PtsBlockLine;
  235. fLastLine: PtsBlockLine;
  236. function GetRect: TtsRect;
  237. function PushLineItem(const aItem: PtsLineItem; const aUpdateLineWidth: Boolean = true): Boolean;
  238. procedure PushSpacing(const aWidth: Integer);
  239. procedure FreeLineItem(var aItem: PtsLineItem);
  240. procedure FreeLineItems(var aItem: PtsLineItem);
  241. procedure FreeLines(var aItem: PtsBlockLine);
  242. function SplitText(aText: PWideChar): PtsLineItem;
  243. function SplitIntoLines(aItem: PtsLineItem): Boolean;
  244. procedure TrimSpaces(const aLine: PtsBlockLine);
  245. procedure UpdateLineMeta(const aLine: PtsBlockLine);
  246. protected
  247. property Lines: PtsBlockLine read fFirstLine;
  248. procedure PushNewLine;
  249. constructor Create(const aRenderer: TtsRenderer; const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags);
  250. public
  251. property Renderer: TtsRenderer read fRenderer;
  252. property CurrentColor: TtsColor4f read fCurrentColor;
  253. property CurrentFont: TtsFont read fCurrentFont;
  254. property Rect: TtsRect read GetRect;
  255. property Width: Integer read fWidth;
  256. property Height: Integer read fHeight;
  257. property Flags: TtsBlockFlags read fFlags;
  258. property Top: Integer read fTop write fTop;
  259. property Left: Integer read fLeft write fLeft;
  260. property VertAlign: TtsVertAlignment read fVertAlign write fVertAlign;
  261. property HorzAlign: TtsHorzAlignment read fHorzAlign write fHorzAlign;
  262. property Clipping: TtsClipping read fClipping write fClipping;
  263. procedure ChangeFont(const aFont: TtsFont);
  264. procedure ChangeColor(const aColor: TtsColor4f);
  265. function GetActualBlockHeight: Integer;
  266. procedure TextOutA(const aText: PAnsiChar);
  267. procedure TextOutW(const aText: PWideChar);
  268. destructor Destroy; override;
  269. end;
  270. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  271. TtsRenderer = class(TObject)
  272. private
  273. fContext: TtsContext;
  274. fFormat: TtsFormat;
  275. fSaveImages: Boolean;
  276. fRenderCS: TCriticalSection;
  277. protected
  278. function CreateRenderRef(const aChar: TtsChar; const aCharImage: TtsImage): TtsCharRenderRef; virtual; abstract;
  279. procedure FreeRenderRef(const aCharRef: TtsCharRenderRef); virtual; abstract;
  280. procedure BeginRender; virtual;
  281. procedure EndRender; virtual;
  282. procedure SetDrawPos(const X, Y: Integer); virtual; abstract;
  283. function GetDrawPos: TtsPosition; virtual; abstract;
  284. procedure MoveDrawPos(const X, Y: Integer); virtual; abstract;
  285. procedure SetColor(const aColor: TtsColor4f); virtual; abstract;
  286. procedure Render(const aCharRef: TtsCharRenderRef); virtual; abstract;
  287. public
  288. property Context: TtsContext read fContext;
  289. property Format: TtsFormat read fFormat;
  290. property SaveImages: Boolean read fSaveImages write fSaveImages;
  291. function BeginBlock(const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags): TtsTextBlock;
  292. procedure EndBlock(var aBlock: TtsTextBlock);
  293. constructor Create(const aContext: TtsContext; const aFormat: TtsFormat);
  294. destructor Destroy; override;
  295. end;
  296. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  297. TtsContext = class(TObject)
  298. private
  299. fID: Cardinal;
  300. fCodePage: TtsCodePage;
  301. fCodePagePtr: Pointer;
  302. fCodePageFunc: TtsAnsiToWideCharFunc;
  303. fCodePageDefault: WideChar;
  304. public
  305. function AnsiToWide(const aText: PAnsiChar): PWideChar;
  306. constructor Create;
  307. destructor Destroy; override;
  308. end;
  309. EtsException = class(Exception);
  310. EtsRenderer = class(EtsException);
  311. EtsOutOfRange = class(EtsException)
  312. public
  313. constructor Create(const aMin, aMax, aIndex: Integer);
  314. end;
  315. const
  316. IMAGE_MODES_REPLACE: TtsImageModes = (tsModeReplace, tsModeReplace, tsModeReplace, tsModeReplace);
  317. IMAGE_MODES_NORMAL: TtsImageModes = (tsModeReplace, tsModeReplace, tsModeReplace, tsModeModulate);
  318. COLOR_CHANNELS_RGB: TtsColorChannels = [tsChannelRed, tsChannelGreen, tsChannelBlue];
  319. COLOR_CHANNELS_RGBA: TtsColorChannels = [tsChannelRed, tsChannelGreen, tsChannelBlue, tsChannelAlpha];
  320. implementation
  321. var
  322. gLastContextID: Cardinal = 0;
  323. const
  324. IMAGE_MODE_FUNCTIONS: array[TtsImageMode] of TtsImageModeFunc = (
  325. @tsImageModeFuncIgnore,
  326. @tsImageModeFuncReplace,
  327. @tsImageModeFuncModulate);
  328. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  329. //TtsKernel1D///////////////////////////////////////////////////////////////////////////////////////////////////////////
  330. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  331. constructor TtsKernel1D.Create(const aRadius, aStrength: Single);
  332. var
  333. TempRadius, SQRRadius, TempStrength, TempValue: Double;
  334. Idx: Integer;
  335. function CalcValue(const aIndex: Integer): Single;
  336. var
  337. Temp: Double;
  338. begin
  339. Temp := Max(0, Abs(aIndex) - TempStrength);
  340. Temp := Sqr(Temp * TempRadius) / SQRRadius;
  341. result := Exp(-Temp);
  342. end;
  343. begin
  344. inherited Create;
  345. // calculate new radius and strength
  346. TempStrength := Min(aRadius - 1, aRadius * aStrength);
  347. TempRadius := aRadius - TempStrength;
  348. SQRRadius := sqr(TempRadius) * sqr(TempRadius);
  349. // caluculating size of the kernel
  350. Size := Round(TempRadius);
  351. while CalcValue(Size) > 0.001 do
  352. Inc(Size);
  353. Size := Size -1;
  354. ItemCount := Size * 2 +1;
  355. SetLength(Items, ItemCount);
  356. // calculate Value (yes thats right. there is no -1)
  357. for Idx := 0 to Size do begin
  358. TempValue := CalcValue(Idx);
  359. with Items[Size + Idx] do begin
  360. Offset := Idx;
  361. Value := TempValue;
  362. end;
  363. with Items[Size - Idx] do begin
  364. Offset := -Idx;
  365. Value := TempValue;
  366. end;
  367. end;
  368. end;
  369. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  370. //TtsImage//////////////////////////////////////////////////////////////////////////////////////////////////////////////
  371. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  372. function TtsImage.GetScanline(const aIndex: Integer): Pointer;
  373. begin
  374. if not fHasScanlines then
  375. UpdateScanlines;
  376. if fHasScanlines and (aIndex >= 0) and (aIndex <= High(fScanlines)) then
  377. result := fScanlines[aIndex]
  378. else
  379. result := nil;
  380. end;
  381. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  382. function TtsImage.GetIsEmpty: Boolean;
  383. begin
  384. result := not Assigned(fData);
  385. end;
  386. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  387. procedure TtsImage.SetData(const aData: Pointer; const aFormat: TtsFormat; const aWidth: Integer; const aHeight: Integer);
  388. begin
  389. fHasScanlines := false;
  390. if Assigned(fData) then
  391. FreeMemory(fData);
  392. fData := aData;
  393. if Assigned(fData) then begin
  394. fWidth := aWidth;
  395. fHeight := aHeight;
  396. fFormat := aFormat;
  397. end else begin
  398. fWidth := 0;
  399. fHeight := 0;
  400. fFormat := tsFormatEmpty;
  401. end;
  402. end;
  403. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  404. procedure TtsImage.UpdateScanlines;
  405. var
  406. i, LineSize: Integer;
  407. tmp: PByte;
  408. begin
  409. LineSize := fWidth * tsFormatSize(fFormat);
  410. SetLength(fScanlines, fHeight);
  411. for i := 0 to fHeight-1 do begin
  412. tmp := fData;
  413. inc(tmp, i * LineSize);
  414. fScanlines[i] := tmp;
  415. end;
  416. fHasScanlines := true;
  417. end;
  418. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  419. function TtsImage.GetPixelAt(const x, y: Integer; out aColor: TtsColor4f): Boolean;
  420. var
  421. p: PByte;
  422. begin
  423. result := (x >= 0) and (x < Width) and (y >= 0) and (y < Height);
  424. if result then begin
  425. p := Scanline[y];
  426. inc(p, x * tsFormatSize(Format));
  427. tsFormatUnmap(Format, p, aColor);
  428. end;
  429. end;
  430. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  431. procedure TtsImage.Assign(const aImage: TtsImage);
  432. var
  433. ImgData: Pointer;
  434. ImgSize: Integer;
  435. begin
  436. ImgSize := aImage.Width * aImage.Height * tsFormatSize(aImage.Format);
  437. GetMem(ImgData, ImgSize);
  438. if Assigned(ImgData) then
  439. Move(aImage.Data, ImgData, ImgSize);
  440. SetData(ImgData, aImage.Format, aImage.Width, aImage.Height);
  441. end;
  442. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  443. procedure TtsImage.CreateEmpty(const aFormat: TtsFormat; const aWidth, aHeight: Integer);
  444. var
  445. ImgData: PByte;
  446. begin
  447. ImgData := AllocMem(aWidth * aHeight * tsFormatSize(aFormat));
  448. SetData(ImgData, aFormat, aWidth, aHeight);
  449. end;
  450. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  451. procedure TtsImage.LoadFromFunc(const aFunc: TtsImageFunc; const aArgs: Pointer);
  452. var
  453. X, Y: Integer;
  454. c: TtsColor4f;
  455. p, tmp: PByte;
  456. begin
  457. for Y := 0 to Height - 1 do begin
  458. p := ScanLine[Y];
  459. for X := 0 to Width - 1 do begin
  460. tmp := p;
  461. tsFormatUnmap(fFormat, tmp, c);
  462. aFunc(Self, X, Y, c, aArgs);
  463. tsFormatMap(fFormat, p, c);
  464. end;
  465. end;
  466. end;
  467. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  468. procedure TtsImage.Resize(const aNewWidth, aNewHeight, X, Y: Integer);
  469. var
  470. ImgData: PByte;
  471. PixSize, LineSize, ImageSize, OrgLineSize: Integer;
  472. src, dst: PByte;
  473. YStart, YEnd, YPos, XStart, XEnd: Integer;
  474. begin
  475. if (aNewHeight = 0) or (aNewWidth = 0) then begin
  476. SetData(nil);
  477. exit;
  478. end;
  479. PixSize := tsFormatSize(Format);
  480. LineSize := PixSize * aNewWidth;
  481. ImageSize := LineSize * aNewHeight;
  482. OrgLineSize := PixSize * Width;
  483. GetMem(ImgData, ImageSize);
  484. try
  485. FillChar(ImgData^, ImageSize, 0);
  486. // positions
  487. YStart := Max(0, Y);
  488. YEnd := Min(aNewHeight, Y + Height);
  489. XStart := Max(0, X);
  490. XEnd := Min(aNewWidth, X + Width);
  491. // copy data
  492. for YPos := YStart to YEnd -1 do begin
  493. dst := ImgData;
  494. Inc(dst, LineSize * YPos + PixSize * XStart);
  495. src := fData;
  496. Inc(src, OrgLineSize * (YPos - Y) + PixSize * (XStart - X));
  497. Move(src^, dst^, (XEnd - XStart) * PixSize);
  498. end;
  499. // assign
  500. SetData(ImgData, Format, aNewWidth, aNewHeight);
  501. except
  502. FreeMem(ImgData);
  503. end;
  504. end;
  505. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  506. procedure TtsImage.FindMinMax(out aRect: TtsRect);
  507. var
  508. X, Y: Integer;
  509. c: TtsColor4f;
  510. p: PByte;
  511. begin
  512. aRect.Top := -1;
  513. aRect.Left := -1;
  514. aRect.Right := -1;
  515. aRect.Bottom := -1;
  516. // Search for MinMax
  517. for Y := 0 to Height-1 do begin
  518. p := ScanLine[Y];
  519. for X := 0 to Width-1 do begin
  520. tsFormatUnmap(Format, p, c);
  521. if c.a > 0 then begin
  522. if (X < aRect.Left) or (aRect.Left = -1) then
  523. aRect.Left := X;
  524. if (X+1 > aRect.Right) or (aRect.Right = -1) then
  525. aRect.Right := X+1;
  526. if (Y < aRect.Top) or (aRect.Top = -1) then
  527. aRect.Top := Y;
  528. if (Y+1 > aRect.Bottom) or (aRect.Bottom = -1) then
  529. aRect.Bottom := Y+1;
  530. end;
  531. end;
  532. end;
  533. end;
  534. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  535. procedure TtsImage.FillColor(const aColor: TtsColor4f; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  536. var
  537. x, y: Integer;
  538. p: PByte;
  539. c: TtsColor4f;
  540. ch: TtsColorChannel;
  541. i: Integer;
  542. begin
  543. for y := 0 to Height-1 do begin
  544. p := Scanline[y];
  545. for x := 0 to Width-1 do begin
  546. tsFormatUnmap(Format, p, c);
  547. for i := 0 to 3 do begin
  548. ch := TtsColorChannel(i);
  549. if (ch in aChannelMask) then
  550. c.arr[i] := IMAGE_MODE_FUNCTIONS[aModes[ch]](aColor.arr[i], c.arr[i]);
  551. end;
  552. end;
  553. end;
  554. end;
  555. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  556. procedure TtsImage.FillPattern(const aPattern: TtsImage; X, Y: Integer; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  557. var
  558. _x, _y, posX, i: Integer;
  559. src, dst, tmp: PByte;
  560. cSrc, cDst: TtsColor4f;
  561. ch: TtsColorChannel;
  562. begin
  563. if x < 0 then
  564. x := Random(aPattern.Width);
  565. if y < 0 then
  566. y := Random(aPattern.Height);
  567. for _y := 0 to Height-1 do begin
  568. src := aPattern.Scanline[(y + _y) mod aPattern.Height];
  569. dst := Scanline[_y];
  570. inc(src, x);
  571. posX := x;
  572. for _x := 0 to Width-1 do begin
  573. if (posX >= aPattern.Width) then begin
  574. src := aPattern.Scanline[(y + _y) mod aPattern.Height];
  575. posX := 0;
  576. end;
  577. tmp := dst;
  578. tsFormatUnmap(Format, src, cSrc);
  579. tsFormatUnmap(Format, tmp, cDst);
  580. for i := 0 to 3 do begin
  581. ch := TtsColorChannel(i);
  582. if (ch in aChannelMask) then
  583. cDst.arr[i] := IMAGE_MODE_FUNCTIONS[aModes[ch]](cSrc.arr[i], cDst.arr[i]);
  584. end;
  585. tsFormatMap(Format, dst, cDst);
  586. inc(posX);
  587. end;
  588. end;
  589. end;
  590. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  591. procedure TtsImage.BlendImage(const aImage: TtsImage; const X, Y: Integer);
  592. var
  593. _x, _y, i: Integer;
  594. c, cOver, cUnder: TtsColor4f;
  595. FaqOver, FaqUnder: Single;
  596. UnionRect, IntersectRect: TtsRect;
  597. NewSize: TtsPosition;
  598. ImgSize: Integer;
  599. ImgData, dst, src, pOver, pUnder: PByte;
  600. tmpLines: array of Pointer;
  601. begin
  602. UnionRect := tsRect(
  603. Min(X, 0),
  604. Min(Y, 0),
  605. Max(X + aImage.Width, Width),
  606. Max(Y + aImage.Height, Height));
  607. IntersectRect := tsRect(
  608. Max(X, 0),
  609. Max(Y, 0),
  610. Min(X + aImage.Width, Width),
  611. Min(X + aImage.Height, Height));
  612. NewSize := tsPosition(
  613. UnionRect.Right - UnionRect.Left,
  614. UnionRect.Bottom - UnionRect.Top);
  615. ImgSize := NewSize.x * NewSize.y * tsFormatSize(Format);
  616. GetMem(ImgData, ImgSize);
  617. try
  618. FillByte(ImgData^, ImgSize, $00);
  619. // temporary scanlines
  620. SetLength(tmpLines, NewSize.y);
  621. for _y := 0 to NewSize.y-1 do begin
  622. tmpLines[_y] := ImgData;
  623. inc(tmpLines[_y], _y * NewSize.y);
  624. end;
  625. // copy data from underlaying image
  626. for _y := 0 to Height-1 do begin
  627. src := Scanline[_y];
  628. dst := tmpLines[_y - UnionRect.Top];
  629. dec(dst, UnionRect.Left);
  630. for _x := 0 to Width-1 do begin
  631. dst^ := src^;
  632. inc(src);
  633. inc(dst);
  634. end;
  635. end;
  636. // copy data from overlaying image
  637. for _y := 0 to aImage.Height-1 do begin
  638. src := aImage.Scanline[_y];
  639. dst := tmpLines[_y + y - UnionRect.Top];
  640. inc(dst, X - UnionRect.Left);
  641. for _x := 0 to Width-1 do begin
  642. dst^ := src^;
  643. inc(src);
  644. inc(dst);
  645. end;
  646. end;
  647. // blend overlapped
  648. for _y := IntersectRect.Top to IntersectRect.Bottom-1 do begin
  649. pOver := aImage.Scanline[_y - Min(IntersectRect.Top, UnionRect.Top)];
  650. inc(pOver, IntersectRect.Left - UnionRect.Left);
  651. pUnder := Scanline[_y - Min(IntersectRect.Top, 0)];
  652. inc(pUnder, IntersectRect.Left);
  653. dst := tmpLines[_y - Min(Y, 0)];
  654. inc(dst, IntersectRect.Left - Min(X, 0));
  655. for _x := IntersectRect.Left to IntersectRect.Right-1 do begin
  656. tsFormatUnmap(aImage.Format, pOver, cOver);
  657. tsFormatUnmap(Format, pUnder, cUnder);
  658. c.a := cOver.a + cUnder.a * (1 - cOver.a);
  659. if (c.a > 0) then begin
  660. FaqUnder := (cUnder.a * (1 - cOver.a)) / c.a;
  661. FaqOver := cOver.a / c.a;
  662. for i := 0 to 2 do
  663. c.arr[i] := cOver.arr[i] * FaqOver + cUnder.arr[i] * FaqUnder;
  664. end else begin
  665. c.r := 0;
  666. c.g := 0;
  667. c.b := 0;
  668. end;
  669. tsFormatMap(Format, dst, c);
  670. end;
  671. end;
  672. except
  673. FreeMem(ImgData);
  674. end;
  675. end;
  676. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  677. procedure TtsImage.Blur(const aHorzKernel, aVertKernel: TtsKernel1D; const aChannelMask: TtsColorChannels);
  678. var
  679. tmpImage: TtsImage;
  680. procedure DoBlur(const aSrc, aDst: TtsImage; const aKernel: TtsKernel1D; const ShiftX, ShiftY: Integer);
  681. var
  682. x, y, i, j: Integer;
  683. src, dst: PByte;
  684. v: Single;
  685. c, tmp: TtsColor4f;
  686. begin
  687. for y := 0 to Height-1 do begin
  688. src := aSrc.Scanline[y];
  689. dst := aDst.Scanline[y];
  690. for x := 0 to Width-1 do begin
  691. // read color and clear channels
  692. v := 0;
  693. tsFormatUnmap(aSrc.Format, src, c);
  694. for j := 0 to 3 do
  695. if (TtsColorChannel(j) in aChannelMask) then
  696. c.arr[j] := 0;
  697. // do blur
  698. for i := 0 to aKernel.ItemCount-1 do with aKernel.Items[i] do begin
  699. if aSrc.GetPixelAt(x + Offset * ShiftX, y + Offset * ShiftY, tmp) then begin
  700. for j := 0 to 3 do begin
  701. if (TtsColorChannel(j) in aChannelMask) then
  702. c.arr[j] := c.arr[j] + tmp.arr[j] * Value;
  703. end;
  704. v := v + Value;
  705. end;
  706. end;
  707. // calc final color and write
  708. for j := 0 to 3 do
  709. if (TtsColorChannel(i) in aChannelMask) then
  710. c.arr[j] := c.arr[j] / v;
  711. tsFormatMap(aDst.Format, dst, c);
  712. end;
  713. end;
  714. end;
  715. begin
  716. tmpImage := TtsImage.Create;
  717. try
  718. tmpImage.CreateEmpty(Format, Width, Height);
  719. tmpImage.FillColor(tsColor4f(1, 1, 1, 0), COLOR_CHANNELS_RGBA, IMAGE_MODES_REPLACE);
  720. DoBlur(self, tmpImage, aHorzKernel, 1, 0);
  721. DoBlur(tmpImage, self, aVertKernel, 0, 1);
  722. finally
  723. FreeAndNil(tmpImage);
  724. end;
  725. end;
  726. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  727. procedure TtsImage.AddResizingBorder;
  728. var
  729. c, cTmp, cSum: TtsColor4f;
  730. x, y, cnt: Integer;
  731. p, tmp: PByte;
  732. procedure AddCol(const aColor: TtsColor4f);
  733. var
  734. i: Integer;
  735. begin
  736. if (aColor.a > 0) then begin
  737. inc(cnt);
  738. for i := 0 to 2 do
  739. cSum.arr[i] := cSum.arr[i] + cTmp.arr[i];
  740. end;
  741. end;
  742. var
  743. i: Integer;
  744. begin
  745. Resize(Width + 4, Height + 4, 2, 2);
  746. for y := 0 to Height-1 do begin
  747. p := Scanline[y];
  748. for x := 0 to Width-1 do begin
  749. FillByte(cSum, SizeOf(cSum), 0);
  750. cnt := 0;
  751. tmp := p;
  752. tsFormatUnmap(Format, tmp, c);
  753. if (c.a = 0) then begin
  754. // row - 1
  755. if (y > 0) then begin
  756. // row - 1 | col
  757. GetPixelAt(x, y-1, cTmp);
  758. AddCol(cTmp);
  759. //row - 1 | col - 1
  760. if (x > 0) then begin
  761. GetPixelAt(x-1, y-1, cTmp);
  762. AddCol(cTmp);
  763. end;
  764. // row - 1 | col + 1
  765. if (x < Width-1) then begin
  766. GetPixelAt(x+1, y-1, cTmp);
  767. AddCol(cTmp);
  768. end;
  769. end;
  770. // row + 1
  771. if (y < Height-1) then begin
  772. // row - 1 | col
  773. GetPixelAt(x, y+1, cTmp);
  774. AddCol(cTmp);
  775. //row + 1 | col - 1
  776. if (x > 0) then begin
  777. GetPixelAt(x-1, y+1, cTmp);
  778. AddCol(cTmp);
  779. end;
  780. // row + 1 | col + 1
  781. if (x < Width-1) then begin
  782. GetPixelAt(x+1, y+1, cTmp);
  783. AddCol(cTmp);
  784. end;
  785. end;
  786. //row | col - 1
  787. if (x > 0) then begin
  788. GetPixelAt(x-1, y+1, cTmp);
  789. AddCol(cTmp);
  790. end;
  791. // row | col + 1
  792. if (x < Width-1) then begin
  793. GetPixelAt(x+1, y+1, cTmp);
  794. AddCol(cTmp);
  795. end;
  796. // any pixel next to the transparent pixel they are opaque?
  797. if (cnt > 0) then begin
  798. for i := 0 to 2 do
  799. c.arr[i] := cSum.arr[i] / cnt;
  800. end;
  801. end;
  802. tsFormatMap(Format, p, c);
  803. end;
  804. end;
  805. end;
  806. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  807. constructor TtsImage.Create;
  808. begin
  809. inherited Create;
  810. SetData(nil);
  811. end;
  812. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  813. destructor TtsImage.Destroy;
  814. begin
  815. SetData(nil);
  816. inherited Destroy;
  817. end;
  818. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  819. //TtsChar///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  820. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  821. constructor TtsChar.Create(const aCharCode: WideChar);
  822. begin
  823. inherited Create;
  824. fCharCode := aCharCode;
  825. end;
  826. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  827. //TtsFont///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  828. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  829. function TtsFont.HasChar(const aCharCode: WideChar): Boolean;
  830. begin
  831. result := Assigned(GetChar(aCharCode));
  832. end;
  833. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  834. function TtsFont.GetChar(const aCharCode: WideChar): TtsChar;
  835. var
  836. Chars: PtsFontCharArray;
  837. begin
  838. if (Ord(aCharCode) > 0) then begin
  839. Chars := fChars[(Ord(aCharCode) shr 8) and $FF];
  840. if Assigned(Chars) then
  841. result := Chars^.Chars[Ord(aCharCode) and $FF]
  842. else
  843. result := nil;
  844. end else
  845. result := nil;
  846. end;
  847. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  848. function TtsFont.GetCharCreate(const aCharCode: WideChar): TtsChar;
  849. begin
  850. result := GetChar(aCharCode);
  851. if not Assigned(result) then
  852. result := AddChar(aCharCode);
  853. end;
  854. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  855. procedure TtsFont.AddChar(const aCharCode: WideChar; const aChar: TtsChar);
  856. var
  857. h, l: Integer;
  858. Chars: PtsFontCharArray;
  859. begin
  860. h := (Ord(aCharCode) shr 8) and $FF;
  861. Chars := fChars[h];
  862. if not Assigned(Chars) then begin
  863. New(Chars);
  864. FillChar(Chars^, SizeOf(Chars^), 0);
  865. fChars[h] := Chars;
  866. end;
  867. if Assigned(Chars) then begin
  868. l := Ord(aCharCode) and $FF;
  869. Chars^.Chars[l] := aChar;
  870. inc(Chars^.CharCount);
  871. end;
  872. end;
  873. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  874. constructor TtsFont.Create(const aRenderer: TtsRenderer; const aCreator: TtsFontGenerator; const aProperties: TtsFontProperties);
  875. begin
  876. inherited Create;
  877. fRenderer := aRenderer;
  878. fCreator := aCreator;
  879. fProperties := aProperties;
  880. fCharSpacing := 0;
  881. fTabWidth := 0;
  882. fLineSpacing := 0.0;
  883. fCreateChars := true;
  884. end;
  885. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  886. function TtsFont.AddChar(const aCharCode: WideChar): TtsChar;
  887. begin
  888. result := GetChar(aCharCode);
  889. if not Assigned(result) and fCreateChars and (Ord(aCharCode) > 0) then begin
  890. result := fCreator.GenerateChar(aCharCode, self, fRenderer);
  891. if Assigned(result) then
  892. AddChar(aCharCode, result);
  893. end;
  894. end;
  895. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  896. procedure TtsFont.AddCharRange(const aCharCodeBeg, aCharCodeEnd: WideChar);
  897. var
  898. c: WideChar;
  899. begin
  900. for c := aCharCodeBeg to aCharCodeEnd do
  901. AddChar(c);
  902. end;
  903. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  904. procedure TtsFont.RemoveChar(const aCharCode: WideChar);
  905. var
  906. h, l: Integer;
  907. Chars: PtsFontCharArray;
  908. c: TtsChar;
  909. begin
  910. // find char array
  911. h := (Ord(aCharCode) shr 8) and $FF;
  912. Chars := fChars[h];
  913. if not Assigned(Chars) then
  914. exit;
  915. // find char
  916. l := Ord(aCharCode) and $FF;
  917. c := Chars^.Chars[l];
  918. if not Assigned(c) then
  919. exit;
  920. // remove char
  921. Chars^.Chars[l] := nil;
  922. dec(Chars^.CharCount);
  923. if (Chars^.CharCount <= 0) then begin
  924. fChars[h] := nil;
  925. Dispose(Chars);
  926. end;
  927. if Assigned(c.RenderRef) then begin
  928. fRenderer.FreeRenderRef(c.RenderRef);
  929. c.RenderRef := nil;
  930. end;
  931. FreeAndNil(c);
  932. end;
  933. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  934. procedure TtsFont.ClearChars;
  935. var
  936. h, l: Integer;
  937. Chars: PtsFontCharArray;
  938. c: TtsChar;
  939. begin
  940. for h := Low(fChars) to High(fChars) do begin
  941. Chars := fChars[h];
  942. if Assigned(Chars) then begin
  943. for l := Low(Chars^.Chars) to High(Chars^.Chars) do begin
  944. c := Chars^.Chars[l];
  945. if Assigned(c) then begin
  946. if Assigned(c.RenderRef) then
  947. fRenderer.FreeRenderRef(c.RenderRef);
  948. FreeAndNil(c);
  949. end;
  950. end;
  951. Dispose(Chars);
  952. fChars[h] := nil;
  953. end;
  954. end;
  955. end;
  956. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  957. function TtsFont.GetTextWidthW(aText: PWideChar): Integer;
  958. var
  959. c: TtsChar;
  960. begin
  961. result := 0;
  962. if not Assigned(aText) then
  963. exit;
  964. while (aText^ <> #0) do begin
  965. c := AddChar(aText^);
  966. if not Assigned(c) then
  967. c := AddChar(fProperties.DefaultChar);
  968. if Assigned(c) then begin
  969. if (result > 0) then
  970. result := result + CharSpacing;
  971. result := result + c.Advance;
  972. end;
  973. inc(aText);
  974. end;
  975. end;
  976. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  977. procedure TtsFont.GetTextMetric(out aMetric: TtsTextMetric);
  978. begin
  979. aMetric.Ascent := fProperties.Ascent;
  980. aMetric.Descent := fProperties.Descent;
  981. aMetric.ExternalLeading := fProperties.ExternalLeading;
  982. aMetric.BaseLineOffset := fProperties.BaseLineOffset;
  983. aMetric.CharSpacing := CharSpacing;
  984. aMetric.LineHeight := fProperties.Ascent + fProperties.Descent + fProperties.ExternalLeading;
  985. aMetric.LineSpacing := Trunc(fProperties.Size * fLineSpacing);
  986. end;
  987. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  988. destructor TtsFont.Destroy;
  989. begin
  990. ClearChars;
  991. inherited Destroy;
  992. end;
  993. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  994. //TtsPostProcessStep////////////////////////////////////////////////////////////////////////////////////////////////////
  995. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  996. procedure TtsPostProcessStep.ClearList(const aList: TList);
  997. var
  998. i: Integer;
  999. p: PtsPostProcessStepRange;
  1000. begin
  1001. for i := 0 to aList.Count-1 do begin
  1002. p := aList[i];
  1003. Dispose(p);
  1004. end;
  1005. aList.Clear;
  1006. end;
  1007. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1008. function TtsPostProcessStep.IsInRange(const aCharCode: WideChar): Boolean;
  1009. var
  1010. i: Integer;
  1011. p: PtsPostProcessStepRange;
  1012. begin
  1013. result := (fIncludeCharRange.Count = 0);
  1014. if not result then for i := 0 to fIncludeCharRange.Count-1 do begin
  1015. p := fIncludeCharRange[i];
  1016. if (aCharCode >= p^.StartChar) and (aCharCode <= p^.EndChar) then begin
  1017. result := true;
  1018. break;
  1019. end;
  1020. end;
  1021. if result then for i := 0 to fExcludeCharRange.Count-1 do begin
  1022. p := fExcludeCharRange[i];
  1023. if (aCharCode >= p^.StartChar) and (aCharCode <= p^.EndChar) then begin
  1024. result := false;
  1025. break;
  1026. end;
  1027. end;
  1028. end;
  1029. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1030. procedure TtsPostProcessStep.AddUsageRange(const aUsage: TtsFontProcessStepUsage; const aStartChar, aEndChar: WideChar);
  1031. var
  1032. p: PtsPostProcessStepRange;
  1033. begin
  1034. New(p);
  1035. p^.StartChar := aStartChar;
  1036. p^.EndChar := aEndChar;
  1037. case aUsage of
  1038. tsUsageInclude:
  1039. fIncludeCharRange.Add(p);
  1040. tsUsageExclude:
  1041. fExcludeCharRange.Add(p);
  1042. end;
  1043. end;
  1044. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1045. procedure TtsPostProcessStep.AddUsageChars(const aUsage: TtsFontProcessStepUsage; aChars: PWideChar);
  1046. begin
  1047. if Assigned(aChars) then
  1048. while (aChars^ <> #0) do begin
  1049. AddUsageRange(aUsage, aChars^, aChars^);
  1050. inc(aChars);
  1051. end;
  1052. end;
  1053. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1054. procedure TtsPostProcessStep.ClearIncludeRange;
  1055. begin
  1056. ClearList(fIncludeCharRange);
  1057. end;
  1058. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1059. procedure TtsPostProcessStep.ClearExcludeRange;
  1060. begin
  1061. ClearList(fExcludeCharRange);
  1062. end;
  1063. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1064. constructor TtsPostProcessStep.Create;
  1065. begin
  1066. inherited Create;
  1067. fIncludeCharRange := TList.Create;
  1068. fExcludeCharRange := TList.Create;
  1069. end;
  1070. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1071. destructor TtsPostProcessStep.Destroy;
  1072. begin
  1073. ClearList(fIncludeCharRange);
  1074. ClearList(fExcludeCharRange);
  1075. FreeAndNil(fIncludeCharRange);
  1076. FreeAndNil(fExcludeCharRange);
  1077. inherited Destroy;
  1078. end;
  1079. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1080. //TtsFontGenerator//////////////////////////////////////////////////////////////////////////////////////////////////////////
  1081. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1082. function TtsFontGenerator.GetPostProcessStepCount: Integer;
  1083. begin
  1084. result := fPostProcessSteps.Count;
  1085. end;
  1086. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1087. function TtsFontGenerator.GetPostProcessStep(const aIndex: Integer): TtsPostProcessStep;
  1088. begin
  1089. if (aIndex >= 0) and (aIndex < fPostProcessSteps.Count) then
  1090. Result := TtsPostProcessStep(fPostProcessSteps[aIndex])
  1091. else
  1092. raise EtsOutOfRange.Create(0, fPostProcessSteps.Count-1, aIndex);
  1093. end;
  1094. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1095. procedure TtsFontGenerator.DrawLine(const aChar: TtsChar; const aCharImage: TtsImage; aLinePosition, aLineSize: Integer);
  1096. var
  1097. NewSize, NewPos: TtsPosition;
  1098. YOffset, y: Integer;
  1099. procedure FillLine(aData: PByte);
  1100. var
  1101. w, i: Integer;
  1102. c: TtsColor4f;
  1103. tmp: PByte;
  1104. begin
  1105. w := NewSize.x;
  1106. while (w > 0) do begin
  1107. tmp := aData;
  1108. tsFormatUnmap(aCharImage.Format, tmp, c);
  1109. for i := 0 to 3 do
  1110. c.arr[i] := 1.0;
  1111. tsFormatMap(aCharImage.Format, aData, c);
  1112. dec(w);
  1113. end;
  1114. end;
  1115. begin
  1116. if aLineSize <= 0 then
  1117. exit;
  1118. aLinePosition := aLinePosition - aLineSize;
  1119. // calculate width and height
  1120. NewPos.x := 0;
  1121. NewPos.y := 0;
  1122. NewSize.x := aCharImage.Width;
  1123. NewSize.y := aCharImage.Height;
  1124. // expand image to the full advance
  1125. if aChar.Advance > aCharImage.Width then
  1126. NewSize.x := aChar.Advance;
  1127. // add glyph position to image width and set position
  1128. if aChar.GlyphOrigin.x > aChar.GlyphRect.Left then begin
  1129. NewSize.x := NewSize.x + aChar.GlyphOrigin.x;
  1130. NewPos.x := aChar.GlyphOrigin.x;
  1131. end;
  1132. if (aChar.GlyphOrigin.x < 0) then
  1133. NewSize.x := NewSize.x - aChar.GlyphOrigin.x;
  1134. // line is under the image
  1135. if aLinePosition < (aChar.GlyphOrigin.y - aCharImage.Height) then
  1136. NewSize.y := NewSize.y + (aChar.GlyphOrigin.y - aCharImage.Height - aLinePosition);
  1137. // line is above the image
  1138. if aLinePosition + aLineSize > aChar.GlyphOrigin.y then begin
  1139. NewPos.y := ((aLinePosition + aLineSize) - aChar.GlyphOrigin.y);
  1140. NewSize.y := NewSize.y + NewPos.y;
  1141. end;
  1142. // resize
  1143. aCharImage.Resize(NewSize.x, NewSize.y, NewPos.x, NewPos.y);
  1144. // draw lines
  1145. YOffset := (aChar.GlyphOrigin.y + NewPos.y) - aLinePosition;
  1146. for y := 1 to aLineSize do
  1147. FillLine(aCharImage.ScanLine[YOffset - y]);
  1148. // move glyph rect
  1149. aChar.GlyphRect := tsRect(
  1150. aChar.GlyphRect.Left + NewPos.x,
  1151. aChar.GlyphRect.Right + NewPos.x,
  1152. aChar.GlyphRect.Top + NewPos.y,
  1153. aChar.GlyphRect.Bottom + NewPos.y);
  1154. end;
  1155. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1156. procedure TtsFontGenerator.DoPostProcess(const aChar: TtsChar; const aCharImage: TtsImage);
  1157. var
  1158. i: Integer;
  1159. step: TtsPostProcessStep;
  1160. begin
  1161. if not aCharImage.IsEmpty then begin
  1162. for i := 0 to fPostProcessSteps.Count-1 do begin
  1163. step := TtsPostProcessStep(fPostProcessSteps[i]);
  1164. if step.IsInRange(aChar.CharCode) then
  1165. step.Execute(aChar, aCharImage);
  1166. end;
  1167. end;
  1168. end;
  1169. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1170. function TtsFontGenerator.GenerateChar(const aCharCode: WideChar; const aFont: TtsFont; const aRenderer: TtsRenderer): TtsChar;
  1171. var
  1172. GlyphOrigin, GlyphSize: TtsPosition;
  1173. Advance: Integer;
  1174. CharImage: TtsImage;
  1175. begin
  1176. result := nil;
  1177. if (Ord(aCharCode) = 0) or
  1178. not GetGlyphMetrics(aFont, aCharCode, GlyphOrigin, GlyphSize, Advance) or
  1179. not ((GlyphOrigin.x <> 0) or (GlyphOrigin.y <> 0) or (GlyphSize.x <> 0) or (GlyphSize.y <> 0) or (Advance <> 0)) then
  1180. exit;
  1181. CharImage := TtsImage.Create;
  1182. try
  1183. if aRenderer.SaveImages then begin
  1184. if (GlyphSize.x > 0) and (GlyphSize.y > 0) then begin
  1185. GetCharImage(aFont, aCharCode, CharImage);
  1186. end else if ([tsStyleUnderline, tsStyleStrikeout] * aFont.Properties.Style <> []) then begin
  1187. CharImage.CreateEmpty(aRenderer.Format, Advance, 1);
  1188. GlyphOrigin.y := 1;
  1189. end;
  1190. end;
  1191. result := TtsChar.Create(aCharCode);
  1192. try
  1193. result.GlyphOrigin := GlyphOrigin;
  1194. result.GlyphRect := tsRect(0, 0, CharImage.Width, CharImage.Height);
  1195. result.Advance := Advance;
  1196. if (aRenderer.SaveImages) then begin
  1197. try
  1198. if (tsStyleUnderline in aFont.Properties.Style) then
  1199. DrawLine(result, CharImage, aFont.Properties.UnderlinePos, aFont.Properties.UnderlineSize);
  1200. if (tsStyleUnderline in aFont.Properties.Style) then
  1201. DrawLine(result, CharImage, aFont.Properties.StrikeoutPos, aFont.Properties.StrikeoutSize);
  1202. except
  1203. CharImage.FillColor(tsColor4f(1, 0, 0, 0), COLOR_CHANNELS_RGB, IMAGE_MODES_NORMAL);
  1204. end;
  1205. DoPostProcess(result, CharImage);
  1206. result.RenderRef := aRenderer.CreateRenderRef(result, CharImage);
  1207. end;
  1208. except
  1209. FreeAndNil(result);
  1210. end;
  1211. finally
  1212. FreeAndNil(CharImage);
  1213. end;
  1214. end;
  1215. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1216. function TtsFontGenerator.AddPostProcessStep(const aStep: TtsPostProcessStep): TtsPostProcessStep;
  1217. begin
  1218. result := aStep;
  1219. fPostProcessSteps.Add(aStep);
  1220. end;
  1221. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1222. function TtsFontGenerator.InsertPostProcessStep(const aIndex: Integer; const aStep: TtsPostProcessStep): TtsPostProcessStep;
  1223. begin
  1224. result := aStep;
  1225. fPostProcessSteps.Insert(aIndex, aStep);
  1226. end;
  1227. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1228. procedure TtsFontGenerator.DeletePostProcessStep(const aIndex: Integer);
  1229. begin
  1230. if (aIndex >= 0) and (aIndex < fPostProcessSteps.Count) then
  1231. fPostProcessSteps.Delete(aIndex)
  1232. else
  1233. raise EtsOutOfRange.Create(0, fPostProcessSteps.Count-1, aIndex);
  1234. end;
  1235. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1236. procedure TtsFontGenerator.ClearPostProcessSteps;
  1237. begin
  1238. fPostProcessSteps.Clear;
  1239. end;
  1240. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1241. constructor TtsFontGenerator.Create;
  1242. begin
  1243. inherited Create;
  1244. fPostProcessSteps := TObjectList.Create(true);
  1245. end;
  1246. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1247. destructor TtsFontGenerator.Destroy;
  1248. begin
  1249. ClearPostProcessSteps;
  1250. FreeAndNil(fPostProcessSteps);
  1251. inherited Destroy;
  1252. end;
  1253. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1254. //TtsTextBlock//////////////////////////////////////////////////////////////////////////////////////////////////////////
  1255. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1256. function TtsTextBlock.GetRect: TtsRect;
  1257. begin
  1258. result.Left := fLeft;
  1259. result.Top := fTop;
  1260. result.Right := fLeft + fWidth;
  1261. result.Bottom := fTop + fHeight;
  1262. end;
  1263. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1264. function TtsTextBlock.PushLineItem(const aItem: PtsLineItem; const aUpdateLineWidth: Boolean): Boolean;
  1265. begin
  1266. result := false;
  1267. if not Assigned(fLastLine) then
  1268. PushNewLine;
  1269. if not Assigned(fLastLine^.First) and
  1270. (aItem^.ItemType in [tsItemTypeSpace, tsItemTypeSpacing]) then
  1271. exit; // di not add line space or line spacing if line is empty
  1272. if Assigned(fLastLine^.Last) then begin
  1273. aItem^.Prev := fLastLine^.Last;
  1274. aItem^.Next := nil;
  1275. fLastLine^.Last^.Next := aItem;
  1276. fLastLine^.Last := aItem;
  1277. end;
  1278. if not Assigned(fLastLine^.First) then begin
  1279. fLastLine^.First := aItem;
  1280. fLastLine^.Last := aItem;
  1281. end;
  1282. case aItem^.ItemType of
  1283. tsItemTypeSpace, tsItemTypeText:
  1284. fLastLine^.meta.Width := fLastLine^.meta.Width + aItem^.TextWidth;
  1285. tsItemTypeSpacing:
  1286. fLastLine^.meta.Width := fLastLine^.meta.Width + aItem^.Spacing;
  1287. end;
  1288. result := true;
  1289. end;
  1290. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1291. procedure TtsTextBlock.PushSpacing(const aWidth: Integer);
  1292. var
  1293. p: PtsLineItem;
  1294. begin
  1295. if (aWidth <= 0) then
  1296. exit;
  1297. new(p);
  1298. FillByte(p^, SizeOf(p^), 0);
  1299. p^.ItemType := tsItemTypeSpacing;
  1300. p^.Spacing := aWidth;
  1301. PushLineItem(p);
  1302. end;
  1303. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1304. procedure TtsTextBlock.FreeLineItem(var aItem: PtsLineItem);
  1305. begin
  1306. if Assigned(aItem^.Prev) then
  1307. aItem^.Prev^.Next := aItem^.Next;
  1308. if Assigned(aItem^.Next) then
  1309. aItem^.Next^.Prev := aItem^.Prev;
  1310. case aItem^.ItemType of
  1311. tsItemTypeText, tsItemTypeSpace:
  1312. tsStrDispose(aItem^.Text);
  1313. end;
  1314. Dispose(aItem);
  1315. aItem := nil;
  1316. end;
  1317. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1318. procedure TtsTextBlock.FreeLineItems(var aItem: PtsLineItem);
  1319. var
  1320. p: PtsLineItem;
  1321. begin
  1322. while Assigned(aItem) do begin
  1323. p := aItem;
  1324. aItem := aItem^.Next;
  1325. FreeLineItem(p);
  1326. end;
  1327. end;
  1328. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1329. procedure TtsTextBlock.FreeLines(var aItem: PtsBlockLine);
  1330. var
  1331. p: PtsBlockLine;
  1332. begin
  1333. while Assigned(aItem) do begin
  1334. p := aItem;
  1335. aItem := aItem^.Next;
  1336. FreeLineItems(p^.First);
  1337. p^.Last := nil;
  1338. Dispose(p);
  1339. end;
  1340. end;
  1341. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1342. function TtsTextBlock.SplitText(aText: PWideChar): PtsLineItem;
  1343. var
  1344. TextBegin: PWideChar;
  1345. TextLength: Integer;
  1346. State: TtsLineItemType;
  1347. LastItem: PtsLineItem;
  1348. procedure AddItem(const aItem: PtsLineItem);
  1349. begin
  1350. if Assigned(result) then begin
  1351. LastItem^.Next := aItem;
  1352. aItem^.Prev := LastItem;
  1353. aItem^.Next := nil;
  1354. LastItem := aItem;
  1355. end;
  1356. if not Assigned(result) then begin
  1357. result := aItem;
  1358. LastItem := aItem;
  1359. end;
  1360. end;
  1361. procedure ExtractWord;
  1362. var
  1363. p: PtsLineItem;
  1364. Text: PWideChar;
  1365. begin
  1366. if (State = tsItemTypeUnknown) then
  1367. exit;
  1368. new(p);
  1369. FillByte(p^, SizeOf(p^), 0);
  1370. p^.ItemType := State;
  1371. case State of
  1372. tsItemTypeText, tsItemTypeSpace: begin
  1373. p^.Text := tsStrAlloc(TextLength);
  1374. TextLength := 0;
  1375. Text := p^.Text;
  1376. while (TextBegin <> aText) do begin
  1377. Text^ := TextBegin^;
  1378. inc(Text, 1);
  1379. inc(TextBegin, 1);
  1380. end;
  1381. AddItem(p);
  1382. end;
  1383. tsItemTypeLineBreak: begin
  1384. AddItem(p);
  1385. TextBegin := aText;
  1386. end;
  1387. tsItemTypeTab: begin
  1388. AddItem(p);
  1389. end;
  1390. else
  1391. Dispose(p);
  1392. p := nil;
  1393. end;
  1394. end;
  1395. begin
  1396. result := nil;
  1397. LastItem := nil;
  1398. TextBegin := aText;
  1399. TextLength := 0;
  1400. State := tsItemTypeUnknown;
  1401. if not Assigned(aText) then
  1402. exit;
  1403. while (aText^ <> #0) do begin
  1404. case aText^ of
  1405. // line breaks
  1406. #$000D, #$000A: begin
  1407. if (State <> tsItemTypeLineBreak) then begin
  1408. ExtractWord;
  1409. State := tsItemTypeLineBreak;
  1410. end else if (TextBegin^ <> #13) or (aText^ <> #10) or (TextBegin + 1 < aText) then
  1411. ExtractWord;
  1412. end;
  1413. // spaces
  1414. #$0020: begin
  1415. if (State <> tsItemTypeSpace) then
  1416. ExtractWord;
  1417. State := tsItemTypeSpace;
  1418. end;
  1419. // tabulator
  1420. #$0009: begin
  1421. if (State <> tsItemTypeTab) then
  1422. ExtractWord;
  1423. State := tsItemTypeTab;
  1424. end;
  1425. else
  1426. if (State <> tsItemTypeText) then
  1427. ExtractWord;
  1428. State := tsItemTypeText;
  1429. end;
  1430. inc(aText, 1);
  1431. inc(TextLength, 1);
  1432. end;
  1433. if (TextBegin <> aText) then
  1434. ExtractWord;
  1435. end;
  1436. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1437. function TtsTextBlock.SplitIntoLines(aItem: PtsLineItem): Boolean;
  1438. var
  1439. p: PtsLineItem;
  1440. begin
  1441. result := falsE;
  1442. if not Assigned(fCurrentFont) then
  1443. exit;
  1444. result := true;
  1445. while Assigned(aItem) do begin
  1446. p := aItem;
  1447. aItem := aItem^.Next;
  1448. p^.Next := nil;
  1449. p^.Prev := nil;
  1450. if not Assigned(fLastLine) then
  1451. PushNewLine;
  1452. case p^.ItemType of
  1453. tsItemTypeText, tsItemTypeSpace: begin
  1454. // increment word counter
  1455. if (p^.ItemType = tsItemTypeSpace) then begin
  1456. if not (tsLastItemIsSpace in fLastLine^.Flags) then
  1457. inc(fLastLine^.meta.SpaceCount, 1);
  1458. Include(fLastLine^.Flags, tsLastItemIsSpace);
  1459. end else
  1460. Exclude(fLastLine^.Flags, tsLastItemIsSpace);
  1461. // update and check line width
  1462. p^.TextWidth := fCurrentFont.GetTextWidthW(p^.Text);
  1463. if (tsBlockFlagWordWrap in fFlags) and
  1464. (fLastLine^.meta.Width + p^.TextWidth > fWidth) then
  1465. begin
  1466. if (fLastLine^.meta.Width = 0) then begin
  1467. if not PushLineItem(p, false) then // if is first word, than add anyway
  1468. FreeLineItem(p);
  1469. p := nil;
  1470. end;
  1471. PushNewLine;
  1472. end;
  1473. // add item
  1474. if Assigned(p) then begin
  1475. if not PushLineItem(p) then
  1476. FreeLineItem(p);
  1477. PushSpacing(fCurrentFont.CharSpacing);
  1478. end;
  1479. end;
  1480. tsItemTypeLineBreak: begin
  1481. if not PushLineItem(p) then
  1482. FreeLineItem(p);
  1483. PushNewLine;
  1484. end;
  1485. tsItemTypeTab: begin
  1486. if not PushLineItem(p) then
  1487. FreeLineItem(p);
  1488. end;
  1489. else
  1490. raise EtsException.Create('unexpected line item');
  1491. end;
  1492. end;
  1493. end;
  1494. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1495. procedure TtsTextBlock.TrimSpaces(const aLine: PtsBlockLine);
  1496. procedure Trim(var aItem: PtsLineItem; const aMoveNext: Boolean);
  1497. var
  1498. tmp, p: PtsLineItem;
  1499. IsFirst: Boolean;
  1500. begin
  1501. IsFirst := true;
  1502. p := aItem;
  1503. while Assigned(p) do begin
  1504. tmp := p;
  1505. if aMoveNext then
  1506. p := p^.Next
  1507. else
  1508. p := p^.Prev;
  1509. case tmp^.ItemType of
  1510. tsItemTypeText: begin //done
  1511. break;
  1512. end;
  1513. tsItemTypeSpace,
  1514. tsItemTypeSpacing: begin
  1515. // update line meta
  1516. if (tmp^.ItemType = tsItemTypeSpace) then begin
  1517. aLine^.meta.Width := aLine^.meta.Width - tmp^.TextWidth;
  1518. dec(aLine^.meta.SpaceCount, 1);
  1519. end else
  1520. aLine^.meta.Width := aLine^.meta.Width - tmp^.Spacing;
  1521. FreeLineItem(tmp);
  1522. if IsFirst then
  1523. aItem := p;
  1524. end;
  1525. else
  1526. IsFirst := false;
  1527. end;
  1528. end;
  1529. end;
  1530. begin
  1531. if not Assigned(aLine) then
  1532. exit;
  1533. Trim(aLine^.First, true);
  1534. Trim(aLine^.Last, false);
  1535. end;
  1536. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1537. procedure TtsTextBlock.UpdateLineMeta(const aLine: PtsBlockLine);
  1538. var
  1539. metric: TtsTextMetric;
  1540. begin
  1541. if not Assigned(fCurrentFont) or
  1542. not Assigned(aLine) then
  1543. exit;
  1544. fCurrentFont.GetTextMetric(metric);
  1545. if (tsMetaValid in aLine^.Flags) then begin
  1546. aLine^.meta.Height := max(
  1547. aLine^.meta.Height,
  1548. metric.LineHeight);
  1549. aLine^.meta.Spacing := max(
  1550. aLine^.meta.Spacing,
  1551. metric.LineSpacing);
  1552. aLine^.meta.Ascent := max(
  1553. aLine^.meta.Ascent,
  1554. metric.Ascent);
  1555. end else begin
  1556. Include(aLine^.Flags, tsMetaValid);
  1557. aLine^.meta.Height := metric.LineHeight;
  1558. aLine^.meta.Spacing := metric.LineSpacing;
  1559. aLine^.meta.Ascent := metric.Ascent;
  1560. end;
  1561. end;
  1562. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1563. procedure TtsTextBlock.PushNewLine;
  1564. var
  1565. p: PtsBlockLine;
  1566. begin
  1567. TrimSpaces(fLastLine);
  1568. new(p);
  1569. FillByte(p^, SizeOf(p^), 0);
  1570. UpdateLineMeta(p);
  1571. if Assigned(fLastLine) then begin
  1572. fLastLine^.Next := p;
  1573. fLastLine := p;
  1574. end;
  1575. if not Assigned(fFirstLine) then begin
  1576. fFirstLine := p;
  1577. fLastLine := p;
  1578. end;
  1579. end;
  1580. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1581. constructor TtsTextBlock.Create(const aRenderer: TtsRenderer; const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags);
  1582. begin
  1583. inherited Create;
  1584. fRenderer := aRenderer;
  1585. fTop := aTop;
  1586. fLeft := aLeft;
  1587. fWidth := aWidth;
  1588. fHeight := aHeight;
  1589. fFlags := aFlags;
  1590. fVertAlign := tsVertAlignTop;
  1591. fHorzAlign := tsHorzAlignLeft;
  1592. PushNewLine;
  1593. end;
  1594. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1595. procedure TtsTextBlock.ChangeFont(const aFont: TtsFont);
  1596. var
  1597. p: PtsLineItem;
  1598. begin
  1599. if not Assigned(aFont) then
  1600. exit;
  1601. New(p);
  1602. FillByte(p^, SizeOf(p^), 0);
  1603. fCurrentFont := aFont;
  1604. p^.ItemType := tsItemTypeFont;
  1605. p^.Font := fCurrentFont;
  1606. PushLineItem(p);
  1607. UpdateLineMeta(fLastLine);
  1608. end;
  1609. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1610. procedure TtsTextBlock.ChangeColor(const aColor: TtsColor4f);
  1611. var
  1612. p: PtsLineItem;
  1613. begin
  1614. New(p);
  1615. FillByte(p^, SizeOf(p^), 0);
  1616. p^.ItemType := tsItemTypeColor;
  1617. p^.Color := aColor;
  1618. PushLineItem(p);
  1619. fCurrentColor := aColor;
  1620. end;
  1621. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1622. function TtsTextBlock.GetActualBlockHeight: Integer;
  1623. var
  1624. line: PtsBlockLine;
  1625. begin
  1626. result := 0;
  1627. line := fFirstLine;
  1628. while Assigned(line) do begin
  1629. result := result + line^.meta.Height;
  1630. line := line^.Next;
  1631. end;
  1632. end;
  1633. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1634. procedure TtsTextBlock.TextOutA(const aText: PAnsiChar);
  1635. var
  1636. tmp: PWideChar;
  1637. begin
  1638. tmp := Renderer.Context.AnsiToWide(aText);
  1639. try
  1640. TextOutW(tmp);
  1641. finally
  1642. tsStrDispose(tmp);
  1643. end;
  1644. end;
  1645. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1646. procedure TtsTextBlock.TextOutW(const aText: PWideChar);
  1647. var
  1648. p: PtsLineItem;
  1649. begin
  1650. p := SplitText(aText);
  1651. if not SplitIntoLines(p) then
  1652. FreeLineItems(p);
  1653. end;
  1654. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1655. destructor TtsTextBlock.Destroy;
  1656. begin
  1657. FreeLines(fFirstLine);
  1658. fLastLine := nil;
  1659. inherited Destroy;
  1660. end;
  1661. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1662. //TtsRenderer///////////////////////////////////////////////////////////////////////////////////////////////////////////
  1663. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1664. procedure TtsRenderer.BeginRender;
  1665. begin
  1666. fRenderCS.Enter;
  1667. end;
  1668. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1669. procedure TtsRenderer.EndRender;
  1670. begin
  1671. fRenderCS.Leave;
  1672. end;
  1673. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1674. function TtsRenderer.BeginBlock(const aTop, aLeft, aWidth, aHeight: Integer; const aFlags: TtsBlockFlags): TtsTextBlock;
  1675. begin
  1676. result := TtsTextBlock.Create(self, aTop, aLeft, aWidth, aHeight, aFlags);
  1677. end;
  1678. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1679. procedure TtsRenderer.EndBlock(var aBlock: TtsTextBlock);
  1680. var
  1681. c: PWideChar;
  1682. pos: TtsPosition;
  1683. x, y, tmp, tab: Integer;
  1684. ExtraSpaceTotal, ExtraSpaceActual: Single;
  1685. rect: TtsRect;
  1686. line: PtsBlockLine;
  1687. item: PtsLineItem;
  1688. font: TtsFont;
  1689. char: TtsChar;
  1690. metric: TtsTextMetric;
  1691. DrawText: Boolean;
  1692. function GetChar(const aCharCode: WideChar): TtsChar;
  1693. begin
  1694. result := font.AddChar(aCharCode);
  1695. if not Assigned(result) then
  1696. result := font.AddChar(font.Properties.DefaultChar);
  1697. end;
  1698. procedure DrawItem;
  1699. begin
  1700. case item^.ItemType of
  1701. tsItemTypeFont: begin
  1702. font := item^.Font;
  1703. font.GetTextMetric(metric);
  1704. end;
  1705. tsItemTypeColor: begin
  1706. SetColor(item^.Color);
  1707. end;
  1708. tsItemTypeText: begin
  1709. if DrawText and Assigned(font) then begin
  1710. c := item^.Text;
  1711. while (c^ <> #0) do begin
  1712. char := GetChar(c^);
  1713. if Assigned(char) then begin
  1714. MoveDrawPos(Char.GlyphOrigin.x, -metric.BaseLineOffset);
  1715. Render(char.RenderRef);
  1716. MoveDrawPos(char.Advance - char.GlyphOrigin.x + font.CharSpacing, metric.BaseLineOffset);
  1717. end;
  1718. inc(c);
  1719. end;
  1720. end;
  1721. end;
  1722. tsItemTypeSpace: begin
  1723. if DrawText and Assigned(font) then begin
  1724. ExtraSpaceActual := ExtraSpaceActual + ExtraSpaceTotal;
  1725. c := item^.Text;
  1726. while (c^ <> #0) do begin
  1727. char := GetChar(c^);
  1728. if Assigned(char) then begin
  1729. if (font.Properties.Style * [tsStyleUnderline, tsStyleStrikeout] <> []) then begin
  1730. MoveDrawPos(char.GlyphOrigin.x, -metric.BaseLineOffset);
  1731. Render(char.RenderRef);
  1732. MoveDrawPos(char.Advance - char.GlyphOrigin.x + font.CharSpacing, metric.BaseLineOffset);
  1733. end else begin
  1734. MoveDrawPos(char.Advance + font.CharSpacing, 0);
  1735. end;
  1736. end;
  1737. inc(c);
  1738. end;
  1739. tmp := Trunc(ExtraSpaceActual);
  1740. ExtraSpaceActual := ExtraSpaceActual - tmp;
  1741. if (font.Properties.Style * [tsStyleUnderline, tsStyleStrikeout] <> []) then begin
  1742. // TODO draw lines; maybe with a temporary created fake char or something like an empty char?
  1743. end;
  1744. MoveDrawPos(tmp, 0);
  1745. end;
  1746. end;
  1747. tsItemTypeLineBreak: begin
  1748. // because this should be the last item in a line, we have nothing to do here
  1749. end;
  1750. tsItemTypeTab: begin
  1751. // get current x pos and round it to TabWidth
  1752. pos := GetDrawPos;
  1753. tab := font.TabWidth * font.Properties.Size;
  1754. pos.x := Ceil(pos.x * tab) div tab;
  1755. SetDrawPos(pos.x, pos.y);
  1756. end;
  1757. tsItemTypeSpacing: begin
  1758. MoveDrawPos(item^.Spacing, 0);
  1759. end;
  1760. end;
  1761. end;
  1762. procedure DrawLine;
  1763. begin
  1764. // check vertical clipping
  1765. case aBlock.Clipping of
  1766. tsClipCharBorder, tsClipWordBorder:
  1767. DrawText := (y + line^.meta.Height > rect.Top) and (y < rect.Bottom);
  1768. tsClipCharComplete, tsClipWordComplete:
  1769. DrawText := (y > rect.Top) and (y + line^.meta.Height < rect.Bottom);
  1770. end;
  1771. // check horizontal alignment
  1772. x := rect.Left;
  1773. ExtraSpaceTotal := 0;
  1774. ExtraSpaceActual := 0;
  1775. case aBlock.HorzAlign of
  1776. tsHorzAlignCenter:
  1777. x := rect.Left + (aBlock.Width div 2) - (line^.meta.Width div 2);
  1778. tsHorzAlignRight:
  1779. x := rect.Right - line^.meta.Width;
  1780. tsHorzAlignJustify:
  1781. ExtraSpaceTotal := (aBlock.Width - line^.meta.Width) / line^.meta.SpaceCount;
  1782. end;
  1783. if DrawText then
  1784. SetDrawPos(x, y + line^.meta.Ascent);
  1785. inc(y, line^.meta.Height + line^.meta.Spacing);
  1786. item := line^.First;
  1787. while Assigned(item) do begin
  1788. DrawItem;
  1789. item := item^.Next;
  1790. end;
  1791. end;
  1792. begin
  1793. BeginRender;
  1794. try
  1795. // init variables
  1796. y := aBlock.Top;
  1797. font := nil;
  1798. line := aBlock.Lines;
  1799. rect := aBlock.Rect;
  1800. // check vertical alignment
  1801. case aBlock.VertAlign of
  1802. tsVertAlignCenter:
  1803. y := y + (aBlock.Height div 2 - aBlock.GetActualBlockHeight div 2);
  1804. tsVertAlignBottom:
  1805. y := y + (aBlock.Height - aBlock.GetActualBlockHeight);
  1806. end;
  1807. while Assigned(line) do begin
  1808. DrawLine;
  1809. line := line^.Next;
  1810. end;
  1811. finally
  1812. EndRender;
  1813. FreeAndNil(aBlock);
  1814. end;
  1815. end;
  1816. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1817. constructor TtsRenderer.Create(const aContext: TtsContext; const aFormat: TtsFormat);
  1818. begin
  1819. inherited Create;
  1820. fContext := aContext;
  1821. fFormat := aFormat;
  1822. fSaveImages := true;
  1823. fRenderCS := TCriticalSection.Create;
  1824. end;
  1825. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1826. destructor TtsRenderer.Destroy;
  1827. begin
  1828. FreeAndNil(fRenderCS);
  1829. inherited Destroy;
  1830. end;
  1831. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1832. //TtsContext////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1833. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1834. function TtsContext.AnsiToWide(const aText: PAnsiChar): PWideChar;
  1835. var
  1836. len: Integer;
  1837. begin
  1838. result := nil;
  1839. if not Assigned(aText) then
  1840. exit;
  1841. len := Length(aText);
  1842. // UTF-8
  1843. if (fCodePage = tsUTF8) then begin
  1844. result := tsStrAlloc(len);
  1845. tsUTF8ToWide(result, len, aText, fCodePageDefault);
  1846. // ISO 8859-1
  1847. end else if (fCodePage = tsISO_8859_1) then begin
  1848. result := tsStrAlloc(len);
  1849. tsISO_8859_1ToWide(result, len, aText);
  1850. // single or double byte CodePage
  1851. end else if Assigned(fCodePageFunc) and Assigned(fCodePagePtr) then begin
  1852. result := tsStrAlloc(len);
  1853. fCodePageFunc(result, len, aText, fCodePage, fCodePageDefault);
  1854. end;
  1855. end;
  1856. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1857. constructor TtsContext.Create;
  1858. begin
  1859. inherited Create;
  1860. fID := InterLockedIncrement(gLastContextID);
  1861. fCodePage := tsUTF8;
  1862. fCodePageFunc := nil;
  1863. fCodePagePtr := nil;
  1864. fCodePageDefault := WideChar('?');
  1865. end;
  1866. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1867. destructor TtsContext.Destroy;
  1868. begin
  1869. inherited Destroy;
  1870. end;
  1871. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1872. //Exceptions////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1873. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1874. constructor EtsOutOfRange.Create(const aMin, aMax, aIndex: Integer);
  1875. begin
  1876. inherited Create(Format('index (%d) is out of range (%d - %d)', [aIndex, aMin, aMax]));
  1877. end;
  1878. initialization
  1879. Randomize;
  1880. end.