TTFLoader.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import {
  2. FileLoader,
  3. Loader
  4. } from 'three';
  5. import opentype from '../libs/opentype.module.js';
  6. /**
  7. * A loader for the TTF format.
  8. *
  9. * Loads TTF files and converts them into typeface JSON that can be used directly
  10. * to create THREE.Font objects.
  11. *
  12. * ```js
  13. * const loader = new TTFLoader();
  14. * const json = await loader.loadAsync( 'fonts/ttf/kenpixel.ttf' );
  15. * const font = new Font( json );
  16. * ```
  17. *
  18. * @augments Loader
  19. * @three_import import { TTFLoader } from 'three/addons/loaders/TTFLoader.js';
  20. */
  21. class TTFLoader extends Loader {
  22. /**
  23. * Constructs a new TTF loader.
  24. *
  25. * @param {LoadingManager} [manager] - The loading manager.
  26. */
  27. constructor( manager ) {
  28. super( manager );
  29. /**
  30. * Whether the TTF commands should be reversed or not.
  31. *
  32. * @type {boolean}
  33. * @default false
  34. */
  35. this.reversed = false;
  36. }
  37. /**
  38. * Starts loading from the given URL and passes the loaded TTF asset
  39. * to the `onLoad()` callback.
  40. *
  41. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  42. * @param {function(Object)} onLoad - Executed when the loading process has been finished.
  43. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  44. * @param {onErrorCallback} onError - Executed when errors occur.
  45. */
  46. load( url, onLoad, onProgress, onError ) {
  47. const scope = this;
  48. const loader = new FileLoader( this.manager );
  49. loader.setPath( this.path );
  50. loader.setResponseType( 'arraybuffer' );
  51. loader.setRequestHeader( this.requestHeader );
  52. loader.setWithCredentials( this.withCredentials );
  53. loader.load( url, function ( buffer ) {
  54. try {
  55. onLoad( scope.parse( buffer ) );
  56. } catch ( e ) {
  57. if ( onError ) {
  58. onError( e );
  59. } else {
  60. console.error( e );
  61. }
  62. scope.manager.itemError( url );
  63. }
  64. }, onProgress, onError );
  65. }
  66. /**
  67. * Parses the given TTF data and returns a JSON for creating a font.
  68. *
  69. * @param {ArrayBuffer} arraybuffer - The raw TTF data as an array buffer.
  70. * @return {Object} The result JSON.
  71. */
  72. parse( arraybuffer ) {
  73. function convert( font, reversed ) {
  74. const round = Math.round;
  75. const glyphs = {};
  76. const scale = ( 100000 ) / ( ( font.unitsPerEm || 2048 ) * 72 );
  77. const glyphIndexMap = font.encoding.cmap.glyphIndexMap;
  78. const unicodes = Object.keys( glyphIndexMap );
  79. for ( let i = 0; i < unicodes.length; i ++ ) {
  80. const unicode = unicodes[ i ];
  81. const glyph = font.glyphs.glyphs[ glyphIndexMap[ unicode ] ];
  82. if ( unicode !== undefined ) {
  83. const token = {
  84. ha: round( glyph.advanceWidth * scale ),
  85. x_min: round( glyph.xMin * scale ),
  86. x_max: round( glyph.xMax * scale ),
  87. o: ''
  88. };
  89. if ( reversed ) {
  90. glyph.path.commands = reverseCommands( glyph.path.commands );
  91. }
  92. glyph.path.commands.forEach( function ( command ) {
  93. if ( command.type.toLowerCase() === 'c' ) {
  94. command.type = 'b';
  95. }
  96. token.o += command.type.toLowerCase() + ' ';
  97. if ( command.x !== undefined && command.y !== undefined ) {
  98. token.o += round( command.x * scale ) + ' ' + round( command.y * scale ) + ' ';
  99. }
  100. if ( command.x1 !== undefined && command.y1 !== undefined ) {
  101. token.o += round( command.x1 * scale ) + ' ' + round( command.y1 * scale ) + ' ';
  102. }
  103. if ( command.x2 !== undefined && command.y2 !== undefined ) {
  104. token.o += round( command.x2 * scale ) + ' ' + round( command.y2 * scale ) + ' ';
  105. }
  106. } );
  107. glyphs[ String.fromCodePoint( glyph.unicode ) ] = token;
  108. }
  109. }
  110. return {
  111. glyphs: glyphs,
  112. familyName: font.getEnglishName( 'fullName' ),
  113. ascender: round( font.ascender * scale ),
  114. descender: round( font.descender * scale ),
  115. underlinePosition: font.tables.post.underlinePosition,
  116. underlineThickness: font.tables.post.underlineThickness,
  117. boundingBox: {
  118. xMin: font.tables.head.xMin,
  119. xMax: font.tables.head.xMax,
  120. yMin: font.tables.head.yMin,
  121. yMax: font.tables.head.yMax
  122. },
  123. resolution: 1000,
  124. original_font_information: font.tables.name
  125. };
  126. }
  127. function reverseCommands( commands ) {
  128. const paths = [];
  129. let path;
  130. commands.forEach( function ( c ) {
  131. if ( c.type.toLowerCase() === 'm' ) {
  132. path = [ c ];
  133. paths.push( path );
  134. } else if ( c.type.toLowerCase() !== 'z' ) {
  135. path.push( c );
  136. }
  137. } );
  138. const reversed = [];
  139. paths.forEach( function ( p ) {
  140. const result = {
  141. type: 'm',
  142. x: p[ p.length - 1 ].x,
  143. y: p[ p.length - 1 ].y
  144. };
  145. reversed.push( result );
  146. for ( let i = p.length - 1; i > 0; i -- ) {
  147. const command = p[ i ];
  148. const result = { type: command.type };
  149. if ( command.x2 !== undefined && command.y2 !== undefined ) {
  150. result.x1 = command.x2;
  151. result.y1 = command.y2;
  152. result.x2 = command.x1;
  153. result.y2 = command.y1;
  154. } else if ( command.x1 !== undefined && command.y1 !== undefined ) {
  155. result.x1 = command.x1;
  156. result.y1 = command.y1;
  157. }
  158. result.x = p[ i - 1 ].x;
  159. result.y = p[ i - 1 ].y;
  160. reversed.push( result );
  161. }
  162. } );
  163. return reversed;
  164. }
  165. return convert( opentype.parse( arraybuffer ), this.reversed );
  166. }
  167. }
  168. export { TTFLoader };