0
Fork 0
mirror of https://github.com/penpot/penpot-export.git synced 2025-02-12 18:18:01 -05:00

feat(core): describe typography requirements and Google Fonts implementation aid

This commit is contained in:
Roberto Redradix 2023-09-08 13:19:43 +02:00 committed by Roberto RedRadix
parent 562e1d77e3
commit 1fcb0e0860
8 changed files with 150 additions and 26 deletions

View file

@ -0,0 +1,36 @@
import { PenpotApiFile } from '../../api'
import { FontsSummary } from '../../types'
export const summarizeTypographies = (
penpotFile: PenpotApiFile,
): FontsSummary => {
const typographies = Object.values(penpotFile.data.typographies ?? {})
const separator = '|>'
const dedupedFontsKeys = Array.from(
typographies.reduce((set, typography) => {
const { fontId, fontFamily, fontWeight } = typography
const typographyKey = [fontId, fontFamily, fontWeight].join(separator)
set.add(typographyKey)
return set
}, new Set<string>()),
)
const fontsSummary = dedupedFontsKeys.reduce<FontsSummary>(
(summary, typographyKey) => {
const [fontId, fontFamily, fontWeight] = typographyKey.split(separator)
const fontSource = fontId.startsWith('gfont-')
? 'googleFonts'
: 'userCustomFonts'
summary[fontSource][fontFamily] ??= []
summary[fontSource][fontFamily].push(fontWeight)
return summary
},
{ googleFonts: {}, userCustomFonts: {} },
)
return fontsSummary
}

View file

@ -1,6 +1,7 @@
import path from 'node:path'
import { adaptTypographiesToCssClassDefinitions } from './adapters/inbound/typographiesToCssClasses'
import { summarizeTypographies } from './adapters/inbound/typographiesToFontSummary'
import { adaptColorsToCssVariables } from './adapters/inbound/colorsToCssVariables'
import { adaptPageComponentsToCssClassDefinitions } from './adapters/inbound/pageComponentsToCssClasses'
@ -16,30 +17,41 @@ import {
cssOutputter,
scssOutputter,
jsonOutputter,
OutputterFunction,
} from './outputters'
import { CSSClassDefinition, CSSCustomPropertyDefinition } from './types'
import {
CSSClassDefinition,
CSSCustomPropertyDefinition,
FontsSummary,
} from './types'
const processOutput = ({
outputFormat = 'css',
outputPath,
content,
metadata,
}: {
outputFormat: AssetConfig['format']
outputPath: string
content: CSSClassDefinition[] | CSSCustomPropertyDefinition[]
metadata?: FontsSummary
}) => {
if (outputFormat === 'css') {
return writeTextFile(outputPath, cssOutputter, content)
}
if (outputFormat === 'scss') {
return writeTextFile(outputPath, scssOutputter, content)
}
if (outputFormat === 'json') {
return writeTextFile(outputPath, jsonOutputter, content)
}
throw new Error(
'Unable to process output format. This is an error in penpot-export code, please contact their authors.',
)
const outputter: OutputterFunction | null =
outputFormat === 'css'
? cssOutputter
: outputFormat === 'scss'
? scssOutputter
: outputFormat === 'json'
? jsonOutputter
: null
if (outputter === null)
throw new Error(
'Unable to process output format. This is an error in penpot-export code, please contact their authors.',
)
const textContents = outputter(content, metadata)
return writeTextFile(outputPath, textContents)
}
export type * from './types'
@ -78,6 +90,7 @@ export default async function penpotExport(
outputFormat: typographiesConfig.format,
outputPath: path.resolve(rootProjectPath, typographiesConfig.output),
content: adaptTypographiesToCssClassDefinitions(penpotFile),
metadata: summarizeTypographies(penpotFile),
})
console.log('✅ Typographies: %s', typographiesConfig.output)

View file

@ -1,9 +1,11 @@
import {
CSSClassDefinition,
CSSCustomPropertyDefinition,
FontsSummary,
isCssClassDefinition,
} from '../../types'
import { describeFontsRequirements } from '../fileHeader'
import { OutputterFunction } from '../types'
import {
@ -40,9 +42,15 @@ const serializeCssCustomProperty = (
return `${padding}${key}: ${value};`
}
const serializeCss: OutputterFunction = (
const composeFileHeader = (fontsSummary: FontsSummary) => {
const message = describeFontsRequirements(fontsSummary)
return ['/*', ...message.map((line) => ' * ' + line), '*/'].join('\n')
}
const composeFileBody = (
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
): string => {
) => {
if (areCssCustomPropertiesDefinitions(cssDefinitions)) {
const pad = 2
const cssDeclarations = cssDefinitions.map((customPropertyDefinition) =>
@ -54,4 +62,16 @@ const serializeCss: OutputterFunction = (
}
}
const serializeCss: OutputterFunction = (
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
fontsSummary?: FontsSummary,
): string => {
const body: string = composeFileBody(cssDefinitions)
if (fontsSummary === undefined) return body
const header: string = composeFileHeader(fontsSummary)
return header + '\n\n' + body
}
export default serializeCss

View file

@ -0,0 +1,26 @@
import { FontFamily, FontWeights, FontsSummary } from '../types'
const createGoogleFontLink = (
fontFamily: FontFamily,
fontWeights: FontWeights,
) => {
const family = encodeURIComponent(fontFamily)
const weights = fontWeights.join(',')
return `<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=${family}:${weights}">`
}
export const describeFontsRequirements = (
fontSummary: FontsSummary,
): string[] => {
const userFontsCount = Object.keys(fontSummary.userCustomFonts).length
const googleFontsCount = Object.keys(fontSummary.googleFonts).length
return [
'These generated typography declarations rely on the given font being already available in your front end code.',
`There are ${userFontsCount} custom fonts and ${googleFontsCount} fonts from Google Fonts.`,
`You can choose to load the latter from the Google Fonts CSS API in a HTML document using:`,
...Object.entries(fontSummary.googleFonts).map(([family, weights]) =>
createGoogleFontLink(family, weights),
),
]
}

View file

@ -1,18 +1,13 @@
import fs from 'node:fs'
import path from 'node:path'
import { OutputterFunction } from './types'
export { default as cssOutputter } from './css'
export { default as scssOutputter } from './scss'
export { default as jsonOutputter } from './json'
export function writeTextFile(
outputPath: string,
outputter: OutputterFunction,
...outputterParams: Parameters<OutputterFunction>
) {
const textContents = outputter(...outputterParams)
export type { OutputterFunction } from './types'
export function writeTextFile(outputPath: string, textContents: string) {
const dirname = path.dirname(outputPath)
if (!fs.existsSync(dirname)) {

View file

@ -1,11 +1,13 @@
import {
CSSClassDefinition,
CSSCustomPropertyDefinition,
FontsSummary,
isCssClassDefinition,
} from '../../types'
import { camelToKebab } from '../css/syntax'
import { describeFontsRequirements } from '../fileHeader'
import { OutputterFunction } from '../types'
import { textToScssVariableName } from './syntax'
@ -39,9 +41,15 @@ const serializeScssVariable = (
return `${property}: ${value};`
}
const serializeScss: OutputterFunction = (
const composeFileHeader = (fontsSummary: FontsSummary) => {
const message = describeFontsRequirements(fontsSummary)
return message.map((line) => '// ' + line).join('\n')
}
const composeFileBody = (
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
): string => {
) => {
if (areCssCustomPropertiesDefinitions(cssDefinitions)) {
const cssDeclarations = cssDefinitions.map((customPropertyDefinition) =>
serializeScssVariable(customPropertyDefinition),
@ -52,4 +60,16 @@ const serializeScss: OutputterFunction = (
}
}
const serializeScss: OutputterFunction = (
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
fontsSummary?: FontsSummary,
): string => {
const body: string = composeFileBody(cssDefinitions)
if (fontsSummary === undefined) return body
const header: string = composeFileHeader(fontsSummary)
return header + '\n\n' + body
}
export default serializeScss

View file

@ -1,5 +1,10 @@
import { CSSClassDefinition, CSSCustomPropertyDefinition } from '../types'
import {
CSSClassDefinition,
CSSCustomPropertyDefinition,
FontsSummary,
} from '../types'
export type OutputterFunction = (
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
metadata?: FontsSummary,
) => string

View file

@ -15,3 +15,12 @@ export interface CSSCustomPropertyDefinition {
name: string
value: string
}
export type FontFamily = string
export type FontWeights = string[]
export type FontsDetails = Record<FontFamily, FontWeights>
export interface FontsSummary {
googleFonts: FontsDetails
userCustomFonts: FontsDetails
}