Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Design and implementation of "Markdown to Googl...

Design and implementation of "Markdown to Google Slides" / phpconfuk 2025

Avatar for Ken’ichiro Oyama

Ken’ichiro Oyama

November 08, 2025
Tweet

More Decks by Ken’ichiro Oyama

Other Decks in Technology

Transcript

  1. Design and implementation of "Markdown to Google Slides" ⼩⼭健⼀郎 /

    Tailor Inc. 2025.11.8 PHPカンファレンス福岡2025
  2. 少し実⽤的で⼩さなOSSを書くのが趣味 • GitHub: @k1LoW • X: @k1LoW • SWE at

    Tailor Inc. / Fukuoka.go ⼩⼭健⼀郎 ⾃⼰紹介 3 Ken'ichiro Oyama
  3. • スライド ... プレゼンテーションをするときに使⽤する複数ページにわたるコンテンツ(ファイル) • スライドページ ... スライドのうちの1ページのコンテンツ • (スライド)レイアウト

    ... スライドページごとに設定されるコンテンツのレイアウト設定(タイトルや本 ⽂の数や位置など) • (スライド)テンプレート ... レイアウトの集合 ⽤語の定義 5
  4. • Marp • Slidev • Deckset • reveal.js • md2key

    • md2googleslides • etc... Before v0 9 Markdownを⼊⼒としたスライド作成ツール
  5. • Generate Google Slides from markdown ◦ https://github.com/googleworkspace/md2googleslides ◦ This

    project was developed as an example of how to use the Slides API. ◦ While it does not yet produce stunningly beautiful decks, you are encouraged to use this tool for quickly prototyping presentations. • 3年前からメンテナンスが滞っており、そのままではもう動かない ◦ Fork版の https://github.com/wescpy/md2googleslides を使⽤ ▪ Thx kromiii! https://zenn.dev/kromiii/scraps/95fbc1dcf251a0 • スライド作成の体験がとても良かった md2googleslides Before v0 12
  6. • Markdownから別のスライド作成ツール(Google Slides)上にスライドコンテンツを作成できる ◦ Markdownから直接スライドやプレゼンテーションを作成しているのではない。 • Markdownでは表現できない構成をGoogle Slides側で実現できる ◦ Google

    Slidesはやはりスライド作成ツール。当然ながら明らかにMarkdownより表現の幅が広い • MarkdownとGoogle Slides、2つのエディタを持つことで強⼒なコンテンツ作成システムになっている ◦ ⼀種のスライド作成パイプライン "Markdown to Google Slides" Before v0 13
  7. • md2googleslidesでGoogle SlidesとGoogle Slides APIに可能性を感じた • md2googleslidesのスライド作成体験が完全にはしっくりこなかった • 私のスライド作成時のメンタルモデルが「コンテンツからスライドのレイアウトが決まる」ではなくて 「選択したスライドレイアウトにコンテンツを流し込む」

    だった ◦ コンテンツの内容でスライドレイアウトが意図せず変化することを抑⽌できないスライド作成体験 に違和感があった ▪ この感覚はKeynoteやPowerPointのスライドテンプレートを使っていたことに由来していそう • Google Slidesで修正した内容が消えてしまうため、どこかのタイミングでMarkdownからの修正を⽌め、 Google Slidesのみの修正に移⾏する必要があった。 ◦ Keynote時代と同じフローではあるが、不便ではあるのでなんとかしたいと思っていた。 • ⾃分⽤ツールとして作ってみる なぜ新しくスライド作成ツールを作るのか v0 - v0.13.1 15
  8. • deckの機能を作るための主要なパッケージ(ライブラリ)は事前に揃っていた ◦ あとはやりたいことが実装できるかどうか • Google Slides API ◦ https://google.golang.org/api

    ◦ Google謹製のAPI Clientパッケージ。全部⾃動⽣成されている • Markdown ◦ https://github.com/yuin/goldmark ◦ CommonMarkのSpecを実装している狂気(褒めてる)のMarkdownパーサ • CLI Framework ◦ https://github.com/spf13/cobra ◦ 私が普段使いしているCLIフレームワーク deckの機能を⽀えるパッケージ v0 - v0.13.1 16
  9. • 何はともあれまずは1枚のスライドページにAPI経由で⽂字列を⼊⼒できるところを⽬指す ◦ MinimumもMinimumなViable Product ◦ そもそもできるかどうかを確認する • 同時に、選択したパッケージの使い⽅を獲得する ◦

    リポジトリやドキュメントにあるExampleの中でしたいことに近いものを選択して⾃分のしたいこと に近づける ◦ パッケージ全体を知るのは必要になってから。求める機能が実現できれば良い。 • ツール名を考える ◦ モチベーションに関わるので最重要 PoC v0 - v0.13.1 17
  10. • ドキュメントは https://developers.google.com/workspace/slides?hl=ja ◦ あとは https://google.golang.org/api のコード ◦ 深めの知⾒がインターネット上にほとんど⾒当たらない (紹介系、使ってみた系は多い)

    ◦ Google Slides APIでガッツリ何かを作ろうとする⼈は世の中にあまりいない印象 • クセのあるAPI ◦ 差し込むテキストデータは 構造化データではない。WYSIWYGなテキストデータ⼊⼒をAPIで表現し ている。 ▪ 「テキストを⼊⼒」「対象テキストの範囲を指定してスタイルを指定」「対象テキストの範囲 を指定してリストスタイルに変更。するとなんとテキストの位置情報がズレる 」 ◦ レイアウト変更APIが存在しない ▪ Web UIではできるのに... Google Slides API v0 - v0.13.1 19
  11. • Markdownから最低限スライドを作成できる ◦ ⽂字スタイル( Bold 、 Italic ) / リスト

    / リンク ◦ <br> タグの改⾏への変換 • Markdown内のコメントでスライドページのレイアウトを指定する⽅式 ◦ <!-- {"layout":"section-dark"} -->
 ◦ md2googleslidesと違って⾃動判定ではない ◦ コンテンツはGoogle Slides側に存在するプレースホルダに流し込む • デザインも含めて完成したページを消さないように freeze 設定を追加 • Google Slides APIのレートリミットに引っかかりまくるので v0.1.0 の時点でHTTP Clientにリトライ機構 を組み込んでいる v0.13.1までの主な機能 v0 - v0.13.1 20
  12. • Continuous deck building ◦ Generate and modify deck iteratively.

    ◦ 「さっき修正した内容が次の更新反映で意図せず消えてしまう」を可能な限り抑⽌する • Separate content and design ◦ Markdown for content, Google Slides for design. ▪ 例えば、Webアプリケーション開発において、コンテンツ(バックエンドAPI)とデザイン (フロントエンド)が分離しているのは⾃然 ◦ 2つのエディタ(MarkdownとGoogle Slides)を⾏き来できることを⽬指す Key Concept v0 - v0.13.1 22
  13. • 更新前後で既存スライドページやコンテンツをできるだけ維持する ◦ 「同じページIndex && 同じレイアウト」のスライドページは再作成せずプレースホルダ内だけを更 新 ▪ Google Slides

    Web UI経由で登録された画像も消さない ▪ プレースホルダの位置やサイズの調整をしていてもそのまま残す ◦ 存在しないスライドは新規作成 ◦ レイアウト変更を削除‧再作成で実現 ◦ 逆にいうと 「違うページIndex || 違うレイアウト」である場合は別スライドページとして再作成処理 が⾛ってしまう ▪ まだコンテンツ内容をみたスライドページ移動判定はしていない。 ◦ まだコンテンツ内容の更新判定もしていない。全てのページでプレースホルダ内の更新が常に実⾏ • Initial commit のときからmd2googleslidesとは異なるスライド更新ロジックを志向 この時点でのスライド更新ロジック v0 - v0.13.1 23
  14. • この時点でPHPerKaigi 2025の発表資料を作成できた。 ◦ https://speakerdeck.com/k1low/phperkaigi-2025 ◦ 多少の不便はあるが、私の⽤途に合わせて設計しただけあって個⼈的体験としては⼗分。 • ⾃⾝のはてなブログでdeckの紹介エントリを公開 ◦

    "私的MarkdownとGoogle Slidesでスライドを作成する⽅法(またはdeckの紹介)" ▪ https://k1low.hatenablog.com/entry/2025/03/31/083000 • 紹介エントリも書いたし、直近の登壇機会もなかったため開発は落ち着いていた ブログエントリ公開 v0.13.2 - v0.30.0 25
  15. • エントリ公開から約1ヶ⽉たって⾵向きが変わってきた。 ◦ 「 tadsan さんが興味を持ちはじめたようです」 ▪ Emacsでdeckを使うためのマイナーモードdeck-slides.el が誕⽣ •

    https://github.com/zonuexe/deck-slides.el ◦ 「 lacolaco さんが興味を持ちはじめたようです」 ▪ スライド作成にトライしてくれて バグをIssueを登録してくれる ように ▪ deckで⾜りないところを補助ツールを作成して解決していることをエントリで紹介 ▪ TSKaigi 2025で実際にdeckを利⽤して発表 ◦ 「 hanhan1978 さんが興味を持ちはじめたようです」 ▪ バグ修正や、Vimで使⽤するためのPull request • OSSらしい 利⽤者からのフィードバックを得て改善に繋げるループがまわりはじめた OSSらしいフィードバックループ v0.13.2 - v0.30.0 26
  16. • 全スライドページが更新対象のため ページが増えるほどに遅くなる • 同⼀ページ判定はページIndexのみのためスライドページ再作成が頻繁に発⽣。 Google Slides Web UI側 で⾏った編集内容が消えてしまう

    ◦ ページの挿⼊‧削除に弱い ◦ どの要素の追加‧削除なのかを決定するためには各要素を追跡する識別⼦が必要 ◦ ref: "k1LoW/deck: MarkdownでGoogleスライドを作る" lacolaco's marginalia ▪ https://blog.lacolaco.net/posts/deck-markdown-to-google-slides/ スライド更新が遅い問題 / ページ内容を維持できない問題 v0.13.2 - v0.30.0 27
  17. • MarkdownとGoogle Slidesそれぞれのスライド情報をその共通の中間構造 • Virtual DOMと同じイメージでVirtualスライドページとして deck.Slide 構造体を定義 ◦ スライドは

    []deck.Slide としてスライスで表現 • Markdownのスライドページ情報もGoogle Slidesのスライドページ情報も⼀旦 deck.Slide に変換してか ら操作する形に実装を更新 • MarkdownとGoogle Slidesは持っている情報量が⾮対称なので完全な中間構造を作成するのは不可能。し かし必要。 Virtualスライドページ v0.13.2 - v0.30.0 29
  18. • 「移動前のスライドの各スライドページのIndex」を「移動後スライドの各スライドページIndex」に 割り 当てる(マッピングする)問題 としてとらえる。 • できるだけ似ているスライドページ同⼠をマッピングすることが望ましい。特にレイアウトが同じである ことが望ましい ◦ deck.Slide

    間の 類似度 を算出できるようにする • スライドページの追加や削除もあるが、更新前後のスライドページ数を調整し同数にすることで N:Nの割 り当て問題 に落とし込む ◦ N:Nにするのがポイント スライドページの移動を「割り当て問題」としてとらえる v0.13.2 - v0.30.0 31
  19. • 3⼈の従業員(A, B, C)がいる • 3つの仕事(仕事1, 仕事2, 仕事3)がある • 各従業員が各仕事をした場合のコスト(または利益)がわかっている

    • 各従業員に1つずつ仕事を割り当てて、全体のコストを最⼩(または利益を最⼤)にしたい • これを「割り当て問題(Assignment Problem)」と呼ぶ 割り当て問題 v0.13.2 - v0.30.0 32
  20. • 従業員 → 更新前のスライド • 仕事 → 更新後のスライド • 利益

    → スライドページ同⼠の類似度 つまり、 更新前の各スライドページを更新後のどのスライドページに対応させれば、全体として最も類似度が⾼ くなるか を⾒つける問題 スライドページの移動問題への変換 v0.13.2 - v0.30.0 33
  21. スライドページ deck.Slide 間の類似度が計算できたら更新前後のスライドを before 、 after としてスライ スを作成し、類似度のマトリックスを作成 v0.13.2 -

    v0.30.0 35 類似度マトリックスを作成する Step1 - ハンガリアン法 after[0] after[1] after[2] before[0] 100 30 20 before[1] 20 25 110 before[2] 30 105 25
  22. 「類似度 最⼤ 」から「コスト 最⼩ 」の問題にするためにそれぞれの値を 最⼤値110から 引く v0.13.2 - v0.30.0

    36 コストマトリックスに変換 Step2 - ハンガリアン法 after[0] after[1] after[2] before[0] 10 80 90 before[1] 90 85 0 before[2] 80 5 85
  23. 例えば before[0] の⾏では 10 を引く v0.13.2 - v0.30.0 37 各⾏から⾏の最⼩値を引く

    Step3 - ハンガリアン法 after[0] after[1] after[2] before[0] 0 70 80 before[1] 90 85 0 before[2] 75 0 80
  24. 結果 • before[0] → after[0] • before[1] → after[2] •

    before[2] → after[1] v0.13.2 - v0.30.0 39 コスト0を⾒て割り当てを探す Step5 - ハンガリアン法 after[0] after[1] after[2] before[0] 0 70 80 before[1] 90 85 0 before[2] 75 0 80
  25. • 割り当て情報から Google Slides APIのリクエストに変換できるようなスライド操作(アクション)にしな ければならない • 「Google Slides APIでリクエストできるスライド操作」を意識する必要があり、なかなかバグがない実装

    を作ることができなかった • 実際にGoogle Slides APIを使うことができればテストできるが、その実⾏スピードは遅く、早いフィード バックループを回すことができなかった。 • Virtualスライドページの列に⽣成したアクションを渡すことで、操作後のVirtualスライドページの列が受 け取れるような簡易 Google Slides 操作エミュレータのようなテストヘルパーを作成 • これが功を奏してバグを潰すことができ、最終的にはファジングテストができるところまで持っていくこ とができた。 簡易Google Slides操作エミュレータ v0.13.2 - v0.30.0 41
  26. • --watch モードの追加 • 画像対応 • コードブロック対応 • エラーレポート機能 ◦

    詳細はFukuoka.go #22で発表 ◦ https://speakerdeck.com/k1low/fukuoka-dot-go-number-22 v0.30.0までに追加されたその他の主な機能 v0.13.2 - v0.30.0 42
  27. • CLIの「引数(args)は実⾏のために必須」、「flagsはオプション」という切り分けをすることが多い • deck apply [PRESENTATION_ID] [DECK_FILE] と(Google Slides の)PresentationIDが必須である

    ことに違和感を覚えつつ仕⽅なく必須の引数にしていた ◦ 本当は terraform apply のようにしたかった • SongmuさんよりYAMLフロントマターの導⼊とCLIのコマンドインターフェイスの変更提案を受けた ◦ Markdown内のフロントマターにPresentationIDを設定できる ◦ = deck applyで必ずしもPresentationIDを指定しなくても良い ◦ = PresentationIDの指定をflagsにすることが可能!!! • これを思いつかなかったのが当時⾮常に悔しかった • This is Songmu's work CLIのコマンドインターフェイスのBREAKING CHANGE v0.31.0 - v1 45
  28. • v0.31.0 まではk1LoWのユースケースに対応できる最低限のMarkdown -> deck.Slide 変換ロジックだっ た ◦ 改⾏の取り扱いなどヒューリスティックな実装も多かった •

    CommonMark Spec に沿って、MarkdownがGoogle Slides上のスライドページの表現に変換できるよう に多くの修正がされた ◦ https://spec.commonmark.org/ • 結果としてdeckの品質が⼤幅に向上 ◦ スライドページ類似度判定や、MarkdownとGoogle Slidesの相互変換の精度が上がった ◦ 多くのMarkdown記法がサポートされるようになった • This is Songmu's work Markdownのvalidな変換 v0.31.0 - v1 46
  29. • deck内のGoogle Slides API callを全⾯的にBatch callに移⾏ • 画像アップロードもキャッシュなど様々な⼯夫で当初より15%⾼速化 • ここら辺の⾼速化お話はおそらく6⽇後にSongmuさん⾃ら発表してくれるはずなので割愛

    ◦ "k1LoW/deckを急激に(100倍以上)⾼速化する⽅法 YAPC::Fukuoka 2025 ▪ https://fortee.jp/yapc-fukuoka-2025/proposal/15e6392e-8647-4b4e-856a-6067e82a8d3a ◦ 私も知りたい ◦ (Google Slides APIを解析するために独⾃ツールを作成したという噂がある) • This is Songmu's work Google Slides API call最適化 / 画像アップロード効率化 v0.31.0 - v1 47
  30. • 当初k1LoWとSongmuで意⾒が分かれていた機能 • 「Headingレベルからの動的タイトル検出」 ◦ Songmu ... そのスライドページ内の最⼩のHeadingレベルを判定してタイトルプレースホルダに⼊ れる機能として提案 ◦

    k1LoW ... 当初あまり必要性を感じなかった(⼀⽅で既存ユースケースに影響がないとも思ってい た)。 • 「レイアウトの動的判定設定」 ◦ k1LoW ... どのレイアウトを適⽤するかの判定条件を設定できる機能として提案 ◦ Songmu ... 複雑になることを懸念。固定の判定機能を⼊れるだけで⼗分ではないか。 1つのOSSにおける2つの異なるデザイン v0.31.0 - v1 48 Headingレベルからの動的タイトル検出 / レイアウトの動的判定設定
  31. • そもそもスライドの作り⽅が違う ◦ Songmu ... スライド全体を1ドキュメントとしてとらえて Headingを活⽤。最後にスライドページに 分割する ◦ k1LoW

    ... スライドページを1ドキュメントとしてとらえて 、毎ページごとに h1 を使⽤する • レイアウトの⾃動適⽤機能についての最初の考えが違う ◦ Songmu ... 最低限の設定 ができるところにとどめたほうがいい。キリがない ◦ k1LoW ... 汎⽤性⾼く⾊々な設定 ができたほうがいい。作り⽅が違う以上全てできるようにする • レイアウトの⾃動適⽤の設定を書く場所の提案が違う ◦ Songmu ... Markdown内に設定があるべき 。スライドに関係する設定は分散しないほうがいい。 ◦ k1LoW ... Google Slidesにあるべき 。レイアウトに関係するからデザインっぽいから。 • 当時共通していたのは「設定ファイルはできるだけ作りたくない」ということだけ 1つのOSSにおける2つの異なるデザイン v0.31.0 - v1 49 2⼈のデザインの違い
  32. • IssueやPull requestで何回か議論を続け、最終的にPodcast収録の後の開発者会議をへて両者が納得する形 に落ち着いた ◦ "趣味でOSSをやっている者だ 48: 特別編: 突発deck開発者会議 @

    2025-07-11 (k1LoW)" • 結局どう収束したのか ◦ スライドの作り⽅が違うのはツールとして許容できたほうがいい ▪ h1 と h2 のどちらがページタイトルなのかは、動的に判定してユーザが意識しないようにする ◦ DSLとして実績のあるCELを導⼊することで破綻することなく柔軟な設定を提供する ▪ https://cel.dev/ ◦ 設定はMarkdownのYAMLフロントマターに記載できると同時に 共通の設定ファイルにも 記載できる ようにする ▪ 「レイアウトの⾃動適⽤の設定はGitで管理したくなる」の⼀⾔ ▪ デフォルトのベーススライドなど、設定ファイルの要求もでてきてはいた 1つのOSSにおける2つの異なるデザイン v0.31.0 - v1 50 対話と、より良いデザイン
  33. • v1に上げる提案 ◦ https://github.com/k1LoW/deck/issues/284 • This isn't an issue I

    should be raising. Sorry for bringing it up. ◦ そんなことはない!!!! • k1LoW 1名で開発していたら、あと数年はv1にはならなかったところを後押ししてもらった ◦ こういうのも共同メンテナがいるメリット • そして、 2025/07/21 v1.0.0リリース🎉 • This is Songmu's work Bump up version to v1 v0.31.0 - v1 51
  34. • deck is a tool for creating deck using Markdown

    and Google Slides. ◦ https://github.com/k1LoW/deck ◦ 2025/11/08時点で現在 v1.21.6 ◦ 929 Star • いくつかの課題はあるが開発⾃体は落ち着いている • 2つのKey conceptは依然としてそのまま ◦ もう⼤きな機能追加はないと思う(N回⽬) • 今後 ◦ まだ海外へのPRをしていないので、そこに⼿をつけたら(もしかしたら)もう少し今までのような エキサイティングOSS活動を楽しめるかもしれない ◦ 「よく知られた枯れたツール」を⽬指したい v1 - 53