# UBGitea 소개 PPT 생성 (PowerPoint COM). 사용: powershell -File docs\presentation\build-pptx.ps1 # 좌표는 HTML 슬라이드와 같은 1920x1080 px 기준, 0.5배 해서 pt로 넣는다. $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing $root = Split-Path -Parent $MyInvocation.MyCommand.Path $img = Join-Path $root 'images' $out = Join-Path $root 'UBGitea_소개.pptx' $pdf = Join-Path $root 'UBGitea_소개.pdf' $KR = '맑은 고딕'; $MONO = 'Consolas' $BR = [string][char]11 function C($hex) { $r=[Convert]::ToInt32($hex.Substring(0,2),16); $g=[Convert]::ToInt32($hex.Substring(2,2),16); $b=[Convert]::ToInt32($hex.Substring(4,2),16); return $r + $g*256 + $b*65536 } function P($v) { return [double]$v * 0.5 } function T($s, $x, $y, $w, $h, $text, $size, $color, $bold=$false, $font=$KR, $align=1, $spacing=1.15) { $b = $s.Shapes.AddTextbox(1, (P $x), (P $y), (P $w), (P $h)) $tf = $b.TextFrame; $tf.WordWrap = -1; $tf.AutoSize = 0 $tf.MarginLeft = 0; $tf.MarginRight = 0; $tf.MarginTop = 0; $tf.MarginBottom = 0 $r = $tf.TextRange; $r.Text = $text $r.Font.Name = $font; $r.Font.NameFarEast = $KR; $r.Font.Size = (P $size) $r.Font.Color.RGB = (C $color); $r.Font.Bold = $(if ($bold) { -1 } else { 0 }) $r.ParagraphFormat.Alignment = $align $r.ParagraphFormat.LineRuleWithin = -1; $r.ParagraphFormat.SpaceWithin = $spacing return $b } function B($s, $x, $y, $w, $h, $items, $size=26, $color='3E5068') { $b = T $s $x $y $w $h ($items -join "`r") $size $color $false $KR 1 1.3 $pf = $b.TextFrame.TextRange.ParagraphFormat $pf.Bullet.Visible = -1; $pf.Bullet.Character = 8226 $pf.LineRuleAfter = 0; $pf.SpaceAfter = 6 $b.TextFrame.Ruler.Levels.Item(1).FirstMargin = 0 $b.TextFrame.Ruler.Levels.Item(1).LeftMargin = 14 return $b } function Box($s, $x, $y, $w, $h, $fill, $line=$null, $round=$false) { $shape = $s.Shapes.AddShape($(if ($round) { 5 } else { 1 }), (P $x), (P $y), (P $w), (P $h)) if ($round) { $shape.Adjustments.Item(1) = 0.06 } $shape.Fill.ForeColor.RGB = (C $fill) if ($line) { $shape.Line.ForeColor.RGB = (C $line); $shape.Line.Weight = 1 } else { $shape.Line.Visible = 0 } $shape.Shadow.Visible = 0 return $shape } function I($s, $name, $x, $y, $w, $h, $border='C9DCF2') { $path = Join-Path $img "$name.png" $im = [System.Drawing.Image]::FromFile($path); $iw = $im.Width; $ih = $im.Height; $im.Dispose() $k = [Math]::Min($w / $iw, $h / $ih); $pw = $iw * $k; $ph = $ih * $k $px = $x + ($w - $pw) / 2; $py = $y + ($h - $ph) / 2 $p = $s.Shapes.AddPicture($path, 0, -1, (P $px), (P $py), (P $pw), (P $ph)) if ($border) { $p.Line.Visible = -1; $p.Line.ForeColor.RGB = (C $border); $p.Line.Weight = 1 } return $p } function NewSlide($bg, $notes) { $s = $pres.Slides.Add($pres.Slides.Count + 1, 12) $s.FollowMasterBackground = 0; $s.Background.Fill.Solid(); $s.Background.Fill.ForeColor.RGB = (C $bg) $s.NotesPage.Shapes.Placeholders.Item(2).TextFrame.TextRange.Text = $notes return $s } function Eyebrow($s, $x, $y, $text, $color='1565C0', $size=26) { T $s $x $y 900 40 $text $size $color $true $MONO | Out-Null } function H2($s, $x, $y, $w, $h, $text, $size=56, $color='10243F') { T $s $x $y $w $h $text $size $color $true $KR 1 1.1 | Out-Null } # 왼쪽 글 + 오른쪽 폰 화면 3장 function ThreeShots($bg, $eyebrow, $title, $bullets, $shots, $notes) { $s = NewSlide $bg $notes Eyebrow $s 128 250 $eyebrow H2 $s 128 296 440 160 $title B $s 128 480 440 420 $bullets | Out-Null $x = 648 foreach ($sh in $shots) { I $s $sh[0] $x 130 360 760 | Out-Null T $s $x 906 360 40 $sh[1] 24 '3E5068' $false $KR 2 | Out-Null $x += 392 } } # 왼쪽 글 + 오른쪽 펼친 화면 1장 function WideShot($bg, $eyebrow, $title, $bullets, $shot, $notes) { $s = NewSlide $bg $notes Eyebrow $s 128 280 $eyebrow H2 $s 128 326 760 160 $title B $s 128 500 760 420 $bullets | Out-Null I $s $shot 968 130 824 821 | Out-Null } $app = New-Object -ComObject PowerPoint.Application $pres = $app.Presentations.Add(0) $pres.PageSetup.SlideWidth = 960; $pres.PageSetup.SlideHeight = 540 # 1 표지 $s = NewSlide 'F4F8FD' '회사 Gitea 서버 조회용 안드로이드 앱. 순서: 개발 배경 → 주요 화면 → 기술 · 개발 방식 → 현황.' Box $s 1180 0 740 1080 'DCEAF9' | Out-Null I $s 'logo' 128 250 300 90 $null | Out-Null $s.Shapes.Item($s.Shapes.Count).Left = (P 128) Eyebrow $s 128 384 'UBGitea · Android · v0.2.6' '1565C0' 28 T $s 128 444 960 230 ("사내 Gitea" + $BR + "모바일 앱") 96 '10243F' $true $KR 1 1.0 | Out-Null T $s 128 700 900 60 '팀 활동 · 코드 · 변경 내용 모바일 조회용 사내 Git 앱' 32 '3E5068' | Out-Null T $s 128 790 900 40 '2026년 10월 · 사내 공유용' 24 '5A6B80' | Out-Null I $s 'home' 1340 96 420 887 'B9D3F0' | Out-Null # 2 개발 배경 $s = NewSlide '10243F' '웹 기능 전체 이식 아님. 1순위 팀 활동 확인, 2순위 코드 · 변경 내용 조회. 소스 수정 · 병합 · 삭제 기능 의도적 제외.' Eyebrow $s 128 128 '개발 배경' '7DB9F5' H2 $s 128 176 1500 180 ("목적: 이석 중에도" + $BR + "팀 작업 현황 모바일 확인") 64 'F4F8FD' $cards = @( @('팀 활동 우선', 'Push 이력 · 열린 PR · 이슈 · 내 확인 필요 항목 첫 화면 표시', '4FA3F7'), @('필요 부분만 코드 조회', '목차 · 클래스 · 함수 단위 이동, 두 파일 좌우 비교. 팀 주력 언어 C# · C++ 기준', '4FA3F7'), @('읽기 중심 · 보안', '회사 Gitea 계정 로그인. 쓰기 기능은 저장소 즐겨찾기(별표)만 허용', 'F0A04B')) $x = 128 foreach ($c in $cards) { Box $s $x 620 533 332 '1A3558' | Out-Null Box $s $x 620 533 6 $c[2] | Out-Null T $s ($x+40) 666 453 50 $c[0] 34 'F4F8FD' $true | Out-Null T $s ($x+40) 736 453 190 $c[1] 26 'D3E2F4' $false $KR 1 1.4 | Out-Null $x += 565 } # 3 탭 구성 $s = NewSlide 'F4F8FD' '우측: Z Fold 펼친 화면. 넓은 화면에서 탭은 좌측 레일로 이동, 홈은 팀 활동 · 타임라인 2단 표시.' Eyebrow $s 128 220 '화면 구성' H2 $s 128 266 760 80 '탭 4개 구성' $rows = @(@('홈','오늘의 팀 활동과 타임라인, 내 확인이 필요한 항목'), @('저장소','전체·즐겨찾기 저장소, 브랜치, 파일, 커밋'), @('작업','팀 전체·리뷰 요청·나에게 할당된 이슈와 PR'), @('알림','Gitea 알림과 즐겨찾기 저장소의 Push 알림')) $y = 380 foreach ($r in $rows) { Box $s 128 $y 760 2 'C9DCF2' | Out-Null T $s 128 ($y+26) 150 50 $r[0] 34 '1565C0' $true | Out-Null T $s 306 ($y+32) 582 80 $r[1] 26 '3E5068' | Out-Null $y += 104 } Box $s 128 $y 760 2 'C9DCF2' | Out-Null T $s 128 ($y+36) 760 40 '접은 화면: 하단 탭 / 펼친 화면 · 가로 화면: 좌측 레일' 24 '5A6B80' | Out-Null I $s 'home_wide' 968 130 824 821 | Out-Null # 4 홈 $s = NewSlide 'FBFCFE' '상단 숫자: 당일 Push, 열린 PR, 신규 이슈. 즐겨찾기 선택 시 별표 저장소 활동만 표시. 하단 타임라인: Push · PR · 이슈 시간순.' Eyebrow $s 128 250 '홈 · 팀 활동' H2 $s 128 296 1178 160 ("첫 화면:" + $BR + "당일 팀 활동 요약") $stats = @(@('Push','당일 커밋'), @('PR','열린 요청'), @('이슈','신규 이슈')) $x = 128 foreach ($st in $stats) { Box $s $x 486 368 150 'E6F0FB' $null $true | Out-Null T $s ($x+28) 508 320 60 $st[0] 44 '1565C0' $true | Out-Null T $s ($x+28) 582 320 40 $st[1] 24 '3E5068' | Out-Null $x += 392 } B $s 128 676 1178 240 @('조회 범위: 내 피드 · 즐겨찾기 · 조직별 (선택 범위 저장)', '활동 저장소별 최근 브랜치 · 활동량 그래프', '타임라인: 작성자 · 브랜치 · Push 내용') 28 | Out-Null I $s 'home' 1402 128 390 824 | Out-Null # 5 저장소 ThreeShots 'F4F8FD' '저장소' ("저장소 조회 ·" + $BR + "즐겨찾기") @('조직별 저장소 목록', '별표 저장소: 즐겨찾기 탭 · 홈에 표시', '브랜치 · 태그 목록', '저장소 목록 기기 저장, 오프라인 조회') @(@('repos','전체 저장소'), @('favorites','즐겨찾기'), @('branch','브랜치 · 태그')) '계정 접근 가능 저장소 조직별 표시. 별표는 Gitea 별표와 동기화. 앱 내 유일한 쓰기 작업.' # 6 코드 보기 ThreeShots 'F4F8FD' '코드 보기' ("함수 단위" + $BR + "빠른 탐색") @('Visual Studio 기본 색상 기준 C# · C++ 강조', '.h 선언 / .cpp 구현 탭 전환', '목차에서 함수 선택 후 이동', '폴더 구조 · 클래스 · 함수 옆 창 표시') @(@('code','코드 · 선언/구현 탭'), @('outline','목차'), @('side_pane','폴더 · 클래스 옆 창')) '파일 전체 열람보다 함수 탐색 위주 설계. 하단 도구 막대: 검색, 목차, 줄바꿈, 글자 크기, 복사, 탭 저장, 비교.' # 7 펼친 화면 코드 WideShot 'FBFCFE' '펼친 화면 · 코드' ("펼친 화면:" + $BR + "구조 · 코드 동시 표시") @('Z Fold 펼침 · 가로 화면 대응', '좌측: 폴더 구조 · 클래스 · 함수 목록 / 우측: 코드', '옆 창 파일 선택 시 즉시 열림', '클래스 · 함수 선택 시 해당 줄 이동', '두 칸 위아래 순서 변경') 'code_wide' '펼친 화면에서 옆 창 상시 표시. 예시: C# 파일.' # 8 파일 비교 WideShot 'F4F8FD' '파일 비교' ("저장소 간" + $BR + "동일 파일 비교") @('다른 저장소 · 브랜치 파일 간 좌우 비교', '예시: OC6 PRI 저장소(main) ↔ OC6 저장소(SubMain)', '차이 개수 표시, 이전 · 다음 차이 이동', '추가 · 삭제 · 변경 줄 색상 구분') 'compare_repos' '동일 장비 PRI 저장소 · 현장 저장소의 WeldingRecipeManager.cs 비교. 차이 5곳 중 첫 번째 위치. 사용 순서: 파일에서 비교 → 왼쪽 배치 → 다른 파일에서 비교 실행.' # 9 커밋 ThreeShots 'FBFCFE' '커밋' ("커밋별 변경 내용" + $BR + "줄 단위 확인") @('브랜치별 커밋 목록', '커밋 선택: 변경 파일 · 추가/삭제 줄 수', '파일 선택: 변경 내용 표시') @(@('commits','커밋 목록'), @('commit_detail','바뀐 파일'), @('diff','변경 내용')) '이동 중 팀원 커밋 즉시 확인. 접은 화면: 상하 배치 / 펼친 화면: 좌우 배치.' # 10 펼친 화면 변경 내용 WideShot 'F4F8FD' '펼친 화면 · 변경 내용' ("펼친 화면:" + $BR + "변경 전 · 후 좌우 비교") @('PC 비교 도구 방식: 좌측 변경 전 / 우측 변경 후', '삭제: 빨강 / 추가: 초록', '코드 화면과 동일한 문법 강조') 'diff_wide' '코드 리뷰 요청 시 PC 없이 변경 내용 확인 가능.' # 11 작업 · 알림 ThreeShots 'FBFCFE' '작업 · 알림' ("이슈 · PR ·" + $BR + "알림 통합 조회") @('필터: 팀 전체 · 리뷰 요청 · 나에게 할당 · 내가 작성', '상태 배지: 열림 · 병합됨 · 닫힘', '이슈 · PR 본문 및 댓글 조회', 'Gitea 알림: 읽지 않음 · 전체') @(@('work','작업 탭'), @('issue_detail','이슈 상세'), @('notifications','알림')) '리뷰 요청 · 할당 업무 일괄 확인. 읽기 전용으로 댓글 작성 · 병합은 PC Gitea에서 처리.' # 12 파일 보기 ThreeShots 'F4F8FD' '파일 보기' ("코드 외" + $BR + "파일 조회") @('이미지', 'CSV 표', 'PDF 문서', '별도 앱 없이 앱 내 열람') @(@('image','이미지'), @('csv','CSV 표'), @('pdf','PDF')) '장비 문서 · 설정 표 등 코드와 함께 관리하는 파일 모바일 확인.' # 13 설정 $s = NewSlide 'FBFCFE' '신규 버전은 팀 Gitea 릴리스 기준. 업데이트 알림 시 설정에서 바로 설치.' Eyebrow $s 128 320 '설정' H2 $s 128 366 772 80 '설정 항목' B $s 128 470 772 420 @('테마: 시스템 · 라이트 · 다크', '코드 자동 줄바꿈, 글자 크기 11–16sp', '즐겨찾기 저장소 Push 알림', '인증 실패 시 재시도', '팀 Gitea 기반 앱 업데이트') | Out-Null I $s 'settings' 980 104 390 824 | Out-Null T $s 980 940 390 40 '설정' 24 '3E5068' $false $KR 2 | Out-Null I $s 'dark_home' 1402 104 390 824 | Out-Null T $s 1402 940 390 40 '다크 테마' 24 '3E5068' $false $KR 2 | Out-Null # 14 기술 · 보안 $s = NewSlide 'F4F8FD' '로그인은 브라우저에서 Gitea 직접 인증, 앱은 발급 토큰만 보관. 저장소 목록 기기 저장으로 오프라인 조회 가능.' Eyebrow $s 128 128 '기술 · 보안' H2 $s 128 176 1664 80 '회사 계정 인증 · 읽기 중심 설계' $rows = @(@('로그인','팀 Gitea OAuth 2.0 + PKCE. 앱 내 비밀번호 입력 없음'), @('토큰 보관','Android Keystore 암호화, 기기 내 저장'), @('권한','읽기 전용. 쓰기는 즐겨찾기 별표만'), @('만든 기술','Kotlin · Jetpack Compose · Material 3'), @('지원 기기','Android 9 (API 28) 이상, 폴더블 · 가로 화면 대응'), @('배포','팀 Gitea 릴리스 배포, 앱 내 업데이트')) $y = 310 foreach ($r in $rows) { Box $s 128 $y 1664 2 'C9DCF2' | Out-Null T $s 128 ($y+24) 320 50 $r[0] 30 '1565C0' $true | Out-Null $f = $(if ($r[0] -eq '만든 기술') { $MONO } else { $KR }) T $s 488 ($y+28) 1304 50 $r[1] 26 '3E5068' $false $f | Out-Null $y += 96 } Box $s 128 $y 1664 2 'C9DCF2' | Out-Null # 15 개발 방식 $s = NewSlide '10243F' '작업별 완료 기준 파일 선작성 → AI 에이전트 구현 → 별도 세션 리뷰. 전 과정 docs/development-log.md 기록.' Eyebrow $s 128 128 '만든 방법' 'F0A04B' H2 $s 128 176 1664 80 'AI 에이전트 활용 개발 · 7주' 56 'FFFFFF' $stats = @(@('77','작업 (TASK-001 → 077)'), @('7주','2026-08-25 ~ 10-12'), @('104','개발 기록 항목'), @('0.2.6','현재 버전')) $x = 128 foreach ($st in $stats) { Box $s $x 320 392 190 '1A3558' $null $true | Out-Null T $s ($x+32) 346 340 90 $st[0] 60 'F0A04B' $true $MONO | Out-Null T $s ($x+32) 448 340 40 $st[1] 24 'C9DCF2' | Out-Null $x += 424 } $steps = @(@('1','작업 정의','완료 기준 선작성'), @('2','AI 구현','Codex · Claude'), @('3','별도 세션 리뷰','구현과 별도 에이전트'), @('4','빌드 · 테스트','실기기 확인'), @('5','배포','팀 Gitea 릴리스')) $x = 128 foreach ($st in $steps) { Box $s $x 600 320 4 'F0A04B' | Out-Null T $s $x 626 320 34 $st[0] 22 'F0A04B' $false $MONO | Out-Null T $s $x 670 320 40 $st[1] 26 'FFFFFF' $true | Out-Null T $s $x 716 320 36 $st[2] 22 'C9DCF2' | Out-Null $x += 336 } # 16 현황 $s = NewSlide 'F4F8FD' '개발 완료, 현재 버전 0.2.6. 설치 파일 팀 Gitea 릴리스 제공, 이후 신규 버전 앱 내 알림.' Eyebrow $s 128 370 '현황' H2 $s 128 418 640 170 ("개발 완료 ·" + $BR + "사내 배포") 64 T $s 128 610 640 50 '현재 버전 v0.2.6' 28 '3E5068' | Out-Null $items = @(@('설치','팀 Gitea 릴리스 설치 파일'), @('로그인','회사 Gitea 계정'), @('업데이트','신규 버전 앱 내 알림')) $y = 330 foreach ($it in $items) { Box $s 864 $y 928 116 'FFFFFF' 'C9DCF2' $true | Out-Null T $s 896 ($y+36) 160 50 $it[0] 30 '1565C0' $true | Out-Null T $s 1084 ($y+36) 680 50 $it[1] 30 '10243F' | Out-Null $y += 140 } $pres.SaveAs($out, 24) $pres.SaveAs($pdf, 32) $n = $pres.Slides.Count $pres.Close(); $app.Quit() [System.Runtime.InteropServices.Marshal]::ReleaseComObject($app) | Out-Null "slides=$n" "pptx=$out" "pdf=$pdf"