0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KonvaでCanvasにルビをふった文字を描画したメモ

Posted at

概要

ルビのある文言をcanvas要素に表示したかった。
KonvaではTextを使うと、widthをみて自動的に改行をしてくれるなどの機能はあるが、ルビをふる機能はない。

ライブラリを探したところ、Canvas Text Template v1.1が出てきたが、これはやりたいことに対してリッチすぎた。

konvajs.org - Rich_Text.htmlを参考に、html2canvasを使用することで、ちょっと強引だが解決できた。

実装

ソースコード


const Preview: React.FC<{
  form: UseFormReturn<ActionCardEditFormData>
}> = ({ form }) => {
  const card = form.getValues()
  const ref = useRef<HTMLDivElement>(null)
  const [url, setUrl] = useState('')
  const [image] = useImage(url)
  const watchFlavor = form.watch('flavor')
  useEffect(() => {
    ;(async () => {
      if (ref.current == null) return
      // そのままだとぼやけるので、2倍に出力して0.5に縮小する
      const canvas = await html2canvas(ref.current, { width: 210, scale: 2 })
      setUrl(canvas.toDataURL('img/png'))
    })()
  }, [watchFlavor])

  return (
    <div>
      <div style={{ width: '1px', height: '1px', overflow: 'hidden' }}>
        <div
          style={{
            whiteSpace: 'pre-line',
            width: '210px',
            backgroundColor: 'white',
            color: 'black',
            fontSize: '14px',
            fontFamily: family.serif,
          }}
          ref={ref}
          dangerouslySetInnerHTML={{
            __html: textToIncludeRubyTagsTextSnitized(watchFlavor),
          }}
        ></div>
      </div>
      <BaseCard>
        <CardType text="アクション" />
        <CardName name={card.name} ruby={card.nameRuby} />
        <Image x={30} y={50} image={image} scaleX={0.5} scaleY={0.5} />
        <RightBottom value="icon: Material Design icons" />
      </BaseCard>
    </div>
  )
}
useImage
import React from 'react'
type Status = 'loading' | 'loaded' | 'failed'
export const useImage = (
  url: string,
  crossOrigin?: 'anonymous' | 'use-credentials',
) => {
  const statusRef = React.useRef<Status>('loading')
  const imageRef = React.useRef<HTMLImageElement>()

  const [_, setStateToken] = React.useState(0)

  const oldUrl = React.useRef<string>()
  const oldCrossOrigin = React.useRef<string>()
  if (oldUrl.current !== url || oldCrossOrigin.current !== crossOrigin) {
    statusRef.current = 'loading'
    imageRef.current = undefined
    oldUrl.current = url
    oldCrossOrigin.current = crossOrigin
  }

  React.useLayoutEffect(
    function () {
      if (!url) return
      const img = document.createElement('img')

      function onload() {
        statusRef.current = 'loaded'
        imageRef.current = img
        setStateToken(Math.random())
      }

      function onerror() {
        statusRef.current = 'failed'
        imageRef.current = undefined
        setStateToken(Math.random())
      }

      img.addEventListener('load', onload)
      img.addEventListener('error', onerror)
      crossOrigin && (img.crossOrigin = crossOrigin)
      img.src = url

      return function cleanup() {
        img.removeEventListener('load', onload)
        img.removeEventListener('error', onerror)
      }
    },
    [url, crossOrigin],
  )
  return [imageRef.current, statusRef.current] as const
}
textToRubyTag

import DOMPurify from 'dompurify'
const textToRubyTag = (text: string) =>
  text
    /* 半角または全角の縦棒以降の文字をベーステキスト、括弧内の文字をルビテキスト。 */
    .replace(/[||](.+?)(.+?))/g, '<ruby>$1<rt>$2</rt></ruby>')
    .replace(/[||](.+?)\((.+?)\)/g, '<ruby>$1<rt>$2</rt></ruby>')
    .replace(/[||](.+?))/g, '($1)')


export const textToIncludeRubyTagsTextSnitized = (text = '') =>
  DOMPurify.sanitize(textToRubyTag(text))
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?