SVG
VSCode

VSCodeでSVGを編集できるやつ2.0(SVG Editor)

これは何

VSCode上でSVGを画像編集ソフトっぽく編集したくて作っている拡張です。Marketplaceのページ。

capture.png

右側のウィンドウで図形を選択して動かしたりできます。この度Elmで書いていたのをすべてTypeScriptで書き直したので苦労話とかをまとめてみました。使って~

なぜElmをやめたの?

portが辛かった...辛かったんです...許して...

まあ実際のdom要素から取得しないといけない値とか結構あったので、だんだんきつくなっていったんですね。

はまった点

  • cssでwidthとかheightを指定するときはpxとか単位が必須、SVGと同じ気分で書いてはいけない
  • SVGの要素はdocument.createElementNSで名前空間を指定する
  • 単位換算
    • units-cssという便利ライブラリがあるがSVGにそのまま適用はできない
    • %の計算にライブラリではoffsetWidth, offsetHeightを使って要素の幅や高さを測っているがSVG要素にはそのプロパティはない...
    • あとex, emの計算がライブラリは何かおかしい
    • のでgetComputedStyleしまくる
  • viewBoxが難しい(つらい)多分負の値でまだバグがある
  • ネストされたsvg要素の当たり判定がそれの内部にある要素以上に拡大されない(ので透明のrectをこっそり挿入)
  • transform属性は頭が痛い。g要素と絡むともっと頭が痛い。
    • inkscapeはすごい
  • text要素の拡大とか
    • 「文字列の高さ(lineHeight)→フォントサイズ」を求める方法がない。逆はできる(フォントの属性を与えてlineHeightとかbaselineとかを測ってくれるライブラリはfont-measureが優秀)
    • 仕方ないので二分探索

優秀なライブラリたち

  • font-measure フォントの属性を与えるといろいろな高さを測ってくれる。多分canvasで頑張っている。
  • incremental-dom vdomライブラリ。React、Vueは軟弱。ちゃんとSVGにも使える。
  • svgpath path要素のd属性の値をパースしていろいろいじってくれる感動ライブラリ。
  • transform-matrix transform属性のパース、行列計算のライブラリ。これも頼もしい。

VSCodeの拡張を書いて得た知識

  • 最近できたWebview APIで色々便利になった。適用されているテーマの色をcss変数で取得できるところとか素敵。
  • untitledファイルを開く関数はvscode.workspace.openTextDocument。以下。
function workspace.openTextDocument(options?: {
    language?: string | undefined;
    content?: string | undefined;
} | undefined): Thenable<vscode.TextDocument>

おまけ Webview APIで継承されるcss変数一覧

リンクの色ってテーマで指定できないんですね。

:root {
    --background-color: #1e1e1e;
    --color: #d4d4d4;
    --font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", HelveticaNeue-Light, "Noto Sans", Meiryo, "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
    --font-size: 13px;
    --font-weight: normal;
    --link-active-color: #4080d0;
    --link-color: #4080d0;
    --vscode-activityBar-background: #333333;
    --vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
    --vscode-activityBar-foreground: #ffffff;
    --vscode-activityBarBadge-background: #007acc;
    --vscode-activityBarBadge-foreground: #ffffff;
    --vscode-badge-background: #4d4d4d;
    --vscode-badge-foreground: #ffffff;
    --vscode-button-background: #0e639c;
    --vscode-button-foreground: #ffffff;
    --vscode-button-hoverBackground: #1177bb;
    --vscode-debugExceptionWidget-background: #420b0d;
    --vscode-debugExceptionWidget-border: #a31515;
    --vscode-debugToolBar-background: #333333;
    --vscode-descriptionForeground: rgba(204, 204, 204, 0.7);
    --vscode-diffEditor-insertedTextBackground: rgba(155, 185, 85, 0.2);
    --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2);
    --vscode-dropdown-background: #3c3c3c;
    --vscode-dropdown-border: #3c3c3c;
    --vscode-dropdown-foreground: #f0f0f0;
    --vscode-editor-background: #1e1e1e;
    --vscode-editor-findMatchBackground: #515c6a;
    --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
    --vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4);
    --vscode-editor-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", HelveticaNeue-Light, "Noto Sans", Meiryo, "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
    --vscode-editor-font-size: 13px;
    --vscode-editor-font-weight: normal;
    --vscode-editor-foreground: #d4d4d4;
    --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25);
    --vscode-editor-inactiveSelectionBackground: #3a3d41;
    --vscode-editor-lineHighlightBorder: #282828;
    --vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04);
    --vscode-editor-selectionBackground: #264f78;
    --vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.15);
    --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72);
    --vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72);
    --vscode-editorActiveLineNumber-foreground: #aaaaaa;
    --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1);
    --vscode-editorBracketMatch-border: #888888;
    --vscode-editorCodeLens-foreground: #999999;
    --vscode-editorCursor-foreground: #aeafad;
    --vscode-editorError-foreground: #ea4646;
    --vscode-editorGroup-border: #444444;
    --vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5);
    --vscode-editorGroupHeader-noTabsBackground: #1e1e1e;
    --vscode-editorGroupHeader-tabsBackground: #252526;
    --vscode-editorGutter-addedBackground: #587c0c;
    --vscode-editorGutter-background: #1e1e1e;
    --vscode-editorGutter-deletedBackground: #94151b;
    --vscode-editorGutter-modifiedBackground: #0c7d9d;
    --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7);
    --vscode-editorHoverWidget-background: #2d2d30;
    --vscode-editorHoverWidget-border: #454545;
    --vscode-editorIndentGuide-activeBackground: #707070;
    --vscode-editorIndentGuide-background: #404040;
    --vscode-editorInfo-foreground: #008000;
    --vscode-editorLineNumber-activeForeground: #aaaaaa;
    --vscode-editorLineNumber-foreground: #5a5a5a;
    --vscode-editorLink-activeForeground: #4e94ce;
    --vscode-editorMarkerNavigation-background: #2d2d30;
    --vscode-editorMarkerNavigationError-background: #ea4646;
    --vscode-editorMarkerNavigationInfo-background: #008000;
    --vscode-editorMarkerNavigationWarning-background: #4d9e4d;
    --vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
    --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3);
    --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
    --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
    --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5);
    --vscode-editorOverviewRuler-deletedForeground: rgba(0, 122, 204, 0.6);
    --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
    --vscode-editorOverviewRuler-findMatchForeground: rgba(246, 185, 77, 0.7);
    --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5);
    --vscode-editorOverviewRuler-infoForeground: rgba(18, 18, 136, 0.7);
    --vscode-editorOverviewRuler-modifiedForeground: rgba(0, 122, 204, 0.6);
    --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6);
    --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8);
    --vscode-editorOverviewRuler-warningForeground: rgba(18, 136, 18, 0.7);
    --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8);
    --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8);
    --vscode-editorPane-background: #1e1e1e;
    --vscode-editorRuler-foreground: #5a5a5a;
    --vscode-editorSuggestWidget-background: #2d2d30;
    --vscode-editorSuggestWidget-border: #454545;
    --vscode-editorSuggestWidget-foreground: #d4d4d4;
    --vscode-editorSuggestWidget-highlightForeground: #0097fb;
    --vscode-editorSuggestWidget-selectedBackground: #073655;
    --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67);
    --vscode-editorWarning-foreground: #4d9e4d;
    --vscode-editorWhitespace-foreground: rgba(227, 228, 226, 0.16);
    --vscode-editorWidget-background: #2d2d30;
    --vscode-editorWidget-border: #454545;
    --vscode-errorForeground: #f48771;
    --vscode-extensionButton-prominentBackground: #327e36;
    --vscode-extensionButton-prominentForeground: #ffffff;
    --vscode-extensionButton-prominentHoverBackground: #28632b;
    --vscode-focusBorder: rgba(14, 99, 156, 0.6);
    --vscode-foreground: #cccccc;
    --vscode-gitDecoration-addedResourceForeground: #81b88b;
    --vscode-gitDecoration-conflictingResourceForeground: #6c6cc4;
    --vscode-gitDecoration-deletedResourceForeground: #c74e39;
    --vscode-gitDecoration-ignoredResourceForeground: #a7a8a9;
    --vscode-gitDecoration-modifiedResourceForeground: #e2c08d;
    --vscode-gitDecoration-submoduleResourceForeground: #8db9e2;
    --vscode-gitDecoration-untrackedResourceForeground: #73c991;
    --vscode-input-background: #3c3c3c;
    --vscode-input-foreground: #cccccc;
    --vscode-inputOption-activeBorder: #007acc;
    --vscode-inputValidation-errorBackground: #5a1d1d;
    --vscode-inputValidation-errorBorder: #be1100;
    --vscode-inputValidation-infoBackground: #063b49;
    --vscode-inputValidation-infoBorder: #007acc;
    --vscode-inputValidation-warningBackground: #352a05;
    --vscode-inputValidation-warningBorder: #b89500;
    --vscode-list-activeSelectionBackground: #094771;
    --vscode-list-activeSelectionForeground: #ffffff;
    --vscode-list-dropBackground: #383b3d;
    --vscode-list-errorForeground: #ea4646;
    --vscode-list-focusBackground: #073655;
    --vscode-list-highlightForeground: #0097fb;
    --vscode-list-hoverBackground: #2a2d2e;
    --vscode-list-inactiveFocusBackground: #313135;
    --vscode-list-inactiveSelectionBackground: #3f3f46;
    --vscode-list-invalidItemForeground: #b89500;
    --vscode-list-warningForeground: #4d9e4d;
    --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
    --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
    --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
    --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
    --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
    --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
    --vscode-notificationCenterHeader-background: #3b3b3e;
    --vscode-notificationLink-foreground: #4080d0;
    --vscode-notifications-background: #2d2d30;
    --vscode-notifications-border: #3b3b3e;
    --vscode-panel-background: #1e1e1e;
    --vscode-panel-border: rgba(128, 128, 128, 0.35);
    --vscode-panel-dropBackground: rgba(255, 255, 255, 0.12);
    --vscode-panelTitle-activeBorder: rgba(128, 128, 128, 0.35);
    --vscode-panelTitle-activeForeground: #e7e7e7;
    --vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.5);
    --vscode-peekView-border: #007acc;
    --vscode-peekViewEditor-background: #001f33;
    --vscode-peekViewEditor-matchHighlightBackground: rgba(255, 143, 0, 0.6);
    --vscode-peekViewEditorGutter-background: #001f33;
    --vscode-peekViewResult-background: #252526;
    --vscode-peekViewResult-fileForeground: #ffffff;
    --vscode-peekViewResult-lineForeground: #bbbbbb;
    --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3);
    --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2);
    --vscode-peekViewResult-selectionForeground: #ffffff;
    --vscode-peekViewTitle-background: #1e1e1e;
    --vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7);
    --vscode-peekViewTitleLabel-foreground: #ffffff;
    --vscode-pickerGroup-border: #3f3f46;
    --vscode-pickerGroup-foreground: rgba(0, 151, 251, 0.6);
    --vscode-progressBar-background: #0e70c0;
    --vscode-scrollbar-shadow: #000000;
    --vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4);
    --vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4);
    --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
    --vscode-settings-modifiedItemForeground: #73c991;
    --vscode-sideBar-background: #252526;
    --vscode-sideBar-dropBackground: rgba(255, 255, 255, 0.12);
    --vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2);
    --vscode-sideBarTitle-foreground: #bbbbbb;
    --vscode-statusBar-background: #007acc;
    --vscode-statusBar-debuggingBackground: #cc6633;
    --vscode-statusBar-debuggingForeground: #ffffff;
    --vscode-statusBar-foreground: #ffffff;
    --vscode-statusBar-noFolderBackground: #68217a;
    --vscode-statusBar-noFolderForeground: #ffffff;
    --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
    --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12);
    --vscode-statusBarItem-prominentBackground: #388a34;
    --vscode-statusBarItem-prominentHoverBackground: #369432;
    --vscode-tab-activeBackground: #1e1e1e;
    --vscode-tab-activeForeground: #ffffff;
    --vscode-tab-border: #252526;
    --vscode-tab-inactiveBackground: #2d2d2d;
    --vscode-tab-inactiveForeground: rgba(255, 255, 255, 0.5);
    --vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, 0.5);
    --vscode-tab-unfocusedInactiveForeground: rgba(255, 255, 255, 0.25);
    --vscode-terminal-ansiBlack: #000000;
    --vscode-terminal-ansiBlue: #2472c8;
    --vscode-terminal-ansiBrightBlack: #666666;
    --vscode-terminal-ansiBrightBlue: #3b8eea;
    --vscode-terminal-ansiBrightCyan: #29b8db;
    --vscode-terminal-ansiBrightGreen: #23d18b;
    --vscode-terminal-ansiBrightMagenta: #d670d6;
    --vscode-terminal-ansiBrightRed: #f14c4c;
    --vscode-terminal-ansiBrightWhite: #e5e5e5;
    --vscode-terminal-ansiBrightYellow: #f5f543;
    --vscode-terminal-ansiCyan: #11a8cd;
    --vscode-terminal-ansiGreen: #0dbc79;
    --vscode-terminal-ansiMagenta: #bc3fbc;
    --vscode-terminal-ansiRed: #cd3131;
    --vscode-terminal-ansiWhite: #e5e5e5;
    --vscode-terminal-ansiYellow: #e5e510;
    --vscode-terminal-foreground: #cccccc;
    --vscode-terminal-selectionBackground: rgba(255, 255, 255, 0.25);
    --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
    --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
    --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4);
    --vscode-textLink-activeForeground: #4080d0;
    --vscode-textLink-foreground: #4080d0;
    --vscode-textPreformat-foreground: #d7ba7d;
    --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18);
    --vscode-titleBar-activeBackground: #3c3c3c;
    --vscode-titleBar-activeForeground: #cccccc;
    --vscode-titleBar-inactiveBackground: rgba(60, 60, 60, 0.6);
    --vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6);
    --vscode-widget-shadow: #000000;
}