From 336e1bc3323250f964abb724f2b2bf1228e67a57 Mon Sep 17 00:00:00 2001 From: xiaoiver Date: Wed, 15 Jan 2025 22:05:46 +0800 Subject: [PATCH] chore: add svg importer example --- README.md | 2 + README.zh_CN.md | 2 + __tests__/unit/serialize.spec.ts | 2 +- packages/core/src/shaders/sdf.ts | 3 +- packages/core/src/utils/serialize.ts | 59 +- packages/site/docs/.vitepress/config/en.js | 12 + packages/site/docs/.vitepress/config/index.js | 35 +- packages/site/docs/.vitepress/config/zh.js | 14 +- packages/site/docs/components/Harfbuzz.vue | 72 +- packages/site/docs/components/ImportSVG.vue | 126 + packages/site/docs/components/Opentype.vue | 37 +- .../site/docs/components/WebFontLoader.vue | 70 + packages/site/docs/example/import-svg.md | 9 + packages/site/docs/example/opentype.md | 24 + packages/site/docs/example/web-font-loader.md | 30 + packages/site/docs/guide/lesson-016.md | 8 +- packages/site/docs/index.md | 4 +- packages/site/docs/public/claude-test.svg | 15 + packages/site/docs/zh/example/harfbuzz.md | 43 + packages/site/docs/zh/example/import-svg.md | 9 + packages/site/docs/zh/example/opentype.md | 24 + .../site/docs/zh/example/web-font-loader.md | 30 + packages/site/docs/zh/guide/lesson-016.md | 35 +- packages/site/docs/zh/guide/lesson-019.md | 8 + packages/site/docs/zh/guide/lesson-021.md | 5 + packages/site/docs/zh/index.md | 4 +- packages/site/package.json | 8 +- pnpm-lock.yaml | 2188 +++++++++++++++-- 28 files changed, 2675 insertions(+), 203 deletions(-) create mode 100644 packages/site/docs/components/ImportSVG.vue create mode 100644 packages/site/docs/components/WebFontLoader.vue create mode 100644 packages/site/docs/example/import-svg.md create mode 100644 packages/site/docs/example/opentype.md create mode 100644 packages/site/docs/example/web-font-loader.md create mode 100644 packages/site/docs/public/claude-test.svg create mode 100644 packages/site/docs/zh/example/import-svg.md create mode 100644 packages/site/docs/zh/example/opentype.md create mode 100644 packages/site/docs/zh/example/web-font-loader.md diff --git a/README.md b/README.md index 1cb3f48..7f5983a 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,8 @@ pnpm run dev Lesson 15 - text Lesson 15 - text +## Lesson 16 - Advanced text features [🔗](https://infinitecanvas.cc/guide/lesson-016) + [infinitecanvas]: https://infinitecanvas.tools/ [Figma]: https://madebyevan.com/figma/building-a-professional-design-tool-on-the-web/ [Modyfi]: https://digest.browsertech.com/archive/browsertech-digest-how-modyfi-is-building-with/ diff --git a/README.zh_CN.md b/README.zh_CN.md index bb9834b..e8a5932 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -188,6 +188,8 @@ pnpm run dev Lesson 15 - text Lesson 15 - text +## 课程 16 - 文本的高级特性 [🔗](https://infinitecanvas.cc/zh/guide/lesson-016) + [infinitecanvas]: https://infinitecanvas.tools/ [Figma]: https://madebyevan.com/figma/building-a-professional-design-tool-on-the-web/ [Modyfi]: https://digest.browsertech.com/archive/browsertech-digest-how-modyfi-is-building-with/ diff --git a/__tests__/unit/serialize.spec.ts b/__tests__/unit/serialize.spec.ts index 0459a8f..c8f4e99 100644 --- a/__tests__/unit/serialize.spec.ts +++ b/__tests__/unit/serialize.spec.ts @@ -293,7 +293,7 @@ describe('Serialize', () => { children: [], uid: 4, attributes: { - batchable: true, + batchable: false, cullable: true, fill: '#F67676', fillOpacity: 1, diff --git a/packages/core/src/shaders/sdf.ts b/packages/core/src/shaders/sdf.ts index 871cd23..7909e07 100644 --- a/packages/core/src/shaders/sdf.ts +++ b/packages/core/src/shaders/sdf.ts @@ -173,7 +173,8 @@ layout(std140) uniform SceneUniforms { #else layout(std140) uniform ShapeUniforms { mat3 u_ModelMatrix; - vec4 u_PositionSize; + vec4 u_Position; + vec4 u_Size; vec4 u_FillColor; vec4 u_StrokeColor; vec4 u_ZIndexStrokeWidth; diff --git a/packages/core/src/utils/serialize.ts b/packages/core/src/utils/serialize.ts index a0123f2..5e396d5 100644 --- a/packages/core/src/utils/serialize.ts +++ b/packages/core/src/utils/serialize.ts @@ -319,6 +319,8 @@ export async function deserializeNode(data: SerializedNode) { shape.transform.skew.set(skew.x, skew.y); shape.transform.rotation = rotation; shape.transform.pivot.set(pivot.x, pivot.y); + + console.log(shape); } if (children && children.length > 0) { @@ -845,35 +847,78 @@ export function toSVGElement(node: SerializedNode, doc?: Document) { * @see https://github.com/ShukantPal/pixi-essentials/blob/master/packages/svg */ export function fromSVGElement(element: SVGElement, uid = 0): SerializedNode { - const type = element.tagName.toLowerCase() as SerializedNode['type']; + let type = element.tagName.toLowerCase(); + const attributes = Array.from(element.attributes).reduce((prev, attr) => { - const attributeName = kebabToCamelCase(attr.name); + let attributeName = kebabToCamelCase(attr.name); let value: string | number | SerializedTransform = attr.value; if (attributeName === 'transform') { value = parseTransform(value); } else if ( + type === 'rect' && + (attributeName === 'rx' || attributeName === 'ry') + ) { + attributeName = 'radius'; + value = Number(value); + } else if ( + attributeName === 'cx' || + attributeName === 'cy' || + attributeName === 'x' || + attributeName === 'y' || + attributeName === 'rx' || + attributeName === 'ry' || + attributeName === 'r' || + attributeName === 'width' || + attributeName === 'height' || attributeName === 'opacity' || attributeName === 'fillOpacity' || attributeName === 'strokeOpacity' || attributeName === 'strokeWidth' || attributeName === 'strokeMiterlimit' || - attributeName === 'strokeDashoffset' + attributeName === 'strokeDashoffset' || + attributeName === 'fontSize' ) { value = Number(value); + } else if (attributeName === 'textAnchor') { + attributeName = 'textAlign'; + if (value === 'middle') { + value = 'center'; + } } prev[attributeName] = value; return prev; }, {} as SerializedNode['attributes']); - const children = Array.from(element.children).map((e: SVGElement) => - fromSVGElement(e, uid++), - ); + if (type === 'text') { + attributes.content = element.textContent; + } else if (type === 'line') { + type = 'polyline'; + // @ts-ignore + attributes.points = `${attributes.x1},${attributes.y1} ${attributes.x2},${attributes.y2}`; + // @ts-ignore + delete attributes.x1; + // @ts-ignore + delete attributes.y1; + // @ts-ignore + delete attributes.x2; + // @ts-ignore + delete attributes.y2; + } + + const children = Array.from(element.children) + .map((e: SVGElement) => { + if (e.tagName.toLowerCase() === 'tspan') { + return; + } + return fromSVGElement(e, uid++); + }) + .filter(Boolean); return { uid, - type, + type: type as SerializedNode['type'], attributes, children, }; diff --git a/packages/site/docs/.vitepress/config/en.js b/packages/site/docs/.vitepress/config/en.js index da0dcba..b771707 100644 --- a/packages/site/docs/.vitepress/config/en.js +++ b/packages/site/docs/.vitepress/config/en.js @@ -129,6 +129,10 @@ export const en = defineConfig({ text: 'Export canvas content as image', link: 'exporter', }, + { + text: 'Import SVG', + link: 'import-svg', + }, { text: 'Wikipedia Datamap', link: 'wikipedia-datamap', @@ -165,6 +169,14 @@ export const en = defineConfig({ text: 'Shaping with HarfBuzz', link: 'harfbuzz', }, + { + text: 'Shaping with Opentype.js', + link: 'opentype', + }, + { + text: 'Load web font', + link: 'web-font-loader', + }, ], }, ], diff --git a/packages/site/docs/.vitepress/config/index.js b/packages/site/docs/.vitepress/config/index.js index d578cfc..60ebf68 100644 --- a/packages/site/docs/.vitepress/config/index.js +++ b/packages/site/docs/.vitepress/config/index.js @@ -1,4 +1,6 @@ import { defineConfig } from 'vitepress'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import VueMacros from 'unplugin-vue-macros/vite'; import { genjiAttrs } from 'genji-theme-vitepress/config'; import config from 'genji-theme-vitepress/config'; import implicitFigures from 'markdown-it-implicit-figures'; @@ -16,7 +18,7 @@ export default defineConfig({ }); md.use(genjiAttrs); }, - math: true + math: true, }, cleanUrls: true, extends: config, @@ -41,17 +43,24 @@ export default defineConfig({ chunkSizeWarningLimit: 800, }, ssr: { - noExternal: ["@antv/g-device-api"] + noExternal: ['@antv/g-device-api', 'ant-design-vue'], }, - plugins: [RssPlugin({ - title: 'An infinite canvas tutorial', - baseUrl: 'https://infinitecanvas.cc', - copyright: 'Copyright (c) 2024-present xiaoiver', - author: { - name: 'xiaoiver', - email: 'pyqiverson@gmail.com', - link: 'https://github.com/xiaoiver' - }, - })] - } + plugins: [ + VueMacros({ + plugins: { + vueJsx: vueJsx(), + }, + }), + RssPlugin({ + title: 'An infinite canvas tutorial', + baseUrl: 'https://infinitecanvas.cc', + copyright: 'Copyright (c) 2024-present xiaoiver', + author: { + name: 'xiaoiver', + email: 'pyqiverson@gmail.com', + link: 'https://github.com/xiaoiver', + }, + }), + ], + }, }); diff --git a/packages/site/docs/.vitepress/config/zh.js b/packages/site/docs/.vitepress/config/zh.js index b90e88f..816def3 100644 --- a/packages/site/docs/.vitepress/config/zh.js +++ b/packages/site/docs/.vitepress/config/zh.js @@ -111,6 +111,10 @@ export const zh = defineConfig({ text: '将画布内容导出成图片', link: 'exporter', }, + { + text: '导入 SVG', + link: 'import-svg', + }, { text: 'wikipedia 数据集可视化', link: 'wikipedia-datamap', @@ -144,9 +148,17 @@ export const zh = defineConfig({ link: 'bidi', }, { - text: '使用 HarfBuzz', + text: '使用 HarfBuzz 进行 Shaping', link: 'harfbuzz', }, + { + text: '使用 Opentype.js 进行 Shaping', + link: 'opentype', + }, + { + text: '加载 Web 字体', + link: 'web-font-loader', + }, ], }, ], diff --git a/packages/site/docs/components/Harfbuzz.vue b/packages/site/docs/components/Harfbuzz.vue index cc7ff86..4f56c90 100644 --- a/packages/site/docs/components/Harfbuzz.vue +++ b/packages/site/docs/components/Harfbuzz.vue @@ -1,12 +1,17 @@