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

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

目前为 2020-08-06 提交的版本。查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址