Skip to content

Commit

Permalink
chore: add svg importer example
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Jan 15, 2025
1 parent f2cce61 commit 336e1bc
Show file tree
Hide file tree
Showing 28 changed files with 2,675 additions and 203 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ pnpm run dev
<img src="./screenshots/lesson15.png" width="300" alt="Lesson 15 - text">
<img src="./screenshots/lesson15-2.png" width="300" alt="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/
Expand Down
2 changes: 2 additions & 0 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ pnpm run dev
<img src="./screenshots/lesson15.png" width="300" alt="Lesson 15 - text">
<img src="./screenshots/lesson15-2.png" width="300" alt="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/
Expand Down
2 changes: 1 addition & 1 deletion __tests__/unit/serialize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ describe('Serialize', () => {
children: [],
uid: 4,
attributes: {
batchable: true,
batchable: false,
cullable: true,
fill: '#F67676',
fillOpacity: 1,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/shaders/sdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 52 additions & 7 deletions packages/core/src/utils/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
};
Expand Down
12 changes: 12 additions & 0 deletions packages/site/docs/.vitepress/config/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
},
],
},
],
Expand Down
35 changes: 22 additions & 13 deletions packages/site/docs/.vitepress/config/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,7 +18,7 @@ export default defineConfig({
});
md.use(genjiAttrs);
},
math: true
math: true,
},
cleanUrls: true,
extends: config,
Expand All @@ -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: '[email protected]',
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: '[email protected]',
link: 'https://github.com/xiaoiver',
},
}),
],
},
});
14 changes: 13 additions & 1 deletion packages/site/docs/.vitepress/config/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ export const zh = defineConfig({
text: '将画布内容导出成图片',
link: 'exporter',
},
{
text: '导入 SVG',
link: 'import-svg',
},
{
text: 'wikipedia 数据集可视化',
link: 'wikipedia-datamap',
Expand Down Expand Up @@ -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',
},
],
},
],
Expand Down
72 changes: 56 additions & 16 deletions packages/site/docs/components/Harfbuzz.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
<script setup>
import { Text } from '@infinite-canvas-tutorial/core';
import { Path } from '@infinite-canvas-tutorial/core';
import '@infinite-canvas-tutorial/ui';
import { ref, onMounted } from 'vue';
import { ref, onMounted, onUnmounted } from 'vue';
import Stats from 'stats.js';
import init from "harfbuzzjs/hb.wasm?init";
import hbjs, { HBBlob, HBFace, HBFont, HBHandle } from "harfbuzzjs/hbjs.js";
import hbjs from "harfbuzzjs/hbjs.js";
let canvas;
let hb;
let blob;
let face;
let font;
let buffer;
const stats = new Stats();
stats.showPanel(0);
Expand All @@ -28,30 +33,65 @@ onMounted(() => {
canvas = e.detail;
const instance = await init();
const hb = hbjs(instance);
hb = hbjs(instance);
const data = await (await window.fetch('/fonts/NotoSans-Regular.ttf')).arrayBuffer();
const blob = hb.createBlob(data);
const face = hb.createFace(blob, 0);
const font = hb.createFont(face);
blob = hb.createBlob(data);
face = hb.createFace(blob, 0);
font = hb.createFont(face);
font.setScale(32, 32);
font.setVariations({ wdth: 200, wght: 700 });
console.log(font);
buffer = hb.createBuffer();
buffer.addText('H');
buffer.guessSegmentProperties();
// TODO: use BiDi
// buffer.setDirection(segment.direction);
// const text = new Text({
// x: 50,
// y: 100,
// content: 'Hello, world! \n🌹🌍🌞🌛',
// fontSize: 30,
// fill: '#F67676',
// });
// canvas.appendChild(text);
hb.shape(font, buffer);
const result = buffer.json(font);
buffer.destroy();
const base = { x: 0, y: 0 };
const glyphs = new Array();
for (const glyph of result) {
glyphs.push({
id: glyph.g,
base: { x: base.x + glyph.dx, y: base.y + glyph.dy },
});
base.x += glyph.ax;
base.y += glyph.ay;
}
const bounds = { width: base.x, height: face.upem };
window.console.log(glyphs, bounds);
result.forEach(function (x) {
const d = font.glyphToPath(x.g);
const path = new Path({
d,
fill: '#F67676',
});
canvas.appendChild(path);
path.position.x = 100;
path.position.y = 100;
});
});
$canvas.addEventListener('ic-frame', (e) => {
stats.update();
});
});
onUnmounted(() => {
blob?.destroy();
face?.destroy();
font?.destroy();
buffer?.destroy();
});
</script>
<template>
Expand Down
Loading

0 comments on commit 336e1bc

Please sign in to comment.