支持上传字体
This commit is contained in:
@@ -72,7 +72,10 @@
|
||||
"defaultText": "please input your text",
|
||||
"textColor": "Text Color",
|
||||
"fontFamily": "Font Family",
|
||||
"fontWeight": "Font Weight"
|
||||
"how2UploadFont": "How to Upload Font?",
|
||||
"fontWeight": "Font Weight",
|
||||
"uploadFontButton": "Upload Font | Files with the same name will replace the fonts uploaded previously",
|
||||
"uploadedFonts": "Uploaded Fonts"
|
||||
},
|
||||
"Metadata": {
|
||||
"title": "Screen Designer",
|
||||
|
||||
@@ -72,7 +72,10 @@
|
||||
"defaultText": "输入您的文字",
|
||||
"textColor": "文字颜色",
|
||||
"fontFamily": "选择字体",
|
||||
"fontWeight": "字体粗细"
|
||||
"how2UploadFont": "如何上传字体?",
|
||||
"fontWeight": "字体粗细",
|
||||
"uploadFontButton": "上传字体 | 相同名字的文件会替换前面上传的字体",
|
||||
"uploadedFonts": "已上传字体"
|
||||
},
|
||||
"Metadata": {
|
||||
"title": "屏幕背景设计工具",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"lucide-react": "^0.479.0",
|
||||
"lucide-react": "^0.536.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"motion": "^12.23.11",
|
||||
"next": "15.2.4",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -27,8 +27,8 @@ importers:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
lucide-react:
|
||||
specifier: ^0.479.0
|
||||
version: 0.479.0(react@19.0.0)
|
||||
specifier: ^0.536.0
|
||||
version: 0.536.0(react@19.0.0)
|
||||
lz-string:
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0
|
||||
@@ -2250,8 +2250,8 @@ packages:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
lucide-react@0.479.0:
|
||||
resolution: {integrity: sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==}
|
||||
lucide-react@0.536.0:
|
||||
resolution: {integrity: sha512-2PgvNa9v+qz4Jt/ni8vPLt4jwoFybXHuubQT8fv4iCW5TjDxkbZjNZZHa485ad73NSEn/jdsEtU57eE1g+ma8A==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
@@ -5129,7 +5129,7 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
lucide-react@0.479.0(react@19.0.0):
|
||||
lucide-react@0.536.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
Copyright @ 2004 by MAGENTA Ltd. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word "MgOpen", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "MgOpen" name.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -1,9 +1,15 @@
|
||||
import { Box, Heading, Text, Link, Flex } from '@radix-ui/themes';
|
||||
import Image from 'next/image';
|
||||
|
||||
import img from "./1024_576.png";
|
||||
export default function Page() {
|
||||
return (
|
||||
<Flex p="4" direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4">How to Create 3D Text with the Barbie Font (Free & Online Method)</Heading>
|
||||
<Flex gap={"4"} direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4" className='text-center'>How to Create 3D Text with the Barbie Font (Free & Online Method)</Heading>
|
||||
|
||||
<Flex justify={"center"}>
|
||||
<Image src={img} alt="Barbie Font Sample" width={1024} height={576} />
|
||||
</Flex>
|
||||
|
||||
<Text as="p" mb="4">
|
||||
Want to create stylish 3D text using the iconic Barbie font? In this tutorial, you'll learn how to generate 3D Barbie-style text using free online tools — no design experience needed.
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Box, Heading, Text, Link, Flex } from '@radix-ui/themes';
|
||||
import Image from 'next/image';
|
||||
|
||||
import img from "./1024_576.png";
|
||||
export default function Page() {
|
||||
return (
|
||||
<Flex p="4" direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4">如何使用芭比字体创建3D文字(免费在线方法)</Heading>
|
||||
<Flex gap={"4"} direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4" className='text-center'>如何使用芭比字体创建3D文字(免费在线方法)</Heading>
|
||||
|
||||
<Flex justify={"center"}>
|
||||
<Image src={img} alt="Barbie Font Sample" width={1024} height={576} />
|
||||
</Flex>
|
||||
|
||||
<Text as="p" mb="4">
|
||||
想要使用标志性的芭比字体创建时尚的3D文字吗?本教程将教你如何使用免费在线工具生成3D芭比风格文字——无需设计经验。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface BlogItem {
|
||||
id: string;
|
||||
date: string;
|
||||
cover: string;
|
||||
cover: StaticImageData;
|
||||
en: {
|
||||
title: string;
|
||||
summary: string;
|
||||
@@ -11,11 +11,14 @@ export interface BlogItem {
|
||||
summary: string;
|
||||
};
|
||||
}
|
||||
|
||||
import { StaticImageData } from "next/image";
|
||||
import Cover1 from "./Create-3D-Text-with-the-Barbie-Font/512_288.png";
|
||||
export const blogs = [
|
||||
{
|
||||
id: "Create-3D-Text-with-the-Barbie-Font",
|
||||
date: "2024-01-01",
|
||||
cover: "/next.svg",
|
||||
cover: Cover1,
|
||||
en: {
|
||||
title: "How to Create 3D Text with the Barbie Font",
|
||||
summary:
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useLocale, useTranslations } from 'next-intl';
|
||||
import { Locales } from '@/i18n/config';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function BlogListPage() {
|
||||
const locale = useLocale() as "zh" | "en";
|
||||
@@ -22,13 +23,13 @@ export default function BlogListPage() {
|
||||
<Card key={blog.id} size="2" style={{ maxWidth: 300, boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)' }} mx="4" my="4">
|
||||
<Link href={`/${locale}/blogs/${blog.id}`} color='iris'>
|
||||
<Flex direction="column" gap="4">
|
||||
<Box style={{ width: '100%', height: 200, overflow: 'hidden' }}>
|
||||
<img src={blog.cover} alt={blog[locale].title} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
<Box style={{ overflow: 'hidden' }}>
|
||||
<Image src={blog.cover} alt={blog[locale].title} width={512} height={288} />
|
||||
</Box>
|
||||
<Flex direction={"column"} gap={"1"}>
|
||||
<Heading as='h2' size="5" weight="bold" className='text-black dark:text-white'>{blog[locale].title}</Heading>
|
||||
<Text color="gray" >{blog.date}</Text>
|
||||
<Text >{blog[locale].summary}</Text>
|
||||
<Text truncate={true} >{blog[locale].summary}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Link>
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function generateMetadata({
|
||||
const name = "editor";
|
||||
|
||||
return {
|
||||
title: t("How to Create 3D Text with the Barbie Font (Free & Online Method)"),
|
||||
title: t("seoTitle"),
|
||||
description: t("seoDescription"),
|
||||
openGraph: {
|
||||
title: t("seoTitle"),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState, useRef, useEffect, } from "react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { Eye, Download, Share } from "lucide-react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { Text, Flex, Button, Select, AlertDialog, Code, Blockquote, Box } from "@radix-ui/themes";
|
||||
import { Text, Flex, Button, Select, AlertDialog, Code, AspectRatio } from "@radix-ui/themes";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateTextProps } from "./ThreeTools";
|
||||
import { TextProp } from "./TextSetting";
|
||||
import { encodeText, getShareLink } from "@/lib/utils";
|
||||
@@ -13,15 +13,15 @@ const Sizes = [
|
||||
"1024x768",
|
||||
"800x600",
|
||||
]
|
||||
|
||||
function gcd(a: number, b: number): number {
|
||||
return b === 0 ? a : gcd(b, a % b);
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const AspectRatio = Sizes.map(o => {
|
||||
const AspectRatios = Sizes.map(o => {
|
||||
const [w, h] = o.split("x").map(Number);
|
||||
const a = gcd(w, h);
|
||||
return `${w / a}/${h / a}`;
|
||||
// const a = gcd(w, h);
|
||||
return w / h;
|
||||
})
|
||||
export default function PreviewToolbar({
|
||||
background,
|
||||
@@ -33,6 +33,8 @@ export default function PreviewToolbar({
|
||||
let host = process.env.NEXT_PUBLIC_HOST?.substring("https://".length);
|
||||
const t = useTranslations("PreviewBar");
|
||||
const [aspectRadio, setAspectRadio] = useState<number>(0);
|
||||
const split = Sizes[0].split("x").map(Number);
|
||||
const [size, setSize] = useState<Size>({ width: split[0], height: split[1] });
|
||||
const container = useRef<HTMLCanvasElement>(null);
|
||||
const fullscreenElement = useRef<HTMLImageElement>(null);
|
||||
const [picture, setPicture] = useState<string | null>(null);
|
||||
@@ -42,6 +44,8 @@ export default function PreviewToolbar({
|
||||
|
||||
const updateSize = () => {
|
||||
const split = Sizes[aspectRadio].split("x").map(Number);
|
||||
|
||||
setSize({ width: split[0], height: split[1] });
|
||||
resize(split[0], split[1], container.current!.clientWidth, container.current!.clientHeight);
|
||||
}
|
||||
useEffect(() => {
|
||||
@@ -51,8 +55,7 @@ export default function PreviewToolbar({
|
||||
} else {
|
||||
const box = container.current;
|
||||
const split = Sizes[aspectRadio].split("x").map(Number);
|
||||
const initSuccess = threeInit(box, split[0], split[1]);
|
||||
console.log("three init ", initSuccess);
|
||||
threeInit(box, split[0], split[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,25 +67,17 @@ export default function PreviewToolbar({
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
updateBackground(background);
|
||||
|
||||
console.log("background change", background);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
updateBackground(background);
|
||||
|
||||
}, [background]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
updateTextProps(text);
|
||||
updateTextProps(text);
|
||||
|
||||
console.log("text change", text);
|
||||
|
||||
console.log("text change", text);
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [text]);
|
||||
|
||||
const handleDownload = () => {
|
||||
@@ -179,14 +174,17 @@ export default function PreviewToolbar({
|
||||
<Text>{t("mouseMiddle")}</Text>
|
||||
<Text>{t("mouseRight")}</Text>
|
||||
</Flex>
|
||||
<canvas ref={container} className="w-full border border-gray-300" style={{
|
||||
aspectRatio: AspectRatio[aspectRadio],
|
||||
// backgroundColor: background.type === "color" ? background.color : "none",
|
||||
backgroundImage: (background.type === "image" && background.image) ? `url(${background.image})` : "none",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "contain",
|
||||
backgroundPosition: "center",
|
||||
}} />
|
||||
|
||||
<AspectRatio ratio={AspectRatios[aspectRadio]}>
|
||||
<canvas ref={container} className="w-full border border-gray-300" style={{
|
||||
// backgroundColor: background.type === "color" ? background.color : "none",
|
||||
backgroundImage: (background.type === "image" && background.image) ? `url(${background.image})` : "none",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "contain",
|
||||
backgroundPosition: "center",
|
||||
maxWidth: size.width, maxHeight: size.height
|
||||
}} />
|
||||
</AspectRatio>
|
||||
|
||||
{picture && (
|
||||
<img ref={fullscreenElement} src={picture} className="w-screen h-screen" />
|
||||
@@ -253,7 +251,7 @@ export default function PreviewToolbar({
|
||||
<Select.Root defaultValue={`${aspectRadio}`} onValueChange={(e) => setAspectRadio(parseInt(e))}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{AspectRatio.map((_, i) => <Select.Item key={i} value={i + ""}>{Sizes[i]}</Select.Item>)}
|
||||
{AspectRatios.map((_, i) => <Select.Item key={i} value={i + ""}>{Sizes[i]}</Select.Item>)}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Flex, Heading, Select } from "@radix-ui/themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
'use client'
|
||||
import { Flex, Heading, Select, Tooltip, IconButton, Link } from "@radix-ui/themes";
|
||||
import { PlusIcon, MessageCircleQuestionIcon, CircleQuestionMark, CircleQuestionMarkIcon } from "lucide-react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const FontWeights = ["regular", "bold"];
|
||||
export const FontNames = ["gentilis", "helvetiker", "optimer", "Noto_Sans_SC_zh", "Alibaba_PuHuiTi_3.0_zh"];
|
||||
export const FontWeights = ["Regular", "Bold"];
|
||||
export const FontNames = ["Gentilis", "Helvetiker", "Optimer", "Noto_Sans_SC_zh", "Alibaba_PuHuiTi_3.0_zh"];
|
||||
|
||||
type FontFrom = "local" | "upload";
|
||||
type FontFrom = "online" | "upload";
|
||||
export class TextProp {
|
||||
text: string
|
||||
color: string
|
||||
fontFrom: FontFrom
|
||||
font: string
|
||||
fontUrl: string
|
||||
weight: string
|
||||
|
||||
constructor(
|
||||
@@ -23,6 +27,7 @@ export class TextProp {
|
||||
this.color = color;
|
||||
this.fontFrom = fontFrom;
|
||||
this.font = font;
|
||||
this.fontUrl = getOnlineFontPath(font, weight);
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
@@ -37,28 +42,32 @@ export class TextProp {
|
||||
text,
|
||||
color: "#8e86fe",
|
||||
font,
|
||||
fontUrl: getOnlineFontPath(font, FontWeights[0]),
|
||||
weight: FontWeights[0],
|
||||
fontFrom: "local",
|
||||
fontFrom: "online",
|
||||
}
|
||||
}
|
||||
}
|
||||
function getOnlineFontPath(fontName: string, fontWeight: String) {
|
||||
|
||||
let font = fontName;
|
||||
if (fontName.endsWith("zh")) {
|
||||
font = fontName.slice(0, -3);
|
||||
}
|
||||
return `https://fast3dtest.mysoul.fun/${font}_${fontWeight}.json`;
|
||||
|
||||
}
|
||||
|
||||
function containsChinese(str: string) {
|
||||
return /[\u4e00-\u9fa5]/.test(str);
|
||||
}
|
||||
|
||||
export function getFontPath(fontName: string, fontWeight: String) {
|
||||
if (!fontName.endsWith("zh")) {
|
||||
return `/fonts/${fontName}_${fontWeight}.typeface.json`;
|
||||
} else {
|
||||
fontWeight = fontWeight.charAt(0).toUpperCase() + fontWeight.slice(1);
|
||||
let font = fontName.slice(0, -3);
|
||||
return `https://fast3dtest.mysoul.fun/${font}_${fontWeight}.json`;
|
||||
}
|
||||
export interface UploadFont {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function TextSetting({
|
||||
text,
|
||||
setText,
|
||||
@@ -66,8 +75,35 @@ export default function TextSetting({
|
||||
text: TextProp;
|
||||
setText: (text: TextProp) => void;
|
||||
}) {
|
||||
const locale = useLocale();
|
||||
|
||||
let inited = false;
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
const [uploadFonts, setUploadFonts] = useState<UploadFont[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uploadFonts.length > 0) {
|
||||
handleSelectFont(uploadFonts[uploadFonts.length - 1].name)
|
||||
} else {
|
||||
|
||||
if (inited) {
|
||||
handleSelectFont(FontNames[0])
|
||||
}
|
||||
}
|
||||
|
||||
inited = true;
|
||||
}, [uploadFonts]);
|
||||
|
||||
const handleSelectFont = (font: string) => {
|
||||
if (FontNames.indexOf(font) !== -1) {
|
||||
setText({ ...text, font: font, fontFrom: "online", fontUrl: getOnlineFontPath(font, text.weight) });
|
||||
} else {
|
||||
let f = uploadFonts.find((item) => item.name === font)!;
|
||||
setText({ ...text, font: font, fontFrom: "upload", fontUrl: f.url });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex className="p-4 border rounded-lg " gap={"3"} direction={"column"}>
|
||||
<Heading size={"3"} className="font-medium text-lg" >{t("title")}</Heading>
|
||||
@@ -89,15 +125,69 @@ export default function TextSetting({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontFamily")}
|
||||
</label>
|
||||
<Select.Root defaultValue={`${text.font}`} onValueChange={(e) => setText({ ...text, font: e })}>
|
||||
|
||||
<Flex gap={"2"}>
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontFamily")}
|
||||
</label>
|
||||
<Tooltip content={t("how2UploadFont")} >
|
||||
<Link href={`/${locale}/blogs/Create-3D-Text-with-the-Barbie-Font`}>
|
||||
<IconButton radius="full" variant="ghost" >
|
||||
<CircleQuestionMarkIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<Select.Root value={text.font} onValueChange={(e) => handleSelectFont(e)}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{FontNames.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
{uploadFonts.length > 0 && (
|
||||
<>
|
||||
<Select.Group>
|
||||
<Select.Label>Upload</Select.Label>
|
||||
{uploadFonts.map((f) => <Select.Item key={f.name} value={f.name}>{f.name}</Select.Item>)}
|
||||
</Select.Group>
|
||||
<Select.Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Select.Group>
|
||||
<Select.Label>Online</Select.Label>
|
||||
{FontNames.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
</Select.Group>
|
||||
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
<Tooltip content={t("uploadFontButton")} >
|
||||
<IconButton asChild radius="full" className="ml-4">
|
||||
<label className="cursor-pointer" htmlFor="fontUpload">
|
||||
<input
|
||||
id="fontUpload"
|
||||
type="file"
|
||||
accept=".json"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const fontName = file.name.replace('.json', '');
|
||||
const newFont = {
|
||||
name: fontName,
|
||||
url: URL.createObjectURL(file)
|
||||
};
|
||||
setUploadFonts([...uploadFonts.filter(font => font.name !== fontName), newFont]);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}}
|
||||
/>
|
||||
<PlusIcon />
|
||||
</label>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
@@ -107,7 +197,7 @@ export default function TextSetting({
|
||||
<Select.Root defaultValue={`${text.weight}`} onValueChange={(e) => setText({ ...text, weight: e })}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{FontWeights.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
{FontWeights.map((name) => <Select.Item disabled={text.fontFrom == "upload"} key={name} value={name}>{name}</Select.Item>)}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as THREE from "three";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { Font, FontLoader } from "three/addons/loaders/FontLoader.js";
|
||||
import { FontLoader } from "three/addons/loaders/FontLoader.js";
|
||||
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
|
||||
|
||||
THREE.Cache.enabled = true;
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { TextProp, getFontPath } from "./TextSetting";
|
||||
import { TextProp } from "./TextSetting";
|
||||
|
||||
let camera: THREE.PerspectiveCamera,
|
||||
scene: THREE.Scene,
|
||||
@@ -212,9 +212,7 @@ async function getTextGeometry(textProps: TextProp) {
|
||||
|
||||
async function loadFont(textProps: TextProp) {
|
||||
const loader = new FontLoader();
|
||||
let font = await loader.loadAsync(
|
||||
getFontPath(textProps.font, textProps.weight)
|
||||
);
|
||||
let font = await loader.loadAsync(textProps.fontUrl);
|
||||
return font;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user