No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

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