aframevr / aframe

:a: Web framework for building virtual reality experiences.
https://aframe.io/
MIT License
16.61k stars 3.94k forks source link

Ability to autosize text container #2675

Open eng1neer opened 7 years ago

eng1neer commented 7 years ago

It would be great to have a way to render a text on a background with some padding around it.

I’ve tried an example from docs, however, it seems that auto width mode actually sets the maximum width of the container, leaving the gap on the right:

screen shot 2017-05-18 at 14 31 53

What I would like to achieve is an ability to render a label with some background around it (similar to Bootstrap labels for example)

Here is a hacky solution that I came up with:

import AFRAME from 'aframe';

const fontWidthFactor = 1000;

AFRAME.registerComponent('text-auto-width', {
    dependencies: ['text'],
    schema: {
        padding: {default: 0}
    },
    init() {
        // Hook into text component's updateLayout method
        const textComponent = this.el.components.text;

        const textMesh = this.el.object3D.children[1];

        if (textMesh.geometry.layout) {
            resize(textMesh.geometry.layout);
        } else {
            const origUpdate = textComponent.updateLayout;

            textComponent.updateLayout = (data) => {
                origUpdate.call(textComponent, data);

                resize(textMesh.geometry.layout);
            };
        }

        const resize = (layout) => {
            const {value, width} = this.el.getAttribute('text');

            const metrics = layout.computeMetrics(value, 0, value.length, Math.INT_MAX);

            const actualWidth = metrics.width * width / fontWidthFactor + this.data.padding * 2;

            const geometry = this.el.getAttribute('geometry');

            this.el.setAttribute('geometry', {...geometry, width: actualWidth});
        }
    }
});
ngokevin commented 7 years ago

text="align: center"

Also https://github.com/mayognaise/aframe-html-shader/pull/12 is an option for rendering HTML/CSS as a texture. Slightly expensive to compute, but perhaps cheaper than pulling in and rendering glyphs?

eng1neer commented 7 years ago

@ngokevin align: center has nothing to do with auto sizing, it will render container with the same width

ngokevin commented 7 years ago

I suppose text.padding wouldn't be too bad.

machenmusik commented 7 years ago

The text auto width was intended to size the text width to the geometry, not the geometry to the text width - you can't automatically size both height and width, you need to pick something as a FRAME of reference. Is it a more common case to want arbitrary width based upon fixed height, rather than arbitrary height (including line wrap) based upon fixed width? If so, perhaps we should add some switches for that (whether it's padding, or something else)

weddingdj commented 7 years ago

+1 for text padding

vectris-dev commented 6 years ago

@eng1neer did you end up finding a solution to this? I tried using the code you posted but I get "TypeError: textMesh is undefined"

leo-nerd commented 3 years ago

How would you go about dynamically adjusting the height of the background geometry (a plane in my case) to fit multi-line text when wrapCount and width are fixed? The height and lineHeight properties are both at 0 for some reason so I can't apply a formula that scales based on text height. height: 0 seems to work when the component is attached but I need the height to update when the text lengthens.

hulkinBrain commented 3 years ago

@eng1neer I wrote a Component which works well to add padding. I have added an example HTML code (including the fiddle) which demonstrates the usage of the component.

Component:

AFRAME.registerComponent("planepadder", {
    schema: {
        addPadding: {type: "boolean", default: false},
        padding: {type: "number", default: 0.01}
    },
    init: function(){
        let data = this.data;
        this.el.setAttribute("planepadder", "addPadding: false");
    },
    update: function (oldData) {
        let data = this.data;
        let el = this.el;

        if(Object.keys(data).length === 0) { return; }

        /**
         * If the flag is true add padding to the plane
        */
        if(data.addPadding === true) {

            /**
             * Set padding using the provided padding value
            */ 
            el.getObject3D("mesh").geometry = new THREE.PlaneGeometry(
                el.components.geometry.data.width + el.components.planepadder.data.padding, 
                el.components.geometry.data.height + el.components.planepadder.data.padding
            );

            /**
             * Remove planepadder attribute so that it padding can be 
             * changed in the future by adding the attribute again
            */
            this.el.removeAttribute("planepadder");
        }
    },
    tick(time, delta) {
        let data = this.data;
        let el = this.el;

        /**
         * Check if the width is not NaN, padding hasn't already been
         * added i.e `addPadding` schema attribute is false and that the 
         * plane's width is same as the text's width which means padding 
         * hasn't been added yet 
        */            
        if(el.components.geometry.data.width != NaN && el.components.planepadder.data.addPadding === false && el.components.geometry.data.width == el.components.text.data.width) {
            // Set the schema attribute `addPadding` to true so that the padding can be added
            el.setAttribute("planepadder", "addPadding: true");
        }
    }
});

Usage example:

<a-entity 
    id="textEntity" 
    geometry="primitive: plane; width: auto; height: auto" 
    planepadder="padding: 0.02" 
    material="color: skyblue;" 
    text="
        width: 0.1;
        value: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry;
        color: black;" 
    position="0 0.02 -0.02">
</a-entity>
<a-camera camera position="0 0.02 0.05" wasd-controls="acceleration: 5" />

Here is the Fiddle