1- 'use strict' ;
1+ const fs = require ( 'fs' )
2+ const get = require ( 'simple-get' )
3+ const webp = require ( '@cwasm/webp' )
24
3- /*!
4- * Canvas - Image
5- * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
6- * MIT Licensed
5+ const bindings = require ( './bindings' )
6+
7+ const kOriginalSource = Symbol ( 'original-source' )
8+
9+ /** @typedef {Object } Image */
10+ const Image = module . exports = bindings . Image
11+
12+ const proto = Image . prototype
13+ const _getSource = proto . getSource
14+ const _setSource = proto . setSource
15+
16+ delete proto . getSource
17+ delete proto . setSource
18+
19+ /**
20+ * @param {Image } image
21+ * @param {Error } err
722 */
23+ function signalError ( image , err ) {
24+ if ( typeof image . onerror === 'function' ) return image . onerror ( err )
25+
26+ throw err
27+ }
828
929/**
10- * Module dependencies.
30+ * @param {Image } image
31+ * @param {string } value
1132 */
33+ function loadDataUrl ( image , value ) {
34+ const firstComma = value . indexOf ( ',' )
35+ const isBase64 = value . lastIndexOf ( 'base64' , firstComma ) !== - 1
36+ const source = value . slice ( firstComma + 1 )
1237
13- const bindings = require ( './bindings' )
14- const Image = module . exports = bindings . Image
15- const http = require ( "http" )
16- const https = require ( "https" )
38+ let data
39+ try {
40+ data = Buffer . from ( source , isBase64 ? 'base64' : 'utf8' )
41+ } catch ( err ) {
42+ return signalError ( image , err )
43+ }
1744
18- const proto = Image . prototype ;
19- const _getSource = proto . getSource ;
20- const _setSource = proto . setSource ;
45+ return setSource ( image , data , value )
46+ }
2147
22- delete proto . getSource ;
23- delete proto . setSource ;
48+ /**
49+ * @param {Image } image
50+ * @param {string } value
51+ */
52+ function loadHttpUrl ( image , value ) {
53+ return get . concat ( value , ( err , res , data ) => {
54+ if ( err ) return signalError ( image , err )
55+
56+ if ( res . statusCode < 200 || res . statusCode >= 300 ) {
57+ return signalError ( image , new Error ( `Server responded with ${ res . statusCode } ` ) )
58+ }
59+
60+ return setSource ( image , data , value )
61+ } )
62+ }
63+
64+ /**
65+ * @param {Image } image
66+ * @param {string } value
67+ */
68+ function loadFileUrl ( image , value ) {
69+ fs . readFile ( value . replace ( 'file://' , '' ) , ( err , data ) => {
70+ if ( err ) return signalError ( image , err )
71+
72+ setSource ( image , data , value )
73+ } )
74+ }
75+
76+ /**
77+ * @param {Image } image
78+ * @param {string } value
79+ */
80+ function loadLocalFile ( image , value ) {
81+ fs . readFile ( value , ( err , data ) => {
82+ if ( err ) return signalError ( image , err )
83+
84+ setSource ( image , data , value )
85+ } )
86+ }
2487
2588Object . defineProperty ( Image . prototype , 'src' , {
2689 /**
@@ -33,49 +96,52 @@ Object.defineProperty(Image.prototype, 'src', {
3396 * @param {String|Buffer } val filename, buffer, data URI, URL
3497 * @api public
3598 */
36- set ( val ) {
37- if ( typeof val === 'string' ) {
38- if ( / ^ \s * d a t a : / . test ( val ) ) { // data: URI
39- const commaI = val . indexOf ( ',' )
40- // 'base64' must come before the comma
41- const isBase64 = val . lastIndexOf ( 'base64' , commaI ) !== - 1
42- const content = val . slice ( commaI + 1 )
43- setSource ( this , Buffer . from ( content , isBase64 ? 'base64' : 'utf8' ) , val ) ;
44- } else if ( / ^ \s * h t t p s ? : \/ \/ / . test ( val ) ) { // remote URL
45- const onerror = err => {
46- if ( typeof this . onerror === 'function' ) {
47- this . onerror ( err )
48- } else {
49- throw err
50- }
51- }
52-
53- const type = / ^ \s * h t t p s : \/ \/ / . test ( val ) ? https : http
54- type . get ( val , res => {
55- if ( res . statusCode !== 200 ) {
56- return onerror ( new Error ( `Server responded with ${ res . statusCode } ` ) )
57- }
58- const buffers = [ ]
59- res . on ( 'data' , buffer => buffers . push ( buffer ) )
60- res . on ( 'end' , ( ) => {
61- setSource ( this , Buffer . concat ( buffers ) ) ;
62- } )
63- } ) . on ( 'error' , onerror )
64- } else { // local file path assumed
65- setSource ( this , val ) ;
66- }
67- } else if ( Buffer . isBuffer ( val ) ) {
68- setSource ( this , val ) ;
99+ set ( val ) {
100+ // Clear current source
101+ clearSource ( this )
102+
103+ // Allow directly setting a buffer
104+ if ( Buffer . isBuffer ( val ) ) {
105+ this [ kOriginalSource ] = val
106+ Promise . resolve ( ) . then ( ( ) => setSource ( this , val , val ) )
107+ return
108+ }
109+
110+ // Coerce into string and strip leading & trailing whitespace
111+ val = String ( val ) . trim ( )
112+ this [ kOriginalSource ] = val
113+
114+ // Clear image
115+ if ( val === '' ) {
116+ return
117+ }
118+
119+ // Data URL
120+ if ( / ^ d a t a : / . test ( val ) ) {
121+ return loadDataUrl ( this , val )
122+ }
123+
124+ // HTTP(S) URL
125+ if ( / ^ h t t p s ? : \/ \/ / . test ( val ) ) {
126+ return loadHttpUrl ( this , val )
69127 }
128+
129+ // File URL
130+ if ( / ^ f i l e : \/ \/ / . test ( val ) ) {
131+ return loadFileUrl ( this , val )
132+ }
133+
134+ // Assume local file path
135+ loadLocalFile ( this , val )
70136 } ,
71137
72- get ( ) {
73- // TODO https://github.com/Automattic/node-canvas/issues/118
74- return getSource ( this ) ;
138+ /** @returns { String|Buffer } */
139+ get ( ) {
140+ return this [ kOriginalSource ] || ''
75141 } ,
76142
77143 configurable : true
78- } ) ;
144+ } )
79145
80146/**
81147 * Inspect image.
@@ -86,19 +152,35 @@ Object.defineProperty(Image.prototype, 'src', {
86152 * @api public
87153 */
88154
89- Image . prototype . inspect = function ( ) {
90- return '[Image'
91- + ( this . complete ? ':' + this . width + 'x' + this . height : '' )
92- + ( this . src ? ' ' + this . src : '' )
93- + ( this . complete ? ' complete' : '' )
94- + ']' ;
95- } ;
155+ Image . prototype . inspect = function ( ) {
156+ return '[Image' +
157+ ( this . complete ? ':' + this . width + 'x' + this . height : '' ) +
158+ ( this . src ? ' ' + this . src : '' ) +
159+ ( this . complete ? ' complete' : '' ) +
160+ ']'
161+ }
162+
163+ /**
164+ * @param {Buffer } source
165+ */
166+ function isWebP ( source ) {
167+ return ( source . toString ( 'ascii' , 0 , 4 ) === 'RIFF' && source . toString ( 'ascii' , 8 , 12 ) === 'WEBP' )
168+ }
96169
97- function getSource ( img ) {
98- return img . _originalSource || _getSource . call ( img ) ;
170+ /**
171+ * @param {Image } image
172+ * @param {Buffer } source
173+ * @param {Buffer|string } originalSource
174+ */
175+ function setSource ( image , source , originalSource ) {
176+ if ( image [ kOriginalSource ] === originalSource ) {
177+ _setSource . call ( image , isWebP ( source ) ? webp . decode ( source ) : source )
178+ }
99179}
100180
101- function setSource ( img , src , origSrc ) {
102- _setSource . call ( img , src ) ;
103- img . _originalSource = origSrc ;
181+ /**
182+ * @param {Image } image
183+ */
184+ function clearSource ( image ) {
185+ _setSource . call ( image , null )
104186}
0 commit comments