babylon.text2d.ts 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. module BABYLON {
  2. export class Text2DRenderCache extends ModelRenderCache {
  3. effectsReady: boolean = false;
  4. vb: WebGLBuffer = null;
  5. ib: WebGLBuffer = null;
  6. instancingAttributes: InstancingAttributeInfo[] = null;
  7. fontTexture: BaseFontTexture = null;
  8. effect: Effect = null;
  9. effectInstanced: Effect = null;
  10. render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
  11. // Do nothing if the shader is still loading/preparing
  12. if (!this.effectsReady) {
  13. if ((this.effect && (!this.effect.isReady() || (this.effectInstanced && !this.effectInstanced.isReady())))) {
  14. return false;
  15. }
  16. this.effectsReady = true;
  17. }
  18. let canvas = instanceInfo.owner.owner;
  19. var engine = canvas.engine;
  20. this.fontTexture.update();
  21. let effect = context.useInstancing ? this.effectInstanced : this.effect;
  22. engine.enableEffect(effect);
  23. effect.setTexture("diffuseSampler", this.fontTexture);
  24. engine.bindBuffersDirectly(this.vb, this.ib, [1], 4, effect);
  25. let sdf = this.fontTexture.isSignedDistanceField;
  26. // Enable alpha mode only if the texture is not using SDF, SDF is rendered in AlphaTest mode, which mean no alpha blend
  27. var curAlphaMode: number;
  28. if (!sdf) {
  29. curAlphaMode = engine.getAlphaMode();
  30. engine.setAlphaMode(Engine.ALPHA_COMBINE, true);
  31. }
  32. let pid = context.groupInfoPartData[0];
  33. if (context.useInstancing) {
  34. if (!this.instancingAttributes) {
  35. this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, effect);
  36. }
  37. let glBuffer = context.instancedBuffers ? context.instancedBuffers[0] : pid._partBuffer;
  38. let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
  39. canvas._addDrawCallCount(1, context.renderMode);
  40. engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingAttributes);
  41. engine.draw(true, 0, 6, count);
  42. engine.unbindInstanceAttributes();
  43. } else {
  44. canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
  45. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  46. this.setupUniforms(effect, 0, pid._partData, i);
  47. engine.draw(true, 0, 6);
  48. }
  49. }
  50. if (!sdf) {
  51. engine.setAlphaMode(curAlphaMode, true);
  52. }
  53. return true;
  54. }
  55. public dispose(): boolean {
  56. if (!super.dispose()) {
  57. return false;
  58. }
  59. if (this.vb) {
  60. this._engine._releaseBuffer(this.vb);
  61. this.vb = null;
  62. }
  63. if (this.ib) {
  64. this._engine._releaseBuffer(this.ib);
  65. this.ib = null;
  66. }
  67. if (this.fontTexture) {
  68. this.fontTexture.decCachedFontTextureCounter();
  69. this.fontTexture = null;
  70. }
  71. this.effect = null;
  72. this.effectInstanced = null;
  73. return true;
  74. }
  75. }
  76. export class Text2DInstanceData extends InstanceDataBase {
  77. constructor(partId: number, dataElementCount: number) {
  78. super(partId, dataElementCount);
  79. }
  80. @instanceData()
  81. get topLeftUV(): Vector2 {
  82. return null;
  83. }
  84. set topLeftUV(value: Vector2) {
  85. }
  86. @instanceData()
  87. get sizeUV(): Vector2 {
  88. return null;
  89. }
  90. set sizeUV(value: Vector2) {
  91. }
  92. @instanceData()
  93. get textureSize(): Vector2 {
  94. return null;
  95. }
  96. set textureSize(value: Vector2) {
  97. }
  98. @instanceData()
  99. get color(): Color4 {
  100. return null;
  101. }
  102. set color(value: Color4) {
  103. }
  104. @instanceData()
  105. get superSampleFactor(): number {
  106. return null;
  107. }
  108. set superSampleFactor(value: number) {
  109. }
  110. }
  111. @className("Text2D", "BABYLON")
  112. /**
  113. * Primitive that render text using a specific font
  114. */
  115. export class Text2D extends RenderablePrim2D {
  116. static TEXT2D_MAINPARTID = 1;
  117. static TEXT2D_CATEGORY_SDF = "SignedDistanceField";
  118. public static fontProperty: Prim2DPropInfo;
  119. public static defaultFontColorProperty: Prim2DPropInfo;
  120. public static textProperty: Prim2DPropInfo;
  121. public static sizeProperty: Prim2DPropInfo;
  122. public static fontSuperSampleProperty: Prim2DPropInfo;
  123. public static fontSignedDistanceFieldProperty: Prim2DPropInfo;
  124. /**
  125. * Alignment is made relative to the left edge of the Content Area. Valid for horizontal alignment only.
  126. */
  127. public static get AlignLeft(): number { return Text2D._AlignLeft; }
  128. /**
  129. * Alignment is made relative to the top edge of the Content Area. Valid for vertical alignment only.
  130. */
  131. public static get AlignTop(): number { return Text2D._AlignTop; }
  132. /**
  133. * Alignment is made relative to the right edge of the Content Area. Valid for horizontal alignment only.
  134. */
  135. public static get AlignRight(): number { return Text2D._AlignRight; }
  136. /**
  137. * Alignment is made relative to the bottom edge of the Content Area. Valid for vertical alignment only.
  138. */
  139. public static get AlignBottom(): number { return Text2D._AlignBottom; }
  140. /**
  141. * Alignment is made to center the text from equal distance to the opposite edges of the Content Area
  142. */
  143. public static get AlignCenter(): number { return Text2D._AlignCenter; }
  144. private static _AlignLeft = 1;
  145. private static _AlignTop = 1; // Same as left
  146. private static _AlignRight = 2;
  147. private static _AlignBottom = 2; // Same as right
  148. private static _AlignCenter = 3;
  149. @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Text2D.fontProperty = pi, false, true)
  150. /**
  151. * Get/set the font name to use, using HTML CSS notation.
  152. * Set is not supported right now.
  153. */
  154. public get fontName(): string {
  155. return this._fontName;
  156. }
  157. public set fontName(value: string) {
  158. if (this._fontName) {
  159. throw new Error("Font Name change is not supported right now.");
  160. }
  161. this._fontName = value;
  162. }
  163. @dynamicLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Text2D.defaultFontColorProperty = pi)
  164. /**
  165. * Get/set the font default color
  166. */
  167. public get defaultFontColor(): Color4 {
  168. return this._defaultFontColor;
  169. }
  170. public set defaultFontColor(value: Color4) {
  171. if (!this._defaultFontColor) {
  172. this._defaultFontColor = value.clone();
  173. } else {
  174. this._defaultFontColor.copyFrom(value);
  175. }
  176. }
  177. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Text2D.textProperty = pi, false, true)
  178. /**
  179. * Get/set the text to render.
  180. * \n \t character are supported.
  181. */
  182. public get text(): string {
  183. return this._text;
  184. }
  185. public set text(value: string) {
  186. if (!value) {
  187. value = "";
  188. }
  189. this._text = value;
  190. this._textSize = null; // A change of text will reset the TextSize which will be recomputed next time it's used
  191. this._size = null;
  192. this._updateCharCount();
  193. // Trigger a textSize to for a sizeChange if necessary, which is needed for layout to recompute
  194. let s = this.textSize;
  195. }
  196. @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Text2D.sizeProperty = pi)
  197. /**
  198. * Get/set the size of the area where the text is drawn.
  199. * You should not set this size, the default behavior compute the size based on the actual text.
  200. */
  201. public get size(): Size {
  202. if (this._size != null) {
  203. return this._size;
  204. }
  205. return this.textSize;
  206. }
  207. public set size(value: Size) {
  208. this.internalSetSize(value);
  209. }
  210. @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Text2D.fontSuperSampleProperty = pi, false, false)
  211. /**
  212. * Get/set the font name to use, using HTML CSS notation.
  213. * Set is not supported right now.
  214. */
  215. public get fontSuperSample(): boolean {
  216. return this._fontTexture && this._fontTexture.isSuperSampled;
  217. }
  218. @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 6, pi => Text2D.fontSuperSampleProperty = pi, false, false)
  219. /**
  220. * Get/set the font name to use, using HTML CSS notation.
  221. * Set is not supported right now.
  222. */
  223. public get fontSignedDistanceField(): boolean {
  224. return this._fontTexture && this._fontTexture.isSignedDistanceField;
  225. }
  226. public get isSizeAuto(): boolean {
  227. return false;
  228. }
  229. /**
  230. * Get the actual size of the Text2D primitive
  231. */
  232. public get actualSize(): Size {
  233. if (this._actualSize) {
  234. return this._actualSize;
  235. }
  236. return this.size;
  237. }
  238. /**
  239. * Get the area that bounds the text associated to the primitive
  240. */
  241. public get textSize(): Size {
  242. if (!this._textSize) {
  243. if (this.owner && this._text) {
  244. let newSize = this.fontTexture.measureText(this._text, this._tabulationSize);
  245. if (!newSize.equals(this._textSize)) {
  246. this.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);
  247. this._positioningDirty();
  248. }
  249. this._textSize = newSize;
  250. } else {
  251. return Text2D.nullSize;
  252. }
  253. }
  254. return this._textSize;
  255. }
  256. protected get fontTexture(): BaseFontTexture {
  257. if (this._fontTexture) {
  258. return this._fontTexture;
  259. }
  260. if (this.fontName == null || this.owner == null || this.owner.scene == null) {
  261. return null;
  262. }
  263. this._fontTexture = FontTexture.GetCachedFontTexture(this.owner.scene, this.fontName, this._fontSuperSample, this._fontSDF);
  264. return this._fontTexture;
  265. }
  266. /**
  267. * Dispose the primitive, remove it from its parent
  268. */
  269. public dispose(): boolean {
  270. if (!super.dispose()) {
  271. return false;
  272. }
  273. if (this._fontTexture) {
  274. FontTexture.ReleaseCachedFontTexture(this.owner.scene, this.fontName, this._fontSuperSample, this._fontSDF);
  275. this._fontTexture = null;
  276. }
  277. return true;
  278. }
  279. protected updateLevelBoundingInfo() {
  280. if (!this.owner || !this._text) {
  281. return false;
  282. }
  283. BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
  284. return true;
  285. }
  286. /**
  287. * You can get/set the text alignment through this property
  288. */
  289. public get textAlignment(): string {
  290. return this._textAlignment;
  291. }
  292. public set textAlignment(value: string) {
  293. this._textAlignment = value;
  294. this._setTextAlignmentfromString(value);
  295. }
  296. /**
  297. * Create a Text primitive
  298. * @param text the text to display
  299. * @param settings a combination of settings, possible ones are
  300. * - parent: the parent primitive/canvas, must be specified if the primitive is not constructed as a child of another one (i.e. as part of the children array setting)
  301. * - children: an array of direct children
  302. * - id a text identifier, for information purpose
  303. * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
  304. * - rotation: the initial rotation (in radian) of the primitive. default is 0
  305. * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
  306. * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
  307. * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
  308. * - zOrder: override the zOrder with the specified value
  309. * - origin: define the normalized origin point location, default [0.5;0.5]
  310. * - fontName: the name/size/style of the font to use, following the CSS notation. Default is "12pt Arial".
  311. * - fontSuperSample: if true the text will be rendered with a superSampled font (the font is twice the given size). Use this settings if the text lies in world space or if it's scaled in.
  312. * - signedDistanceField: if true the text will be rendered using the SignedDistanceField technique. This technique has the advantage to be rendered order independent (then much less drawing calls), but only works on font that are a little more than one pixel wide on the screen but the rendering quality is excellent whatever the font size is on the screen (which is the purpose of this technique). Outlining/Shadow is not supported right now. If you can, you should use this mode, the quality and the performances are the best. Note that fontSuperSample has no effect when this mode is on.
  313. * - defaultFontColor: the color by default to apply on each letter of the text to display, default is plain white.
  314. * - areaSize: the size of the area in which to display the text, default is auto-fit from text content.
  315. * - tabulationSize: number of space character to insert when a tabulation is encountered, default is 4
  316. * - isVisible: true if the text must be visible, false for hidden. Default is true.
  317. * - isPickable: if true the Primitive can be used with interaction mode and will issue Pointer Event. If false it will be ignored for interaction/intersection test. Default value is true.
  318. * - isContainer: if true the Primitive acts as a container for interaction, if the primitive is not pickable or doesn't intersection, no further test will be perform on its children. If set to false, children will always be considered for intersection/interaction. Default value is true.
  319. * - childrenFlatZOrder: if true all the children (direct and indirect) will share the same Z-Order. Use this when there's a lot of children which don't overlap. The drawing order IS NOT GUARANTED!
  320. * - levelCollision: this primitive is an actor of the Collision Manager and only this level will be used for collision (i.e. not the children). Use deepCollision if you want collision detection on the primitives and its children.
  321. * - deepCollision: this primitive is an actor of the Collision Manager, this level AND ALSO its children will be used for collision (note: you don't need to set the children as level/deepCollision).
  322. * - layoutData: a instance of a class implementing the ILayoutData interface that contain data to pass to the primitive parent's layout engine
  323. * - marginTop: top margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  324. * - marginLeft: left margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  325. * - marginRight: right margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  326. * - marginBottom: bottom margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  327. * - margin: top, left, right and bottom margin formatted as a single string (see PrimitiveThickness.fromString)
  328. * - marginHAlignment: one value of the PrimitiveAlignment type's static properties
  329. * - marginVAlignment: one value of the PrimitiveAlignment type's static properties
  330. * - marginAlignment: a string defining the alignment, see PrimitiveAlignment.fromString
  331. * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  332. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  333. * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  334. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  335. * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
  336. * - textAlignmentH: align text horizontally (Text2D.AlignLeft, Text2D.AlignCenter, Text2D.AlignRight)
  337. * - textAlignmentV: align text vertically (Text2D.AlignTop, Text2D.AlignCenter, Text2D.AlignBottom)
  338. * - textAlignment: a string defining the text alignment, text can be: [<h:|horizontal:><left|right|center>], [<v:|vertical:><top|bottom|center>]
  339. * - wordWrap: if true the text will wrap inside content area
  340. */
  341. constructor(text: string, settings?: {
  342. parent ?: Prim2DBase,
  343. children ?: Array<Prim2DBase>,
  344. id ?: string,
  345. position ?: Vector2,
  346. x ?: number,
  347. y ?: number,
  348. rotation ?: number,
  349. scale ?: number,
  350. scaleX ?: number,
  351. scaleY ?: number,
  352. dontInheritParentScale ?: boolean,
  353. opacity ?: number,
  354. zOrder ?: number,
  355. origin ?: Vector2,
  356. fontName ?: string,
  357. fontSuperSample ?: boolean,
  358. fontSignedDistanceField ?: boolean,
  359. bitmapFontTexture ?: BitmapFontTexture,
  360. defaultFontColor ?: Color4,
  361. size ?: Size,
  362. tabulationSize ?: number,
  363. isVisible ?: boolean,
  364. isPickable ?: boolean,
  365. isContainer ?: boolean,
  366. childrenFlatZOrder ?: boolean,
  367. levelCollision ?: boolean,
  368. deepCollision ?: boolean,
  369. layoutData ?: ILayoutData,
  370. marginTop ?: number | string,
  371. marginLeft ?: number | string,
  372. marginRight ?: number | string,
  373. marginBottom ?: number | string,
  374. margin ?: number | string,
  375. marginHAlignment ?: number,
  376. marginVAlignment ?: number,
  377. marginAlignment ?: string,
  378. paddingTop ?: number | string,
  379. paddingLeft ?: number | string,
  380. paddingRight ?: number | string,
  381. paddingBottom ?: number | string,
  382. padding ?: string,
  383. textAlignmentH ?: number,
  384. textAlignmentV ?: number,
  385. textAlignment ?: string,
  386. wordWrap ?: boolean
  387. }) {
  388. if (!settings) {
  389. settings = {};
  390. }
  391. super(settings);
  392. if (settings.bitmapFontTexture != null) {
  393. this._fontTexture = settings.bitmapFontTexture;
  394. this._fontName = null;
  395. this._fontSuperSample = false;
  396. this._fontSDF = false;
  397. } else {
  398. this._fontName = (settings.fontName==null) ? "12pt Arial" : settings.fontName;
  399. this._fontSuperSample= (settings.fontSuperSample!=null && settings.fontSuperSample);
  400. this._fontSDF = (settings.fontSignedDistanceField!=null && settings.fontSignedDistanceField);
  401. }
  402. this._defaultFontColor = (settings.defaultFontColor==null) ? new Color4(1,1,1,1) : settings.defaultFontColor.clone();
  403. this._tabulationSize = (settings.tabulationSize == null) ? 4 : settings.tabulationSize;
  404. this._textSize = null;
  405. this.text = text;
  406. this.size = (settings.size==null) ? null : settings.size;
  407. this.textAlignmentH = (settings.textAlignmentH==null) ? Text2D.AlignLeft : settings.textAlignmentH;
  408. this.textAlignmentV = (settings.textAlignmentV==null) ? Text2D.AlignTop : settings.textAlignmentV;
  409. this.textAlignment = (settings.textAlignment==null) ? "" : settings.textAlignment;
  410. this._wordWrap = (settings.wordWrap==null) ? false : settings.wordWrap;
  411. this._updateRenderMode();
  412. }
  413. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  414. // For now I can't do something better that boundingInfo is a hit, detecting an intersection on a particular letter would be possible, but do we really need it? Not for now...
  415. return true;
  416. }
  417. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  418. let renderCache = new Text2DRenderCache(this.owner.engine, modelKey);
  419. return renderCache;
  420. }
  421. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  422. let renderCache = <Text2DRenderCache>modelRenderCache;
  423. let engine = this.owner.engine;
  424. renderCache.fontTexture = this.fontTexture;
  425. renderCache.fontTexture.incCachedFontTextureCounter();
  426. let vb = new Float32Array(4);
  427. for (let i = 0; i < 4; i++) {
  428. vb[i] = i;
  429. }
  430. renderCache.vb = engine.createVertexBuffer(vb);
  431. let ib = new Float32Array(6);
  432. ib[0] = 0;
  433. ib[1] = 2;
  434. ib[2] = 1;
  435. ib[3] = 0;
  436. ib[4] = 3;
  437. ib[5] = 2;
  438. renderCache.ib = engine.createIndexBuffer(ib);
  439. // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
  440. let ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"], null, true);
  441. if (ei) {
  442. renderCache.effectInstanced = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
  443. }
  444. ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"], null, false);
  445. renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null);
  446. return renderCache;
  447. }
  448. protected createInstanceDataParts(): InstanceDataBase[] {
  449. return [new Text2DInstanceData(Text2D.TEXT2D_MAINPARTID, this._charCount)];
  450. }
  451. // Looks like a hack!? Yes! Because that's what it is!
  452. // For the InstanceData layer to compute correctly we need to set all the properties involved, which won't be the case if there's no text
  453. // This method is called before the layout construction for us to detect this case, set some text and return the initial one to restore it after (there can be some text without char to display, say "\t\n" for instance)
  454. protected beforeRefreshForLayoutConstruction(part: InstanceDataBase): any {
  455. if (!this._charCount) {
  456. let curText = this._text;
  457. this.text = "A";
  458. return curText;
  459. }
  460. }
  461. // if obj contains something, we restore the _text property
  462. protected afterRefreshForLayoutConstruction(part: InstanceDataBase, obj: any) {
  463. if (obj !== undefined) {
  464. this.text = obj;
  465. }
  466. }
  467. protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
  468. var cat = super.getUsedShaderCategories(dataPart);
  469. if (this._fontSDF) {
  470. cat.push(Text2D.TEXT2D_CATEGORY_SDF);
  471. }
  472. return cat;
  473. }
  474. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  475. if (!super.refreshInstanceDataPart(part)) {
  476. return false;
  477. }
  478. if (part.id === Text2D.TEXT2D_MAINPARTID) {
  479. let d = <Text2DInstanceData>part;
  480. let texture = this.fontTexture;
  481. let superSampleFactor = texture.isSuperSampled ? 0.5 : 1;
  482. let ts = texture.getSize();
  483. let offset = Vector2.Zero();
  484. let lh = this.fontTexture.lineHeight;
  485. d.dataElementCount = this._charCount;
  486. d.curElement = 0;
  487. let lineLengths = [];
  488. let charWidths = [];
  489. let charsPerLine = [];
  490. let numCharsCurrenLine = 0;
  491. let contentAreaWidth = this.contentArea.width;
  492. let contentAreaHeight = this.contentArea.height;
  493. let numCharsCurrentWord = 0;
  494. let widthCurrentWord = 0;
  495. let numWordsPerLine = 0;
  496. let text = this.text;
  497. let tabWidth = this._tabulationSize * texture.spaceWidth;
  498. for (let i = 0; i < text.length; i++) {
  499. let char = text[i];
  500. numCharsCurrenLine++;
  501. charWidths[i] = 0;
  502. // Line feed
  503. if (this._isWhiteSpaceCharVert(char)) {
  504. lineLengths.push(offset.x);
  505. charsPerLine.push(numCharsCurrenLine - 1);
  506. numCharsCurrenLine = 1;
  507. offset.x = 0;
  508. if (widthCurrentWord > 0) {
  509. numWordsPerLine++;
  510. }
  511. numWordsPerLine = 0;
  512. numCharsCurrentWord = 0;
  513. widthCurrentWord = 0;
  514. continue;
  515. }
  516. let ci = texture.getChar(char);
  517. let charWidth = 0;
  518. if (char === "\t") {
  519. charWidth = tabWidth;
  520. }else{
  521. charWidth = ci.charWidth;
  522. }
  523. offset.x += charWidth;
  524. charWidths[i] = charWidth;
  525. if (this._isWhiteSpaceCharHoriz(char)) {
  526. if (widthCurrentWord > 0) {
  527. numWordsPerLine++;
  528. }
  529. numCharsCurrentWord = 0;
  530. widthCurrentWord = 0;
  531. }else {
  532. widthCurrentWord += ci.charWidth;
  533. numCharsCurrentWord++;
  534. }
  535. if (this._wordWrap && numWordsPerLine > 0 && offset.x > contentAreaWidth) {
  536. lineLengths.push(offset.x - widthCurrentWord);
  537. numCharsCurrenLine -= numCharsCurrentWord;
  538. let j = i - numCharsCurrentWord;
  539. //skip white space at the end of this line
  540. while (this._isWhiteSpaceCharHoriz(text[j])) {
  541. lineLengths[lineLengths.length - 1] -= charWidths[j];
  542. j--;
  543. }
  544. charsPerLine.push(numCharsCurrenLine);
  545. if(this._isWhiteSpaceCharHoriz(text[i])){
  546. //skip white space at the beginning of next line
  547. let numSpaces = 0;
  548. while (this._isWhiteSpaceCharHoriz(text[i+numSpaces])) {
  549. numSpaces++;
  550. charWidths[i+numSpaces] = 0;
  551. }
  552. i += numSpaces-1;
  553. offset.x = 0;
  554. numCharsCurrenLine = numSpaces-1;
  555. }else{
  556. numCharsCurrenLine = numCharsCurrentWord;
  557. offset.x = widthCurrentWord;
  558. }
  559. numWordsPerLine = 0;
  560. }
  561. }
  562. lineLengths.push(offset.x);
  563. charsPerLine.push(numCharsCurrenLine);
  564. //skip white space at the end
  565. let i = text.length - 1;
  566. while (this._isWhiteSpaceCharHoriz(text[i])) {
  567. lineLengths[lineLengths.length - 1] -= charWidths[i];
  568. i--;
  569. }
  570. let charNum = 0;
  571. let maxLineLen = 0;
  572. let alignH = this.textAlignmentH;
  573. let alignV = this.textAlignmentV;
  574. offset.x = 0;
  575. if (alignH == Text2D.AlignRight || alignH == Text2D.AlignCenter) {
  576. for (let i = 0; i < lineLengths.length; i++) {
  577. if (lineLengths[i] > maxLineLen) {
  578. maxLineLen = lineLengths[i];
  579. }
  580. }
  581. }
  582. let textHeight = lineLengths.length * lh;
  583. let offsetX = this.padding.leftPixels;
  584. if (alignH == Text2D.AlignRight) {
  585. offsetX += contentAreaWidth - maxLineLen;
  586. } else if (alignH == Text2D.AlignCenter) {
  587. offsetX += (contentAreaWidth - maxLineLen) * .5;
  588. }
  589. offset.x += offsetX;
  590. offset.y += contentAreaHeight + textHeight - lh;
  591. offset.y += this.padding.bottomPixels;
  592. if (alignV == Text2D.AlignBottom) {
  593. offset.y -= contentAreaHeight;
  594. }else if (alignV == Text2D.AlignCenter) {
  595. offset.y -= (contentAreaHeight - textHeight) * .5 + lineLengths.length * lh;
  596. }else {
  597. offset.y -= lineLengths.length * lh;
  598. }
  599. for (let i = 0; i < lineLengths.length; i++) {
  600. let numChars = charsPerLine[i];
  601. let lineLength = lineLengths[i];
  602. if (alignH == Text2D.AlignRight) {
  603. offset.x += maxLineLen - lineLength;
  604. }else if (alignH == Text2D.AlignCenter) {
  605. offset.x += (maxLineLen - lineLength) * .5;
  606. }
  607. for (let j = 0; j < numChars; j++) {
  608. let char = text[charNum];
  609. let charWidth = charWidths[charNum];
  610. if(!this._isWhiteSpaceCharHoriz(char) && !this._isWhiteSpaceCharVert(char)){
  611. this.updateInstanceDataPart(d, offset);
  612. let ci = texture.getChar(char);
  613. d.topLeftUV = ci.topLeftUV;
  614. let suv = ci.bottomRightUV.subtract(ci.topLeftUV);
  615. d.sizeUV = suv;
  616. d.textureSize = new BABYLON.Vector2(ts.width, ts.height);
  617. d.color = this.defaultFontColor;
  618. d.superSampleFactor = superSampleFactor;
  619. ++d.curElement;
  620. }
  621. offset.x += charWidth;
  622. charNum++;
  623. }
  624. offset.x = offsetX;
  625. offset.y -= texture.lineHeight;
  626. }
  627. }
  628. return true;
  629. }
  630. private _isWhiteSpaceCharHoriz(char): boolean {
  631. if(char === " " || char === "\t"){
  632. return true;
  633. }
  634. }
  635. private _isWhiteSpaceCharVert(char): boolean {
  636. if(char === "\n" || char === "\r"){
  637. return true;
  638. }
  639. }
  640. private _updateCharCount() {
  641. let count = 0;
  642. for (let char of this._text) {
  643. if (char === "\r" || char === "\n" || char === "\t" || char < " ") {
  644. continue;
  645. }
  646. ++count;
  647. }
  648. this._charCount = count;
  649. }
  650. private _setTextAlignmentfromString(value: string) {
  651. let m = value.trim().split(",");
  652. for (let v of m) {
  653. v = v.toLocaleLowerCase().trim();
  654. // Horizontal
  655. let i = v.indexOf("h:");
  656. if (i === -1) {
  657. i = v.indexOf("horizontal:");
  658. }
  659. if (i !== -1) {
  660. v = v.substr(v.indexOf(":") + 1);
  661. this._setTextAlignmentHorizontal(v);
  662. continue;
  663. }
  664. // Vertical
  665. i = v.indexOf("v:");
  666. if (i === -1) {
  667. i = v.indexOf("vertical:");
  668. }
  669. if (i !== -1) {
  670. v = v.substr(v.indexOf(":") + 1);
  671. this._setTextAlignmentVertical(v);
  672. continue;
  673. }
  674. }
  675. }
  676. private _setTextAlignmentHorizontal(text: string) {
  677. let v = text.trim().toLocaleLowerCase();
  678. switch (v) {
  679. case "left":
  680. this.textAlignmentH = Text2D.AlignLeft;
  681. return;
  682. case "right":
  683. this.textAlignmentH = Text2D.AlignRight;
  684. return;
  685. case "center":
  686. this.textAlignmentH = Text2D.AlignCenter;
  687. return;
  688. }
  689. }
  690. private _setTextAlignmentVertical(text: string) {
  691. let v = text.trim().toLocaleLowerCase();
  692. switch (v) {
  693. case "top":
  694. this.textAlignmentV = Text2D.AlignTop;
  695. return;
  696. case "bottom":
  697. this.textAlignmentV = Text2D.AlignBottom;
  698. return;
  699. case "center":
  700. this.textAlignmentV = Text2D.AlignCenter;
  701. return;
  702. }
  703. }
  704. protected _useTextureAlpha(): boolean {
  705. return this._fontSDF;
  706. }
  707. protected _shouldUseAlphaFromTexture(): boolean {
  708. return !this._fontSDF;
  709. }
  710. private _fontTexture: BaseFontTexture;
  711. private _tabulationSize: number;
  712. private _charCount: number;
  713. private _fontName: string;
  714. private _fontSuperSample: boolean;
  715. private _fontSDF: boolean;
  716. private _defaultFontColor: Color4;
  717. private _text: string;
  718. private _textSize: Size;
  719. private _wordWrap: boolean;
  720. private _textAlignment: string;
  721. public textAlignmentH: number;
  722. public textAlignmentV: number;
  723. }
  724. }