developer's diary

最近はc#のエントリが多いです

pdf-lib.jsというライブラリを利用すると、日本語フォントを埋め込んだPDFをブラウザ上で生成できる

2014年にjsPDFとかFileSaver.jsとか使って、ブラウザでPDF出力みたいなことしてたんですが、 その時は、日本語フォントの埋め込みができなかったので、canvasに日本語の文字書いて無理くりimageをPDFに出力するということをやってました。

で、6年たった今どれくらい技術は発達してるのか?と思い。 調べてみました。

あるじゃないですか。フォント埋め込みできるjsのpdf生成ライブラリ

pdf-lib.js

pdf-lib.js.org

サンプルソース

    const { PDFDocument, rgb } = PDFLib

    // ボタン押下時の処理
    async function createPdf() {
      // PDFドキュメント生成
      const pdfDoc = await PDFDocument.create();
      // フォント埋込のおまじない
      pdfDoc.registerFontkit(fontkit);
      //読み込みフォントのURL
      const url = './font/DotGothic16-Regular.ttf';
      //フォントを読み込んでバイト配列で保持
      const fontBytes = await fetch(url).then((res) => res.arrayBuffer());
      //フォント埋め込み
      const font = await pdfDoc.embedFont(fontBytes);
      //PDFに1ページ追加
      const page = pdfDoc.addPage();
      //印字
      page.drawText('勇者は頑張ってjavascriptでpdfを印刷し...',{x: 0,y: 800, size: 30,font: font,color: rgb(0,0,0)});
      page.drawText('勇者は頑張ってjavascriptでpdfを印刷し...',{x: 0,y: 770, size: 20,font: font,color: rgb(0,0,0)});
      page.drawText('勇者は頑張ってjavascriptでpdfを印刷し...',{x: 0,y: 750, size: 10,font: font,color: rgb(0,0,0)});
      page.drawText('勇者は頑張ってjavascriptでpdfを印刷し...',{x: 0,y: 740, size: 8,font: font,color: rgb(0,0,0)});
      //PDFのバイト配列取得
      const pdfBytes = await pdfDoc.save();
      //バイト配列をダウンロードさせる
      download(pdfBytes, "勇者は.pdf", "application/pdf");
    }

サンプル

mitsugeek.github.io

PDFのサイズは?

フォント自体のサイズは、4.12 MBなんですが、生成されたPDF自体は、1.75 MBでした。

ちなみに、ipag.ttfでやってみると、フォントのサイズが5.94 MBで、生成された4.13 MBとなりました。

感想

ファイルサイズが大きい印象です。

Laravel-dompdfだと、画像含んでも1MByteいかないので、埋め込みフォントのところがボトルネックになるのかな。

ただ、サーバレスでPDFが生成できるのは、メリットが大きいですね。

javascriptでPDF出力。jsPDFとかFileSaver.jsとか。

ソース

<input id="download-pdf" type="button" value="PDFをダウンロード" />
<script type="text/javascript" >
$(document).ready(function() {
  $('#download-pdf').click(function(){
    var doc = new jsPDF();
    doc.setFontSize(40);
    doc.text(10, 20, "out put pdf for jsPDF");
    doc.setFontSize(20);
    doc.text(10, 80, "example1 pdf");
    doc.text(10, 90, "example2 pdf");
    doc.text(10, 100, "example3 pdf");
    doc.addPage();
    doc.text(10, 20, "example4 pdf");
    doc.save('sample.pdf');
  });
});
</script>

DEMO

↑ 上記ボタンを押すとPDFがダウンロードされます。

動き

f:id:mitsugi-bb:20140305012533p:plain

PDFがダウンロードされている様子

f:id:mitsugi-bb:20140305012650p:plain

出力されたPDFを開いてみる

f:id:mitsugi-bb:20140305011022p:plain

利用ライブラリ

textareaの内容を抽出して別窓に書き出すブックマークレット

テキストエリアの内容を全部抜き出したいときに利用。

用途としてはあんまりないですが。

ソース

javascript:v="";s=document.getElementsByTagName("textarea");for(i=0;i<s.length;i++){v+=s[i].value+"<br />";}w=window.open("","","width=280,height=480");w.document.open();w.document.write(v);

example

textarea抽出ブックマークレット

test1: test2:

javascriptの複雑の複雑さとコードの品質について「メモ」(かなり途中)

http://www.slideshare.net/JarrodOverson/complexity-28214103 を読んだメモ

かなり途中ですごく荒削りです。

重要事項

  • Javascriptは動的言語である
  • 未熟なツールとIDE
  • モジュールのスタイルが乱暴
  • ベストプラクティスは言語の進化によって変化する
  • サーバとクライアントが似てるようで異なる

  • 人材がいない

    • クロージャーがわかる人(少ない)
    • jQueryのエキスパート(さらに少ない)
    • webプラットフォームの専門家(もっと少ない)
  • 進歩は驚異

    • そしてそれについていくのは難しい
    • 新しい技術は使用できなくなる場合があり、実際にそれを利用できるようにする必要もある
  • リファクタリングは容易ではない

    • コールバック地獄はただ深いネスト以上のもの。
    • IDEはまだあまり役立たない
    • しかし、柔軟性はどこよりもWEB上の方が重要
  • WEBは難しい( The Web is hard )

  • WEBアプリケーションは解決されていない
  • 事実巨大な方向転換と、同じ道をたどっている
  • 多くのソリューションはまだ存在しない。

  • jsは中立のリードにフリーランでも、最終的な形ではありません。(わからん。。。)

静的解析とlinting(lint)

  • 静的解析とlintingはあなたのコードを尊重し、体系化する

    • すべてのコードが同じになるはず
      • スタイル
      • 命名
      • 句読点
      • インデント
  • GitHubの分析に基づいたコーディング規則

    • 90%以上が、最後のコンマを使用
//最後のカンマ
var foo = 1,
    bar = 2;

//最初のカンマ
var foo = 1
   ,bar = 2;
  • 80%以上が、スペースを利用したインデント
  • 55%以上が、単一引用符を使用する
  • 詳しくはこちら

  • 緩い施行は違反を生む

    • 警告はビルド失敗する必要がある
    • これらはテストの失敗と同様に重要
  • 対応するオプションが存在

    • JSLint
    • Closure Linter
    • JSHint
    • ESLint
    • これらを積極的に利用
  • 循環複雑度はコードのブロックパスの数

  • lint*1 ⇒wikipedia

複雑度の可視化( visualizing complexity )

*1:主にC言語のソースコードに対し、コンパイラより詳細かつ厳密なチェックを行うプログラム

クラウド開発者宣言な人はAWSJavaScriptSDK/guide/browser-configuring.htmlを読もう。

クラウドって宣言できるWEBアプリケーションを開発しようとする人(スタートアップメイン)は、このドキュメント( http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-configuring.html )必読な気がする。

webブラウザからawsSDK利用するってことは、

大げさに言うとWEBアプリケーションのアーキテクチャが変わるってこと。そしてインフラ回り、サーバーサイドを意識しないようになる。 そしてユーザ数が増えてもawsが面倒みてくれる。

相互運用性フレームワークの概念からは離れていって、ベンダーロックインが加速するけど、スケールをあまり意識せずにフロントエンドに集中できると考えると凄い。

近いうちに、AngularJS とawsを利用したwebアプリケーションとかきっと出てくるよね。

cloudfront+SSLも重要

http://dev.classmethod.jp/cloud/aws/original-domain-cloudfront-ssl/ も読んどかなくっちゃ。

ということで、http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-configuring.htmlを読もう。

参考URL

localStorageをWeb SQL Databaseで代用してみる。(3)

の続きです。

最終的にこうなりました。

メソッド 意味
setItem keyとvalueを指定してlocalStorageに保存する
getItem keyを指定してcallback関数で、localStorageから取り出したvalueを渡す
getItem(key, callback)
removeItem keyを指定してlocalStorageからkey valueのペアを取り除く
clear localStorageの中身を全て取り除く

localStorage-db.jsという名前にしております。

(function(w){
  var ls = {};

  var db = openDatabase("localStorage", "", "localStorage", 1024*1024);
  db.transaction(
    function(tr){
      tr.executeSql("CREATE TABLE IF NOT EXISTS LOCALSTRAGE(key TEXT, value TEXT) ", []);
    }
  );
  ls.setItem = function(key, value){
    db.transaction(function(tr){
      tr.executeSql("SELECT * FROM LOCALSTRAGE WHERE key = ?", [ key ],
        function(rt, rs) {
          if(rs.rows.length > 0){
            tr.executeSql("UPDATE LOCALSTRAGE SET value = ? WHERE key = ?", [ value, key ]);
          }else{
            tr.executeSql("INSERT INTO LOCALSTRAGE (key, value) VALUES (?, ?)", [ key, value ]);
          }
        }
      )
    });
    return undefined;
  }

  ls.getItem = function(key, setFunc){
    db.transaction(function(tr){
      tr.executeSql("SELECT * FROM LOCALSTRAGE WHERE key = ?", [ key ],
        function(rt, rs) {
          if(rs.rows.length > 0){
            var row = rs.rows.item(0);
            setFunc(row.value);
          }else{
            setFunc(null);
          }
        }
      )
    });

    return undefined;
  }  
  ls.removeItem = function(key){
    db.transaction(function(tr){
      tr.executeSql("DELETE FROM LOCALSTRAGE WHERE key = ?", [ key ]);
    });
    return undefined;
  }
  ls.clear = function(){
    db.transaction(function(tr){
      tr.executeSql("DELETE FROM LOCALSTRAGE", []);
    });
    return localStorage.clear();
  }

  if(!w.localStorageDB){
    w.localStorageDB = ls;
  }
}(window));

qunitでのテスト

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>localStorageDB test</title>
  <link rel="stylesheet" href="qunit-1.11.0.css" />  
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="qunit-1.11.0.js"></script>
  <script src="localStorage-db.js"></script>
  <script type="text/javascript"> 

test( "setItem result", function() {
  localStorageDB.clear();
  ok(localStorageDB.setItem("key", "value") === undefined, "Passed !");
});

asyncTest( "setItem update getItem", function() {
  expect(1);
  localStorageDB.clear();
  localStorageDB.setItem("key", "value");
  localStorageDB.setItem("key", "value2");
  localStorageDB.getItem("key", function(value){
    ok( value === "value2" , "Passed!" );
    start();
  });
});

asyncTest( "asyncTest getItem", function() {
  expect(1);
  localStorageDB.clear();
  localStorageDB.setItem("key", "value");
  localStorageDB.getItem("key", function(value){
    ok( value === "value" , "Passed!" );
    start();
  });
});

asyncTest( "asyncTest getItem Nothing result", function() {
  expect(1);
  localStorageDB.clear();
  localStorageDB.getItem("key", function(value){
    ok( value === null , "Passed!" );
    start();
  });
});


asyncTest( "asyncTest removeItem test", function() {
  expect(1);
  localStorageDB.setItem("key", "value");
  localStorageDB.removeItem("key");
  localStorageDB.getItem("key", function(value){
    ok( value === null , "Passed!" );
    start();
  });
});


asyncTest( "clear test" , function(){
  expect(3);

  localStorageDB.setItem("key1", "value");
  localStorageDB.setItem("key2", "value");
  localStorageDB.setItem("key3", "value");

  localStorageDB.clear();

  localStorageDB.getItem("key1", function(value){
    ok( value === null , "Passed!" );
    localStorageDB.getItem("key2", function(value){
      ok( value === null , "Passed!" );
      localStorageDB.getItem("key3", function(value){
        ok( value === null , "Passed!" );
        start();
      });
    });
  });
  
});
  </script>
</body>
</html>

そして、結果です。

f:id:mitsugi-bb:20130129005720p:plain

この検証でわかったことは、Web SQL Databaseを利用して、localStorageっぽいインターフェースの部品を作ることは現時点では不可能ということがわかりました。残念。

※どうも、WebWorkersだとopenDatabaseSyncが利用できるみたいです。(参考:iOS 5 から WebWorker と Web SQL Database(同期) が使えます

localStorageをWeb SQL Databaseで代用してみる。(2)

前回(localStorageをWeb SQL Databaseで代用してみる。(1))の続きです。

特に何も考えず、localStorageをWeb SQL Databaseに置き換えようと考えていた私は、 コーディングを初めて間もなくあることに気づきました。

Web SQL Databaseが非同期だということに。

setItemを実装する段階で気がつきました。 setItemするんだけども存在したらupdateだよね。あ、selectというかWeb SQL Databaseってコールバックで色々制御するんだ。 なるほど〜c言語で初めてデータベース触る感覚に似ているね・・・ と考えつつ、若干嫌な予感しながら、setItemを実装。

(function(w){
  var ls = {};

  var db = openDatabase("localStorage", "", "localStorage", 1024*1024);
  db.transaction(
    function(tr){
      tr.executeSql("CREATE TABLE IF NOT EXISTS LOCALSTRAGE(key TEXT, value TEXT) ", []);
    }
  );
  ls.setItem = function(key, value){
    db.transaction(function(tr){
      tr.executeSql("SELECT * FROM LOCALSTRAGE WHERE key = ?", [ key ],
        function(rt, rs) {
          if(rs.rows.length > 0){
            tr.executeSql("UPDATE LOCALSTRAGE SET value = ? WHERE key = ?", [ value, key ]);
          }else{
            tr.executeSql("INSERT INTO LOCALSTRAGE (key, value) VALUES (?, ?)", [ key, value ]);
          }
        }
      )
    });
    return undefined;
    //return localStorage.setItem(key, value);
  }
  ls.getItem = function(key){
    return localStorage.getItem(key);
  }  
  ls.removeItem = function(key){
    return localStorage.removeItem(key);
  }
  ls.clear = function(){
    return localStorage.clear();
  }

  if(!w.localStorageDB){
    w.localStorageDB = ls;
  }
}(window));

さあgetItemを・・・

そうそう、getItemは戻り値が重要・・・・ あ、実装できない。

Web SQL Databaseってselectの受け取りがcallbackだから、localStorageみたいなことできない。。

ということで、無理矢理ですが、getItemのメソッドの役割を若干変えてしまいます。

メソッド 意味
setItem keyとvalueを指定してlocalStorageに保存する
getItem keyを指定してlocalStorageからvalueを取り出す
getItem※ keyを指定してcallback関数で、localStorageから取り出したvalueを渡す
getItem(key, callback)
removeItem keyを指定してlocalStorageからkey valueのペアを取り除く
clear localStorageの中身を全て取り除く

仕様が変わったので、テストも書き換える。テストは前回と同じくqunitで。

asyncTest( "asyncTest getItem", function() {
  expect(1);
  localStorageDB.clear();
  localStorageDB.setItem("key", "value");
  localStorageDB.getItem("key", function(value){
    ok( value === "value" , "Passed!" );
    start();
  });
});

Web SQL Databaseは非同期ということで、asyncTestに置き換えます。 これで、setItemとgetItemの実装が終了。

次回で完結としましょう。

localStorageをWeb SQL Databaseで代用してみる。(1)

実装しときたいメソッド

メソッド 意味
setItem keyとvalueを指定してlocalStorageに保存する
getItem keyを指定してlocalStorageからvalueを取り出す
removeItem keyを指定してlocalStorageからkey valueのペアを取り除く
clear localStorageの中身を全て取り除く

実際のコードの書き出し。localStorageDBという名前にしてみよう。 既存の実装の置き換えということで、メソッド内部をlocalStorageで実装しておく

(function(w){
  var ls = {};
  ls.setItem = function(key, value){
    return localStorage.setItem(key, value);
  }
  ls.getItem = function(key){
    return localStorage.getItem(key);
  }
  ls.removeItem = function(key){
    return localStorage.removeItem(key);
  }
  ls.clear = function(){
    return localStorage.clear();
  }

  if(!w.localStorageDB){
    w.localStorageDB = ls;
  }
}(window));

空のメソッドだけ用意して次にテストコード。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>localStorage-db test</title>
  <link rel="stylesheet" href="qunit-1.11.0.css" />  
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="qunit-1.11.0.js"></script>
  <script src="localStorage-db.js"></script>
  <script type="text/javascript"> 

test( "setItem result", function() {
  localStorageDB.clear();
  ok(localStorageDB.setItem("key", "value") === undefined, "Passed !");
});

test( "setItem getItem", function(){
  localStorageDB.clear();
  localStorageDB.setItem("key", "value");
  ok( localStorageDB.getItem("key") === "value" , "Passed!" );
});

test( "getItem Nothing result" , function(){
  localStorageDB.clear();
  ok( localStorageDB.getItem("key") === null , "Passed!" );
});

test( "removeItem test" , function(){
  localStorageDB.setItem("key", "value");
  localStorageDB.removeItem("key");
  ok(localStorageDB.getItem("key") === null, "Passed!");
});

test( "clear test" , function(){
  localStorageDB.setItem("key1", "value");
  localStorageDB.setItem("key2", "value");
  localStorageDB.setItem("key3", "value");

  localStorageDB.clear();

  ok(localStorageDB.getItem("key1") === null, "Passed!");
  ok(localStorageDB.getItem("key2") === null, "Passed!");
  ok(localStorageDB.getItem("key3") === null, "Passed!");
 
});
  </script>
</body>
</html>

結果

f:id:mitsugi-bb:20130127163611p:plain

画像の取得に失敗した場合に使えるかもしれないブックマークレットとか

手順とかが画像で表示されているサイトが重くて見れない。。ってことで作ってみたんだけども。

javascript:(function(){var%20attach=function(e,t,h){if(e.addEventListener){e.addEventListener(t,h,false);}else{e.attachEvent('on'+t,h);}};var%20o=function(){this.src=this.src+"?a="+Math.floor(Math.random()*100);return false;};var%20imgs=document.getElementsByTagName("img");for(i%20in%20imgs){attach(imgs[i],'click',o);}})();

試してる途中でネットを探してみたらたくさんあるある。

http://d.hatena.ne.jp/caisui/20090502/1241256689

こちらのほうが洗練されてる気がします。

調べてみるとたくさんあるある。greasemonkeyとか。

http://d.hatena.ne.jp/TakiTake/20090123/p2

こんなのとか。
http://d.hatena.ne.jp/os0x/20100131/1264934725