독립적으로 쓸 수 있는 라이브러리
모두 Apache-2.0 라이선스이며, Publira의 프로덕션 코드에서 실제로 쓰이고 있습니다. 플랫폼을 도입하지 않아도 사용할 수 있습니다.
@publira/comic-viewer
에피소드 화면을 렌더링하는 React 만화 뷰어를 별도 패키지로 분리했습니다. UI와 데이터 파이프라인은 모두 직접 구성할 수 있습니다.
- 헤드리스 UI 아키텍처. 서로 독립적이고 tree-shaking이 가능한 컴포넌트를 조합하고, 스타일은 모두 직접 지정합니다.
- 컨테이너 너비에 따라 한 쪽 보기와 두 쪽 펼침 보기를 자동으로 전환합니다.
- 가상화로 고해상도 페이지를 대량으로 불러와도 모바일 브라우저가 멈추지 않습니다.
- 교체 가능한 데이터 파이프라인. definePlugin()으로 데이터 가져오기, 복호화, 분석 기능을 끼워 넣을 수 있습니다.
- 만화에 맞춰 오른쪽에서 왼쪽으로 넘기는 것이 기본이며, 서양 만화를 위한 왼쪽에서 오른쪽 방향도 지원합니다. 스와이프, 클릭, 키보드 조작을 기본으로 제공합니다.
- resolvePage()로 페이지 메타데이터를 필요할 때 불러옵니다. 페이지가 많은 문서나 만료되는 서명 URL에 대응합니다.
1.0 이전 버전입니다. 같은 0.x 계열 안에서 패치 릴리스는 호환성을 유지하는 것을 목표로 하지만, 마이너 릴리스에서는 공개 API가 바뀔 수 있습니다. 업그레이드하기 전에 변경 기록을 확인하세요.
문서 읽기npm install @publira/comic-viewer
import * as ComicViewer from "@publira/comic-viewer";
import "@publira/comic-viewer/core.css";
export function Reader({ pages }: ReaderProps) {
return (
<ComicViewer.Root
initialReadingDirection="rtl"
pages={pages}
>
<ComicViewer.Viewport />
<ComicViewer.Toolbar />
<ComicViewer.PageNavigation />
</ComicViewer.Root>
);
}
publira/epub
EPUB 3를 디코딩, 인코딩, 검증하는 Go 라이브러리로, 파일을 디스크에 저장하지 않는 서버를 위해 만들었습니다.
- 파일 시스템에 의존하지 않습니다. Decode에 필요한 것은 io.ReaderAt과 아카이브 크기뿐입니다.
- 페일 패스트. 구조 위반이 있으면 바로 DecodeError를 반환합니다.
- Asset.Open으로 필요할 때만 스트림에 접근하므로 메모리를 효율적으로 씁니다.
- EBPAJ와 KADOKAWA 방식의 파일명과 디렉터리 규칙을 엄격하게 검증합니다.
- 고정 레이아웃의 이미지 페이지를 SVG로 감싸, 어떤 뷰어에서 열어도 페이지가 화면에 맞게 표시됩니다.
- 표지, 목차, 본문, 색인, 용어집을 구조적 시맨틱을 갖춘 랜드마크로 표시합니다.
생성한 출판물은 저장소에 포함된 스크립트로 W3C의 공식 EPUBCheck를 실행해 검사할 수 있습니다. 도구는 .tools/ 아래에 캐시됩니다.
문서 읽기go get github.com/publira/epub
f, err := os.Open("book.epub")
if err != nil {
log.Fatal(err)
}
defer f.Close()
st, err := f.Stat()
if err != nil {
log.Fatal(err)
}
opt := epub.WithCompliance(epub.LevelEBPAJ)
doc, err := epub.Decode(f, st.Size(), opt)
if err != nil {
log.Fatal(err)
}
if err := epub.Encode(out, doc); err != nil {
log.Fatal(err)
}
publira/messageformat-dart
LDML 48.2를 준수하는 Unicode MessageFormat 2.0의 순수 Dart 구현으로, 모바일 클라이언트의 메시지 포맷에 쓰입니다.
- Flutter에 의존하지 않는 순수 Dart 라이브러리라서, 서버, 명령줄, Flutter 앱 어디서나 똑같이 동작합니다.
- MessageFormat 워킹 그룹의 LDML48.2 적합성 테스트 461건을 하나도 건너뛰지 않고 모두 통과하며, 변경할 때마다 CI에서 확인합니다.
- 명세에서 Stable로 정한 함수(:string, :number, :integer, :offset, :currency, :percent)를 모두 지원하며, 모든 CLDR 로캘에서 숫자를 포맷할 수 있습니다.
- Draft 단계인 날짜와 시간 함수는 messageformat_datetime으로 분리되어 있어, 날짜를 다루지 않는 앱에는 해당 CLDR 데이터가 포함되지 않습니다.
- formatToParts는 sealed 클래스 계층의 파트를 반환하므로, 스타일을 적용한 텍스트 같은 출력을 만들 수 있습니다.
- 포맷 중 오류가 나도 예외를 던지지 않고 콜백으로 알린 뒤 대체 값으로 출력합니다. 기본 함수와 함께 사용자 정의 함수도 등록할 수 있습니다.
1.0 이전 버전입니다. LDML 48.2(UTS #35 Part 9, tr35-78)와 Unicode CLDR 48.2 데이터를 따르며, 이후 버전의 명세로 옮기는 작업은 별도 릴리스로 진행합니다.
문서 읽기dart pub add messageformat
import 'package:messageformat/messageformat.dart';
final mf = MessageFormat(
'en',
r'''
.input {$count :number}
.match $count
0 {{No episodes}}
one {{{$count} episode}}
* {{{$count} episodes}}
''',
options: const MessageFormatOptions(
bidiIsolation: BidiIsolation.none,
),
);
mf.format({'count': 0});
mf.format({'count': 1});
mf.format({'count': 1000});