* Reading Assist R (読書アシスト)

大日本印刷(DNP)と日本ユニシスが開発した「読書アシスト」をの手法を、手軽に試してみよう。

質問やレビューの投稿はこちらへ、スクリプトの通報はこちらへお寄せください。
  1. // ==UserScript==
  2. // @name * Reading Assist R (読書アシスト)
  3. // @namespace knoa.jp
  4. // @description 大日本印刷(DNP)と日本ユニシスが開発した「読書アシスト」をの手法を、手軽に試してみよう。
  5. // @include *
  6. // @version 1.0.3
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. /*
  11. [update]
  12. 「小説家になろう」など、一部のサイトで動作しなかった問題を修正しました。
  13.  
  14. [memo]
  15. 赤月ゆにちゃんの指摘など
  16. https://twitter.com/kantankikaku/status/1310747945730322433
  17.  
  18. 横幅75%を超えて句読点があれば積極的に改行していくとか
  19. br追加?ってわけにはいかない気が。
  20. margin-right追加して改行させる?
  21. */
  22. (function(){
  23. const KEY = 'r';/*起動キー*/
  24. const Y = 0.1;/*斜め字下げ(em)*/
  25. const GAP = 0.1;/*斜め字下げ横間隔(em)*/
  26. const X = 1.0;/*インデント(em)*/
  27. const MAXINDENT = 8;/*最大インデント回数*/
  28. const RE = /.+?([、。!?!?\s]+|\p{sc=Hiragana}(?=\p{sc=Katakana})|\p{sc=Hiragana}(?=\p{sc=Han})|[^\w,\.](?=[\w,\.(「―])|$)/u;/*句読点、ひらがなの終わり、記号の始まりを検出して文節とみなす*/
  29. window.addEventListener('keydown', function(e){
  30. if(['input', 'textarea'].includes(e.target.localName) || e.target.isContentEditable) return;
  31. if(e.key === KEY && !e.metaKey && !e.altKey && !e.shiftKey && !e.ctrlKey){
  32. /* 対象要素 */
  33. /* brを含むdivを分割して複数のpにする */
  34. Array.from(document.querySelectorAll('div')).filter(div => Array.from(div.children).some(c => c.localName === 'br')).forEach(div => {
  35. let ps = [];
  36. Array.from(div.childNodes).forEach((n, i) => {
  37. if(i === 0 || n.localName === 'br'){
  38. ps.push(document.createElement('p'));
  39. if(n.localName === 'br') div.replaceChild(ps[ps.length- 1], n);/*i===0かつbrに備えて先に判定*/
  40. else div.insertBefore(ps[ps.length- 1], n);
  41. }else{
  42. ps[ps.length - 1].appendChild(n);
  43. }
  44. });
  45. });
  46. let targets = [
  47. ...Array.from(document.querySelectorAll('p')),
  48. ];
  49. /* 斜め字下げ */
  50. const flow = function(n, i){
  51. n.style.display = 'inline-block';
  52. n.style.transform = `translateY(${i*Y}em)`;
  53. n.style.marginRight = `${GAP}em`;
  54. };
  55. const split = function(n, i){
  56. if(n.nodeType === Node.TEXT_NODE){
  57. n.data = n.data.trim();
  58. let pos = n.data.search(RE);
  59. if(pos !== -1){
  60. let rest = n.splitText(RegExp.lastMatch.length);/*ターゲットであるnと続くrestに分割*/
  61. /* この時点でn(処理済み),target(新規テキスト),rest(次に処理)の3つに分割されている */
  62. let span = document.createElement('span');
  63. flow(span, i)
  64. /* 直前のrubyは1要素として吸収する */
  65. while(n.previousElementSibling && n.previousElementSibling.localName === 'ruby') span.appendChild(n.previousElementSibling);
  66. span.appendChild(n);/*textNode*/
  67. rest.before(span);
  68. return split(rest, ++i);
  69. }else{
  70. if(n.nextSibling) return split(n.nextSibling, i);
  71. }
  72. }
  73. else if(n.localName === 'ruby'){
  74. if(n.nextSibling) return split(n.nextSibling, i);
  75. }
  76. /* もともと含まれる a や span など */
  77. else if(n.nodeType === Node.ELEMENT_NODE){
  78. flow(n, i);
  79. if(n.nextSibling) return split(n.nextSibling, ++i);
  80. }
  81. };
  82. const getMarginBottom = (e) => parseFloat(getComputedStyle(e).marginBottom);
  83. const getTranslateY = (e) => parseFloat((getComputedStyle(e).transform.match(/[0-9.]+/g) || [0,0,0,0,0,0])[5]);
  84. targets.forEach(p => {
  85. if(p.firstChild) split(p.firstChild, 0);/*回しながらchildNodesは増えていく*/
  86. if(p.children.length === 0) return;
  87. p.dataset.originalMarginBottom = p.dataset.originalMarginBottom || getMarginBottom(p);
  88. p.style.marginBottom = parseFloat(p.dataset.originalMarginBottom) + getTranslateY(p.lastElementChild) + 'px';
  89. });
  90. /* インデント */
  91. targets.forEach(p => {
  92. /* 複数回起動に備えてリセット */
  93. for(let i = 1; p.children[i]; i++){
  94. p.children[i].style.marginLeft = `0em`;
  95. }
  96. let x = [0], breaks = 0;
  97. for(let i = 1; p.children[i]; i++){
  98. setTimeout(function(){
  99. x[i] = p.children[i].getBoundingClientRect().x;
  100. if(x[i-1] < x[i]) return;
  101. if(MAXINDENT <= ++breaks) breaks = 0;
  102. p.children[i].style.marginLeft = `${breaks * X}em`;
  103. }, i);
  104. }
  105. });
  106. }
  107. });
  108. })();