0
Fork 0
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:
Alex Sánchez 2024-04-29 15:44:49 +02:00 committed by GitHub
parent 881ccabe86
commit 8021da2623
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 27114 additions and 1582 deletions

View file

@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---
Add support for Google Fonts

26942
plugin-src/gfonts.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -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';

View 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)
};
};

View file

@ -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)
};
};

View file

@ -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),

View file

@ -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';

View 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';

View 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;
}
};

View file

@ -1,3 +0,0 @@
export const translateFontVariantId = (style: string) => {
return style.toLowerCase().replace(/\s/g, '');
};

View file

@ -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)
};
});
};

View file

@ -5,6 +5,7 @@
"lib": ["es6"],
"strict": true,
"typeRoots": ["../node_modules/@figma"],
"moduleResolution": "Node"
"moduleResolution": "Node",
"resolveJsonModule": true
}
}

View file

@ -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';

View file

@ -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);
}
};

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -1 +0,0 @@
export * from './validateFont';

View file

@ -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);
};