FontLoader.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import {
  2. FileLoader,
  3. Loader,
  4. ShapePath
  5. } from 'three';
  6. /**
  7. * A loader for loading fonts.
  8. *
  9. * You can convert fonts online using [facetype.js]{@link https://gero3.github.io/facetype.js/}.
  10. *
  11. * ```js
  12. * const loader = new FontLoader();
  13. * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' );
  14. * ```
  15. *
  16. * @augments Loader
  17. */
  18. class FontLoader extends Loader {
  19. /**
  20. * Constructs a new font loader.
  21. *
  22. * @param {LoadingManager} [manager] - The loading manager.
  23. */
  24. constructor( manager ) {
  25. super( manager );
  26. }
  27. /**
  28. * Starts loading from the given URL and passes the loaded font
  29. * to the `onLoad()` callback.
  30. *
  31. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  32. * @param {function(Font)} onLoad - Executed when the loading process has been finished.
  33. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  34. * @param {onErrorCallback} onError - Executed when errors occur.
  35. */
  36. load( url, onLoad, onProgress, onError ) {
  37. const scope = this;
  38. const loader = new FileLoader( this.manager );
  39. loader.setPath( this.path );
  40. loader.setRequestHeader( this.requestHeader );
  41. loader.setWithCredentials( this.withCredentials );
  42. loader.load( url, function ( text ) {
  43. const font = scope.parse( JSON.parse( text ) );
  44. if ( onLoad ) onLoad( font );
  45. }, onProgress, onError );
  46. }
  47. /**
  48. * Parses the given font data and returns the resulting font.
  49. *
  50. * @param {Object} json - The raw font data as a JSON object.
  51. * @return {Font} The font.
  52. */
  53. parse( json ) {
  54. return new Font( json );
  55. }
  56. }
  57. /**
  58. * Class representing a font.
  59. */
  60. class Font {
  61. /**
  62. * Constructs a new font.
  63. *
  64. * @param {Object} data - The font data as JSON.
  65. */
  66. constructor( data ) {
  67. /**
  68. * This flag can be used for type testing.
  69. *
  70. * @type {boolean}
  71. * @readonly
  72. * @default true
  73. */
  74. this.isFont = true;
  75. this.type = 'Font';
  76. /**
  77. * The font data as JSON.
  78. *
  79. * @type {Object}
  80. */
  81. this.data = data;
  82. }
  83. /**
  84. * Generates geometry shapes from the given text and size. The result of this method
  85. * should be used with {@link ShapeGeometry} to generate the actual geometry data.
  86. *
  87. * @param {string} text - The text.
  88. * @param {number} [size=100] - The text size.
  89. * @return {Array<Shape>} An array of shapes representing the text.
  90. */
  91. generateShapes( text, size = 100 ) {
  92. const shapes = [];
  93. const paths = createPaths( text, size, this.data );
  94. for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
  95. shapes.push( ...paths[ p ].toShapes() );
  96. }
  97. return shapes;
  98. }
  99. }
  100. function createPaths( text, size, data ) {
  101. const chars = Array.from( text );
  102. const scale = size / data.resolution;
  103. const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
  104. const paths = [];
  105. let offsetX = 0, offsetY = 0;
  106. for ( let i = 0; i < chars.length; i ++ ) {
  107. const char = chars[ i ];
  108. if ( char === '\n' ) {
  109. offsetX = 0;
  110. offsetY -= line_height;
  111. } else {
  112. const ret = createPath( char, scale, offsetX, offsetY, data );
  113. offsetX += ret.offsetX;
  114. paths.push( ret.path );
  115. }
  116. }
  117. return paths;
  118. }
  119. function createPath( char, scale, offsetX, offsetY, data ) {
  120. const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
  121. if ( ! glyph ) {
  122. console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
  123. return;
  124. }
  125. const path = new ShapePath();
  126. let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
  127. if ( glyph.o ) {
  128. const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
  129. for ( let i = 0, l = outline.length; i < l; ) {
  130. const action = outline[ i ++ ];
  131. switch ( action ) {
  132. case 'm': // moveTo
  133. x = outline[ i ++ ] * scale + offsetX;
  134. y = outline[ i ++ ] * scale + offsetY;
  135. path.moveTo( x, y );
  136. break;
  137. case 'l': // lineTo
  138. x = outline[ i ++ ] * scale + offsetX;
  139. y = outline[ i ++ ] * scale + offsetY;
  140. path.lineTo( x, y );
  141. break;
  142. case 'q': // quadraticCurveTo
  143. cpx = outline[ i ++ ] * scale + offsetX;
  144. cpy = outline[ i ++ ] * scale + offsetY;
  145. cpx1 = outline[ i ++ ] * scale + offsetX;
  146. cpy1 = outline[ i ++ ] * scale + offsetY;
  147. path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
  148. break;
  149. case 'b': // bezierCurveTo
  150. cpx = outline[ i ++ ] * scale + offsetX;
  151. cpy = outline[ i ++ ] * scale + offsetY;
  152. cpx1 = outline[ i ++ ] * scale + offsetX;
  153. cpy1 = outline[ i ++ ] * scale + offsetY;
  154. cpx2 = outline[ i ++ ] * scale + offsetX;
  155. cpy2 = outline[ i ++ ] * scale + offsetY;
  156. path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
  157. break;
  158. }
  159. }
  160. }
  161. return { offsetX: glyph.ha * scale, path: path };
  162. }
  163. export { FontLoader, Font };
粤ICP备19079148号