You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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