I need to show some text in Persian/Arabic language. I loaded a font including characters and I used TextGeometry to create a text on the scene:
var loader = new THREE.FontLoader();
loader.load('B Zar_Regular.js', function (font) {
var textGeo = new THREE.TextGeometry('سلام!', {
font: font,
size: 1,
height: 0.05,
curveSegments: 12,
});
var material = new THREE.MeshNormalMaterial();
var textMesh = new THREE.Mesh(textGeo, material);
textMesh.position.x = 15;
scene.add(textMesh);
});
I was expecting to see سلام! but the output was:
letters are separated and order of characters is reversed mistakenly. After all it seems threejs do not support rtl languages. Am I right or I missed something? Is there any workaround as a quick solution? Thanks.
First of all I found very useful blog post here by Chris Loer. He has written a plugin (mapbox-gl-rtl-text.js) that solves the problem.
Usage example:
var rtlText = require('mapbox-gl-rtl-text');
var arabicString = "سلام";
var shapedArabicText = rtlText.applyArabicShaping(arabicString);
var readyForDisplay = rtlText.processBidirectionalText(shapedArabicText, []);
PS: When I used this plugin with common Farsi fonts some of letters was not shown so I add some extra code to fix it.
RtlTextHelper.farsify("سلام");
Usage example:
private createTextMesh(font, text) {
var shapedText = RtlTextHelper.farsify(text);
var fontSize = 0.3;
var textHieght = 0.2;
var material = new THREE.MeshBasicMaterial({
color: this.colors.label.normal,
side: THREE.DoubleSide
});
var textGeo = new THREE.TextGeometry(shapedText, {
font: font,
size: fontSize,
height: 0.05,
curveSegments: 12
});
var textMesh = new THREE.Mesh(textGeo, material);
textGeo.computeBoundingBox();
var box = new THREE.Box3().setFromObject(textMesh);
var textLength = box.max.x;
return {
mesh: textMesh,
hieght: textHieght,
font: {
size: fontSize
},
length: textLength
};
}
This is a complex problem. Ligatures, the calligraphic strokes which connect letters, most familiar to western readers as a component of cursive English, is not optional in Arabic. Ligatures are intrinsic to the language, changing the meaning and formality of the text. The font.js class is designed to take each letter (glyph) one by one and convert it to a shape path. This doesn't work in Arabic because joining rules are complex depending on specific letter combinations.
I do not believe there is a fix that can be put in place in three.js. Although I would be anxious to know what the potential solution is as mentioned by O.P.
It may be possible to convert isolated letters to final Arabic ligatured forms via this script, or maybe this one among others. The final presentation forms might then be sent to Three.js for display so that the ligature joins are handled already and three.js receives the final ligatured forms rather than the isolated glyphs.
It may be a simpler task to convert text to svg paths using a text to path converter and then using the SVGLoader.js in three.js to load the letters as paths for extrusion. But this would have to be tested to ensure the conversion of text to a path handles Arabic at all and then if it does, the ligature joins will also need to be checked for accuracy.
Related
I'm trying to change the following code to use TextBufferGeometry instead of TextGeometry as I feel like it may improve the performance in my use case. The code below works for rendering multiple text elements into my scene however when I change..
let geometry = new THREE.TextGeometry(... to let geometry = new THREE.TextBufferGeometry(...
this code no longer renders text into the scene. I'm unsure of what needs changing in order to make use of TextBufferGeometry
const materialWhite = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide });
const textArray = [
{ text: `Message 12345`, zDistance: 100 },
{ text: `Message 67890`, zDistance: 200 },
{ text: `Message 13579`, zDistance: 300 },
];
var singleFontGeometry = new THREE.Geometry();
for (let index = 0; index < textArray.length; index++) {
loaderFonts.load('./assets/fonts/Oxanium.json', function(font) {
let geometry = new THREE.TextGeometry(`${textArray[index].text}`, {
font: font,
size: 20,
height: 1
});
let mesh = new THREE.Mesh(geometry, materialWhite);
mesh.position.set(100, 100, textArray[index].zDistance);
singleFontGeometry.mergeMesh(mesh);
});
}
var meshFont = new THREE.Mesh(singleFontGeometry, materialWhite);
this.scene.add(meshFont);
The code above will require three main changes:
In the loop, create THREE.TextBufferGeometry instances instead of TextGeometry.
Instead of setting a position on each mesh, bake that transform into the geometry before merging, with geometry.translate(...).
Instead of adding geometries to a THREE.Geometry instance, create a merged BufferGeometry instance using BufferGeometryUtils. BufferGeometry does not have a .mergeMesh() method, and .merge() overwrites vertices rather than creating a union, so neither are available here.
Also, but somewhat unrelated to the question, you probably don't want to load the font inside your For-loop, to avoid unnecessary requests. With these changes, the code should look something like this:
import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
var mergedGeometry;
// Load the font once.
loaderFonts.load('./assets/fonts/Oxanium.json', function(font) {
// Then create a *BufferGeometry for each item of text.
var geometries = textArray.map(function(text) {
var geometry = new THREE.TextBufferGeometry(text.text, {
font: font,
size: 20,
height: 1
});
// Take each item's unique Z offset, and bake it into the geometry.
geometry.translate(0, 0, text.zDistance);
return geometry;
});
// Merge all text geometries.
mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
var mesh = new THREE.Mesh(mergedGeometry, materialWhite);
// Put the shared x=100,y=100 offset onto the mesh, so it can be changed later.
mesh.position.set(100, 100, 0);
scene.add(mesh);
});
^Note that BufferGeometryUtils is not included in the three.js build itself, and must be added to your application separately, like loaders, controls, and other things in the examples/js* folder.
I use plugin «threex.dynamictexture.js» in my app and have some trouble with loading my font (I have trouble even using existing helvetiker_bold.typeface.json ). I use the following pattern:
function loadFont() {
var loader = new THREE.FontLoader();
loader.load('/threejs/fonts/helvetiker_bold.typeface.json', function (font) {
dynamicTexture = new THREEx.DynamicTexture(canvasWrapper.offsetWidth,canvasWrapper.offsetHeight);
// dynamicTexture.context.font = 'bolder 90px Helvetiker';
var geometry2 = new THREE.SphereGeometry( 5, 100, 100);
var material2 = new THREE.MeshBasicMaterial({
map : dynamicTexture.texture
});
var mesh2 = new THREE.Mesh( geometry2, material2 );
console.log(font);
dynamicTexture.texture.needsUpdate = true;
mesh2.position.set(10, 10, 10);
scene.add( mesh2 );
dynamicTexture.drawText('Hello', 100, 300, 'black', 'bolder 90px helvetiker');
});
}
After that I can see rendered text texture on my geometry, but the font-family is still the default. And if I use Arial instead Helvetiker(or helvetiker) the rendered text become Arial.
I looked everywhere, but found only about TextGeometry solution. By the way I could load my font-family using TextGeometry loader.
Can you help me? Thanks!
Seems you mix up system fonts with three.js fonts.
helvetiker_bold.typeface.json has nothing in common with Helvetiker.ttf. It's intended to be used for TextGeometry. To draw with "Helvetiker" on a canvas, you have to add/install Helvetiker.ttf to the fonts on your system.
If you use it for yourself, it's okay if you find and install the font you need. But the other users, who don't have such font on their system, will see writings with the default font.
The solution is to find a font from standard system fonts which is more or less similar to Helvetiker (or the font you would like to use/see).
I am facing difficulty in adding text to my three.js script. I have searched a lot but wherever i find a working model, it uses a previous version of three.js and thus i am not able to use it. Also textgeometry seems to not work. I wish to add 3d text and not 2d.
You should always check three.js's official examples. There are many hints with latest three.js for you.
http://threejs.org/examples/#webgl_geometry_text
http://threejs.org/examples/#webgl_geometry_text_earcut
http://threejs.org/examples/#webgl_geometry_text_pnltri
First, you need to load a font file with new THREE.FontLoader. The font file is supposed to be in JS file, like http://threejs.org/examples/fonts/optimer_bold.typeface.js. You can convert your font to JS with http://gero3.github.io/facetype.js/
var font;
var loader = new THREE.FontLoader();
loader.load( 'path/to/fontfile.js', function ( response ) {
font = response;
} );
Then make a text geometry with new THREE.TextGeometry class using the font.
var textGeo = new THREE.TextGeometry( text, {
font: font,
size: size,
height: height,
curveSegments: curveSegments,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: bevelEnabled,
material: 0,
extrudeMaterial: 1
});
Then, make a mesh
new THREE.Mesh( textGeo, material );
I have this fiddle where I have text arranged in a circle, I would like now to animate it and rotate the text in a clockwise/counter clockwise motion.
Every animation demo I have seen uses a container as the starting point however all the examples i could find about manipulating text in a circular arrangement have all started with the element. I have tried 100's of variations trying to get this working but I am either missing something or it's not possible with the construction i have used thus far.
Here is the fiddle for the circular text I have so far:
http://jsfiddle.net/jamesburt/Sa2G8/
<canvas id="canvas1" width="500" height="500"></canvas>
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
Where as the animation examples start off with:
<div id="container"></div>
var stage = new Kinetic.Stage({container: 'container'});
I'm open to any ideas / rewrites needed as ultimately my goal is an animated text circle.
Also if this is easily accomplished in an alternative to KineticJS I'd be interested in trying that out.
Here is a demo I made using KineticJS: http://jsfiddle.net/Moonseeker/Xf7hp/
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer();
var myText = "My text in a circle. ";
var centerCoords = {x:250, y:250};
for(var i=0;i<myText.length;i++){
var rotation = i*360/myText.length;
var oneChar = new Kinetic.Text({
x: centerCoords.x,
y: centerCoords.y,
text: myText[i],
fontSize: 30,
fontFamily: 'Calibri',
fill: 'green',
offset: {x:0, y:100},
rotationDeg: rotation
});
layer.add(oneChar);
}
// add the layer to the stage
stage.add(layer);
var angularSpeed = Math.PI / 2;
var anim = new Kinetic.Animation(function(frame){
var angleDiff = frame.timeDiff * angularSpeed / 1000;
for(var i=0;i<layer.children.length;i++){
layer.children[i].rotate(angleDiff);
};
}, layer);
anim.start();
You can rotate at every direction or speed you wish, you can change the style of the circle.
You should be able to use layer.find('Text').each() instead of the for-loop for looping through the text to rotate but the KineticJS library at jsfiddle seems outdated as it doesn't know the find method.
One efficient way:
Render your text-around-a-circle on an offscreen canvas.
Save that offscreen canvas as an image using .toDataURL
Create a Kinetic.Image from that offscreen image.
You can then efficiently rotate/animate the Kinetic.Image as you need.
Total noob question. I've been abusing Google and for some reason, have surprisingly not been able to find anything regarding this...? I feel like I'm missing something here. :P
I currently have a resize() function that modifies the canvas dimensions to the size of the window. In a minimal example (which also uses jQuery), I have a variable that references my Shape object. According to the documents, the Shape Object does not include a width & height property. What is the most efficient way of resizing a Shape Object? Removing/redrawing dynamically?
This is what I have:
var stage;
var bgColor;
$(document).ready(function(){
init();
});
function init()
{
stage = new createjs.Stage("canvasStage");
bgColor = new createjs.Shape();
bgColor.graphics.beginFill("#000000").drawRect(0,0, stage.canvas.width, stage.canvas.width);
stage.addChild(bgColor);
$(window).resize(function(){windowResize();});
windowResize();
}
function windowResize()
{
stage.canvas.width = $(window).width();
stage.canvas.height = $(window).height();
//bgColor.width = $(window).height();// No width property
//bgColor.height = $(window).height();// NO height property
stage.update();}
You can use the shape's scaleX and scaleY to scale the shape.
Note: The Shape Object extends the DisplayObject so you might also want to look at the DisplayObject docs for many more useful properties/methods.
myShape.scaleX=1.2;
myShape.scaleY=1.2;
as mentioned by #markE, only scaleX and scaleY are available.
A way to work around the problem is to instantiate shapes with 1px width and 1px height:
mc_bg = new createjs.Shape();
mc_bg.graphics.beginFill("#CCCCCC").drawRect(0,0,1,1);
stage.addChild(mc_bg);
mc_left = new createjs.Shape();
mc_left.graphics.beginFill("#333333").drawRect(0,0,1,1);
stage.addChild(mc_left);
mc_circle = new createjs.Shape();
mc_circle.graphics.beginFill("#888888").drawCircle(0,0,1,1);
stage.addChild(mc_circle);
Allowing to set your dimensions with pixel units without conversion directly by using scaleX and scaleY before rendering:
mc_bg.scaleX = stage_width;
mc_bg.scaleY = stage_height;
mc_left.scaleX = stage_width/2;
mc_left.scaleY = stage_height;
mc_circle.x = stage_width/4;
mc_circle.y = stage_width/4;
mc_circle.scaleX = stage_width/4;
mc_circle.scaleY = stage_width/4;
stage.update();
See fiddle example with live resizing on window resize: https://jsfiddle.net/jckleinbourg/go3fs1zt/
Conversion methods to change scale --> dimensions and vice versa:
function dimToScale(origDim, desiredDim)
{
return desiredDim / origDim;
}
function scaleToDim(origDim, scale)
{
return scale * origDim;
}