Skip to content

Commit 1225f52

Browse files
RikaheiMugen87
andauthored
FontLoader: Add text direction. (#31683)
* Rebase from latest dev branch * Change random color to fix * Revert webgl_loader_ttf example * Move location of MPLUS font and rename * Fix font path * zip MPLUSRounded1c to reduce size * Fix un-used class * Update TextGeometry.js * Update FontLoader.js --------- Co-authored-by: Michael Herzog <michael.herzog@human-interactive.org>
1 parent f972284 commit 1225f52

File tree

6 files changed

+211
-64
lines changed

6 files changed

+211
-64
lines changed
Binary file not shown.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
2+
This license is copied below, and is also available with a FAQ at:
3+
https://openfontlicense.org
4+
5+
6+
-----------------------------------------------------------
7+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
8+
-----------------------------------------------------------
9+
10+
PREAMBLE
11+
The goals of the Open Font License (OFL) are to stimulate worldwide
12+
development of collaborative font projects, to support the font creation
13+
efforts of academic and linguistic communities, and to provide a free and
14+
open framework in which fonts may be shared and improved in partnership
15+
with others.
16+
17+
The OFL allows the licensed fonts to be used, studied, modified and
18+
redistributed freely as long as they are not sold by themselves. The
19+
fonts, including any derivative works, can be bundled, embedded,
20+
redistributed and/or sold with any software provided that any reserved
21+
names are not used by derivative works. The fonts and derivatives,
22+
however, cannot be released under any other type of license. The
23+
requirement for fonts to remain under this license does not apply
24+
to any document created using the fonts or their derivatives.
25+
26+
DEFINITIONS
27+
"Font Software" refers to the set of files released by the Copyright
28+
Holder(s) under this license and clearly marked as such. This may
29+
include source files, build scripts and documentation.
30+
31+
"Reserved Font Name" refers to any names specified as such after the
32+
copyright statement(s).
33+
34+
"Original Version" refers to the collection of Font Software components as
35+
distributed by the Copyright Holder(s).
36+
37+
"Modified Version" refers to any derivative made by adding to, deleting,
38+
or substituting -- in part or in whole -- any of the components of the
39+
Original Version, by changing formats or by porting the Font Software to a
40+
new environment.
41+
42+
"Author" refers to any designer, engineer, programmer, technical
43+
writer or other person who contributed to the Font Software.
44+
45+
PERMISSION & CONDITIONS
46+
Permission is hereby granted, free of charge, to any person obtaining
47+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
48+
redistribute, and sell modified and unmodified copies of the Font
49+
Software, subject to the following conditions:
50+
51+
1) Neither the Font Software nor any of its individual components,
52+
in Original or Modified Versions, may be sold by itself.
53+
54+
2) Original or Modified Versions of the Font Software may be bundled,
55+
redistributed and/or sold with any software, provided that each copy
56+
contains the above copyright notice and this license. These can be
57+
included either as stand-alone text files, human-readable headers or
58+
in the appropriate machine-readable metadata fields within text or
59+
binary files as long as those fields can be easily viewed by the user.
60+
61+
3) No Modified Version of the Font Software may use the Reserved Font
62+
Name(s) unless explicit written permission is granted by the corresponding
63+
Copyright Holder. This restriction only applies to the primary font name as
64+
presented to the users.
65+
66+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
67+
Software shall not be used to promote, endorse or advertise any
68+
Modified Version, except to acknowledge the contribution(s) of the
69+
Copyright Holder(s) and the Author(s) or with their explicit written
70+
permission.
71+
72+
5) The Font Software, modified or unmodified, in part or in whole,
73+
must be distributed entirely under this license, and must not be
74+
distributed under any other license. The requirement for fonts to
75+
remain under this license does not apply to any document created
76+
using the Font Software.
77+
78+
TERMINATION
79+
This license becomes null and void if any of the above conditions are
80+
not met.
81+
82+
DISCLAIMER
83+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
85+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
86+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
87+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
88+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
89+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
90+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
91+
OTHER DEALINGS IN THE FONT SOFTWARE.

examples/jsm/geometries/TextGeometry.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class TextGeometry extends ExtrudeGeometry {
4343

4444
} else {
4545

46-
const shapes = font.generateShapes( text, parameters.size );
46+
const shapes = font.generateShapes( text, parameters.size, parameters.direction );
4747

4848
// defaults
4949

@@ -76,6 +76,7 @@ class TextGeometry extends ExtrudeGeometry {
7676
* @property {number} [bevelSize=8] - Distance from the shape outline that the bevel extends.
7777
* @property {number} [bevelOffset=0] - Distance from the shape outline that the bevel starts.
7878
* @property {number} [bevelSegments=3] - Number of bevel layers.
79+
* @property {string} [direction='ltr'] - Char direction: ltr(left to right), rtl(right to left) & tb(top bottom).
7980
* @property {?Curve} [extrudePath=null] - A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion.
8081
* @property {Object} [UVGenerator] - An object that provides UV generator functions for custom UV generation.
8182
**/

examples/jsm/loaders/FontLoader.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ class Font {
109109
*
110110
* @param {string} text - The text.
111111
* @param {number} [size=100] - The text size.
112+
* @param {string} [direction='ltr'] - Char direction: ltr(left to right), rtl(right to left) & tb(top bottom).
112113
* @return {Array<Shape>} An array of shapes representing the text.
113114
*/
114-
generateShapes( text, size = 100 ) {
115+
generateShapes( text, size = 100, direction = 'ltr' ) {
115116

116117
const shapes = [];
117-
const paths = createPaths( text, size, this.data );
118+
const paths = createPaths( text, size, this.data, direction );
118119

119120
for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
120121

@@ -128,7 +129,7 @@ class Font {
128129

129130
}
130131

131-
function createPaths( text, size, data ) {
132+
function createPaths( text, size, data, direction ) {
132133

133134
const chars = Array.from( text );
134135
const scale = size / data.resolution;
@@ -138,6 +139,12 @@ function createPaths( text, size, data ) {
138139

139140
let offsetX = 0, offsetY = 0;
140141

142+
if ( direction == 'rtl' || direction == 'tb' ) {
143+
144+
chars.reverse();
145+
146+
}
147+
141148
for ( let i = 0; i < chars.length; i ++ ) {
142149

143150
const char = chars[ i ];
@@ -150,7 +157,18 @@ function createPaths( text, size, data ) {
150157
} else {
151158

152159
const ret = createPath( char, scale, offsetX, offsetY, data );
153-
offsetX += ret.offsetX;
160+
161+
if ( direction == 'tb' ) {
162+
163+
offsetX = 0;
164+
offsetY += data.ascender * scale;
165+
166+
} else {
167+
168+
offsetX += ret.offsetX;
169+
170+
}
171+
154172
paths.push( ret.path );
155173

156174
}
17.6 KB
Loading

examples/webgl_geometry_text_stroke.html

Lines changed: 96 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535

3636
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3737
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
38-
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
38+
import { Font } from 'three/addons/loaders/FontLoader.js';
39+
import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
3940

4041
let camera, scene, renderer;
4142

@@ -44,108 +45,144 @@
4445
function init( ) {
4546

4647
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
47-
camera.position.set( 0, - 400, 600 );
48+
camera.position.set( 0, - 400, 1000 );
4849

4950
scene = new THREE.Scene();
5051
scene.background = new THREE.Color( 0xf0f0f0 );
5152

52-
const loader = new FontLoader();
53-
loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
53+
new THREE.FileLoader()
54+
.setResponseType( 'arraybuffer' )
55+
.load( 'fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip', function ( data ) {
56+
const zip = unzipSync( new Uint8Array( data ) );
57+
const strArray = strFromU8( new Uint8Array( zip[ 'MPLUSRounded1c-Regular.typeface.json' ].buffer ) );
5458

55-
const color = new THREE.Color( 0x006699 );
59+
const font = new Font( JSON.parse( strArray ) );
60+
const color = new THREE.Color( 0x006699 );
5661

57-
const matDark = new THREE.MeshBasicMaterial( {
58-
color: color,
59-
side: THREE.DoubleSide
60-
} );
62+
const matDark = new THREE.MeshBasicMaterial( {
63+
color: color,
64+
side: THREE.DoubleSide
65+
} );
6166

62-
const matLite = new THREE.MeshBasicMaterial( {
63-
color: color,
64-
transparent: true,
65-
opacity: 0.4,
66-
side: THREE.DoubleSide
67-
} );
67+
const matLite = new THREE.MeshBasicMaterial( {
68+
color: color,
69+
transparent: true,
70+
opacity: 0.4,
71+
side: THREE.DoubleSide
72+
} );
6873

69-
const message = ' Three.js\nStroke text.';
74+
const material = {
75+
dark: matDark,
76+
lite: matLite,
77+
color: color
78+
};
7079

71-
const shapes = font.generateShapes( message, 100 );
80+
const english = ' Three.js\nStroke text.'; // Left to right
7281

73-
const geometry = new THREE.ShapeGeometry( shapes );
82+
const hebrew = 'טקסט קו'; // Right to left
7483

75-
geometry.computeBoundingBox();
84+
const chinese = '文字描邊'; // Top to bottom
7685

77-
const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
86+
const message1 = generateStrokeText( font, material, english, 80, 'ltr' );
7887

79-
geometry.translate( xMid, 0, 0 );
88+
const message2 = generateStrokeText( font, material, hebrew, 80, 'rtl' );
8089

81-
// make shape ( N.B. edge view not visible )
90+
const message3 = generateStrokeText( font, material, chinese, 80, 'tb' );
8291

83-
const text = new THREE.Mesh( geometry, matLite );
84-
text.position.z = - 150;
85-
scene.add( text );
92+
message1.position.x = - 100;
8693

87-
// make line shape ( N.B. edge view remains visible )
94+
message2.position.x = - 100;
95+
message2.position.y = - 300;
8896

89-
const holeShapes = [];
97+
message3.position.x = 300;
98+
message3.position.y = - 300;
9099

91-
for ( let i = 0; i < shapes.length; i ++ ) {
100+
scene.add( message1, message2, message3 );
92101

93-
const shape = shapes[ i ];
102+
render();
94103

95-
if ( shape.holes && shape.holes.length > 0 ) {
104+
} ); //end load function
96105

97-
for ( let j = 0; j < shape.holes.length; j ++ ) {
106+
renderer = new THREE.WebGLRenderer( { antialias: true } );
107+
renderer.setPixelRatio( window.devicePixelRatio );
108+
renderer.setSize( window.innerWidth, window.innerHeight );
109+
document.body.appendChild( renderer.domElement );
98110

99-
const hole = shape.holes[ j ];
100-
holeShapes.push( hole );
111+
const controls = new OrbitControls( camera, renderer.domElement );
112+
controls.target.set( 0, 0, 0 );
113+
controls.update();
101114

102-
}
115+
controls.addEventListener( 'change', render );
103116

104-
}
117+
window.addEventListener( 'resize', onWindowResize );
105118

106-
}
119+
} // end init
120+
121+
function generateStrokeText(font, material, message, size, direction = 'ltr') {
122+
123+
const shapes = font.generateShapes( message, size, direction );
124+
125+
const geometry = new THREE.ShapeGeometry( shapes );
126+
127+
const strokeText = new THREE.Group();
128+
129+
geometry.computeBoundingBox();
130+
131+
const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
132+
133+
geometry.translate( xMid, 0, 0 );
134+
135+
// make shape ( N.B. edge view not visible )
107136

108-
shapes.push( ...holeShapes );
137+
const text = new THREE.Mesh( geometry, material.lite );
109138

110-
const style = SVGLoader.getStrokeStyle( 5, color.getStyle() );
139+
text.position.z = - 150;
111140

112-
const strokeText = new THREE.Group();
141+
strokeText.add(text);
113142

114-
for ( let i = 0; i < shapes.length; i ++ ) {
143+
// make line shape ( N.B. edge view remains visible )
115144

116-
const shape = shapes[ i ];
145+
const holeShapes = [];
117146

118-
const points = shape.getPoints();
147+
for ( let i = 0; i < shapes.length; i ++ ) {
119148

120-
const geometry = SVGLoader.pointsToStroke( points, style );
149+
const shape = shapes[ i ];
121150

122-
geometry.translate( xMid, 0, 0 );
151+
if ( shape.holes && shape.holes.length > 0 ) {
123152

124-
const strokeMesh = new THREE.Mesh( geometry, matDark );
125-
strokeText.add( strokeMesh );
153+
for ( let j = 0; j < shape.holes.length; j ++ ) {
154+
155+
const hole = shape.holes[ j ];
156+
holeShapes.push( hole );
157+
158+
}
126159

127160
}
128161

129-
scene.add( strokeText );
162+
}
130163

131-
render();
164+
shapes.push( ...holeShapes );
132165

133-
} ); //end load function
166+
const style = SVGLoader.getStrokeStyle( 5, material.color.getStyle() );
134167

135-
renderer = new THREE.WebGLRenderer( { antialias: true } );
136-
renderer.setPixelRatio( window.devicePixelRatio );
137-
renderer.setSize( window.innerWidth, window.innerHeight );
138-
document.body.appendChild( renderer.domElement );
168+
for ( let i = 0; i < shapes.length; i ++ ) {
139169

140-
const controls = new OrbitControls( camera, renderer.domElement );
141-
controls.target.set( 0, 0, 0 );
142-
controls.update();
170+
const shape = shapes[ i ];
143171

144-
controls.addEventListener( 'change', render );
172+
const points = shape.getPoints();
145173

146-
window.addEventListener( 'resize', onWindowResize );
174+
const geometry = SVGLoader.pointsToStroke( points, style );
147175

148-
} // end init
176+
geometry.translate( xMid, 0, 0 );
177+
178+
const strokeMesh = new THREE.Mesh( geometry, material.dark );
179+
strokeText.add( strokeMesh );
180+
181+
}
182+
183+
return strokeText;
184+
185+
}
149186

150187
function onWindowResize() {
151188

0 commit comments

Comments
 (0)