123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- // /**
- // * adapted from http://stemkoski.github.io/Three.js/Sprite-Text-Labels.html
- // */
- import * as THREE from "../../../libs/three.js/build/three.module.js";
- import Sprite from './Sprite.js'
- import Common from '../utils/Common.js';
-
-
- export class TextSprite extends THREE.Object3D{
- //注:为了分两层控制scale,不直接extend Sprite
- constructor( options={}){
- super()
- let map = new THREE.Texture();
- map.minFilter = THREE.LinearFilter;//清晰一些?
- map.magFilter = THREE.LinearFilter;
-
- this.sprite = new Sprite( Object.assign({
- root:this
- }
- ,options,
- {
- map,
- })
- )
- this.add(this.sprite)
-
- this.fontWeight = options.fontWeight == void 0 ? 'Bold' : options.fontWeight
- this.rectBorderThick = options.rectBorderThick || 0
- this.textBorderThick = options.textBorderThick || 0
- this.fontface = 'Arial';
- this.fontsize = options.fontsize || 16;
- this.lineSpace = options.lineSpace
- this.textBorderColor = options.textBorderColor ? Common.CloneObject(options.textBorderColor):{ r: 0, g: 0, b: 0, a: 1.0 };
- this.backgroundColor = options.backgroundColor ? Common.CloneObject(options.backgroundColor):{ r: 255, g: 255, b: 255, a: 1.0 };
- this.textColor = options.textColor ? Common.CloneObject(options.textColor):{r: 0, g: 0, b: 0, a: 1.0};
- this.borderColor = options.borderColor ? Common.CloneObject(options.borderColor):{ r: 0, g: 0, b: 0, a: 0.0 };
- this.borderRadius = options.borderRadius || 6;
- this.margin = options.margin
- this.textAlign = options.textAlign || 'center'
- this.name = options.name
- this.transform2Dpercent = options.transform2Dpercent
- this.maxLineWidth = options.maxLineWidth
- this.setText(options.text)
-
- }
- setText(text){
- if(text == void 0)text = ''
- if (this.text !== text) {
- if (!(text instanceof Array)) {
- this.text = text.split('\n') //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
- //this.text = [text + '']
- } else this.text = text
- this.updateTexture()
- }
- }
- /* setText(text){
- if(text == void 0)text = ''
- if (this.text !== text) {
- if (!(text instanceof Array)) {
- this.text = text.split('\n') //如果是input手动输入的\n这里会是\\n且不会被拆分, 绘制的依然是\n。
- if(this.maxRowWordsCount){//每行显示最大字数
- this.text.forEach(str=>{
- if(str.length > this.maxRowWordsCount){
-
- }
- })
- }
-
- //this.text = [text + '']
- } else this.text = text
- this.updateTexture()
- }
- } */
- setTextColor(color){
- this.textColor = Common.CloneObject(color);
- this.updateTexture();
- }
- setBorderColor(color){
- this.borderColor = Common.CloneObject(color);
- this.updateTexture();
- }
- setBackgroundColor(color){
- this.backgroundColor = Common.CloneObject(color);
- this.updateTexture();
- }
- setPos(pos){
- this.position.copy(pos)
- this.sprite.waitUpdate()
- }
-
- updatePose(){
- this.sprite.waitUpdate()
- }
-
- setUniforms(name,value){
- this.sprite.setUniforms(name,value)
- }
-
-
-
- updateTexture(){
- //canvas原点在左上角
- let canvas = document.createElement('canvas');
- let context = canvas.getContext('2d');
-
-
- const r = window.devicePixelRatio //不乘会模糊
- context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; //context["font-weight"] = 100; //语法与 CSS font 属性相同。
-
-
- let textMaxWidth = 0, infos = []
- context.textBaseline = 'alphabetic' // "middle" //设置文字基线。当起点y设置为0时,只有该线以下的部分被绘制出来。middle时文字显示一半(但是对该字体所有字的一半,有的字是不一定显示一半的,尤其汉字),alphabetic时是英文字母的那条基线。
-
- let textHeightAll = 0
- let texts = []
- if(this.maxLineWidth){
- this.text.forEach((words)=>{
- if(!words){texts.push("");return;}
- texts = texts.concat( breakLinesForCanvas( words, context, this.maxLineWidth ) )
- })
- }else{
- texts = this.text
- }
-
- for (let text of texts) {
- let metrics = context.measureText(text)
- let textWidth = metrics.width
- infos.push(metrics)
- textMaxWidth = Math.max(textMaxWidth, textWidth)
- textHeightAll += metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent //文字真实高度
- }
-
- let margin = (this.margin ? new THREE.Vector2().copy(this.margin) : new THREE.Vector2(this.fontsize, this.fontsize * 0.8 )).multiplyScalar(r);
-
- const lineSpace = (this.lineSpace || this.fontsize * 0.5) * r
- let rectBorderThick = this.rectBorderThick * r,
- textBorderThick = this.textBorderThick * r
-
- let spriteWidth = 2 * (margin.x + rectBorderThick + textBorderThick ) + textMaxWidth //还要考虑this.textshadowColor,太麻烦了不写了
- let spriteHeight = 2 * (margin.y + rectBorderThick + textBorderThick * texts.length) + lineSpace * (texts.length - 1)+ textHeightAll;
-
- //canvas宽高只会向下取整数,所以为了防止拉伸模糊这里必须先取整
- spriteWidth = Math.floor(spriteWidth)
- spriteHeight = Math.floor(spriteHeight)
-
-
- context.canvas.width = spriteWidth;
- context.canvas.height = spriteHeight;
- context.font = this.fontWeight + ' ' + this.fontsize * r + 'px ' + this.fontface; //为何要再写一遍??
-
- if(spriteWidth>4000){
- console.error('spriteWidth',spriteWidth,'spriteHeight',spriteHeight,this.fontsize,r,texts,margin)
- }
-
-
- let expand = 0//Math.max(1, Math.pow(this.fontsize / 16, 1.1)) * r // 针对英文大部分在baseLine之上所以降低一点,或者可以识别当不包含jgqp时才加这个值 . 但即使都是汉字也会不同,如"哈哈"和"粉色",前者居中后者不
-
-
- context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' + this.borderColor.b + ',' + this.borderColor.a + ')';
-
- context.lineWidth = rectBorderThick
- context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' + this.backgroundColor.b + ',' + this.backgroundColor.a + ')';
- this.roundRect(context, rectBorderThick / 2 , rectBorderThick / 2, spriteWidth - rectBorderThick, spriteHeight - rectBorderThick, this.borderRadius * r);
-
-
- context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
-
- let y = margin.y + rectBorderThick
- for (let i = 0; i < texts.length; i++) {
- //文字y向距离从textBaseline向上算
- let actualBoundingBoxAscent = infos[i].fontBoundingBoxAscent == void 0 ? this.fontsize * r * 0.8 : infos[i].fontBoundingBoxAscent //有的流览器没有。只能大概给一个
- y += actualBoundingBoxAscent + textBorderThick
-
- let textLeftSpace = this.textAlign == 'center' ? (textMaxWidth - infos[i].width) / 2 : this.textAlign == 'left' ? 0 : textMaxWidth - infos[i].width
- let x = rectBorderThick + textBorderThick + margin.x + textLeftSpace
-
-
- // text color
- if (this.textBorderThick) {
- context.strokeStyle = 'rgba(' + this.textBorderColor.r + ',' + this.textBorderColor.g + ',' + this.textBorderColor.b + ',' + this.textBorderColor.a + ')'
- context.lineWidth = this.textBorderThick * r
- context.strokeText(texts[i], x, y)
- }
-
-
- if (this.textshadowColor) {
- context.shadowOffsetX = 0
- context.shadowOffsetY = 0
- context.shadowColor = this.textshadowColor //'red'
- context.shadowBlur = (this.textShadowBlur || this.fontSize/3) * r
- }
- context.fillText(texts[i], x, y)
-
-
- let actualBoundingBoxDescent = infos[i].fontBoundingBoxDescent == void 0 ? this.fontsize * r * 0.2 : infos[i].fontBoundingBoxDescent
- y += actualBoundingBoxDescent + textBorderThick + lineSpace
- }
-
- let texture = new THREE.Texture(canvas);
- texture.minFilter = THREE.LinearFilter;
- texture.magFilter = THREE.LinearFilter;
- texture.needsUpdate = true;
- //this.material.needsUpdate = true;
-
- if(this.sprite.material.map){
- this.sprite.material.map.dispose()
- }
- this.sprite.material.map = texture;
-
- let oldScale = this.sprite.scale.clone()
- this.sprite.scale.set(spriteWidth * 0.01 / r, spriteHeight * 0.01 / r, 1.0);
-
- if(!oldScale.equals(this.sprite.scale)){
- this.updateTransform2D()
- this.sprite.waitUpdate() //重新计算各个viewport的matrix
-
- }
- }
-
- roundRect(ctx, x, y, w, h, r){
- ctx.beginPath();
- ctx.moveTo(x + r, y);
- ctx.lineTo(x + w - r, y);
- ctx.arcTo(x + w, y, x + w, y + r, r );//圆弧。前四个参数同quadraticCurveTo
- //ctx.quadraticCurveTo(x + w, y, x + w, y + r); //二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。
- ctx.lineTo(x + w, y + h - r);
- ctx.arcTo(x + w, y + h, x + w - r, y + h, r );
- ctx.lineTo(x + r, y + h);
- ctx.arcTo(x, y + h, x, y + h - r, r );
- ctx.lineTo(x, y + r);
- ctx.arcTo(x, y, x + r, y, r );
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- }
-
- updateTransform2D(){
- if(this.transform2Dpercent){
- ['x','y'].forEach((axis)=>{
- let percent = this.transform2Dpercent[axis]
- this.sprite.position.y = this.sprite.scale.y * percent
- })
- }
- }
-
- dispose(){
- this.sprite.material.uniforms.map.value.dispose()
- this.parent && this.parent.remove(this)
- this.sprite.dispose()
- this.removeAllListeners()
- this.dispatchEvent('dispose')
- }
- }
- function findBreakPoint(text, width, context) {
- var min = 0;
- var max = text.length - 1;
- while (min <= max) {
- var middle = Math.floor((min + max) / 2);
- var middleWidth = context.measureText(text.substr(0, middle)).width;
- var oneCharWiderThanMiddleWidth = context.measureText(text.substr(0, middle + 1)).width;
- if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
- return middle;
- }
- if (middleWidth < width) {
- min = middle + 1;
- } else {
- max = middle - 1;
- }
- }
- return -1;
- }
- function breakLinesForCanvas(text, context, width, font) {
- var result = [];
- var breakPoint = 0;
- if (font) {
- context.font = font;
- }
- while ((breakPoint = findBreakPoint(text, width, context)) !== -1) {
- result.push(text.substr(0, breakPoint));
- text = text.substr(breakPoint);
- }
- if (text) {
- result.push(text);
- }
- return result;
- } //'使用很寻常的二分查找,如果某一个位置之前的文字宽度小于等于设定的宽度,并且它之后一个字之前的文字宽度大于设定的宽度,那么这个位置就是文本的换行点。上面只是找到一个换行点,对于输入的一段文本,需要循环查找,直到不存在这样的换行点为止, 完整的代码如下',
- //待改进:breakLinesForCanvas后的lineSpace稍小于预设的[]换行的lineSpace, 用于测量标签
- /*
- function wrapText(text, maxWidth) {
- // 创建一个 canvas 元素来测量文本宽度
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- // 设置字体样式
- ctx.font = '16px Arial';
- let currentLine = '';
- const lines = [];
- // 拆分文本为单词数组
- const words = text.split(' ');
- for (let i = 0; i < words.length; i++) {
- const word = words[i];
- const wordWidth = ctx.measureText(word).width;
- const currentLineWidth = ctx.measureText(currentLine).width;
- if (currentLineWidth + wordWidth < maxWidth) {
- // 如果当前行加上这个单词不会超出最大宽度,就将它添加到当前行
- currentLine += (currentLine ? ' ' : '') + word;
- } else {
- // 如果当前行加上这个单词会超出最大宽度,就将当前行添加到结果数组,并开始新的一行
- lines.push(currentLine.trim());
- currentLine = word;
- }
- }
- // 添加最后一行
- if (currentLine) {
- lines.push(currentLine.trim());
- }
- return lines;
- }
- */
|