1
1
import { last } from '@react-pdf/fns' ;
2
-
3
- import empty from '../../attributedString/empty' ;
4
2
import { AttributedString , Run } from '../../types' ;
5
3
6
- /**
7
- * @param run - Run
8
- * @returns Font size
9
- */
10
- const getFontSize = ( run : Run ) => {
11
- return run . attributes . fontSize || 12 ;
12
- } ;
4
+ const IGNORED_CODE_POINTS = [ 173 ] ;
5
+
6
+ const getFontSize = ( run : Run ) => run . attributes . fontSize || 12 ;
13
7
14
- /**
15
- * Resolve font runs in an AttributedString, grouping equal
16
- * runs and performing font substitution where necessary.
17
- */
18
- const fontSubstitution = ( ) => {
19
- /**
20
- * @param attributedString - Attributed string
21
- * @returns Attributed string
22
- */
23
- return ( attributedString : AttributedString ) => {
24
- const { string, runs } = attributedString ;
8
+ const pickFontFromFontStack = ( codePoint , fontStack , lastFont ) => {
9
+ const fontStackWithFallback = [ ...fontStack , lastFont ] ;
10
+ for ( let i = 0 ; i < fontStackWithFallback . length ; i += 1 ) {
11
+ const font = fontStackWithFallback [ i ] ;
12
+ if (
13
+ ! IGNORED_CODE_POINTS . includes ( codePoint ) &&
14
+ font &&
15
+ font . hasGlyphForCodePoint &&
16
+ font . hasGlyphForCodePoint ( codePoint )
17
+ ) {
18
+ return font ;
19
+ }
20
+ }
21
+ return fontStack . at ( - 1 ) ;
22
+ } ;
25
23
24
+ const fontSubstitution =
25
+ ( ) =>
26
+ ( { string, runs } : AttributedString ) => {
26
27
let lastFont = null ;
28
+ let lastFontSize = null ;
27
29
let lastIndex = 0 ;
28
30
let index = 0 ;
29
- const res : Run [ ] = [ ] ;
30
31
31
- if ( ! string ) return empty ( ) ;
32
+ const res : Run [ ] = [ ] ;
32
33
33
- for ( const run of runs ) {
34
- const fontSize = getFontSize ( run ) ;
35
- const defaultFont = run . attributes . font ;
34
+ for ( let i = 0 ; i < runs . length ; i += 1 ) {
35
+ const run = runs [ i ] ;
36
36
37
37
if ( string . length === 0 ) {
38
- res . push ( { start : 0 , end : 0 , attributes : { font : defaultFont } } ) ;
38
+ res . push ( {
39
+ start : 0 ,
40
+ end : 0 ,
41
+ attributes : { font : run . attributes . font } ,
42
+ } ) ;
39
43
break ;
40
44
}
41
45
42
- for ( const char of string . slice ( run . start , run . end ) ) {
43
- const font = defaultFont ;
46
+ const chars = string . slice ( run . start , run . end ) ;
47
+
48
+ for ( let j = 0 ; j < chars . length ; j += 1 ) {
49
+ const char = chars [ j ] ;
50
+ const codePoint = char . codePointAt ( 0 ) ;
51
+ // If the default font does not have a glyph and the fallback font does, we use it
52
+ const font = pickFontFromFontStack (
53
+ codePoint ,
54
+ run . attributes . font ,
55
+ lastFont ,
56
+ ) ;
44
57
45
- if ( font !== lastFont ) {
58
+ const fontSize = getFontSize ( run ) ;
59
+
60
+ // If anything that would impact res has changed, update it
61
+ if (
62
+ font !== lastFont ||
63
+ fontSize !== lastFontSize ||
64
+ font . unitsPerEm !== lastFont . unitsPerEm
65
+ ) {
46
66
if ( lastFont ) {
47
67
res . push ( {
48
68
start : lastIndex ,
49
69
end : index ,
50
70
attributes : {
51
71
font : lastFont ,
52
- scale : lastFont ? fontSize / lastFont . unitsPerEm : 0 ,
72
+ scale : lastFontSize / lastFont . unitsPerEm ,
53
73
} ,
54
74
} ) ;
55
75
}
56
76
57
77
lastFont = font ;
78
+ lastFontSize = fontSize ;
58
79
lastIndex = index ;
59
80
}
60
81
@@ -70,13 +91,12 @@ const fontSubstitution = () => {
70
91
end : string . length ,
71
92
attributes : {
72
93
font : lastFont ,
73
- scale : lastFont ? fontSize / lastFont . unitsPerEm : 0 ,
94
+ scale : fontSize / lastFont . unitsPerEm ,
74
95
} ,
75
96
} ) ;
76
97
}
77
98
78
- return { string, runs : res } ;
99
+ return { string, runs : res } as AttributedString ;
79
100
} ;
80
- } ;
81
101
82
102
export default fontSubstitution ;
0 commit comments