1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
|
# frozen_string_literal: true
require_relative 'spec_helper'
describe 'Asciidoctor::PDF::Converter - Font' do
context 'bundled with default themes' do
it 'should not apply fallback font when using default theme', visual: true do
input_file = Pathname.new fixture_file 'i18n-font-test.adoc'
to_file = to_pdf_file input_file, 'font-i18n-default.pdf'
(expect to_file).to visually_match 'font-i18n-default.pdf'
end
# intentionally use the deprecated alias for this test
it 'should apply fallback font when using default theme with fallback font', visual: true do
input_file = Pathname.new fixture_file 'i18n-font-test.adoc'
(expect do
to_file = to_pdf_file input_file, 'font-i18n-default-with-fallback.pdf', attribute_overrides: { 'pdf-theme' => 'default-with-fallback-font' }
(expect to_file).to visually_match 'font-i18n-default-with-fallback.pdf'
end).to log_message using_log_level: :INFO
end
it 'should include expected glyphs in bundled default font', visual: true do
input_file = Pathname.new fixture_file 'glyph-font-test.adoc'
to_file = to_pdf_file input_file, 'font-glyph-default.pdf'
(expect to_file).to visually_match 'font-glyph-default.pdf'
end
it 'should include expected glyphs in bundled default font with fallback font', visual: true do
input_file = Pathname.new fixture_file 'glyph-font-test.adoc'
to_file = to_pdf_file input_file, 'font-glyph-default-with-fallback.pdf', attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }
(expect to_file).to visually_match 'font-glyph-default-with-fallback.pdf'
end
it 'should include expected glyphs in fallback font', visual: true do
input_file = Pathname.new fixture_file 'glyph-font-test.adoc'
to_file = to_pdf_file input_file, 'font-glyph-fallback-only.pdf', pdf_theme: { extends: 'default-with-font-fallbacks', base_font_family: 'M+ 1p Fallback' }, attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }
(expect to_file).to visually_match 'font-glyph-fallback-only.pdf'
end
it 'should use notdef from original font of glyph not found in any fallback font', visual: true do
input = ?\u0278 * 10
to_file = to_pdf_file input, 'font-notdef-glyph.pdf', attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }
(expect to_file).to visually_match 'font-notdef-glyph.pdf'
end
it 'should use glyph from fallback font if not present in primary font', visual: true do
to_file = to_pdf_file '*を*', 'font-fallback-font.pdf', attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }
(expect to_file).to visually_match 'font-fallback-font.pdf'
end
it 'should use black glyph from fallback font if not present in primary font and theme is default-for-print-with-fallback-font', visual: true do
to_file = to_pdf_file '*を*', 'font-fallback-font-for-print.pdf', attribute_overrides: { 'pdf-theme' => 'default-for-print-with-fallback-font' }
(expect to_file).to visually_match 'font-fallback-font-for-print.pdf'
end
it 'should look for glyph in font for the specified font style when fallback font is enabled' do
pdf_theme = {
extends: 'default',
font_catalog: {
'Noto Serif' => {
'normal' => 'notoemoji-subset.ttf',
'bold' => 'notoserif-bold-subset.ttf',
},
'M+ 1p Fallback' => {
'normal' => 'mplus1p-regular-fallback.ttf',
'bold' => 'mplus1p-regular-fallback.ttf',
},
},
font_fallbacks: ['M+ 1p Fallback'],
}
pdf = to_pdf %(**\u03a9**), analyze: true, pdf_theme: pdf_theme
text = (pdf.find_text ?\u03a9)[0]
(expect text).not_to be_nil
(expect text[:font_name]).to eql 'NotoSerif-Bold'
end
it 'should include box drawing glyphs in bundled monospace font', visual: true do
input_file = Pathname.new fixture_file 'box-drawing.adoc'
to_file = to_pdf_file input_file, 'font-box-drawing.pdf'
(expect to_file).to visually_match 'font-box-drawing.pdf'
end
it 'should render emoji when using default theme with fallback font', visual: true do
to_file = to_pdf_file <<~'END', 'font-emoji.pdf', attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }
Don't 😢 over spilled 🍺.
Asciidoctor is 👍.
END
(expect to_file).to visually_match 'font-emoji.pdf'
end
it 'should use sans base font when using sans theme with fallback font', visual: true do
to_file = to_pdf_file <<~'END', 'font-sans-emoji.pdf', attribute_overrides: { 'pdf-theme' => 'default-sans-with-font-fallbacks' }
== Lessons
Don't 😢 over spilled 🍺.
Asciidoctor is 👍.
END
(expect to_file).to visually_match 'font-sans-emoji.pdf'
end
it 'should log warning once per character not found in any font when fallback font is used and verbose mode is enabled' do
(expect do
input_lines = [%(Bitcoin (\u20bf) is a cryptocurrency.), %(The currency is represented using the symbol \u20bf.)]
input = input_lines.join %(\n\n)
pdf = to_pdf input, attribute_overrides: { 'pdf-theme' => 'default-with-font-fallbacks' }, analyze: true
(expect pdf.lines).to eql input_lines
end).to log_message severity: :WARN, message: %(Could not locate the character `\u20bf' (\\u20bf) in the following fonts: Noto Serif, M+ 1p Fallback, Noto Emoji), using_log_level: :INFO
end
end
context 'built-in (AFM)' do
it 'should warn if document contains glyph not supported by AFM font' do
[true, false].each do |in_block|
(expect do
input = 'α to ω'
input = %(====\n#{input}\n====) if in_block
pdf = to_pdf input, analyze: true, attribute_overrides: { 'pdf-theme' => 'base' }
not_glyph = ?\u00ac
text = pdf.text
(expect text).to have_size 1
(expect text[0][:string]).to eql %(#{not_glyph} to #{not_glyph})
end).to log_message severity: :WARN, message: %(The following text could not be fully converted to the Windows-1252 character set:\n| α to ω), using_log_level: :INFO
end
end
it 'should replace essential characters with suitable replacements to avoid warnings' do
(expect do
pdf = to_pdf <<~'END', pdf_theme: { base_font_family: 'Helvetica' }, analyze: true
:experimental:
* disc
** circle
*** square
no{zwsp}space
button:[Save]
END
(expect pdf.find_text font_name: 'Helvetica').to have_size pdf.text.size
(expect pdf.lines).to eql [%(\u2022 disc), '- circle', %(\u00b7 square), 'nospace', 'button:[Save]']
end).to not_log_message
end
end
context 'OTF' do
it 'should allow theme to specify an OTF font', visual: true do
to_file = to_pdf_file <<~'END', 'font-otf.pdf', enable_footer: true, attribute_overrides: { 'pdf-theme' => (fixture_file 'otf-theme.yml'), 'pdf-fontsdir' => fixtures_dir }
== OTF
You're looking at an OTF font!
END
(expect to_file).to visually_match 'font-otf.pdf'
end
end
context 'custom' do
it 'should resolve fonts in specified fonts dir' do
pdf = to_pdf 'content', attribute_overrides: { 'pdf-theme' => (fixture_file 'bundled-fonts-theme.yml'), 'pdf-fontsdir' => Asciidoctor::PDF::ThemeLoader::FontsDir }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
it 'should look for font file in all specified font dirs' do
%w(; ,).each do |separator|
pdf = to_pdf 'content', attribute_overrides: { 'pdf-theme' => (fixture_file 'bundled-fonts-theme.yml'), 'pdf-fontsdir' => ([fixtures_dir, Asciidoctor::PDF::ThemeLoader::FontsDir].join separator) }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
end
it 'should look for font file in pwd if path entry is .' do
Dir.chdir fixtures_dir do
pdf = to_pdf 'content', attribute_overrides: { 'pdf-theme' => './otf-theme.yml', 'pdf-fontsdir' => ([examples_dir, '.'].join ';') }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+Quicksand-Regular'
end
end
it 'should look for font file in gem fonts dir if path entry is empty' do
pdf = to_pdf 'content', attribute_overrides: { 'pdf-theme' => (fixture_file 'bundled-fonts-theme.yml'), 'pdf-fontsdir' => ([fixtures_dir, ''].join ';') }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
it 'should look for font file in gem fonts dir if path entry includes GEM_FONTS_DIR' do
pdf = to_pdf 'content', attribute_overrides: { 'pdf-theme' => (fixture_file 'bundled-fonts-theme.yml'), 'pdf-fontsdir' => ([fixtures_dir, 'GEM_FONTS_DIR'].join ';') }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
it 'should allow built-in theme to be extended when using custom fonts dir' do
pdf = to_pdf %(content\n\n content), attribute_overrides: { 'pdf-theme' => (fixture_file 'custom-fonts-theme.yml'), 'pdf-fontsdir' => fixtures_dir }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 2
(expect fonts[0][:BaseFont]).to end_with '+mplus-1p-regular'
(expect fonts[1][:BaseFont]).to end_with '+mplus1mn-regular'
end
it 'should expand GEM_FONTS_DIR in theme file' do
pdf = to_pdf 'content'
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
it 'should expand GEM_FONTS_DIR in theme file when custom fonts dir is specified' do
pdf = to_pdf 'content', attribute_overrides: { 'pdf-fontsdir' => fixtures_dir }
fonts = pdf.objects.values.select {|it| Hash === it && it[:Type] == :Font }
(expect fonts).to have_size 1
(expect fonts[0][:BaseFont]).to end_with '+NotoSerif'
end
it 'should throw error if font with relative path cannot be found in GEM_FONTS_DIR' do
pdf_theme = {
font_catalog: {
'NoSuchFont' => {
'normal' => 'no-such-font.ttf',
},
},
}
expect { to_pdf 'content', pdf_theme: pdf_theme }.to raise_exception Errno::ENOENT, /no-such-font\.ttf not found in GEM_FONTS_DIR$/
end
it 'should throw error if font with relative path cannot be found in custom font dirs' do
%w(, ;).each do |separator|
pdf_theme = {
font_catalog: {
'NoSuchFont' => {
'normal' => 'no-such-font.ttf',
},
},
}
expect { to_pdf 'content', attribute_overrides: { 'pdf-fontsdir' => (%w(here there).join separator) }, pdf_theme: pdf_theme }.to raise_exception Errno::ENOENT, /no-such-font\.ttf not found in here or there$/
end
end
it 'should throw error if font with absolute path cannot be found in custom font dirs' do
pdf_theme = {
font_catalog: {
'NoSuchFont' => {
'normal' => (font_path = fixture_file 'no-such-font.ttf'),
},
},
}
expect { to_pdf 'content', pdf_theme: pdf_theme, 'pdf-fontsdir' => 'there' }.to raise_exception Errno::ENOENT, /#{Regexp.escape font_path} not found$/
end
it 'should throw error that reports font name and style when font is not registered' do
(expect do
to_pdf <<~'END', pdf_theme: { base_font_family: 'Lato' }
== Section Title
paragraph
END
end).to raise_exception Prawn::Errors::UnknownFont, 'Lato (normal) is not a known font.'
end
it 'should throw error that reports font name and style when style is not found for registered font' do
pdf_theme = {
font_catalog: {
'Quicksand' => {
'normal' => (fixture_file 'Quicksand-Regular.otf'),
},
},
base_font_family: 'Quicksand',
}
(expect do
to_pdf <<~'END', pdf_theme: pdf_theme
== Section Title
paragraph
END
end).to raise_exception Prawn::Errors::UnknownFont, 'Quicksand (bold) is not a known font.'
end
end
context 'Kerning' do
it 'should enable kerning when using default theme', visual: true do
to_file = to_pdf_file <<~'END', 'font-kerning-default.pdf'
[%hardbreaks]
AVA
Aya
WAWA
WeWork
DYI
END
(expect to_file).to visually_match 'font-kerning-default.pdf'
end
it 'should enable kerning when using base theme', visual: true do
to_file = to_pdf_file <<~'END', 'font-kerning-base.pdf', attribute_overrides: { 'pdf-theme' => 'base' }
[%hardbreaks]
AVA
Aya
WAWA
WeWork
DYI
END
(expect to_file).to visually_match 'font-kerning-base.pdf'
end
it 'should allow theme to disable kerning globally', visual: true do
to_file = to_pdf_file <<~'END', 'font-kerning-disabled.pdf', pdf_theme: { base_font_kerning: 'none' }
[%hardbreaks]
AVA
Aya
WAWA
WeWork
DYI
END
(expect to_file).to visually_match 'font-kerning-disabled.pdf'
end
it 'should allow theme to disable kerning per category' do
{
'example' => %([example]\nAV T. ij WA *guideline*),
'sidebar' => %([sidebar]\nAV T. ij WA *guideline*),
'heading' => '== AV T. ij WA *guideline*',
'table' => %(|===\n| AV T. ij WA *guideline*\n|===),
'table_head' => %([%header]\n|===\n| AV T. ij WA *guideline*\n|===),
'caption' => %(.AV T. ij WA *guideline*'\n|===\n|content\n|===),
}.each do |category, input|
pdf = to_pdf input, analyze: true
guideline_column_with_kerning = (pdf.find_text 'guideline')[0][:x]
pdf = to_pdf input, pdf_theme: { %(#{category}_font_kerning) => 'none' }, analyze: true
guideline_column_without_kerning = (pdf.find_text 'guideline')[0][:x]
(expect guideline_column_without_kerning).to be > guideline_column_with_kerning
end
end
end
context 'Line breaks' do
it 'should break line on any CJK character if value of scripts attribute is cjk' do
pdf = to_pdf <<~'END', analyze: true
:scripts: cjk
:pdf-theme: default-with-font-fallbacks
AsciiDoc 是一个人类可读的文件格式,语义上等同于 DocBook 的 XML,但使用纯文本标记了约定。可以使用任何文本编辑器创建文件把 AsciiDoc 和阅读“原样”,或呈现为HTML 或由 DocBook 的工具链支持的任何其他格式,如 PDF,TeX 的,Unix 的手册页,电子书,幻灯片演示等。
AsciiDoc は、意味的には DocBook XML のに相当するが、プレーン·テキスト·マークアップの規則を使用して、人間が読めるドキュメントフォーマット、である。 AsciiDoc は文書は、任意のテキストエディタを使用して作成され、「そのまま"または、HTML や DocBook のツールチェーンでサポートされている他のフォーマット、すなわち PDF、TeX の、Unix の man ページ、電子書籍、スライドプレゼンテーションなどにレンダリングすることができます。
END
lines = pdf.lines
(expect lines).to have_size 8
(expect lines[0]).to end_with '任何'
(expect lines[1]).to start_with '文本'
(expect lines[3]).to end_with '使用'
(expect lines[4]).to start_with 'して'
end
# intentionally use the deprecated alias for this test
it 'should not break line immediately before an ideographic full stop' do
pdf = to_pdf <<~'END', analyze: true
:scripts: cjk
:pdf-theme: default-with-fallback-font
Asciidoctor PDF 是一个 Asciidoctor 转换器,可将 AsciiDoc 文档转换为PDF文档。填料填料。转换器不会创建临时格式。
END
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[1]).to start_with '式。'
end
it 'should not break line where no-break hyphen is adjacent to formatted text' do
pdf = to_pdf <<~'END', analyze: true
foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar **foo**‑bar‑**foo**
END
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[1]).to eql %(foo\u2011bar\u2011foo)
end
# NOTE: this test demonstrates a bug in Prawn
it 'should break line if no-break hyphen is isolated into its own fragment' do
pdf = to_pdf <<~'END', analyze: true
foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar **foo**‑**bar**‑**foo**
END
lines = pdf.lines
(expect lines).to have_size 2
(expect lines[1]).to eql %(\u2011bar\u2011foo)
end
end
context 'Separators' do
it 'should not break line at location of no-break space' do
input = (%w(a b c d).reduce([]) {|accum, it| accum << (it * 20) }.join ' ') + ?\u00a0 + ('e' * 20)
pdf = to_pdf input, analyze: true
text = pdf.text
(expect text).to have_size 2
(expect text[0][:string]).to end_with 'c'
(expect text[1][:string]).to start_with 'd'
(expect text[1][:y]).to be < text[0][:y]
end
it 'should not break line at location of non-breaking hyphen' do
input = (%w(a b c d).reduce([]) {|accum, it| accum << (it * 20) }.join ' ') + ?\u2011 + ('e' * 20)
pdf = to_pdf input, analyze: true
text = pdf.text
(expect text).to have_size 2
(expect text[0][:string]).to end_with 'c'
(expect text[1][:string]).to start_with 'd'
(expect text[1][:y]).to be < text[0][:y]
end
it 'should use zero-width space a line break opportunity' do
input = (%w(a b c d e f).reduce([]) {|accum, it| accum << (it * 5) + ?\u200b + (it * 10) }.join ' ')
pdf = to_pdf input, analyze: true
text = pdf.text
(expect text).to have_size 2
(expect text[0][:string]).to eql 'aaaaaaaaaaaaaaa bbbbbbbbbbbbbbb ccccccccccccccc ddddddddddddddd eeeeeeeeeeeeeee fffff'
(expect text[1][:string]).to eql 'ffffffffff'
(expect text[1][:y]).to be < text[0][:y]
end
end
context 'font sizes' do
it 'should resolve font size of inline element specified in em units' do
pdf_theme = {
base_font_size: 12,
sidebar_font_size: 10,
link_font_size: '0.75em',
}
pdf = to_pdf <<~'END', pdf_theme: pdf_theme, analyze: true
****
Check out https://asciidoctor.org[Asciidoctor]'
****
END
normal_text = pdf.find_unique_text 'Check out '
(expect normal_text[:font_size].to_f).to eql 10.0
linked_text = pdf.find_unique_text 'Asciidoctor'
(expect linked_text[:font_size].to_f).to eql 7.5
end
it 'should use font size as is if value is less than 1' do
pdf_theme = {
base_font_size: 12,
sidebar_font_size: 10,
link_font_size: 0.75,
}
pdf = to_pdf <<~'END', pdf_theme: pdf_theme, analyze: true
****
Check out https://asciidoctor.org[Asciidoctor]'
****
END
normal_text = pdf.find_unique_text 'Check out '
(expect normal_text[:font_size].to_f).to eql 10.0
linked_text = pdf.find_unique_text 'Asciidoctor'
(expect linked_text[:font_size].to_f).to eql 0.75
end
it 'should resolve font size of inline element specified in rem units' do
pdf_theme = {
base_font_size: 12,
sidebar_font_size: 10,
link_font_size: '0.75rem',
}
pdf = to_pdf <<~'END', pdf_theme: pdf_theme, analyze: true
****
https://asciidoctor.org[Asciidoctor]
****
END
linked_text = pdf.find_unique_text 'Asciidoctor'
(expect linked_text[:font_size].to_f).to eql 9.0
end
it 'should resolve font size of inline element specified in percentage' do
pdf_theme = {
base_font_size: 12,
link_font_size: '75%',
}
pdf = to_pdf 'https://asciidoctor.org[Asciidoctor]', pdf_theme: pdf_theme, analyze: true
linked_text = pdf.find_unique_text 'Asciidoctor'
(expect linked_text[:font_size].to_f).to eql 9.0
end
it 'should resolve font size of inline element specified in points as a String' do
pdf_theme = {
base_font_size: 12,
link_font_size: '9',
}
pdf = to_pdf 'https://asciidoctor.org[Asciidoctor]', pdf_theme: pdf_theme, analyze: true
linked_text = pdf.find_unique_text 'Asciidoctor'
(expect linked_text[:font_size].to_f).to eql 9.0
end
end
end
|