HTMLでWordのようにプレビューし、ファイルをダウンロードする

## はじめに

Webサービス上でいろいろやって、それをWordで出力したい。
っていうののとりあえず出力部分のプロトタイプをつくってみようと思い、見た目の調節はjQueryで書きました。

思ったよりもいい感じだったので残しておこうかと。
変数名とか適当すぎるけどゆるして・・・
あとたぶんもっと効率的な書き方あるけど、そこも適当なままです

もしかしたら普通にプラグインとかあるかも（しらべていない）

## こだわったところ

* ただ縦に長く表示するんじゃなくて、Wordのように、ページごとに区切って表示した。
* 最後のページがテキスト量で大きさが変わらないよう、ちゃんと1ページ分表示されるようにした。

## 諦めたこと（やらなかったこと）

* 本当は文字が改行されるタイミングでページが切り替わるようにしたかったが、今回はプロトタイプがつくれればよかったので、セクションごとにページを切り替えた。
* ページの下に `x/6` のようにページャーふってもよかった。

## できたもの

こんなかんじ

![duinr-6c14a (1).gif](https://qiita-image-store.s3.amazonaws.com/0/34634/853dbdf8-5c55-62b1-da3d-950eb8a5579d.gif)

## 参考にしたページ

* [How to Export HTML to Word Document with JavaScript](https://phppot.com/javascript/how-to-export-html-to-word-document-with-javascript/)
* [JavaScriptでファイルダウンロード処理を実現する](https://qiita.com/wadahiro/items/eb50ac6bbe2e18cf8813)
* [コピペでできる！cssとhtmlのみで作るいい感じの背景パターン 12選](https://copypet.jp/2206/)
* [Promiseを使った非同期ループ処理の書き方について](https://blog.htmlhifive.com/2016/02/22/promise-async-loop/)

## 実装内容

大きく２つ

1. docファイルでのダウンロード
2. Wordのような見た目

### 1. docファイルでのダウンロード

HTMLは簡単にこんな感じ
あ、プロトタイプなので、参考にしたコードほぼそのままです。

```html
<div class="content_area">
  <section id="export_detail">
<h1><center>「セロ弾きのゴーシュ」宮沢賢治</center></h1>
<section><p>窓は形のごつごつセロ団にしんにきい棒うだ。</p></section>
<section><p>またしばらくだめたますという顔たな。上手ましましんましはたではゴーシュの無理たちのときをはがさがさ変たたで、みんなばかりゴーシュにかかえせんました。弾いすぎみんなも下を青いたて途中のゴーシュの猫たちがきい第二セロ館のアンコールを走ってくれだう。</p></section>
・・・
<section><p>「おまえだながら」ゴーシュに弾きますた。</p></section>
  </section>

  <div class="export_btn">
    <button id="btn-export" onclick="exportHTML();">Export .doc</button>
    <a id="download" href="#" download="document.txt" onclick="handleDownload()">Export .txt</a>
  </div>
</div>
```

docファイルのダウンロード

```js
  function exportHTML(){
     var header = "<html xmlns:o='urn:schemas-microsoft-com:office:office' "+
          "xmlns:w='urn:schemas-microsoft-com:office:word' "+
          "xmlns='http://www.w3.org/TR/REC-html40'>"+
          "<head><meta charset='utf-8'><title>Export HTML to Word Document with JavaScript</title></head><body>";
     var footer = "</body></html>";
     var sourceHTML = header+document.getElementById("export_detail").innerHTML+footer;

     var source = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(sourceHTML);
     var fileDownload = document.createElement("a");
     document.body.appendChild(fileDownload);
     fileDownload.href = source;
     fileDownload.download = 'document.doc';
     fileDownload.click();
     document.body.removeChild(fileDownload);
  }
```

あと一応txtファイルでのダウンロードも

```js
  function handleDownload() {
      var content = document.getElementById("export_detail").textContent;
      var blob = new Blob([ content ], { "type" : "text/plain" });

      if (window.navigator.msSaveBlob) {
          window.navigator.msSaveBlob(blob, "document.txt");

          window.navigator.msSaveOrOpenBlob(blob, "document.txt");
      } else {
          document.getElementById("download").href = window.URL.createObjectURL(blob);
      }
  }
```

変更したところは、 `content` の内容をHTMLでできている `#export_detail` のテキストだけを取るようにした。
該当部分はこちらの1行

```js
      var content = document.getElementById("export_detail").textContent;
```


#### その他注意点

* docxで出力すると、Microsoft Office以外のツールは開けない場合がある。  
（詳しく調べていないので、私の環境に問題があるかもしれない）
* CSSでつけたstyleを反映させるときは、インラインに書かないと反映されない。  
（今回は出力部分のCSS省略）
* その場合、 `1.5em` のような書き方でも反映されないので、`16px`のように指定しないといけない。
* `<section>`はdocファイルに出力しても、影響を与えないらしい。（改行もされない）  
なので、そこにこのwebページでつけたいCSS（ページをまたぐときのマージン）をつけ、文字サイズなどはここでいう `<p>` につけると良さそう。


### 2. Wordのような見た目

はいこっち！
こっちのほうがいろいろ頭使った。し、楽しかった。

まずは、A4サイズ。
まあぶなんに72dpiでいいでしょう。

dpiがわからない人は[こちら](http://smile-sharing.co.jp/blog/03/pixel_dpi.html)ってリンクを用意してみたけど、なかなかわかりやすいの見つけられなかった。
簡単にいうと、pxはスクリーンに表示するときの単位、dpiは紙にプリントするときの単位。

話しだすと長くなるのでこのあたりで・・・

A4の72dpiは `595px × 842px`
今回は、
上：30mm
下：20mm
左：19mm
右：19mm
の余白にしたかったので、pxに直すと、だいたい `padding: 85px 54px 56px;`

以下ざっとCSS

```css
  *{
    box-sizing: border-box;
  }
  html{
    background-color: #aaa;
    color: #333;
    font-size: 14px;
    line-height: 1.4;
  }
  h2,h3{
    margin: 14px 0;
  }
  .content_area{
    margin: 60px auto;
  }
  #export_detail{
    width: 595px;
    /* height: 842px; */
    margin: 0 auto;
    padding: 85px 54px 56px;
    position: relative;
    background-image: linear-gradient(#fff 95%, transparent 5%, transparent);
    background-size: 595px 886px;
  }
  .export_btn{
    position: fixed;
    top: 60px;
    left: 50%;
    margin-left: 318px;
    width: 140px;
  }
  a,button{
    display: block;
    width: 100%;
    padding: 8px 16px;
    margin-bottom: 10px;
    background-color: #333;
    color: #fff;
    font-size: 20px;
    line-height: inherit;
    text-align: center;
    text-decoration: none;
    vertical-align: middle;
    border: none;
    border-radius: 3px;
    transition: .2s;
    cursor: pointer;
  }
  a:hover,button:hover{
    box-shadow: 3px 3px 0 0 #000;
  }
```

**ここでこだわったポイント①**

1ページごとに背景を表示したい！
手を動かす前から、だいたいイメージはできていたが、思った通りに出来たから嬉しい(●´ω｀●)

背景をシマシマにする方法でやってます。
ここの部分

```css
  #export_detail{
    background-image: linear-gradient(#fff 95%, transparent 5%, transparent);
    background-size: 595px 886px;
  }
```

あとは、１ページごとにテキストが白い背景の上に来るように調整。

```js
  $(window).on('load',function(){
    const a4h = 842,       //A4の縦
          a4m = 85 + 56,   //A4の上下の余白
          a4i = a4h - a4m, //A4の余白を除いた縦の部分
          a4n = 44,        //A4とA4の間
          mt = 60,         //A4エリアの上のマージン
          im = a4m + a4n,
          dh = $('#export_detail').height(),
          sl = $('#export_detail section').length,
          sh = [];
    let md = 0,
        a4p = 1;

    new Promise(function(res, rej) {
      function loop(i) {
        return new Promise(function(resolve, reject) {
          if($('#export_detail section').eq(i).offset()){
            sh[i] = $('#export_detail section').eq(i).offset().top;
            if((mt+85+(a4i*a4p+im*(a4p-1))) < sh[i]){
              md = (mt+85+(a4i*a4p+im*(a4p-1))) - sh[i-1] + im + 14;
              $('#export_detail section').eq(i-1).css('margin-top',md+'px');
              a4p++;
            }
          }
          resolve(i+1);
        })
        .then(function(i) {
          if (i > sl) {
            res();
          } else {
            loop(i);
          }
        });
      }
      loop(0);
    }).then(function() {
      $('#export_detail').outerHeight(a4i*a4p+im*a4p-a4n);
    })
  });
```

結構ごにょごにょしましたが、考え方はシンプル。
各 `<section>` の `.offset().top` をとって、A4の1ページ分をはみ出たら、その一つ上の `<section>` にマージンをつける。
どれくらいのマージンをつけるかというと、一つ上 `<section>` とA4の1ページ分との差分、A4の余白、A4とA4の間の余白を足したもの。そんな感じです。
（今回は `<p>` についているマージンを手書きで `14px` としています。）

**最後にこだわったポイント②**
Wordのように、最後のページがちゃんとA4の大きさになるように調整。

```js
      $('#export_detail').outerHeight(a4i*a4p+im*a4p-a4n);
```


おわり。