PerfectVector
著者: Irene Kim

SatoriのSVGロゴ:ブランドカード内のアートワークを保つ

明示的な寸法と自己完結した画像データで、SVGロゴをSatoriのカードに埋め込みます。入れ子のアートワークを確認し、納品用PNGを別に描画します。

2 分で読める
目次

SatoriのカードにSVGロゴを置くには、画像ソースとして渡し、画像の幅と高さを設定して、生成したカードと一緒に元のSVGを残します。以下の例では、base64のSVGはSatoriの出力内で入れ子の画像になります。外側のカードに、新たな編集可能なロゴのパス群として組み込まれるわけではありません。

Satoriは、限られたHTMLとCSSからSVGを作ります。公式の画像ガイドでは、後段のレンダラーでPNGを作る場合、寸法の明示と自己完結した画像データを推奨しています。そのため、この作業には、構成したSVGカードとラスターの納品用コピーという二つの出力があります。

役割が異なる三つのファイルを残す

ファイル含まれるものそこで変更するもの
元のロゴSVGエンブレムの形状と開口部形、色、元データの範囲
SatoriカードSVGカードのレイアウト、文字、埋め込みロゴ画像アートワークの枠、配置、カードの内容
描画したPNG納品するカードのピクセル元データの変更後に再生成する

MDNのimageリファレンスが説明するように、SVGの<image>は別のSVGを参照できます。そのため、画像要素があるだけで、埋め込みアートワークがラスターとは限りません。より広いSVGのラスター画像チェックリストでカードを診断する前に、データの種類とデコードした内容を確認してください。

マスターをカードと一緒に保管する
中空ひし形のマスターと、シンプルなブランドカードに異なるサイズで置いたコピーの概念図
模式図:編集可能なアートワークをカードの構成とは別に保管します。以下の元データと出力の確認は、この生成図ではなく、独立して作成したSatoriの例によるものです。

完全なローカルの例

このひな形は、Satori 0.35.0の標準のSVG出力インポートとresvg-js 2.6.2を使います。空の作業フォルダーに、バージョンを固定したパッケージをインストールします。

npm install --save-exact satori@0.35.0 @resvg/resvg-js@2.6.2

fontsフォルダーを作り、Google FontsのAbelディレクトリからAbel-Regular.ttfとOFL.txtをダウンロードします。AbelのライセンスはSIL Open Font License 1.1です。この例と一緒にフォントを配布する場合は、付属の告知とライセンスを残してください。

以下をstarter.mjsとして保存し、node starter.mjsを実行します。Satoriにフォントを渡し、中空のひし形を作り、80×80と160×160のアートワーク枠を持つ二つの600×320カードを生成します。ファイルURLによって、フォントと出力のパスをスクリプトからの相対位置に保っています。

import { readFile, writeFile } from 'node:fs/promises';
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
 
const logo = `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><path d="M64 8 L120 64 L64 120 L8 64 Z M64 42 L86 64 L64 86 L42 64 Z" fill="#176b5b" fill-rule="evenodd"/></svg>`;
const src = `data:image/svg+xml;base64,${Buffer.from(logo).toString('base64')}`;
const font = await readFile(new URL('./fonts/Abel-Regular.ttf', import.meta.url));
await writeFile(new URL('./logo-master.svg', import.meta.url), logo);
 
for (const size of [80, 160]) {
  const svg = await satori({
    type: 'div',
    props: {
      style: { display: 'flex', width: 600, height: 320,
        backgroundColor: '#f6f2e8', color: '#152e29', fontFamily: 'Abel' },
      children: [
        { type: 'img', props: { src, width: size, height: size,
          style: { position: 'absolute', left: 24, top: 64 } } },
        { type: 'div', props: {
          style: { position: 'absolute', left: 220, top: 112, fontSize: 42 },
          children: 'CEDAR FIELD' } },
        { type: 'div', props: {
          style: { position: 'absolute', left: 220, top: 168, fontSize: 20 },
          children: 'A study in simple shapes' } },
      ],
    },
  }, {
    width: 600, height: 320,
    fonts: [{ name: 'Abel', data: font, weight: 400, style: 'normal' }],
  });
  const png = new Resvg(svg, { font: { loadSystemFonts: false } }).render().asPng();
  await writeFile(new URL(`./card-${size}.svg`, import.meta.url), svg);
  await writeFile(new URL(`./card-${size}.png`, import.meta.url), png);
}

Buffer.from(logo).toString('base64')は、NodeのBufferエンコードAPIを使い、自作SVGのUTF-8バイトをエンコードします。data:image/svg+xml;base64,の接頭辞は内容を示します。変わるのは転送用の表現で、ロゴの形状ではありません。

Satoriの呼び出しはカードSVGを返します。次の行で、そのSVGを別のresvg-js PNGレンダラーに渡します。ひな形にはリモート画像URLがなく、描画前にローカルフォントを読みます。リモート取得やデプロイ時の自分の動作は、別にテストしてください。

実際の二つの呼び出しが生成したもの

固定したパッケージでこのひな形をそのまま実行し、両方の出力SVGを確認して、各SVGとPNGをブラウザーで開きました。四つすべてのカードファイルは600×320で読み込まれました。エンブレムの枠が2倍になっても、文字は同じ位置に残りました。

各カードには、外側の<image>が一つと、外側の<path>要素が二つありました。二つのパスは文字を表し、ひし形の外周と内周ではありませんでした。画像のbase64データをデコードすると、元のSVGの正確なバイトが復元されました。一つのパス、画像要素ゼロ、同じ中空ひし形の定義です。この実行では、カードに生きた<text>要素はありませんでした。

確認80×80のアートワーク枠160×160のアートワーク枠
カード寸法600×320600×320
外側の画像の配置x24, y64x24, y64
埋め込みSVG元の128×128マスター同じマスターのバイト
正確なティール単色のPNG範囲68×68ピクセル138×138ピクセル
開口部中央のカードピクセルクリーム色、アルファ255クリーム色、アルファ255

塗り範囲の行は、エンブレムの正確なティール単色と一致するピクセルを数えています。アンチエイリアスされた縁は含みません。画像の長方形の寸法ではありません。元のひし形は元座標8から120までなので、この枠内での数学的な広がりは70または140単位になります。計測した単色範囲は、それより少し小さくなりました。

元のエンブレムの中央は透明で、単独で描画するとアルファは0でした。各最終カードPNGでは、その開口部は不透明なクリーム色の背景を示しました。RGBAは246,242,232,255です。見える開口部と、最終カードの透明ピクセルは、異なる要件です。納品用の背景を透明にする必要があるなら、カード背景を変え、結果のアルファを別に確認してください。

これは、一つの独自エンブレムを二つのサイズで使った二回の呼び出しです。すべてのSVG機能が使えること、繰り返し行うリモート画像処理が解決されていること、すべての利用先でカードが同じに描画されることを示すものではありません。

元データを失わずにアートワークの枠を変える

自分のロゴには、logo文字列を自己完結したSVGに置き換えるか、エンコード前にマスターファイルを読み込んでください。まずviewBox、幅、高さ、開口部を確認します。その範囲内に余計な余白があると、見えるロゴが割り当てたカードの枠より小さくなる場合があります。

意図した縦横比に合うよう、画像の両方の寸法を設定してください。この元画像は正方形で、二つの枠も正方形です。出力画像はpreserveAspectRatio="none"を使いましたが、ここでは縦横比の変化はありませんでした。このテストファイルは、長方形のロゴを正方形の枠に押し込む場合をテストしていません。レイアウトの変数を一つずつ変え、実際のSVGと納品PNGを確認します。

文字を独立して変えたい場合は、埋め込みロゴの外に置いてください。この例のカードテキストは納品SVG内でパスになりますが、単語はスクリプト内で編集できます。フォント入力、アートワーク入力、構成した出力には異なる役割があります。resvgのフォント解説では、生きたSVGテキストを描画する別の手順を扱っています。

SatoriのHTMLとCSSの文書には、対応する部分集合と制限が記載されています。既存ページのブラウザースクリーンショットは、このレイアウトエンジンが完全に再現すると約束するものではありません。小さなカードと通常のsatoriインポートから始め、デザインに実際に必要なプロパティを加えてください。

ピクセルから始まるロゴを準備する

ロゴがPNGやJPGでしかなく、編集可能な輪郭が必要な場合は、PerfectVectorでSVGロゴを準備し、シルエットと内側の開口部を確認して、きれいなSVGをマスターとして残します。その後、カードに埋め込み、アートワークの両方のサイズと最終PNGを確認してください。ベクター化はアートワークを準備し、Satoriはカードの構成を担当します。きれいなSVGがすでにあれば、直接埋め込みの段階に進めます。ベクター化ガイドは、元データと出力の違いを説明しています。

PNGの納品後も、マスター、スクリプト、フォント入力を保管してください。別のラスタライズ段階を使う場合は、そのサイズ設定を別に検証します。SharpによるSVGからPNGへの例は、そのレンダラーの寸法を扱っています。

よくある質問

SVG画像はSatoriカード内で編集可能なパスになりますか? この例では、ロゴは入れ子のSVG画像のままです。元の一つのパスのデータは画像内に残り、外側のカードのパスは文字です。形状の編集には元のマスターを残してください。

見えるロゴが幅と高さより小さいのはなぜですか? 画像の枠には元データの範囲が含まれます。このひし形は128×128の元画像内に余白を残しており、正確な色のピクセル範囲にはアンチエイリアスされた縁も含みません。元画像の余白と割り当てた縦横比は、別々に確認してください。

ロゴの透明な穴はカードも透明にしますか? 穴からカード背景が見えます。このクリーム色の背景は不透明なので、開口部のPNGピクセルはアルファ255です。納品に透明なカード背景が必要なら、別にテストしてください。

SatoriはPNGを返しますか? このひな形はSatoriにSVG文字列を要求し、その後resvg-jsでPNGに描画します。レイアウトとラスター納品を独立して確認できるよう、段階を分けてください。

出典

  1. Satoriの画像、レイアウト、フォントの文書 — 画像寸法、自己完結した入力、標準SVG出力、対応するHTML/CSSの範囲。
  2. Satori 0.35.0リリース — 元の例で使った固定バージョン。
  3. SVGのimage要素 — SVG画像は別のSVGファイルを参照できます。
  4. NodeのBufferエンコードAPI — ひな形のbase64エンコード操作。
  5. resvg-jsの使い方 — 別のSVGからPNGへの描画段階。
  6. Abelフォントのライセンス — フォントの出所、告知、再配布ライセンス。

自分のPNGやJPGロゴには、PerfectVectorでSVGマスターを準備し、輪郭と内側の開口部を確認してください。マスターを保管し、予定の使用サイズで、カードSVGと納品PNG内の配置を確認します。

ブログのその他の記事

PerfectVector

編集しやすい
よりきれいなSVGから始めましょう

クレジットカード不要