nativescript-dynamic-label
by tremho | v1.0.1
A version of Label that adjusts font to fit size
npm i --save nativescript-dynamic-label

nativescript-dynamic-label

Build Status NPM version Downloads TotalDownloads Twitter Follow

Features

  • Text Measurement
  • Text Fitting
  • Label control that dynamically sizes text to fit
  • Android and iOS

Installation

To get started, install the plugin, per normal methods:

tns plugin add nativescript-dynamic-label

Usage

Generally, DynamicLabel can be used in either XML markup or in code much the same way as a normal Label control.

Use via XML markup

Use pretty much the same as a regular Label.

Import the plugin into the namespace

In your page declaration, include a reference to the DynamicLabel module, like this:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo"
xmlns:dl="nativescript-dynamic-label"
class="page"

the xmlns:dl="nativescript-dynamic-label" is the part you want to add, and it sets the namespace dl as relating to the DynamicLabel module.

Then, invoke a DynamicLabel within your markup like so..

<StackLayout> <!-- or whatever layout container you are using... -->
<dl:DynamicLabel text="Hello, World!"></dl:DynamicLabel>
</StackLayout>

You can use all the properties of <Label> for the dynamic label also. The most relevant of these being width and textWrap. Note that these properties can also be set via CSS.

Use via code

You may create and use a DynamicLabel in the same ways as a normal Label.
You may create one with new DynamicLabel() or retrieve an existing one from the page with page.getViewById('your-D-Label-ID) from there, set or get the properties you would of a normal label, e.g. dlabel.text = 'Hello, World'. Do not set a font size to the dynamic label for display, as it will find its own. The text fitting is triggered on any new text change.

Like the normal NativeScript <Label> and other Nativescript controls, text may be set via the Observable class and changes made to the bound Observable property.

Using to measure text

To use this class to measure text, but not necessarily display it, you can call the getTextExtent method of the class. To use this, first set a font size to the control, and then pass the maximum width and height you are attempting to fit text for to getTextExtent, and it will return the bounds of the text, as well as an indication of whether or not the text will fit in this space without being truncated, and also the text per-line as determined by the internal layout algorithm.

Note that the line layout may not necessarily match what will be displayed by the actual control, since each platform handles its word wrapping and fitting in subtle but often significantly different ways.
However, it should be reasonably representative of what likely would display if set to the control at this font size (assuming text wrap is enabled). This information may be more useful for any do-it-yourself layout tasks than for actual representation of what the control renders.


let computedWidth, computedHeight;
let maxWidth = 100; // constrain to this width
let maxHeight = 1000; // let it find the height < this
let myDLabel = page.getViewById('myDLabel');
myDLabel.fontSize = 20; // let's compute for this font size
let bounds = myDLabel.getTextExtent(myText, maxWidth, maxHeight);
if(bounds.wasCut) {
console.error("Text doesn't fit in these bounds at this size!")
} else {
computedWidth = bounds.width;
computedHeight = bounds.height;
}
// computed width,height can now inform how big to make
// a display that can hold this text at this size.

// Additional information we get from this about the
// text per lines (according to internal layout) can be
// retrieved like this:
for (let i = 0; i< bounds.lines.length; i++) {
let t = bounds.lines[i].text;
// let y = bounds.lines[i].top; // if we wanted to draw it
console.log(t)
}

API

DynamicLabel inherits from Label and so has all of the characteristics of that class.

Properties specifically important to DynamicLabel are listed here.

Property Default Description
textWrap inherits from Label turn this on to wrap text. Changes the choices made by the text measurer / formatter.
width inherits from Label must be set (direct or CSS) for sizing to be effective
public getTextExtent(
       text : string, 
       textSize: number, 
       maxWidth : number, 
       maxHeight: number) : FitResults`

Use to measure text as it will appear in the current typeface in the given font size (textSize) constrained to the bounds (maxWidth, maxHeight). The returned FitResults object looks like this:

{  
width: number, // width of text extent bounds
height: number, // height of text extent bounds
lines: LineInfo[], // array of line info, see below
wasCut: boolean // true if text was truncated at this size
}

each entry in the lines property above will be an object in this format (LineInfo):

 {  
text: string, // text that appears on this line
top: number // y offset to start drawing text
}
public fitText() : void

may be called explicitly to force a recompute/redisplay of text. Normally not needed, as this is called after any text or layout changes to the DynamicLabel control.

Tips and Caveats

Multiline displays

If the property textWrap is false (the default), a font size will be picked that allows all of the text to appear in one line according to the width of the control, regardless of the control height. If textWrap is true, the control is enabled for wrapping. This is done according to the algorithms of the underlying platform as has been implemented for Label.
To assist its predictions, DynamicLabel computes the word breaks and line spans itself and uses these for measurement.

A paradox of the multiline scenario is that the fitter is working to find measurements that fit first in width, and selecting a smaller font if this is not acheived, but with multiline text, the new font size may change the layout and collapse to fewer lines -- which creates a wider width rather than a smaller one, and so the tendency is to result in a smaller font choice than one might be able to manually choose.

You can avoid this if you have text that you already know is multiline, and placing hard breaks (\n) in the string to force a specific layout. You still must set 'textWrap=true' to allow this to display multiple lines, but it should honor your layout and find a size that is reasonable for your control size.

Known Issues

Still early in development!

As of 2/14/2020 this is the first version released for testing.

As issues arise, they will be recorded in this space.

Contributing

Comments and contributions welcome! Please submit your Pull Requests, with as much explanation and examples you can provide to support your changes.

Feel free to email me at [email protected] to start a discussion for other suggestions.

License

Apache License Version 2.0, January 2004