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.

2532 line
75 KiB

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