튜토리얼¶
이 튜토리얼은 Python에서 PyMuPDF, MuPDF 사용법을 단계별로 보여줍니다.
MuPDF 가 PDF뿐만 아니라 XPS, OpenXPS, CBZ, CBR, FB2, EPUB 형식도 지원하므로 PyMuPDF 도 마찬가지입니다 [1]. 그러나 간결성을 위해 PDF 파일만 다룹니다. PDF 파일만 지원되는 경우에는 명시적으로 언급합니다.
이 소개 외에도 PyMuPDF 의 YouTube 채널 을 방문하시기 바랍니다. YouTube “Shorts” 및 더 긴 비디오 형식으로 대부분의 내용을 다루고 있습니다.
바인딩 가져오기¶
이 import 문으로 MuPDF 에 대한 Python 바인딩을 사용할 수 있습니다. 여기서 버전을 확인하는 방법도 보여줍니다:
>>> import pymupdf
>>> print(pymupdf.__doc__)
PyMuPDF 1.16.0: Python bindings for the MuPDF 1.16.0 library.
Version date: 2019-07-28 07:30:14.
Built for Python 3.7 on win32 (64-bit).
fitz 이름에 대한 참고사항¶
이전 버전의 PyMuPDF 는 Python import 이름이 fitz 였습니다. 최신 버전은 대신 pymupdf 를 사용하며, 이전 코드가 계속 작동하도록 fitz 를 대체 옵션으로 제공합니다.
fitz 라는 이름의 이유는 역사적인 호기심입니다:
MuPDF 의 원래 렌더링 라이브러리는 Libart 라고 불렸습니다.
“Artifex Software가 MuPDF 프로젝트를 인수한 후, 개발 초점은 “Fitz”라는 새로운 현대적인 그래픽 라이브러리 작성으로 옮겨졌습니다. Fitz는 원래 노후화된 Ghostscript 그래픽 라이브러리를 대체하기 위한 R&D 프로젝트로 의도되었지만, 대신 MuPDF를 구동하는 렌더링 엔진이 되었습니다.” (Wikipedia 에서 인용).
참고
사용되지 않는 pypi.org 패키지 fitz 가 설치된 경우 레거시 이름 fitz 사용이 실패할 수 있습니다. 설치 후 문제 을 참조하세요.
문서 열기¶
지원되는 문서 에 액세스하려면 다음 문으로 열어야 합니다:
doc = pymupdf.open(filename) # or pymupdf.Document(filename)
이것은 Document 객체 doc 을 생성합니다. filename 은 기존 파일의 이름을 지정하는 Python 문자열(또는 pathlib.Path)이어야 합니다.
메모리 데이터에서 문서를 열거나 새로운 빈 PDF를 생성할 수도 있습니다. 자세한 내용은 Document 를 참조하세요. Document 를 context manager 로 사용할 수도 있습니다.
문서에는 많은 속성과 함수가 포함되어 있습니다. 그 중에는 메타 정보(“author” 또는 “subject” 등), 총 페이지 수, 개요 및 암호화 정보가 있습니다.
일부 Document 메서드 및 속성¶
메서드 / 속성 |
설명 |
|---|---|
페이지 수 (int) |
|
메타데이터 (dict) |
|
목차 가져오기 (list) |
|
Page 읽기 |
메타데이터 액세스¶
PyMuPDF 는 표준 메타데이터를 완전히 지원합니다. Document.metadata 는 다음 키를 가진 Python 딕셔너리입니다. 모든 문서 타입 에서 사용할 수 있지만, 모든 항목이 항상 데이터를 포함하지는 않을 수 있습니다. 의미와 형식에 대한 자세한 내용은 각 매뉴얼(예: PDF의 경우 Adobe PDF 참조)을 참조하세요. 추가 정보는 Document 장에서도 찾을 수 있습니다. 메타데이터 필드는 달리 표시되지 않는 한 문자열 또는 None 입니다. 또한 모든 필드가 항상 의미 있는 데이터를 포함하는 것은 아니라는 점에 유의하세요. None 이 아니더라도 그렇습니다.
키 |
값 |
|---|---|
producer |
생성자(생성 소프트웨어) |
format |
형식: ‘PDF-1.4’, ‘EPUB’ 등 |
encryption |
사용된 암호화 방법(있는 경우) |
author |
author |
modDate |
마지막 수정 날짜 |
keywords |
keywords |
title |
title |
creationDate |
생성 날짜 |
creator |
생성 애플리케이션 |
subject |
subject |
참고
이러한 표준 메타데이터 외에도 PDF 버전 1.4부터 시작하는 PDF 문서 는 소위 “metadata streams” (stream 도 참조)를 포함할 수도 있습니다. 이러한 스트림의 정보는 XML로 인코딩됩니다. PyMuPDF 는 의도적으로 이 목적을 위한 XML 구성 요소를 포함하지 않습니다(PyMuPDF Xml class 는 Story 객체의 DOM 콘텐츠에 액세스하기 위한 헬퍼 클래스입니다). 따라서 그 안에 포함된 정보에 대한 직접 액세스를 지원하지 않습니다. 그러나 스트림 전체를 추출하고 lxml 같은 패키지를 사용하여 검사하거나 수정한 다음 결과를 PDF에 다시 저장할 수 있습니다. 원하는 경우 이 데이터를 완전히 삭제할 수도 있습니다.
참고
저장소에는 CSV 파일에서 메타데이터를 가져오거나(metadata import (PDF only)) CSV 파일로 내보내는(metadata export) 두 가지 유틸리티 스크립트가 있습니다.
개요 작업¶
문서의 모든 개요(“bookmarks”라고도 함)를 가져오는 가장 쉬운 방법은 목차 를 로드하는 것입니다:
toc = doc.get_toc()
이것은 책에서 볼 수 있는 일반적인 목차와 매우 유사한 Python 리스트의 리스트 [[lvl, title, page, …], …] 를 반환합니다.
lvl 은 항목의 계층 레벨(1부터 시작), title 은 항목의 제목, page 는 페이지 번호(1 기반!)입니다. 다른 매개변수는 북마크 대상의 세부 정보를 설명합니다.
참고
저장소에는 CSV 파일에서 목차를 가져오거나(toc import (PDF only)) CSV 파일로 내보내는(toc export) 두 가지 유틸리티 스크립트가 있습니다.
페이지 작업¶
Page 처리는 MuPDF 기능의 핵심입니다.
페이지를 래스터 또는 벡터(SVG) 이미지로 렌더링할 수 있으며, 선택적으로 확대/축소, 회전, 이동 또는 기울이기를 할 수 있습니다.
페이지의 텍스트와 이미지를 여러 형식으로 추출하고 텍스트 문자열을 검색할 수 있습니다.
PDF 문서의 경우 페이지에 텍스트나 이미지를 추가하는 더 많은 메서드를 사용할 수 있습니다.
먼저 Page 를 생성해야 합니다. 이것은 Document 의 메서드입니다:
page = doc.load_page(pno) # loads page number 'pno' of the document (0-based)
page = doc[pno] # the short form
여기서는 -∞ < pno < page_count 범위의 정수가 가능합니다. 음수는 끝에서 역순으로 계산되므로 Python 시퀀스와 같이 doc[-1] 은 마지막 페이지입니다.
더 고급 방법은 문서를 페이지에 대한 반복자 로 사용하는 것입니다:
for page in doc:
# do something with 'page'
# ... or read backwards
for page in reversed(doc):
# do something with 'page'
# ... or even use 'slicing'
for page in doc.pages(start, stop, step):
# do something with 'page'
페이지를 얻으면 일반적으로 다음과 같은 작업을 수행합니다:
페이지의 링크, 주석 또는 양식 필드 검사¶
링크는 문서가 뷰어 소프트웨어로 표시될 때 “hot areas”로 표시됩니다. 커서가 손 기호를 표시하는 동안 클릭하면 일반적으로 해당 hot area에 인코딩된 대상으로 이동합니다. 모든 링크를 가져오는 방법은 다음과 같습니다:
# get all links on a page
links = page.get_links()
links 는 딕셔너리의 Python 리스트입니다. 자세한 내용은 Page.get_links() 를 참조하세요.
한 번에 하나의 링크를 생성하는 반복자를 사용할 수도 있습니다:
for link in page.links():
# do something with 'link'
PDF 문서 페이지를 다루는 경우 주석(Annot (주석)) 또는 양식 필드(Widget (위젯))가 있을 수도 있으며, 각각 고유한 반복자를 가집니다:
for annot in page.annots():
# do something with 'annot'
for field in page.widgets():
# do something with 'field'
페이지 렌더링¶
이 예제는 페이지 콘텐츠의 래스터 이미지를 생성합니다:
pix = page.get_pixmap()
pix 는 (이 경우) 페이지의 RGB 이미지를 포함하는 Pixmap 객체로, 다양한 용도로 사용할 준비가 되어 있습니다. Page.get_pixmap() 메서드는 이미지를 제어하기 위한 많은 변형을 제공합니다: 해상도/DPI, 색 공간(예: 그레이스케일 이미지 또는 감산 색상 체계의 이미지 생성), 투명도, 회전, 미러링, 이동, 기울이기 등. 예를 들어: RGBA 이미지(즉, 알파 채널 포함)를 생성하려면 pix = page.get_pixmap(alpha=True) 를 지정하세요.
Pixmap 은 아래에서 참조되는 많은 메서드와 속성을 포함합니다. 그 중에는 정수 width, height (각각 픽셀 단위) 및 stride (한 수평 이미지 라인의 바이트 수)가 있습니다. 속성 samples 는 이미지 데이터를 나타내는 바이트의 사각형 영역(Python bytes 객체)을 나타냅니다.
참고
Page.get_svg_image() 를 사용하여 페이지의 벡터 이미지를 생성할 수도 있습니다. 자세한 내용은 Vector Image Support page 를 참조하세요.
파일에 페이지 이미지 저장¶
이미지를 PNG 파일에 간단히 저장할 수 있습니다:
pix.save(f"page-{page.number}.png")
GUI에서 이미지 표시¶
GUI 대화 상자 관리자에서도 사용할 수 있습니다. Pixmap.samples 는 모든 픽셀의 바이트 영역을 Python bytes 객체로 나타냅니다. 다음은 몇 가지 예입니다. examples 디렉토리에서 더 많은 예를 찾을 수 있습니다.
wxPython¶
RGB(A) pixmap 조정 및 wxPython 릴리스별 세부 사항에 대해서는 해당 문서를 참조하세요:
if pix.alpha:
bitmap = wx.Bitmap.FromBufferRGBA(pix.width, pix.height, pix.samples)
else:
bitmap = wx.Bitmap.FromBuffer(pix.width, pix.height, pix.samples)
Tkinter¶
Pillow documentation 의 섹션 3.19도 참조하세요:
from PIL import Image, ImageTk
# set the mode depending on alpha
mode = "RGBA" if pix.alpha else "RGB"
img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
tkimg = ImageTk.PhotoImage(img)
다음은 Pillow 사용을 피합니다:
# remove alpha if present
pix1 = pymupdf.Pixmap(pix, 0) if pix.alpha else pix # PPM does not support transparency
imgdata = pix1.tobytes("ppm") # extremely fast!
tkimg = tkinter.PhotoImage(data = imgdata)
지원되는 모든 문서를 페이지로 나누는 완전한 Tkinter 스크립트를 찾고 있다면 여기 있습니다!. 페이지 확대도 가능하며 Python 2 또는 3에서 실행됩니다. 매우 편리한 PySimpleGUI 순수 Python 패키지가 필요합니다.
PyQt4, PyQt5, PySide¶
Pillow documentation 의 섹션 3.16도 참조하세요:
from PIL import Image, ImageQt
# set the mode depending on alpha
mode = "RGBA" if pix.alpha else "RGB"
img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
qtimg = ImageQt.ImageQt(img)
다시 말하지만, Pillow를 사용하지 않고도 진행할 수 있습니다. Qt의 QImage 는 다행히 네이티브 Python 포인터를 지원하므로 다음은 Qt 이미지를 생성하는 권장 방법입니다:
from PyQt5.QtGui import QImage
# set the correct QImage format depending on alpha
fmt = QImage.Format_RGBA8888 if pix.alpha else QImage.Format_RGB888
qtimg = QImage(pix.samples_ptr, pix.width, pix.height, fmt)
텍스트 및 이미지 추출¶
페이지의 모든 텍스트, 이미지 및 기타 정보를 다양한 형식과 세부 수준으로 추출할 수도 있습니다:
text = page.get_text(opt)
다양한 형식을 얻으려면 opt 에 다음 문자열 중 하나를 사용하세요 [2]:
“text”: (기본값) 줄바꿈이 있는 일반 텍스트. 서식 없음, 텍스트 위치 세부 정보 없음, 이미지 없음.
“blocks”: 텍스트 블록(= 단락) 목록 생성.
“words”: 단어 목록 생성(공백을 포함하지 않는 문자열).
“html”: 이미지를 포함한 페이지의 전체 시각적 버전 생성. 인터넷 브라우저로 표시할 수 있습니다.
“dict” / “json”: HTML과 동일한 정보 수준이지만 Python 딕셔너리 또는 JSON 문자열로 제공됩니다. 구조에 대한 자세한 내용은
TextPage.extractDICT()를 참조하세요.“rawdict” / “rawjson”: “dict” / “json” 의 상위 집합. XML과 같은 문자 세부 정보를 추가로 제공합니다. 구조에 대한 자세한 내용은
TextPage.extractRAWDICT()를 참조하세요.“xhtml”: TEXT 버전과 동일한 텍스트 정보 수준이지만 이미지를 포함합니다. 인터넷 브라우저로도 표시할 수 있습니다.
“xml”: 이미지는 포함하지 않지만 각 단일 텍스트 문자까지 전체 위치 및 글꼴 정보를 포함합니다. XML 모듈을 사용하여 해석합니다.
이러한 대안의 출력에 대한 아이디어를 제공하기 위해 텍스트 예제 추출을 수행했습니다. 부록 1: 텍스트 추출에 대한 세부사항 을 참조하세요.
텍스트 검색¶
페이지에서 특정 텍스트 문자열이 정확히 어디에 나타나는지 확인할 수 있습니다:
areas = page.search_for("mupdf")
이것은 사각형 목록(Rect 참조)을 제공하며, 각각은 문자열 “mupdf” 의 한 번 발생을 둘러쌉니다(대소문자 구분 안 함). 이 정보를 사용하여 예를 들어 해당 영역을 강조 표시하거나(PDF만) 문서의 상호 참조를 생성할 수 있습니다.
함께 작업하기: DisplayList 및 TextPage 장과 데모 프로그램 demo.py 및 demo-lowlevel.py 도 살펴보세요. 특히 성능 고려 사항이 제안될 때 TextPage, Device (디바이스) 및 DisplayList (디스플레이 리스트) 클래스를 더 직접적으로 제어하는 방법에 대한 세부 정보를 포함합니다.
Stories: HTML 소스에서 PDF 생성¶
Story 클래스는 PyMuPDF 버전 1.21.0의 새로운 기능입니다. MuPDF 의 “story” 인터페이스 지원을 나타냅니다.
다음은 Artifex 의 Robin Watts가 쓴 “MuPDF Explored” 책에서 인용한 내용입니다:
Stories는 Document Writers가 제공하는 것과 같은 장치에서 사용할 수 있도록 스타일이 지정된 콘텐츠를 쉽게 레이아웃하는 방법을 제공합니다(…). story의 개념은 데스크톱 출판에서 나왔으며, 이는 차례로(…) 신문에서 가져온 것입니다. 전통적인 신문 레이아웃을 고려하면 여러 열로 배치되고 여러 페이지에 걸칠 수 있는 다양한 뉴스 기사(stories)로 구성됩니다.
따라서 |MuPDF| 는 스타일 정보가 있는 텍스트 흐름을 나타내기 위해 story를 사용합니다. story 사용자는 story가 배치될 사각형 시퀀스를 제공할 수 있으며, 배치된 텍스트는 출력 장치에 그려질 수 있습니다. 이것은 텍스트 자체(story)의 개념을 텍스트가 흐를 영역(레이아웃)과 분리된 상태로 유지합니다.
참고
Story는 인터넷 브라우저와 다소 유사하게 작동합니다: HTML 하이퍼텍스트와 선택적 스타일시트(CSS)를 충실하게 구문 분석하고 렌더링합니다. 그러나 출력은 PDF 입니다. 웹 페이지가 아닙니다.
Story 를 생성할 때 최대 세 가지 다른 정보 소스의 입력이 고려됩니다. 이러한 항목은 모두 선택 사항입니다.
HTML 소스 코드, Python 문자열이거나 Xml 의 메서드를 사용하여 스크립트로 생성된 것.
CSS(Cascaded Style Sheet) 소스 코드, Python 문자열로 제공됩니다. CSS는 웹 페이지에서와 같이 스타일 정보(텍스트 글꼴 크기, 색상 등)를 제공하는 데 사용할 수 있습니다. 명백히 이 문자열은 파일에서 읽을 수도 있습니다.
DOM이 이미지를 참조하거나 표준 PDF Base 14 글꼴, CJK 글꼴 및 PyMuPDF 바이너리에 생성된 NOTO 글꼴을 제외한 텍스트 글꼴을 사용할 때마다 Archive (아카이브) 를 사용해야 합니다.
API 는 원하는 스타일 정보를 포함하여 처음부터 DOM을 생성할 수 있게 합니다. 또한 제공된 HTML을 수정하거나 확장하는 데 사용할 수 있습니다: 텍스트를 삭제하거나 교체하거나 스타일을 변경할 수 있습니다. 데이터베이스에서 추출한 것과 같은 텍스트도 추가하고 템플릿과 같은 HTML 문서를 채울 수 있습니다.
구문적으로 완전한 HTML 문서를 제공할 필요는 없습니다: <b>Hello 와 같은 스니펫이 완전히 허용되며 많은/대부분의 구문 오류가 자동으로 수정됩니다.
HTML이 완료된 것으로 간주되면 PDF 문서를 생성하는 데 사용할 수 있습니다. 이것은 새로운 DocumentWriter 클래스를 통해 수행됩니다. 프로그래머는 메서드를 호출하여 새로운 빈 페이지를 만들고 Story에 사각형을 전달하여 채웁니다.
story는 차례로 더 많은 콘텐츠가 작성되기를 기다리고 있는지 여부를 나타내는 완료 코드를 반환합니다. 콘텐츠의 어느 부분이 어떤 사각형이나 어떤 페이지에 배치될지는 story 자체에 의해 자동으로 결정됩니다. 사각형을 제공하는 것 외에는 영향을 줄 수 없습니다.
일반적인 사용 사례는 Stories recipes 를 참조하세요.
PDF 유지보수¶
PDF는 PyMuPDF 를 사용하여 수정 할 수 있는 유일한 문서 타입입니다. 다른 파일 타입은 읽기 전용입니다.
그러나 모든 문서 (이미지 포함)를 PDF로 변환한 다음 변환 결과에 모든 PyMuPDF 기능을 적용할 수 있습니다. 자세한 내용은 Document.convert_to_pdf() 를 참조하고, 모든 :ref:` 지원되는 문서<Supported_File_Types>` 를 PDF로 변환할 수 있는 데모 스크립트 pdf-converter.py 도 살펴보세요.
Document.save() 는 항상 PDF를 현재(잠재적으로 수정된) 상태로 디스크에 저장합니다.
일반적으로 새 파일에 저장할지 또는 기존 파일에 수정 사항만 추가할지(“incremental save”) 선택할 수 있으며, 후자가 종종 훨씬 빠릅니다.
다음은 PDF 문서를 조작하는 방법을 설명합니다. 이 설명은 결코 완전하지 않습니다. 다음 장에서 더 많은 내용을 찾을 수 있습니다.
페이지 수정, 생성, 재배열 및 삭제¶
PDF의 소위 페이지 트리 (모든 페이지를 설명하는 구조)를 조작하는 여러 방법이 있습니다:
Document.delete_page() 및 Document.delete_pages() 는 페이지를 삭제합니다.
Document.copy_page(), Document.fullcopy_page() 및 Document.move_page() 는 페이지를 같은 문서 내의 다른 위치로 복사하거나 이동합니다.
Document.select() 는 PDF를 선택한 페이지로 축소합니다. 매개변수는 유지하려는 페이지 번호의 시퀀스 [3] 입니다. 이러한 정수는 모두 0 <= i < page_count 범위에 있어야 합니다. 실행되면 이 목록에 없는 모든 페이지가 삭제됩니다. 남은 페이지는 시퀀스에서 지정한 횟수만큼(!) 나타납니다.
따라서 다음과 같은 새 PDF를 쉽게 만들 수 있습니다:
처음 또는 마지막 10페이지,
홀수 페이지만 또는 짝수 페이지만(양면 인쇄용),
주어진 텍스트를 포함하는 또는 포함하지 않는 페이지,
페이지 시퀀스 반전, …
… 생각할 수 있는 모든 것.
저장된 새 문서는 여전히 유효한 링크, 주석 및 북마크를 포함합니다(즉, 선택한 페이지 또는 일부 외부 리소스를 가리킴).
Document.insert_page() 및 Document.new_page() 는 새 페이지를 삽입합니다.
페이지 자체는 다양한 메서드(예: 페이지 회전, 주석 및 링크 유지보수, 텍스트 및 이미지 삽입)로 수정할 수 있습니다.
PDF 문서 결합 및 분할¶
Document.insert_pdf() 메서드는 다른 PDF 문서 간에 페이지를 복사합니다. 다음은 간단한 결합 예제입니다(doc1 및 doc2 는 열린 PDF):
# append complete doc2 to the end of doc1
doc1.insert_pdf(doc2)
다음은 doc1 을 분할 하는 스니펫입니다. 처음 10페이지와 마지막 10페이지로 새 문서를 만듭니다:
doc2 = pymupdf.open() # new empty PDF
doc2.insert_pdf(doc1, to_page = 9) # first 10 pages
doc2.insert_pdf(doc1, from_page = len(doc1) - 10) # last 10 pages
doc2.save("first-and-last-10.pdf")
더 많은 내용은 Document 장에서 찾을 수 있습니다. PDFjoiner.py 도 살펴보세요.
데이터 임베딩¶
PDF는 ZIP 아카이브와 매우 유사하게 임의의 데이터(실행 파일, 다른 PDF, 텍스트 또는 바이너리 파일 등)의 컨테이너로 사용할 수 있습니다.
PyMuPDF fully supports this feature via Document embfile_* methods and attributes. For some detail read 부록 2: 임베디드 파일에 대한 고려사항, consult the Wiki on dealing with embedding files, or the example scripts embedded-copy.py, embedded-export.py, embedded-import.py, and embedded-list.py.
저장¶
위에서 언급한 대로 Document.save() 는 항상 문서를 현재 상태로 저장합니다.
incremental=True 옵션을 지정하여 변경 사항을 원본 PDF 에 다시 쓸 수 있습니다. 이 프로세스는 (일반적으로) 매우 빠릅니다. 파일을 완전히 다시 쓰지 않고 변경 사항을 원본 파일에 추가 하기 때문입니다.
Document.save() 옵션은 MuPDF 의 명령줄 유틸리티 mutool clean 의 옵션에 해당합니다. 다음 표를 참조하세요.
저장 옵션 |
mutool |
효과 |
|---|---|---|
garbage=1 |
g |
사용하지 않는 객체 가비지 수집 |
garbage=2 |
gg |
1에 추가하여 |
garbage=3 |
ggg |
2에 추가하여 중복 객체 병합 |
garbage=4 |
gggg |
3에 추가하여 중복 스트림 콘텐츠 병합 |
clean=True |
cs |
콘텐츠 스트림 정리 및 정화 |
deflate=True |
z |
압축되지 않은 스트림 압축 |
deflate_images=True |
i |
이미지 스트림 압축 |
deflate_fonts=True |
f |
글꼴 파일 스트림 압축 |
ascii=True |
a |
바이너리 데이터를 ASCII 형식으로 변환 |
linear=True |
l |
선형화된 버전 생성 |
expand=True |
d |
모든 스트림 압축 해제 |
참고
object, stream, xref 와 같은 용어에 대한 설명은 용어집 장을 참조하세요.
예를 들어 mutool clean -ggggz file.pdf 는 우수한 압축 결과를 제공합니다. 이것은 doc.save(filename, garbage=4, deflate=True) 에 해당합니다.
닫기¶
프로그램이 계속 실행되는 동안 기본 파일에 대한 제어를 OS에 반환하기 위해 문서를 “닫는” 것이 종종 바람직합니다.
이것은 Document.close() 메서드로 달성할 수 있습니다. 기본 파일을 닫는 것 외에도 문서와 연결된 버퍼 영역이 해제됩니다.
추가 읽기¶
PyMuPDF 의 Wiki 페이지도 살펴보세요. 특히 사이드바의 “Recipes” 제목 아래에 나열된 항목은 “How-To” 스타일로 작성된 15개 이상의 주제를 다룹니다.
이 문서에는 자주 묻는 질문 도 포함되어 있습니다. 이 장은 앞서 언급한 recipes와 밀접한 관련이 있으며, 시간이 지나면서 더 많은 콘텐츠로 확장될 것입니다.
각주
