import { isEqual , cloneDeep , uniqWith } from 'lodash' ; import getAllFonts from './getAllFonts' ; import loadFonts from './loadFonts' ; const styleFonts = [ 'fontSize' , 'fontName' , 'textCase' , 'textDecoration' , 'letterSpacing' , 'lineHeight' , 'fills' , 'textStyleId' , 'fillStyleId' ] ; function parseTextStyle ( node , start = 0 , end ? , styleName ? ) { if ( ! end ) end = node . characters . length ; if ( ! styleName ) styleName = styleFonts ; if ( end <= start ) { console . error ( 'Start must be greater than end' ) ; return [ ] ; } const styleMap = [ ] ; let textStyle ; const names = styleName . map ( ( name ) => { return name . replace ( /^(.)/g , ( $1 ) => $1 . toUpperCase ( ) ) ; } ) ; for ( let startIndex = start ; startIndex < end ; startIndex ++ ) { const endIndex = startIndex + 1 ; const letter = { characters : node . characters [ startIndex ] } ; names . forEach ( ( n , i ) => { letter [ styleName [ i ] ] = node [ 'getRange' + n ] ( startIndex , endIndex ) ; } ) ; if ( textStyle ) { if ( isEqualLetterStyle ( letter , textStyle ) ) { textStyle . characters += letter . characters ; } else { styleMap . push ( textStyle ) ; textStyle = letter ; } } else { textStyle = letter ; } } styleMap . push ( textStyle ) ; return styleMap ; } function splitTextStyleIntoLines ( textStyle , removeNewlineCharacters = false , removeEmptylines = false ) { let line = [ ] ; let lines = [ ] ; const re = new RegExp ( '(.+|(?<=\n)(.?)(?=$))(\n|\u2028)?|(\n|\u2028)' , 'g' ) ; const re2 = new RegExp ( '\n|\u2028' ) ; textStyle . forEach ( ( style , index ) => { if ( re2 . test ( style . characters ) ) { const ls = style . characters . match ( re ) ; if ( ls === null ) { line . push ( style ) ; } else if ( ls . length === 1 ) { line . push ( style ) ; lines . push ( line ) ; line = [ ] ; } else { style = cloneDeep ( style ) ; style . characters = ls . shift ( ) ; line . push ( style ) ; lines . push ( line ) ; line = [ ] ; const last = ls . pop ( ) ; lines . push ( ... ls . map ( ( e ) => { style = cloneDeep ( style ) ; style . characters = e ; return [ style ] ; } ) ) ; style = cloneDeep ( style ) ; style . characters = last ; if ( last === '' ) { if ( ! textStyle [ index + 1 ] ) { lines . push ( [ style ] ) ; } } else { line . push ( style ) ; } } } else { line . push ( style ) ; } } ) ; if ( line . length ) lines . push ( line ) ; if ( removeNewlineCharacters ) { lines . forEach ( ( l ) => { const style = l [ l . length - 1 ] ; style . characters = style . characters . replace ( re2 , '' ) ; } ) ; } if ( removeEmptylines ) { lines = lines . filter ( ( l ) => l . filter ( ( l ) => l . characters . replace ( re2 , '' ) !== '' ) . length !== 0 ) ; } return lines ; } function joinTextLinesStyles ( textStyle , addNewlineCharacters = false ) { const tStyle = cloneDeep ( textStyle ) ; let newline = '' ; switch ( typeof addNewlineCharacters ) { case 'boolean' : if ( addNewlineCharacters ) newline = '\n' ; break ; case 'string' : newline = addNewlineCharacters ; break ; } if ( addNewlineCharacters && newline ) { tStyle . forEach ( ( style , i ) => { if ( i !== tStyle . length - 1 ) style [ style . length - 1 ] . characters += newline ; } ) ; } const line = tStyle . shift ( ) ; tStyle . forEach ( ( style ) => { const fitst = style . shift ( ) ; if ( isEqualLetterStyle ( fitst , line [ line . length - 1 ] ) ) { line [ line . length - 1 ] . characters += fitst . characters ; } else { line . push ( fitst ) ; } if ( style . length ) line . push ( ... style ) ; } ) ; return line ; } async function applyTextStyleToTextNode ( textStyle , textNode ? , isLoadFonts = true ) { if ( isLoadFonts ) { let fonts = [ { family : 'Roboto' , style : 'Regular' } ] ; if ( textStyle [ 0 ] . fontName ) { fonts . push ( ... textStyle . map ( ( e ) => e . fontName ) ) ; } if ( textNode ) { fonts . push ( ... getAllFonts ( [ textNode ] ) ) ; } fonts = uniqWith ( fonts , isEqual ) ; await loadFonts ( fonts ) ; } if ( ! textNode ) textNode = figma . createText ( ) ; textNode . characters = textStyle . reduce ( ( str , style ) => { return str + style . characters ; } , '' ) ; let n = 0 ; textStyle . forEach ( ( style ) => { const L = style . characters . length ; if ( L ) { for ( const key in style ) { if ( key !== 'characters' ) { const name = key . replace ( /^(.)/g , ( $1 ) => $1 . toUpperCase ( ) ) ; textNode [ 'setRange' + name ] ( n , n + L , style [ key ] ) ; } } n += L ; } } ) ; return textNode ; } function changeCharactersTextStyle ( textStyle , characters ) { textStyle = cloneDeep ( textStyle ) ; let n = 0 ; const length = textStyle . length - 1 ; const charactersLength = characters . length ; for ( let i = 0 ; i <= length ; i ++ ) { const s = textStyle [ i ] ; let l = s . characters . length ; if ( i == length ) l = charactersLength ; s . characters = characters . slice ( n , n + l ) ; n += l ; if ( n > charactersLength ) { textStyle = textStyle . splice ( 0 , i + 1 ) ; continue ; } } return textStyle ; } function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) ; function changeTextStyle ( textStyle , styleName , newValue , beforeValue ? ) { textStyle = cloneDeep ( textStyle ) ; textStyle . forEach ( ( style ) => { if ( beforeValue === undefined || ( beforeValue !== undefined && isEqual ( style [ styleName ] , beforeValue ) ) ) { ; ( style as any ) [ styleName ] = newValue ; } } ) ; return textStyle ; } function isEqualLetterStyle ( letter , textStyle ) { let is = true ; for ( const key in letter ) { if ( key !== 'characters' ) { if ( ! isEqual ( letter [ key ] , textStyle [ key ] ) ) { is = false ; break ; } } } return is ; } export { parseTextStyle , splitTextStyleIntoLines , joinTextLinesStyles , applyTextStyleToTextNode , changeCharactersTextStyle , changeTextStyle } ;