review-retrovert 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +9 -0
  3. data/.editorconfig +3 -0
  4. data/.github/workflows/retrovert.yml +20 -5
  5. data/.gitignore +1 -0
  6. data/.vscode/launch.json +48 -0
  7. data/Gemfile.lock +13 -4
  8. data/Makefile +24 -0
  9. data/docker/review.Dockerfile +10 -0
  10. data/lib/review/retrovert/converter.rb +374 -111
  11. data/lib/review/retrovert/reviewcompat.rb +57 -0
  12. data/lib/review/retrovert/reviewdef.rb +70 -0
  13. data/lib/review/retrovert/sty/{ird.sty → ird.sty.erb} +9 -1
  14. data/lib/review/retrovert/sty/review-retrovert-custom.sty.erb +6 -0
  15. data/lib/review/retrovert/utils.rb +35 -0
  16. data/lib/review/retrovert/version.rb +1 -1
  17. data/lib/review/retrovert/yamlconfig.rb +0 -1
  18. data/package-lock.json +6 -0
  19. data/package.json +1 -0
  20. data/review-retrovert.gemspec +5 -1
  21. data/testdata/mybook/.gitignore +3 -1
  22. data/testdata/mybook/.textlintrc +11 -0
  23. data/testdata/mybook/Rakefile +8 -0
  24. data/testdata/mybook/catalog.yml +36 -10
  25. data/testdata/mybook/config-starter.yml +134 -9
  26. data/testdata/mybook/config.yml +44 -15
  27. data/testdata/mybook/contents/00-preface.re +48 -2
  28. data/testdata/mybook/contents/01-install.re +38 -5
  29. data/testdata/mybook/contents/02-tutorial.re +333 -113
  30. data/testdata/mybook/contents/03-syntax.re +2370 -373
  31. data/testdata/mybook/contents/04-customize.re +424 -88
  32. data/testdata/mybook/contents/05-faq.re +288 -10
  33. data/testdata/mybook/contents/06-bestpractice.re +431 -59
  34. data/testdata/mybook/contents/91-compare.re +402 -2
  35. data/testdata/mybook/contents/92-filelist.re +14 -8
  36. data/testdata/mybook/contents/93-background.re +10 -10
  37. data/testdata/mybook/contents/99-postface.re +2 -1
  38. data/testdata/mybook/contents/r0-root.re +1 -1
  39. data/testdata/mybook/css/webstyle.css +180 -2
  40. data/testdata/mybook/data/terms.txt +3 -0
  41. data/testdata/mybook/data/words.txt +15 -0
  42. data/testdata/mybook/images/03-syntax/index-page.png +0 -0
  43. data/testdata/mybook/images/03-syntax/order-detail.png +0 -0
  44. data/testdata/mybook/images/04-customize/section_decoration_samples.png +0 -0
  45. data/testdata/mybook/images/05-faq/dummy-image.png +0 -0
  46. data/testdata/mybook/images/06-bestpractice/section_title_wlines.png +0 -0
  47. data/testdata/mybook/images/avatar-b.png +0 -0
  48. data/testdata/mybook/images/avatar-g.png +0 -0
  49. data/testdata/mybook/images/avatar-r.png +0 -0
  50. data/testdata/mybook/images/caution-icon.png +0 -0
  51. data/testdata/mybook/images/info-icon.png +0 -0
  52. data/testdata/mybook/images/warning-icon.png +0 -0
  53. data/testdata/mybook/layouts/layout.html5.erb +3 -1
  54. data/testdata/mybook/layouts/layout.tex.erb +265 -379
  55. data/testdata/mybook/layouts/layout.tex.erb.orig +386 -0
  56. data/testdata/mybook/lib/ruby/review-book.rb +64 -0
  57. data/testdata/mybook/lib/ruby/review-builder.rb +902 -63
  58. data/testdata/mybook/lib/ruby/review-compiler.rb +675 -22
  59. data/testdata/mybook/lib/ruby/review-epubbuilder.rb +33 -0
  60. data/testdata/mybook/lib/ruby/review-epubmaker.rb +10 -7
  61. data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +354 -66
  62. data/testdata/mybook/lib/ruby/review-latexbuilder.rb +429 -146
  63. data/testdata/mybook/lib/ruby/review-maker.rb +117 -6
  64. data/testdata/mybook/lib/ruby/review-markdownbuilder.rb +945 -0
  65. data/testdata/mybook/lib/ruby/review-markdownmaker.rb +91 -0
  66. data/testdata/mybook/lib/ruby/review-monkeypatch.rb +2 -0
  67. data/testdata/mybook/lib/ruby/review-pdfmaker.rb +160 -82
  68. data/testdata/mybook/lib/ruby/review-webmaker.rb +20 -5
  69. data/testdata/mybook/lib/tasks/review.rake +14 -0
  70. data/testdata/mybook/lib/tasks/starter.rake +148 -4
  71. data/testdata/mybook/sty/indexstyle.ist +25 -0
  72. data/testdata/mybook/sty/mytextsize.sty +29 -0
  73. data/testdata/mybook/sty/mytitlepage.sty +34 -11
  74. data/testdata/mybook/sty/review-base.sty +276 -0
  75. data/testdata/mybook/sty/starter-codeblock.sty +237 -106
  76. data/testdata/mybook/sty/starter-color.sty +72 -17
  77. data/testdata/mybook/sty/starter-heading.sty +60 -13
  78. data/testdata/mybook/sty/starter-misc.sty +894 -0
  79. data/testdata/mybook/sty/starter-note.sty +67 -14
  80. data/testdata/mybook/sty/starter-talklist.sty +105 -0
  81. data/testdata/mybook/sty/starter-util.sty +35 -0
  82. data/testdata/mybook/sty/starter.sty +8 -526
  83. metadata +80 -4
@@ -14,13 +14,19 @@ module ReVIEW
14
14
 
15
15
  class LATEXBuilder
16
16
 
17
+ public :print, :puts
18
+
19
+ def target_name
20
+ "latex"
21
+ end
22
+
17
23
  ## 章や節や項や目のタイトル
18
24
  def headline(level, label, caption)
19
25
  with_context(:headline) do
20
26
  _, anchor = headline_prefix(level)
21
27
  headname = _headline_name(level) # 'chapter', 'section', 'subsection', ...
22
28
  headstar = _headline_star(level) # '*' or nil
23
- blank unless @output.pos == 0
29
+ blank() unless @output.pos == 0
24
30
  with_context(:caption) do
25
31
  print macro("#{headname}#{headstar}", compile_inline(caption))
26
32
  print "\n" unless level >= 5 # \paragraphと\subpararaphでは改行しない
@@ -31,7 +37,12 @@ module ReVIEW
31
37
  puts "\\ifx\\Chapter\\undefined{\\addcontentsline{toc}{#{headname}}{#{compile_inline(caption)}}}\\fi"
32
38
  end
33
39
  if _headline_chapter?(level)
34
- puts macro('label', chapter_label)
40
+ ## \Chapter直後の\addvspaceが効くように、
41
+ ## \lastskipをいったん保存し、\labelのあとで復元する。
42
+ puts "\\keeplastskip{"
43
+ puts " \\label{#{chapter_label()}}"
44
+ puts " \\par\\nobreak"
45
+ puts "}"
35
46
  elsif level >= 5 # 段(Paragraph)と小段(Subparagraph)では
36
47
  nil # 何もしない、\labelもつけない
37
48
  else
@@ -72,7 +83,7 @@ module ReVIEW
72
83
  public
73
84
 
74
85
  def nonum_begin(level, _label, caption)
75
- blank unless @output.pos == 0
86
+ blank() unless @output.pos == 0
76
87
  with_context(:headline) do
77
88
  with_context(:caption) do
78
89
  puts macro(HEADLINE[level] + '*', compile_inline(caption))
@@ -84,7 +95,7 @@ module ReVIEW
84
95
  end
85
96
 
86
97
  def notoc_begin(level, _label, caption)
87
- blank unless @output.pos == 0
98
+ blank() unless @output.pos == 0
88
99
  with_context(:headline) do
89
100
  with_context(:caption) do
90
101
  puts "\\ifx\\Chapter\\undefined"
@@ -96,18 +107,51 @@ module ReVIEW
96
107
  end
97
108
  end
98
109
 
99
- ## テーブルヘッダー
100
- ## (TODO: 第3引数にpos=htbpを指定)
101
- def table_header(id, caption)
102
- if caption.present?
110
+ ## テーブル
111
+ def table(lines, id=nil, caption=nil, option=nil)
112
+ super
113
+ end
114
+
115
+ def table_header(id, caption, options)
116
+ if id.present? || caption.present?
103
117
  @table_caption = true
118
+ pos = options[:pos] || 'h'
104
119
  star = id.present? ? '' : '*'
105
- s = with_context(:caption) { compile_inline(caption) }
106
- puts "\\begin{table}[h]%%#{id}"
120
+ s = with_context(:caption) { compile_inline(caption || '') }
121
+ puts "\\begin{table}[#{pos}]%%#{id}"
107
122
  puts "\\centering%"
108
123
  puts macro("reviewtablecaption#{star}", s)
109
124
  end
110
- puts macro('label', table_label(id)) if id.present?
125
+ begin
126
+ puts macro('label', table_label(id)) if id.present?
127
+ rescue KeyError
128
+ error "no such table: #{id}"
129
+ end
130
+ end
131
+
132
+ alias __original_table_begin table_begin
133
+
134
+ def table_begin(ncols, fontsize: nil)
135
+ if fontsize
136
+ font = FONTSIZES[fontsize]
137
+ puts "\\def\\startertablefont{\\#{font}}" if font
138
+ end
139
+ __original_table_begin(ncols)
140
+ end
141
+
142
+ ## CSVテーブル
143
+ def _table_hline()
144
+ "\\hline"
145
+ end
146
+
147
+ def _table_bottom(hline: false)
148
+ puts "\\hline" if hline
149
+ end
150
+
151
+ def _table_tr(cells, hline: false)
152
+ s = "#{cells.join(' & ')} \\\\"
153
+ s << " \\hline" if hline
154
+ return s
111
155
  end
112
156
 
113
157
  ## 改行命令「\\」のあとに改行文字「\n」を置かない。
@@ -131,18 +175,10 @@ module ReVIEW
131
175
  "\\\\[0pt]" # これなら後続行の先頭に1/4空白が入らない
132
176
  end
133
177
 
178
+ protected
134
179
 
135
- ## コードブロック(//program, //terminal)
136
-
137
- def program(lines, id=nil, caption=nil, optionstr=nil)
138
- _codeblock('program', lines, id, caption, optionstr)
139
- end
140
-
141
- def terminal(lines, id=nil, caption=nil, optionstr=nil)
142
- _codeblock('terminal', lines, id, caption, optionstr)
143
- end
144
180
 
145
- protected
181
+ ## コードブロック(//program, //terminal, //output)
146
182
 
147
183
  FONTSIZES = {
148
184
  "small" => "small",
@@ -151,125 +187,226 @@ module ReVIEW
151
187
  "large" => "large",
152
188
  "x-large" => "Large",
153
189
  "xx-large" => "LARGE",
190
+ "medium" => "normalsize",
154
191
  }
155
192
 
156
- ## コードブロック(//program, //terminal)
157
- def _codeblock(blockname, lines, id, caption, optionstr)
158
- ## ブロックコマンドのオプション引数はCompilerクラスでパースすべき。
159
- ## しかしCompilerクラスがそのような設計になってないので、
160
- ## 仕方ないのでBuilderクラスでパースする。
161
- opts = _parse_codeblock_optionstr(optionstr, blockname)
162
- CODEBLOCK_OPTIONS.each {|k, v| opts[k] = v unless opts.key?(k) }
163
- #
193
+ def _codeblock_eolmark()
194
+ "{\\startereolmark}"
195
+ end
196
+
197
+ def _codeblock_indentmark()
198
+ "{\\starterindentchar}"
199
+ end
200
+
201
+ LATEX_ESCAPE_TABLE = {
202
+ '{' => '\{',
203
+ '}' => '\}',
204
+ '%' => '\%',
205
+ '$' => '\$',
206
+ '#' => '\#',
207
+ '&' => '\&',
208
+ '_' => '\_',
209
+ '\\' => '{\textbackslash}',
210
+ '^' => '{\textasciicircum}',
211
+ '~' => '{\textasciitilde}',
212
+ }
213
+
214
+ def _render_codeblock(blockname, lines, id, caption_str, opts)
164
215
  if opts['eolmark']
165
- lines = lines.map {|line| "#{detab(line)}\\startereolmark{}" }
216
+ eolmark = _codeblock_eolmark() # ex: '{\startereolmark}'
166
217
  else
167
- lines = lines.map {|line| detab(line) }
218
+ eolmark = nil
168
219
  end
169
220
  #
170
- if opts['indentwidth'] && opts['indentwidth'] > 0
171
- indent_w = opts['indentwidth']
172
- indent_str = " " * (indent_w - 1) + "{\\starterindentchar}"
173
- lines = lines.map {|line|
174
- line.sub(/\A( +)/) {
175
- m, n = ($1.length - 1).divmod indent_w
176
- " " << indent_str * m << " " * n
177
- }
221
+ eol = eolmark ? "#{eolmark}\\par\n" : "\\par\n"
222
+ lines = lines.map {|line|
223
+ line = _parse_inline(line) {|text|
224
+ _convert_and_escape_str(text)
178
225
  }
179
- end
226
+ if line =~ /\A\n/
227
+ "\\mbox{}#{eol}\n"
228
+ else
229
+ line.gsub(/(?:\\-)?\n/, eol)
230
+ end
231
+ }
180
232
  #
181
- if id.present? || caption.present?
182
- caption_str = _build_caption_str(id, caption)
183
- else
184
- caption_str = nil
233
+ indent_width = opts['indent']
234
+ if indent_width && indent_width > 0
235
+ lines = _add_indent_mark(lines, indent_width)
185
236
  end
186
237
  #
238
+ default_opts = _codeblock_default_options(blockname)
239
+ opts2 = {}
240
+ opts.each do |k, v|
241
+ opts2[k] = v unless v == default_opts[k]
242
+ end
187
243
  fontsize = FONTSIZES[opts['fontsize']]
188
- print "\\def\\startercodeblockfontsize{#{fontsize}}\n"
189
244
  #
190
245
  environ = "starter#{blockname}"
246
+ puts "\\begingroup"
247
+ puts " \\makeatletter" if fontsize
248
+ puts " \\def\\starter@#{blockname}@fontsize{#{fontsize}}" if fontsize
249
+ puts " \\makeatother" if fontsize
250
+ puts " \\makeatletter" unless opts2.empty?
251
+ opts2.each do |k, v|
252
+ x = k.gsub(/[^a-zA-Z]/, '_')
253
+ v = v == true ? 'Y' : v == false ? '' : v #escape(v.to_s)
254
+ puts " \\edef\\starter@#{blockname}@#{x}{#{v}}"
255
+ #puts " \\edef\\starter@codeblock@#{x}{#{v}}"
256
+ end
257
+ puts " \\makeatother" unless opts2.empty?
191
258
  print "\\begin{#{environ}}[#{id}]{#{caption_str}}"
192
259
  print "\\startersetfoldmark{}" unless opts['foldmark']
193
- if opts['eolmark']
194
- print "\\startereolmarkdark{}" if blockname == 'terminal'
195
- print "\\startereolmarklight{}" if blockname != 'terminal'
196
- end
197
260
  if opts['lineno']
198
261
  gen = LineNumberGenerator.new(opts['lineno'])
199
- width = opts['linenowidth']
262
+ width = opts['linenowidth'] || -1
200
263
  if width && width >= 0
201
264
  if width == 0
202
265
  last_lineno = gen.each.take(lines.length).compact.last
203
266
  width = last_lineno.to_s.length
204
267
  end
205
268
  print "\\startersetfoldindentwidth{#{'9'*(width+2)}}"
206
- format = "\\textcolor{gray}{%#{width}s:} "
269
+ format = "\\starterinnerlineno{%#{width}s:} "
207
270
  else
208
- format = "\\starterlineno{%s}"
271
+ format = "\\starterouterlineno{%s}"
209
272
  end
210
273
  buf = []
211
- opt_fold = opts['fold']
212
274
  lines.zip(gen).each do |x, n|
213
- buf << ( opt_fold \
214
- ? "#{format % n.to_s}\\seqsplit{#{x}}" \
215
- : "#{format % n.to_s}#{x}" )
275
+ buf << "#{(format % n.to_s).gsub(' ', '~')}#{x}"
216
276
  end
217
- print buf.join("\n")
277
+ print buf.join()
218
278
  else
219
- print "\\seqsplit{" if opts['fold']
220
- print lines.join("\n")
221
- print "}" if opts['fold']
279
+ print lines.join()
222
280
  end
223
281
  puts "\\end{#{environ}}"
282
+ puts "\\endgroup"
224
283
  nil
225
284
  end
226
285
 
286
+ def _convert_and_escape_str(str)
287
+ #str.gsub(/[\{\}\\\%\^\_\$\&\~]/, LATEX_ESCAPE_TABLE)
288
+ rexp = /[\{\}\\\#\%\^\_\$\&\~]/
289
+ cs = []
290
+ zenkaku_rexp = ZENKAKU_CHAR_REXP
291
+ str.each_char do |c|
292
+ cs << (
293
+ case c
294
+ when / / ; '~'
295
+ when /\n/ ; "\n"
296
+ when rexp ; LATEX_ESCAPE_TABLE[c]
297
+ when zenkaku_rexp ; "\\ZC{#{c}}" # 全角文字
298
+ else ; c # 半角文字
299
+ end
300
+ )
301
+ end
302
+ cs << "" unless cs[-1] == "\n"
303
+ return cs.join('\\-')
304
+ end
305
+
306
+ ZENKAKU_CHAR_REXP = /\A[^\000-\177]\z/
307
+
308
+ def _add_indent_mark(lines, indent_width)
309
+ space = "~\\-"
310
+ rexp = /\A((?:~\\-)+)/
311
+ #
312
+ width = indent_width
313
+ mark = _codeblock_indentmark() # ex: '{\starterindentmark}'
314
+ indent = space * (width - 1) + mark
315
+ nchar = space.length
316
+ return lines.map {|line|
317
+ line.sub(rexp) {
318
+ m, n = ($1.length / nchar - 1).divmod width
319
+ "#{space}#{indent * m}#{space * n}"
320
+ }
321
+ }
322
+ end
323
+
227
324
  public
228
325
 
229
326
  ## ・\caption{} のかわりに \reviewimagecaption{} を使うよう修正
230
327
  ## ・「scale=X」に加えて「pos=X」も受け付けるように拡張
231
- def image_image(id, caption, option_str)
232
- pos = nil; border = nil; arr = []
233
- _each_block_option(option_str) do |k, v|
234
- case k
235
- when 'pos'
236
- v =~ /\A[Hhtb]+\z/ or # H: Here, h: here, t: top, b: bottom
237
- raise "//image[][][pos=#{v}]: expected 'pos=H' or 'pos=h'."
238
- pos = v # detect 'pos=H' or 'pos=h'
239
- when 'border', 'draft'
240
- case v
241
- when nil ; flag = true
242
- when 'on' ; flag = true
243
- when 'off'; flag = false
244
- else
245
- raise "//image[][][#{k}=#{v}]: expected '#{k}=on' or '#{k}=off'"
246
- end
247
- border = flag if k == 'border'
248
- arr << "draft=#{flag}" if k == 'draft'
328
+ def _render_image(id, image_filepath, caption, opts)
329
+ width = "\\maxwidth"
330
+ if opts[:scale]
331
+ case (scale = opts[:scale])
332
+ when /\A\d+\z/, /\A\d\.\d*\z/, /\A\.\d+\z/
333
+ when /\A\d+(\.\d+)?%\z/
334
+ scale = scale.sub(/%\z/, '').to_f / 100.0
249
335
  else
250
- arr << (v.nil? ? k : "#{k}=#{v}")
336
+ error "scale=#{scale}: invalid scale value."
251
337
  end
338
+ width = "#{scale}\\maxwidth" # not '\textwidth'
252
339
  end
253
340
  #
254
- metrics = parse_metric('latex', arr.join(","))
255
- puts "\\begin{reviewimage}[#{pos}]%%#{id}" if pos
256
- puts "\\begin{reviewimage}%%#{id}" unless pos
257
- metrics = "width=\\maxwidth" unless metrics.present?
258
- puts "\\starterimageframe{%" if border
259
- puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}%"
260
- puts "}%" if border
341
+ if opts[:width]
342
+ case opts[:width]
343
+ when /\A\d+\z/, /\A\d\.\d*\z/, /\A\.\d+\z/
344
+ width = "#{opts[:width]}\\textwidth" # not '\maxwidth'
345
+ when /\A(\d+(\.\d+)?)%\z/
346
+ width = "#{$1.to_f/100.0}\\textwidth" # not '\maxwidth'
347
+ when /\A(\d+(?:\.\d*)?)(mm|cm)\z/
348
+ width = "#{$1}true#{$2}" # 'mm'->'truemm', 'cm'->'truecm'
349
+ else
350
+ width = opts[:width]
351
+ end
352
+ end
353
+ #
354
+ metrics = ["width=#{width}"]
355
+ metrics << "draft=#{opts[:draft]}" if opts[:draft] != nil
356
+ #
357
+ pos = opts[:pos] || config_starter()['image_position']
358
+ puts "\\begin{reviewimage}[#{pos}]%%#{id}"
359
+ puts "\\starterimageframe{%" if opts[:border]
360
+ puts "\\includegraphics[#{metrics.join(',')}]{#{image_filepath}}%"
361
+ puts "}%" if opts[:border]
261
362
  with_context(:caption) do
262
363
  #puts macro('caption', compile_inline(caption)) if caption.present? # original
263
- puts macro('reviewimagecaption', compile_inline(caption)) if caption.present?
364
+ puts "\\reviewimagecaption{#{compile_inline(caption)}}" if caption.present?
264
365
  end
265
- puts macro('label', image_label(id))
366
+ puts "\\label{#{image_label(id)}}"
266
367
  puts "\\end{reviewimage}"
267
368
  end
268
369
 
370
+ ## //imgtable
371
+ def imgtable(lines, id, caption=nil, option=nil)
372
+ super
373
+ end
374
+
375
+ def _render_imgtable(id, caption, opts)
376
+ pos = opts['pos'] || 'h'
377
+ puts "\\begin{table}[#{pos}]%%#{id}"
378
+ puts "\\centering"
379
+ yield
380
+ puts "\\end{table}"
381
+ blank()
382
+ end
383
+
384
+ def _render_imgtable_caption(caption)
385
+ puts macro('reviewimgtablecaption', compile_inline(caption))
386
+ end
387
+
388
+ def _render_imgtable_label(id)
389
+ puts macro('label', table_label(id))
390
+ rescue ReVIEW::KeyError
391
+ error "no such table: #{id}"
392
+ end
393
+
394
+ def imgtable_image(id, _caption, metric)
395
+ metrics = parse_metric('latex', metric)
396
+ # image is always bound here
397
+ #puts "\\begin{reviewimage}%%#{id}" #-
398
+ metrics = "width=\\maxwidth" unless metrics.present?
399
+ imagefile = @chapter.image(id).path
400
+ puts "\\includegraphics[#{metrics}]{#{imagefile}}"
401
+ #puts '\end{reviewimage}' #-
402
+ end
403
+
404
+ ##
405
+
269
406
  def _build_secref(chap, num, title, parent_title)
270
407
  s = ""
271
408
  ## 親セクションのタイトルがあれば使う
272
- if parent_title && @book.config['starter']['secref_parenttitle']
409
+ if parent_title && self.config_starter['secref_parenttitle']
273
410
  s << "「%s」内の" % parent_title # TODO: I18n化
274
411
  end
275
412
  ## 対象セクションへのリンクを作成する
@@ -294,17 +431,17 @@ module ReVIEW
294
431
  public
295
432
 
296
433
  def ul_begin
297
- blank
434
+ blank()
298
435
  puts '\begin{starteritemize}' # instead of 'itemize'
299
436
  end
300
437
 
301
438
  def ul_end
302
439
  puts '\end{starteritemize}' # instead of 'itemize'
303
- blank
440
+ blank()
304
441
  end
305
442
 
306
443
  def ol_begin(start_num=nil)
307
- blank
444
+ blank()
308
445
  puts '\begin{starterenumerate}' # instead of 'enumerate'
309
446
  if start_num.nil?
310
447
  return true unless @ol_num
@@ -315,7 +452,7 @@ module ReVIEW
315
452
 
316
453
  def ol_end
317
454
  puts '\end{starterenumerate}' # instead of 'enumerate'
318
- blank
455
+ blank()
319
456
  end
320
457
 
321
458
  def ol_item_begin(lines, num)
@@ -327,10 +464,20 @@ module ReVIEW
327
464
  def ol_item_end()
328
465
  end
329
466
 
467
+ def dt(str)
468
+ puts "\\starterdt{#{str}}%"
469
+ end
470
+
471
+ def dl_dd_begin()
472
+ end
473
+
474
+ def dl_dd_end()
475
+ end
476
+
330
477
  ## コラム
331
478
 
332
479
  def column_begin(level, label, caption)
333
- blank
480
+ blank()
334
481
  @doc_status[:column] = true
335
482
  puts "\\begin{reviewcolumn}\n"
336
483
  puts "\\phantomsection % for hyperref" #+
@@ -367,12 +514,85 @@ module ReVIEW
367
514
  ## (導入文 //lead{ ... //} と似ているが、導入文では詩や物語を
368
515
  ## 引用するのが普通らしく、概要 (abstract) とは違うみたいなので、
369
516
  ## 概要を表すブロックを用意した。)
370
- def abstract(lines)
517
+ def on_abstract_block()
371
518
  puts '\begin{starterabstract}'
372
- puts lines
519
+ yield
373
520
  puts '\end{starterabstract}'
374
521
  end
375
522
 
523
+ ## 章 (Chapter) の著者
524
+ def on_chapterauthor_block(name)
525
+ puts "\\starterchapterauthor{#{escape(name)}}"
526
+ end
527
+
528
+ ## 会話リスト
529
+ def _render_talklist(opts, &b)
530
+ puts '\begingroup'
531
+ puts ' \makeatletter' unless opts.empty?
532
+ opts.each do |k, v|
533
+ puts " \\def\\starter@talklist@#{k}{#{v}}"
534
+ end
535
+ puts ' \makeatother' unless opts.empty?
536
+ puts '\begin{startertalklist}'
537
+ yield
538
+ puts '\end{startertalklist}'
539
+ puts '\endgroup'
540
+ end
541
+
542
+ ## 会話項目
543
+ def _render_talk(image_filepath=nil, name=nil, &b)
544
+ s = name ? compile_inline(name) : nil
545
+ print "\\startertalk{#{image_filepath}}{#{s}}{%"
546
+ yield
547
+ @blank_needed = false
548
+ puts "}"
549
+ end
550
+
551
+ ## キーと説明文のリスト
552
+ def _render_desclist(opts, &b)
553
+ bkup = @_desclist_opts
554
+ @_desclist_opts = opts
555
+ star = opts[:compact] ? '*' : ''
556
+ puts "\\begingroup"
557
+ puts " \\makeatletter" unless opts.empty?
558
+ opts.each do |k, v|
559
+ k_ = k.to_s.gsub('_', '@')
560
+ v_ = v == true ? 'Y' : v == false ? '' : escape(v.to_s)
561
+ puts " \\def\\starter@desclist@#{k_}{#{v_}}"
562
+ end
563
+ puts " \\makeatother" unless opts.empty?
564
+ puts "\\begin{starterdesclist}"
565
+ yield
566
+ puts "\\end{starterdesclist}"
567
+ puts "\\endgroup"
568
+ @_desclist_opts = bkup
569
+ end
570
+
571
+ ## キーと説明文
572
+ def on_desc_block(key, text=nil, &b)
573
+ text = "\n" + text if text
574
+ super(key, text, &b)
575
+ end
576
+ def _render_desc(key, &b)
577
+ opts = @_desclist_opts
578
+ s = compile_inline(key)
579
+ #print "\\begin{starterdesc}{#{s}}"
580
+ #print "\\begin{starterdesc}{#{s}}%"
581
+ print "\\begin{starterdesc}{#{s}}\\ignorespaces "
582
+ yield
583
+ @blank_needed = false
584
+ puts "\\end{starterdesc}"
585
+ end
586
+
587
+ ## 縦方向の空きを入れる
588
+ def _render_vspace(size)
589
+ puts "\\vspace{#{size}}"
590
+ end
591
+
592
+ def _render_addvspace(size)
593
+ puts "\\addvspace{#{size}}"
594
+ end
595
+
376
596
  ## 章タイトルを独立したページに
377
597
  def makechaptitlepage(option=nil)
378
598
  case option
@@ -387,10 +607,8 @@ module ReVIEW
387
607
  end
388
608
 
389
609
  ## 縦方向のスペースがなければ改ページ
390
- def needvspace(builder_name, height)
391
- if builder_name == 'latex'
392
- puts "\\needvspace{#{height}}"
393
- end
610
+ def _render_needvspace(height)
611
+ puts "\\needvspace{#{height}}"
394
612
  end
395
613
 
396
614
  ## 段(Paragraph)の終わりにスペースを入れる
@@ -428,11 +646,13 @@ module ReVIEW
428
646
  ## ノート(//note[caption]{ ... //})
429
647
  ## (入れ子対応なので、中に箇条書きや別のブロックを入れられる)
430
648
  def on_note_block(label=nil, caption=nil)
431
- caption, label = label, nil if caption.nil?
432
- s = compile_inline(caption || "")
433
- puts "\\begin{starternote}[#{label}]{#{s}}"
434
- yield
435
- puts "\\end{starternote}"
649
+ with_context(:minicolumn) do
650
+ caption, label = label, nil if caption.nil?
651
+ s = compile_inline(caption || "")
652
+ puts "\\begin{starternote}[#{label}]{#{s}}"
653
+ yield
654
+ puts "\\end{starternote}"
655
+ end
436
656
  end
437
657
  def note(lines, label=nil, caption=nil)
438
658
  on_note_block(label, caption) do
@@ -476,7 +696,7 @@ module ReVIEW
476
696
  arr = []
477
697
  arr << lang if lang
478
698
  if lineno_flag
479
- first_line_num = line_num
699
+ first_line_num = line_num()
480
700
  arr << "lineno=#{first_line_num}"
481
701
  arr << "linenowidth=0"
482
702
  end
@@ -484,24 +704,12 @@ module ReVIEW
484
704
  end
485
705
  private :_codeblock_optstr
486
706
 
487
-
488
- ## 入れ子可能なブロック命令
489
-
490
- def on_minicolumn(type, caption, &b)
491
- s = with_context(:caption) { compile_inline(caption) }
492
- puts "\\begin{starter#{type}}{#{s}}"
493
- yield
494
- puts "\\end{starter#{type}}\n"
495
- end
496
- protected :on_minicolumn
497
-
498
- def on_sideimage_block(imagefile, imagewidth, option_str=nil, &b)
499
- imagefile, imagewidth, opts = validate_sideimage_args(imagefile, imagewidth, option_str)
500
- filepath = find_image_filepath(imagefile)
501
- side = opts['side'] || 'L'
707
+ ## 画像横に文章
708
+ def _render_sideimage(filepath, imagewidth, opts, &b)
709
+ side = opts['side'] || 'L'
502
710
  normalize = proc {|s|
503
- s =~ /\A(\d+(?:\.\d+)?)(%|mm|cm)\z/
504
- if $2.nil? ; s
711
+ rexp = /\A(\d+(?:\.\d+)?)(%|mm|cm)\z/
712
+ if s !~ rexp ; s
505
713
  elsif $2 == '%' ; "#{$1.to_f/100.0}\\textwidth"
506
714
  else ; "#{$1}true#{$2}"
507
715
  end
@@ -517,6 +725,20 @@ module ReVIEW
517
725
  puts "}\n"
518
726
  end
519
727
 
728
+ ## 入れ子可能なブロック命令
729
+
730
+ def on_minicolumn(type, caption=nil, &b)
731
+ with_context(:minicolumn) do
732
+ s = caption ? with_context(:caption) { compile_inline(caption) } : nil
733
+ puts "\\begin{starter#{type}}{#{s}}"
734
+ yield
735
+ puts "\\end{starter#{type}}\n"
736
+ end
737
+ end
738
+ protected :on_minicolumn
739
+
740
+ ## 数式
741
+
520
742
  def texequation(lines, label=nil, caption=nil)
521
743
  blank()
522
744
  #
@@ -561,6 +783,11 @@ module ReVIEW
561
783
 
562
784
  #### インライン命令
563
785
 
786
+ ## 改段落(箇条書き内では空行を入れられないため)
787
+ def inline_par(arg)
788
+ "\\starterpar{#{arg}}"
789
+ end
790
+
564
791
  ## ファイル名
565
792
  def inline_file(str)
566
793
  on_inline_file { escape(str) }
@@ -574,11 +801,11 @@ module ReVIEW
574
801
  on_inline_userinput { escape(str) }
575
802
  end
576
803
  def on_inline_userinput
577
- if within_codeblock?()
578
- "{\\starteruserinput{\\seqsplit{#{yield}}}}"
579
- else
804
+ #if within_codeblock?()
805
+ # "{\\starteruserinput{\\seqsplit{#{yield}}}}"
806
+ #else
580
807
  "\\starteruserinput{#{yield}}"
581
- end
808
+ #end
582
809
  end
583
810
 
584
811
  ## 引数をそのまま表示
@@ -597,11 +824,11 @@ module ReVIEW
597
824
  on_inline_weak { escape(str) }
598
825
  end
599
826
  def on_inline_weak
600
- if within_codeblock?()
601
- "{\\starterweak{\\seqsplit{#{yield}}}}"
602
- else
827
+ #if within_codeblock?()
828
+ # "{\\starterweak{\\seqsplit{#{yield}}}}"
829
+ #else
603
830
  "\\starterweak{#{yield}}"
604
- end
831
+ #end
605
832
  end
606
833
 
607
834
  ## 文字を小さくする
@@ -672,19 +899,17 @@ module ReVIEW
672
899
  def on_inline_balloon(); "{\\reviewballoon{#{yield}}}" ; end
673
900
 
674
901
  def on_inline_tt()
675
- ## LaTeXでは、'\texttt{}' 中の '!?:.' の直後の空白が2文字分で表示される。
676
- ## その問題を回避するために、' ' を '\ ' にする。
677
- s = yield
678
- s = s.gsub(/([!?:.]) /, '\\1\\ ')
679
- return "{\\reviewtt{#{s}}}"
902
+ return "{\\reviewtt{#{yield}}}"
680
903
  end
681
904
 
682
905
  def on_inline_code()
683
906
  with_context(:inline_code) {
684
- ## LaTeXでは、'\texttt{}' 中の '!?:.' の直後の空白が2文字分で表示される。
685
- ## その問題を回避するために、' ' を '\ ' にする。
907
+ ## 連続した空白が1つの空白として扱われるのを防ぐ
686
908
  s = yield
687
- s = s.gsub(/([!?:.]) /, '\\1\\ ')
909
+ s = s.gsub(/ /, '{\\starterspacechar}')
910
+ ## 連続した「`」や「'」はエスケープする必要がある
911
+ s = s.gsub(/\'\'/, "{'}{'}")
912
+ s = s.gsub(/\`\`/, '{`}{`}')
688
913
  ## コンテキストによって、背景色をつけないことがある
689
914
  if false
690
915
  elsif within_context?(:headline) # 章タイトルや節タイトルでは
@@ -719,35 +944,43 @@ module ReVIEW
719
944
  if within_codeblock?()
720
945
  #"\\reviewstrike{#{yield}}" # \seqsplit{} 内でエラーになる
721
946
  #"{\\reviewstrike{#{yield}}}" # \seqsplit{} 内でもエラーにならないが折り返しされない
722
- "{\\reviewstrike{\\seqsplit{#{yield}}}}" # エラーにならないし、折り返しもされる
947
+ #"{\\reviewstrike{\\seqsplit{#{yield}}}}" # エラーにならないし、折り返しもされる
948
+ "{\\reviewstrike{#{yield}}}"
723
949
  else
724
950
  macro('reviewstrike', yield)
725
951
  end
726
952
  end
727
953
 
728
954
  def build_inline_href(url, escaped_label) # compile_href()をベースに改造
729
- flag_footnote = @book.config['starter']['linkurl_footnote']
955
+ flag_footnote = self.config_starter['linkurl_footnote']
730
956
  return _inline_hyperlink(url, escaped_label, flag_footnote)
731
957
  end
732
958
 
733
959
  ## @<href>{} の代わり
734
960
  def inline_hlink(str)
735
961
  url, label = str.split(/, /, 2)
736
- flag_footnote = @book.config['starter']['hyperlink_footnote']
737
- return _inline_hyperlink(url, escape(label), flag_footnote)
962
+ flag_footnote = self.config_starter['hyperlink_footnote']
963
+ label_ = label.present? ? escape(label) : nil
964
+ return _inline_hyperlink(url, label_, flag_footnote)
738
965
  end
739
966
 
740
967
  def _inline_hyperlink(url, escaped_label, flag_footnote)
741
968
  if /\A[a-z]+:/ !~ url
742
969
  "\\ref{#{url}}"
743
970
  elsif ! escaped_label.present?
744
- "\\url{#{escape_url(url)}}"
971
+ #"\\url{#{escape_url(url)}}"
972
+ "\\starterurl{#{escape_url(url)}}{#{escape(url)}}"
745
973
  elsif ! flag_footnote
746
974
  "\\href{#{escape_url(url)}}{#{escaped_label}}"
747
975
  elsif within_context?(:footnote)
748
- "#{escaped_label}(\\url{#{escape_url(url)}})"
976
+ #"#{escaped_label}(\\url{#{escape_url(url)}})"
977
+ "#{escaped_label}(\\starterurl{#{escape_url(url)}}{#{escape(url)}})"
749
978
  else
750
- "#{escaped_label}\\footnote{\\url{#{escape_url(url)}}}"
979
+ #"#{escaped_label}\\footnote{\\url{#{escape_url(url)}}}"
980
+ #"#{escaped_label}\\footnote{\\starterurl{#{escape_url(url)}}{#{escape(url)}}}"
981
+ url1 = escape_url(url)
982
+ url2 = escape(url)
983
+ "\\href{#{url1}}{#{escaped_label}}\\footnote{\\starterurl{#{url1}}{#{url2}}}"
751
984
  end
752
985
  end
753
986
  private :_inline_hyperlink
@@ -776,6 +1009,56 @@ module ReVIEW
776
1009
  escape("#{I18n.t('equation')}#{chapter.number}.#{number}")
777
1010
  end
778
1011
 
1012
+ public
1013
+
1014
+ ## 数式を $...$ から \(...\) に変更する。
1015
+ ## これで //list の中でも @<m>$...$ が使えるようになる。
1016
+ ## ただし '^' や '_' がエスケープされるので、実用性はイマイチ。
1017
+ def inline_m(str)
1018
+ #" $#{str}$ " # original
1019
+ "\\(#{str}\\)"
1020
+ end
1021
+
1022
+ ## 「``」と「''」で囲む
1023
+ def on_inline_qq()
1024
+ "``#{yield}''"
1025
+ end
1026
+
1027
+ ## 索引に載せる語句 (@<idx>{}, @<term>{})
1028
+ def inline_idx(str)
1029
+ s1, s2 = _compile_term(str)
1030
+ "#{s1}\\index{#{s2}}"
1031
+ end
1032
+ def inline_hidx(str)
1033
+ _, s2 = _compile_term(str)
1034
+ "\\index{#{s2}}"
1035
+ end
1036
+ def inline_term(str)
1037
+ s1, s2 = _compile_term(str)
1038
+ "\\starterterm{#{s1}}\\index{#{s2}}"
1039
+ end
1040
+ def on_inline_termnoidx()
1041
+ "\\starterterm{#{yield}}"
1042
+ end
1043
+
1044
+ def _compile_term(str)
1045
+ arr = []
1046
+ placeholder = "\\starterindexplaceholder{}"
1047
+ display_str, see = parse_term(str, placeholder) do |term, term_e, yomi|
1048
+ if yomi
1049
+ arr << "#{escape_index(escape_latex(yomi))}@#{term_e}"
1050
+ elsif escape_index(term) != term_e
1051
+ arr << "#{escape_index(term)}@#{term_e}"
1052
+ else
1053
+ arr << term_e
1054
+ end
1055
+ end
1056
+ argstr = arr.join('!')
1057
+ argstr += "|see{#{escape_latex(see)}}" if see
1058
+ return display_str, argstr
1059
+ end
1060
+ private :_compile_term
1061
+
779
1062
  end
780
1063
 
781
1064