- 投稿日:2021-06-23T20:31:42+09:00
PythonでHTMLをPDFに変換する際に画像を埋め込む
はじめに 昨今の"DX"ブームにより、自動化検討が進んでいます。レポートの自動化は誰もが思いつくDXの初手ともいうべきものです。その際に、Pythonでレポートを作成しPDFで書き換え不能にして保管する場面があるかもです。 私はさらに、そのPDFに画像を埋め込むという場面に遭遇しました。AtomエディタやVSCodeを使っている人であればMarkdownをPDFに変換するのが簡単だと思うでしょうか。今回PythonでHTMLをPDFに変換する際に、ローカルに保存した画像の扱いに手こずったため備忘録として以下の記事を残します。 環境 とりあえずWindows10の環境で実行します。pdfkitというライブラリが動くようであれば他のOSでもかまいません。 画像の準備 今回は以下のガイ・フォークスマスクをPDFに埋め込みます。権威に対する叛乱の象徴です。'Guy-Fawkes.jpg'という名前で保存します。 ライブラリのインストール:pdfkit pdfkitというPythonライブラリをインストールします。また、pdfkitのためにmkhtmltopdfというexeソフトウェアを使います。 画像埋め込みの要諦:Base64 URI HTMLにローカルの画像を埋め込む際、画像をBase64 URIという文字列へ変換し、そのURIをimgタグのsrcで参照させれば画像が表示されます。これはJupyter NotebookでノートブックからHTMLをエクスポートした際にHTMLファイルに画像が埋め込む方法と同じです(Jupyterから出力したHTMLファイルのソース中で、base64で検索するとヒットします)。 Base64 URIを参照するとPNG画像は <img src="data:image/png;base64,..." />のように表記されます。JEPG画像を埋め込むときは"data:image/jpg;base64,..."のように書きます。"..."の部分はハッシュ化されたような人の目で理解できない文字列になっています。非常に長くなるので基本的に割愛します。 実装1:素直に画像を読み出してBase64を使う HTMLのヘッダーを追加する関数を定義します。これを使うことでHTMLの内容だけに注力して記録できます。 def set_html_meta(body, lang='ja'): """ <!DOCTYPE html> is essential """ assert lang in ('ja', 'en'), "it can take ('ja', 'en'); but %s was fed"%lang html = f'<!DOCTYPE html><html lang="{lang}">\n <meta charset="utf-8">\n' html += '<style> body { font-size: 1rem; } </style>\n <body>\n' html += body + '\n </body>\n</html>' return html 参考:Open base64 String Image in Jupyter Notebook Without Saving 次に、画像ファイルをopen()で読み出して、Base64を使ってエンコードします。エンコードされた文字はbytes型で、通常のstring型と異なるためPython3で比較/結合する前には.decode()でstring型へ直す必要があります。 import base64 imgname='Guy-Fawkes.jpg' encoded_str = base64.b64encode(open(imgname,'rb').read()) print(type(encoded_str)) data_uri = 'data:image/jpg;base64,'+encoded_str.decode() body="<img src='{}' width='200px'/>".format(data_uri) html=set_html_meta(body) with open('test.html', 'w') as out: """for debug purpose""" for row in html.split('\n'): out.write(row+'\n') # <class 'bytes'> 最後に、出来上がったHTMLファイルをPDFへ変換します。ここでpdfkitを使います。pdfkitの設定について、pdfkit.configurationで指定することができます。mkhtmltopdf.exeへのパスを指定しないと動作しないので気を付けてください。 import pdfkit options = { 'page-size': 'A4', 'margin-top': '1in', 'margin-right': '1in', 'margin-bottom': '1in', 'margin-left': '1in', 'encoding': "UTF-8", 'no-outline': None, 'disable-smart-shrinking': '', } conf_ = pdfkit.configuration(wkhtmltopdf='C:/wkhtmltox/bin/wkhtmltopdf.exe') pdfkit.from_string(html, 'test.pdf', options=options, configuration=conf_) #Loading pages (1/6) #Counting pages (2/6) #Resolving links (4/6) #Loading headers and footers (5/6) #Printing pages (6/6) #Done # Out[]: True 出来上がったtest.pdfを開くと、下図のように画像が埋め込まれていました。 躓いた点 変換したいHTMLファイルに<!DOCTYPE html>が記載されていないと、pdfkitがHTMLファイルを認識できず画像埋め込みに失敗します。正確にHTMLの文法を守るようにしましょう。 実装2:画像をBytesIOに書き出してgetvalue()する 参考:Is it possible to create encoded base64 URL from Image object? Base64の理解を深めるために少しExerciseします。PillowのImageオブジェクトとして読み出した画像をnumpy arrayに変換して、matplotlibで表示することができます。 import numpy as np from PIL import Image import matplotlib.pyplot as plt from io import BytesIO imgname='Guy-Fawkes.jpg' # Take in base64 string and return a numpy image array def stringToRGB(base64_string): imgdata = base64.b64decode(base64_string) image = Image.open(BytesIO(imgdata)) return np.array(image) byte_string=open(imgname,'rb').read() encoded_string = base64.b64encode( byte_string ) plt.imshow(stringToRGB(encoded_string)) plt.axis('off') plt.show()# Guy-Fawkesの画像がでる 上記のコードを踏まえて、BytesIOを利用していきます。以下のコードではPillowオブジェクトをBytesIOに保存し、BytesIOオブジェクトのgetvalue()メソッドを利用したらbytes型が得られました。これは画像データをrawのまま取得した形で、これをbase64.b64encode関数で変換するとBase64 URIが得られます。あとは先ほどと同じですね。 imgname='Guy-Fawkes.jpg' im = Image.open(imgname) output = BytesIO() im.save(output, format='JPEG') im_data = output.getvalue() print(type(im_data)) image_data = base64.b64encode(im_data) if not isinstance(image_data, str): # Python 3, decode from bytes to string image_data = image_data.decode() data_uri = 'data:image/jpg;base64,' + image_data body="<img src='{}' width='200px'/>".format(data_uri) html2=set_html_meta(body) #<class 'bytes'> 実行例1と同様にしてPDFを保存します。 options = { 'page-size': 'A4', 'margin-top': '1in', 'margin-right': '1in', 'margin-bottom': '1in', 'margin-left': '1in', 'encoding': "UTF-8", 'no-outline': None, 'disable-smart-shrinking': '', } conf_ = pdfkit.configuration(wkhtmltopdf='C:/wkhtmltox/bin/wkhtmltopdf.exe') pdfkit.from_string(html2, 'test2.pdf', options=options, configuration=conf_) #Loading pages (1/6) #Counting pages (2/6) #Resolving links (4/6) #Loading headers and footers (5/6) #Printing pages (6/6) #Done # Out[]: True ファイルを開けば以下のようにtest2.pdfが生成されていることが確認できます。 応用 Markdownを使ってMarkdown→HTMLへの変換をしたり、Jinja2を使ってMarkdownをテンプレートとして扱い動的にコンテンツを埋め込むことにすれば、機械的に様々なレポートを生成するソフトが作成できます。是非実装してみてください。 終わりに V for Vendetta(映画)は何度見ても名作です。1605年の火薬陰謀事件から400年後にガイ・フォークスをモチーフにした V for Vendettaが公開されたのは非常に上手かったなと思います。 “Beneath this mask there is more than just flesh. Beneath this mask there is an idea... and ideas are bulletproof." - Alan Moore, V for Vendetta 安直な"DX"ブームへ反抗していきましょう。 関連文献 Markdownの議事録 → HTML → PDF へ変換出力 【Python】WebページやHTMLをPDF出力する - wkhtmltopdfのインストールに言及している。 PythonでマークダウンをPDFに変換する
- 投稿日:2021-06-23T20:14:20+09:00
メールアドレスをWebに書くときのホスピタリティ
このように書くと、たぶん、きっと、おそらく、、、クロールbotに捕食されにくい。はず。知らんけど。 脆弱性診断などで引っかかった場合、この方法でパスできる。 ポイントは、CSSの content: attr(); を使うこと。 HTML <a class="email" data-domain="domain" data-user="user" data-subject="タイトル" data-body="本文">@</a> CSS .email { cursor: pointer; } .email::before { content: attr(data-user); } .email::after { content: attr(data-domain); } JavaScript var mailto = document.querySelector('.email') mailto.addEventListener('click', onClick) function onClick () { var user = event.target.getAttribute('data-user') var domain = event.target.getAttribute('data-domain') var subject = event.target.getAttribute('data-subject') var body = event.target.getAttribute('data-body') window.location.href = 'mailto:' + user + '@' + domain + '?subject=' + subject + '&body=' + body } DEMO CodePen https://codepen.io/YusukeNakaya/pen/LYWqJyW 免責 保証はできません(笑)
- 投稿日:2021-06-23T18:53:09+09:00
【初心者でもわかる】ポップアップウィンドウの作り方
どうも7noteです。ポップアップウィンドウの作り方 ポップアップを開いたまま、サイトの閲覧を継続してもらいたいなら、 ポップアップを別ウィンドウで開くように設定してみましょう。 最近のwebサイトのポップアップは全て閲覧中のウィンドウの真ん中に出るので ポップアップを消さないと閲覧を続行できません。 別タブで開くと、同様にタブを切り換えないと両方の情報を同時に見ることができません。 古典的な方法ですが、javascriptを使ってリンク先を専用の別ウィンドウでポップアップ表示させる方法を紹介します。 最近めっきりみなくなったので備忘録的に記事に残します (スマホが主流の時代なんで、別ウィンドウで開くはあんまり使われないかもですが。。。) ポップアップウィンドウの表示方法 index.html <a href="javascript:void(0);" onclick="window.open('{リンク先URL}','{ウィンドウ名}','{ウィンドウ設定}');return false;">別ウィンドウを開く</a> <!-- 入力例 --> <a href="javascript:void(0);" onclick="window.open('http://hoge-hoge.com/window/','window1','width=500,height=300');return false;">別ウィンドウを開く</a> 解説 window.open()で開いたウィンドウはタブ分けやブックマーク、拡張機能などのメニューなしで開かれます。 ウィンドウ設定の箇所で表示位置や大きさを変更することが可能です。 (top,left,width,hright等) ちなみにスマホの場合は単純に別タブで開かれます。ウィンドウの概念がないため。 まとめ PC表示の時しか使わない手法ですが、「別ウィンドウで情報を確認しながら入力フォームに情報を入力してもらう」みたいに使いどころはあるかなと思います。 javascriptといっても難しい計算や処理を入れていないので、普段javascriptに触れていない方でも簡単に実装が可能です。 おそまつ! ~ Qiitaで毎日投稿中!! ~ 【初心者向け】WEB制作のちょいテク詰め合わせ
- 投稿日:2021-06-23T10:39:53+09:00
ReactでTextareaの高さを可変にする
問題点 textareaのstyleにline-heightを指定すると 入力された値が1行の時でも、余分に余白ができてしまった。。。 textareaの高さを入力された行数によって、可変にしたい。 結論 textareaのrow属性を動的に指定して、実現できた。 コード(React_classコンポーネント) constructor() constructor(){ super(); this.state={ text:"サンプルテキスト" } } 高さを計算する関数 textarea内の改行は\nで取得できる 入力値を\nで分割すれば、改行数が取得できる calcTextAreaHeight(value){ let rowsNum = value.split('\n').length; return rowsNum } render() <textarea defaultValue={this.state.text} rows={this.calcTextAreaHeight(this.state.text)} onChange={e => this.setState({text:e.target.value})} />
- 投稿日:2021-06-23T09:03:12+09:00
input type=dateの未入力時の「年/月/日」「yyyy/mm/dd」を非表示にする
input type=dateの値が未入力のときに「年/月/日」「yyyy/mm/dd」と表示されたままになるのが見にくい、未入力のときは空欄のままにしてほしいという要望があり、いろいろ調べたけれど設定で変えることはできないようなので、強引ですがJavaScriptで文字色を変えるという方法で対処しました。 ※「年/月/日」「yyyy/mm/dd」等の表記はブラウザに依存するため変更できません。これも属性値で変更出来たら楽なのになあ。 // 初期表示時の文字色変更 $(window).on('load', function () { $.each($('input[type=date]'), (index, datebox) => { datebox.style.color = (datebox.value) ? 'black' : 'white'; } ); } ); $(function () { // フォーカス取得時は入力用にいったん色を付ける $('input[type=date]').focus(function (event) { this.style.color = 'black'; } ); // フォーカス喪失後の文字色変更 $('input[type=date]').blur(function (event) { this.style.color = (this.value) ? 'black' : 'white'; } ); } ); 一部だけに適応したいときは、セレクタに条件を追加してください。 色はわかりやすいように black white と書いていますが、画面デザインに合わせてお好きなように。 テキストボックスの色が複数ある場合は、white を transparent としておけばOKです。
- 投稿日:2021-06-23T06:57:03+09:00
Web技術の基本まとめ Chapter2
Chapter2 コンピュータが互いに接続して情報のやりとりをする仕組みをコンピュータネットワークという。 ネットワーク上で情報やサービスを提供する役割を持つコンピュータをサーバー サーバーから提供された情報やサービスを利用する役割を持つコンピュータをクライアントと呼ぶ。 スマートフォンやパソコンを利用してWebサイトを閲覧する場合、インターネットプロバイダーを利用する必要がある。 プロトコルはネットワークに接続された機器同士が通信をするときにあらかじめ決められた共通のルールや手順のこと。 TCP/IPとはインターネットにおけるさまざまなサービスを実現するためのプロトコルの集まりのこと。 TCP/IPは役割ごとに4つの階層に分かれている。 アプリケーション層(レイヤー4) Webブラウザやメールソフトなどのアプリケーションごとのやりとりを想定している。(HTTP,SMTP,FTP) トランスポート層(レイヤー3) データの役割や品質保証を規定 (TCP,UDP) インターネット層(レイヤー2) ネットワーク間の通信を規定 (IP,ICMP) ネットワーク層(レイヤー1) コネクタ形状や周波数といったハードウェアに関する規定 (Wi-Fi,イーサネット) データの行先を管理するために利用されているのがIPアドレスという。 なお数字で表記されるため覚えにくく扱いづらいためドメインを使用する。 コンピュータ上ではドメインでは処理できないためドメインをIPアドレスに変換する必要がある。 変換する仕組みをDNSという。それを提供するサーバーをDNSサーバーという。 IPアドレスでは接続するコンピュータを指定できるが、コンピュータが提供するサービスを指定できない。それを指定するためのものがポート番号。 HTTPは簡単なやりとりの手順や内容を決めている。