Vue は PDF.js を統合して PDF プレビューを実装し、透かしを追加する手順を実行します。

Vue は PDF.js を統合して PDF プレビューを実装し、透かしを追加する手順を実行します。

成果を達成する

利用可能なプラグインの紹介

Mozilla は PDF.js と pdfjs-dist を提供しています。両者の違いは次のとおりです。

  • 完全な PDF ビューアである PDF.js は、提供される viewer.html を直接使用して、完全なスタイルと関連機能を含む PDF コンテンツを表示できます。利点は、迅速な統合が可能で、ビューアの機能やスタイルを自分で実装する必要がないことです。デメリットとしては、スタイルや機能をカスタマイズしたい場合に面倒になってしまうことです。
  • PDF.js のビルド済みバージョンである pdfjs-dist には、PDF コンテンツのレンダリング機能のみが含まれています。ビューアのスタイルや関連機能は自分で実装する必要があります。

公式 Vue プラグイン ライブラリである Awesome Vue.js が推奨する vue-pdf は、pdfjs-dist をカプセル化したものです。一般的に、vue-pdf を使用すると、PDF プレビュー効果をすばやく実現できます。

ニーズに応じてプラグインを選択する

私たちの要件は、既存のページに PDF プレビューを実装しながら、PDF コンテンツに透かしを追加することです。

PDF.js のフルバージョンは肥大化しすぎています。vue-pdf はプレビュー効果を素早く実現できますが、透かしを追加するときに PDF を表示するキャンバスを 2 回レンダリングする必要があります。試してみたところ、Failed to execute 'drawImage' on 'CanvasRenderingContext2D': Overload resolution failed. というエラーがスローされることがわかりました。

結局、pdfjs-distを直接統合してすべての機能を完成させることにしました。

プラグインのインストールとインポート

インストール

yarn pdfjs-dist を追加する

導入

workerSrc は手動で指定する必要があります。そうしないと、偽のワーカーの設定に失敗しましたというエラーがスローされます。

ファイルはローカルディレクトリ node_modules/pdfjs-dist/build/pdf.worker.js に存在しますが、実際にインポートするとエラーが報告されるため、CDN アドレスの下にある pdf.worker.js のみを使用できます。 PDFJS.version を渡すことで、インポートの柔軟性を高めることができます。

'pdfjs-dist' から * を PDFJS としてインポートします。

PDFJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js`

プラグインを初期化する

コンテンツをレンダリングするためのキャンバスノード

<canvas id="pdfCanvas"></canvas>

PDFJSインスタンスを受信するために使用されるオブジェクト

小道具: {
 // PDF ファイルの実際のリンク URL: {
 タイプ: 文字列
 }
},
データ () {
 戻る {
 合計ページ: 1,
 // PDFJS インスタンス pdfDoc: null
 }
},
メソッド: {
 _initPdf() {
 PDFJS.getDocument(this.url).promise.then(pdf => {
 // ドキュメントオブジェクト this.pdfDoc = pdf
 //ページ総数 this.totalPage = pdf.numPages
 // ページをレンダリングします this.$nextTick(() => {
 this._renderPage()
 })
 })
 }
}

リンクの変更を監視し、インスタンスを初期化する

外部からのURLが有効な場合、PDFビューアの初期化機能が起動されます。

時計:
 'url' (値) {
 もし(!val) {
 戻る
 }

 this._initPdf()
 }
},

PDFコンテンツのレンダリング

現在のページ比率を取得して、コンテンツの実際の幅と高さを計算します

メソッド: {
 _getRatio(ctx) {
 dpr = window.devicePixelRatio || 1 とします
 bsr = とします
 ctx.webkitBackingStoreピクセル比 ||
 ctx.mozBackingStoreピクセル比 ||
 ctx.msBackingStoreピクセル比 ||
 ctx.oBackingStoreピクセル比 ||
 ctx.backingStoreピクセル比 ||
 1

 dpr / bsrを返す
 }
}

現在のページをレンダリングする

page.getViewport({ scale }) のスケールは非常に重要で、レンダリングされたコンテンツが親コンテナ全体を埋めることができるかどうかに直接関係します。したがって、ここでは親コンテナの幅とページ自体の幅がそれぞれ取得されます。親コンテナの幅/ページ幅の比率は、実際のページをどれだけ拡大する必要があるかの比率です。

page.view は、x 軸オフセット、y 軸オフセット、幅、高さの 4 つの値を持つ配列です。 実際の幅を取得するには、現在のページ比率も考慮する必要があるため、page.view[2] * ratio を使用して実際の幅を計算します。

データ () {
 戻る {
 現在のページ: 1,
 合計ページ: 1,
 幅: 0,
 高さ: 0,
 pdfDoc: ヌル
 }
},
メソッド: {
 _renderPage() {
 this.pdfDoc.getPage(this.currentPage).then(ページ => {
 キャンバスを document.querySelector('#pdfCanvas') にします。
 ctx = canvas.getContext('2d') とします。
 // ページ比率を取得する let ratio = this._getRatio(ctx)

 // ページ幅とビューポート幅の比率がコンテンツ領域の拡大率です。let dialogWidth = this.$refs['pdfDialog'].$el.querySelector('.el-dialog').clientWidth - 40
 pageWidth = page.view[2] * 比率とする
 scale = dialogWidth / pageWidth とします。

 viewport = page.getViewport({ scale }) とします。

 // コンテンツ領域の幅と高さを記録します。これは後で透かしを追加するときに必要になります。this.width = viewport.width * ratio
 this.height = viewport.height * 比率

 キャンバスの幅 = this.width
 キャンバスの高さ = this.height

 // スケーリング比率 ctx.setTransform(ratio, 0, 0, ratio, 0, 0)

 ページ.レンダリング({
 キャンバスコンテキスト: ctx,
 ビューポート
 }).promise.then(() => {})
 })
 }
}

ページジャンプの実装

レンダリング順序の混乱を防ぐためにレンダリングキューを準備する

ページジャンプがトリガーされると、レンダリングを開始するかどうかは現在レンダリング中のページがないかどうかによって決まるため、_renderPage() 関数を直接呼び出すのではなく、_renderQueue() 関数が呼び出されます。

データ () {
 戻る {
 // キューレンダリング中かどうか: false
 }
},
メソッド: {
 _renderQueue() {
 if (this.rendering) {
 戻る
 }

 this._renderPage()
 }
}

ページをレンダリングするときにキューの状態を変更する

メソッド: {
 _renderPage() {
 // キューが開始されます this.rendering = true

 this.pdfDoc.getPage(this.currentPage).then(ページ => {
 // ... 実装コードを省略 page.render({
 キャンバスコンテキスト: ctx,
 ビューポート
 }).promise.then(() => {
 // キューが終了します this.rendering = false
 })
 })
 }
}

ページめくり機能の実装

データ () {
 戻る {
 現在のページ: 1,
 合計ページ: 1
 }
},
計算: {
 // ホームページですか firstPage() {
 this.currentPage <= 1 を返す
 },
 // 最後のページかどうか lastPage() {
 this.currentPage >= this.totalPage を返します。
 },
},
メソッド: {
 // ホームページにジャンプ firstPageHandler () {
 if (this.firstPage) {
 戻る
 }

 this.currentPage = 1
 this._renderQueue()
 },
 // 最後のページにジャンプ lastPageHandler () {
 最後のページの場合
 戻る
 }

 this.currentPage = this.totalPage
 this._renderQueue()
 },
 // 前のページ previousPage() {
 if (this.firstPage) {
 戻る
 }

 this.currentPage--
 this._renderQueue()
 },
 // 次のページ nextPage() {
 最後のページの場合
 戻る
 }

 this.currentPage++
 this._renderQueue()
 }
}

ページコンテンツにタイルテキストの透かしを追加する

フロントエンドに透かしを追加する方法は、描画にキャンバスを使用することであることに間違いはありません。

最初の解決策は、コンテンツ領域の上層をブロックする透明なマスク レイヤーとして div を用意し、canvas.toDataURL('image/png') を使用して、canvas で描画された透かしを Base64 形式でエクスポートし、マスク レイヤーの背景画像としてタイル化することでした。 効果は得られますが、この方法ではブラウザコンソールを開いてマスクレイヤーを削除するだけで透かしを削除できます。

その後、Canvas が別の Canvas を描画する際に、Canvas が実際に Canvas をそれ自体の上に画像として描画できることがわかったので、次の解決策を思いつきました。

キャンバスに透かしを描く

コンポーネントなので、透かしテキストの透かしは外部から渡されます。

ウォーターマークを描画するためのキャンバスをページに追加する必要はありません。描画が完了したら、DOM 要素を直接返すことができます。返されるのは getContext(2d) を使用して取得したキャンバス インスタンスではなく、DOM 要素であることに注意してください。

ctx.fillStyle はテキストの透明度を示します。 ctx.fillText(this.watermark, 50, 50) は、キャンバス内のテキストの位置を示します。最初の値はテキスト コンテンツ、2 番目の値は x 軸のオフセット、3 番目の値は y 軸のオフセットです。

小道具: {
 透かし:
 タイプ: 文字列、
 デフォルト: 'asing1elife'
 }
},
メソッド: {
 _initWatermark() {
 キャンバスを document.createElement('キャンバス') にします。
 キャンバス幅 = 200
 キャンバスの高さ = 200

 ctx = canvas.getContext('2d') とします。
 ctx.rotate(-18 * Math.PI / 180)
 ctx.font = '14px ヴェダナ'
 ctx.fillStyle = 'rgba(200, 200, 200, .3)'
 ctx.textAlign = '左'
 ctx.textBaseline = '中央'
 ctx.fillText(this.watermark, 50, 50)

 キャンバスを返す
 }
}

コンテンツをレンダリングするキャンバスに透かしを並べて表示します

この方法は、HTML5 キャンバス タイリングのいくつかの方法に基づいています。ctx.rect(0, 0, this.width, this.height) の幅と高さは、_renderPage() 関数で記録されたページ コンテンツ領域の実際の幅と高さです。実際の幅と高さが渡されていれば、キャンバスは透かし画像のサイズとコンテンツ領域のサイズに応じて、x 軸と y 軸の繰り返し回数を自動的に実現します。

メソッド: {
 _renderWatermark() {
 キャンバスを document.querySelector('#pdfCanvas') にします。
 ctx = canvas.getContext('2d') とします。

 // タイル状の透かし let pattern = ctx.createPattern(this._initWatermark(), 'repeat')
 ctx.rect(0, 0, this.width, this.height)
 ctx.fillStyle = パターン
 ctx.fill()
 }
}

ページコンテンツがレンダリングされた後、透かしのレンダリングが再度トリガーされます。

メソッド: {
 // レンダリング page_renderPage () {
 this.pdfDoc.getPage(this.currentPage).then(ページ => {
 // ... 実装コードを省略 page.render({
 キャンバスコンテキスト: ctx,
 ビューポート
 }).promise.then(() => {
 // 透かしをレンダリングする this._renderWatermark()
 })
 })
 }
}

上記は、Vue と PDF.js を統合して PDF プレビューと透かしの追加を実現する詳細です。Vue で PDF プレビューと透かしの追加を実現する詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue はアップロードされた画像に透かしを追加する機能を実装します
  • Vue で画像と画像の透かしを隠しテキスト情報とともに使用する方法
  • Vueはページに透かし効果を追加する機能を実装します
  • Vueはページ透かし機能を実装
  • Vueを使用して写真をアップロードする3つの方法
  • モバイル端末上のVue+Vantのアップローダーは、画像のアップロード、圧縮、回転の機能を実現します。
  • Vue+elementUI はフォームと画像のアップロードおよび検証機能の例を実装します
  • vue+elementUIは画像アップロード機能を実現します
  • VUEをベースに画像を選択してアップロードし、ページに表示します(画像は削除可能です)
  • Vue はアップロードした画像に透かしを追加できるようになりました (アップグレード版)

<<:  Linux で open-vswitch をインストールおよびアンインストールする方法

>>:  MySQL無料インストールバージョンの設定チュートリアル

推薦する

vue $setは配列コレクションオブジェクトへの値の割り当てを実装します

Vue $set 配列コレクションオブジェクトの割り当てVue カスタム配列オブジェクト コレクショ...

Linux で 1 回限りのスケジュールされたタスクを実行するための at コマンドの使用に関する詳細な説明

目次序文1. 一度限りの計画タスクの紹介2. コマンド3. 1回限りのスケジュールタスクを作成する4...

MySQL GROUP_CONCAT 制限の解決

効果: GROUP_CONCAT関数は、フィールド値を文字列に連結することができます。デフォルトの区...

分散監視システムにおけるZabbixのアクティブ、パッシブ、Web監視のプロセスの詳細な説明

前回の記事では、Zabbix のネットワーク検出機能について学習し、アクションと組み合わせてホストの...

HTML のインラインブロックの空白を素早く削除する 5 つの方法

inline-block プロパティ値は、「インライン」要素のマージンとパディングを制御する必要があ...

MySQL サービス 1067 エラーの解決策: mysql 実行可能ファイルのパスを変更する

今日、MySQLサービス1067エラー問題に遭遇しました。システムアカウントを使用するように設定して...

動的および静的分離を実装するための Nginx サンプル コード

この記事のシナリオと組み合わせて、Nginx と Java 環境 (SpringBoot プロジェク...

LinuxカーネルとSVNバージョン間の競合の解決策

現象システムは Linux システムカーネルを正常にコンパイルできましたが、SVN をインストールし...

ショッピングカートのスライド削除効果を実装するReactネイティブサンプルコード

基本的にすべてのeコマースプロジェクトにはショッピングカートの機能があります。これはreact-na...

Mysql で自動増分主キー ID を更新するときに問題が発生しました

目次自動インクリメント ID を更新する理由は何ですか?質問解決方法これは私が知っている問題ですが、...

HTML の水平および垂直中央揃えの問題の概要

最近、センタリングの問題に数多く遭遇したので、後で簡単に見つけられるように、時間をかけてそれらを要約...

TypeScript 2.0 マーク付き共用体型の詳細な説明

目次タグ付きユニオン型を使用した支払い方法の構築タグ付きユニオン型を使用した Redux アクション...

HTML の iframe と frame の違いを例を使って説明します

プロジェクトで frameset 属性を使用したことがあるかどうかはわかりません。昨年、オンライン ...

HTMLでカメラを読み込む方法

効果図: 全体的な効果: ビデオ読み込み中: 写真:ステップ1: HTML要素を作成するまず、HTM...

MySQLインデックスの基礎となるデータ構造の詳細

目次1. インデックスの種類1. B+ツリー2. MyISAM と InnoDB の B+ ツリー ...