const getFontWeight = ( layer ) : any => { const sketchRatio = layer . style . fontWeight / 12 ; const domScale = [ 'thin' , 'extra light' , 'light' , 'normal' , 'medium' , 'semi bold' , 'bold' , 'extra bold' , 'black' ] ; return domScale [ Math . floor ( sketchRatio * domScale . length ) ] ; } ; const getFontStretch = ( layer ) : any => { switch ( layer . style . fontStretch ) { case 'compressed' : return 'extra condensed' ; case 'condensed' : return 'condensed' ; case 'narrow' : return 'semi condensed' ; case 'expanded' : return 'expanded' ; case 'poster' : return 'extra expanded' ; default : return 'normal' ; } ; } ; const getFontStyle = ( layer ) : any => { if ( layer . style . fontStyle === 'italic' ) { return 'italic' ; } else { return 'normal' ; } } ; export const getSystemFontsLocation = ( ) : string | null => { const systemLibrary = NSFileManager . defaultManager ( ) . URLsForDirectory_inDomains ( NSLibraryDirectory , 8 ) [ 0 ] ; const systemLibraryPath = systemLibrary ? systemLibrary . absoluteString ( ) . replace ( 'file://' , '' ) : null ; const systemFonts = systemLibraryPath ? NSFileManager . defaultManager ( ) . fileExistsAtPath ( `${ systemLibraryPath }Fonts` ) : null ; if ( systemFonts ) { return `${ systemLibraryPath }Fonts/` ; } else { return null ; } } ; export const getUserFontsLocation = ( ) : string | null => { const userLibrary = NSFileManager . defaultManager ( ) . URLsForDirectory_inDomains ( NSLibraryDirectory , 1 ) [ 0 ] ; const userLibraryPath = userLibrary ? userLibrary . absoluteString ( ) . replace ( 'file://' , '' ) : null ; const userFonts = userLibraryPath ? NSFileManager . defaultManager ( ) . fileExistsAtPath ( `${ userLibraryPath }Fonts` ) : null ; if ( userFonts ) { return `${ userLibraryPath }Fonts/` ; } else { return null ; } } ; export const getSupplementalFontsLocation = ( ) : string | null => { const systemLibrary = NSFileManager . defaultManager ( ) . URLsForDirectory_inDomains ( NSLibraryDirectory , 8 ) [ 0 ] ; const systemLibraryPath = systemLibrary ? systemLibrary . absoluteString ( ) . replace ( 'file://' , '' ) : null ; const systemFonts = systemLibraryPath ? NSFileManager . defaultManager ( ) . fileExistsAtPath ( `${ systemLibraryPath }Fonts/Supplemental` ) : null ; if ( systemFonts ) { return `${ systemLibraryPath }Fonts/Supplemental/` ; } else { return null ; } } ; export const getUserFonts = ( ) : string [ ] | null => { const userFontsLoc = getUserFontsLocation ( ) ; if ( userFontsLoc ) { const userFonts = NSFileManager . defaultManager ( ) . contentsOfDirectoryAtPath_error ( userFontsLoc , nil ) ; return Array . from ( userFonts , ( item ) => { return String ( item ) ; } ) ; } else { return null ; } } ; export const getSystemFonts = ( ) : string [ ] | null => { const systemFontsLoc = getSystemFontsLocation ( ) ; if ( systemFontsLoc ) { const systemFonts = NSFileManager . defaultManager ( ) . contentsOfDirectoryAtPath_error ( systemFontsLoc , nil ) ; return Array . from ( systemFonts , ( item ) => { return String ( item ) ; } ) ; } else { return null ; } } ; export const getSupplimentalFonts = ( ) : string [ ] | null => { const supplementalFontsLoc = getSupplementalFontsLocation ( ) ; if ( supplementalFontsLoc ) { const supplementalFonts = NSFileManager . defaultManager ( ) . contentsOfDirectoryAtPath_error ( supplementalFontsLoc , nil ) ; return Array . from ( supplementalFonts , ( item ) => { return String ( item ) ; } ) ; } else { return null ; } } ; export const getAllFonts = ( ) : next . FontDir [ ] | null => { const userFontsLoc = getUserFontsLocation ( ) ; const systemFontsLoc = getSystemFontsLocation ( ) ; const supplementalFontsLoc = getSupplementalFontsLocation ( ) ; const userFonts = getUserFonts ( ) ; const systemFonts = getSystemFonts ( ) ; const supplementalFonts = getSupplimentalFonts ( ) ; const fontLocations = [ userFontsLoc , systemFontsLoc , supplementalFontsLoc ] ; const fontLocationContents = [ userFonts , systemFonts , supplementalFonts ] ; const availableFontLocations = fontLocations . filter ( ( fontDir ) => fontDir !== null ) ; if ( availableFontLocations . length > 0 ) { const allFonts = ( availableFontLocations as string [ ] ) . map ( ( fontLocation , index ) => { return { location : fontLocation , contents : fontLocationContents [ index ] } ; } ) ; return allFonts as next . FontDir [ ] ; } else { return null ; } } ; const getFontNameVariations = ( font ) : string [ ] => { const noSpace = font . replace ( /\s/g , '' ) ; const hyphenCase = font . replace ( /\s/g , '-' ) ; return [ font , noSpace , hyphenCase ] ; } ; const containsFontNameVariation = ( fontFileName , fontNameVariations ) : boolean => { let contains = false ; const normalizedFileName = fontFileName . toUpperCase ( ) ; fontNameVariations . forEach ( ( variation ) => { const normalizedNameVariant = variation . toUpperCase ( ) ; if ( normalizedFileName . indexOf ( normalizedNameVariant ) !== - 1 ) { contains = true ; } } ) ; return contains ; } ; const processFontFile = ( fontFile ) => { const string = fontFile . split ( '' ) . filter ( ( str ) => { if ( str !== '-' && str !== '_' && str !== ' ' ) { return str ; } } ) . join ( '' ) . toLowerCase ( ) ; const weights = [ 'thin' , 'hairline' , 'extra light' , 'ultra light' , 'light' , 'regular' , 'medium' , 'semi bold' , 'demi bold' , 'extra bold' , 'ultra bold' , 'bold' , 'black' , 'heavy' ] ; const stretchs = [ 'extra condensed' , 'semi condensed' , 'condensed' , 'extra expanded' , 'expanded' ] ; const style = 'italic' ; const weight = weights . find ( ( w ) => { return string . indexOf ( w . replace ( /\s/g , '' ) ) !== - 1 ; } ) ; const stretch = stretchs . find ( ( s ) => { return string . indexOf ( s . replace ( /\s/g , '' ) ) !== - 1 ; } ) ; const italic = string . indexOf ( style ) !== - 1 ; return { weight : weight ? weight === 'regular' ? 'normal' : weight : 'normal' , stretch : stretch ? stretch : 'normal' , style : italic ? 'italic' : 'normal' } ; } ; export const getFontFiles = ( fonts ) : { [ id ] : next . ProcessedFont [ ] ; } => { const allFonts = getAllFonts ( ) ; return ( allFonts as any ) . reduce ( ( result , current ) => { const fontFamily = fonts . allFontFamilies . find ( ( ff ) => { const fontNameVariations = getFontNameVariations ( ff ) ; return current . contents . some ( ( fontFileName ) => { return containsFontNameVariation ( fontFileName , fontNameVariations ) ; } ) ; } ) ; if ( fontFamily ) { const fontNameVariations = getFontNameVariations ( fontFamily ) ; const fontFiles = current . contents . filter ( ( fontFileName ) => { return containsFontNameVariation ( fontFileName , fontNameVariations ) ; } ) . map ( ( fontFileName ) => { const processedFontFileName = processFontFile ( fontFileName ) ; return { fileName : fontFileName , location : current . location , path : `${ current . location }/${ fontFileName }` , font : { family : fontFamily , ... processedFontFileName } } ; } ) ; result = { ... result , [ `${ fontFamily }` ] : fontFiles } ; } return result ; } , { } ) ; } ; export const processFonts = ( fonts ) : { [ id ] : next . ProcessedFont [ ] ; } => { const fontFiles = getFontFiles ( fonts ) ; return fonts . allFontFamilies . reduce ( ( result , current ) => { const fontFamilyFiles = fontFiles [ `${ current }` ] ; if ( fontFamilyFiles ) { const tempFontFamilyDirectory = NSTemporaryDirectory ( ) ; const tempFontFiles = fontFamilyFiles . map ( ( fwp ) => { const tempFilePath = `${ tempFontFamilyDirectory }${ fwp . fileName }` ; NSFileManager . defaultManager ( ) . copyItemAtPath_toPath_error ( `${ fwp . path }` , `${ tempFilePath }` , nil ) ; return { fileName : fwp . fileName , location : tempFontFamilyDirectory , path : tempFilePath , font : fwp . font } ; } ) ; result = { ... result , [ `${ current }` ] : tempFontFiles } ; } return result ; } , { } ) ; } ; const createSvgFromLayer = ( page , layer , sketch , id ? ) : next . SvgAsset => { let borderSize = 0 ; const activeBorders = layer . style . borders . filter ( ( border ) => border . enabled ) ; if ( activeBorders ) { activeBorders . forEach ( ( border ) => { if ( border . thickness > borderSize ) { borderSize = border . thickness ; } } ) ; } const layerDuplicate = layer . duplicate ( ) ; layerDuplicate . parent = page ; layerDuplicate . style . opacity = 1 ; layerDuplicate . transform . rotation = 0 ; layerDuplicate . transform . flippedHorizontally = false ; layerDuplicate . transform . flippedVertically = false ; sketch . export ( layerDuplicate , { formats : 'svg' , output : NSTemporaryDirectory ( ) , [ 'use-id-for-name' ] : true , overwriting : true } ) ; layer . frame . width = Math . round ( layer . frame . width + ( borderSize * 1.5 ) ) ; layer . frame . height = Math . round ( layer . frame . height + ( borderSize * 1.5 ) ) ; layer . frame . x = Math . round ( layer . frame . x - ( ( borderSize * 1.5 ) / 2 ) ) ; layer . frame . y = Math . round ( layer . frame . y - ( ( borderSize * 1.5 ) / 2 ) ) ; layerDuplicate . remove ( ) ; return { id : id ? id : layer . id , src : `${ NSTemporaryDirectory ( ) }${ layerDuplicate . id }.svg` } ; } ; const createShapeFromLayer = ( layer , sketch , name ? ) : next . Shape => { const shapeReplacement = new sketch . Shape ( { name : name ? name : layer . name , frame : layer . frame , style : layer . style , transform : { rotation : layer . transform . rotation , flippedHorizontally : layer . transform . flippedHorizontally , flippedVertically : layer . transform . flippedVertically } } ) ; return shapeReplacement ; } ; const createShapeFromGroup = ( layer , sketch , prefix ) : next . Shape => { const newName = layer . name . substr ( prefix . length , layer . name . length - prefix . length ) . trim ( ) ; const shapeReplacement = createShapeFromLayer ( layer , sketch , newName ) ; return shapeReplacement ; } ; const createImageLayerImage = ( page , layer , sketch ) : next . ImgAsset => { const layerDuplicate = layer . duplicate ( ) ; layerDuplicate . parent = page ; sketch . export ( layerDuplicate , { scales : '1, 2' , formats : 'png' , output : NSTemporaryDirectory ( ) , [ 'use-id-for-name' ] : true , [ 'save-for-web' ] : true , overwriting : true } ) ; layerDuplicate . remove ( ) ; return { id : ( < next . Image > layer ) . image . id , src : { [ `1x` ] : `${ NSTemporaryDirectory ( ) }${ layerDuplicate . id }.png` , [ `2x` ] : `${ NSTemporaryDirectory ( ) }${ layerDuplicate . id }@2x.png` } , } ; } ; const createGradientFillImage = ( page , layer , sketch ) : next . ImgAsset => { const activeGradients = layer . style . fills . filter ( ( fill ) => { return fill . enabled && fill . fillType === 'Gradient' ; } ) ; const topGradient = activeGradients [ activeGradients . length - 1 ] ; const gradientImage = new sketch . ShapePath ( { parent : page , frame : layer . frame , style : { fills : [ topGradient ] , borders : [ ] } } ) ; sketch . export ( gradientImage , { scales : '1, 2' , formats : 'png' , output : NSTemporaryDirectory ( ) , [ 'use-id-for-name' ] : true , [ 'save-for-web' ] : true , overwriting : true } ) ; gradientImage . remove ( ) ; return { id : layer . id , src : { [ `1x` ] : `${ NSTemporaryDirectory ( ) }${ gradientImage . id }.png` , [ `2x` ] : `${ NSTemporaryDirectory ( ) }${ gradientImage . id }@2x.png` , } } ; } ; const createImageFillImage = ( page , image , sketch ) : next . ImgAsset => { const width = image . nsimage . size ( ) . width ; const height = image . nsimage . size ( ) . height ; const fillImage = new sketch . Image ( { image : image , parent : page , frame : { width , height , x : 0 , y : 0 } } ) ; sketch . export ( fillImage , { scales : '1, 2' , formats : 'png' , output : NSTemporaryDirectory ( ) , [ 'use-id-for-name' ] : true , [ 'save-for-web' ] : true , overwriting : true } ) ; fillImage . remove ( ) ; return { id : image . id , src : { [ `1x` ] : `${ NSTemporaryDirectory ( ) }${ fillImage . id }.png` , [ `2x` ] : `${ NSTemporaryDirectory ( ) }${ fillImage . id }@2x.png` } } ; } ; const processLayerFills = ( page , layer , images , sketch , fillImages = [ ] ) : next . ImgAsset [ ] => { ( < next . Shape | next . ShapePath > layer ) . style . fills . forEach ( ( fill ) => { if ( fill . pattern . image !== null && fill . enabled && ! images . find ( image => image . id === ( ( fill . pattern as next . Pattern ) . image as next . ImageData ) . id ) ) { const fillImage = createImageFillImage ( page , fill . pattern . image , sketch ) ; fillImages . push ( fillImage ) ; } else if ( fill . fillType === 'Gradient' && fill . enabled ) { const gradientImage = createGradientFillImage ( page , layer as next . ShapePath | next . Shape , sketch ) ; fillImages . push ( gradientImage ) ; } } ) ; return fillImages ; } ; const isComplexShapePath = ( layer ) : boolean => { const hasOpenPath = ! ( < next . ShapePath > layer ) . closed ; const notRectangle = ( < next . ShapePath > layer ) . shapeType !== 'Rectangle' ; const notOval = ( < next . ShapePath > layer ) . shapeType !== 'Oval' ; const isOddShape = notRectangle && notOval ; const hasDashPattern = ( < next . ShapePath > layer ) . style . borderOptions . dashPattern . length > 0 ; return hasOpenPath || isOddShape || hasDashPattern ; } ; const processShapePath = ( page , layer , images , sketch , callback ) : void => { const isComplex = isComplexShapePath ( layer ) ; const shapePathFillImages = processLayerFills ( page , layer , images , sketch ) ; if ( isComplex ) { const shapeReplacement = createShapeFromLayer ( layer as next . ShapePath , sketch ) ; const svg = createSvgFromLayer ( page , layer as next . ShapePath , sketch , shapeReplacement . id ) ; layer . parent . layers . splice ( layer . index , 1 , shapeReplacement ) ; callback ( shapePathFillImages , svg ) ; } else { callback ( shapePathFillImages , null ) ; } } ; const processShape = ( page , layer , images , sketch , callback ) : void => { const shapeFillImages = processLayerFills ( page , layer , images , sketch ) ; const svg = createSvgFromLayer ( page , layer , sketch ) ; callback ( shapeFillImages , svg ) ; } ; const processImage = ( page , layer , images , sketch , callback ) : void => { if ( ! images . find ( image => image . id === layer . image . id ) ) { const image = createImageLayerImage ( page , layer , sketch ) ; callback ( image ) ; } else { callback ( null ) ; } } ; const processGroup = ( page , layer , sketch , callback ) : void => { if ( layer . name . startsWith ( '[next.svg]' ) ) { const shapeReplacement = createShapeFromGroup ( layer as next . Group , sketch , '[next.svg]' ) ; const svg = createSvgFromLayer ( page , layer as next . Group , sketch , shapeReplacement . id ) ; layer . parent . layers . splice ( layer . index , 1 , shapeReplacement ) ; callback ( svg ) ; } else { callback ( null ) ; } } ; const processText = ( layer , fonts , callback ) : void => { const fontFamily = ( < next . Text > layer ) . style . fontFamily ; const fontWeight = getFontWeight ( layer ) ; const fontStretch = getFontStretch ( layer ) ; const fontStyle = getFontStyle ( layer ) ; const availableFamilies = NSFontManager . sharedFontManager ( ) . availableFontFamilies ( ) ; const availableFamiliesArray = Array . from ( availableFamilies , item => String ( item ) ) ; const fontAvailable = availableFamiliesArray . includes ( fontFamily ) ; const fontExists = fonts . allFontFamilies . includes ( fontFamily ) ; const variantExists = fontExists && fonts . byFamily [ `${ fontFamily }` ] . find ( ( variant ) => { return variant . weight === fontWeight && variant . stretch === fontStretch && variant . style === fontStyle ; } ) ; if ( fonts && fontAvailable && ( ! fontExists || ! variantExists ) ) { if ( ! fontExists ) { fonts = { ... fonts , allFontFamilies : [ ... fonts . allFontFamilies , fontFamily ] , byFamily : { ... fonts . byFamily , [ `${ fontFamily }` ] : [ ] } } ; } if ( ! variantExists ) { fonts = { ... fonts , byFamily : { ... fonts . byFamily , [ `${ fontFamily }` ] : [ ... fonts . byFamily [ `${ fontFamily }` ] , { weight : fontWeight , style : fontStyle , stretch : fontStretch } ] } } ; } callback ( fonts ) ; } else { callback ( fonts ) ; } } ; const processLayer = ( page , layer , sketch , images , svgs , fonts , callback ) : void => { switch ( layer . type ) { case 'Image' : processImage ( page , layer as next . Image , images , sketch , ( image ) => { if ( image ) { images . push ( image ) ; } } ) ; break ; case 'Shape' : processShape ( page , layer as next . Shape , images , sketch , ( shapeImages , shapeSvg ) => { images . push ( ... shapeImages ) ; svgs . push ( shapeSvg ) ; } ) ; break ; case 'ShapePath' : processShapePath ( page , layer as next . ShapePath , images , sketch , ( shapePathImages , shapePathSvg ) => { if ( shapePathSvg ) { svgs . push ( shapePathSvg as next . SvgAsset ) ; } images . push ( ... shapePathImages ) ; } ) ; break ; case 'Text' : processText ( layer as next . Text , fonts , ( newFonts ) => { fonts = newFonts ; } ) ; break ; case 'Group' : processGroup ( page , layer as next . Group , sketch , ( groupSvg ) => { if ( groupSvg ) { svgs . push ( groupSvg ) ; } } ) ; break ; } callback ( { images , svgs , fonts } ) ; } ; const processLayers = ( page , layers , sketch , images = [ ] , svgs = [ ] , fonts = { allFontFamilies : [ ] , byFamily : { } } ) : any => { if ( layers . length > 0 ) { layers . forEach ( ( layer ) => { processLayer ( page , layer , sketch , images , svgs , fonts , ( newAssets ) => { images = newAssets . images ; svgs = newAssets . svgs ; fonts = newAssets . fonts ; } ) ; if ( layer . type === 'Group' ) { processLayers ( page , ( layer as next . Group ) . layers , sketch , images , svgs , fonts ) ; } } ) ; } return { images , svgs , fonts } ; } ; export const createArtboardImage = ( artboard , sketch ) : string => { const buffer = sketch . export ( artboard , { scales : '0.10' , formats : 'png' , output : false , [ 'save-for-web' ] : true } ) ; const bufferImg = new sketch . Image ( { image : buffer } ) ; const base64 = bufferImg . image . nsdata . base64EncodedStringWithOptions ( 0 ) ; return `data:image/png;base64, ${ base64 }` ; } ; export const getAssets = ( page , artboard , sketch ) => { const artboardAssets = processLayers ( page , artboard . layers , sketch ) ; const processedFonts = processFonts ( artboardAssets . fonts ) ; const artboardImage = createArtboardImage ( artboard , sketch ) ; return { ... artboardAssets , fonts : processedFonts , artboardImage } ; } ; export default getAssets ;