@hidemikimura/receipt-html-to-pdf 0.2.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.
- package/CHANGELOG.md +17 -0
- package/README.md +20 -4
- package/dist/receipt-html-to-pdf.min.js +6 -6
- package/dist/receipt-html-to-pdf.min.js.map +3 -3
- package/examples/cdn.html +110 -0
- package/package.json +1 -1
- package/skills/receipt-html-to-pdf/SKILL.md +13 -0
- package/skills/receipt-html-to-pdf/references/troubleshooting.md +1 -0
- package/src/index.js +1 -1
- package/src/renderer.js +27 -1
- package/types/index.d.ts +1 -1
|
@@ -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/package.json
CHANGED
|
@@ -40,6 +40,17 @@ const pdf = await htmlToPdf(document.getElementById('receipt'), {
|
|
|
40
40
|
downloadPdf(pdf, 'receipt.pdf');
|
|
41
41
|
```
|
|
42
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
|
+
|
|
43
54
|
`htmlToPdf` は既定で `Blob` を返す。`output: 'uint8array'` でバイト列、`'dataurl'` で data URL。サーバーへ送るなら `Blob` のまま `FormData` に入れればよい。
|
|
44
55
|
|
|
45
56
|
## フォント: ここが一番詰まる
|
|
@@ -118,6 +129,7 @@ footer: '<div style="text-align:center;font-size:8pt">{{pageNumber}} / {{totalPa
|
|
|
118
129
|
- **Web Components**: シャドウ DOM に対応している。ホスト要素をそのまま渡してよく、`<slot>` の割り当て・`:host` / `::slotted()`・`adoptedStyleSheets`(Lit の `static styles`)・`:defined` はすべて引き継がれる。シャドウルート内の要素(`renderRoot.querySelector()`)を渡した場合も、そのツリーのスタイルは既定の `stylesheets: 'inherit'` で拾われる。
|
|
119
130
|
- ただし **`closed` なシャドウルートは中身が出ない**(外から参照できないため)。`Element.getHTML()` が無い古いブラウザでも同様で、その場合は警告が出る。
|
|
120
131
|
- iframe 側ではカスタム要素はアップグレードされない。`connectedCallback` で DOM を組む要素は、`customElements.whenDefined()` と `document.fonts.ready` を待ってから変換する。
|
|
132
|
+
- **幅は本文領域に合わせる**。A4・余白 15mm なら 180mm ちょうど。固定幅に `padding` / `border` を足すときは `box-sizing: border-box` を付けないと右端が切れる(`other` の警告が出る)。
|
|
121
133
|
- HTML 文字列も渡せる。その場合 `stylesheets` を明示するのが確実。
|
|
122
134
|
- `display: none` の要素は子孫ごと出力されない(場所も取らない)。渡したルート要素自身が `display: none` だと空の PDF になる。`visibility: hidden` は描かれないが場所は残るので、PDF 上は空白になる。
|
|
123
135
|
- **画面に出さずに PDF にだけ載せたい**ときは `display: none` ではなく、画面外へ逃がす(`position: absolute; left: -10000px`)か、`@media print` に書いて `mediaPrint: true` で変換する。
|
|
@@ -135,4 +147,5 @@ footer: '<div style="text-align:center;font-size:8pt">{{pageNumber}} / {{totalPa
|
|
|
135
147
|
- `CFF outlines are not supported` → OTF ではなく静的 TTF を渡す
|
|
136
148
|
- 文字が □ になる → そのフォントにグリフが無い(`missing-glyph` 警告に該当文字が出る)
|
|
137
149
|
- 文字がずれる → `@font-face` と `registerFont` のファイルが違う
|
|
150
|
+
- **右端が切れる** → 内容が本文領域より横に広い(`other` の警告が出る)。A4・余白 15mm なら本文領域は 180mm ちょうど。`width: 180mm` に `padding` / `border` を足すなら `box-sizing: border-box` を付ける
|
|
138
151
|
- 何も描かれない → 要素が `display:none`、または Shadow DOM でスタイルが届いていない
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
| カスタム要素の中身が出ない(枠だけになる) | `closed` なシャドウルート、または `Element.getHTML()` が無いブラウザ | `mode: 'open'` にする。警告(`unsupported-css`)にブラウザ側の理由が出ている |
|
|
24
24
|
| カスタム要素の中身が古い | `connectedCallback` の描画が終わる前に変換した | `customElements.whenDefined()` と `document.fonts.ready` を待ってから呼ぶ |
|
|
25
25
|
| シャドウ外の CSS が当たらない | 文書のスタイルシートはシャドウツリーに届かない(ブラウザの仕様どおり) | 必要な CSS をシャドウルート内に置くか、`stylesheets` に文字列で渡す |
|
|
26
|
+
| 右端が切れる | 内容が本文領域より横に広い。`other` の警告にはみ出し量が出る(0.2.1 以降) | A4・余白 15mm なら本文領域は 180mm。固定幅 + `padding` / `border` には `box-sizing: border-box` を付ける。縮まない表なら列幅を見直す |
|
|
26
27
|
| 画像が出ない | クロスオリジンで CORS ヘッダーが無い | `crossorigin="anonymous"` と `Access-Control-Allow-Origin` を設定する。`image-failed` 警告が出ている |
|
|
27
28
|
| 影や角丸グラデーションが消える | 未対応 CSS | `onWarning` の `unsupported-css` を見る。ボーダーや単色で代替する |
|
|
28
29
|
| テーブルの罫線が二重になる | `border-collapse: separate` のまま隣接セルに罫線を引いた | `border-collapse: collapse` を使う |
|
package/src/index.js
CHANGED
package/src/renderer.js
CHANGED
|
@@ -50,6 +50,7 @@ export async function renderDocument(input, opts) {
|
|
|
50
50
|
fit();
|
|
51
51
|
// レイアウトを確定させる
|
|
52
52
|
void doc.body.offsetHeight;
|
|
53
|
+
warnIfTooWide(doc, opts.widthPx, opts.warn ?? (() => {}));
|
|
53
54
|
|
|
54
55
|
return {
|
|
55
56
|
iframe,
|
|
@@ -68,7 +69,9 @@ export async function renderDocument(input, opts) {
|
|
|
68
69
|
function buildHtml(input, opts) {
|
|
69
70
|
const baseUrl = opts.baseUrl ?? document.baseURI;
|
|
70
71
|
const base = `<base href="${escapeAttr(baseUrl)}">`;
|
|
71
|
-
|
|
72
|
+
// 親文書の body マージンが PDF に持ち込まれると内容が右へずれて右端が切れるので、
|
|
73
|
+
// !important で確実に打ち消す(用紙の余白は options.page.margin が受け持つ)。
|
|
74
|
+
const reset = `<style data-rhtp-reset>html,body{margin:0 !important;padding:0 !important;background:transparent}html{-webkit-text-size-adjust:100%}</style>`;
|
|
72
75
|
|
|
73
76
|
if (typeof input === 'string') {
|
|
74
77
|
// 完全な HTML 文書ならそのまま。<head> の直後に base とリセットを差し込む。
|
|
@@ -151,6 +154,29 @@ function shadowHostStyles(input, stylesheets, mediaPrint) {
|
|
|
151
154
|
return parts.map((css) => `<style>${mediaPrint ? expandPrintMediaCss(css) : css}</style>`).join('');
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
/**
|
|
158
|
+
* 内容が本文領域より横に広いと、右側が切れたまま気づかれにくいので警告する。
|
|
159
|
+
* 固定幅 + `box-sizing: content-box` や、畳めない表が原因になりやすい。
|
|
160
|
+
*
|
|
161
|
+
* @param {Document} doc
|
|
162
|
+
* @param {number} widthPx 本文領域の幅(px)
|
|
163
|
+
* @param {(w: import('./index.js').ConversionWarning) => void} warn
|
|
164
|
+
*/
|
|
165
|
+
function warnIfTooWide(doc, widthPx, warn) {
|
|
166
|
+
const width = Math.max(doc.documentElement.scrollWidth, doc.body.scrollWidth);
|
|
167
|
+
const over = width - widthPx;
|
|
168
|
+
// 1px 未満は丸め誤差とみなす
|
|
169
|
+
if (over < 1) return;
|
|
170
|
+
const mm = (/** @type {number} */ px) => Math.round((px / 96) * 25.4 * 10) / 10;
|
|
171
|
+
warn({
|
|
172
|
+
code: 'other',
|
|
173
|
+
message:
|
|
174
|
+
`Content is ${Math.round(over)}px (${mm(over)}mm) wider than the page content area ` +
|
|
175
|
+
`(${Math.round(width)}px vs ${Math.round(widthPx)}px); the right side will be clipped. ` +
|
|
176
|
+
'Common causes: a fixed width plus padding/border without box-sizing: border-box, or a table that cannot shrink.',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
154
180
|
/** シャドウルートを持てない要素(void 要素)。outerHTML にフォールバックする。 */
|
|
155
181
|
const VOID_TAGS = new Set(['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'LINK', 'META', 'SOURCE', 'TRACK', 'WBR']);
|
|
156
182
|
|
package/types/index.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export { expandPrintMediaCss } from "./renderer.js";
|
|
|
93
93
|
* @typedef {Element|string} ConvertInput
|
|
94
94
|
*/
|
|
95
95
|
/** ライブラリのバージョン(package.json と同期) */
|
|
96
|
-
export const version: "0.2.
|
|
96
|
+
export const version: "0.2.1";
|
|
97
97
|
/**
|
|
98
98
|
* 登録するフォントの定義。
|
|
99
99
|
* `src` は TrueType アウトライン(glyf)を持つ静的 TTF のみ対応。
|