Skip to content

Commit 34b9452

Browse files
committed
feat: small perf improvements
1 parent ed149b0 commit 34b9452

File tree

15 files changed

+147
-104
lines changed

15 files changed

+147
-104
lines changed

packages/textkit/src/attributedString/ascent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AttributedString, Run } from '../types';
88
* @returns Ascent
99
*/
1010
const ascent = (attributedString: AttributedString) => {
11-
const reducer = (acc, run: Run) => Math.max(acc, runAscent(run));
11+
const reducer = (acc: number, run: Run) => Math.max(acc, runAscent(run));
1212
return attributedString.runs.reduce(reducer, 0);
1313
};
1414

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AttributedString, Fragment } from '../types';
1+
import { AttributedString, Fragment, Run } from '../types';
22

33
/**
44
* Create attributed string from text fragments
@@ -8,23 +8,26 @@ import { AttributedString, Fragment } from '../types';
88
*/
99
const fromFragments = (fragments: Fragment[]): AttributedString => {
1010
let offset = 0;
11-
let string = '';
12-
const runs = [];
11+
const strings: string[] = [];
12+
const runs: Run[] = [];
1313

14-
fragments.forEach((fragment) => {
15-
string += fragment.string;
14+
for (let i = 0; i < fragments.length; i += 1) {
15+
const fragment = fragments[i];
16+
const fragmentLength = fragment.string.length;
17+
18+
strings.push(fragment.string);
1619

1720
runs.push({
1821
...fragment,
1922
start: offset,
20-
end: offset + fragment.string.length,
23+
end: offset + fragmentLength,
2124
attributes: fragment.attributes || {},
2225
});
2326

24-
offset += fragment.string.length;
25-
});
27+
offset += fragmentLength;
28+
}
2629

27-
return { string, runs };
30+
return { string: strings.join(''), runs };
2831
};
2932

3033
export default fromFragments;

packages/textkit/src/attributedString/leadingOffset.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
import isWhiteSpace from '../glyph/isWhiteSpace';
1+
import isRunWhiteSpace from '../run/isWhiteSpace';
22
import runLeadingOffset from '../run/leadingOffset';
3-
import { AttributedString, Run } from '../types';
4-
5-
/**
6-
* Check if run is entirely whitespace
7-
*
8-
* @param run - Run
9-
* @returns Whether run is entirely whitespace
10-
*/
11-
const isRunWhiteSpace = (run: Run) => {
12-
const glyphs = run?.glyphs || [];
13-
14-
return glyphs.length > 0 && glyphs.every(isWhiteSpace);
15-
};
3+
import { AttributedString } from '../types';
164

175
/**
186
* Get attributed string leading white space offset
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import add from '../run/add';
22
import emptyRun from '../run/empty';
33
import prependToRun from '../run/prepend';
4-
import { AttributedString, Glyph } from '../types';
4+
import { AttributedString, Glyph, Run } from '../types';
55
import stringFromCodePoints from '../utils/stringFromCodePoints';
66

77
/**
8-
* prepend glyph into last run of attributed string
8+
* Prepend glyph into first run of attributed string
99
*
10-
* @param glyph
10+
* @param glyph - Glyph to prepend
1111
* @param attributedString - Attributed string
1212
* @returns Attributed string with new glyph
1313
*/
@@ -19,13 +19,16 @@ const prepend = (
1919
const string = stringFromCodePoints(codePoints) + attributedString.string;
2020

2121
const offset = codePoints.length;
22-
const firstRun = attributedString.runs[0] || emptyRun();
23-
const lastRuns = attributedString.runs
24-
.slice(1)
25-
.map((run) => add(offset, run));
26-
const runs = [prependToRun(glyph, firstRun)].concat(lastRuns);
22+
const { runs: existingRuns } = attributedString;
23+
const firstRun = existingRuns[0] || emptyRun();
2724

28-
return Object.assign({}, attributedString, { runs, string });
25+
// Build new runs array: prepend to first run, offset remaining runs
26+
const runs: Run[] = [prependToRun(glyph, firstRun)];
27+
for (let i = 1; i < existingRuns.length; i += 1) {
28+
runs.push(add(offset, existingRuns[i]));
29+
}
30+
31+
return { ...attributedString, runs, string };
2932
};
3033

3134
export default prepend;

packages/textkit/src/attributedString/trailingOffset.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
import isWhiteSpace from '../glyph/isWhiteSpace';
1+
import isRunWhiteSpace from '../run/isWhiteSpace';
22
import runTrailingOffset from '../run/trailingOffset';
3-
import { AttributedString, Run } from '../types';
4-
5-
/**
6-
* Check if run is entirely whitespace
7-
*
8-
* @param run - Run
9-
* @returns Whether run is entirely whitespace
10-
*/
11-
const isRunWhiteSpace = (run: Run) => {
12-
const glyphs = run?.glyphs || [];
13-
14-
return glyphs.length > 0 && glyphs.every(isWhiteSpace);
15-
};
3+
import { AttributedString } from '../types';
164

175
/**
186
* Get attributed string trailing white space offset

packages/textkit/src/attributedString/trim.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import { AttributedString } from '../types';
22
import slice from './slice';
33

4+
const WHITESPACE_REGEX = /\S/;
5+
6+
/**
7+
* Find index of first non-whitespace character
8+
*
9+
* @param string - String to search
10+
* @returns Index of first non-whitespace character, or -1 if not found
11+
*/
412
const findCharIndex = (string: string) => {
5-
return string.search(/\S/g);
13+
return string.search(WHITESPACE_REGEX);
614
};
715

16+
/**
17+
* Find index of last non-whitespace character
18+
*
19+
* @param string - String to search
20+
* @returns Index of last non-whitespace character, or -1 if not found
21+
*/
822
const findLastCharIndex = (string: string) => {
9-
const match = string.match(/\S/g);
10-
return match ? string.lastIndexOf(match[match.length - 1]) : -1;
23+
for (let i = string.length - 1; i >= 0; i -= 1) {
24+
if (WHITESPACE_REGEX.test(string[i])) return i;
25+
}
26+
return -1;
1127
};
1228

1329
/**
@@ -18,7 +34,6 @@ const findLastCharIndex = (string: string) => {
1834
*/
1935
const trim = (attributedString: AttributedString) => {
2036
const start = findCharIndex(attributedString.string);
21-
2237
const end = findLastCharIndex(attributedString.string);
2338

2439
return slice(start, end + 1, attributedString);
Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { Font } from '../types';
1+
import { Font, Glyph } from '../types';
22

33
/**
44
* Get glyph for a given code point
55
*
6-
* @param value - CodePoint
7-
* @param font - Font
8-
* @returns Glyph
9-
* */
10-
const fromCodePoint = (value: number | null, font: Font | string | null) => {
6+
* @param codePoint - Unicode code point (0 is treated as invalid)
7+
* @param font - Font to get glyph from
8+
* @returns Glyph or null if font/codePoint is invalid
9+
*/
10+
const fromCodePoint = (
11+
codePoint: number | null,
12+
font: Font | string | null,
13+
): Glyph | null => {
14+
// String fonts (e.g., 'Helvetica') don't support glyph lookup
1115
if (typeof font === 'string') return null;
12-
return font && value ? font.glyphForCodePoint(value) : null;
16+
17+
// Require valid font and non-zero code point
18+
if (!font || !codePoint) return null;
19+
20+
return font.glyphForCodePoint(codePoint);
1321
};
1422

1523
export default fromCodePoint;
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import { Glyph } from '../types';
22

3-
const WHITE_SPACES_CODE = 32;
3+
/**
4+
* ASCII code for space character.
5+
* Note: Only space (32) is considered whitespace for typographic layout purposes,
6+
* not tabs, newlines, or other control characters.
7+
*/
8+
const SPACE_CODE = 32;
49

510
/**
6-
* Check if glyph is white space
11+
* Check if glyph contains a space character
712
*
813
* @param glyph - Glyph
9-
* @returns Whether glyph is white space
10-
* */
14+
* @returns Whether glyph contains a space
15+
*/
1116
const isWhiteSpace = (glyph: Glyph | null) => {
1217
const codePoints = glyph?.codePoints || [];
13-
return codePoints.includes(WHITE_SPACES_CODE);
18+
return codePoints.includes(SPACE_CODE);
1419
};
1520

1621
export default isWhiteSpace;
Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
import { Font, Glyph } from '../types';
22

33
/**
4-
* Slice glyph between codePoints range
5-
* Util for breaking ligatures
4+
* Slice glyph between codePoints range.
5+
* Useful for breaking ligatures into individual glyphs.
66
*
77
* @param start - Start code point index
88
* @param end - End code point index
9-
* @param font - Font to generate new glyph
9+
* @param font - Font to generate new glyphs from
1010
* @param glyph - Glyph to be sliced
11-
* @returns Sliced glyph parts
11+
* @returns Array of sliced glyph parts
1212
*/
13-
const slice = (start: number, end: number, font: Font, glyph: Glyph) => {
13+
const slice = (
14+
start: number,
15+
end: number,
16+
font: Font,
17+
glyph: Glyph,
18+
): Glyph[] => {
1419
if (!glyph) return [];
1520
if (start === end) return [];
16-
if (start === 0 && end === glyph.codePoints.length) return [glyph];
1721

18-
const codePoints = glyph.codePoints.slice(start, end);
19-
const string = String.fromCodePoint(...codePoints);
22+
const { codePoints } = glyph;
2023

21-
// passing LTR To force fontkit to not reverse the string
22-
return font
23-
? font.layout(string, undefined, undefined, undefined, 'ltr').glyphs
24-
: [glyph];
24+
if (start === 0 && end === codePoints.length) return [glyph];
25+
if (!font) return [glyph];
26+
27+
const slicedCodePoints = codePoints.slice(start, end);
28+
const string = String.fromCodePoint(...slicedCodePoints);
29+
30+
// Force LTR direction to prevent fontkit from reversing the string
31+
return font.layout(string, undefined, undefined, undefined, 'ltr').glyphs;
2532
};
2633

2734
export default slice;

packages/textkit/src/indices/append.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { isNil, last } from '@react-pdf/fns';
55
*
66
* Ex. appendIndices(3, [0, 1, 2, 2]) => [0, 1, 2, 2, 3, 3, 3]
77
*
8-
* @param length - Length
8+
* @param length - Length to append
99
* @param indices - Glyph indices
1010
* @returns Extended glyph indices
1111
*/

0 commit comments

Comments
 (0)