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.

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