UNPKG

jsroot

Version:
278 lines (243 loc) 9.46 kB
import { isStr, clTLatex } from '../core.mjs'; import { THREE, getMaterialArgs, getHelveticaFont } from './base3d.mjs'; import { isPlainText, translateLaTeX, produceLatex } from './latex.mjs'; import { ObjectPainter } from './ObjectPainter.mjs'; class TextParseWrapper { constructor(kind, parent, font_size) { this.kind = kind ?? 'g'; this.childs = []; this.x = 0; this.y = 0; this.font_size = parent?.font_size ?? font_size; this.stroke_width = parent?.stroke_width ?? 5; parent?.childs.push(this); } append(kind) { if (kind === 'svg:g') return new TextParseWrapper('g', this); if (kind === 'svg:text') return new TextParseWrapper('text', this); if (kind === 'svg:path') return new TextParseWrapper('path', this); console.warn('missing handle for svg', kind); } style(name, value) { if ((name === 'stroke-width') && value) this.stroke_width = Number.parseInt(value); return this; } property(name, value) { if (value === undefined) return this[name]; this[name] = value; return this; } attr(name, value) { const get = () => { if (!value) return ''; const res = value[0]; value = value.slice(1); return res; }, getN = skip => { let p = 0; while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) p++; const res = Number.parseInt(value.slice(0, p)); value = value.slice(p); if (skip) get(); return res; }; if ((name === 'font-size') && value) this.font_size = Number.parseInt(value); else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { const arr = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')')).split(','); this.x += arr[0] ? Number.parseInt(arr[0]) * 0.01 : 0; this.y -= arr[1] ? Number.parseInt(arr[1]) * 0.01 : 0; } else if ((name === 'x') && (this.kind === 'text')) this.x += Number.parseInt(value) * 0.01; else if ((name === 'y') && (this.kind === 'text')) this.y -= Number.parseInt(value) * 0.01; else if ((name === 'fill') && (this.kind === 'text')) this.fill = value; else if ((name === 'd') && (this.kind === 'path') && (value !== 'M0,0')) { if (get() !== 'M') return console.error('Not starts with M'); let x1 = getN(true), y1 = getN(), next; const pnts = [], add_line = (x2, y2) => { const angle = Math.atan2(y2 - y1, x2 - x1), dx = 0.5 * this.stroke_width * Math.sin(angle), dy = -0.5 * this.stroke_width * Math.cos(angle); // front side pnts.push(x1 - dx, y1 - dy, 0, x2 - dx, y2 - dy, 0, x2 + dx, y2 + dy, 0, x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x1 + dx, y1 + dy, 0); // back side pnts.push(x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x2 - dx, y2 - dy, 0, x1 - dx, y1 - dy, 0, x1 + dx, y1 + dy, 0, x2 + dx, y2 + dy, 0); x1 = x2; y1 = y2; }; while ((next = get())) { switch (next) { case 'L': add_line(getN(true), getN()); continue; case 'l': add_line(x1 + getN(true), y1 + getN()); continue; case 'H': add_line(getN(), y1); continue; case 'h': add_line(x1 + getN(), y1); continue; case 'V': add_line(x1, getN()); continue; case 'v': add_line(x1, y1 + getN()); continue; case 'a': { const rx = getN(true), ry = getN(true), angle = getN(true) / 180 * Math.PI, flag1 = getN(true); getN(true); // skip unused flag2 const x2 = x1 + getN(true), y2 = y1 + getN(), x0 = x1 + rx * Math.cos(angle), y0 = y1 + ry * Math.sin(angle); let angle2 = Math.atan2(y0 - y2, x0 - x2); if (flag1 && (angle2 < angle)) angle2 += 2 * Math.PI; else if (!flag1 && (angle2 > angle)) angle2 -= 2 * Math.PI; for (let cnt = 0; cnt < 10; ++cnt) { const a = angle + (angle2 - angle) / 10 * (cnt + 1); add_line(x0 - rx * Math.cos(a), y0 - ry * Math.sin(a)); } continue; } default: console.log('not supported path operator', next); } } if (pnts.length) { const pos = new Float32Array(pnts); this.geom = new THREE.BufferGeometry(); this.geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); this.geom.scale(0.01, -0.01, 0.01); this.geom.computeVertexNormals(); } } return this; } text(v) { if (this.kind === 'text') this._text = v; } collect(geoms, geom_args, as_array) { if (this._text) { geom_args.size = Math.round(0.01 * this.font_size); const geom = new THREE.TextGeometry(this._text, geom_args); if (as_array) { // this is latex parsing // while three.js uses full height, make it more like normal fonts geom.scale(1, 0.9, 1); geom.translate(0, 0.0005 * this.font_size, 0); } geom.translate(this.x, this.y, 0); geom._fill = this.fill; geoms.push(geom); } if (this.geom) { this.geom.translate(this.x, this.y, 0); this.geom._fill = this.fill; geoms.push(this.geom); } this.childs.forEach(chld => { chld.x += this.x; chld.y += this.y; chld.collect(geoms, geom_args, as_array); }); } } // class TextParseWrapper function createLatexGeometry(painter, lbl, size, as_array, use_latex = true) { const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }, font_size = size * 100, node = new TextParseWrapper('g', null, font_size), arg = { font_size, latex: use_latex ? 1 : 0, x: 0, y: 0, text: lbl, align: ['start', 'top'], fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }, geoms = []; if (THREE.REVISION > 162) geom_args.depth = 0; else geom_args.height = 0; if (!isPlainText(lbl)) { produceLatex(painter, node, arg); node.collect(geoms, geom_args, as_array); } if (!geoms.length) { geom_args.size = size; const res = new THREE.TextGeometry(translateLaTeX(lbl), geom_args); return as_array ? [res] : res; } if (as_array) return geoms; if (geoms.length === 1) return geoms[0]; let total_size = 0; geoms.forEach(geom => { total_size += geom.getAttribute('position').array.length; }); const pos = new Float32Array(total_size), norm = new Float32Array(total_size); let indx = 0; geoms.forEach(geom => { const p1 = geom.getAttribute('position').array, n1 = geom.getAttribute('normal').array; for (let i = 0; i < p1.length; ++i, ++indx) { pos[indx] = p1[i]; norm[indx] = n1[i]; } }); const fullgeom = new THREE.BufferGeometry(); fullgeom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); fullgeom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); return fullgeom; } /** @summary Build three.js object for the TLatex * @private */ function build3dlatex(obj, opt, painter, fp) { if (!painter) painter = new ObjectPainter(null, obj, opt); const handle = painter.createAttText({ attr: obj }), valign = handle.align % 10, halign = (handle.align - valign) / 10, text_size = handle.size > 1 ? handle.size : 2 * handle.size * (fp?.size_z3d || 100), arr3d = createLatexGeometry(painter, obj.fTitle, text_size || 10, true, fp || (obj._typename === clTLatex)), bb = new THREE.Box3().makeEmpty(); arr3d.forEach(geom => { geom.computeBoundingBox(); bb.expandByPoint(geom.boundingBox.max); bb.expandByPoint(geom.boundingBox.min); }); let dx = 0, dy = 0; if (halign === 2) dx = 0.5 * (bb.max.x + bb.min.x); else if (halign === 3) dx = bb.max.x; if (valign === 2) dy = 0.5 * (bb.max.y + bb.min.y); else if (valign === 3) dy = bb.max.y; const obj3d = new THREE.Object3D(), materials = [], getMaterial = color => { if (!color) color = 'black'; if (!materials[color]) materials[color] = new THREE.MeshBasicMaterial(getMaterialArgs(color, { vertexColors: false })); return materials[color]; }; arr3d.forEach(geom => { geom.translate(-dx, -dy, 0); obj3d.add(new THREE.Mesh(geom, getMaterial(geom._fill || handle.color))); }); return arr3d.length === 1 ? obj3d.children[0] : obj3d; } export { createLatexGeometry, build3dlatex };