yotiky Tech Blog

とあるエンジニアの備忘録

Azure Functions で Azure Blob Storage からファイルを取得する

目次

検証環境

  • Azure Functions v3
  • Azure.Storage.Blobs v12.8.0

実装

Azure Functions のプロジェクトに NuGet で「Azure.Storage.Blobs」をインストールする。

using Azure.Storage.Blobs;
private static string connectionString = "YOUR CONNECTION STRING";
private static string blobContainerName = "CONTAINER_NAME";
private static string blobName = "BLOB_NAME";

private static async Task<byte[]> Download()
{
    var blobServiceClient = new BlobServiceClient(connectionString);
    var blobContainerClient = blobServiceClient.GetBlobContainerClient(blobContainerName);
    var blobClient = blobContainerClient.GetBlobClient(blobName);

    using (var stream = new MemoryStream())
    {
        var response = await blobClient.DownloadToAsync(stream).ConfigureAwait(false);
        return stream.ToArray();
    }
}

関連記事

Azure Functions で MeCab.DotNet を使って形態素解析

目次

概要

Azure Functions で形態素解析を行うため MeCab.DotNet を使用します。こちらのライブラリはローカルディレクトリから辞書ファイルを読み込むため、Azure Blob Storage にアップロードした各種ファイルを Azure Functions の TMP ディレクトリ配下にダウンロードして利用するようにします。 容量制限やライフサイクルなどは考慮が必要です。

qiita.com


WPFで動かす記事やユーザー辞書を追加する前回の記事など書いています。

インストール

Azure Functions のプロジェクトを作成したら、NuGet で「MeCab.DotNet」と「Azure.Storage.Blobs」をインストールします。

f:id:yotiky:20210126211348p:plain

MeCab.DotNet は「MeCab」、「NMeCab」を .NET Core に移植したパッケージです。

github.com

以下パッケージサイトから抜粋。

"MeCab" は、日本語形態素解析エンジンのプロジェクトです。

"NMeCab" は、上記MeCabを、.NET Framework 2.0のマネージライブラリとして実装し直したものです。ただ、もう更新されていないようです...

"MeCab.DotNet" (このプロジェクト)は、上記NMeCabを最新の.NET Core 1/2/3と.NET Frameworkで使えるように移植し、NuGetのパッケージに固めて使いやすくしたものです。

なお、ライセンスは GPL2 または LGPL2.1 とのことで利用に際しては注意が必要です。

サンプル実装

まずは Json を受けるためのパラメータクラスです。

public class TextAnarytics
{
    public bool DeleteCacheDics { get; set; }
    public bool ForceDownload { get; set; }
    public string[] UserDics { get; set; }
    public string Text { get; set; }
}

Text が解析対象の文字列です。 ユーザー辞書はパラメータで指定するようになっています。

一度ダウンロードした辞書を刷新するために TMP ディレクトリから全辞書を削除するフラグと、ユーザー辞書などを更新するために上書きをするフラグを用意してあります。


続いて、Function です。

[FunctionName("Function1")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    var data = JsonConvert.DeserializeObject<TextAnarytics>(requestBody);

    var deleteCacheDics = data.DeleteCacheDics;
    var forceDownload = data.ForceDownload;
    var userDics = data.UserDics;
    var text = data.Text;

    if (deleteCacheDics) { DeleteDir(); }

    await Task.WhenAll(
        Download("char.bin", forceDownload),
        Download("dicrc", forceDownload),
        Download("matrix.bin", forceDownload),
        Download("sys.dic", forceDownload),
        Download("unk.dic", forceDownload));

    if(userDics?.Length > 0)
    {
        var tasks = new List<Task>();
        foreach (var dic in userDics)
            tasks.Add(Download(dic, forceDownload));

        await Task.WhenAll(tasks);
    }

    var result = "";
    if (!string.IsNullOrEmpty(text))
    {
        var dicDir = Directory.CreateDirectory(Path.Combine(Environment.GetEnvironmentVariable("TMP"), "dic"));

        var param = new MeCabParam();
        param.DicDir = dicDir.FullName;
        param.UserDic = userDics ?? param.UserDic;
        var tagger = MeCabTagger.Create(param);

        foreach (var node in tagger.ParseToNodes(text))
        {
            if (0 < node.CharType)
            {
                result += $"{node.Surface}\t{node.Feature}\r\n";
            }
        }
    }


    string responseMessage = string.IsNullOrEmpty(result)
        ? "This HTTP triggered function executed faild."
        : result;

    return new OkObjectResult(responseMessage);
}


TMP ディレクトリから dic フォルダ毎削除するコードです。

private static void DeleteDir()
{
    Directory.Delete(Path.Combine(Environment.GetEnvironmentVariable("TMP"), "dic"), true);
}


最後に辞書をダウンロードして TMP ディレクトリに保存するコードです。

private static string connectionString = "YOUR CONNECTION STRING";
private static string blobContainerName = "dic";
private static async Task Download(string blobName, bool force = false)
{
    var dicDir = Directory.CreateDirectory(Path.Combine(Environment.GetEnvironmentVariable("TMP"), "dic"));
    var dicFile = Path.Combine(dicDir.FullName, blobName);

    if(!force && File.Exists(dicFile)) { return; }

    var blobServiceClient = new BlobServiceClient(connectionString);
    var blobContainerClient = blobServiceClient.GetBlobContainerClient(blobContainerName);
    var blobClient = blobContainerClient.GetBlobClient(blobName);

    using (var stream = new MemoryStream())
    {
        var response = await blobClient.DownloadToAsync(stream).ConfigureAwait(false);

        using (var file = new FileStream(dicFile, FileMode.Create, FileAccess.Write))
        {
            stream.Position = 0;
            await stream.CopyToAsync(file);
        }
    }
}

プロジェクト設定

ユーザー辞書を使用するためには csproj を開いて、PropertyGroup に以下の1行を追加します。

<MeCabUseDefaultDictionary>false</MeCabUseDefaultDictionary>

f:id:yotiky:20210126213352p:plain

Azure Blob Storage にアップロード

MeCab.DotNet に含まれている以下のファイルを Azure Blob Storage にアップロードします。

  • char.bin
  • dicrc
  • matrix.bin
  • sys.dic
  • unk.dic

加えて、前回の記事で作成したユーザー辞書もアップロードしておきます。

  • userdic1.dic
  • userdic2.dic

f:id:yotiky:20210126213610p:plain

実行

プロジェクトをサーバーにデプロイします。

ちなみにローカル実行した場合の TMP ディレクトリは C:\Users\<YOURNAME>\AppData\Local\Temp になります。

デプロイが終わったら、Postman などを使って 以下の Json を投げてみます。

{
    "DeleteCacheDics" : false,
    "ForceDownload" : false,
    "Text" : "はじめまして。 山田太郎、山田が苗字で、太郎が名前です。 日本の大阪出身です。 私は、1973年4月14日生まれです。山田花子は姉です。",
}

システム辞書のみなので、「山田太郎」が「山田」と「太郎」で分解されます。

f:id:yotiky:20210126214720p:plain


続いて、ユーザー辞書を指定します。

{
    "DeleteCacheDics" : false,
    "ForceDownload" : false,
    "Text" : "はじめまして。 山田太郎、山田が苗字で、太郎が名前です。 日本の大阪出身です。 私は、1973年4月14日生まれです。山田花子は姉です。",
    "UserDics" : ["userdic1.dic", "userdic2.dic"]
}

ユーザー辞書で「山田太郎」を名詞として登録したので、「山田太郎」で認識されています。

f:id:yotiky:20210126214655p:plain

MeCab.DotNet にユーザー辞書を追加する

目次

インストール

本家 MeCab より Windows 用のバイナリパッケージをダウンロードします。

f:id:yotiky:20210126163108p:plain:w350

ダウンロードしたインストーラーを実行します。 今回は辞書の文字コードは「SHIFT-JIS」を選択。

f:id:yotiky:20210126163445p:plain:w350

辞書の作成

以下の形式の csv でデータを作成します。末尾に追加エントリもできるようです。

表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

引用元


作成例1)

山田太郎,,,10,名詞,一般,,,,,やまだたろう,ヤマダタロウ,ヤマダタロウ,独自辞書

f:id:yotiky:20210126175451p:plain


作成例2)

山田花子,,,10,名詞,一般,,,,,やまだはなこ,ヤマダハナコ,ヤマダハナコ,独自辞書



管理者権限でコマンドを開いて mecab-dict-index を実行します。

cd C:\Program Files (x86)\MeCab
bin\mecab-dict-index -d "dic\ipadic" -u "dic\userdic\userdic1.dic" -f shift-jis -t utf-8 "dic\userdic\userdic1.csv"
bin\mecab-dict-index -d "dic\ipadic" -u "dic\userdic\userdic2.dic" -f shift-jis -t utf-8 "dic\userdic\userdic2.csv"

オプションは以下の通り。

mecab-dict-index [options] files
-d : システム辞書のディレクトリ
-u : ユーザー辞書の保存先
-f : CSVファイルの文字コード
-t : ユーザー辞書の文字コード

実装

前回のアプリを元に修正します。

yotiky.hatenablog.com


前項で作成した辞書をシステム辞書と同じ実行ディレクトリの dic フォルダに配置します。(bin\Debug\netcoreapp3.1\dic など)

MeCabTagger を作成する際に、ユーザー辞書を設定したパラメータを引数に指定します。

    var param = new MeCabParam();
    param.DicDir = @"dic";
    param.UserDic = new[] { "userdic1.dic", "userdic2.dic" };
    var tagger = MeCabTagger.Create(param);


ユーザー辞書を追加しなかった場合、「山田太郎」が「山田」と「太郎」で分解されます。 f:id:yotiky:20210126185034p:plain


ユーザー辞書を追加した場合、「山田太郎」が名詞として1つになっています。また、末尾に追加した「独自辞書」のエントリも問題なく動いてます。 f:id:yotiky:20210126184930p:plain

参考

辞書を作成できるサンプルアプリを公開してる方もいらっしゃいました。

resanaplaza.com

関連記事

C# で MeCab.DotNet を使って形態素解析

目次

インストール

NuGet で「MeCab.DotNet」をインストールします。 MeCab.DotNet は「MeCab」、「NMeCab」を .NET Core に移植したパッケージです。

github.com

以下パッケージサイトから抜粋。

"MeCab" は、日本語形態素解析エンジンのプロジェクトです。

"NMeCab" は、上記MeCabを、.NET Framework 2.0のマネージライブラリとして実装し直したものです。ただ、もう更新されていないようです...

"MeCab.DotNet" (このプロジェクト)は、上記NMeCabを最新の.NET Core 1/2/3と.NET Frameworkで使えるように移植し、NuGetのパッケージに固めて使いやすくしたものです。

なお、ライセンスは GPL2 または LGPL2.1 とのことで利用に際しては注意が必要です。

サンプル実装

WPFのサンプル実装です。

XAML

<Window x:Class="Samples.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MeCab"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBox x:Name="textbox" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,10,150,0" TextWrapping="Wrap" Width="600" AcceptsReturn="True"/>
        <Button Content="解析" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="620,10,0,0" Width="75" Click="Button_Click"/>
        <TextBlock x:Name="result" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,40,10,0" />
    </Grid>
</Window>

コードビハインド

using MeCab;
using System.Windows;

namespace Samples
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var tagger = MeCabTagger.Create();

            result.Text = "";
            foreach(var node in tagger.ParseToNodes(textbox.Text))
            {
                if (0 < node.CharType)
                {
                    result.Text += $"{node.Surface}\t{node.Feature}\r\n";
                }
            }
        }
    }
}

実行結果は次の通り。 f:id:yotiky:20210125165517p:plain

関連記事

Azure Cognitive Services - Text Analytics の試し打ち

目次

TL;DR

  • 名前付きエンティティの認識は、人、場所、組織などを抽出できる
  • 日本語では Person、Location、Organization のみ解析可能で、英語に比べるといまいちの精度
  • 個人を特定できる情報の検出は、個人情報と医療情報を検出する機能が提供されているが現在英語とスペイン語のみ
  • Azure Cognitive Search は、クラウドのストレージを高度に検索するサービス
  • インデックス作成で Analyzer を使用して形態素解析を行うが、検証目的でAPIが用意されている

名前付きエンティティの認識 (NER)

API : text/analytics/v3.1-preview.3/entities/recognition/general

NER v3 では英語とスペイン語のみがサポートされている。日本語は v2 が代替として動作し、"Person"、"Location"、"Organization"のみ返却される。

すべてのカテゴリは、固有表現認識でサポートされるカテゴリ で確認できる。

En

Nice to meet you. Yamada is my family name and Taro is my given name. I'm from Osaka, Japan. I was born on April 14th in 1973.

Request

{ documents: [
        { 
            id: "1", 
            language: "en", 
            text: "Nice to meet you. Yamada is my family name and Taro is my given name. I'm from Osaka, Japan. I was born on April 14th in 1973."
        }
    ]
}

Response

    "documents": [
        {
            "id": "1",
            "entities": [
                {
                    "text": "Yamada",
                    "category": "Person",
                    "offset": 18,
                    "length": 6,
                    "confidenceScore": 0.52
                },
                {
                    "text": "Taro",
                    "category": "Person",
                    "offset": 47,
                    "length": 4,
                    "confidenceScore": 0.76
                },
                {
                    "text": "Osaka",
                    "category": "Location",
                    "subcategory": "GPE",
                    "offset": 79,
                    "length": 5,
                    "confidenceScore": 0.8
                },
                {
                    "text": "Japan",
                    "category": "Location",
                    "subcategory": "GPE",
                    "offset": 86,
                    "length": 5,
                    "confidenceScore": 0.55
                },
                {
                    "text": "April 14th",
                    "category": "DateTime",
                    "subcategory": "Date",
                    "offset": 107,
                    "length": 10,
                    "confidenceScore": 0.8
                },
                {
                    "text": "1973",
                    "category": "DateTime",
                    "subcategory": "DateRange",
                    "offset": 121,
                    "length": 4,
                    "confidenceScore": 0.8
                }
            ],
            "warnings": []
        }

Ja

はじめまして。 山田が苗字で、太郎が名前です。 日本の大阪出身です。 私は、1973年4月14日生まれです。

Request

{ documents: [
        { 
            id: "1", 
            language: "ja", 
            text: "はじめまして。 山田が苗字で、太郎が名前です。 日本の大阪出身です。 私は、1973年4月14日生まれです。"
        }
    ]
}

Response

    "documents": [
        {
            "id": "1",
            "entities": [
                {
                    "text": "山田",
                    "category": "Person",
                    "offset": 8,
                    "length": 2,
                    "confidenceScore": 0.66
                },
                {
                    "text": "日本",
                    "category": "Location",
                    "subcategory": "GPE",
                    "offset": 24,
                    "length": 2,
                    "confidenceScore": 0.98
                },
                {
                    "text": "大阪",
                    "category": "Location",
                    "subcategory": "GPE",
                    "offset": 27,
                    "length": 2,
                    "confidenceScore": 0.98
                }
            ],
            "warnings": []
        }

個人を特定できる情報の検出

API : /text/analytics/v3.1-preview.3/entities/recognition/pii

NER v3.1 のプレビューでは、個人情報 (PII) と医療情報 (PHI) を検出する機能が備わっている。 APIは現在、英語とスペイン語のみで提供されている。

抽出されるカテゴリは、固有表現認識でサポートされるカテゴリで確認できる。

  • 日本

En

Nice to meet you. Yamada is my family name and Taro is my given name. I'm from Osaka, Japan. I was born on April 14th in 1973. My number is 345-6890. My bank account number is 1234567.

Request

{ documents: [
        { 
            id: "1", 
            language: "en", 
            text: "Nice to meet you. Yamada is my family name and Taro is my given name. I'm from Osaka, Japan. I was born on April 14th in 1973."
        },
        { 
            id: "2", 
            language: "en", 
            text: "My number is 345-6890. My bank account number is 1234567."
        }
    ]
}

Response

    "documents": [
        {
            "redactedText": "Nice to meet you. ****** is my family name and **** is my given name. I'm from Osaka, Japan. I was born on ********** in 1973.",
            "id": "1",
            "entities": [
                {
                    "text": "Yamada",
                    "category": "Person",
                    "offset": 18,
                    "length": 6,
                    "confidenceScore": 0.52
                },
                {
                    "text": "Taro",
                    "category": "Person",
                    "offset": 47,
                    "length": 4,
                    "confidenceScore": 0.76
                },
                {
                    "text": "April 14th",
                    "category": "DateTime",
                    "subcategory": "Date",
                    "offset": 107,
                    "length": 10,
                    "confidenceScore": 0.8
                }
            ],
            "warnings": []
        },
        {
            "redactedText": "My number is ********. My bank account number is ********",
            "id": "2",
            "entities": [
                {
                    "text": "345-6890",
                    "category": "Phone Number",
                    "offset": 13,
                    "length": 8,
                    "confidenceScore": 0.8
                },
                {
                    "text": "1234567.",
                    "category": "Japan Bank Account Number",
                    "offset": 49,
                    "length": 8,
                    "confidenceScore": 0.85
                }
            ],
            "warnings": []
        }
    ],

Ja

未対応

キー フレーズの抽出

API : /text/analytics/v3.1-preview.3/keyPhrases

En

Request

{ documents: [
        { 
            id: "1", 
            language: "en", 
            text: "Nice to meet you. Yamada is my family name and Taro is my given name. I'm from Osaka, Japan. I was born on April 14th in 1973."
        }
    ]
}

Response

    "documents": [
        {
            "id": "1",
            "keyPhrases": [
                "family",
                "Taro",
                "Yamada",
                "Osaka",
                "Japan"
            ],
            "warnings": []
        }
    ],

Ja

Request

{ documents: [
        { 
            id: "1", 
            language: "ja", 
            text: "はじめまして。 山田が苗字で、太郎が名前です。 日本の大阪出身です。 私は、1973年4月14日生まれです。"
        }
    ]
}

Response

    "documents": [
        {
            "id": "1",
            "keyPhrases": [
                "苗字",
                "太郎",
                "日本",
                "大阪出身",
                "山田",
                "名前",
                "まし",
                "生まれ"
            ],
            "warnings": []
        }
    ],

Azure Cognitive Search 自体はクラウドのストレージを高度に検索するためのサービス。
インデックスを作成する際に使われる Analyzer で形態素解析を行っており、Analyzer の検証用に Analyzer API が用意されている。

docs.microsoft.com

blog.johtani.info

Unity - よく使うパッケージ

UPM

manifest.json の dependencies に追加する。

UniRX

"com.neuecc.unirx": "https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts",

UniTask

"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",

Extenject

"com.svermeulen.extenject": "https://github.com/svermeulen/Extenject.git?path=UnityProject/Assets/Plugins/Zenject",

VContainer

"jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.4.3",

Unity Package

package をダウンロードしてプロジェクトにインポートする。

MessagePack for C

Releases · neuecc/MessagePack-CSharp · GitHub

Utf8Json

Releases · neuecc/Utf8Json · GitHub

ZString

Releases · Cysharp/ZString · GitHub

ZLogger

Releases · Cysharp/ZLogger · GitHub

MasterMemory

Releases · Cysharp/MasterMemory · GitHub

Ulid

Releases · Cysharp/Ulid · GitHub

Asset Store

Asset Store からインポートする。

LINQ to GameObject

LINQ to GameObject | Integration | Unity Asset Store

Unity - TextMeshProで日本語を表示する

目次

フォントのインポート

使用する日本語フォントを準備し、Unityにインポートする。

Windows

商用利用はしない方が良さそう。*1

Meiryo UI

Windows 8 使われていたシステムフォントで、C:\Windows\Fontsに含まれている。

f:id:yotiky:20210122125209p:plain:w400

Yu Gothic UI

Windows 10 で使われているシステムフォントで、C:\Windows\Fontsに含まれている。

f:id:yotiky:20210122135639p:plain:w400

Google Fonts

Google が提供しているフォント。 Kosugi / Kosugi Maru は Apache License 2.0、ほかはSILライセンス。
個人利用、商用利用可能なフリーライセンスで、ライセンス表記が必要。

fonts.google.com

Google Fonts からフォントを選んでダウンロード。ttf と otf があるがどちらも使用可能。

f:id:yotiky:20210122153719p:plain:w400

日本語のフォントは11書体34種類。

  • Yusei Magic
  • Potta One
  • Hachi Maru Pop
  • Noto Sans JP
  • Noto Serif JP
  • M PLUS 1p
  • M PLUS Rounded 1c
  • Sawarabi Gothic
  • Sawarabi Mincho
  • Kosugi
  • Kosugi Maru

f:id:yotiky:20210122154758p:plain:w400

qiita.com

Fontworks

最近話題になった Fontworks が Google Fonts に提供したフォント。

SILライセンス。
個人利用、商用利用可能なフリーライセンスで、ライセンス表記が必要。

github.com

Google Fonts ではまた提供されていないらしいので Fontworks から ttf ファイルもしくはコードまるごとダウンロード。

f:id:yotiky:20210122144803p:plain:w300

日本語のフォントは7書体8種類。

  • クレー One Regular
  • クレー One SemiBold
  • トレイン One Regular
  • ロックンロール One Regular
  • ステッキ Regular
  • ランパート One Regular
  • レゲエ One Regular
  • ドットゴシック16 Regular

f:id:yotiky:20210122150634p:plain:w400

coliss.com

M+

Google Fonts で公開されている M+ は、和文 Type-1 と欧文 P Type-1 の組み合わせである「M+ P Type-1」と、丸ゴシックタイプの和文 Rounded M+ と欧文 C Type-1 の組み合わせである「M PLUS Rounded 1c」。

以下のサイトでは、和文2種類、欧文4種の組み合わせで構成されたフォント11種類がダウンロードできる。「M PLUS Rounded 1c」は含まれない。

mplus-fonts.osdn.jp

ライセンス

ライセンスやライセンスの表記について参考になりそうなサイト。

Font Asset を作成

FontAssetCreatorを開く。

f:id:yotiky:20210122160442p:plain:w300

Unity にインポートしたフォントを選択、Character List には Adobe-Japan1-0 を設定したら、ジェネレートして保存。

f:id:yotiky:20210122161022p:plain

blog.kyubuns.dev

Font を使用

TextMesPro の FontAsset にフォントを設定する。

f:id:yotiky:20210122162406p:plain

表示例

Windows

  • MEIRYO f:id:yotiky:20210122173146p:plain

  • YUGOTHR f:id:yotiky:20210122173100p:plain

Google Fonts

  • YuseiMagic-Regular f:id:yotiky:20210122173041p:plain

  • PottaOne-Regular f:id:yotiky:20210122173011p:plain

  • HachiMaruPop-Regular f:id:yotiky:20210122172939p:plain

  • NotoSansJP-Regular f:id:yotiky:20210122173258p:plain

  • NotoSerifJP-Regular f:id:yotiky:20210122173326p:plain

  • MPLUS1p-Regular f:id:yotiky:20210122173342p:plain

  • MPLUSRounded1c-Regular f:id:yotiky:20210122173356p:plain

  • SawarabiGothic-Regular f:id:yotiky:20210122173416p:plain

  • SawarabiMincho-Regular f:id:yotiky:20210122173442p:plain

※2,250字の漢字しか含まれていないため「融」が出ていない。

  • Kosugi-Regular f:id:yotiky:20210122173709p:plain

  • KosugiMaru-Regular f:id:yotiky:20210122173723p:plain

Fontworks

  • KleeOne-Regular f:id:yotiky:20210122173746p:plain

  • TrainOne-Regular f:id:yotiky:20210122173807p:plain

  • RocknRollOne-Regular f:id:yotiky:20210122173825p:plain

  • Stick-Regular f:id:yotiky:20210122173842p:plain

  • RampartOne-Regular f:id:yotiky:20210122173857p:plain

  • ReggaeOne-Regular f:id:yotiky:20210122173917p:plain

  • DotGothic16-Regular f:id:yotiky:20210122174217p:plain

参考

関連記事

yotiky.hatenablog.com

yotiky.hatenablog.com