@hidemikimura/receipt-html-to-pdf 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <!--
3
+ CDN から読み込むだけの例。npm もバンドラーもビルドも要らない。
4
+ このファイルをブラウザで開けばそのまま動く(`npx serve` などローカルサーバー経由が確実)。
5
+
6
+ ライブラリ本体もフォントも CDN から読む。フォントは 4.6MB あるので、実運用では
7
+ 使う文字に絞ってサブセット化したものを自前で配信すること(pyftsubset など)。
8
+ -->
9
+ <html lang="ja">
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <title>Receipt html to pdf — CDN から使う例</title>
13
+ <style>
14
+ /* PDF に埋め込むフォントと、ブラウザが計測に使うフォントは同じファイルにする */
15
+ @font-face {
16
+ font-family: "BIZ UDPGothic";
17
+ font-weight: 400;
18
+ /* registerFont に渡すファイルと必ず同じものにする。違うと文字幅がずれる */
19
+ src: url("https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/bizudpgothic/BIZUDPGothic-Regular.ttf") format("truetype");
20
+ }
21
+ body { font-family: system-ui, sans-serif; margin: 24px; line-height: 1.6; }
22
+ button { font-size: 15px; padding: 8px 16px; cursor: pointer; }
23
+ button[disabled] { cursor: default; opacity: .5; }
24
+ #status { margin-left: 12px; color: #555; font-size: 14px; }
25
+
26
+ /* 変換対象。用紙幅に合わせて組む */
27
+ /* 幅は A4(210mm)から左右の余白 15mm ずつを引いた 180mm ちょうど。
28
+ box-sizing: border-box を忘れると padding と border の分だけはみ出し、右端が切れる */
29
+ #receipt { font-family: "BIZ UDPGothic", sans-serif; font-size: 10pt; color: #222; width: 180mm; box-sizing: border-box; border: 1px solid #ccc; padding: 5mm; margin-top: 16px; }
30
+ #receipt h1 { text-align: center; font-size: 20pt; letter-spacing: .5em; margin: 0 0 6mm; padding-bottom: 2mm; border-bottom: 2px solid #222; }
31
+ #receipt .to { font-size: 13pt; font-weight: 700; border-bottom: 1px solid #222; padding: 0 2mm 1mm; min-width: 70mm; display: inline-block; }
32
+ #receipt .amount { font-size: 24pt; font-weight: 700; text-align: center; border: 1px solid #222; padding: 4mm; margin: 4mm 0; }
33
+ #receipt table { width: 100%; border-collapse: collapse; margin-top: 4mm; }
34
+ #receipt th, #receipt td { border: 1px solid #222; padding: 1.5mm 2mm; }
35
+ #receipt th { background: #f2f2f2; }
36
+ #receipt td.num { text-align: right; }
37
+ </style>
38
+ </head>
39
+ <body>
40
+
41
+ <button id="save" disabled>PDF をダウンロード</button>
42
+ <span id="status">読み込み中…</span>
43
+
44
+ <div id="receipt">
45
+ <h1>領 収 証</h1>
46
+ <p><span class="to">株式会社テスト商事 御中</span></p>
47
+ <p class="amount">¥33,000-(税込)</p>
48
+ <p>但し、下記の通り商品代として、上記金額を正に領収いたしました。</p>
49
+ <table>
50
+ <thead>
51
+ <tr><th>品名</th><th>数量</th><th>単価</th><th>金額</th><th>税率</th></tr>
52
+ </thead>
53
+ <tbody>
54
+ <tr><td>Web サイト制作サービス</td><td class="num">1</td><td class="num">30,000</td><td class="num">30,000</td><td class="num">10%</td></tr>
55
+ </tbody>
56
+ <tfoot>
57
+ <tr><td colspan="3">10% 対象</td><td class="num">30,000</td><td class="num">消費税 3,000</td></tr>
58
+ </tfoot>
59
+ </table>
60
+ </div>
61
+
62
+ <!--
63
+ ここが CDN からの読み込み。type="module" が要る。
64
+ バージョン(@0.2.1)は必ず固定する。外すと最新版が読み込まれ、更新のたびに挙動が変わりうる。
65
+ -->
66
+ <script type="module">
67
+ import * as ReceiptHtmlToPdf
68
+ from 'https://cdn.jsdelivr.net/npm/@hidemikimura/receipt-html-to-pdf@0.2.1/dist/receipt-html-to-pdf.min.js';
69
+
70
+ // type="module" でない普通のスクリプトからも使えるようにグローバルへ載せる
71
+ window.ReceiptHtmlToPdf = ReceiptHtmlToPdf;
72
+
73
+ const status = document.querySelector('#status');
74
+ const button = document.querySelector('#save');
75
+
76
+ try {
77
+ // 上の @font-face と同じファイルを渡す(TrueType の静的 TTF のみ。woff2 / otf は不可)
78
+ await ReceiptHtmlToPdf.registerFont({
79
+ family: 'BIZ UDPGothic',
80
+ weight: 400,
81
+ src: 'https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/bizudpgothic/BIZUDPGothic-Regular.ttf',
82
+ });
83
+ await document.fonts.ready;
84
+ status.textContent = `準備完了(v${ReceiptHtmlToPdf.version})`;
85
+ button.disabled = false;
86
+ } catch (e) {
87
+ status.textContent = `フォントの読み込みに失敗しました: ${e.message}`;
88
+ }
89
+
90
+ button.addEventListener('click', async () => {
91
+ button.disabled = true;
92
+ status.textContent = '生成中…';
93
+ try {
94
+ const pdf = await ReceiptHtmlToPdf.htmlToPdf(document.querySelector('#receipt'), {
95
+ page: { size: 'A4', margin: '15mm' },
96
+ metadata: { title: '領収証' },
97
+ onWarning: (w) => console.warn(w.code, w.message),
98
+ });
99
+ ReceiptHtmlToPdf.downloadPdf(pdf, 'receipt.pdf');
100
+ status.textContent = '生成しました';
101
+ } catch (e) {
102
+ status.textContent = `失敗: ${e.message}`;
103
+ } finally {
104
+ button.disabled = false;
105
+ }
106
+ });
107
+ </script>
108
+
109
+ </body>
110
+ </html>
package/examples/lit.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // Lit(Web Components)サンプル。
2
- // 注意: Shadow DOM 内の要素を渡すと、親文書のスタイルシートは継承されない。
3
- // 変換用の HTML light DOM に置く、または `stylesheets` オプションで CSS を明示的に渡す。
2
+ // シャドウルート内の要素をそのまま渡せる。シャドウ DOM は宣言的シャドウ DOM として
3
+ // 直列化され、`static styles`(adoptedStyleSheets)と <slot> の割り当ても引き継がれる。
4
+ // 親文書のスタイルシートはシャドウツリーに届かないので、シャドウ外の CSS に依存するなら
5
+ // `stylesheets` オプションで明示的に渡す。
4
6
  import { LitElement, html, css, unsafeCSS } from 'lit';
5
7
  import { registerFont, htmlToPdf, downloadPdf } from '@hidemikimura/receipt-html-to-pdf';
6
8
 
@@ -9,7 +11,7 @@ const fontsReady = Promise.all([
9
11
  registerFont({ family: 'BIZ UDPGothic', weight: 700, src: '/fonts/BIZUDPGothic-Bold.ttf' }),
10
12
  ]);
11
13
 
12
- // PDF 側にも同じスタイルを渡す(Shadow DOM CSS は inherit で拾えないため)
14
+ // シャドウ DOM 内のスタイル。PDF 変換時にもそのまま引き継がれる。
13
15
  const RECEIPT_CSS = `
14
16
  @font-face { font-family: "BIZ UDPGothic"; font-weight: 400; src: url("/fonts/BIZUDPGothic-Regular.ttf"); }
15
17
  @font-face { font-family: "BIZ UDPGothic"; font-weight: 700; src: url("/fonts/BIZUDPGothic-Bold.ttf"); }
@@ -26,9 +28,10 @@ export class ReceiptPdf extends LitElement {
26
28
  this.busy = true;
27
29
  try {
28
30
  await fontsReady;
31
+ // ホスト要素(this)を渡してもよい。その場合シャドウ DOM ごと変換される。
32
+ // シャドウルート内のスタイル(static styles)は既定の stylesheets: 'inherit' で引き継がれる。
29
33
  const el = this.renderRoot.querySelector('.receipt');
30
34
  const pdf = await htmlToPdf(el, {
31
- stylesheets: [RECEIPT_CSS],
32
35
  page: { size: 'A4', margin: '15mm' },
33
36
  metadata: { title: '領収証' },
34
37
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hidemikimura/receipt-html-to-pdf",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Receipt html to pdf — ブラウザ内で HTML/CSS をテキスト選択可能なベクター PDF に変換するライブラリ(日本の領収証・適格請求書向け)",
5
5
  "license": "MIT",
6
6
  "author": "Hidemi Kimura",
@@ -32,6 +32,7 @@
32
32
  "dist/receipt-html-to-pdf.min.js.map",
33
33
  "types",
34
34
  "examples",
35
+ "skills",
35
36
  "README.md",
36
37
  "LICENSE",
37
38
  "CHANGELOG.md"
@@ -52,7 +53,9 @@
52
53
  "fonts": "sh scripts/fetch-fonts.sh",
53
54
  "dev": "npx --yes serve -l 5173 .",
54
55
  "prepublishOnly": "npm run typecheck && npm test && npm run types && npm run build -- --check",
55
- "pack:check": "npm pack --dry-run"
56
+ "pack:check": "npm pack --dry-run",
57
+ "site": "node scripts/build-site.mjs",
58
+ "site:dev": "node scripts/build-site.mjs && npx --yes serve -l 5174 site"
56
59
  },
57
60
  "devDependencies": {
58
61
  "@playwright/test": "^1.50.0",
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: receipt-html-to-pdf
3
+ description: ブラウザ内で HTML/CSS をテキスト選択可能なベクター PDF に変換する @hidemikimura/receipt-html-to-pdf を使うときに読む。領収証・請求書・納品書など日本語帳票の PDF 出力、htmlToPdf / registerFont の使い方、フォント選定の制約、対応 CSS、複数ページ制御、よくあるエラーの対処を含む。
4
+ ---
5
+
6
+ # Receipt html to pdf
7
+
8
+ サーバーを使わず、ブラウザの中だけで HTML/CSS を**テキスト選択・検索できるベクター PDF** に変換するライブラリ。日本の領収証・適格請求書の出力を主な用途に作られている。依存ゼロ、minify で gzip 19KB。
9
+
10
+ レイアウトエンジンは持たない。非表示 iframe に HTML を描画してブラウザが計算した座標を読み取り、それを PDF の描画命令に変換する。つまり **Flexbox・Grid・テーブル・日本語の行分割と禁則は、ブラウザが表示したとおりに出る**。実装が担当するのは「見えているものを同じ位置に PDF へ書き出すこと」だけ。
11
+
12
+ ## 最初に確認すること
13
+
14
+ 作業を始める前に、次の 3 点を満たせるか確かめる。満たせないなら設計を変える必要がある。
15
+
16
+ 1. **ブラウザで動くか** — Node では動かない(DOM のレイアウト計算が必要)。サーバー側で PDF が要るなら Puppeteer など別の手段を使う。
17
+ 2. **埋め込むフォントファイルを用意できるか** — ブラウザは OS のフォントを読めないので、アプリ側が TTF を渡す。後述の制約あり。
18
+ 3. **必要な CSS が対応範囲か** — `box-shadow`・グラデーション・縦書きなどは出力されない(例外にはならず警告が出る)。
19
+
20
+ ## 使い方
21
+
22
+ ```sh
23
+ npm install @hidemikimura/receipt-html-to-pdf
24
+ ```
25
+
26
+ ```js
27
+ import { registerFont, htmlToPdf, downloadPdf } from '@hidemikimura/receipt-html-to-pdf';
28
+
29
+ // 1. フォント登録はアプリ起動時に 1 回だけ。パース結果はキャッシュされる。
30
+ await registerFont({ family: 'BIZ UDPGothic', weight: 400, src: '/fonts/BIZUDPGothic-Regular.ttf' });
31
+ await registerFont({ family: 'BIZ UDPGothic', weight: 700, src: '/fonts/BIZUDPGothic-Bold.ttf' });
32
+
33
+ // 2. 変換する要素は DOM 上にあり、スタイルが適用済みであること。
34
+ const pdf = await htmlToPdf(document.getElementById('receipt'), {
35
+ page: { size: 'A4', margin: '15mm' },
36
+ metadata: { title: '領収証 No. R-2026-000123', author: '株式会社サンプル商店' },
37
+ onWarning: (w) => console.warn(w.code, w.message),
38
+ });
39
+
40
+ downloadPdf(pdf, 'receipt.pdf');
41
+ ```
42
+
43
+ npm を使わない場合は CDN の URL をそのまま `import` できる(`dist/` は依存ゼロの単一 ESM)。バージョンは必ず固定する。
44
+
45
+ ```html
46
+ <script type="module">
47
+ import { registerFont, htmlToPdf, downloadPdf }
48
+ from 'https://cdn.jsdelivr.net/npm/@hidemikimura/receipt-html-to-pdf@0.2.1/dist/receipt-html-to-pdf.min.js';
49
+ </script>
50
+ ```
51
+
52
+ グローバル変数を配るビルド(IIFE / UMD)は無い。`type="module"` でない普通のスクリプトから使いたいときは、`import * as ReceiptHtmlToPdf` して `window` に載せる。モジュールスクリプトは `defer` 相当で後から実行されるので、読み込み完了をイベントで知らせるかボタンを `disabled` にしておくこと。動く一式は `examples/cdn.html`。
53
+
54
+ `htmlToPdf` は既定で `Blob` を返す。`output: 'uint8array'` でバイト列、`'dataurl'` で data URL。サーバーへ送るなら `Blob` のまま `FormData` に入れればよい。
55
+
56
+ ## フォント: ここが一番詰まる
57
+
58
+ **HTML 側の `@font-face` と `registerFont` に同じファイルを渡すこと。** ブラウザが計測に使うフォントと PDF に埋め込むグリフが一致していないと、文字幅がずれる。
59
+
60
+ ```css
61
+ @font-face { font-family: "BIZ UDPGothic"; font-weight: 400; src: url("/fonts/BIZUDPGothic-Regular.ttf"); }
62
+ ```
63
+
64
+ 使えるフォントファイルの制約:
65
+
66
+ | 形式 | 可否 |
67
+ |---|---|
68
+ | TrueType(`glyf` アウトライン)の**静的** TTF | ✅ |
69
+ | OpenType/CFF(`.otf` の多く) | ❌ `CFF outlines are not supported` で例外 |
70
+ | 可変フォント(`fvar` あり) | ⚠️ デフォルトインスタンスだけ埋め込まれ、Bold などは出ない(console.warn) |
71
+ | WOFF / WOFF2 / TTC | ❌ 例外 |
72
+
73
+ 推奨は **BIZ UDPGothic**(Regular / Bold、SIL OFL、Google Fonts の静的 TTF)。
74
+
75
+ **Noto Sans JP に注意**: Google Fonts 配布版は可変フォント(`NotoSansJP[wght].ttf`)で Bold が出ない。GitHub の notofonts 配布版 OTF は CFF で埋め込めない。Noto を使うなら `fonttools varLib.instancer` で静的 TTF にインスタンス化したものを渡す。
76
+
77
+ 日本語フォントは 4〜5MB ある。初回ロードを軽くしたいなら、使う文字に絞ってサブセット化した TTF を配信する(`pyftsubset` など)。`registerFont` は URL のほか `ArrayBuffer` も受け取る。
78
+
79
+ ## 主なオプション
80
+
81
+ | オプション | 既定 | 説明 |
82
+ |---|---|---|
83
+ | `page.size` | `'A4'` | `A3` `A4` `A5` `B4` `B5` `Letter` `Legal` または `{ width: '80mm', height: '200mm' }` |
84
+ | `page.orientation` | `'portrait'` | `'landscape'` |
85
+ | `page.margin` | `'15mm'` | 文字列で四辺共通、`{ top, right, bottom, left }` で個別 |
86
+ | `header` / `footer` | `null` | 各ページに合成する HTML。`{{pageNumber}}` `{{totalPages}}` を置換 |
87
+ | `stylesheets` | `'inherit'` | 親文書の `<style>` / `<link>` を継承。`'none'`、または URL・CSS 文字列の配列 |
88
+ | `mediaPrint` | `false` | `@media print {}` の中身を通常ルールとして適用(`@media screen {}` は除去) |
89
+ | `fontFallback` | `[]` | 未登録ファミリーが要求されたときに試す family 名の順序 |
90
+ | `metadata` | — | `title` `author` `subject` `keywords` `creator` `creationDate`。日本語可 |
91
+ | `compress` | `true` | `CompressionStream` があれば FlateDecode |
92
+ | `baseUrl` | 現在の文書 | 相対 URL(フォント・画像)の基準 |
93
+ | `output` | `'blob'` | `'uint8array'` `'dataurl'` |
94
+ | `onWarning` | — | 未対応 CSS・欠落グリフ・画像失敗の通知。**必ず配線する** |
95
+
96
+ ## 対応している CSS の要点
97
+
98
+ レイアウト系(`display` 全般・Flexbox・Grid・テーブル・`position`・`margin`・`padding`・`white-space`・`word-break`・`text-align`・`line-height`・`letter-spacing`)は**ブラウザの計算結果をそのまま使うので全部そのとおりに出る**。追加実装は不要。
99
+
100
+ 描画系で対応しているもの: `color`、`background-color`、`background-image: url()`(単一・`no-repeat`)、`border-*`(辺ごと、solid / dashed / dotted)、`border-collapse: collapse`、`border-radius`、`opacity`、`text-decoration`(underline / line-through)、`overflow: hidden` のクリップ、`<img>`(PNG 透過・JPEG・`object-fit`)、2D `transform`、`::before` / `::after`(引用文字列の `content` のみ)。
101
+
102
+ **出力されないもの**(`onWarning` に `unsupported-css` が届く): `box-shadow`、`text-shadow`、グラデーション、`filter`、`clip-path`、`outline`、縦書き、3D transform、`counter()` の content、インライン `<svg>` / `<canvas>` / `<video>`。
103
+
104
+ 代替の指針: 影 → ボーダーか薄い背景色。グラデーション → 単色か画像。インライン SVG → `<img src="x.svg">`(ラスタライズされて埋め込まれる)。
105
+
106
+ 全プロパティの詳細表は `docs/css-support.md`。
107
+
108
+ ## 複数ページ
109
+
110
+ ページ分割は「命令を動かさず、ページごとに描く範囲を決める」方式。境界は次のものを跨がない位置まで自動で繰り上がる。
111
+
112
+ - テキストの行、`<tr>`、`<thead>`、`<tfoot>`、`<img>`
113
+ - `break-inside: avoid`(`page-break-inside: avoid`)を指定した要素
114
+
115
+ 強制改ページは `break-before: page` / `break-after: page`(`page-break-*: always` も可)。表が次ページへ続くときは **`<thead>` が各ページ先頭に、`<tfoot>` がそのページ最後の行の直下に**自動で繰り返される。
116
+
117
+ ページ番号を入れるなら:
118
+
119
+ ```js
120
+ footer: '<div style="text-align:center;font-size:8pt">{{pageNumber}} / {{totalPages}}</div>'
121
+ ```
122
+
123
+ `orphans` / `widows` / `break-*: avoid` / `@page` は未対応。用紙サイズと余白は `options.page` で指定する。
124
+
125
+ ## 変換対象の要素についての決まり
126
+
127
+ - 要素は **DOM 上にあり、スタイルとフォントが適用済み**であること。`document.fonts.ready` を待ってから呼ぶと確実。
128
+ - 要素を渡すと、親文書の `<html>` / `<body>` の属性(class・lang・data-*)も内部 iframe に写される。`body.reissue .watermark { display: block }` のような祖先依存のセレクタがそのまま効く。
129
+ - **Web Components**: シャドウ DOM に対応している。ホスト要素をそのまま渡してよく、`<slot>` の割り当て・`:host` / `::slotted()`・`adoptedStyleSheets`(Lit の `static styles`)・`:defined` はすべて引き継がれる。シャドウルート内の要素(`renderRoot.querySelector()`)を渡した場合も、そのツリーのスタイルは既定の `stylesheets: 'inherit'` で拾われる。
130
+ - ただし **`closed` なシャドウルートは中身が出ない**(外から参照できないため)。`Element.getHTML()` が無い古いブラウザでも同様で、その場合は警告が出る。
131
+ - iframe 側ではカスタム要素はアップグレードされない。`connectedCallback` で DOM を組む要素は、`customElements.whenDefined()` と `document.fonts.ready` を待ってから変換する。
132
+ - **幅は本文領域に合わせる**。A4・余白 15mm なら 180mm ちょうど。固定幅に `padding` / `border` を足すときは `box-sizing: border-box` を付けないと右端が切れる(`other` の警告が出る)。
133
+ - HTML 文字列も渡せる。その場合 `stylesheets` を明示するのが確実。
134
+ - `display: none` の要素は子孫ごと出力されない(場所も取らない)。渡したルート要素自身が `display: none` だと空の PDF になる。`visibility: hidden` は描かれないが場所は残るので、PDF 上は空白になる。
135
+ - **画面に出さずに PDF にだけ載せたい**ときは `display: none` ではなく、画面外へ逃がす(`position: absolute; left: -10000px`)か、`@media print` に書いて `mediaPrint: true` で変換する。
136
+ - クロスオリジン画像には `crossorigin="anonymous"` と CORS ヘッダーが要る。無いと `image-failed` 警告になり、その画像だけ描かれない。
137
+
138
+ ## 日本の領収証を作るとき
139
+
140
+ 適格請求書(インボイス)の記載事項、消費税の端数処理、収入印紙の扱いは `references/receipt-format.md` を読む。**領収証 HTML を新しく生成するなら必ず参照すること**(記載漏れがあると買い手が仕入税額控除を受けられない)。
141
+
142
+ ## エラーが出たら
143
+
144
+ `references/troubleshooting.md` に症状別の原因と対処をまとめてある。よくあるもの:
145
+
146
+ - `no fonts registered` → `registerFont` を `htmlToPdf` より先に await する
147
+ - `CFF outlines are not supported` → OTF ではなく静的 TTF を渡す
148
+ - 文字が □ になる → そのフォントにグリフが無い(`missing-glyph` 警告に該当文字が出る)
149
+ - 文字がずれる → `@font-face` と `registerFont` のファイルが違う
150
+ - **右端が切れる** → 内容が本文領域より横に広い(`other` の警告が出る)。A4・余白 15mm なら本文領域は 180mm ちょうど。`width: 180mm` に `padding` / `border` を足すなら `box-sizing: border-box` を付ける
151
+ - 何も描かれない → 要素が `display:none`、または Shadow DOM でスタイルが届いていない
@@ -0,0 +1,56 @@
1
+ # 日本の領収証を HTML で作るときの決まり
2
+
3
+ 領収証 HTML を新規に生成・修正するときの記載要件をまとめる。リポジトリの `fixtures/receipt-invoice/index.html` が、この要件をすべて満たした実物のテンプレートなので、迷ったらそれを写すのが早い。
4
+
5
+ > 税務の最終判断は税理士等に確認すること。ここに書くのは HTML を組むうえでの構造的な要件。
6
+
7
+ ## 適格請求書(インボイス)の記載事項
8
+
9
+ 領収証は民法 486 条の受取証書だが、買い手が仕入税額控除を受けるには、消費税法 57 条の 4 が定める**適格請求書の記載事項 6 つ**を満たす必要がある。どれか 1 つでも欠けると控除に使えない。
10
+
11
+ | # | 記載事項 | HTML 上の置き場所の例 |
12
+ |---|---|---|
13
+ | 1 | 適格請求書発行事業者の氏名または名称、および**登録番号** | 発行者ブロック。登録番号は `T` + 13 桁 |
14
+ | 2 | 取引年月日 | 「発行日」 |
15
+ | 3 | 取引内容(**軽減税率対象品目である旨**) | 明細の品名。軽減対象に `※` を付け、欄外に「※は軽減税率対象」と注記 |
16
+ | 4 | **税率ごとに区分して合計した対価の額**および適用税率 | 内訳表の「10% 対象」「8% 対象」行 |
17
+ | 5 | **税率ごとに区分した消費税額等** | 内訳表の「消費税額」列 |
18
+ | 6 | 書類の交付を受ける事業者の氏名または名称 | 宛名「○○ 御中」 |
19
+
20
+ 不特定多数に交付する小売・飲食・タクシーなどは**適格簡易請求書**でよく、その場合 6(宛名)は省略でき、「適用税率」か「税率ごとの消費税額」のどちらか一方でよい。
21
+
22
+ ## 消費税の端数処理
23
+
24
+ **1 つの適格請求書につき、税率ごとに 1 回**しか端数処理できない。
25
+
26
+ - ✅ 税率ごとに税抜金額を合計してから税率を掛け、1 回だけ丸める
27
+ - ❌ 明細行ごとに税額を計算して丸め、それを合計する
28
+
29
+ 丸め方(切り捨て・切り上げ・四捨五入)は事業者が選べるが、書類内で統一する。実装では切り捨てが一般的。
30
+
31
+ ```
32
+ taxable10 = 34,000 → tax10 = floor(34000 * 0.10) = 3,400
33
+ taxable8 = 4,200 → tax8 = floor(4200 * 0.08) = 336
34
+ 合計 = 38,200 + 3,736 = 41,936
35
+ ```
36
+
37
+ ## 収入印紙
38
+
39
+ **電子データ(PDF)として交付する領収証は印紙税の課税対象外**。メール添付やダウンロードで渡すなら印紙欄は不要で、「本書は電子的に交付された領収証であり、収入印紙は不要です」と明記しておくと親切。
40
+
41
+ 紙に印刷して交付する場合は、記載金額 5 万円以上で印紙が必要(税抜金額が区分記載されていれば税抜で判定)。この場合は印紙貼付欄を設ける。
42
+
43
+ ## 慣例的な書き方
44
+
45
+ - **金額**: 改ざん防止のため先頭に `¥`、末尾に `-` を付ける(`¥41,936-`)
46
+ - **但し書き**: 「お品代」だけでは記載事項 3(取引内容)を満たさない。「下記の通り商品代として」とし、具体的内容は明細表に委ねる
47
+ - **宛名**: 「上様」は取引先名として不適切。正式名称 + 「御中」
48
+ - **社判**: 法的な必須要件ではないが商慣習として押す。透過 PNG を `position:absolute` で重ねる
49
+ - **再発行**: 二重計上を防ぐため「再発行」と明示する。`transform: rotate()` + `opacity` の透かしで表現できる
50
+
51
+ ## このライブラリで組むときの注意
52
+
53
+ - 金額欄の右寄せ(`text-align: right`)はブラウザの計算どおりに出るので追加実装は不要
54
+ - `font-variant-numeric: tabular-nums` は GSUB 依存で**効かない**。数字が元から等幅のフォント(BIZ UDPGothic は等幅)を使う
55
+ - 明細が長くなる帳票では、集計ブロックと発行者ブロックに `break-inside: avoid` を付けるとページ境界で割れない
56
+ - `<thead>` に見出し行、`<tfoot>` に合計行を置くと、複数ページ時に各ページへ自動で繰り返される
@@ -0,0 +1,45 @@
1
+ # 症状別の原因と対処
2
+
3
+ ## 例外になるもの
4
+
5
+ | エラーメッセージ | 原因 | 対処 |
6
+ |---|---|---|
7
+ | `htmlToPdf: no fonts registered` | `registerFont` を呼ぶ前、または await せずに `htmlToPdf` を呼んだ | フォント登録の Promise を保持して await してから変換する |
8
+ | `htmlToPdf must run in a browser` | Node で呼んだ | ブラウザで実行する。サーバー側 PDF が必要なら別の手段を検討 |
9
+ | `CFF outlines are not supported` | OpenType/CFF の `.otf` を渡した | TrueType(`glyf`)の静的 TTF を渡す |
10
+ | `WOFF/WOFF2 are not supported` | Web フォント形式を渡した | 元の `.ttf` を配信して渡す |
11
+ | `Not a TrueType font (bad sfnt version)` | フォント以外(HTML エラーページなど)を取得した | URL と配信サーバーの Content-Type を確認する |
12
+ | `Unknown page size "..."` | `page.size` の綴り違い | `A3` `A4` `A5` `B4` `B5` `Letter` `Legal` または `{width, height}` |
13
+ | `Page content area is empty` | 余白 + ヘッダー + フッターの高さが用紙を超えた | 余白を減らすか、ヘッダー/フッターを低くする |
14
+
15
+ ## 見た目がおかしいもの
16
+
17
+ | 症状 | 原因 | 対処 |
18
+ |---|---|---|
19
+ | 文字が □ になる | そのフォントに該当グリフが無い(丸囲み数字・㈱・㍿ など) | `missing-glyph` 警告に該当文字が出る。グリフを持つフォントを `fontFallback` に足す |
20
+ | 文字幅が少しずつずれる | `@font-face` と `registerFont` のファイルが別物 | 同じ TTF を両方に渡す |
21
+ | Bold が Regular で出る | 可変フォントを渡した(console.warn が出る) | 静的 TTF の Bold を別途 `registerFont` する |
22
+ | 何も描かれない | 要素が `display:none`、または DOM 上に無い | 表示された状態の要素を渡す |
23
+ | カスタム要素の中身が出ない(枠だけになる) | `closed` なシャドウルート、または `Element.getHTML()` が無いブラウザ | `mode: 'open'` にする。警告(`unsupported-css`)にブラウザ側の理由が出ている |
24
+ | カスタム要素の中身が古い | `connectedCallback` の描画が終わる前に変換した | `customElements.whenDefined()` と `document.fonts.ready` を待ってから呼ぶ |
25
+ | シャドウ外の CSS が当たらない | 文書のスタイルシートはシャドウツリーに届かない(ブラウザの仕様どおり) | 必要な CSS をシャドウルート内に置くか、`stylesheets` に文字列で渡す |
26
+ | 右端が切れる | 内容が本文領域より横に広い。`other` の警告にはみ出し量が出る(0.2.1 以降) | A4・余白 15mm なら本文領域は 180mm。固定幅 + `padding` / `border` には `box-sizing: border-box` を付ける。縮まない表なら列幅を見直す |
27
+ | 画像が出ない | クロスオリジンで CORS ヘッダーが無い | `crossorigin="anonymous"` と `Access-Control-Allow-Origin` を設定する。`image-failed` 警告が出ている |
28
+ | 影や角丸グラデーションが消える | 未対応 CSS | `onWarning` の `unsupported-css` を見る。ボーダーや単色で代替する |
29
+ | テーブルの罫線が二重になる | `border-collapse: separate` のまま隣接セルに罫線を引いた | `border-collapse: collapse` を使う |
30
+
31
+ ## ページ分割の症状
32
+
33
+ | 症状 | 原因 | 対処 |
34
+ |---|---|---|
35
+ | 意図しない位置で切れる | 分割禁止の指定が無い | 割りたくないブロックに `break-inside: avoid` |
36
+ | 表の見出しが 2 ページ目に無い | 見出し行が `<thead>` に入っていない | `<thead>` でマークアップする(自動で繰り返される) |
37
+ | ページ数が想定と違う | ヘッダー/フッターの高さぶん本文領域が縮む | フッターを付けると 1 ページあたりの行数が減る。実測して期待値を決める |
38
+ | ページ下部が大きく空く | 直後に分割禁止の大きなブロックがある | そのブロックを分割可能にするか、明示的に `break-before: page` を置く |
39
+
40
+ ## 確認の手順
41
+
42
+ 1. `onWarning` をコンソールに出して、未対応 CSS・欠落グリフ・画像失敗が出ていないか見る
43
+ 2. 生成した PDF を開いてテキストを選択・コピーできるか確認する(できなければフォント埋め込みの問題)
44
+ 3. `pdftotext` や pdf.js でテキストを抽出し、期待する文字列が全部入っているか照合する
45
+ 4. 構造を疑うなら `qpdf --check` にかける
package/src/index.js CHANGED
@@ -80,7 +80,7 @@ export { expandPrintMediaCss } from './renderer.js';
80
80
  */
81
81
 
82
82
  /** ライブラリのバージョン(package.json と同期) */
83
- export const version = '0.1.0';
83
+ export const version = '0.2.1';
84
84
 
85
85
  /** モジュール共有のフォントレジストリ */
86
86
  const registry = new FontRegistry();