Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Create flatSizes function that also retrieves widths
  • Loading branch information
hsource committed May 3, 2022
commit 0e161a9f580da9395c09fe3d361cc5bc5830b860
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Added

- Updated README.md with example for flatHeights - Thanks to @donni106
- Update README.md with example for flatHeights - Thanks to @donni106
- Create new flatSizes function that also gets the widths
- Add support for `numberOfLines` and `maxWidth/maxHeight` dimensions.

### Changed

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ See [Manual Installation][2] on the Wiki as an alternative if you have problems

- [`measure`](#measure)

- [`flatHeights`](#flatheights)
- [`flatHeights` and `flatSizes`](#flatheights)

- [`specsForTextStyles`](#specsfortextstyles)

Expand Down Expand Up @@ -199,9 +199,10 @@ class Test extends Component<Props, State> {

```ts
flatHeights(options: TSHeightsParams): Promise<number[]>
flatSizes(options: TSHeightsParams): Promise<TSFlatSizes>
```

Calculate the height of each of the strings in an array.
Calculate the height (or sizes) of each of the strings in an array.

This is an alternative to `measure` designed for cases in which you have to calculate the height of numerous text blocks with common characteristics (width, font, etc), a typical use case with `<FlatList>` or `<RecyclerListView>` components.

Expand All @@ -214,6 +215,8 @@ I did tests on 5,000 random text blocks and these were the results (ms):
Android | 49,624 | 1,091
iOS | 1,949 | 732

Note that `flatSizes` is somewhat slower than `flatHeights` on Android since it needs to iterate through lines to find the maximum line length.

In the future I will prepare an example of its use with FlatList and multiple styles on the same card.

### TSHeightsParams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,61 +163,26 @@ public void measure(@Nullable final ReadableMap specs, final Promise promise) {
}
}

// https://stackoverflow.com/questions/3654321/measuring-text-height-to-be-drawn-on-canvas-android
/**
* Retrieves sizes of each entry in an array of strings rendered with the same style.
*
* https://stackoverflow.com/questions/3654321/measuring-text-height-to-be-drawn-on-canvas-android
*/
@SuppressWarnings("unused")
@ReactMethod
public void flatHeights(@Nullable final ReadableMap specs, final Promise promise) {
final RNTextSizeConf conf = getConf(specs, promise, true);
if (conf == null) {
return;
}

final ReadableArray texts = conf.getArray("text");
if (texts == null) {
promise.reject(E_MISSING_TEXT, "Missing required text, must be an array.");
return;
}

final float density = getCurrentDensity();
final float width = conf.getWidth(density);
final boolean includeFontPadding = conf.includeFontPadding;
final int textBreakStrategy = conf.getTextBreakStrategy();

final WritableArray result = Arguments.createArray();

final SpannableStringBuilder sb = new SpannableStringBuilder(" ");
RNTextSizeSpannedText.spannedFromSpecsAndText(mReactContext, conf, sb);

final TextPaint textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
Layout layout;
try {

for (int ix = 0; ix < texts.size(); ix++) {

// If this element is `null` or another type, return zero
if (texts.getType(ix) != ReadableType.String) {
result.pushInt(0);
continue;
}

final String text = texts.getString(ix);

// If empty, return the minimum height of <Text> components
if (text.isEmpty()) {
result.pushDouble(minimalHeight(density, includeFontPadding));
continue;
}

// Reset the SB text, the attrs will expand to its full length
sb.replace(0, sb.length(), text);
layout = buildStaticLayout(conf, includeFontPadding, sb, textPaint, (int) width);
result.pushDouble(layout.getHeight() / density);
}
public void flatSizes(@Nullable final ReadableMap specs, final Promise promise) {
flatHeightsInner(specs, promise, true);
}

promise.resolve(result);
} catch (Exception e) {
promise.reject(E_UNKNOWN_ERROR, e);
}
/**
* Retrieves heights of each entry in an array of strings rendered with the same style.
*
* https://stackoverflow.com/questions/3654321/measuring-text-height-to-be-drawn-on-canvas-android
*/
@SuppressWarnings("unused")
@ReactMethod
public void flatHeights(@Nullable final ReadableMap specs, final Promise promise) {
flatHeightsInner(specs, promise, false);
}

/**
Expand Down Expand Up @@ -313,6 +278,77 @@ public void fontNamesForFamilyName(final String ignored, final Promise promise)
//
// ============================================================================

private void flatHeightsInner(@Nullable final ReadableMap specs, final Promise promise, boolean includeWidths) {
final RNTextSizeConf conf = getConf(specs, promise, true);
if (conf == null) {
return;
}

final ReadableArray texts = conf.getArray("text");
if (texts == null) {
promise.reject(E_MISSING_TEXT, "Missing required text, must be an array.");
return;
}

final float density = getCurrentDensity();
final float width = conf.getWidth(density);
final boolean includeFontPadding = conf.includeFontPadding;
final int textBreakStrategy = conf.getTextBreakStrategy();

final WritableArray heights = Arguments.createArray();
final WritableArray widths = Arguments.createArray();

final SpannableStringBuilder sb = new SpannableStringBuilder(" ");
RNTextSizeSpannedText.spannedFromSpecsAndText(mReactContext, conf, sb);

final TextPaint textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
Layout layout;
try {

for (int ix = 0; ix < texts.size(); ix++) {

// If this element is `null` or another type, return zero
if (texts.getType(ix) != ReadableType.String) {
heights.pushInt(0);
continue;
}

final String text = texts.getString(ix);

// If empty, return the minimum height of <Text> components
if (text.isEmpty()) {
heights.pushDouble(minimalHeight(density, includeFontPadding));
continue;
}

// Reset the SB text, the attrs will expand to its full length
sb.replace(0, sb.length(), text);
layout = buildStaticLayout(conf, includeFontPadding, sb, textPaint, (int) width);
heights.pushDouble(layout.getHeight() / density);

if (includeWidths) {
final int lineCount = layout.getLineCount();
float measuredWidth = 0;
for (int i = 0; i < lineCount; i++) {
measuredWidth = Math.max(measuredWidth, layout.getLineMax(i));
}
widths.pushDouble(measuredWidth / density);
}
}

if (includeWidths) {
final WritableMap output = Arguments.createMap();
output.putArray("widths", widths);
output.putArray("heights", heights);
promise.resolve(output);
} else {
promise.resolve(heights);
}
} catch (Exception e) {
promise.reject(E_UNKNOWN_ERROR, e);
}
}

@Nullable
private RNTextSizeConf getConf(final ReadableMap specs, final Promise promise, boolean forText) {
if (specs == null) {
Expand Down
14 changes: 10 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ declare module "react-native-text-size" {
export type TSFontVariant = 'small-caps' | 'oldstyle-nums' | 'lining-nums' | 'tabular-nums' | 'proportional-nums'
export type TSTextBreakStrategy = 'simple' | 'highQuality' | 'balanced'

export type TSFontSize = {
export interface TSFontSize {
readonly default: number,
readonly button: number,
readonly label: number,
Expand Down Expand Up @@ -41,7 +41,7 @@ declare module "react-native-text-size" {
| 'title2'
| 'title3'

export type TSFontInfo = {
export interface TSFontInfo {
fontFamily: string | null,
fontName?: string | null,
fontWeight: TSFontWeight,
Expand Down Expand Up @@ -88,7 +88,7 @@ declare module "react-native-text-size" {
textBreakStrategy?: TSTextBreakStrategy;
}

export type TSFontForStyle = {
export interface TSFontForStyle {
fontFamily: string,
/** Unscaled font size, untits are SP in Android, points in iOS */
fontSize: number,
Expand Down Expand Up @@ -143,7 +143,7 @@ declare module "react-native-text-size" {
lineInfoForLine?: number;
}

export type TSMeasureResult = {
export interface TSMeasureResult {
/**
* Total used width. It may be less or equal to the `width` option.
*
Expand Down Expand Up @@ -185,8 +185,14 @@ declare module "react-native-text-size" {
};
}

export interface TSFlatSizes {
widths: number[];
heights: number[];
}

interface TextSizeStatic {
measure(params: TSMeasureParams): Promise<TSMeasureResult>;
flatSizes(params: TSHeightsParams): Promise<TSFlatSizes>;
flatHeights(params: TSHeightsParams): Promise<number[]>;
specsForTextStyles(): Promise<{ [key: string]: TSFontForStyle }>;
fontFromSpecs(specs?: TSFontSpecs): Promise<TSFontInfo>;
Expand Down
6 changes: 6 additions & 0 deletions index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,14 @@ export interface TSMeasureResult {
}
}

export interface TSFlatSizes {
widths: number[];
heights: number[];
}

declare interface TextSizeStatic {
measure(params: TSMeasureParams): Promise<TSMeasureResult>;
flatSizes(params: TSHeightsParams): Promise<TSFlatSizes>;
flatHeights(params: TSHeightsParams): Promise<number[]>;
specsForTextStyles(): Promise<{ [string]: TSFontForStyle }>;
fontFromSpecs(specs: TSFontSpecs): Promise<TSFontInfo>;
Expand Down
Loading