Skip to content

HTML에서 생성

두 가지 진입점이 제공됩니다.

  1. Pdf::from_html(content) — 기본 구조 HTML(제목, 단락, 목록, 코드, 굵게/기울임). 스타일 없음. 모든 바인딩에서 사용 가능.
  2. Pdf::from_html_css(html, css, font_bytes) — v0.3.37에 도입된 완전한 순수 Rust HTML+CSS 파이프라인. 직접 구현한 CSS 엔진(L3 + L4 셀렉터 일부, 캐스케이드, calc() / var(), @page / @media print), Taffy 기반 block / flex / grid 레이아웃, UAX #14 줄바꿈, rustybuzz를 통한 RTL 셰이핑, ::before / ::after, page-break-*, <a href> → 링크 주석, <img> data-URI → /XObject, 다중 폰트 캐스케이드를 지원합니다. MPL 의존성 제로. 모든 바인딩에서 사용 가능.

빠른 예제

Python

from pdf_oxide import Pdf

pdf = Pdf.from_html("<h1>Hello</h1><p>World</p>")
pdf.save("out.pdf")

WASM

import { WasmPdf } from "pdf-oxide-wasm";
import { writeFileSync } from "fs";

const pdf = WasmPdf.fromHtml("<h1>Hello</h1><p>World</p>");
writeFileSync("out.pdf", pdf.toBytes());

Rust

use pdf_oxide::api::Pdf;

let pdf = Pdf::from_html("<h1>Hello</h1><p>World</p>")?;
pdf.save("out.pdf")?;

Go

package main

import (
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    pdf, err := pdfoxide.FromHtml("<h1>Hello</h1><p>World</p>")
    if err != nil { log.Fatal(err) }
    defer pdf.Close()

    if err := pdf.Save("out.pdf"); err != nil { log.Fatal(err) }
}

C#

using PdfOxide;

using var pdf = Pdf.FromHtml("<h1>Hello</h1><p>World</p>");
pdf.Save("out.pdf");

Java

import fyi.oxide.pdf.Pdf;
import java.nio.file.Path;

try (Pdf pdf = Pdf.fromHtml("<h1>Hello</h1><p>World</p>")) {
    pdf.saveTo(Path.of("out.pdf"));
}

PHP

use PdfOxide\Pdf;

$pdf = Pdf::fromHtml('<h1>Hello</h1><p>World</p>');
file_put_contents('out.pdf', $pdf->save());

Ruby

require 'pdf_oxide'

PdfOxide::Pdf.from_html('<h1>Hello</h1><p>World</p>') { |pdf| pdf.save('out.pdf') }

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto pdf = pdf_oxide::Pdf::from_html("<h1>Hello</h1><p>World</p>");
pdf.save("out.pdf");

Swift

import PdfOxide

let pdf = try Pdf.fromHtml("<h1>Hello</h1><p>World</p>")
try pdf.save("out.pdf")

Kotlin

import fyi.oxide.pdf.Pdf

Pdf.fromHtml("<h1>Hello</h1><p>World</p>").use { it.saveTo(java.nio.file.Path.of("out.pdf")) }

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final pdf = Pdf.fromHtml('<h1>Hello</h1><p>World</p>');
pdf.save('out.pdf');

R

library(pdfoxide)

pdf <- pdf_from_html("<h1>Hello</h1><p>World</p>")
pdf_save(pdf, "out.pdf")

Julia

using PdfOxide

pdf = from_html("<h1>Hello</h1><p>World</p>")
save(pdf, "out.pdf")

Zig

const pdf_oxide = @import("pdf_oxide");

var pdf = try pdf_oxide.Pdf.fromHtml("<h1>Hello</h1><p>World</p>");
try pdf.save("out.pdf");

Scala

import fyi.oxide.pdf.Pdf
import scala.util.Using

Using.resource(Pdf.fromHtml("<h1>Hello</h1><p>World</p>"))(_.saveTo(java.nio.file.Path.of("out.pdf")))

Clojure

(require '[pdf-oxide.core :as pdf])

(let [p (pdf/from-html "<h1>Hello</h1><p>World</p>")]
  (.saveTo p (java.nio.file.Path/of "out.pdf" (into-array String []))))

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXPdf *pdf = [POXPdf fromHtml:@"<h1>Hello</h1><p>World</p>" error:&err];
[pdf saveToPath:@"out.pdf" error:&err];

Elixir

{:ok, pdf} = PdfOxide.from_html("<h1>Hello</h1><p>World</p>")
PdfOxide.save(pdf, "out.pdf")

HTML + CSS 파이프라인 (v0.3.37)

Pdf::from_html_css(html, css, font_bytes)는 HTML, CSS 스타일시트, TTF/OTF 폰트 바이트를 받아 페이지로 분할된 PDF를 반환합니다. extract_text가 바이트 단위로 완전히 왕복하므로 생성된 PDF는 기존 테스트 인프라에 그대로 편입됩니다.

Rust:

use pdf_oxide::api::Pdf;

let font = std::fs::read("DejaVuSans.ttf")?;
let pdf = Pdf::from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt } p { line-height: 1.5 }",
    font,
)?;
pdf.save("out.pdf")?;

Python:

from pdf_oxide import Pdf

with open("DejaVuSans.ttf", "rb") as f:
    font = f.read()

pdf = Pdf.from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font,
)
pdf.save("out.pdf")

Node / TypeScript:

import { Pdf } from "pdf-oxide";
import { readFileSync } from "fs";

const font = readFileSync("DejaVuSans.ttf");
const pdf = Pdf.fromHtmlCss(
  "<h1>Hello</h1><p>World</p>",
  "h1 { color: blue; font-size: 24pt }",
  font,
);
pdf.save("out.pdf");

Go:

font, _ := os.ReadFile("DejaVuSans.ttf")
pdf, err := pdfoxide.FromHtmlCss(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font,
)
if err != nil { log.Fatal(err) }
defer pdf.Close()
_ = pdf.Save("out.pdf")

C#:

var font = File.ReadAllBytes("DejaVuSans.ttf");
using var pdf = Pdf.FromHtmlCss(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font);
pdf.Save("out.pdf");

C++:

#include <pdf_oxide/pdf_oxide.hpp>
#include <fstream>

std::ifstream in("DejaVuSans.ttf", std::ios::binary);
std::string font((std::istreambuf_iterator<char>(in)), {});
auto pdf = pdf_oxide::Pdf::from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    std::vector<uint8_t>(font.begin(), font.end()));
pdf.save("out.pdf");

Swift:

import PdfOxide
import Foundation

let font = [UInt8](try Data(contentsOf: URL(fileURLWithPath: "DejaVuSans.ttf")))
let pdf = try Pdf.fromHtmlCss(
    html: "<h1>Hello</h1><p>World</p>",
    css: "h1 { color: blue; font-size: 24pt }",
    fontBytes: font)
try pdf.save("out.pdf")

Dart:

import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';

final font = File('DejaVuSans.ttf').readAsBytesSync();
final pdf = Pdf.fromHtmlCss(
    '<h1>Hello</h1><p>World</p>',
    'h1 { color: blue; font-size: 24pt }',
    font);
pdf.save('out.pdf');

R:

library(pdfoxide)

font <- readBin("DejaVuSans.ttf", "raw", file.info("DejaVuSans.ttf")$size)
pdf <- pdf_from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font)
pdf_save(pdf, "out.pdf")

Julia:

using PdfOxide

font = read("DejaVuSans.ttf")
pdf = from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font)
save(pdf, "out.pdf")

Zig:

const pdf_oxide = @import("pdf_oxide");
const std = @import("std");

const font = try std.fs.cwd().readFileAlloc(std.heap.page_allocator, "DejaVuSans.ttf", 1 << 24);
var pdf = try pdf_oxide.Pdf.fromHtmlCss(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font);
try pdf.save("out.pdf");

Objective-C:

#import "POXPdfOxide.h"
NSError *err = nil;

NSData *font = [NSData dataWithContentsOfFile:@"DejaVuSans.ttf"];
POXPdf *pdf = [POXPdf fromHtml:@"<h1>Hello</h1><p>World</p>"
                          css:@"h1 { color: blue; font-size: 24pt }"
                    fontBytes:font
                        error:&err];
[pdf saveToPath:@"out.pdf" error:&err];

Elixir:

font = File.read!("DejaVuSans.ttf")
{:ok, pdf} = PdfOxide.from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font)
PdfOxide.save(pdf, "out.pdf")

다중 폰트 캐스케이드

문서가 여러 폰트 패밀리를 함께 사용하는 경우 Pdf::from_html_css_with_fonts(html, css, fonts)를 사용하세요. 임의의 요소에 지정된 CSS font-family는 등록된 패밀리에 대해 해석됩니다(대소문자 구분 없음, 따옴표 유무 무관, 따옴표 없는 다단어 이름 허용). 알 수 없는 패밀리는 처음 등록된 폰트로 폴백됩니다.

from pdf_oxide import Pdf

fonts = [
    ("DejaVu Sans", open("DejaVuSans.ttf", "rb").read()),
    ("Noto Sans CJK", open("NotoSansCJKtc-Regular.otf", "rb").read()),
]

pdf = Pdf.from_html_css_with_fonts(
    '<h1 style="font-family: DejaVu Sans">English</h1>'
    '<p style="font-family: \'Noto Sans CJK\'">中文段落</p>',
    "h1 { font-size: 24pt }",
    fonts,
)
pdf.save("multilang.pdf")

CJK 콘텐츠는 출력 시 자동으로 서브셋화됩니다(v0.3.38 #385). 약 17 MB CJK 폰트에서 5개 글자만 사용한 PDF는 일반적으로 100 KB 미만으로 만들어집니다.

지원되는 CSS 범위

  • 셀렉터 — L3 + L4 일부: :is / :where / :not / :has, 구조적 의사 클래스, i / s 플래그가 있는 속성 매처.
  • 캐스케이드 — 출처/명시도/소스 순서 정렬, 상속, 인라인 스타일 병합, 사용자 정의 속성(순환 감지가 포함된 var()).
  • 함수calc(), min(), max(), clamp().
  • At 규칙@media print(항상 참), (min/max-width), 마진 박스가 있는 @page :first / :left / :right / :blank, @font-face, @import, @supports.
  • 타입이 지정된 값 — 색상(이름 색 약 150개, 16진수, rgb/rgba, hsl), 길이(CSS Values L4의 모든 단위), display, font-size / weight / style / family, margin / padding 단축 표기, line-height.
  • 카운터counter / counters, counter-reset / -increment / -set, 로마 숫자/그리스 문자/알파벳 번호 매기기.
  • 의사 요소::before / ::after(리터럴 문자열, attr(name), open-quote / close-quote).
  • 레이아웃 — block, flex, grid(모두 Taffy 사용), 마진 상쇄, 다단(column-count / column-width / column-gap), 표(auto 및 fixed 열 알고리즘).
  • 인라인 — UAX #14 줄바꿈, text-align, white-space 모드, 강제 줄바꿈, 원자적 인라인 박스.
  • 효과opacity, transform: translate*(), page-break-before: always, page-break-after: always.
  • HTML — HTML5 토크나이저, <style> / <link rel="stylesheet"> / 인라인 style="" 추출, <img> data-URI 디코딩(/XObject), <a href>/URI가 포함된 /Link 주석, <ul> / <ol> 목록 마커.

지원 범위 밖

CSS 필터, 3D 변환, 애니메이션, HTML 내 SVG(쓸 만한 Rust SVG 크레이트는 모두 MPL), MathML, hyphens: auto, shape-outside, JavaScript 실행, 전체 행렬 transform(확대/축소·회전), 그라데이션, box-shadow.

라이선스

cargo deny check licenses는 MPL 전이 의존성이 제로인 상태로 통과합니다. Mozilla의 CSS 스택(cssparser, selectors, html5ever, lightningcss, stylo)은 모두 MPL-2.0이지만, v0.3.37에서는 pdf_oxide 전체를 MIT/Apache로 유지하기 위해 이에 상응하는 기능을 직접 구현했습니다.

지원되는 HTML 요소

요소 설명
<h1> ~ <h6> 제목(PDF 제목 크기로 매핑)
<p> 자동 간격이 적용된 단락
<b>, <strong> 굵은 텍스트
<i>, <em> 기울임 텍스트
<ul>, <ol>, <li> 순서 없는 목록과 순서 있는 목록
<pre>, <code> 서식 보존 텍스트와 인라인 코드
<blockquote> 블록 인용
<br> 줄바꿈
<hr> 수평선

전체 API 레퍼런스

Pdf::from_html(content) (정적 메서드)

기본 설정(Letter 페이지, 72pt 마진, 12pt Helvetica)을 사용해 HTML 콘텐츠로 PDF를 생성합니다.

Rust:

use pdf_oxide::api::Pdf;

let html = r#"
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
<h2>Requirements</h2>
<ul>
    <li>Operating temperature: -20C to 60C</li>
    <li>Power consumption: &lt;5W</li>
    <li>Weight: &lt;200g</li>
</ul>
"#;

let pdf = Pdf::from_html(html)?;
pdf.save("spec.pdf")?;

JavaScript:

import { WasmPdf } from "pdf-oxide-wasm";
import { writeFileSync } from "fs";

const html = `
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
`;

const pdf = WasmPdf.fromHtml(html);
writeFileSync("spec.pdf", pdf.toBytes());

Python:

from pdf_oxide import Pdf

html = """
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
"""

pdf = Pdf.from_html(html)
pdf.save("spec.pdf")

Java:

import fyi.oxide.pdf.Pdf;
import java.nio.file.Path;

String html = "<h1>Product Specification</h1>"
            + "<p>This document describes the <strong>technical requirements</strong>.</p>";

try (Pdf pdf = Pdf.fromHtml(html)) {
    pdf.saveTo(Path.of("spec.pdf"));
}

PHP:

use PdfOxide\Pdf;

$html = '<h1>Product Specification</h1>'
      . '<p>This document describes the <strong>technical requirements</strong>.</p>';

$pdf = Pdf::fromHtml($html);
file_put_contents('spec.pdf', $pdf->save());

Ruby:

require 'pdf_oxide'

html = '<h1>Product Specification</h1>' \
       '<p>This document describes the <strong>technical requirements</strong>.</p>'

PdfOxide::Pdf.from_html(html) { |pdf| pdf.save('spec.pdf') }

C++:

#include <pdf_oxide/pdf_oxide.hpp>

std::string html =
    "<h1>Product Specification</h1>"
    "<p>This document describes the <strong>technical requirements</strong>.</p>";

auto pdf = pdf_oxide::Pdf::from_html(html);
pdf.save("spec.pdf");

Swift:

import PdfOxide

let html = """
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>.</p>
"""

let pdf = try Pdf.fromHtml(html)
try pdf.save("spec.pdf")

Kotlin:

import fyi.oxide.pdf.Pdf

val html = """
    <h1>Product Specification</h1>
    <p>This document describes the <strong>technical requirements</strong>.</p>
""".trimIndent()

Pdf.fromHtml(html).use { it.saveTo(java.nio.file.Path.of("spec.pdf")) }

Dart:

import 'package:pdf_oxide/pdf_oxide.dart';

final html = '<h1>Product Specification</h1>'
    '<p>This document describes the <strong>technical requirements</strong>.</p>';

final pdf = Pdf.fromHtml(html);
pdf.save('spec.pdf');

R:

library(pdfoxide)

html <- paste0(
    "<h1>Product Specification</h1>",
    "<p>This document describes the <strong>technical requirements</strong>.</p>")

pdf <- pdf_from_html(html)
pdf_save(pdf, "spec.pdf")

Julia:

using PdfOxide

html = """
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>.</p>
"""

pdf = from_html(html)
save(pdf, "spec.pdf")

Zig:

const pdf_oxide = @import("pdf_oxide");

const html =
    "<h1>Product Specification</h1>" ++
    "<p>This document describes the <strong>technical requirements</strong>.</p>";

var pdf = try pdf_oxide.Pdf.fromHtml(html);
try pdf.save("spec.pdf");

Scala:

import fyi.oxide.pdf.Pdf
import scala.util.Using

val html =
  "<h1>Product Specification</h1>" +
  "<p>This document describes the <strong>technical requirements</strong>.</p>"

Using.resource(Pdf.fromHtml(html))(_.saveTo(java.nio.file.Path.of("spec.pdf")))

Clojure:

(require '[pdf-oxide.core :as pdf])

(let [html (str "<h1>Product Specification</h1>"
                "<p>This document describes the <strong>technical requirements</strong>.</p>")
      p    (pdf/from-html html)]
  (.saveTo p (java.nio.file.Path/of "spec.pdf" (into-array String []))))

Objective-C:

#import "POXPdfOxide.h"
NSError *err = nil;

NSString *html = @"<h1>Product Specification</h1>"
                  "<p>This document describes the <strong>technical requirements</strong>.</p>";

POXPdf *pdf = [POXPdf fromHtml:html error:&err];
[pdf saveToPath:@"spec.pdf" error:&err];

Elixir:

html =
  "<h1>Product Specification</h1>" <>
  "<p>This document describes the <strong>technical requirements</strong>.</p>"

{:ok, pdf} = PdfOxide.from_html(html)
PdfOxide.save(pdf, "spec.pdf")

Python 시그니처:

Pdf.from_html(
    content: str,
    title: str | None = None,
    author: str | None = None
) -> Pdf

PdfBuilder::new().from_html(content) (빌더 패턴)

페이지 크기, 마진, 폰트 크기, 문서 메타데이터를 제어하려면 PdfBuilder를 사용하세요.

Rust:

use pdf_oxide::api::PdfBuilder;
use pdf_oxide::writer::PageSize;

let pdf = PdfBuilder::new()
    .title("Technical Specification")
    .author("Engineering")
    .page_size(PageSize::A4)
    .margin(54.0)
    .font_size(11.0)
    .from_html("<h1>Spec</h1><p>Version 2.0</p>")?;

pdf.save("spec_a4.pdf")?;

고급 예제

구조화된 보고서

use pdf_oxide::api::Pdf;

let html = r#"
<h1>Incident Report</h1>
<h2>Summary</h2>
<p>On <em>2025-11-15</em>, a service disruption was detected in the
<strong>payment processing</strong> pipeline.</p>

<h2>Timeline</h2>
<ol>
    <li>14:32 UTC - Alert triggered for elevated error rates</li>
    <li>14:35 UTC - On-call engineer acknowledged</li>
    <li>14:48 UTC - Root cause identified: database connection pool exhaustion</li>
    <li>15:02 UTC - Fix deployed, services recovering</li>
    <li>15:15 UTC - Full recovery confirmed</li>
</ol>

<h2>Root Cause</h2>
<p>A configuration change deployed at 14:00 UTC reduced the maximum
connection pool size from 100 to 10.</p>

<h2>Code Reference</h2>
<pre><code>max_connections: 10  # Should be 100
timeout_seconds: 30
</code></pre>

<h2>Action Items</h2>
<ul>
    <li>Add validation for connection pool configuration</li>
    <li>Implement canary deployment for config changes</li>
    <li>Add alerting for connection pool utilization</li>
</ul>
"#;

let pdf = Pdf::from_html(html)?;
pdf.save("incident_report.pdf")?;

Python으로 동적 HTML 생성

from pdf_oxide import Pdf

rows = [
    ("Widget A", "$12.99", 150),
    ("Widget B", "$24.50", 89),
    ("Widget C", "$7.25", 312),
]

html = "<h1>Inventory Report</h1>"
html += "<p>Generated on 2025-11-20</p>"
html += "<h2>Current Stock</h2><ul>"
for name, price, qty in rows:
    html += f"<li><strong>{name}</strong> - {price} ({qty} units)</li>"
html += "</ul>"

pdf = Pdf.from_html(html, title="Inventory Report")
pdf.save("inventory.pdf")

파일에서 HTML 읽기

from pdf_oxide import Pdf

with open("report.html") as f:
    html = f.read()

pdf = Pdf.from_html(html, title="Report")
pdf.save("report.pdf")
import { WasmPdf } from "pdf-oxide-wasm";
import { readFileSync, writeFileSync } from "fs";

const html = readFileSync("report.html", "utf-8");
const pdf = WasmPdf.fromHtml(html);
writeFileSync("report.pdf", pdf.toBytes());
use pdf_oxide::api::Pdf;

let html = std::fs::read_to_string("report.html")?;
let pdf = Pdf::from_html(&html)?;
pdf.save("report.pdf")?;

관련 페이지