1
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 3 years have passed since last update.

[習作] 数式をSVGに変換するVSCode拡張機能を作った

Last updated at Posted at 2021-04-24

[習作] 数式をSVGに変換するVSCode拡張機能を作った

はじめに

  • ふと VSCode の拡張機能を作ってみたくなった
  • そういえば先日、pandoc のマニュアルを見ていたら、数式(latex)をSVGやPNGに変換するサービスが書かれていた
    • 以下の --webtex のあたりに記載があります

  • とりあえず、数式をSVGに変換する拡張機能を作ってみた
    • HTML や Markdown に埋め込めるかな? と思っています
    • 内容的には Hello world レベルです
  • httpsで通信するけど proxy には対応していない
  • サーバに負担がかかるような使い方は厳禁

環境構築

細かい手順は省略します。

とりあえず、すでに node / npm は入っていたので念のためアップグレードして、それ以外に必要なものをインストールします。

$ sudo apt update
$ sudo apt upgrade
$ sudo npm install -g yo generator-code
$ sudo npm install -g vsce

適当なディレクトリにプロジェクトを作成します(実行するとプロジェクト用のディレクトリが新しくできるので、それを考慮して決定)。

$ yo code

いろいろ聞かれますがとりあえず New Extension (TypeScript) を選択して、プロジェクト名(拡張機能名)を Math2Svg といれて、後はデフォルトのままで進めて、しばらく待つと、カレントディレクトリにプロジェクト名(小文字になっているので、識別名?)で新しいディレクトリができました。
フォルト選択のままで git の初期化もしてくれました。

実装

VSCode の API のドキュメントや、その他のサイトを参考に実装します。

もしかすると https 通信は便利な機能があるのかもしれないですが、わからなかったので node.js の https を直接使用します。

src/extension.ts
import * as vscode from 'vscode';
import * as https from 'https';

// convert latex -> svg
const url = "https://latex.codecogs.com/svg.latex?";
const convertLatex2Svg = (text: string) => {
    return new Promise<string>((resolve, reject) => {
        https.get(url + encodeURIComponent(text), (res) => {
            if (res.statusCode === 200) {
                res.on('data', (d: Buffer) => resolve(d.toString()));
            } else {
                reject(new Error(`${res.statusCode} ${res.statusMessage}`));
            }
        }).on('error', (e) => reject(e));
    });
};

export function activate(context: vscode.ExtensionContext) {
    console.log('"math2svg" is now active.');
    const disposable = vscode.commands.registerCommand('math2svg.convert', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) { return; }
        const selection = editor.selection;
        if (selection.isEmpty) { return; }
        const text = editor.document.getText(selection);
        if (text.charAt(0) !== '$' || text.charAt(text.length - 1) !== '$') {
            return;
        }

        const mathString = text.substring(1, text.length - 1);
        try {
            const svgString = await convertLatex2Svg(mathString);
            await editor.edit((edit) => edit.replace(selection, svgString));
        } catch (e) {
            console.error(e);
            await vscode.window.showErrorMessage(`Math2Svg: ${e.name} ${e.message}`);
        }
    });
    context.subscriptions.push(disposable);
}

export function deactivate() { }

あとは package.jsonactivationEventscontributes を変更しました(それ以外は省略)。

package.json(一部)
{
    "activationEvents": [
        "onCommand:math2svg.convert"
    ],
    "main": "./out/extension.js",
    "contributes": {
        "commands": [
            {
                "command": "math2svg.convert",
                "title": "Math to SVG"
            }
        ],
        "menus": {
            "editor/context": [
                {
                    "when": "editorHasSelection && !editorReadonly && !editorHasMultipleSelections",
                    "command": "math2svg.convert",
                    "group": "z_commands"
                }
            ]
        }
    }
}

あとは、デバッグモードで動かして( F5 で実行)、動作確認をします。

test.html
<html>
<head>
<title>テスト</title>
</head>
<body>

これは
$sin(x)$
です</body>
</html>

ここの $sin(x)$ を選択して、右クリック、コンテキストメニューから Math to SVG を選択すると、数秒後に SVG に置換されました。

test.html
<html>
<head>
<title>テスト</title>
</head>
<body>

これは
<?xml version='1.0' encoding='UTF-8'?>
<!-- Generated by CodeCogs with dvisvgm 2.9.1 -->
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='3em' height='1em' viewBox='-.239051 -.240635 35.11002 13.522849'>
<defs>
<path id='g1-40' d='M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186-.537983 1.817186-2.976837C1.817186-5.296139 2.379078-7.292653 3.765878-8.703362C3.88543-8.810959 3.88543-8.834869 3.88543-8.870735C3.88543-8.942466 3.825654-8.966376 3.777833-8.966376C3.622416-8.966376 2.642092-8.105604 2.056289-6.933998C1.446575-5.726526 1.171606-4.447323 1.171606-2.976837C1.171606-1.912827 1.338979-.490162 1.960648 .789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z'/>
<path id='g1-41' d='M3.371357-2.976837C3.371357-3.88543 3.251806-5.36787 2.582316-6.75467C1.876961-8.18929 .896638-8.966376 .765131-8.966376C.71731-8.966376 .657534-8.942466 .657534-8.870735C.657534-8.834869 .657534-8.810959 .860772-8.607721C2.056289-7.400249 2.725778-5.427646 2.725778-2.988792C2.725778-.669489 2.163885 1.327024 .777086 2.737733C.657534 2.84533 .657534 2.86924 .657534 2.905106C.657534 2.976837 .71731 3.000747 .765131 3.000747C.920548 3.000747 1.900872 2.139975 2.486675 .968369C3.096389-.251059 3.371357-1.542217 3.371357-2.976837Z'/>
<path id='g0-105' d='M3.383313-1.709589C3.383313-1.769365 3.335492-1.817186 3.263761-1.817186C3.156164-1.817186 3.144209-1.78132 3.084433-1.578082C2.773599-.490162 2.283437-.119552 1.888917-.119552C1.745455-.119552 1.578082-.155417 1.578082-.514072C1.578082-.836862 1.721544-1.195517 1.853051-1.554172L2.689913-3.777833C2.725778-3.873474 2.809465-4.088667 2.809465-4.315816C2.809465-4.817933 2.450809-5.272229 1.865006-5.272229C.765131-5.272229 .32279-3.53873 .32279-3.443088C.32279-3.395268 .37061-3.335492 .454296-3.335492C.561893-3.335492 .573848-3.383313 .621669-3.550685C.908593-4.554919 1.362889-5.033126 1.829141-5.033126C1.936737-5.033126 2.139975-5.021171 2.139975-4.638605C2.139975-4.327771 1.984558-3.93325 1.888917-3.670237L1.052055-1.446575C.980324-1.255293 .908593-1.06401 .908593-.848817C.908593-.310834 1.279203 .119552 1.853051 .119552C2.952927 .119552 3.383313-1.625903 3.383313-1.709589ZM3.287671-7.460025C3.287671-7.639352 3.144209-7.854545 2.881196-7.854545C2.606227-7.854545 2.295392-7.591532 2.295392-7.280697C2.295392-6.981818 2.546451-6.886177 2.689913-6.886177C3.012702-6.886177 3.287671-7.197011 3.287671-7.460025Z'/>
<path id='g0-110' d='M2.462765-3.502864C2.486675-3.574595 2.785554-4.172354 3.227895-4.554919C3.53873-4.841843 3.945205-5.033126 4.411457-5.033126C4.889664-5.033126 5.057036-4.674471 5.057036-4.196264C5.057036-3.514819 4.566874-2.15193 4.327771-1.506351C4.220174-1.219427 4.160399-1.06401 4.160399-.848817C4.160399-.310834 4.531009 .119552 5.104857 .119552C6.216687 .119552 6.635118-1.637858 6.635118-1.709589C6.635118-1.769365 6.587298-1.817186 6.515567-1.817186C6.40797-1.817186 6.396015-1.78132 6.336239-1.578082C6.06127-.597758 5.606974-.119552 5.140722-.119552C5.021171-.119552 4.829888-.131507 4.829888-.514072C4.829888-.812951 4.961395-1.171606 5.033126-1.338979C5.272229-1.996513 5.774346-3.335492 5.774346-4.016936C5.774346-4.734247 5.355915-5.272229 4.447323-5.272229C3.383313-5.272229 2.82142-4.519054 2.606227-4.220174C2.570361-4.901619 2.080199-5.272229 1.554172-5.272229C1.171606-5.272229 .908593-5.045081 .705355-4.638605C.490162-4.208219 .32279-3.490909 .32279-3.443088S.37061-3.335492 .454296-3.335492C.549938-3.335492 .561893-3.347447 .633624-3.622416C.824907-4.351681 1.0401-5.033126 1.518306-5.033126C1.793275-5.033126 1.888917-4.841843 1.888917-4.483188C1.888917-4.220174 1.769365-3.753923 1.685679-3.383313L1.350934-2.092154C1.303113-1.865006 1.171606-1.327024 1.111831-1.111831C1.028144-.800996 .896638-.239103 .896638-.179328C.896638-.011955 1.028144 .119552 1.207472 .119552C1.350934 .119552 1.518306 .047821 1.613948-.131507C1.637858-.191283 1.745455-.609714 1.80523-.848817L2.068244-1.924782L2.462765-3.502864Z'/>
<path id='g0-115' d='M2.725778-2.391034C2.929016-2.355168 3.251806-2.283437 3.323537-2.271482C3.478954-2.223661 4.016936-2.032379 4.016936-1.458531C4.016936-1.08792 3.682192-.119552 2.295392-.119552C2.044334-.119552 1.147696-.155417 .908593-.812951C1.3868-.753176 1.625903-1.123786 1.625903-1.3868C1.625903-1.637858 1.458531-1.769365 1.219427-1.769365C.956413-1.769365 .609714-1.566127 .609714-1.028144C.609714-.32279 1.327024 .119552 2.283437 .119552C4.100623 .119552 4.638605-1.219427 4.638605-1.841096C4.638605-2.020423 4.638605-2.355168 4.25604-2.737733C3.957161-3.024658 3.670237-3.084433 3.024658-3.21594C2.701868-3.287671 2.187796-3.395268 2.187796-3.93325C2.187796-4.172354 2.402989-5.033126 3.53873-5.033126C4.040847-5.033126 4.531009-4.841843 4.65056-4.411457C4.124533-4.411457 4.100623-3.957161 4.100623-3.945205C4.100623-3.694147 4.327771-3.622416 4.435367-3.622416C4.60274-3.622416 4.937484-3.753923 4.937484-4.25604S4.483188-5.272229 3.550685-5.272229C1.984558-5.272229 1.566127-4.040847 1.566127-3.550685C1.566127-2.642092 2.450809-2.450809 2.725778-2.391034Z'/>
<path id='g0-120' d='M5.66675-4.877709C5.284184-4.805978 5.140722-4.519054 5.140722-4.291905C5.140722-4.004981 5.36787-3.90934 5.535243-3.90934C5.893898-3.90934 6.144956-4.220174 6.144956-4.542964C6.144956-5.045081 5.571108-5.272229 5.068991-5.272229C4.339726-5.272229 3.93325-4.554919 3.825654-4.327771C3.550685-5.224408 2.809465-5.272229 2.594271-5.272229C1.374844-5.272229 .729265-3.706102 .729265-3.443088C.729265-3.395268 .777086-3.335492 .860772-3.335492C.956413-3.335492 .980324-3.407223 1.004234-3.455044C1.41071-4.782067 2.211706-5.033126 2.558406-5.033126C3.096389-5.033126 3.203985-4.531009 3.203985-4.244085C3.203985-3.981071 3.132254-3.706102 2.988792-3.132254L2.582316-1.494396C2.402989-.777086 2.056289-.119552 1.422665-.119552C1.362889-.119552 1.06401-.119552 .812951-.274969C1.243337-.358655 1.338979-.71731 1.338979-.860772C1.338979-1.099875 1.159651-1.243337 .932503-1.243337C.645579-1.243337 .334745-.992279 .334745-.609714C.334745-.107597 .896638 .119552 1.41071 .119552C1.984558 .119552 2.391034-.334745 2.642092-.824907C2.833375-.119552 3.431133 .119552 3.873474 .119552C5.092902 .119552 5.738481-1.446575 5.738481-1.709589C5.738481-1.769365 5.69066-1.817186 5.618929-1.817186C5.511333-1.817186 5.499377-1.75741 5.463512-1.661768C5.140722-.609714 4.447323-.119552 3.90934-.119552C3.490909-.119552 3.263761-.430386 3.263761-.920548C3.263761-1.183562 3.311582-1.374844 3.502864-2.163885L3.921295-3.789788C4.100623-4.507098 4.507098-5.033126 5.057036-5.033126C5.080946-5.033126 5.415691-5.033126 5.66675-4.877709Z'/>
</defs>
<g id='page1' transform='matrix(1.13 0 0 1.13 -63.986043 -64.41)'>
<use x='56.413267' y='65.753425' xlink:href='#g0-115'/>
<use x='61.927273' y='65.753425' xlink:href='#g0-105'/>
<use x='65.920705' y='65.753425' xlink:href='#g0-110'/>
<use x='72.908311' y='65.753425' xlink:href='#g1-40'/>
<use x='77.460637' y='65.753425' xlink:href='#g0-120'/>
<use x='84.112724' y='65.753425' xlink:href='#g1-41'/>
</g>
</svg>
です</body>
</html>

試しに、Chrome で表示してみると、こんな感じになりました。

image.png

ちょっとベースラインがズレていますし、SVGのサイズはピクセル値固定ですが、とりあえずできました!

1
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
1
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?