mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 05:33:02 -05:00
Google Fonts (#80)
* moved validate font logic * google fonts working * fixes * minor improvements * fix linter * fixes * Changeset * refactor * refactor * move files around --------- Co-authored-by: Jordi Sala Morales <jordism91@gmail.com>
This commit is contained in:
parent
881ccabe86
commit
8021da2623
24 changed files with 27114 additions and 1582 deletions
5
.changeset/good-teachers-clap.md
Normal file
5
.changeset/good-teachers-clap.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"penpot-exporter": minor
|
||||
---
|
||||
|
||||
Add support for Google Fonts
|
26942
plugin-src/gfonts.json
Normal file
26942
plugin-src/gfonts.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,5 +7,5 @@ export * from './transformFills';
|
|||
export * from './transformProportion';
|
||||
export * from './transformSceneNode';
|
||||
export * from './transformStrokes';
|
||||
export * from './transformTextStyle';
|
||||
export * from './transformText';
|
||||
export * from './transformVectorPaths';
|
||||
|
|
89
plugin-src/transformers/partials/transformText.ts
Normal file
89
plugin-src/transformers/partials/transformText.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { transformFills } from '@plugin/transformers/partials';
|
||||
import {
|
||||
translateFills,
|
||||
translateHorizontalAlign,
|
||||
translateVerticalAlign
|
||||
} from '@plugin/translators';
|
||||
import {
|
||||
translateFontId,
|
||||
translateFontStyle,
|
||||
translateFontVariantId,
|
||||
translateGrowType,
|
||||
translateLetterSpacing,
|
||||
translateLineHeight,
|
||||
translateTextDecoration,
|
||||
translateTextTransform
|
||||
} from '@plugin/translators/text';
|
||||
|
||||
import { TextStyle } from '@ui/lib/types/text/textContent';
|
||||
import { TextShape } from '@ui/lib/types/text/textShape';
|
||||
|
||||
export const transformText = (node: TextNode): Partial<TextShape> => {
|
||||
const styledTextSegments = node.getStyledTextSegments([
|
||||
'fontName',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'lineHeight',
|
||||
'letterSpacing',
|
||||
'textCase',
|
||||
'textDecoration',
|
||||
'fills'
|
||||
]);
|
||||
|
||||
return {
|
||||
content: {
|
||||
type: 'root',
|
||||
verticalAlign: translateVerticalAlign(node.textAlignVertical),
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph-set',
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: styledTextSegments.map(segment => ({
|
||||
fills: translateFills(segment.fills, node.width, node.height),
|
||||
text: segment.characters,
|
||||
...transformTextStyle(node, segment)
|
||||
})),
|
||||
...(styledTextSegments.length ? transformTextStyle(node, styledTextSegments[0]) : {}),
|
||||
...transformFills(node)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
growType: translateGrowType(node)
|
||||
};
|
||||
};
|
||||
|
||||
const transformTextStyle = (
|
||||
node: TextNode,
|
||||
segment: Pick<
|
||||
StyledTextSegment,
|
||||
| 'characters'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'fontName'
|
||||
| 'fontSize'
|
||||
| 'fontWeight'
|
||||
| 'lineHeight'
|
||||
| 'letterSpacing'
|
||||
| 'textCase'
|
||||
| 'textDecoration'
|
||||
| 'fills'
|
||||
>
|
||||
): Partial<TextStyle> => {
|
||||
return {
|
||||
fontFamily: segment.fontName.family,
|
||||
fontId: translateFontId(segment.fontName),
|
||||
fontVariantId: translateFontVariantId(segment.fontName, segment.fontWeight),
|
||||
fontSize: segment.fontSize.toString(),
|
||||
fontStyle: translateFontStyle(segment.fontName.style),
|
||||
fontWeight: segment.fontWeight.toString(),
|
||||
textAlign: translateHorizontalAlign(node.textAlignHorizontal),
|
||||
textDecoration: translateTextDecoration(segment),
|
||||
textTransform: translateTextTransform(segment),
|
||||
letterSpacing: translateLetterSpacing(segment),
|
||||
lineHeight: translateLineHeight(segment)
|
||||
};
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
import slugify from 'slugify';
|
||||
|
||||
import {
|
||||
translateFontStyle,
|
||||
translateFontVariantId,
|
||||
translateHorizontalAlign,
|
||||
translateLetterSpacing,
|
||||
translateLineHeight,
|
||||
translateTextDecoration,
|
||||
translateTextTransform
|
||||
} from '@plugin/translators';
|
||||
|
||||
import { TextStyle } from '@ui/lib/types/text/textContent';
|
||||
|
||||
export const transformTextStyle = (
|
||||
node: TextNode,
|
||||
segment: Pick<
|
||||
StyledTextSegment,
|
||||
| 'characters'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'fontName'
|
||||
| 'fontSize'
|
||||
| 'fontWeight'
|
||||
| 'lineHeight'
|
||||
| 'letterSpacing'
|
||||
| 'textCase'
|
||||
| 'textDecoration'
|
||||
| 'fills'
|
||||
>
|
||||
): Partial<TextStyle> => {
|
||||
return {
|
||||
fontFamily: segment.fontName.family,
|
||||
fontId: `gfont-${slugify(segment.fontName.family.toLowerCase())}`,
|
||||
fontSize: segment.fontSize.toString(),
|
||||
fontStyle: translateFontStyle(segment.fontName.style),
|
||||
fontWeight: segment.fontWeight.toString(),
|
||||
fontVariantId: translateFontVariantId(segment.fontName.style),
|
||||
textAlign: translateHorizontalAlign(node.textAlignHorizontal),
|
||||
textDecoration: translateTextDecoration(segment),
|
||||
textTransform: translateTextTransform(segment),
|
||||
letterSpacing: translateLetterSpacing(segment),
|
||||
lineHeight: translateLineHeight(segment)
|
||||
};
|
||||
};
|
|
@ -2,53 +2,19 @@ import {
|
|||
transformBlend,
|
||||
transformDimensionAndPosition,
|
||||
transformEffects,
|
||||
transformFills,
|
||||
transformProportion,
|
||||
transformSceneNode,
|
||||
transformStrokes,
|
||||
transformTextStyle
|
||||
transformText
|
||||
} from '@plugin/transformers/partials';
|
||||
import {
|
||||
translateGrowType,
|
||||
translateStyledTextSegments,
|
||||
translateVerticalAlign
|
||||
} from '@plugin/translators';
|
||||
|
||||
import { TextShape } from '@ui/lib/types/text/textShape';
|
||||
|
||||
export const transformTextNode = (node: TextNode, baseX: number, baseY: number): TextShape => {
|
||||
const styledTextSegments = node.getStyledTextSegments([
|
||||
'fontName',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'lineHeight',
|
||||
'letterSpacing',
|
||||
'textCase',
|
||||
'textDecoration',
|
||||
'fills'
|
||||
]);
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
name: node.name,
|
||||
content: {
|
||||
type: 'root',
|
||||
verticalAlign: translateVerticalAlign(node.textAlignVertical),
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph-set',
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: translateStyledTextSegments(node, styledTextSegments),
|
||||
...(styledTextSegments.length ? transformTextStyle(node, styledTextSegments[0]) : {}),
|
||||
...transformFills(node)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
growType: translateGrowType(node),
|
||||
...transformText(node),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformEffects(node),
|
||||
...transformSceneNode(node),
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
export * from './translateBlendMode';
|
||||
export * from './translateShadowEffects';
|
||||
export * from './translateFills';
|
||||
export * from './translateFontStyle';
|
||||
export * from './translateFontVariantId';
|
||||
export * from './translateGrowType';
|
||||
export * from './translateHorizontalAlign';
|
||||
export * from './translateLetterSpacing';
|
||||
export * from './translateLineHeight';
|
||||
export * from './translateStrokes';
|
||||
export * from './translateStyledTextSegments';
|
||||
export * from './translateTextDecoration';
|
||||
export * from './translateTextTransform';
|
||||
export * from './translateVectorPaths';
|
||||
export * from './translateVerticalAlign';
|
||||
|
|
7
plugin-src/translators/text/index.ts
Normal file
7
plugin-src/translators/text/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * from './translateFontIds';
|
||||
export * from './translateFontStyle';
|
||||
export * from './translateGrowType';
|
||||
export * from './translateLetterSpacing';
|
||||
export * from './translateLineHeight';
|
||||
export * from './translateTextDecoration';
|
||||
export * from './translateTextTransform';
|
60
plugin-src/translators/text/translateFontIds.ts
Normal file
60
plugin-src/translators/text/translateFontIds.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import slugify from 'slugify';
|
||||
|
||||
import { items as gfonts } from '@plugin/gfonts.json';
|
||||
|
||||
export const translateFontId = (fontName: FontName): string => {
|
||||
if (isGfont(fontName)) {
|
||||
return `gfont-${slugify(fontName.family.toLowerCase())}`;
|
||||
}
|
||||
|
||||
// @TODO: check if source sans pro
|
||||
|
||||
// always send font name if not gfont or source sans pro
|
||||
figma.ui.postMessage({ type: 'FONT_NAME', data: fontName.family });
|
||||
|
||||
// @TODO: custom font
|
||||
return slugify(fontName.family.toLowerCase());
|
||||
};
|
||||
|
||||
export const translateFontVariantId = (fontName: FontName, fontWeight: number) => {
|
||||
const variantId = translateGfontVariantId(fontName, fontWeight);
|
||||
if (variantId !== undefined) {
|
||||
return variantId;
|
||||
}
|
||||
|
||||
// @TODO: Custom font
|
||||
// @TODO: Source Sans pro
|
||||
return fontName.style.toLowerCase().replace(/\s/g, '');
|
||||
};
|
||||
|
||||
const findGoogleFont = (fontName: FontName) => {
|
||||
return gfonts.find(font => font.family === fontName.family);
|
||||
};
|
||||
|
||||
const isGfont = (fontName: FontName): boolean => {
|
||||
return findGoogleFont(fontName) !== undefined;
|
||||
};
|
||||
|
||||
const translateGfontVariantId = (fontName: FontName, fontWeight: number): string | undefined => {
|
||||
const gfont = findGoogleFont(fontName);
|
||||
|
||||
if (gfont === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check match directly by style
|
||||
const variant = gfont.variants.find(variant => variant === fontName.style.toLowerCase());
|
||||
if (variant !== undefined) {
|
||||
return variant;
|
||||
}
|
||||
|
||||
// check match by style and weight
|
||||
const italic = fontName.style.toLowerCase().includes('italic') ? 'italic' : '';
|
||||
const variantWithWeight = gfont.variants.find(
|
||||
variant => variant === `${fontWeight.toString()}${italic}`
|
||||
);
|
||||
|
||||
if (variantWithWeight !== undefined) {
|
||||
return variantWithWeight;
|
||||
}
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
export const translateFontVariantId = (style: string) => {
|
||||
return style.toLowerCase().replace(/\s/g, '');
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
import { transformTextStyle } from '@plugin/transformers/partials';
|
||||
import { translateFills } from '@plugin/translators/translateFills';
|
||||
|
||||
import { TextNode as PenpotTextNode } from '@ui/lib/types/text/textContent';
|
||||
|
||||
export const translateStyledTextSegments = (
|
||||
node: TextNode,
|
||||
segments: Pick<
|
||||
StyledTextSegment,
|
||||
| 'characters'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'fontName'
|
||||
| 'fontSize'
|
||||
| 'fontWeight'
|
||||
| 'lineHeight'
|
||||
| 'letterSpacing'
|
||||
| 'textCase'
|
||||
| 'textDecoration'
|
||||
| 'fills'
|
||||
>[]
|
||||
): PenpotTextNode[] => {
|
||||
return segments.map(segment => {
|
||||
figma.ui.postMessage({ type: 'FONT_NAME', data: segment.fontName.family });
|
||||
|
||||
return {
|
||||
fills: translateFills(segment.fills, node.width, node.height),
|
||||
text: segment.characters,
|
||||
...transformTextStyle(node, segment)
|
||||
};
|
||||
});
|
||||
};
|
|
@ -5,6 +5,7 @@
|
|||
"lib": ["es6"],
|
||||
"strict": true,
|
||||
"typeRoots": ["../node_modules/@figma"],
|
||||
"moduleResolution": "Node"
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export * from './detectMimeType';
|
||||
export * from './rgbToHex';
|
||||
export * from './applyMatrixToPoint';
|
||||
export * from './calculateAdjustment';
|
||||
export * from './calculateLinearGradient';
|
||||
export * from './detectMimeType';
|
||||
export * from './matrixInvert';
|
||||
export * from './rgbToHex';
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import slugify from 'slugify';
|
||||
|
||||
import { createPenpotFile } from '@ui/converters';
|
||||
import { PenpotDocument } from '@ui/lib/types/penpotDocument';
|
||||
import { validateFont } from '@ui/validators';
|
||||
|
||||
import Logo from './logo.svg?react';
|
||||
|
||||
|
@ -24,11 +22,7 @@ export const PenpotExporter = () => {
|
|||
|
||||
setExporting(false);
|
||||
} else if (event.data.pluginMessage?.type == 'FONT_NAME') {
|
||||
const fontName = event.data.pluginMessage.data as string;
|
||||
|
||||
if (!validateFont(fontName)) {
|
||||
addFontWarning(slugify(fontName.toLowerCase()));
|
||||
}
|
||||
addFontWarning(event.data.pluginMessage.data as string);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1434
ui-src/gfonts.json
1434
ui-src/gfonts.json
File diff suppressed because it is too large
Load diff
|
@ -7,12 +7,10 @@
|
|||
"allowJs": false,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './validateFont';
|
|
@ -1,10 +0,0 @@
|
|||
import slugify from 'slugify';
|
||||
|
||||
import fonts from '@ui/gfonts.json';
|
||||
|
||||
const gfonts = new Set(fonts);
|
||||
|
||||
export const validateFont = (fontFamily: string): boolean => {
|
||||
const name = slugify(fontFamily.toLowerCase());
|
||||
return gfonts.has(name);
|
||||
};
|
Loading…
Reference in a new issue