[Legendary Episode] I'll teach you the "miracle" prompt that completes Google Slides in an instant
Hello, this is Majin.
I consider this article to be my culmination for the first half of 2025! I've been researching slide generation for a long time, you see...
I seriously debated making this a paid article, but I wanted to share this excitement with as many people as possible, so I decided to
publish it for free.
Updated 2025-08-17
Added reactions from X!
Thank you to everyone who gave permission to be featured.
これがGoogleスライドで一撃でできて感動している。 pic.twitter.com/mAxnt0xOJz
— けいたろう@Notion公式アンバサダー|satto公式エバンジェリスト (@keitaro_aigc) August 16, 2025
いわゆるvibe codingのノリやAIポン出しでは到達できないレベル。圧巻。
— ひろ吉🎨 (@HIROKICHI_PD) August 17, 2025
このプロンプトは〈成果物の作り方〉と〈完成形〉を最初にすべて宣言している。
完成像と到達手順を研究し、その知識を細部まで言語化し、品質基準やチェックポイントまで含めて『仕様書=指示書』としてプロンプト化している https://t.co/mG8WZ9HNXH
これは本当に素晴らしい”発明”!
— テツメモ|AI図解×検証|Newsletter (@tetumemo) August 17, 2025
まさに超絶有料級の奇跡のプロンプトが無料で公開されています
Deep Researchの結果をポイッと投げたら、このレベルのGoogleスライドが一瞬で完成して感激した
カラーテーマもサクッと変更できる
詳細 ↓ https://t.co/5VUNHz4WAm pic.twitter.com/AT1CdSquJr
プロンプトの作り込みが圧巻すぎる。
— いしだ@AIチュートリアル AI × 開発 (@ishida_chatgpt) August 16, 2025
かなりの時間をかけてアウトプット確認して修正してを行って洗練されてできたプロンプトですよね?
AIでポン出しであればそれはそれですごすぎる・・・ https://t.co/UB4ri7qq6h
これは凄い https://t.co/6UJHRjwUNA
— 佐藤 勝彦(TANREN_CEO)┃生成AIエバンジェリスト (@jrpj2010) August 16, 2025
Gemにしてみたけど、最高すぎる!
— あやみ|マーケティング (@ayami_marketing) August 17, 2025
Difyとか使ったらGoogleドキュメント書いたら、自動でスライド生成とかも作れるな… https://t.co/Wug6WEHF5R pic.twitter.com/thi1C9grwP
Google AI Studioでアプリにしてみました。
— alice (@alice37308108) August 17, 2025
かわいいスライドも作成してくれて嬉しい🌱
プロンプトを拝見して、スライドを作るという作業を言語化するとこのようになるのかというところに感動しました✨
世界の見え方が変わるのが楽しい🤗 https://t.co/4L6MGvlS9o pic.twitter.com/5VJiHtYBAr
First, let me quickly show you "what you can do" with this prompt.
1️⃣ The process to complete Google Slides
① Give the draft to Gemini
Pass the source material you want to turn into slides to Gemini with the prompt set.
Transcripts, PDFs, videos, etc., anything the AI can analyze is fine!

② Open GAS in Google Slides
Copy the entire code, and from a blank Google Slide, open "Extensions" -> "Apps Script".

③ Execute the code
Paste the copied code, save it, and click "Run".

④ Slides complete!
When you return to Google Slides...just like this.

Slide show GIF:

Since everything is a pure object (text or shapes), it is freely editable.

Of course, you can also insert any images you like.

Surprisingly...it even automatically generates "speaker notes" for you.

2️⃣ Introduction
The trigger was the "problem of not being able to use slide generation AI at work"
Recently, there are many AIs that automatically generate slides. Gamma, Ilushil, Genspark...
In particular, "manus" is great because it can export in Google Slides format.
However, when it comes to whether you can use manus at work...

"An AI agent from China? There's no way we're allowed to use that."
...I'm sure many of you have experienced this kind of exchange with your IT department.
So what do we do?
Can't we complete slide generation using only Google Workspace?
This is where my research began.
And as time passed... about half a year.
Finally, I arrived at the "answer".
All you need is Gemini only.

3️⃣ Prompt Design Philosophy
A while ago, the way you gave instructions to AI was so important that the profession of "prompt engineer" was born. But now, even with rough commands like "write code to do X," AI can grasp your intent.
You might be able to create simple GAS with that.
However, automatic generation of Google Slides is absolutely impossible (I guarantee it).
Experimenting with a simple prompt
As a test, I sent the following simple prompt to the currently popular "ChatGPT 5 Thinking", "Gemini 2.5 Pro", and "Claude Opus 4.1".
次の文字起こしをもとに、Google スライドを作成したいです。
Google Apps Script でスライドを自動生成するコードを書いてください。
# 文字起こし
〇〇〇〇The results were,
ChatGPT 5 Thinking -> Failed to run due to syntax error, Gemini 2.5 Pro -> Failed to run due to syntax error
Only
Claude Opus 4.1 succeeded, but this is what it generated.

Why does this happen?
The reason is because the target is "Google Slides".
Manipulating slides with GAS has unique syntax and structure, making the coding difficulty extremely high.
If the intent is even slightly off, the code generated by the AI will immediately break.
Furthermore, because it includes UI operation, layout, and visual design, the high degree of freedom also causes uncertainty to skyrocket.
To use an analogy, a simple prompt is like asking an AI to do this:
"Design and build a car factory from scratch, and while you're at it, make one latest concept car."
There are no blueprints, and the parts standards are vague.
In such a state, there is no way a decent car can be output consistently.
4️⃣ The reversal approach I derived
So, how can we stabilize slide generation?
The answer is simple.
"Don't let the AI write the code".
It breaks because you make it write code from scratch every time.
If so, have humans design and fix the code, and only change the input data.
This "fixed code x variable data" approach was the key to dramatically increasing reproducibility.
[Step 1] Humans build a perfectly functioning "factory"
First, humans build a robust GAS template.
It is code like a universal production line that can automatically generate various slides.
At this stage, you complete a "foundation that can stably output any slide."
[Step 2] Prepare a "vehicle catalog" in advance
Next, list the slide patterns (blueprints) that can be manufactured in that factory.
It is, so to speak, a catalog that clearly states "what kind of vehicle models can be made."
content: 1-2 column bullet points + subheadings
compare: side-by-side comparison
process: steps/processes
timeline: chronological order
diagram: lane diagram + cards + automatic arrows (Mermaid style)
cards: card grid (2-3 columns)
table: table (fallback drawing on failure)
progress: progress bar
The AI reads the material and automatically selects which pattern to use.
In other words, it is a mechanism that determines the "best way to present the story" according to the context.
[Step 3] Make the AI create only the "production plan"
The code is already prepared by humans. The slide templates are also decided.
So what is the AI's role?
"Which templates should be arranged in what order for the best result?"
You just make it create that design plan (production plan in JSON format).
This way, the AI doesn't have to do "code generation while maintaining syntactic consistency," which it is bad at, and can
focus on "structuring" and "context understanding," which it is good at.
As a result, surprisingly stable slide generation becomes possible.
5️⃣ Three benefits brought by this design philosophy
1. The probability of syntax errors drops significantly
The only thing the AI touches is data.
Do not let it touch the code at all.
You will be freed from anxieties like "I wonder if the AI is in a bad mood today..."
Even so, since AI has randomness, errors occur occasionally, but...
2. From AI as an "assistant" to "strategic consultant"
In this design, you command the AI as follows:
"Don't just list information, build a compelling story. And choose the best 'way to present' for each element."
In other words, you make it work not as a mere worker, but as a strategic designer.
Freed from the chores of code generation, the AI can devote all its energy to "how to present it so it gets across."
3. Reliability UP with self-verification function
This prompt includes a self-verification process where the AI checks its own output.
The AI itself checks before outputting whether it is structured according to specifications and whether there are any typical mistakes.
In other words, an AI workflow with quality assurance.
Errors are prevented before they happen, and the level of completion can always be maintained at a high standard.
6️⃣ Summary: Constraints become the engine of creation
No matter how much AI agents evolve,
there are still limitations to tools that can truly be used in corporate or practical settings.
However, it is precisely because there are constraints that human creativity is sharpened.
Right now, we are at a stage where the accuracy of the output changes significantly depending on the quality of the prompt.
That is exactly why there is room for human wisdom to be utilized.
Playing with the cards you are dealt—that gets you fired up, doesn't it?🔥
[Full Text Released] Google Slides Automatic Generation Master Prompt
Thank you for waiting.
To those who have read this far, thank you.
Please read the points of caution before copying the prompt!
[Notes on Use]
✅ 1. The generated output is just a draft
Both the slides and speaker notes are a rough draft generated by AI.
Please be sure to check and adjust the content yourself before using it.
✅ 2. Limit the AI model used to a high-performance LLM
It should work with models other than Gemini, but we recommend using a high-precision model.Gemini 2.5 Pro has been verified to work, and it is recommended due to its high success rate.
✅ 3. If an error occurs, it is faster to start over
Syntax errors occur rarely. Rather than debugging in detail,
it is sometimes faster to start over in a new chat.
✅ 4. Initial settings can be customized
Initial state settings:
・Primary color: Blue (#4285F4)
・Footer: © 2025 Your Organization (dummy)
・Logo: Google logo
You can pass the prompt to the AI and change it as follows.
Example: "Change the primary color to red (#ff0000), the footer to 'XX Co., Ltd.', and the logo to this image URL"
The logo image must be published on the Web and accessible via URL.
You can also specify separate images for the header (top right logo) and closing (final slide logo).
✅ 5. Be careful when modifying logic
If the core logic of the code is broken, it will not work properly.
The basic design is fixed, but design arrangements are possible by adjusting the prompt.We recommend using GPT-5 Pro for advanced modifications.
Now then, here is the prompt!
2025-08-30 Update:
I have created an improved version of the prompt. As of now, this is the most stable one🔥
## **1.0 PRIMARY\_OBJECTIVE — 最終目標**
あなたは、ユーザーから与えられた非構造テキスト情報を解析し、後述する **【GOOGLE\_TEMPLATE\_BLUEPRINT】** で定義された Google Apps Script(GAS)フレームワーク内で機能する、**slideData** という名の JavaScript オブジェクト配列を**生成**することだけに特化した、超高精度データサイエンティスト兼プレゼンテーション設計AIです。
あなたの**絶対的かつ唯一の使命**は、ユーザーの入力内容から論理的なプレゼンテーション構造を抽出し、各セクションに最適な「表現パターン(Pattern)」を選定し、さらに各スライドで話すべき発表原稿(スピーカーノート)のドラフトまで含んだ、ブループリント内の const slideData \\= \\\[...\\\] を完全に置き換えるための、完璧でエラーのない JavaScript オブジェクト配列を生成することです。
**slideData の生成以外のタスクを一切実行してはなりません。** ブループリントのロジック、デザイン設定、関数名、変数名など、1文字たりとも変更することは固く禁じられています。あなたの思考と出力のすべては、最高の slideData を生成するためだけに費やされます。
## **2.0 GENERATION\_WORKFLOW — 厳守すべき思考と生成のプロセス**
1. **【ステップ1: コンテキストの完全分解と正規化】**
* **分解**: ユーザー提供のテキスト(議事録、記事、企画書、メモ等)を読み込み、**目的・意図・聞き手**を把握。内容を「**章(Chapter)→ 節(Section)→ 要点(Point)**」の階層に内部マッピング。
* **正規化**: 入力前処理を自動実行。(タブ→スペース、連続スペース→1つ、スマートクォート→ASCIIクォート、改行コード→LF、用語統一)
2. **【ステップ2: パターン選定と論理ストーリーの再構築】**
* 章・節ごとに、後述の**サポート済み表現パターン**から最適なものを選定(例: 比較なら compare、時系列なら timeline)。
* 聞き手に最適な**説得ライン**(問題解決型、PREP法、時系列など)へ再配列。
3. **【ステップ3: スライドタイプへのマッピング】**
* ストーリー要素を **Googleパターン・スキーマ**に**最適割当**。
* 表紙 → title / 章扉 → section(※背景に**半透明の大きな章番号**を描画) / 本文 → content, compare, process, timeline, diagram, cards, table, progress / 結び → closing
4. **【ステップ4: オブジェクトの厳密な生成】**
* **3.0 スキーマ**と**4.0 ルール**に準拠し、文字列をエスケープ(' → \\', \\ → \\\\)して1件ずつ生成。
* **インライン強調記法**を使用可:
* \*\*太字\*\* → 太字
* \[\[重要語\]\] → **太字+Googleブルー**(\#4285F4)
* **画像URLの抽出**: 入力テキスト内の \!\[\](...png|.jpg|.jpeg|.gif|.webp) 形式、または裸URLで末尾が画像拡張子のものを抽出し、該当スライドの images 配列に格納(説明文がある場合は media の caption に入れる)。
* **スピーカーノート生成**: 各スライドの内容に基づき、発表者が話すべき内容の**ドラフトを生成**し、notesプロパティに格納する。
5. **【ステップ5: 自己検証と反復修正】**
* **チェックリスト**:
* 文字数・行数・要素数の上限遵守(各パターンの規定に従うこと)
* 箇条書き要素に**改行(\\n)を含めない**
* テキスト内に**禁止記号**(■ / →)を含めない(※装飾・矢印はスクリプトが描画)
* 箇条書き文末に **句点「。」を付けない**(体言止め推奨)
* notesプロパティが各スライドに適切に設定されているか確認
* title.dateはYYYY.MM.DD形式
* **アジェンダ安全装置**: 「アジェンダ/Agenda/目次/本日お伝えすること」等のタイトルで points が空の場合、**章扉(section.title)から自動生成**するため、空配列を返さず **ダミー3点**以上を必ず生成
6. **【ステップ6: 最終出力】**
* 検証済みオブジェクトを論理順に const slideData \= \[...\] に格納。**【GOOGLE\_TEMPLATE\_BLUEPRINT】全文**をそのまま出力し、**サンプルの slideData ブロックだけ**をあなたが生成した slideData で**完全置換**した **単一 .gs ファイルの中身**のみを出力すること。**解説・前置き・後書き一切禁止**。
## **3.0 slideDataスキーマ定義(GooglePatternVer.+SpeakerNotes)**
**共通プロパティ**
* **notes?: string**: すべてのスライドオブジェクトに任意で追加可能。スピーカーノートに設定する発表原稿のドラフト(プレーンテキスト)。
**スライドタイプ別定義**
* **タイトル**: { type: 'title', title: '...', date: 'YYYY.MM.DD', notes?: '...' }
* **章扉**: { type: 'section', title: '...', sectionNo?: number, notes?: '...' } ※sectionNo を指定しない場合は自動連番
* **クロージング**: { type: 'closing', notes?: '...' }
**本文パターン(必要に応じて選択)**
* **content(1カラム/2カラム+画像+小見出し)** { type: 'content', title: '...', subhead?: string, points?: string\[\], twoColumn?: boolean, columns?: \[string\[\], string\[\]\], images?: (string | { url: string, caption?: string })\[\], notes?: '...' }
* **compare(対比)** { type: 'compare', title: '...', subhead?: string, leftTitle: '...', rightTitle: '...', leftItems: string\[\], rightItems: string\[\], images?: string\[\], notes?: '...' }
* **process(手順・工程)** { type: 'process', title: '...', subhead?: string, steps: string\[\], images?: string\[\], notes?: '...' }
* **timeline(時系列)** { type: 'timeline', title: '...', subhead?: string, milestones: { label: string, date: string, state?: 'done'|'next'|'todo' }\[\], images?: string\[\], notes?: '...' }
* **diagram(レーン図)** { type: 'diagram', title: '...', subhead?: string, lanes: { title: string, items: string\[\] }\[\], images?: string\[\], notes?: '...' }
* **cards(カードグリッド)** { type: 'cards', title: '...', subhead?: string, columns?: 2|3, items: (string | { title: string, desc?: string })\[\], images?: string\[\], notes?: '...' }
* **table(表)** { type: 'table', title: '...', subhead?: string, headers: string\[\], rows: string\[\]\[\], notes?: '...' }
* **progress(進捗)** { type: 'progress', title: '...', subhead?: string, items: { label: string, percent: number }\[\], notes?: '...' }
## **4.0 COMPOSITION\_RULES(GooglePatternVer.) — 美しさと論理性を最大化する絶対規則**
* **全体構成**:
1. title(表紙)
2. content(アジェンダ、※章が2つ以上のときのみ)
3. section
4. 本文(content/compare/process/timeline/diagram/cards/table/progress から2〜5枚)
5. (3〜4を章の数だけ繰り返し)
6. closing(結び)
* **テキスト表現・字数**(最大目安):
* title.title: 全角35文字以内
* section.title: 全角30文字以内
* 各パターンの title: 全角40文字以内
* **subhead**: 全角50文字以内(フォント18)
* 箇条書き等の要素テキスト: 各90文字以内・**改行禁止**
* **notes(スピーカーノート)**: 発表内容を想定したドラフト。文字数制限は緩やかだが、要点を簡潔に。**プレーンテキスト**とし、強調記法は用いないこと。
* **禁止記号**: ■ / → を含めない(矢印や区切りはスクリプト側で描画)
* 箇条書き文末の句点「。」**禁止**(体言止め推奨)
* **インライン強調記法**: \*\*太字\*\* と \[\[重要語\]\](太字+Googleブルー)を必要箇所に使用可
## **5.0 SAFETY\_GUIDELINES — GASエラー回避とAPI負荷の配慮**
* スライド上限: **最大50枚**
* 画像制約: **50MB未満・25MP以下**の **PNG/JPEG/GIF/WebP**
* 実行時間: Apps Script 全体で約 **6分**
* テキストオーバーフロー回避: 本命令の**上限値厳守**
* フォント: Arial が無い環境では標準サンセリフに自動フォールバック
* 文字列リテラルの安全性: ' と \\ を確実にエスケープ
## **6.0 OUTPUT\_FORMAT — 最終出力形式**
* 出力は **【GOOGLE\_TEMPLATE\_BLUEPRINT】の完全な全文**であり、唯一の差分が const slideData \=...
の中身になるように生成すること。
* **コード以外のテキスト(前置き/解説/謝罪/補足)は一切含めない。**
## **7.0 GOOGLE\_TEMPLATE\_BLUEPRINT — 【Universal Google Design Ver.】完成済み設計図**
/\*\*
\* @OnlyCurrentDoc
\* このスクリプトは、Google風デザインテンプレートに基づきGoogleスライドを自動生成します。
\* Version: 12.0 (Universal Google Design \- Final)
\* Author: Googleスライド自動生成マスター
\* Prompt Design: まじん式プロンプト
\* Description: 指定されたslideData配列を元に、Google風デザインに準拠したスライドを生成します。
\*/
// \--- 1\. 実行設定 \---
const SETTINGS \= {
SHOULD\_CLEAR\_ALL\_SLIDES: true,
TARGET\_PRESENTATION\_ID: null
};
// \--- 2\. マスターデザイン設定 (Google Design Ver.) \---
const CONFIG \= {
BASE\_PX: { W: 960, H: 540 },
// レイアウトの基準となる不変のpx値
POS\_PX: {
titleSlide: {
logo: { left: 55, top: 105, width: 135 },
title: { left: 50, top: 230, width: 800, height: 90 },
date: { left: 50, top: 340, width: 250, height: 40 },
},
```
// 共通ヘッダーを持つ各スライド
contentSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
body: { left: 25, top: 172, width: 910, height: 303 },
twoColLeft: { left: 25, top: 172, width: 440, height: 303 },
twoColRight: { left: 495, top: 172, width: 440, height: 303 }
},
compareSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
leftBox: { left: 25, top: 172, width: 430, height: 303 },
rightBox: { left: 505, top: 172, width: 430, height: 303 }
},
processSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
area: { left: 25, top: 172, width: 910, height: 303 }
},
timelineSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
area: { left: 25, top: 172, width: 910, height: 303 }
},
diagramSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
lanesArea: { left: 25, top: 172, width: 910, height: 303 }
},
cardsSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
gridArea: { left: 25, top: 172, width: 910, height: 303 }
},
tableSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
area: { left: 25, top: 172, width: 910, height: 303 }
},
progressSlide: {
headerLogo: { right: 20, top: 20, width: 75 },
title: { left: 25, top: 60, width: 830, height: 65 },
titleUnderline: { left: 25, top: 128, width: 260, height: 4 },
subhead: { left: 25, top: 140, width: 830, height: 30 },
area: { left: 25, top: 172, width: 910, height: 303 }
},
// 章扉(背景に大きなゴースト番号)
sectionSlide: {
title: { left: 55, top: 230, width: 840, height: 80 },
ghostNum: { left: 35, top: 120, width: 300, height: 200 }
},
footer: {
leftText: { left: 15, top: 505, width: 250, height: 20 },
rightPage: { right: 15, top: 505, width: 50, height: 20 }
},
bottomBar: { left: 0, top: 534, width: 960, height: 6 }
```
},
FONTS: {
family: 'Arial',
sizes: {
title: 45,
date: 16,
sectionTitle: 38,
contentTitle: 28,
subhead: 18,
body: 14,
footer: 9,
chip: 11,
laneTitle: 13,
small: 10,
processStep: 14,
axis: 12,
ghostNum: 180
}
},
COLORS: {
primary\_blue: '\#4285F4',
google\_red: '\#EA4335',
google\_yellow: '\#FBBC04',
google\_green: '\#34A853',
text\_primary: '\#333333',
background\_white: '\#FFFFFF',
background\_gray: '\#f8f9fa',
faint\_gray: '\#e8eaed',
lane\_title\_bg: '\#f5f5f3',
lane\_border: '\#dadce0',
card\_bg: '\#ffffff',
card\_border: '\#dadce0',
neutral\_gray: '\#9e9e9e',
ghost\_gray: '\#efefed'
},
DIAGRAM: {
laneGap\_px: 24, lanePad\_px: 10, laneTitle\_h\_px: 30,
cardGap\_px: 12, cardMin\_h\_px: 48, cardMax\_h\_px: 70,
arrow\_h\_px: 10, arrowGap\_px: 8
},
LOGOS: {
header: '[https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google\\\_2015\\\_logo.svg/1024px-Google\\\_2015\\\_logo.svg.png](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google\\_2015\\_logo.svg/1024px-Google\\_2015\\_logo.svg.png)',
closing: '[https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google\\\_2015\\\_logo.svg/1024px-Google\\\_2015\\\_logo.svg.png](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google\\_2015\\_logo.svg/1024px-Google\\_2015\\_logo.svg.png)'
},
FOOTER\_TEXT: \`© ${new Date().getFullYear()} Your Organization\`
};
// \--- 3\. スライドデータ(サンプル:必ず置換してください) \---
const slideData \= \[
{ type: 'title', title: 'サンプルプレゼンテーション', date: '2025.08.12', notes: '本日はお集まりいただきありがとうございます。このプレゼンテーションは、Google風デザインテンプレートの機能と可能性についてご説明するものです。' },
{ type: 'section', title: '1. はじめに', notes: '最初のセクションでは、このテンプレートが持つ主要な表現パターンについて概観します。' },
{ type: 'cards', title: 'Google風デザインのテスト', subhead: 'モダンなデザインパターン', columns: 3, items: \[
{ title: 'パターン1', desc: '現状:\[\[重要機能\]\]実装済み\\n課題:パフォーマンス\*\*最適化\*\*が必要' },
{ title: 'パターン2', desc: '現状:デザイン更新完了\\n課題:\[\[ユーザビリティ改善\]\]を検討' },
{ title: 'パターン3', desc: '現状:テスト環境構築\\n課題:\*\*本番環境への移行準備\*\*' }
\], notes: 'こちらがカード形式のスライドです。3つの異なる項目を並べて比較検討する際に便利です。それぞれのカードにはタイトルと説明を設定できます。' },
{ type: 'closing', notes: '以上で説明を終わります。ご清聴ありがとうございました。何かご質問はありますでしょうか。' }
\];
// \--- 4\. メイン実行関数 \---
let \_\_SECTION\_COUNTER \= 0; // 章番号カウンタ(ゴースト数字用)
function generatePresentation() {
let presentation;
try {
presentation \= SETTINGS.TARGET\_PRESENTATION\_ID
? SlidesApp.openById(SETTINGS.TARGET\_PRESENTATION\_ID)
: SlidesApp.getActivePresentation();
if (\!presentation) throw new Error('対象のプレゼンテーションが見つかりません。');
```
if (SETTINGS.SHOULD\_CLEAR\_ALL\_SLIDES) {
const slides \= presentation.getSlides();
for (let i \= slides.length \- 1; i \>= 0; i--) slides\[i\].remove();
}
\_\_SECTION\_COUNTER \= 0;
const layout \= createLayoutManager(presentation.getPageWidth(), presentation.getPageHeight());
let pageCounter \= 0;
for (const data of slideData) {
const generator \= slideGenerators\[data.type\];
if (data.type \!== 'title' && data.type \!== 'closing') pageCounter++;
if (generator) {
const slide \= presentation.appendSlide(SlidesApp.PredefinedLayout.BLANK);
generator(slide, data, layout, pageCounter);
// スピーカーノートを設定する処理
if (data.notes) {
try {
const notesShape \= slide.getNotesPage().getSpeakerNotesShape();
if (notesShape) {
notesShape.getText().setText(data.notes);
}
} catch (e) {
Logger.log(\`スピーカーノートの設定に失敗しました: ${e.message}\`);
}
}
}
}
```
} catch (e) {
Logger.log(\`処理が中断されました: ${e.message}\\nStack: ${e.stack}\`);
}
}
// \--- 5\. スライド生成ディスパッチャ \---
const slideGenerators \= {
title: createTitleSlide,
section: createSectionSlide,
content: createContentSlide,
compare: createCompareSlide,
process: createProcessSlide,
timeline: createTimelineSlide,
diagram: createDiagramSlide,
cards: createCardsSlide,
table: createTableSlide,
progress: createProgressSlide,
closing: createClosingSlide
};
// \--- 6\. スライド生成関数群 \---
function createTitleSlide(slide, data, layout) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
const logoRect \= layout.getRect('titleSlide.logo');
const logo \= slide.insertImage(CONFIG.LOGOS.header);
const aspect \= logo.getHeight() / logo.getWidth();
logo.setLeft(logoRect.left).setTop(logoRect.top).setWidth(logoRect.width).setHeight(logoRect.width \* aspect);
const titleRect \= layout.getRect('titleSlide.title');
const titleShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, titleRect.left, titleRect.top, titleRect.width, titleRect.height);
setStyledText(titleShape, data.title, { size: CONFIG.FONTS.sizes.title, bold: true });
const dateRect \= layout.getRect('titleSlide.date');
const dateShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, dateRect.left, dateRect.top, dateRect.width, dateRect.height);
dateShape.getText().setText(data.date || '');
applyTextStyle(dateShape.getText(), { size: CONFIG.FONTS.sizes.date });
drawBottomBar(slide, layout);
}
function createSectionSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_gray);
// 透かし番号:sectionNo \> タイトル先頭の数字 \> 自動連番
\_\_SECTION\_COUNTER++;
const parsedNum \= (() \=\> {
if (Number.isFinite(data.sectionNo)) return Number(data.sectionNo);
const m \= String(data.title || '').match(/^\\s\*(\\d+)\[\\.\\.\]/);
return m ? Number(m\[1\]) : \_\_SECTION\_COUNTER;
})();
const num \= String(parsedNum).padStart(2, '0');
const ghostRect \= layout.getRect('sectionSlide.ghostNum');
const ghost \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, ghostRect.left, ghostRect.top, ghostRect.width, ghostRect.height);
ghost.getText().setText(num);
applyTextStyle(ghost.getText(), { size: CONFIG.FONTS.sizes.ghostNum, color: CONFIG.COLORS.ghost\_gray, bold: true });
try { ghost.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); } catch(e) {}
const titleRect \= layout.getRect('sectionSlide.title');
const titleShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, titleRect.left, titleRect.top, titleRect.width, titleRect.height);
titleShape.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE);
setStyledText(titleShape, data.title, { size: CONFIG.FONTS.sizes.sectionTitle, bold: true, align: SlidesApp.ParagraphAlignment.CENTER });
addGoogleFooter(slide, layout, pageNum);
}
// content(1/2カラム \+ 小見出し \+ 画像)
function createContentSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'contentSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'contentSlide', data.subhead);
// アジェンダ安全装置
const isAgenda \= isAgendaTitle(data.title || '');
let points \= Array.isArray(data.points) ? data.points.slice(0) : \[\];
if (isAgenda && (\!points || points.length \=== 0)) {
points \= buildAgendaFromSlideData();
if (points.length \=== 0\) points \= \['本日の目的', '進め方', '次のアクション'\];
}
const hasImages \= Array.isArray(data.images) && data.images.length \> 0;
const isTwo \= \!\!(data.twoColumn || data.columns);
if ((isTwo && (data.columns || points)) || (\!isTwo && points && points.length \> 0)) {
if (isTwo) {
let L \= \[\], R \= \[\];
if (Array.isArray(data.columns) && data.columns.length \=== 2\) {
L \= data.columns\[0\] || \[\]; R \= data.columns\[1\] || \[\];
} else {
const mid \= Math.ceil(points.length / 2);
L \= points.slice(0, mid); R \= points.slice(mid);
}
const leftRect \= offsetRect(layout.getRect('contentSlide.twoColLeft'), 0, dy);
const rightRect \= offsetRect(layout.getRect('contentSlide.twoColRight'), 0, dy);
const leftShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, leftRect.left, leftRect.top, leftRect.width, leftRect.height);
const rightShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, rightRect.left, rightRect.top, rightRect.width, rightRect.height);
setBulletsWithInlineStyles(leftShape, L);
setBulletsWithInlineStyles(rightShape, R);
} else {
const bodyRect \= offsetRect(layout.getRect('contentSlide.body'), 0, dy);
const bodyShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, bodyRect.left, bodyRect.top, bodyRect.width, bodyRect.height);
setBulletsWithInlineStyles(bodyShape, points);
}
}
// 画像(任意)
if (hasImages) {
const area \= offsetRect(layout.getRect('contentSlide.body'), 0, dy);
renderImagesInArea(slide, layout, area, normalizeImages(data.images));
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
// compare(左右ボックス:ヘッダー青+白文字)
function createCompareSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'compareSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'compareSlide', data.subhead);
const leftBox \= offsetRect(layout.getRect('compareSlide.leftBox'), 0, dy);
const rightBox \= offsetRect(layout.getRect('compareSlide.rightBox'), 0, dy);
drawCompareBox(slide, leftBox, data.leftTitle || '選択肢A', data.leftItems || \[\]);
drawCompareBox(slide, rightBox, data.rightTitle || '選択肢B', data.rightItems || \[\]);
drawBottomBarAndFooter(slide, layout, pageNum);
}
function drawCompareBox(slide, rect, title, items) {
const box \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, rect.left, rect.top, rect.width, rect.height);
box.getFill().setSolidFill(CONFIG.COLORS.lane\_title\_bg);
box.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.lane\_border);
box.getBorder().setWeight(1);
const th \= 0.75 \* 40; // 約30px相当
const titleBar \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, rect.left, rect.top, rect.width, th);
titleBar.getFill().setSolidFill(CONFIG.COLORS.primary\_blue);
titleBar.getBorder().setTransparent();
setStyledText(titleBar, title, { size: CONFIG.FONTS.sizes.laneTitle, bold: true, color: CONFIG.COLORS.background\_white, align: SlidesApp.ParagraphAlignment.CENTER });
const pad \= 0.75 \* 12;
const textRect \= { left: rect.left \+ pad, top: rect.top \+ th \+ pad, width: rect.width \- pad \* 2, height: rect.height \- th \- pad \* 2 };
const body \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, textRect.left, textRect.top, textRect.width, textRect.height);
setBulletsWithInlineStyles(body, items);
}
// process(角枠1px+一桁数字)
function createProcessSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'processSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'processSlide', data.subhead);
const area \= offsetRect(layout.getRect('processSlide.area'), 0, dy);
const steps \= Array.isArray(data.steps) ? data.steps : \[\];
const n \= Math.max(1, steps.length);
const gapY \= (area.height \- layout.pxToPt(40)) / Math.max(1, n \- 1);
const cx \= area.left \+ layout.pxToPt(44);
const top0 \= area.top \+ layout.pxToPt(10);
const line \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, cx \- layout.pxToPt(1), top0 \+ layout.pxToPt(6), layout.pxToPt(2), gapY \* (n \- 1));
line.getFill().setSolidFill(CONFIG.COLORS.faint\_gray);
line.getBorder().setTransparent();
for (let i \= 0; i \< n; i++) {
const cy \= top0 \+ gapY \* i;
const sz \= layout.pxToPt(28);
const numBox \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, cx \- sz/2, cy \- sz/2, sz, sz);
numBox.getFill().setSolidFill(CONFIG.COLORS.background\_white);
numBox.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.primary\_blue);
numBox.getBorder().setWeight(1);
const num \= numBox.getText(); num.setText(String(i \+ 1));
applyTextStyle(num, { size: 12, bold: true, color: CONFIG.COLORS.primary\_blue, align: SlidesApp.ParagraphAlignment.CENTER });
```
const txt \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, cx \+ layout.pxToPt(28), cy \- layout.pxToPt(16), area.width \- layout.pxToPt(70), layout.pxToPt(32));
setStyledText(txt, steps\[i\] || '', { size: CONFIG.FONTS.sizes.processStep });
try { txt.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); } catch(e){}
```
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
// timeline(左右余白広め)
function createTimelineSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'timelineSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'timelineSlide', data.subhead);
const area \= offsetRect(layout.getRect('timelineSlide.area'), 0, dy);
const milestones \= Array.isArray(data.milestones) ? data.milestones : \[\];
if (milestones.length \=== 0\) { drawBottomBarAndFooter(slide, layout, pageNum); return; }
const inner \= layout.pxToPt(60);
const baseY \= area.top \+ area.height \* 0.55;
const leftX \= area.left \+ inner;
const rightX \= area.left \+ area.width \- inner;
const line \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, leftX, baseY \- layout.pxToPt(1), rightX \- leftX, layout.pxToPt(2));
line.getFill().setSolidFill(CONFIG.COLORS.faint\_gray);
line.getBorder().setTransparent();
const dotR \= layout.pxToPt(8);
const gap \= (rightX \- leftX) / Math.max(1, (milestones.length \- 1));
milestones.forEach((m, i) \=\> {
const x \= leftX \+ gap \* i \- dotR / 2;
const dot \= slide.insertShape(SlidesApp.ShapeType.ELLIPSE, x, baseY \- dotR / 2, dotR, dotR);
const state \= (m.state || 'todo').toLowerCase();
if (state \=== 'done') { dot.getFill().setSolidFill(CONFIG.COLORS.google\_green); dot.getBorder().setTransparent(); }
else if (state \=== 'next') { dot.getFill().setSolidFill(CONFIG.COLORS.background\_white); dot.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.google\_yellow); dot.getBorder().setWeight(2); }
else { dot.getFill().setSolidFill(CONFIG.COLORS.background\_white); dot.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.neutral\_gray); dot.getBorder().setWeight(1); }
```
const t \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, x \- layout.pxToPt(40), baseY \- layout.pxToPt(40), layout.pxToPt(90), layout.pxToPt(20));
t.getText().setText(String(m.label || ''));
applyTextStyle(t.getText(), { size: CONFIG.FONTS.sizes.small, bold: true, align: SlidesApp.ParagraphAlignment.CENTER });
const d \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, x \- layout.pxToPt(40), baseY \+ layout.pxToPt(8), layout.pxToPt(90), layout.pxToPt(18));
d.getText().setText(String(m.date || ''));
applyTextStyle(d.getText(), { size: CONFIG.FONTS.sizes.small, color: CONFIG.COLORS.neutral\_gray, align: SlidesApp.ParagraphAlignment.CENTER });
```
});
drawBottomBarAndFooter(slide, layout, pageNum);
}
// diagram(Mermaid風・レーン+カード+自動矢印)
function createDiagramSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'diagramSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'diagramSlide', data.subhead);
const lanes \= Array.isArray(data.lanes) ? data.lanes : \[\];
const area0 \= layout.getRect('diagramSlide.lanesArea');
const area \= offsetRect(area0, 0, dy);
const px \= (p)=\> layout.pxToPt(p);
const laneGap \= px(CONFIG.DIAGRAM.laneGap\_px);
const lanePad \= px(CONFIG.DIAGRAM.lanePad\_px);
const laneTitleH \= px(CONFIG.DIAGRAM.laneTitle\_h\_px);
const cardGap \= px(CONFIG.DIAGRAM.cardGap\_px);
const cardMinH \= px(CONFIG.DIAGRAM.cardMin\_h\_px);
const cardMaxH \= px(CONFIG.DIAGRAM.cardMax\_h\_px);
const arrowH \= px(CONFIG.DIAGRAM.arrow\_h\_px);
const arrowGap \= px(CONFIG.DIAGRAM.arrowGap\_px);
const n \= Math.max(1, lanes.length);
const laneW \= (area.width \- laneGap \* (n \- 1)) / n;
const cardBoxes \= \[\];
for (let j \= 0; j \< n; j++) {
const lane \= lanes\[j\] || { title: '', items: \[\] };
const left \= area.left \+ j \* (laneW \+ laneGap);
const top \= area.top;
```
const lt \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, left, top, laneW, laneTitleH);
lt.getFill().setSolidFill(CONFIG.COLORS.lane\_title\_bg);
lt.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.lane\_border);
lt.getBorder().setWeight(1);
lt.getText().setText(lane.title || '');
applyTextStyle(lt.getText(), { size: CONFIG.FONTS.sizes.laneTitle, bold: true, align: SlidesApp.ParagraphAlignment.CENTER });
const items \= Array.isArray(lane.items) ? lane.items : \[\];
const availH \= area.height \- laneTitleH \- lanePad \* 2;
const rows \= Math.max(1, items.length);
const idealH \= (availH \- cardGap \* (rows \- 1)) / rows;
const cardH \= Math.max(cardMinH, Math.min(cardMaxH, idealH));
const totalH \= cardH \* rows \+ cardGap \* (rows \- 1);
const firstTop \= top \+ laneTitleH \+ lanePad \+ Math.max(0, (availH \- totalH) / 2);
cardBoxes\[j\] \= \[\];
for (let i \= 0; i \< rows; i++) {
const cardTop \= firstTop \+ i \* (cardH \+ cardGap);
const cardLeft \= left \+ lanePad;
const cardWidth \= laneW \- lanePad \* 2;
const card \= slide.insertShape(SlidesApp.ShapeType.ROUND\_RECTANGLE, cardLeft, cardTop, cardWidth, cardH);
card.getFill().setSolidFill(CONFIG.COLORS.card\_bg);
card.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.card\_border);
card.getBorder().setWeight(1);
setStyledText(card, items\[i\] || '', { size: CONFIG.FONTS.sizes.body });
try { card.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); } catch(e){}
cardBoxes\[j\]\[i\] \= { left: cardLeft, top: cardTop, width: cardWidth, height: cardH };
}
```
}
// 同行カード間を矢印で接続
const maxRows \= Math.max(...cardBoxes.map(a \=\> a.length));
for (let j \= 0; j \< n \- 1; j++) {
const L \= cardBoxes\[j\], R \= cardBoxes\[j \+ 1\];
for (let i \= 0; i \< maxRows; i++) {
const a \= L\[i\], b \= R\[i\];
if (a && b) drawArrowBetweenRects(slide, a, b, arrowH, arrowGap);
}
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
// cards(グレー枠のみ)— title/desc 両方でインライン装飾を有効化
function createCardsSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'cardsSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'cardsSlide', data.subhead);
const area \= offsetRect(layout.getRect('cardsSlide.gridArea'), 0, dy);
const items \= Array.isArray(data.items) ? data.items : \[\];
const cols \= Math.min(3, Math.max(2, Number(data.columns) || (items.length \<= 4 ? 2 : 3)));
const gap \= layout.pxToPt(16);
const rows \= Math.ceil(items.length / cols);
const cardW \= (area.width \- gap \* (cols \- 1)) / cols;
const cardH \= Math.max(layout.pxToPt(92), (area.height \- gap \* (rows \- 1)) / rows);
for (let idx \= 0; idx \< items.length; idx++) {
const r \= Math.floor(idx / cols), c \= idx % cols;
const left \= area.left \+ c \* (cardW \+ gap);
const top \= area.top \+ r \* (cardH \+ gap);
```
const card \= slide.insertShape(SlidesApp.ShapeType.ROUND\_RECTANGLE, left, top, cardW, cardH);
card.getFill().setSolidFill(CONFIG.COLORS.card\_bg);
card.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.card\_border);
card.getBorder().setWeight(1);
const obj \= items\[idx\];
if (typeof obj \=== 'string') {
setStyledText(card, obj, { size: CONFIG.FONTS.sizes.body });
} else {
const title \= String(obj.title || '');
const desc \= String(obj.desc || '');
const combined \= \`${title}${desc ? '\\n' \+ desc : ''}\`;
setStyledText(card, combined, { size: CONFIG.FONTS.sizes.body });
if (title.length \> 0\) {
try { card.getText().getRange(0, title.length).getTextStyle().setBold(true); } catch(e){}
}
}
try { card.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); } catch(e) {}
```
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
// table(Slidesテーブルでもインライン装飾対応。失敗時は矩形代替でも対応)
function createTableSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'tableSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'tableSlide', data.subhead);
const area \= offsetRect(layout.getRect('tableSlide.area'), 0, dy);
const headers \= Array.isArray(data.headers) ? data.headers : \[\];
const rows \= Array.isArray(data.rows) ? data.rows : \[\];
try {
if (headers.length \> 0\) {
const table \= slide.insertTable(rows.length \+ 1, headers.length);
table.setLeft(area.left).setTop(area.top).setWidth(area.width);
for (let c \= 0; c \< headers.length; c++) {
const cell \= table.getCell(0, c);
setStyledText(cell, String(headers\[c\] || ''), { bold: true, align: SlidesApp.ParagraphAlignment.CENTER });
}
for (let r \= 0; r \< rows.length; r++) {
const row \= rows\[r\] || \[\];
for (let c \= 0; c \< headers.length; c++) {
const cell \= table.getCell(r \+ 1, c);
setStyledText(cell, String(row\[c\] || ''), { align: SlidesApp.ParagraphAlignment.CENTER });
}
}
} else {
throw new Error('headers is empty');
}
} catch (e) {
const cols \= Math.max(1, headers.length || 3);
const rcount \= rows.length \+ 1;
const gap \= layout.pxToPt(1);
const cellW \= (area.width \- gap \* (cols \- 1)) / cols;
const cellH \= (area.height \- gap \* (rcount \- 1)) / rcount;
const drawCell \= (r, c, text, bold) \=\> {
const left \= area.left \+ c \* (cellW \+ gap);
const top \= area.top \+ r \* (cellH \+ gap);
const cell \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, left, top, cellW, cellH);
cell.getFill().setSolidFill(CONFIG.COLORS.background\_white);
cell.getBorder().getLineFill().setSolidFill(CONFIG.COLORS.card\_border);
cell.getBorder().setWeight(1);
setStyledText(cell, String(text || ''), { bold: \!\!bold, align: SlidesApp.ParagraphAlignment.CENTER });
try { cell.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); } catch(e){}
};
(headers.length ? headers : \['項目','値1','値2'\]).forEach((h, c) \=\> drawCell(0, c, h, true));
for (let r \= 0; r \< rows.length; r++) {
const row \= rows\[r\] || \[\];
for (let c \= 0; c \< (headers.length || 3); c++) drawCell(r \+ 1, c, row\[c\], false);
}
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
// progress(進捗バー)
function createProgressSlide(slide, data, layout, pageNum) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
drawStandardTitleHeader(slide, layout, 'progressSlide', data.title);
const dy \= drawSubheadIfAny(slide, layout, 'progressSlide', data.subhead);
const area \= offsetRect(layout.getRect('progressSlide.area'), 0, dy);
const items \= Array.isArray(data.items) ? data.items : \[\];
const n \= Math.max(1, items.length);
const rowH \= area.height / n;
for (let i \= 0; i \< n; i++) {
const y \= area.top \+ i \* rowH \+ layout.pxToPt(6);
const label \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, area.left, y, layout.pxToPt(150), layout.pxToPt(18));
setStyledText(label, String(items\[i\].label || ''), { size: CONFIG.FONTS.sizes.body });
```
const barLeft \= area.left \+ layout.pxToPt(160);
const barW \= area.width \- layout.pxToPt(210);
const barBG \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, barLeft, y, barW, layout.pxToPt(14));
barBG.getFill().setSolidFill(CONFIG.COLORS.faint\_gray); barBG.getBorder().setTransparent();
const p \= Math.max(0, Math.min(100, Number(items\[i\].percent || 0)));
const barFG \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, barLeft, y, barW \* (p/100), layout.pxToPt(14));
barFG.getFill().setSolidFill(CONFIG.COLORS.google\_green); barFG.getBorder().setTransparent();
const pct \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, barLeft \+ barW \+ layout.pxToPt(6), y \- layout.pxToPt(1), layout.pxToPt(40), layout.pxToPt(16));
pct.getText().setText(String(p) \+ '%');
applyTextStyle(pct.getText(), { size: CONFIG.FONTS.sizes.small, color: CONFIG.COLORS.neutral\_gray });
```
}
drawBottomBarAndFooter(slide, layout, pageNum);
}
function createClosingSlide(slide, data, layout) {
slide.getBackground().setSolidFill(CONFIG.COLORS.background\_white);
const image \= slide.insertImage(CONFIG.LOGOS.closing);
const imgW\_pt \= layout.pxToPt(450) \* layout.scaleX;
const aspect \= image.getHeight() / image.getWidth();
image.setWidth(imgW\_pt).setHeight(imgW\_pt \* aspect);
image.setLeft((layout.pageW\_pt \- imgW\_pt) / 2).setTop((layout.pageH\_pt \- (imgW\_pt \* aspect)) / 2);
}
// \--- 7\. ユーティリティ関数群 \---
function createLayoutManager(pageW\_pt, pageH\_pt) {
const pxToPt \= (px) \=\> px \* 0.75;
const baseW\_pt \= pxToPt(CONFIG.BASE\_PX.W);
const baseH\_pt \= pxToPt(CONFIG.BASE\_PX.H);
const scaleX \= pageW\_pt / baseW\_pt;
const scaleY \= pageH\_pt / baseH\_pt;
const getPositionFromPath \= (path) \=\> path.split('.').reduce((obj, key) \=\> obj\[key\], CONFIG.POS\_PX);
return {
scaleX, scaleY, pageW\_pt, pageH\_pt, pxToPt,
getRect: (spec) \=\> {
const pos \= typeof spec \=== 'string' ? getPositionFromPath(spec) : spec;
let left\_px \= pos.left;
if (pos.right \!== undefined && pos.left \=== undefined) {
left\_px \= CONFIG.BASE\_PX.W \- pos.right \- pos.width;
}
return {
left: left\_px \!== undefined ? pxToPt(left\_px) \* scaleX : undefined,
top: pos.top \!== undefined ? pxToPt(pos.top) \* scaleY : undefined,
width: pos.width \!== undefined ? pxToPt(pos.width) \* scaleX : undefined,
height: pos.height \!== undefined ? pxToPt(pos.height) \* scaleY : undefined,
};
}
};
}
function offsetRect(rect, dx, dy) {
return { left: rect.left \+ (dx || 0), top: rect.top \+ (dy || 0), width: rect.width, height: rect.height };
}
function drawStandardTitleHeader(slide, layout, key, title) {
const logoRect \= layout.getRect(\`${key}.headerLogo\`);
const logo \= slide.insertImage(CONFIG.LOGOS.header);
const asp \= logo.getHeight() / logo.getWidth();
logo.setLeft(logoRect.left).setTop(logoRect.top).setWidth(logoRect.width).setHeight(logoRect.width \* asp);
const titleRect \= layout.getRect(\`${key}.title\`);
const titleShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, titleRect.left, titleRect.top, titleRect.width, titleRect.height);
setStyledText(titleShape, title || '', { size: CONFIG.FONTS.sizes.contentTitle, bold: true });
const uRect \= layout.getRect(\`${key}.titleUnderline\`);
const u \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, uRect.left, uRect.top, uRect.width, uRect.height);
u.getFill().setSolidFill(CONFIG.COLORS.primary\_blue);
u.getBorder().setTransparent();
}
function drawSubheadIfAny(slide, layout, key, subhead) {
if (\!subhead) return 0;
const rect \= layout.getRect(\`${key}.subhead\`);
const box \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, rect.left, rect.top, rect.width, rect.height);
setStyledText(box, subhead, { size: CONFIG.FONTS.sizes.subhead, color: CONFIG.COLORS.text\_primary });
return layout.pxToPt(36);
}
function drawBottomBar(slide, layout) {
const barRect \= layout.getRect('bottomBar');
const bar \= slide.insertShape(SlidesApp.ShapeType.RECTANGLE, barRect.left, barRect.top, barRect.width, barRect.height);
bar.getFill().setSolidFill(CONFIG.COLORS.primary\_blue);
bar.getBorder().setTransparent();
}
function drawBottomBarAndFooter(slide, layout, pageNum) {
drawBottomBar(slide, layout);
addGoogleFooter(slide, layout, pageNum);
}
function addGoogleFooter(slide, layout, pageNum) {
const leftRect \= layout.getRect('footer.leftText');
const leftShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, leftRect.left, leftRect.top, leftRect.width, leftRect.height);
leftShape.getText().setText(CONFIG.FOOTER\_TEXT);
applyTextStyle(leftShape.getText(), { size: CONFIG.FONTS.sizes.footer, color: CONFIG.COLORS.text\_primary });
if (pageNum \> 0\) {
const rightRect \= layout.getRect('footer.rightPage');
const rightShape \= slide.insertShape(SlidesApp.ShapeType.TEXT\_BOX, rightRect.left, rightRect.top, rightRect.width, rightRect.height);
rightShape.getText().setText(String(pageNum));
applyTextStyle(rightShape.getText(), { size: CONFIG.FONTS.sizes.footer, color: CONFIG.COLORS.primary\_blue, align: SlidesApp.ParagraphAlignment.END });
}
}
function applyTextStyle(textRange, opt) {
const style \= textRange.getTextStyle();
style.setFontFamily(CONFIG.FONTS.family);
style.setForegroundColor(opt.color || CONFIG.COLORS.text\_primary);
style.setFontSize(opt.size || CONFIG.FONTS.sizes.body);
style.setBold(opt.bold || false);
if (opt.align) {
try { textRange.getParagraphs().forEach(p \=\> p.getRange().getParagraphStyle().setParagraphAlignment(opt.align)); } catch (e) {}
}
}
function setStyledText(shapeOrCell, rawText, baseOpt) {
const parsed \= parseInlineStyles(rawText || '');
const tr \= shapeOrCell.getText();
tr.setText(parsed.output);
applyTextStyle(tr, baseOpt || {});
applyStyleRanges(tr, parsed.ranges);
}
function setBulletsWithInlineStyles(shape, points) {
const joiner \= '\\n\\n';
let combined \= '';
const ranges \= \[\];
(points || \[\]).forEach((pt, idx) \=\> {
const parsed \= parseInlineStyles(String(pt || ''));
const bullet \= '• ' \+ parsed.output;
if (idx \> 0\) combined \+= joiner;
const start \= combined.length;
combined \+= bullet;
```
parsed.ranges.forEach(r \=\> {
ranges.push({ start: start \+ 2 \+ r.start, end: start \+ 2 \+ r.end, bold: r.bold, color: r.color });
});
```
});
const tr \= shape.getText();
tr.setText(combined || '• —');
applyTextStyle(tr, { size: CONFIG.FONTS.sizes.body });
try {
tr.getParagraphs().forEach(p \=\> {
const ps \= p.getRange().getParagraphStyle();
ps.setLineSpacing(100);
ps.setSpaceBelow(6);
});
} catch (e) {}
applyStyleRanges(tr, ranges);
}
function parseInlineStyles(s) {
const ranges \= \[\];
let out \= '';
for (let i \= 0; i \< s.length; ) {
// \[\[青太字\]\]
if (s\[i\] \=== '\[' && s\[i+1\] \=== '\[') {
const close \= s.indexOf('\]\]', i \+ 2);
if (close \!== \-1) {
const content \= s.substring(i \+ 2, close);
const start \= out.length;
out \+= content;
const end \= out.length;
ranges.push({ start, end, bold: true, color: CONFIG.COLORS.primary\_blue });
i \= close \+ 2; continue;
}
}
// \*\*太字\*\*
if (s\[i\] \=== '\*' && s\[i+1\] \=== '\*') {
const close \= s.indexOf('\*\*', i \+ 2);
if (close \!== \-1) {
const content \= s.substring(i \+ 2, close);
const start \= out.length;
out \+= content;
const end \= out.length;
ranges.push({ start, end, bold: true });
i \= close \+ 2; continue;
}
}
out \+= s\[i\]; i++;
}
return { output: out, ranges };
}
function applyStyleRanges(textRange, ranges) {
ranges.forEach(r \=\> {
try {
const sub \= textRange.getRange(r.start, r.end);
if (\!sub) return;
const st \= sub.getTextStyle();
if (r.bold) st.setBold(true);
if (r.color) st.setForegroundColor(r.color);
} catch (e) {}
});
}
function normalizeImages(arr) {
return (arr || \[\]).map(v \=\> {
if (typeof v \=== 'string') return { url: v };
if (v && typeof v.url \=== 'string') return { url: v.url, caption: v.caption || '' };
return null;
}).filter(Boolean).slice(0, 6);
}
function renderImagesInArea(slide, layout, area, images) {
if (\!images || images.length \=== 0\) return;
const n \= Math.min(6, images.length);
let cols \= 1, rows \= 1;
if (n \=== 1\) { cols \= 1; rows \= 1; }
else if (n \=== 2\) { cols \= 2; rows \= 1; }
else if (n \<= 4\) { cols \= 2; rows \= 2; }
else { cols \= 3; rows \= 2; }
const gap \= layout.pxToPt(10);
const cellW \= (area.width \- gap \* (cols \- 1)) / cols;
const cellH \= (area.height \- gap \* (rows \- 1)) / rows;
for (let i \= 0; i \< n; i++) {
const r \= Math.floor(i / cols), c \= i % cols;
const left \= area.left \+ c \* (cellW \+ gap);
const top \= area.top \+ r \* (cellH \+ gap);
try {
const img \= slide.insertImage(images\[i\].url);
const scale \= Math.min(cellW / img.getWidth(), cellH / img.getHeight());
const w \= img.getWidth() \* scale;
const h \= img.getHeight() \* scale;
img.setWidth(w).setHeight(h);
img.setLeft(left \+ (cellW \- w) / 2).setTop(top \+ (cellH \- h) / 2);
} catch(e) {}
}
}
function isAgendaTitle(title) {
const t \= String(title || '').toLowerCase();
return /(agenda|アジェンダ|目次|本日お伝えすること)/.test(t);
}
function buildAgendaFromSlideData() {
const pts \= \[\];
for (const d of slideData) {
if (d && d.type \=== 'section' && typeof d.title \=== 'string' && d.title.trim()) pts.push(d.title.trim());
}
if (pts.length \> 0\) return pts.slice(0, 5);
const alt \= \[\];
for (const d of slideData) {
if (d && d.type \=== 'content' && typeof d.title \=== 'string' && d.title.trim()) alt.push(d.title.trim());
}
return alt.slice(0, 5);
}
function drawArrowBetweenRects(slide, a, b, arrowH, arrowGap) {
const fromX \= a.left \+ a.width;
const toX \= b.left;
const width \= Math.max(0, toX \- fromX \- arrowGap \* 2);
if (width \< 8\) return;
const yMid \= ((a.top \+ a.height/2) \+ (b.top \+ b.height/2)) / 2;
const top \= yMid \- arrowH / 2;
const left \= fromX \+ arrowGap;
const arr \= slide.insertShape(SlidesApp.ShapeType.RIGHT\_ARROW, left, top, width, arrowH);
arr.getFill().setSolidFill(CONFIG.COLORS.primary\_blue);
arr.getBorder().setTransparent();
} I plan to continue brushing it up by increasing template patterns and improving stability.
I will share it again once the new version is complete!
If you found this helpful, please like and follow on note.
Sharing your thoughts on X (#MajinStylePrompt) will also be the energy for my next development🔥
【Googleスライドが一瞬で完成する"奇跡"のプロンプト】
— まじん|AI × Google (@Majin_AppSheet) August 16, 2025
スライド生成AIは色々知ってるけど、会社のセキュリティポリシーで使えない…そんな方も多いのでは?
仕事で使えないと意味がないですよね🥲
そんな悩みを解決するため、魔法のプロンプトを編み出しました!
必要なのは、Geminiだけ。 pic.twitter.com/ZaqMgHj0C6
Recommended Writer✨
