1
+ const { createCanvas, registerFont } = require ( 'canvas' ) ;
2
+
3
+ // this is text-to-image v5.2 package included directly in oxygen for easier control over canvas and nan versions
4
+
5
+ const defaults = {
6
+ bgColor : '#fff' ,
7
+ customHeight : 0 ,
8
+ bubbleTail : { width : 0 , height : 0 } ,
9
+ fontFamily : 'Arial' ,
10
+ fontPath : '' ,
11
+ fontSize : 18 ,
12
+ fontWeight : 'normal' ,
13
+ lineHeight : 28 ,
14
+ margin : 10 ,
15
+ maxWidth : 400 ,
16
+ textAlign : 'left' ,
17
+ textColor : '#000' ,
18
+ verticalAlign : 'top' ,
19
+ } ;
20
+
21
+ const createTextData = ( text , config , canvas ) => {
22
+ const { bgColor, fontFamily, fontPath, fontSize, fontWeight, lineHeight, maxWidth, textAlign, textColor, } = config ;
23
+ if ( fontPath ) {
24
+ registerFont ( fontPath , { family : fontFamily } ) ;
25
+ }
26
+ const textCanvas = canvas || createCanvas ( maxWidth , 100 ) ;
27
+ const textContext = textCanvas . getContext ( '2d' ) ;
28
+ let textX = 0 ;
29
+ let textY = 0 ;
30
+ if ( [ 'center' ] . includes ( textAlign . toLowerCase ( ) ) ) {
31
+ textX = maxWidth / 2 ;
32
+ }
33
+ if ( [ 'right' , 'end' ] . includes ( textAlign . toLowerCase ( ) ) ) {
34
+ textX = maxWidth ;
35
+ }
36
+ textContext . textAlign = textAlign ;
37
+ textContext . fillStyle = bgColor ;
38
+ textContext . fillRect ( 0 , 0 , textCanvas . width , textCanvas . height ) ;
39
+ textContext . fillStyle = textColor ;
40
+ textContext . font = `${ fontWeight } ${ fontSize } px ${ fontFamily } ` ;
41
+ textContext . textBaseline = 'top' ;
42
+ const words = text . split ( ' ' ) ;
43
+ let wordCount = words . length ;
44
+ let line = '' ;
45
+ const addNewLines = [ ] ;
46
+ for ( let n = 0 ; n < wordCount ; n += 1 ) {
47
+ let word = words [ n ] ;
48
+ if ( / \n / . test ( words [ n ] ) ) {
49
+ const parts = words [ n ] . split ( '\n' ) ;
50
+ word = parts . shift ( ) || '' ;
51
+ addNewLines . push ( n + 1 ) ;
52
+ words . splice ( n + 1 , 0 , parts . join ( '\n' ) ) ;
53
+ wordCount += 1 ;
54
+ }
55
+ const testLine = `${ line } ${ word } ` . replace ( / ^ + / , '' ) . replace ( / + $ / , '' ) ;
56
+ const testLineWidth = textContext . measureText ( testLine ) . width ;
57
+ if ( addNewLines . indexOf ( n ) > - 1 || ( testLineWidth > maxWidth && n > 0 ) ) {
58
+ textContext . fillText ( line , textX , textY ) ;
59
+ line = word ;
60
+ textY += lineHeight ;
61
+ }
62
+ else {
63
+ line = testLine ;
64
+ }
65
+ }
66
+ textContext . fillText ( line , textX , textY ) ;
67
+ const height = textY + Math . max ( lineHeight , fontSize ) ;
68
+ return {
69
+ textHeight : height ,
70
+ textData : textContext . getImageData ( 0 , 0 , maxWidth , height ) ,
71
+ } ;
72
+ } ;
73
+
74
+ const createImageCanvas = ( content , conf ) => {
75
+ const { textHeight } = createTextData ( content , {
76
+ maxWidth : conf . maxWidth - conf . margin * 2 ,
77
+ fontSize : conf . fontSize ,
78
+ lineHeight : conf . lineHeight ,
79
+ bgColor : conf . bgColor ,
80
+ textColor : conf . textColor ,
81
+ fontFamily : conf . fontFamily ,
82
+ fontPath : conf . fontPath ,
83
+ fontWeight : conf . fontWeight ,
84
+ textAlign : conf . textAlign ,
85
+ } ) ;
86
+ const textHeightWithMargins = textHeight + conf . margin * 2 ;
87
+ if ( conf . customHeight && conf . customHeight < textHeightWithMargins ) {
88
+ console . warn ( 'Text is longer than customHeight, clipping will occur.' ) ;
89
+ }
90
+ const height = conf . customHeight || textHeightWithMargins ;
91
+ const canvas = createCanvas ( conf . maxWidth , height + conf . bubbleTail . height ) ;
92
+ const { textData } = createTextData ( content , {
93
+ maxWidth : conf . maxWidth - conf . margin * 2 ,
94
+ fontSize : conf . fontSize ,
95
+ lineHeight : conf . lineHeight ,
96
+ bgColor : conf . bgColor ,
97
+ textColor : conf . textColor ,
98
+ fontFamily : conf . fontFamily ,
99
+ fontPath : conf . fontPath ,
100
+ fontWeight : conf . fontWeight ,
101
+ textAlign : conf . textAlign ,
102
+ } , canvas ) ;
103
+ const ctx = canvas . getContext ( '2d' ) ;
104
+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
105
+ ctx . globalAlpha = 1 ;
106
+ ctx . fillStyle = conf . bgColor ;
107
+ ctx . fillRect ( 0 , 0 , canvas . width , height ) ;
108
+ if ( conf . bubbleTail . width && conf . bubbleTail . height ) {
109
+ ctx . beginPath ( ) ;
110
+ ctx . moveTo ( canvas . width / 2 - conf . bubbleTail . width / 2 , height ) ;
111
+ ctx . lineTo ( canvas . width / 2 , canvas . height ) ;
112
+ ctx . lineTo ( canvas . width / 2 + conf . bubbleTail . width / 2 , height ) ;
113
+ ctx . closePath ( ) ;
114
+ ctx . fillStyle = conf . bgColor ;
115
+ ctx . fill ( ) ;
116
+ }
117
+ const textX = conf . margin ;
118
+ let textY = conf . margin ;
119
+ if ( conf . customHeight && conf . verticalAlign === 'center' ) {
120
+ textY =
121
+ ( conf . customHeight - textData . height ) / 2 +
122
+ Math . max ( 0 , ( conf . lineHeight - conf . fontSize ) / 2 ) ;
123
+ }
124
+ ctx . putImageData ( textData , textX , textY ) ;
125
+ return canvas ;
126
+ } ;
127
+
128
+ const generate = async ( content , config ) => {
129
+ const conf = { ...defaults , ...config } ;
130
+ const canvas = createImageCanvas ( content , conf ) ;
131
+ return canvas . toDataURL ( ) ;
132
+ } ;
133
+
134
+ const generateSync = ( content , config ) => {
135
+ const conf = { ...defaults , ...config } ;
136
+ const canvas = createImageCanvas ( content , conf ) ;
137
+ return canvas . toDataURL ( ) ;
138
+ } ;
139
+
140
+ exports . generate = generate ;
141
+ exports . generateSync = generateSync ;
0 commit comments