Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

2279 linhas
70 KiB

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