Skip to content

Commit

Permalink
Create flatSizes function that also retrieves widths
Browse files Browse the repository at this point in the history
  • Loading branch information
hsource committed May 3, 2022
1 parent 7f495d1 commit f07f273
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 124 deletions.
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);
}
}

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

0 comments on commit f07f273

Please sign in to comment.