AI生成コンテンツ / AI-generated content
外出先でスマートフォンのカメラをQRコードに向け、内容を確かめたくなることがある。逆に、URLや文章をQRコードにして相手へ渡したいこともある。本項では、文字列をQRコードへ変換して画像・PDFに保存し、カメラや画像ファイルからQRコードを読み取ってテキスト・Word・Excel・PDFに保存する「QRコード生成・読取ツール」(QRWorks.html)を作る。対応するパソコン・スマートフォンのブラウザ内で生成・読取・保存を完結させ、内容を外部へ送信しない。スマートフォンでの利用はOSやブラウザの制約を受ける。
作成の動機に続き、QRコードそのものの仕組み、生成・読取に使うライブラリ、重い処理を分離するWeb Worker、内蔵カメラの取得、読み取った文字列のリンク化、そしてWord・Excelファイルをライブラリなしで一から組み立てる方法までを順に解説する。最後にCodexへ渡したプログラム仕様書を示す。
目次
作成の動機
飲食店のメニューや掲示物、名刺の隅など、QRコードを見かける機会は多い。読み取ればURLや連絡先がすぐ分かって便利だが、逆に自分から何かを渡したい場面もある。出先で会った相手に「ぱふぅ家のホームページ」のURLを伝えたい、自宅Wi-Fiの接続情報を家族の端末に渡したい、といった用途である。そのたびにアプリを探したりオンラインサービスを開いたりするのは面倒であり、手元に生成と読取の両方をこなす小さなツールが1つあれば十分である。

既存の手段を比較すると、それぞれに一長一短がある。
既存の手段を比較すると、それぞれに一長一短がある。
| 手段 | できること | 困る点 |
|---|---|---|
| スマートフォンの標準カメラ | QRコードの読取 | 生成はできない機種が多い |
| QRコード作成アプリ | 生成、読取 | 端末ごとに導入が要る。広告表示があるものも多い |
| オンラインのQR作成サービス | 生成、読取、装飾 | 入力した文字列や読み取った画像がサーバへ送られる場合がある |
| QRコード生成・読取ツール(本項) | 生成、読取、画像・文書への保存 | 1枚ずつしか扱えない。装飾機能はない |
このうち気になるのは、オンラインサービスの「文字列や画像がサーバへ送られる」という点である。QRコードにしたい文字列には、Wi-Fiのパスワードや自宅の住所、私的な連絡先が含まれることがある。読み取る側も同様で、届いた紙のQRコードにどんな情報が仕込まれているか分からないまま、それを他人の運用するサーバへ送って解析してもらうのは順序が逆である。

そこで、HTML・CSS・JavaScriptを1つのファイルにまとめ、ブラウザの中だけで生成と読取を完結させることにした。ファイルをブラウザで開けば動き、HTTPサーバもインターネット接続も要らない。文字列も画像も端末の外に出ない。QRコードの生成に使うライブラリ、読取に使うライブラリも、取得は開発時の1回だけで、HTMLファイルの中にあらかじめ組み込んである。
そこで、HTML・CSS・JavaScriptを1つのファイルにまとめ、ブラウザの中だけで生成と読取を完結させることにした。ファイルをブラウザで開けば動き、HTTPサーバもインターネット接続も要らない。文字列も画像も端末の外に出ない。QRコードの生成に使うライブラリ、読取に使うライブラリも、取得は開発時の1回だけで、HTMLファイルの中にあらかじめ組み込んである。
QRコードの仕組み
QRコードは、1994年(平成6年)に自動車部品メーカーの生産管理向けとして、デンソーウェーブ社(当時のデンソーの一部門)が開発した二次元コードである。QRはQuick Response(高速応答)の略で、バーコードが横方向にしか情報を持たないのに対し、縦横2方向に点(モジュールという)を並べることで、同じ面積によりたくさんの情報を詰め込める。仕様はISO/IEC 18004として公開されており、誰でも自由に使える。ただし「QRコード」という名称そのものは株式会社デンソーウェーブの登録商標である。

QRコードの四角い模様は、でたらめに黒白が並んでいるわけではなく、役割の決まった部品の組み合わせでできている。
QRコードの四角い模様は、でたらめに黒白が並んでいるわけではなく、役割の決まった部品の組み合わせでできている。
3隅にある二重の四角形がファインダパターン(位置検出用パターン)である。カメラや読み取り機は、まずこの3つの四角形を探すことでQRコードの位置と向きを知る。3隅だけにあり残る1隅にはない、という非対称な配置になっているのは、それによって上下左右の向きも判別できるようにするためである。読み取りに失敗しやすいのは、この3つの四角形のどれかが影や反射で隠れているときである。QRコードを撮影するときは、4辺の余白(クワイエットゾーン)を含めて全体が映るようにする、というQRWorksの操作案内は、このファインダパターンを確実に見つけてもらうための注意である。

QRコードにはバージョンという区分があり、1から40まである。バージョン1は21×21モジュールの最小サイズで、数字が上がるごとに4モジュールずつ大きくなり、バージョン40では177×177モジュールに達する。同じ内容でも、収める文字数が多いほど、また誤り訂正の強さを上げるほど、必要なバージョン(=モジュール数)は大きくなる。QRWorksでは、どのバージョンを使うかを人間が指定していない。文字数から収まる最小のバージョンをライブラリが自動的に選ぶ仕組みになっており、これは次項で扱う。

データの持たせ方にも種類がある。数字だけの数字モード、英数字と一部の記号までの英数字モード、データ量の単位であるバイト(8ビット)を並べた、任意のバイト列を扱えるバイトモード、日本語の漢字モードである。同じ文字数でも、モードによって詰め込める情報量が変わる。QRWorksは日本語を含む任意の文章を扱うため、常にバイトモードを使い、文字列はUTF-8のバイト列に変換してから収めている。この選択は、次項のコードで qr.addData(data.text, 'Byte') という1行になって表れる。QRコードには文字コードを明示するECIという情報を付ける仕組みもあるが、QRWorksの実装ではこの情報を付加していない。他社製の読み取り機が別の文字コードで解釈すると文字化けする場合がある、という取扱説明書の注意は、この仕組みに由来する。

QRコードがもっとも工夫されているのは、誤り訂正の仕組みである。印刷したQRコードの一部が汚れたり、破れたり、光を反射して読み取れなかったりしても、内容を復元できる場合がある。これは、リード・ソロモン符号という誤り訂正符号の技術によって、本来のデータに加えて「検算用の点」を一緒に記録しているためである。検算用の点をどれだけ多く持たせるかによって、下表の4段階の水準がある。
QRコードにはバージョンという区分があり、1から40まである。バージョン1は21×21モジュールの最小サイズで、数字が上がるごとに4モジュールずつ大きくなり、バージョン40では177×177モジュールに達する。同じ内容でも、収める文字数が多いほど、また誤り訂正の強さを上げるほど、必要なバージョン(=モジュール数)は大きくなる。QRWorksでは、どのバージョンを使うかを人間が指定していない。文字数から収まる最小のバージョンをライブラリが自動的に選ぶ仕組みになっており、これは次項で扱う。
データの持たせ方にも種類がある。数字だけの数字モード、英数字と一部の記号までの英数字モード、データ量の単位であるバイト(8ビット)を並べた、任意のバイト列を扱えるバイトモード、日本語の漢字モードである。同じ文字数でも、モードによって詰め込める情報量が変わる。QRWorksは日本語を含む任意の文章を扱うため、常にバイトモードを使い、文字列はUTF-8のバイト列に変換してから収めている。この選択は、次項のコードで qr.addData(data.text, 'Byte') という1行になって表れる。QRコードには文字コードを明示するECIという情報を付ける仕組みもあるが、QRWorksの実装ではこの情報を付加していない。他社製の読み取り機が別の文字コードで解釈すると文字化けする場合がある、という取扱説明書の注意は、この仕組みに由来する。
QRコードがもっとも工夫されているのは、誤り訂正の仕組みである。印刷したQRコードの一部が汚れたり、破れたり、光を反射して読み取れなかったりしても、内容を復元できる場合がある。これは、リード・ソロモン符号という誤り訂正符号の技術によって、本来のデータに加えて「検算用の点」を一緒に記録しているためである。検算用の点をどれだけ多く持たせるかによって、下表の4段階の水準がある。
| 水準 | 全コードワードに 対する復元率の目安 |
バージョン1での バイトモードの容量 | 向いている用途 |
|---|---|---|---|
| L | 約7% | 17バイト | 汚れにくい画面表示など |
| M (QRWorksが使う水準) | 約15% | 14バイト | 印刷物全般 |
| Q | 約25% | 11バイト | 屋外掲示など汚れやすい場所 |
| H | 約30% | 7バイト | 破損や汚損が多い環境、ロゴを重ねる場合 |
表の復元率は、8ビットをまとめたコードワードという単位の総数に対する目安である。画像の面積に対する保証ではなく、位置検出用の模様が破損した場合などにも読み取りに失敗する。

表の容量はバージョン1(もっとも小さいQRコード)の場合の目安であり、実際にはライブラリが文字数に応じてバージョンを引き上げるため、この容量に縛られるわけではない。ここで押さえておきたいのは、誤り訂正の水準を上げるほど、同じ内容を記録するのに必要なマス数が多くなるという関係である。同じ大きさに印刷すれば、マス数が多いほど1マスは細かくなる。壊れにくさと、コードの細かさ(読み取りやすさ)はトレードオフの関係にあり、QRWorksでは実装後に整理した仕様書QRWorks.mdに「誤り訂正はMとする」と記載し、一般的な印刷物で使われることの多い、両者の中間的な水準を固定で使っている。

誤り訂正の仕組みそのもの(リード・ソロモン符号の計算)は高度な数学であり、本講座では立ち入らない。押さえておきたいのは、QRコードが「1点でも欠けたら読めない画像」ではなく、「ある程度の欠損は検算で埋め合わせられるように、あらかじめ余分な点を含めて設計された図形」だという考え方である。この考え方は、次項で使うライブラリの中で、人間が意識することなく自動的に働いている。
表の容量はバージョン1(もっとも小さいQRコード)の場合の目安であり、実際にはライブラリが文字数に応じてバージョンを引き上げるため、この容量に縛られるわけではない。ここで押さえておきたいのは、誤り訂正の水準を上げるほど、同じ内容を記録するのに必要なマス数が多くなるという関係である。同じ大きさに印刷すれば、マス数が多いほど1マスは細かくなる。壊れにくさと、コードの細かさ(読み取りやすさ)はトレードオフの関係にあり、QRWorksでは実装後に整理した仕様書QRWorks.mdに「誤り訂正はMとする」と記載し、一般的な印刷物で使われることの多い、両者の中間的な水準を固定で使っている。
誤り訂正の仕組みそのもの(リード・ソロモン符号の計算)は高度な数学であり、本講座では立ち入らない。押さえておきたいのは、QRコードが「1点でも欠けたら読めない画像」ではなく、「ある程度の欠損は検算で埋め合わせられるように、あらかじめ余分な点を含めて設計された図形」だという考え方である。この考え方は、次項で使うライブラリの中で、人間が意識することなく自動的に働いている。
ライブラリでQRコードを生成する
前項の仕組みを1から実装するのは現実的ではない。QRWorksは、qrcode-generator(作者:Kazuhiko Arase氏、MIT License)という公開ライブラリを使い、文字列からQRコードの模様(どのマスを黒くするか)を作らせている。「3.3 PDFファイル結合ツール」で使ったpdf-libと同じく、専門的な処理を信頼できるライブラリに任せる考え方である。QRWorksでは、このライブラリと後述の読取用ライブラリを、開発時に取得してHTMLファイルの中にそのまま埋め込んでいる。実行時に外部から取得することはない。

QRコードの模様を作る部分は、次の3行である。

できあがった模様は、qr.getModuleCount() で1辺のマス数(例えば21)を取得し、qr.isDark(y, x) で「上からy番目、左からx番目のマスが黒か」を1マスずつ尋ねて調べる。QRWorksは、この2つの命令を使って真偽値(はい/いいえに相当する値)の二次元配列(行ごとに並べたマス目の表)(matrix)を組み立て、以降はライブラリを介さずこの配列だけを扱う。

配列をcanvasに描く処理は、「3.8 簡易画像編集ツール」で扱ったCanvas APIの応用である。

保存サイズは、数値入力欄と <input type=range>(スライダー)の2つで指定でき、どちらを操作しても refreshQR() が呼ばれて、もう一方の表示とQRコードの再描画が連動する。内容によっては128ピクセルより大きいサイズが必要であり、cellが1未満になる場合にはエラーを表示する。指定範囲を128から1999までの整数に制限しているのは、小さすぎると1マスが1ピクセル未満になって模様が潰れ、大きすぎると保存に時間がかかったり画像が不必要に重くなったりするためである。
QRコードの模様を作る部分は、次の3行である。
const qr = qrcode(0, 'M');1つ目の引数 0 は、バージョン(大きさ)の指定である。0は「文字数に収まる最小のバージョンを自動的に選ぶ」という意味であり、QRWorksはバージョンを固定していない。2つ目の 'M' が、前項で説明した誤り訂正水準である。addData() でバイトモードの文字列を渡し、make() を呼ぶと、模様の計算が完了する。
qr.addData(text, 'Byte');
qr.make();
できあがった模様は、qr.getModuleCount() で1辺のマス数(例えば21)を取得し、qr.isDark(y, x) で「上からy番目、左からx番目のマスが黒か」を1マスずつ尋ねて調べる。QRWorksは、この2つの命令を使って真偽値(はい/いいえに相当する値)の二次元配列(行ごとに並べたマス目の表)(matrix)を組み立て、以降はライブラリを介さずこの配列だけを扱う。
配列をcanvasに描く処理は、「3.8 簡易画像編集ツール」で扱ったCanvas APIの応用である。
const renderQR = (matrix, size, canvas) => {count + 8 は、マス数に加えて上下左右に4マスずつのクワイエットゾーンを確保するための計算である。cell は保存サイズをマス数で割った1マスあたりのピクセル数で、offset によって余白込みの模様を正方形の中央に配置している。あとは matrix を2重ループでたどり、黒いマスだけをfillRect()で塗る。トリミングや画像編集のような複雑な計算はなく、Canvas APIの基本操作の組み合わせで描けることが分かる。
const count = matrix.length;
const cell = Math.floor(size / (count + 8));
if (cell < 1) throw new Error(`この内容には最低${count + 8}pxが必要です。保存サイズを大きくしてください。`);
canvas.width = size; canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000';
const offset = Math.floor((size - cell * count) / 2);
matrix.forEach((row, y) => row.forEach((dark, x) => {
if (dark) ctx.fillRect(offset + x * cell, offset + y * cell, cell, cell);
}));
};
保存サイズは、数値入力欄と <input type=range>(スライダー)の2つで指定でき、どちらを操作しても refreshQR() が呼ばれて、もう一方の表示とQRコードの再描画が連動する。内容によっては128ピクセルより大きいサイズが必要であり、cellが1未満になる場合にはエラーを表示する。指定範囲を128から1999までの整数に制限しているのは、小さすぎると1マスが1ピクセル未満になって模様が潰れ、大きすぎると保存に時間がかかったり画像が不必要に重くなったりするためである。
QRコードを生成する画面。生成済みのQRコードは「表示・保存」から再表示できる
Web Workerで重い処理を切り離す
QRコードの生成や、後述する読取の解析は、文字数や画像の大きさによって計算量が変わる。極端に長い文字列や壊れた画像データを渡すと、処理に時間がかかったり、終わらなくなったりする可能性がある。ここで問題になるのが、JavaScriptがシングルスレッド(一度に1つの処理しか進めない仕組み)で動く、という性質である。重い計算をそのまま実行すると、その間は画面の描画もボタンの反応も止まってしまう。

さらに厄介なのは、「10秒経ったら諦める」という制限を、普通のsetTimeout()だけでは実現できない点である。setTimeoutは指定時間後に処理の追加を予約するだけであり、その時点で別の重い処理が動き続けていれば、予約した処理は順番待ちのまま実行されない。本当に処理を強制的に止めるには、その処理を実行している場所ごと外部から破棄する必要がある。これを可能にするのがWeb Workerである。Web Workerは、メインの画面とは別に用意された、独立した実行の場である。メイン側は worker.postMessage() で仕事を依頼し、worker.onmessage で結果を受け取る。そして、いつでも worker.terminate() で、Workerを問答無用で終了させられる。

QRWorksは、QRコードの生成と読取解析の両方をWorkerの中で行っている。Workerに読み込ませるプログラムは、次のように文字列として組み立てている。

呼び出し側は、Workerの生成から後始末までを1つの関数にまとめ、Promise(「3.6 重複データ検出・並べ替え」で解説)として扱えるようにしている。
さらに厄介なのは、「10秒経ったら諦める」という制限を、普通のsetTimeout()だけでは実現できない点である。setTimeoutは指定時間後に処理の追加を予約するだけであり、その時点で別の重い処理が動き続けていれば、予約した処理は順番待ちのまま実行されない。本当に処理を強制的に止めるには、その処理を実行している場所ごと外部から破棄する必要がある。これを可能にするのがWeb Workerである。Web Workerは、メインの画面とは別に用意された、独立した実行の場である。メイン側は worker.postMessage() で仕事を依頼し、worker.onmessage で結果を受け取る。そして、いつでも worker.terminate() で、Workerを問答無用で終了させられる。
QRWorksは、QRコードの生成と読取解析の両方をWorkerの中で行っている。Workerに読み込ませるプログラムは、次のように文字列として組み立てている。
const workerBody = `qrcode-generator と jsQR (読取用ライブラリ、後述)のソースコードを、実行したい処理(workerBody)とつなげて1本の文字列にし、「3.8 簡易画像編集ツール」の画像読み込みで使ったBlob URLと同じ手口で、new Worker(url) に渡している。ファイルを別に用意せず、1つのHTMLファイルで完結させるという仕様上の制約を満たすための工夫である。
qrcode.stringToBytes = (s) => Array.from(new TextEncoder().encode(s));
self.onmessage = ({ data }) => {
if (data.kind === 'encode') {
const qr = qrcode(0, 'M'); qr.addData(data.text, 'Byte'); qr.make();
const matrix = ・・・;
postMessage({ matrix });
} else {
const code = jsQR(new Uint8ClampedArray(data.pixels), data.width, data.height);
postMessage({ text: code ? code.data : null });
}
};`;
const workerSource = qrLibraryText + '\n' + jsQrLibraryText + '\n' + workerBody;
const url = URL.createObjectURL(new Blob([workerSource], { type: 'text/javascript' }));
const worker = new Worker(url);
呼び出し側は、Workerの生成から後始末までを1つの関数にまとめ、Promise(「3.6 重複データ検出・並べ替え」で解説)として扱えるようにしている。
const runWorker = (data) => new Promise((resolve, reject) => {setTimeout() で10秒後に worker.terminate() を予約しておき、先に結果が届けば clearTimeout() でその予約を取り消す。処理が長引けば、内容に関わらずWorkerごと強制終了させる。カメラの映像から取り出した画素データのように大きなデータを渡すときは、[postMessage(data, data.pixels]) のように2つ目の引数(転送リスト)へ含めている。これを指定すると、データを複製せず、Worker側へ所有権ごと受け渡す。画像1枚分の画素データは数百万個の数値になり得るため、複製のコストを避ける意味がある。
const worker = new Worker(url);
const timer = setTimeout(() => {
worker.terminate();
reject(new Error('QR処理が10秒を超えたため、安全のため終了しました。'));
}, 10000);
worker.onmessage = ({ data: result }) => {
clearTimeout(timer);
worker.terminate();
if (result.error) reject(new Error(result.error)); else resolve(result);
};
worker.postMessage(data, data.pixels ? [data.pixels] : []);
});
カメラ映像を取得する
QRコードの読取には、内蔵カメラの映像が要る。ブラウザでカメラを扱う標準の仕組みがgetUserMediaである。呼び出すと、ブラウザは利用者に「カメラの使用を許可しますか」という確認を出し、許可されて初めて映像が流れ始める。

getUserMediaの利用には、セキュアコンテキストという制約が関わる。ブラウザは、カメラやマイクのような機微な機能を、https://で始まる安全な接続か、localhostのような特別に信頼された環境でしか許可しない。QRWorksはHTTPサーバーを使わずローカルのHTMLファイルとして開く仕様であるため、この扱いはブラウザやOSの設定に依存する。取扱説明書に「ローカルファイルからのカメラ使用を許可する環境が必要」とあるのは、この制約への言及である。許可されない環境では、カメラの利用をあきらめて「画像から読取」機能(後述)に切り替える案内をしている。

カメラは、使い終えたら明示的に止める必要がある。止め忘れると、映像を使っていなくても端末のカメラ利用中を示すランプが点灯し続ける。
const stream = await navigator.mediaDevices.getUserMedia({audio: false は、音声は要らないという明示である。マイクを使わない機能でマイクの許可まで求めるのは、利用者の不安を招くだけであり、必要な権限だけを求める配慮である。facingMode はカメラの向きの指定で、environment(背面・標準カメラ)とuser(前面カメラ)をQRWorksでは選択式にしている。ideal という指定の仕方が要点で、「できればこちらを使ってほしい」という希望であり、端末にそのカメラがなければブラウザが別のカメラで代用する。exact にすると「そのカメラでなければ失敗させる」という厳格な指定になるが、機種によって前面・背面カメラの数や呼び名は異なるため、QRWorksは代用の効く ideal を選んでいる。
audio: false,
video: { facingMode: { ideal: $('facing').value }, width: { ideal: 1280 }, height: { ideal: 720 } },
});
$('video').srcObject = stream;
await $('video').play();
getUserMediaの利用には、セキュアコンテキストという制約が関わる。ブラウザは、カメラやマイクのような機微な機能を、https://で始まる安全な接続か、localhostのような特別に信頼された環境でしか許可しない。QRWorksはHTTPサーバーを使わずローカルのHTMLファイルとして開く仕様であるため、この扱いはブラウザやOSの設定に依存する。取扱説明書に「ローカルファイルからのカメラ使用を許可する環境が必要」とあるのは、この制約への言及である。許可されない環境では、カメラの利用をあきらめて「画像から読取」機能(後述)に切り替える案内をしている。
カメラは、使い終えたら明示的に止める必要がある。止め忘れると、映像を使っていなくても端末のカメラ利用中を示すランプが点灯し続ける。
const stopCamera = () => {getTracks() は、映像や音声などの通信路(トラック)の一覧を返す。それぞれに stop() を呼ぶと、カメラ自体の利用が終わる。QRWorksは、読取に成功したとき、「停止」を押したとき、タブを閉じたり離れたりしたとき(pagehide、visibilitychange)、そして許可待ちや解析が規定の秒数を超えたときのすべてでこの stopCamera() を呼び、カメラを不必要に動かし続けないようにしている。
if (state.stream) state.stream.getTracks().forEach((track) => track.stop());
state.stream = null;
$('video').srcObject = null;
};
window.addEventListener('pagehide', stopCamera);
document.addEventListener('visibilitychange', () => { if (document.hidden) stopCamera(); });
映像からQRコードを読み取る
カメラの映像は動画であり、jsQR(読取用ライブラリ、Apache-2.0ライセンス)のような解析ライブラリに渡すには、1枚の静止画(画素の並び)に変換する必要がある。QRWorksは、<video> 要素に流れている映像を、一定間隔でオフスクリーンの<canvas>へ描き写し、そこから画素データを取り出している。

jsQR() には inversionAttempts: 'attemptBoth' というオプションを渡している。通常のQRコード(白地に黒)だけでなく、明暗を反転させた画像(黒地に白)としても解析を試みる、という指定である。スマートフォンの画面をダークモードで表示したQRコードのように、白黒が反転した状態でも読み取れる可能性を広げている。

カメラが使えない環境や、画面に表示済みのQRコードを確認したいときのために、「画像から読取」という補助機能も用意している。ファイルとして選んだ画像を<canvas>に描き、同じ decodeCanvas() へ渡すだけであり、カメラ用の処理をほとんど書き直さずに済んでいる。1つの解析処理を、カメラ映像と静止画像の両方の入口から使い回す構成である。
const scan = async () => {drawImage(video, ...) は、「3.8 簡易画像編集ツール」で画像に対して使ったのと同じ命令だが、<video> 要素を渡すと、その瞬間に再生されているコマ(フレーム)が描かれる。getImageData() で画素を取り出し、前項のWorkerへ渡して jsQR() に解析させる。QRコードが見つからなければ 180ミリ秒 後に自分自身を再び呼ぶ、という繰り返しである。通常は画面の更新頻度に合わせて呼ばれるrequestAnimationFrameのような滑らかさは不要であり、間隔を空けることで端末の負荷を抑えている。
frame.width = video.videoWidth; frame.height = video.videoHeight;
frame.getContext('2d').drawImage(video, 0, 0, frame.width, frame.height);
const image = frame.getContext('2d').getImageData(0, 0, frame.width, frame.height);
const result = await runWorker({ kind: 'decode', width: frame.width, height: frame.height, pixels: image.data.buffer });
if (result.text !== null) { stopCamera(); showResult(result.text); return; }
setTimeout(scan, 180);
};
jsQR() には inversionAttempts: 'attemptBoth' というオプションを渡している。通常のQRコード(白地に黒)だけでなく、明暗を反転させた画像(黒地に白)としても解析を試みる、という指定である。スマートフォンの画面をダークモードで表示したQRコードのように、白黒が反転した状態でも読み取れる可能性を広げている。
カメラが使えない環境や、画面に表示済みのQRコードを確認したいときのために、「画像から読取」という補助機能も用意している。ファイルとして選んだ画像を<canvas>に描き、同じ decodeCanvas() へ渡すだけであり、カメラ用の処理をほとんど書き直さずに済んでいる。1つの解析処理を、カメラ映像と静止画像の両方の入口から使い回す構成である。
QRコードを読み取る画面。読取に成功すると内容が読取専用のテキストボックスに表示される
読み取った文字列からリンクを作る
読み取った文字列にURLが含まれていた場合、それをクリックできるリンクとして表示する。QRWorksは、これを1つの tokenize() 関数にまとめ、画面表示・Word・Excel・PDFへの保存のすべてで使い回している。

この関数が返す配列は、URLの部分に印を付けた文章の断片({ text, url } の並び)である。画面表示では、URLの部分だけ <a> 要素に変換してクリック可能にする。読み取った内容を自動でページ遷移させることはせず、押した場合に限って外部ブラウザでURLを開く、という取扱説明書の記述は、この慎重な設計に基づく。
const regex = /https?:\/\/[^\s<>"']+/giu;正規表現(「3.6 重複データ検出・並べ替え」で解説)で http:// または https:// から始まり、空白や引用符が現れるまでの文字列を候補として拾う。次に、文末の句読点や閉じ括弧など、URLの一部ではなさそうな記号を後ろから取り除く。最後に new URL(raw) を試し、例外が出れば(catch内のcontinueで、その候補を飛ばして次の候補へ進む)候補から外す。URL は、文字列が正しいURLの形をしているかを検証し、各部分(ホスト名など)に分解して扱えるようにする標準の道具である。「見た目はURLらしい文字列」を「本当にURLとして解釈できる文字列」まで絞り込む2段階の判定になっている。
for (const match of text.matchAll(regex)) {
let raw = match[0].replace(/[.,;!、。)」』】〉》]+$/u, '');
let url;
try { url = new URL(raw); } catch { continue; }
parts.push({ text: raw, url: url.href });
}
この関数が返す配列は、URLの部分に印を付けた文章の断片({ text, url } の並び)である。画面表示では、URLの部分だけ <a> 要素に変換してクリック可能にする。読み取った内容を自動でページ遷移させることはせず、押した場合に限って外部ブラウザでURLを開く、という取扱説明書の記述は、この慎重な設計に基づく。
手作りのZIPでWord・Excelファイルを作る
読み取った内容は、テキストのほかWordの.docx、Excelの.xlsxとしても保存できる。ここで意外な事実がある。WordやExcelの新しい形式(Office Open XML)の正体は、拡張子を.zipに変えて展開すると分かるとおり、複数のXMLファイルをひとまとめにしたZIPファイルである。QRWorksは、.docxや.xlsxを作るための専用ライブラリを使わず、ZIPの組み立てからXMLの記述まで、すべてJavaScriptで直接書いている。

ZIPファイルは、大まかに3つのブロックでできている。収めるファイルごとの本体(ローカルファイルヘッダとその中身)、ファイル一覧を記した索引(セントラルディレクトリ)、そして「索引はここにある」と最後に記す終端レコードである。QRWorksの zipBlob() 関数は、収めたいファイルを1つずつこの形式に変換しながら積み上げていく。

ファイルごとの記録には、CRC32という値を添える。これは、中身から一定の計算手順で導き出す検査用の数値で、「3.8 簡易画像編集ツール」で組み立てたBMPと同じく、ArrayBuffer・DataView・Uint8Arrayを使ってバイト単位で書き込む。

ZIPの中身であるXMLファイルの構成にも、決まりがある。どのファイルがどんな種類か一覧にする「[Content_Types].xml」、ファイル同士の関連を記す_rels/.rels、そして実際の文書やシートの中身(Wordならword/document.xml、Excelならxl/worksheets/sheet1.xml)である。QRWorksは、これらのXMLをテンプレート文字列として組み立てている。
ZIPファイルは、大まかに3つのブロックでできている。収めるファイルごとの本体(ローカルファイルヘッダとその中身)、ファイル一覧を記した索引(セントラルディレクトリ)、そして「索引はここにある」と最後に記す終端レコードである。QRWorksの zipBlob() 関数は、収めたいファイルを1つずつこの形式に変換しながら積み上げていく。
ファイルごとの記録には、CRC32という値を添える。これは、中身から一定の計算手順で導き出す検査用の数値で、「3.8 簡易画像編集ツール」で組み立てたBMPと同じく、ArrayBuffer・DataView・Uint8Arrayを使ってバイト単位で書き込む。
const header = new Uint8Array(30 + filename.length);QRWorksのZIPは、あえて圧縮を行わない無圧縮(ストア方式)で書き出している。「3.8 簡易画像編集ツール」で触れた可逆圧縮の技術(ZIPの標準的な圧縮方式)を自作するのは手間が大きく、文書保存は10,000文字までに制限しているため、圧縮しなくてもファイルの大きさに実用上の問題は出ない。コードを単純に保つための判断である。ここではヘッダに書く数値を暗記する必要はなく、保存形式の決まりに沿ってデータを組み立てる点を押さえればよい。
const h = new DataView(header.buffer);
h.setUint32(0, 0x04034b50, true); // ローカルファイルヘッダの目印
h.setUint16(6, 0x800, true); // ファイル名がUTF-8であることを示すフラグ
h.setUint32(14, crc, true); // CRC32
h.setUint32(18, bytes.length, true);
h.setUint32(22, bytes.length, true);
ZIPの中身であるXMLファイルの構成にも、決まりがある。どのファイルがどんな種類か一覧にする「[Content_Types].xml」、ファイル同士の関連を記す_rels/.rels、そして実際の文書やシートの中身(Wordならword/document.xml、Excelならxl/worksheets/sheet1.xml)である。QRWorksは、これらのXMLをテンプレート文字列として組み立てている。
| ファイル | 役割 |
|---|---|
| [Content_Types].xml | ZIP内の各ファイルの種類(XMLか、文書本体か)を宣言する |
| _rels/.rels | パッケージ全体から見て、どこに文書本体があるかを示す |
| word/document.xml xl/workbook.xml、xl/worksheets/sheet1.xml | Word本文、またはExcelのブックとシートの中身 |
| xl/_rels/workbook.xml.rels | Excelブックとワークシートを関連付ける |
| word/_rels/document.xml.rels xl/worksheets/_rels/sheet1.xml.rels | 本文中のハイパーリンクが指す実際のURLの一覧 |
Wordの本文では、URLを<w:hyperlink>という要素で囲み、リンク先の実際のURLは本文とは別の_relsファイル側に記す。本文の中には「rId1」のような番号(関連付けID)しか書かれておらず、その番号が指す先を _rels ファイルで解決する、という2段構えになっている。この番号の仕組みはURLをそのまま書くより複雑に見えるが、Office Open XML全体で共通のやり方であり、画像の埋め込みなど他の関連付けにも同じ仕組みが使われる。

Excelでは、A1セルに読み取った全文、A2に見出し、A3以降に見つかったURLを並べている。ここで注意しているのが、数式インジェクションと呼ばれる問題である。読み取った文字列がたまたま=で始まっていた場合、Excelがそれを数式と誤認して実行してしまう恐れがある。QRWorksは、セルの種類をt="inlineStr"(文字列として扱う)に固定することで、内容がどんな文字列であっても数式として実行されないようにしている。読み取った内容をそのまま信頼せず、保存先の形式に応じた安全策を講じる、という考え方は「3.5 ToDoリスト」で学んだHTMLエスケープと同じ姿勢である。

ここまで読んで、「ZIPやXMLをこれほど細かく手で書くのか」と驚いたかもしれない。実際には、この部分もCodexが実装したものであり、人間が1行ずつ書いたわけではない。開発開始時のプロンプトではWordやExcelを保存形式に指定しただけで、その中身をどう作るかはCodexの判断に委ねている。人間の役割は、できあがった.docxや.xlsxファイルが実際にWordやExcelで開けるか、URLがリンクとして機能するかを確かめ、不具合があれば「Excelでリンクが開けない」のように具体的に伝えることである。
Excelでは、A1セルに読み取った全文、A2に見出し、A3以降に見つかったURLを並べている。ここで注意しているのが、数式インジェクションと呼ばれる問題である。読み取った文字列がたまたま=で始まっていた場合、Excelがそれを数式と誤認して実行してしまう恐れがある。QRWorksは、セルの種類をt="inlineStr"(文字列として扱う)に固定することで、内容がどんな文字列であっても数式として実行されないようにしている。読み取った内容をそのまま信頼せず、保存先の形式に応じた安全策を講じる、という考え方は「3.5 ToDoリスト」で学んだHTMLエスケープと同じ姿勢である。
ここまで読んで、「ZIPやXMLをこれほど細かく手で書くのか」と驚いたかもしれない。実際には、この部分もCodexが実装したものであり、人間が1行ずつ書いたわけではない。開発開始時のプロンプトではWordやExcelを保存形式に指定しただけで、その中身をどう作るかはCodexの判断に委ねている。人間の役割は、できあがった.docxや.xlsxファイルが実際にWordやExcelで開けるか、URLがリンクとして機能するかを確かめ、不具合があれば「Excelでリンクが開けない」のように具体的に伝えることである。
WordファイルもExcelファイルも、中身はXMLをまとめたZIPファイルである
jsPDFで新しいPDFを組み立てる
QRWorksは、PDFの扱いにjsPDFというライブラリを使う。「3.3 PDFファイル結合ツール」で使ったpdf-libと混同しやすいが、役割が異なる。本講座ではpdf-libを既存PDFの結合・編集に、jsPDFを新しいPDFの作成に使っている。pdf-libでも新しいPDFを作成できるため、作成と編集が両者で完全に分かれているわけではない。QRWorksでは新規にPDFを作るため、目的に合わせてjsPDFを選んでいる。どちらも優れたライブラリだが万能ではなく、やりたいことに応じて使い分ける必要がある。

QRコードをPDFで保存する処理は単純である。

読み取った文字列をPDFで保存する処理は、もう一段複雑である。日本語を文字としてPDFに保存するには、通常は日本語フォントのデータをPDFに組み込む。フォントをあらかじめHTMLに同梱すれば外部通信なしでも実現できるが、QRWorksではその方法を採らず、端末のフォントで文字を<canvas>に描いた絵として扱い、その画像をjsPDFのページに貼り付けるという方法を採っている。

文字を1文字ずつfillText()で描きながら、URLに該当する部分の位置と幅を記録しておく。ページ全体を1枚の画像としてPDFに貼り付けたあと、記録しておいた位置にpdf.link()でリンク領域だけを重ねて設定する。見た目は画像だが、URLの部分だけクリックできるリンクとして機能する、という組み合わせである。ただし本文自体は画像であるため、PDFの中の文字を選択したりコピーしたりはできない。文字として再利用したい場合はテキストやWordの保存を選ぶよう、取扱説明書に明記している。
QRコードをPDFで保存する処理は単純である。
const pdf = new window.jspdf.jsPDF({ unit: 'mm', format: 'a4' });canvas.width * 25.4 / 96 は、ピクセル数を96dpi(1インチ=96ピクセルという画面の標準的な解像度)換算でミリメートルに直す計算である。A4は210×297ミリなので、その中央にQRコードの画像を配置している。表示幅の上限を170ミリに抑えているのは、A4の余白を確保するためである。
const mm = Math.min(170, canvas.width * 25.4 / 96);
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', (210 - mm) / 2, (297 - mm) / 2, mm, mm);
読み取った文字列をPDFで保存する処理は、もう一段複雑である。日本語を文字としてPDFに保存するには、通常は日本語フォントのデータをPDFに組み込む。フォントをあらかじめHTMLに同梱すれば外部通信なしでも実現できるが、QRWorksではその方法を採らず、端末のフォントで文字を<canvas>に描いた絵として扱い、その画像をjsPDFのページに貼り付けるという方法を採っている。
const pdf = new window.jspdf.jsPDF({ unit: 'pt', format: 'a4' });この例のPDFの単位は、先ほどのミリメートルではなくpt(ポイント)である。1ptは1/72インチであり、A4を約595×842ptとして扱う。canvasはその2倍の1190×1684ピクセルで描くため、リンクの座標と幅を2で割ってPDF上の位置へ換算している。
const canvas = document.createElement('canvas');
canvas.width = 1190; canvas.height = 1684;
const ctx = canvas.getContext('2d');
・・・
ctx.fillText(draw, x, y);
if (part.url) annotations.push({ x, y, width, url: part.url });
・・・
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, 595, 842);
annotations.forEach((a) => pdf.link(a.x / 2, a.y / 2, a.width / 2, 17, { url: a.url }));
文字を1文字ずつfillText()で描きながら、URLに該当する部分の位置と幅を記録しておく。ページ全体を1枚の画像としてPDFに貼り付けたあと、記録しておいた位置にpdf.link()でリンク領域だけを重ねて設定する。見た目は画像だが、URLの部分だけクリックできるリンクとして機能する、という組み合わせである。ただし本文自体は画像であるため、PDFの中の文字を選択したりコピーしたりはできない。文字として再利用したい場合はテキストやWordの保存を選ぶよう、取扱説明書に明記している。
ページを外部と通信させない仕組み
これまでの項目でも「外部と通信しない」という仕様を繰り返し取り上げてきたが、それはプログラムの書き方が正しいことを前提にした約束にすぎない。書き間違いや見落としで、うっかり外部へ通信するコードが紛れ込む可能性は常にある。QRWorksは、リソースの読み込みや通信をブラウザの機能で制限するContent-Security-Policy(CSP、コンテンツセキュリティポリシー)を使っている。
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; media-src blob:; worker-src blob:; connect-src 'none'; font-src 'none'; object-src 'none';">CSPは、ページが「何を、どこから読み込んだり通信したりしてよいか」を、ブラウザに直接申告する仕組みである。<meta> 要素の1行で宣言し、違反する動作が起きた場合はブラウザ自身がその動作を止める。プログラムの正しさに頼るのではなく、ブラウザという第三者に見張らせる点が特徴である。
| 項目 | 指定 | 意味 |
|---|---|---|
| default-src | 'none' | 個別指定のない取得系の項目について、リソースの読み込みを既定で禁止する |
| connect-src | 'none' | fetchやXMLHttpRequestなど、対象となるAPIによる通信を禁止する |
| script-src / style-src | 'unsafe-inline' | 1つのHTMLに埋め込んだscript・styleの実行のみ許可する(外部ファイルは読み込めない) |
| img-src | data: blob: | canvasの出力やBlob URLによる画像表示のみ許可する |
| media-src | blob: | blob: URLの音声・動画リソースの読み込みを許可する |
| worker-src | blob: | Blob URLから作るWeb Workerの起動のみ許可する |
中でも要となるのがconnect-src 'none'である。この指定があると、たとえプログラムのどこかに通信を試みるコードが紛れ込んでいたとしても、fetchやXMLHttpRequestなど、この指定の対象となる通信をブラウザが拒否する。「3.7 ハーネスエンジニアリング」で述べた、結果を確かめるセンサーの考え方を、実行時の防御に置き換えたものと見ることができる。worker-src の blob: は、Web Worker用のBlob URLを許可する指定である。一方、カメラの使用許可はgetUserMedia()で別途扱い、取得した映像(MediaStream)はvideo要素のsrcObjectに直接設定している。media-srcのblob:をカメラの使用許可と混同してはならない。CSPは意図しない外部アクセスを防ぐ補助策であり、通常のリンク先への移動まで全面的に禁止するものではない。利用者がリンクをクリックして開けば、リンク先への通信が発生する。CSPは機能を1つ追加するたびに、その機能が必要とする最小限の許可も一緒に見直す必要がある仕組みである。
QRコード生成・読取ツールのプログラム仕様書
以上を踏まえて、QRコード生成・読取ツール「QRWorks.html」の開発開始時にCodexへ渡したプロンプトを示す。掲載にあたって誤字と他のツール用の文言を整理している。実装後の判断を追記したQRWorks.mdとは区別して読んでほしい。生成・読取・保存といった実現したい動作や制約は書いてあるが、リード・ソロモン符号の計算、Web Workerによる処理の分離、ZIPの内部構造など、処理の具体的な実装方法までは指定していない。

今回の開発では、仕様書を渡した後にもいくつかの判断が生まれている。たとえば、生成画面と読取画面を並べて同時表示する案から、タブで切り替える方式へ変更したのはユーザーからの指示によるものである。一方、Word・ExcelをOffice Open XMLとして一から作る方式、日本語PDFを画像として描く方式、URLの判定をhttp://・https://で始まるものに絞る方式は、いずれもCodex側の判断であり、その根拠はQRWorks.mdの「明記した実装判断と既知の制限」という節に書き添えられている。仕様書に書かなかった細部の判断を、実装した側が言葉で残しておく、という進め方は「3.8 簡易画像編集ツール」でも触れたとおりである。
今回の開発では、仕様書を渡した後にもいくつかの判断が生まれている。たとえば、生成画面と読取画面を並べて同時表示する案から、タブで切り替える方式へ変更したのはユーザーからの指示によるものである。一方、Word・ExcelをOffice Open XMLとして一から作る方式、日本語PDFを画像として描く方式、URLの判定をhttp://・https://で始まるものに絞る方式は、いずれもCodex側の判断であり、その根拠はQRWorks.mdの「明記した実装判断と既知の制限」という節に書き添えられている。仕様書に書かなかった細部の判断を、実装した側が言葉で残しておく、という進め方は「3.8 簡易画像編集ツール」でも触れたとおりである。
開発開始時のプロンプト(誤字・文言整理済み)
# QRコード生成・読取ツール
# 目標 - 入力したコードをQRコードに変換し、子画面に表示するとともに、画像やPDFファイルとして保存する。 - 内蔵カメラを使って読み込んだQRコードの内容を画面に表示するとともに、テキストやPDFファイルとして保存する。
# ツール名称 QRコード生成・読取ツール
# プログラム・ファイル名 QRWorks.html
# プロジェクト・フォルダ作成 - プログラム・ファイル名の拡張子を除いた主ファイル名と同じ名前のサブフォルダを作成し、以降の作業はサブフォルダで行う。 - すでにサブフォルダがあれば、そのサブフォルダに移動して以降の作業を進める。
## 画面構成と処理 - プログラム上部にツール名称、バージョン番号、著作権者を記載する。 - 「生成」ボタンをクリックすると、次の処理を行う。 -- テキストボックスに記入されている文字列をQRコードに変換し、QRコード表示エリアに表示する。 -- 「保存」ボタンをクリックすると、指定した形式でQRコードを保存する。 -- 保存形式は、bmp, jpg, png, webp, pdfから選べる。 -- 保存するサイズ(ピクセル数)を入力するテキストボックスがある。初期値は400。最小値は128、最大値は1999。スライダーを使って数値を変更できる。 - 「読取」ボタンをクリックすると、次の処理を行う。 -- 内蔵カメラでQRコードを読み取り、テキストボックスに表示する。URLが含まれている場合は、ブラウザで表示するためのハイパーリンク表示にする。 -- 「保存」ボタンをクリックすると、指定した形式で読み取った内容を保存する。 -- 保存形式は、テキスト, word, excel, pdfから選べる。テキスト以外の場合でURLが含まれている場合はハイパーリンクにする。
## 記録 - なし
## 通信 - 外部と通信しない。
## 例外・エラー処理 - 無限ループに陥ったり、システム・エラーが出たときは、画面にエラー情報を表示して終了すること。
# テスト観点・合格条件 - Codexは、ダミーのQR画像データ(大きさやフォーマットが異なるもの)を5本作成し、上記仕様の通りに動作するか検証する。 - 前提条件、制約条件が守られていること。
# 前提条件 - 仕様変更があった場合は、本書を更新する。 - バージョン番号、著作権者、MIT Licenseであることを画面に表示する。 - 仕様で分からないことがあり、その違いが実装結果へ大きく影響する場合はユーザーに質問する。 - 安全かつ合理的に判断できる軽微な事項は、判断内容を明記して作業を進める。 - HTML、CSS、JavaScriptを1つのHTMLファイルに記述する。 - クライアントPCのブラウザで動作し、スマートフォンでも利用できること。 - HTTPサーバーやNode.jsなどのサーバー技術は使用せず、ブラウザの機能だけで完結すること。 - コーディングは「Airbnb JavaScript Style Guide」に可能な限り準拠すること。 - プログラムファイルのコメントに、名称、バージョン、目的、動作環境、著作権表示および使用条件、インストール方法、お問い合わせを記載すること。
# 制約条件 - 原則として、インターネットとのデータ送受信は行わないこと。 - 外部ライブラリを使用する場合は、下記のサイトに限定すること https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://ajax.googleapis.com/ https://code.jquery.com/ https://ajax.aspnetcdn.com/ - プログラムがMIT Licenseおよび第三者の権利に違反していないこと。 - ユーザーの許可なくプロジェクトフォルダ外のファイルを変更しないこと。 - ユーザーの許可なく既存ファイルを削除しないこと。 - Gitの履歴を破壊する操作を行わないこと。
# 合格判定 - すべての実装とテストが完了したら、テスト結果を一覧で表示する。 - 合格項目、不合格項目、未確認項目を明確に区別する。 - 不合格項目がある場合は原因と修正方針を表示する。 - 未確認項目がある場合は未確認の理由とユーザーによる確認方法を表示する。 - テスト結果を表示した後、合格としてよいかユーザーに質問する。 - ユーザーが合格と回答するまでは、Gitへの最終コミットと配布用ZIPファイルの作成を行わない。 - ユーザーが不合格と回答した場合は、指摘内容を修正して再テストする。
# 簡易取扱説明書の作成 - HTMLファイルと同じ場所に、簡易取扱説明書「README.txt」を作成する。 - 説明書には「プログラムの名称」「バージョン」「目的」「動作環境」「著作権表示および使用条件」「インストール方法」「使い方」「操作方法」「結果の保存」「既知の制限」「変更履歴」「お問い合わせ」を含める。 - インストール方法には、ZIPファイルを展開し、本ツールをブラウザで直接開く手順を記載する。 - マウス、キーボード、画面タッチの操作方法を記載する。
# 著作権表示および使用条件 このプログラムは OpenAI社の Codex によって作成し、作者が動作を確認しました。 このプログラムは外部とのデータ通信を行いません。
本アプリケーションはMIT Licenseです。 商用を含む無償利用が可能です。自由に改造できます。 再配布の際は、下記の著作権表記、URLおよび本使用条件を必ず明記してください。
Copyright by (c)studio pahoo https://www.pahoo.org/
MITライセンスについては、下記のリンク先を参考にしてください。 http://ja.wikipedia.org/wiki/MIT_License http://www.opensource.org/licenses/mit-license.php
なお、本アプリケーションの利用または改造によって生じた得失については一切関知いたしません。また、二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません。
# お問い合わせ ぱふぅ家のホームページ https://www.pahoo.org/
- サイト案内 - お問い合わせ
# リソース管理 - 作業開始前に、今回の作業が「新規作成(評価用)」「新規作成(配布用)」「メジャーバージョンアップ」「マイナーバージョンアップ」「不具合修正」のどれに該当するかユーザーに質問する。 - 新規作成(評価用)は0.1.0、新規作成(配布用)は1.0.0とする。メジャー、マイナー、不具合修正は既存バージョンを確認して適切に更新する。 - 既存バージョンを確認できない場合は推測せずユーザーに質問する。 - 決定したバージョン番号をツール、README.txt、本書、配布用ZIPファイル名へ反映する。 - ユーザーがテスト結果を合格と判定した後、プログラム、README.txt、本書、および必要なファイルをGitへコミットする。 - Gitリポジトリが存在しない場合やコミットできない場合は、勝手に新規リポジトリを作成せず、理由を報告して指示を求める。 - コミットメッセージには、プログラム名、バージョン番号、変更区分が分かる内容を使用する。
# 配布ファイルの作成 - ユーザーがテスト結果を合格と判定した後、ツール、README.txt、本書を1つのZIPファイルに圧縮する。 - ZIPファイル名は「プログラム・ファイル名(拡張子を除く)_バージョン番号.zip」とする。 - ZIPファイル内に一時ファイル、テスト用ファイル、Git管理ファイル、デバッグ専用ファイルを含めない。 - 完了後、作成したZIPファイルの絶対パスをユーザーに伝える。
スマートフォン相当の375ピクセル幅で表示した生成画面(実機での動作は未確認)
できあがったツールの主要なファイルは、program/QRWorks/ の QRWorks.html、README.txt、QRWorks.md、TEST_RESULTS.md である。バージョンは1.0.0である。スマートフォンでもタブ切り替えの画面構成がそのまま使えるように作られている。検証はスマートフォン相当の375ピクセル幅のブラウザ画面でも行っているが、スマートフォン実機、実機カメラ、Word・Excel本体での操作は未確認である。実機動作はOS・ブラウザに依存するため、利用環境で確認する必要がある。取扱説明書にある制限を、下表に抜粋する。
| 制限 | 理由 |
|---|---|
| 生成できる文字列はUTF-8で最大2,000バイト | QRコードのバージョン上限と処理時間を考慮した制限 |
| 読取結果の文書保存は10,000文字まで | 処理量を制限するため。JavaScriptの文字列lengthで数え、一部の絵文字などは2単位以上になる |
| 誤り訂正は水準M固定 | 復元力と読み取りやすさのバランスを優先 |
| PDFの本文は文字として選択・検索できない | 日本語フォントのデータを同梱せず、端末のフォントで画像化している |
| 画像読取の対象はPNG・JPG・BMP・WebPで、PDF・SVG・HEICは対象外 | 本ツールが受け入れる画像形式を4種類に限定しているため |
| カメラの許可待ちは20秒、解析は60秒で打ち切る | 利用者を待たせすぎないための時間制限 |
| URLはhttp://・https://で始まるものに限る | それ以外の形式(メールアドレスなど)は誤検出を避けるため対象外とした |
参考サイト
- QRコード.com:株式会社デンソーウェーブ
- kazuhikoarase/qrcode-generator:GitHub
- cozmo/jsQR:GitHub
- parallax/jsPDF:GitHub
- Web Workers API:MDN Web Docs
- Worker:MDN Web Docs
- MediaDevices: getUserMedia() メソッド:MDN Web Docs
- MediaTrackConstraints: facingMode プロパティ:MDN Web Docs
- セキュアコンテキスト:MDN Web Docs
- URL:MDN Web Docs
- HTMLCanvasElement: getContext() メソッド:MDN Web Docs
- Content Security Policy (CSP):MDN Web Docs
- [https://ja.wikipedia.org/wiki/リード・ソロモン符号:title=リード・ソロモン符号]:ウィキペディア
- Airbnb JavaScript Style Guide:Airbnb
(この項おわり)
