Skip to content

Commit

Permalink
feat: add font loading demo to playground
Browse files Browse the repository at this point in the history
- Support for woff2 and OpenType fonts.
- Supports switching fonts and examples by select.
  • Loading branch information
yisibl committed Sep 25, 2023
1 parent 2c748b8 commit 27d5a1a
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 50 deletions.
Binary file added example/Pacifico-Regular.woff2
Binary file not shown.
Binary file removed example/SourceHanSerifSC-Regular.otf
Binary file not shown.
Binary file removed example/SourceHanSerifSC-Regular.woff2
Binary file not shown.
Binary file added wasm/fonts/Pacifico-Regular.woff2
Binary file not shown.
291 changes: 241 additions & 50 deletions wasm/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
.opts-cell label {
margin-right: .5em;
}

#slt-svg,
#slt-font {
max-width: 180px;
}

.github {
fill: #151513;
color: #fff;
Expand All @@ -155,29 +161,107 @@
animation: octocat-wave 560ms ease-in-out;
}
</style>
<script>
const fontList = [{
name: '思源宋体 Light',
fontFile: '../example/SourceHanSerifCN-Light-subset.ttf'
},
{
name: 'Pacifico Regular',
fontFile: './fonts/Pacifico-Regular.woff2'
}]

const svgList = [{
name: '带文本',
svgString: `<svg width="600" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="0%" y1="45%" x2="100%" y2="55%" id="b">
<stop stop-color="#FF8253" offset="0%"/>
<stop stop-color="#DA1BC6" offset="100%"/>
</linearGradient>
<path id="a" d="M0 0h600v300H0z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<use fill="url(#b)" xlink:href="#a"/>
<text x="50%" y="36%" font-size="52" fill="#FFF" dominant-baseline="middle" text-anchor="middle">竹外桃花三两枝</text>
<text x="50%" y="60%" font-size="52" fill="#FFF" dominant-baseline="middle" text-anchor="middle">Hello resvg-js</text>
</g>
</svg>`
},
{
name: '没有文本',
svgString: `<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<svg id="svg1">
<rect width="50%" height="50%" fill="green" />
</svg>
</defs>
<use id="use1" x="0" y="0" xlink:href="#svg1" />
<use id="use2" x="50%" y="50%" xlink:href="#svg1" />
</svg>
`
}]

const defaultSvgNumber = 1
const defaultFontNumber = 0
const INIT_INPUT_SVG_STRING = svgList[defaultSvgNumber].svgString
// font请求的锁
let fontLock = false
//全局当前的字体文件
let fontFile = fontList[0].fontFile
let SVG_STRING = ''
async function getFontBuffer() {
if (fontLock) {
return
}
fontLock = true
const controller = new AbortController()
const signal = controller.signal
setTimeout(() => controller.abort(), 3000) // Cancel the fetch request

try {
const t = performance.now()
console.log('fontSource:' + fontFile)
const font = await fetch(fontFile)
// const font = await fetch(fontFile || fontList[0].fontFile, { signal })

if (!font.ok) return

const data = await font.arrayBuffer()
const buffer = new Uint8Array(data)
fontLock = false
console.info('✨ 字体请求用时:', performance.now() - t + 'ms')
return {
buffer,
family_name: 'Source Han Serif CN Light',
}
} catch (error) {
return console.error('fetch font error', error)
}
}

<script>
(async function () {
await resvg.initWasm(fetch('./index_bg.wasm'))
initSelectOptions('slt-svg', svgList, defaultSvgNumber)
initSelectOptions('slt-font', fontList, defaultFontNumber)
const output = document.getElementById('output')
const svgElement = document.querySelector('#input-svg')
const inputSVG = svgElement.value.trim()

const font = await fetch('../example/SourceHanSerifSC-Regular.woff2')
const buffer = new Uint8Array(await font.arrayBuffer())
console.log('font buffer size', buffer.length)

let resvgOpts = {
font: {
fontsBuffers: [buffer],
// OpenType 中定义的 Font Family name,也就是 nameID=1
defaultFontFamily: 'Source Han Serif SC',
},
const svgInputElement = document.querySelector('#input-svg') // 在 textarea 中输入 SVG 字符串
svgInputElement.value = INIT_INPUT_SVG_STRING

// 把 INIT_INPUT_SVG_STRING 变成 dom 元素
const inputSvgElement = new DOMParser().parseFromString(INIT_INPUT_SVG_STRING, 'image/svg+xml').documentElement
let resvgOpts = { font: {} }

if (checkHasText()) {
const { buffer, family_name } = await getFontBuffer()
resvgOpts.font.fontBuffers = [buffer]
fontList[0].buffer = buffer
console.log("初始化:" + resvgOpts.font.fontBuffers[0].length)
}
await svg2png(inputSVG, resvgOpts)
await svg2png(INIT_INPUT_SVG_STRING, resvgOpts)

async function svg2png(svgString, opts, hasCrop) {
const svg = svgString ? svgString : svgElement.value.trim()
const svg = svgString ? svgString : svgInputElement.value.trim()

if (!svg) {
alert('SVG is empty')
Expand Down Expand Up @@ -207,8 +291,7 @@

const innerBBox = resvgJS.innerBBox()
const bbox = resvgJS.getBBox()
console.log('SVG innerBBox', innerBBox)
console.log('SVG getBBox', bbox)

if (hasCrop && bbox) resvgJS.cropByBBox(bbox)
const pngData = resvgJS.render()
const pngBuffer = pngData.asPng()
Expand All @@ -220,11 +303,22 @@
document.querySelector('#png-info').textContent = 'PNG size: ' + pngData.width + ' x ' + pngData.height + ' px'
}

addMultipleEventListener(svgElement, ['change', 'keyup', 'paste'], function(event) {
const svgString = event.target.value.trim()
addMultipleEventListener(svgInputElement, ['change', 'keyup', 'paste'], async function (event) {
SVG_STRING = event.target.value.trim()
const hasCrop = cropElement.checked
if (!svgString) return
svg2png(svgString, resvgOpts, hasCrop)
if (!SVG_STRING) return

// 通过 svgString 更新 inputSvgElement
const inputSvgElement = new DOMParser().parseFromString(SVG_STRING, 'image/svg+xml').documentElement
const errorNode = inputSvgElement.querySelector('parsererror')
if (errorNode) {
const errorText = errorNode.querySelector('div').textContent
console.error('Invalid SVG string: \n' + errorText)
return
}

await checkGetFontBuffer()
svg2png(SVG_STRING, resvgOpts, hasCrop)
})

function addMultipleEventListener(element, events, handler) {
Expand All @@ -234,61 +328,172 @@
const colorPickerElement = document.querySelector('#color-picker')
const svgSizeElement = document.querySelector('#svg-size')
const cropElement = document.querySelector('#crop-by-bbox')
const svgSelectElement = document.querySelector('#slt-svg')
const fontSelectElement = document.querySelector('#slt-font')

colorPickerElement.addEventListener('change', function(event) {

svgSelectElement.addEventListener('change', function (event) {
const value = event.target.value
const hasCrop = cropElement.checked
if (!value) return

for (const svg of svgList) {
if (svg.name == value) {
svgInputElement.value = SVG_STRING = svg.svgString
const event = new Event('change')
svgInputElement.dispatchEvent(event)
}
}
})

fontSelectElement.addEventListener('change', async function (event) {
const value = event.target.value
if (!value) return

for (const font of fontList) {
if (font.name == value) {
fontFile = font.fontFile
if (!font.buffer) {
const { buffer } = await getFontBuffer()
font.buffer = buffer
}

resvgOpts = {
font: {
fontBuffers: [font.buffer],
// OpenType 中定义的 Font Family Name,也就是 nameID=1
// defaultFontFamily: family_name,
// defaultFontFamily: 'Smiley Sans Oblique',
},
}
console.log(resvgOpts.font.fontBuffers[0].length)
await svg2png(SVG_STRING, resvgOpts)
}
}
})

colorPickerElement.addEventListener('change', function (event) {
const value = event.target.value
const hasCrop = cropElement.checked
if (!value) return
resvgOpts.background = value
svg2png(null, resvgOpts, hasCrop)
})

svgSizeElement.addEventListener('change', function(event) {
svgSizeElement.addEventListener('change', function (event) {
const value = event.target.value
const hasCrop = cropElement.checked
if (!value) {
Object.assign(resvgOpts, { fitTo: {
mode: 'original',
}})
Object.assign(resvgOpts, {
fitTo: {
mode: 'original',
}
})
} else {
const limitWidth = 3000
const title = `The width(${limitWidth}) is larger and generating images will be slower.\nAre you sure to continue?`
if (value >= limitWidth && !confirm(title)) {
return
}

Object.assign(resvgOpts, { fitTo: {
mode: 'width',
value: parseInt(value, 10),
}})
Object.assign(resvgOpts, {
fitTo: {
mode: 'width',
value: parseInt(value, 10),
}
})
}

svg2png(null, resvgOpts, hasCrop)
})

cropElement.addEventListener('change', function(event) {
cropElement.addEventListener('change', function (event) {
const hasCrop = event.target.checked
svg2png(null, resvgOpts, hasCrop)
})

async function checkGetFontBuffer() {
if (checkHasText() && !checkHasBuffer().checkResult) {
const font = await getFontBuffer()
if (!font?.buffer) {
return
}
const fontIndex = checkHasBuffer().index
const { buffer } = font
resvgOpts.font.fontBuffers = [buffer]
fontList[fontIndex].buffer = buffer
console.log("fontIndex:" + fontIndex + " buffer:" + resvgOpts.font.fontBuffers[0].length)
}
}

})()

function initSelectOptions(sltId, dataList, defaultVal) {
const selectElement = document.querySelector('#' + sltId)
let optionText = ''
for (const [i, item] of dataList.entries()) {
const optionElement = document.createElement('option')
optionElement.value = item.name
optionElement.innerText = item.name
if (i === defaultVal) {
optionElement.setAttribute('selected', true)
}
selectElement.appendChild(optionElement)
}
}

function checkHasText() {
const svgString = document.querySelector('#input-svg').value.trim()
const inputSvgElement = new DOMParser().parseFromString(svgString, 'image/svg+xml').documentElement
const allTextElement = inputSvgElement.querySelectorAll('text')
return allTextElement.length > 0
}

function checkHasBuffer() {
const fontName = document.querySelector('#slt-font').value
let currentFontIndex = -1
for (const [i, font] of fontList.entries()) {
if (font.name === fontName) {
currentFontIndex = i
if (font.buffer) {
return { checkResult: true, index: i }
}
}

}
return { checkResult: false, index: currentFontIndex }
}
</script>
</head>

<body>
<main>
<header>
<h1>resvg-js playground</h1>
<a href="https://github.com/yisibl/resvg-js" title="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" class="github">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body">
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body">
</path>
</svg>
</a>
</header>
<div class="opts">
<div class="opts-cell">
<label for="slt-svg">Select SVG:</label>
<select name="svg" id="slt-svg">
<!-- <option value="svg1">SVG1</option> -->
</select>
</div>
<div class="opts-cell">
<label for="slt-font">Select Font:</label>
<select name="svg" id="slt-font">
</select>
</div>
<div class="opts-cell">
<label for="color-picker">Change background:</label>
<input type="color" name="color-picker" id="color-picker">
Expand All @@ -307,21 +512,7 @@ <h1>resvg-js playground</h1>
<div class="header-info">
<div class="info" id="svg-info"></div>
</div>
<textarea name="input-svg" id="input-svg" placeholder="Enter the svg string" cols="30" rows="10"><svg width="600" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="0%" y1="45%" x2="100%" y2="55%" id="b">
<stop stop-color="#FF8253" offset="0%"/>
<stop stop-color="#DA1BC6" offset="100%"/>
</linearGradient>
<path id="a" d="M0 0h600v300H0z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<use fill="#D8D8D8" xlink:href="#a"/>
<use fill="url(#b)" xlink:href="#a"/>
<text x="50%" y="50%" font-size="60" fill="#FFF" dominant-baseline="middle" text-anchor="middle">思源宋体</text>
</g>
</svg>
</textarea>
<textarea name="input-svg" id="input-svg" placeholder="Enter the svg string" cols="30" rows="10"></textarea>
</div>
<div class="cell" id="output">
<div class="header-info">
Expand Down
Binary file modified wasm/index_bg.wasm
Binary file not shown.

0 comments on commit 27d5a1a

Please sign in to comment.