@@ -221,19 +221,25 @@ function texToSVG(_texString, _config, _callback) {
221221}
222222
223223var TAG_STYLES = {
224- // would like to use baseline-shift but FF doesn't support it yet
224+ // would like to use baseline-shift for sub/sup but FF doesn't support it
225225 // so we need to use dy along with the uber hacky shift-back-to
226226 // baseline below
227227 sup : 'font-size:70%" dy="-0.6em' ,
228228 sub : 'font-size:70%" dy="0.3em' ,
229229 b : 'font-weight:bold' ,
230230 i : 'font-style:italic' ,
231- a : '' ,
231+ a : 'cursor:pointer ' ,
232232 span : '' ,
233233 br : '' ,
234234 em : 'font-style:italic;font-weight:bold'
235235} ;
236236
237+ // sub/sup: extra tspan with zero-width space to get back to the right baseline
238+ var TAG_CLOSE = {
239+ sup : '<tspan dy="0.42em">​</tspan>' ,
240+ sub : '<tspan dy="-0.21em">​</tspan>'
241+ } ;
242+
237243var PROTOCOLS = [ 'http:' , 'https:' , 'mailto:' ] ;
238244
239245var STRIP_TAGS = new RegExp ( '</?(' + Object . keys ( TAG_STYLES ) . join ( '|' ) + ')( [^>]*)?/?>' , 'g' ) ;
@@ -254,6 +260,18 @@ var UNICODE_TO_ENTITY = Object.keys(stringMappings.unicodeToEntity).map(function
254260
255261var NEWLINES = / ( \r \n ? | \n ) / g;
256262
263+ var SPLIT_TAGS = / ( < [ ^ < > ] * > ) / ;
264+
265+ var ONE_TAG = / < ( \/ ? ) ( [ ^ > ] * ) ( \s + ( .* ) ) ? > / i;
266+
267+ // Style and href: pull them out of either single or double quotes.
268+ // Because we hack in other attributes with style (sub & sup), drop any trailing
269+ // semicolon in user-supplied styles so we can consistently append the tag-dependent style
270+ var STYLEMATCH = / ( ^ | [ \s " ' ] ) s t y l e \s * = \s * ( " ( [ ^ " ] * ) ; ? " | ' ( [ ^ ' ] * ) ; ? ' ) / i;
271+ var HREFMATCH = / ( ^ | [ \s " ' ] ) h r e f \s * = \s * ( " ( [ ^ " ] * ) " | ' ( [ ^ ' ] * ) ' ) / i;
272+
273+ var COLORMATCH = / ( ^ | ; ) \s * c o l o r : / ;
274+
257275exports . plainText = function ( _str ) {
258276 // strip out our pseudo-html so we have a readable
259277 // version to put into text fields
@@ -280,84 +298,93 @@ function encodeForHTML(_str) {
280298}
281299
282300function convertToSVG ( _str ) {
283- _str = convertEntities ( _str ) ;
284-
285- // normalize behavior between IE and others wrt newlines and whitespace:pre
286- // this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
287- // Chrome and FF display \n, \r, or \r\n as a space in this mode.
288- // I feel like at some point we turned these into <br> but currently we don't so
289- // I'm just going to cement what we do now in Chrome and FF
290- _str = _str . replace ( NEWLINES , ' ' ) ;
301+ _str = convertEntities ( _str )
302+ /*
303+ * Normalize behavior between IE and others wrt newlines and whitespace:pre
304+ * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
305+ * Chrome and FF display \n, \r, or \r\n as a space in this mode.
306+ * I feel like at some point we turned these into <br> but currently we don't so
307+ * I'm just going to cement what we do now in Chrome and FF
308+ */
309+ . replace ( NEWLINES , ' ' ) ;
291310
292311 var result = _str
293- . split ( / ( < [ ^ < > ] * > ) / ) . map ( function ( d ) {
294- var match = d . match ( / < ( \/ ? ) ( [ ^ > ] * ) \s * ( .* ) > / i) ,
295- tag = match && match [ 2 ] . toLowerCase ( ) ,
296- style = TAG_STYLES [ tag ] ;
297-
298- if ( style !== undefined ) {
299- var close = match [ 1 ] ,
300- extra = match [ 3 ] ,
301- /**
302- * extraStyle: any random extra css (that's supported by svg)
303- * use this like <span style="font-family:Arial"> to change font in the middle
304- *
305- * at one point we supported <font family="..." size="..."> but as this isn't even
306- * valid HTML anymore and we dropped it accidentally for many months, we will not
307- * resurrect it.
308- */
309- extraStyle = extra . match ( / ^ s t y l e \s * = \s * " ( [ ^ " ] + ) " \s * / i) ;
310-
311- // anchor and br are the only ones that don't turn into a tspan
312+ . split ( SPLIT_TAGS ) . map ( function ( d ) {
313+ var match = d . match ( ONE_TAG ) ;
314+ var tag = match && match [ 2 ] . toLowerCase ( ) ;
315+ var tagStyle = TAG_STYLES [ tag ] ;
316+
317+ if ( tagStyle !== undefined ) {
318+ var isClose = match [ 1 ] ;
319+ if ( isClose ) return ( tag === 'a' ? '</a>' : '</tspan>' ) + ( TAG_CLOSE [ tag ] || '' ) ;
320+
321+ // break: later we'll turn these into newline <tspan>s
322+ // but we need to know about all the other tags first
323+ if ( tag === 'br' ) return '<br>' ;
324+
325+ /**
326+ * extra includes href and any random extra css (that's supported by svg)
327+ * use this like <span style="font-family:Arial"> to change font in the middle
328+ *
329+ * at one point we supported <font family="..." size="..."> but as this isn't even
330+ * valid HTML anymore and we dropped it accidentally for many months, we will not
331+ * resurrect it.
332+ */
333+ var extra = match [ 4 ] ;
334+
335+ var out ;
336+
337+ // anchor is the only tag that doesn't turn into a tspan
312338 if ( tag === 'a' ) {
313- if ( close ) return '</a>' ;
314- else if ( extra . substr ( 0 , 4 ) . toLowerCase ( ) !== 'href' ) return '<a>' ;
315- else {
316- // remove quotes, leading '=', replace '&' with '&'
317- var href = extra . substr ( 4 )
318- . replace ( / [ " ' ] / g, '' )
319- . replace ( / = / , '' ) ;
320-
321- // check protocol
339+ var hrefMatch = extra && extra . match ( HREFMATCH ) ;
340+ var href = hrefMatch && ( hrefMatch [ 3 ] || hrefMatch [ 4 ] ) ;
341+
342+ out = '<a' ;
343+
344+ if ( href ) {
345+ // check safe protocols
322346 var dummyAnchor = document . createElement ( 'a' ) ;
323347 dummyAnchor . href = href ;
324- if ( PROTOCOLS . indexOf ( dummyAnchor . protocol ) === - 1 ) return '<a>' ;
325-
326- return '<a xlink:show="new" xlink:href="' + encodeForHTML ( href ) + '">' ;
348+ if ( PROTOCOLS . indexOf ( dummyAnchor . protocol ) !== - 1 ) {
349+ out += ' xlink:show="new" xlink:href="' + encodeForHTML ( href ) + '"' ;
350+ }
327351 }
328352 }
329- else if ( tag === 'br' ) return '<br>' ;
330- else if ( close ) {
331- // closing tag
332-
333- // sub/sup: extra tspan with zero-width space to get back to the right baseline
334- if ( tag === 'sup' ) return '</tspan><tspan dy="0.42em">​</tspan>' ;
335- if ( tag === 'sub' ) return '</tspan><tspan dy="-0.21em">​</tspan>' ;
336- else return '</tspan>' ;
337- }
338353 else {
339- var tspanStart = '<tspan' ;
354+ out = '<tspan' ;
340355
341356 if ( tag === 'sup' || tag === 'sub' ) {
342357 // sub/sup: extra zero-width space, fixes problem if new line starts with sub/sup
343- tspanStart = '​' + tspanStart ;
344- }
345-
346- if ( extraStyle ) {
347- // most of the svg css users will care about is just like html,
348- // but font color is different. Let our users ignore this.
349- extraStyle = extraStyle [ 1 ] . replace ( / ( ^ | ; ) \s * c o l o r : / , '$1 fill:' ) ;
350- style = encodeForHTML ( extraStyle ) + ( style ? ';' + style : '' ) ;
358+ out = '​' + out ;
351359 }
360+ }
352361
353- return tspanStart + ( style ? ' style="' + style + '"' : '' ) + '>' ;
362+ // now add style, from both the tag name and any extra css
363+ // Most of the svg css that users will care about is just like html,
364+ // but font color is different (uses fill). Let our users ignore this.
365+ var cssMatch = extra && extra . match ( STYLEMATCH ) ;
366+ var css = cssMatch && ( cssMatch [ 3 ] || cssMatch [ 4 ] ) ;
367+ if ( css ) {
368+ css = encodeForHTML ( css . replace ( COLORMATCH , '$1 fill:' ) ) ;
369+ if ( tagStyle ) css += ';' + tagStyle ;
354370 }
371+ else if ( tagStyle ) css = tagStyle ;
372+
373+ if ( css ) return out + ' style="' + css + '">' ;
374+
375+ return out + '>' ;
355376 }
356377 else {
357378 return exports . xml_entity_encode ( d ) . replace ( / < / g, '<' ) ;
358379 }
359380 } ) ;
360381
382+ // now deal with line breaks
383+ // TODO: this next section attempts to close and reopen tags that
384+ // span a line break. But
385+ // a) it only closes and reopens one tag, and
386+ // b) all tags are treated like equivalent tspans (even <a> which isn't a tspan even now!)
387+ // we should really do this in a type-aware way *before* converting to tspans.
361388 var indices = [ ] ;
362389 for ( var index = result . indexOf ( '<br>' ) ; index > 0 ; index = result . indexOf ( '<br>' , index + 1 ) ) {
363390 indices . push ( index ) ;
0 commit comments