비교

C#에서 HTML을 PDF로 - 라이브러리 옵션의 현실

C#에서 HTML을 PDF로 변환하려면 실제로 HTML을 렌더링하는 라이브러리가 필요합니다 — 태그의 하위 집합을 파싱하고 CSS 2.1을 대략적으로 구현하는 라이브러리 말고. Stack Overflow 스레드 및 Reddit 토론에서 추천된 대부분의 라이브러리는 현대적인 CSS를 렌더링할 수 없거나 상업적 사용에 불가한 라이선스 제한을 가지거나 미패치 보안 취약성과 함께 방치되었습니다.

이 문서는 개발자가 "HTML to PDF C#"을 검색할 때 실제로 접하는 라이브러리를 비교하고, 각 라이브러리가 렌더링할 수 있는 것과 없는 것을 문서화하며, 방법론과 함께 성능 벤치마크를 포함하고, 각 접근 방식의 실제 운영 비용을 보여줍니다.

Quickstart:HTML to PDFin C#

using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");
$vbLabelText   $csharpLabel

NuGet을 통해 설치: Install-Package IronPDF. 외부 종속성 없이 Windows, Linux, macOS, 및 Docker에 배포.

HTML을 PDF로 변환하는 것이 왜 어려운가요?

HTML을 PDF로 정확히 렌더링하려면 웹 브라우저가 사용하는 다섯 가지 구성 요소를 구현해야 합니다: HTML 파서, CSS 엔진(Flexbox, Grid, 캐스케이딩, 특수성, 미디어 쿼리 포함), JavaScript 런타임, 레이아웃 엔진 및 모든 것을 픽셀 하위 수준 정확도로 PDF에 합성하는 렌더링 파이프라인.

전통적인 PDF 라이브러리는 첫 두 가지를 부분적으로 구현하고 JavaScript를 완전히 생략합니다. 이 때문에 간단한 HTML은 처리할 수 있지만, 최신 브라우저가 올바르게 렌더링하는 것들은 깨집니다. 브라우저 출력과 일치시키는 유일한 방법은 브라우저 엔진을 사용하는 것입니다.

실제로 HTML을 PDF로 변환하는 라이브러리는 무엇입니까?

wkhtmltopdf래퍼 - DLL 로딩 오류 생태계

개발자들을 이 문서로 이끄는 가장 일반적인 검색 쿼리는 다음과 같은 변형입니다:

System.DllNotFoundException: DLL 'libwkhtmltox'를 로드할 수 없음

플랫폼별 변형에는 다음이 포함됩니다:

공유 라이브러리 'wkhtmltox' 또는 그 종속성을 로드할 수 없음
    (Linux — libwkhtmltox.so not found)

지정된 모듈을 찾을 수 없음. (0x8007007E)
    (Windows — wkhtmltox.dll path not configured)

dyld: 라이브러리가 로드되지 않음: libwkhtmltox.dylib
    (macOS — not supported on ARM64/Apple Silicon)

이 오류는 DinkToPdf, NReco.PdfGenerator, WkHtmlToXSharp 및 동일한 버려진 바이너리를 둘러싼 다른 C# 래퍼에서 발생합니다. wkhtmltopdf GitHub 조직은 2024년 7월에 보관되었습니다. 기본 QtWebKit 엔진은 2015년에 Qt에 의해 더 이상 사용되지 않음으로 지정되었습니다. 프로젝트 상태 페이지는 명시적으로 더 이상 사용되지 않음으로 표시되어 있습니다.

DLL 로딩 문제 외에도, 렌더링 엔진은 대략적으로 Safari 2011 기능에 얼어붙게 됩니다. Flexbox 없음, Grid 없음, 제한된 CSS3, 신뢰할 수 없는 JavaScript. 그리고 미패치의 치명적인 취약점이 있습니다: CVE-2022-35583(CVSS 9.8)는 제작된 HTML을 통해 AWS 자격 증명을 탈취할 수 있는 SSRF 공격을 가능하게 합니다.

wkhtmltopdf의 시간은 지났습니다. DLL 로딩 오류는 더 깊은 문제의 증상입니다: 더 이상 진전이 없는 버려진 소프트웨어에 의존하고 있습니다.

iText 7(pdfHTML 애드온) — 제한된 CSS, AGPL 라이센스

iText의 pdfHTML 모듈은 HTML을 PDF로 변환하기 위해 사용자 정의 파서를 사용합니다 — 브라우저 엔진이 아닙니다. 기본 HTML/CSS는 처리하지만 Flexbox, Grid 또는 JavaScript는 렌더링하지 않습니다.

실패 모드는 조용합니다: pdfHTML은 지원되지 않는 CSS를 마주했을 때 예외를 throw하지 않습니다. 가능한 부분은 렌더링하고 나머지는 무시합니다. display: flex 컨테이너는 gap: 20pxjustify-content: space-between로 수직으로 쌓인 요소로 렌더링되며, 간격이 없습니다. 개발자는 통합 후에야 이를 발견합니다.

라이선스: AGPL — 전체 네트워크에 접근할 수 있는 응용 프로그램의 소스를 공개하거나 상업용 라이선스를 구매해야 합니다. 가격은 공개되지 않았습니다; 타사 데이터에 따르면 연간 $15,000–$210,000입니다.

메모리 사용량은 어떻게 비교되나요?

iText의 pdfHTML은 문서 전체를 메모리에 로드하여 처리합니다. 일반적인 비즈니스 문서에서는 관리할 수 있지만, 이미지가 포함된 대규모 HTML 보고서는 스트리밍 방식에 비해 상당한 메모리 압박을 초래할 수 있습니다.

왜 PdfSharp은 HTML을 지원하지 않나요?

PdfSharp는 인기 때문에 "HTML to PDF" 검색 결과에 나타납니다 (3,490만 NuGet 다운로드) 및 자주 추천됨. 하지만 PdfSharp는 HTML 파서를 가지고 있지 않습니다. 명시적인 X/Y 위치를 가지는 좌표 기반 그리기 API를 제공합니다: DrawString(), DrawRectangle(), DrawImage().

일반적으로 제안되는 해결 방법 HtmlRenderer.PdfSharp은 HTML 4.01과 CSS 레벨 2만 지원합니다. HTML에 2010년 이후 도입된 CSS 기능 — Flexbox (2012), Grid (2017), 사용자 정의 속성 (2017), border-radius (2011) — 중 아무거나 사용하면 렌더링되지 않습니다.

HTML 지원이 있을 것으로 기대하고 PdfSharp를 선택한 개발자는 결국 좌표 기반 코드로 모든 요소를 수동으로 위치시키거나 HTML 렌더링을 위한 두 번째 라이브러리를 추가하게 됩니다 — 이 시점에서 PdfSharp는 불필요합니다.

왜 Puppeteer Sharp은 리소스 집약적인가요?

Puppeteer Sharp는 .NET 바인딩을 통해 헤드리스 Chrome을 제어합니다. 렌더링 정확도는 Chrome과 일치합니다, 왜냐하면 그것이 Chrome이기 때문입니다. 비용은 운영상의 것입니다: 외부 브라우저 프로세스를 관리해야 합니다.

이것이 실제로 필요한Puppeteer Sharp배포입니다 — 튜토리얼의 5줄 예제가 아니라 동시 PDF 생성에 필요한 브라우저 풀링 코드입니다.

using PuppeteerSharp;
using System.Collections.Concurrent;

public class PdfBrowserPool : IAsyncDisposable
{
    private readonly ConcurrentBag<IBrowser> _available = new();
    private readonly SemaphoreSlim _semaphore;
    private readonly int _maxBrowsers;

    public PdfBrowserPool(int maxBrowsers = 4)
    {
        _maxBrowsers = maxBrowsers;
        _semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
    }

    public async Task InitializeAsync()
    {
        await new BrowserFetcher().DownloadAsync(); // ~280MB download
        for (int i = 0; i < _maxBrowsers; i++)
        {
            var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
                               "--disable-dev-shm-usage" }
            });
            _available.Add(browser);
        }
    }

    public async Task<byte[]> ConvertHtmlToPdf(string html)
    {
        await _semaphore.WaitAsync();
        IBrowser browser = null;
        try
        {
            if (!_available.TryTake(out browser))
                throw new InvalidOperationException("No browser available");

            await using var page = await browser.NewPageAsync();
            await page.SetContentAsync(html, new NavigationOptions
            {
                WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
            });
            var result = await page.PdfAsync(new PdfOptions
            {
                Format = PaperFormat.A4,
                PrintBackground = true
            });
            return result;
        }
        catch (Exception ex) when (ex is NavigationException or TargetClosedException)
        {
            // Browser crashed — replace it
            browser?.Dispose();
            browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
            });
            throw; // Re-throw after recovery
        }
        finally
        {
            if (browser != null) _available.Add(browser);
            _semaphore.Release();
        }
    }

    public async ValueTask DisposeAsync()
    {
        foreach (var browser in _available)
        {
            await browser.CloseAsync();
            browser.Dispose();
        }
    }
}
using PuppeteerSharp;
using System.Collections.Concurrent;

public class PdfBrowserPool : IAsyncDisposable
{
    private readonly ConcurrentBag<IBrowser> _available = new();
    private readonly SemaphoreSlim _semaphore;
    private readonly int _maxBrowsers;

    public PdfBrowserPool(int maxBrowsers = 4)
    {
        _maxBrowsers = maxBrowsers;
        _semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
    }

    public async Task InitializeAsync()
    {
        await new BrowserFetcher().DownloadAsync(); // ~280MB download
        for (int i = 0; i < _maxBrowsers; i++)
        {
            var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
                               "--disable-dev-shm-usage" }
            });
            _available.Add(browser);
        }
    }

    public async Task<byte[]> ConvertHtmlToPdf(string html)
    {
        await _semaphore.WaitAsync();
        IBrowser browser = null;
        try
        {
            if (!_available.TryTake(out browser))
                throw new InvalidOperationException("No browser available");

            await using var page = await browser.NewPageAsync();
            await page.SetContentAsync(html, new NavigationOptions
            {
                WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
            });
            var result = await page.PdfAsync(new PdfOptions
            {
                Format = PaperFormat.A4,
                PrintBackground = true
            });
            return result;
        }
        catch (Exception ex) when (ex is NavigationException or TargetClosedException)
        {
            // Browser crashed — replace it
            browser?.Dispose();
            browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
            });
            throw; // Re-throw after recovery
        }
        finally
        {
            if (browser != null) _available.Add(browser);
            _semaphore.Release();
        }
    }

    public async ValueTask DisposeAsync()
    {
        foreach (var browser in _available)
        {
            await browser.CloseAsync();
            browser.Dispose();
        }
    }
}
$vbLabelText   $csharpLabel

하나의 PDF를 생성하기 전 약 60줄의 인프라 코드가 필요합니다. 또한 메모리 누수 모니터링 (시간 경과에 따라 Chromium 프로세스가 메모리를 축적함), 상태 체크, 20개 이상의 Chromium 종속성을 가진 Dockerfile이 필요합니다. Docker 이미지 크기는 300–400MB 증가합니다.

IronPDF 접근 방식과 비교해보세요:

using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internally
using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internally
$vbLabelText   $csharpLabel

운영 오버헤드를 소화할 수 있는 팀이라면 Puppeteer Sharp는 적합합니다. 브라우저 인프라가 아니라 애플리케이션에 집중하려는 팀에게는 IronPDF가 동일한 렌더링을 내부에서 처리합니다.

왜 QuestPDF는 HTML을 변환하지 못하나요?

QuestPDF는 Reddit과 Stack Overflow의 모든 "HTML to PDF C#" 토론에 거의 등장합니다. 이는 일관된 패턴을 만듭니다: 개발자가 HTML 변환을 기대하고 QuestPDF를 구매하거나 통합한 후, HTML을 전혀 렌더링하지 않는다는 것을 발견합니다.

QuestPDF는 프로그래밍 문서 작성에 대한 유창한 C# API입니다. 그 위치는 명시적으로 "HTML-to-PDF 변환과 싸우지 마세요" — C# 코드로 HTML 접근을 대체합니다. 이는 의도적인 설계 선택입니다. 2022년부터 2024년까지 GitHub 토론에서는 개발자들이 구현을 시작한 후 이를 발견합니다. 유지보수자들은 일관되게 HTML 지원이 계획되어 있지 않음을 확인합니다.

기존 워크플로가 HTML 템플릿을 사용하는 경우 — 송장을 위한 Razor 뷰, 보고서를 위한 대시보드 HTML, 아카이브를 위한 웹 콘텐츠 등 — QuestPDF는 모든 템플릿을 C# 유창한 API 코드로 다시 작성해야 합니다. 구조화된 데이터로 처음부터 문서 레이아웃을 구성하는 새로운 프로젝트에서는 QuestPDF의 API가 잘 설계되어 생산적입니다.

커뮤니티 라이센스는 연간 총 수익이 $1M 미만인 사업체를 대상으로 합니다. 그 이상은 상업용 라이센스가 필요합니다.

Aspose.PDF는 어떤가요?

Aspose.PDF는 상업용 라이센스를 통해 광범위한 PDF 기능을 제공합니다 (개발자당 약 $999부터 시작). HTML 변환은 브라우저가 아닌 사용자 지정 엔진을 사용합니다 — iText와 유사하게 기본 HTML을 처리하지만 최신 CSS 기능은 정확히 렌더링하지 않습니다.

주요 우려 사항은 플랫폼 안정성입니다: Aspose는 System.Drawing.Common에 의존하며, 이는 Linux에서 libgdiplus가 필요합니다. Microsoft는 이를 .NET 6+의 비-Windows 플랫폼에 더 이상 사용되지 않음을 선언했습니다. 개발자는 Linux 배포에 특정한 메모리 누수가 Windows에서는 발생하지 않는다고 보고합니다. Windows 전용 환경에서는 Aspose가 가능합니다. 크로스 플랫폼 또는 컨테이너화된 배포에 대해서는 종속성 체인이 지속적인 위험을 초래합니다.

IronPDF는 HTML-to-PDF 변환을 어떻게 처리하나요?

IronPDF는 Chromium을 NuGet 패키지에 직접 포함합니다. CSS Flexbox, Grid, 사용자 정의 속성, @font-face, 미디어 쿼리 및 JavaScript는 모두 Chrome에서처럼 실행됩니다. 출력은 동일한 렌더링 엔진을 사용하기 때문에 브라우저와 일치합니다.

using IronPdf;

var renderer = new ChromePdfRenderer();

string html = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        :root { --primary: #2563eb; }
        body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
        .card {
            background: linear-gradient(135deg, #f8fafc, #e2e8f0);
            border-radius: 12px; padding: 24px; text-align: center;
        }
        .card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
        .card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
        table { width: 100%; border-collapse: collapse; margin-top: 30px; }
        th { background: var(--primary); color: white; padding: 12px; text-align: left; }
        td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
    </style>
</head>
<body>
    <div class='grid'>
        <div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
        <div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
        <div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
    </div>
    <table>
        <tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
        <tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
        <tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
    </table>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();

string html = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        :root { --primary: #2563eb; }
        body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
        .card {
            background: linear-gradient(135deg, #f8fafc, #e2e8f0);
            border-radius: 12px; padding: 24px; text-align: center;
        }
        .card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
        .card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
        table { width: 100%; border-collapse: collapse; margin-top: 30px; }
        th { background: var(--primary); color: white; padding: 12px; text-align: left; }
        td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
    </style>
</head>
<body>
    <div class='grid'>
        <div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
        <div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
        <div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
    </div>
    <table>
        <tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
        <tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
        <tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
    </table>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");
$vbLabelText   $csharpLabel

이는 minmax, 사용자 정의 속성, linear-gradient, border-radius:root 선택기가 포함된 CSS Grid를 사용합니다. 이러한 기능들은 모두 iText의 pdfHTML에서 실패하며, wkhtmltopdf에서 깨지고, PdfSharp나 QuestPDF에서는 존재하지 않습니다.

다른 라이브러리에서 어떻게 마이그레이션 할 수 있나요?

iTextSharp 또는 wkhtmltopdf에서 마이그레이션하는 팀이라면, IronPDF는 URL을 직접 받아들이므로 기존 워크플로우에서 HTML 파일을 생성하거나 페이지를 제공할 때 유용합니다:

using IronPdf;

var renderer = new ChromePdfRenderer();

// Convert from URL — useful when migrating fromwkhtmltopdfURL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");

// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();

// Convert from URL — useful when migrating fromwkhtmltopdfURL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");

// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");
$vbLabelText   $csharpLabel

배포

IronPDF는 Windows (x64), Linux (x64, ARM64), macOS (x64, Apple Silicon), Docker 컨테이너에서 실행됩니다. Docker 구성은 표준 .NET 이미지입니다:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Chromium 설치 불필요, 네이티브 라이브러리 종속성 없음, 샌드박스 구성 필요 없음.

라이센스: 영구 라이센스는 $749부터 시작합니다. 가격 책정은 ironpdf.com에 게시되어 있습니다. AGPL 없음, 문서별 요금 없음, 매출 임계값 없음.

성능 벤치마크

Ubuntu 22.04와 .NET 8에서 실행되는 Standard_D4s_v3 Azure VM (4 vCPU, 16GB RAM)에서 테스트되었습니다. 테스트 문서: CSS Grid 레이아웃, 삽입된 이미지, JavaScript로 생성된 차트를 갖춘 200개 요소의 HTML 송장 템플릿. 각 측정치는 5번의 예열 후 50회 반복 시행의 평균값입니다.

시나리오IronPDFPuppeteer SharpiText pdfHTMLwkhtmltopdf
간단한 HTML (JS 없음)~150ms~500ms~200ms~200ms
복잡한 CSS (Flexbox/Grid)~250ms~600ms손상된 출력손상된 출력
JavaScript로 렌더링된 콘텐츠~350ms~800ms실패 (JS 엔진 없음)실패/부분
작업당 메모리~80MB~150MB~60MB~50MB
차가운 시작 (첫 번째 생성)2–5초3–8초<1s<1s

iText와 wkhtmltopdf는 브라우저 엔진을 초기화하지 않기 때문에 차가운 시작 속도가 더 빠릅니다. 그러나 이 비교는 모든 라이브러리가 정확한 출력을 생성하는 시나리오에만 의미가 있으며, 복잡한 CSS 또는 JavaScript 콘텐츠의 경우 IronPDF와 Puppeteer Sharp만 실용적인 결과를 제공합니다.

참고: 이는 지정된 하드웨어에서의 일상적인 관찰을 나타냅니다. HTML 복잡성, 문서 길이 및 서버 리소스에 따라 성능이 달라질 것입니다. 결정을 내리기 전에 실제 워크로드로 테스트하십시오.

기능 비교

기능IronPDFiText 7Puppeteer SharpwkhtmltopdfPdfSharpQuestPDFAspose
HTML to PDF예 (Chromium)제한적 (CSS 2.1)예 (Chrome)더 이상 사용되지 않음아니요아니요제한적
CSS Flexbox/Grid아니요아니요아니요아니요아니요
JavaScript 실행아니요제한적아니요아니요아니요
크로스 플랫폼 (libgdiplus 없음)해당 없음부분적아니요
게시된 가격$749+아니오 ($15K–$210K/yr)무료 (MIT)무료무료 (MIT)무료 <$1M$999+
활발한 유지보수버려짐

어떤 라이브러리를 선택해야 할까요?

최신 CSS의 HTML 템플릿 → IronPDF는 외부 프로세스 관리 없이 Chromium을 포함합니다. 팀에서 브라우저 인프라를 관리할 수 있다면, Puppeteer Sharp도 유효한 대안이 될 수 있습니다.

데이터에서 프로그래마틱한 문서 생성, HTML 없음 → QuestPDF는 우아한 유창한 API를 제공합니다. HTML 변환을 기대하고 선택하지 마십시오.

단순 PDF 조작 (병합, 분할, 워터마크) → PdfSharp은 비 HTML 작업에 무료로 사용 가능하고 유용합니다.

새 프로젝트에서 피해야 할 것:wkhtmltopdf(중단, CVE), 상업 라이센스 없는 iText (AGPL 함정), Linux에서의Aspose(메모리 누수).

핵심 질문은 워크플로우에서 HTML 템플릿을 사용하는지 여부입니다. 사용한다면, Chromium 기반 솔루션만이 최신 CSS로 정확한 출력을 냅니다. 사용하지 않는다면, 선택은 API 선호도와 라이센스 제약에 달려 있습니다.