asciidoctor-pdf 1.5.0.alpha.11 → 1.5.0.alpha.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +10 -0
- data/README.adoc +66 -5
- data/asciidoctor-pdf.gemspec +52 -0
- data/data/themes/default-theme.yml +3 -2
- data/docs/theming-guide.adoc +1402 -654
- data/lib/asciidoctor-pdf/converter.rb +139 -97
- data/lib/asciidoctor-pdf/formatted_text/formatter.rb +2 -2
- data/lib/asciidoctor-pdf/formatted_text/parser.rb +1 -1
- data/lib/asciidoctor-pdf/formatted_text/parser.treetop +2 -1
- data/lib/asciidoctor-pdf/formatted_text/transform.rb +12 -11
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +17 -3
- data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +3 -2
- data/lib/asciidoctor-pdf/sanitizer.rb +1 -1
- data/lib/asciidoctor-pdf/theme_loader.rb +22 -19
- data/lib/asciidoctor-pdf/version.rb +1 -1
- metadata +24 -19
- data/bin/optimize-pdf +0 -64
@@ -299,9 +299,9 @@ class Converter < ::Prawn::Document
|
|
299
299
|
end
|
300
300
|
|
301
301
|
def convert_section sect, opts = {}
|
302
|
-
|
303
|
-
theme_font :heading, level: heading_level do
|
302
|
+
theme_font :heading, level: (h_level = sect.level + 1) do
|
304
303
|
title = sect.numbered_title formal: true
|
304
|
+
align = (@theme[%(heading_h#{h_level}_align)] || @theme.heading_align || :left).to_sym
|
305
305
|
unless at_page_top?
|
306
306
|
if sect.chapter?
|
307
307
|
start_new_chapter sect
|
@@ -315,9 +315,9 @@ class Converter < ::Prawn::Document
|
|
315
315
|
sect.set_attr 'page_start', page_number
|
316
316
|
# NOTE auto-generate an anchor if one doesn't exist so TOC works
|
317
317
|
# QUESTION should we just assign the section this generated id?
|
318
|
-
sect.set_attr 'anchor', (sect_anchor = sect.id || %(
|
318
|
+
sect.set_attr 'anchor', (sect_anchor = sect.id || %(__autoid-#{page_number}-#{y.ceil}))
|
319
319
|
add_dest_for_block sect, sect_anchor
|
320
|
-
sect.chapter? ? (layout_chapter_title sect, title) : (layout_heading title)
|
320
|
+
sect.chapter? ? (layout_chapter_title sect, title, align: align) : (layout_heading title, align: align)
|
321
321
|
end
|
322
322
|
|
323
323
|
convert_content_for_block sect
|
@@ -327,8 +327,9 @@ class Converter < ::Prawn::Document
|
|
327
327
|
|
328
328
|
def convert_floating_title node
|
329
329
|
add_dest_for_block node if node.id
|
330
|
-
|
331
|
-
|
330
|
+
# QUESTION should we decouple styles from section titles?
|
331
|
+
theme_font :heading, level: (h_level = node.level + 1) do
|
332
|
+
layout_heading node.title, align: (@theme[%(heading_h#{h_level}_align)] || @theme.heading_align || :left).to_sym
|
332
333
|
end
|
333
334
|
end
|
334
335
|
|
@@ -808,85 +809,94 @@ class Converter < ::Prawn::Document
|
|
808
809
|
|
809
810
|
theme_margin :block, :top
|
810
811
|
|
811
|
-
# NOTE image is scaled proportionally based on width (height is ignored)
|
812
812
|
# TODO support cover (aka canvas) image layout using "canvas" (or "cover") role
|
813
813
|
width = resolve_explicit_width node.attributes, bounds.width
|
814
|
+
if (width_relative_to_page = (node.attr? 'pdfwidth') && ((node.attr 'pdfwidth').end_with? 'vw'))
|
815
|
+
overflow = [bounds_margin_left, bounds_margin_right]
|
816
|
+
else
|
817
|
+
overflow = 0
|
818
|
+
end
|
814
819
|
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
820
|
+
span_page_width_if width_relative_to_page do
|
821
|
+
case image_type
|
822
|
+
when 'svg'
|
823
|
+
begin
|
824
|
+
svg_data = ::IO.read image_path
|
825
|
+
svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: width, fallback_font_name: default_svg_font, enable_file_requests_with_root: (::File.dirname image_path)
|
826
|
+
rendered_w = (svg_size = svg_obj.document.sizing).output_width
|
827
|
+
if !width && (svg_obj.document.root.attributes.key? 'width')
|
828
|
+
# NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
|
829
|
+
if (adjusted_w = [bounds.width, rendered_w * 0.75].min) != rendered_w
|
830
|
+
# FIXME would be nice to have a resize/recalculate method; instead, just reconstruct
|
831
|
+
svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: (rendered_w = adjusted_w), fallback_font_name: default_svg_font, enable_file_requests_with_root: (::File.dirname image_path)
|
832
|
+
svg_size = svg_obj.document.sizing
|
833
|
+
end
|
828
834
|
end
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
835
|
+
# TODO shrink image to fit on a single page if height exceeds page height
|
836
|
+
rendered_h = svg_size.output_height
|
837
|
+
# TODO layout SVG without using keep_together (since we know the dimensions already); always render caption
|
838
|
+
keep_together do |box_height = nil|
|
839
|
+
svg_obj.instance_variable_set :@prawn, self
|
840
|
+
svg_obj.draw
|
841
|
+
if box_height && (link = node.attr 'link')
|
842
|
+
link_annotation [(abs_left = svg_obj.position[0] + bounds.absolute_left), y, (abs_left + rendered_w), (y + rendered_h)],
|
843
|
+
Border: [0, 0, 0],
|
844
|
+
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
845
|
+
end
|
846
|
+
indent *overflow do
|
847
|
+
layout_caption node, position: :bottom
|
848
|
+
end if node.title?
|
840
849
|
end
|
841
|
-
|
850
|
+
rescue => e
|
851
|
+
warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
|
842
852
|
end
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
# TODO move this calculation into a method
|
860
|
-
caption_height = node.title? ?
|
861
|
-
(@theme.caption_margin_inside + @theme.caption_margin_outside + @theme.base_line_height_length) : 0
|
862
|
-
if rendered_h > (available_height = cursor - caption_height)
|
863
|
-
start_new_page unless at_page_top?
|
864
|
-
# NOTE shrink image so it fits on a single page if height exceeds page height
|
853
|
+
else
|
854
|
+
begin
|
855
|
+
# FIXME this code really needs to be better organized!
|
856
|
+
# FIXME temporary workaround to group caption & image
|
857
|
+
# NOTE use low-level API to access intrinsic dimensions; build_image_object caches image data previously loaded
|
858
|
+
image_obj, image_info = build_image_object image_path
|
859
|
+
if width
|
860
|
+
rendered_w, rendered_h = image_info.calc_image_dimensions width: width
|
861
|
+
else
|
862
|
+
# NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
|
863
|
+
rendered_w = [bounds.width, image_info.width * 0.75].min
|
864
|
+
rendered_h = (rendered_w * image_info.height) / image_info.width
|
865
|
+
end
|
866
|
+
# TODO move this calculation into a method
|
867
|
+
caption_height = node.title? ?
|
868
|
+
(@theme.caption_margin_inside + @theme.caption_margin_outside + @theme.base_line_height_length) : 0
|
865
869
|
if rendered_h > (available_height = cursor - caption_height)
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
870
|
+
start_new_page unless at_page_top?
|
871
|
+
# NOTE shrink image so it fits on a single page if height exceeds page height
|
872
|
+
if rendered_h > (available_height = cursor - caption_height)
|
873
|
+
rendered_w = (rendered_w * available_height) / rendered_h
|
874
|
+
rendered_h = available_height
|
875
|
+
# FIXME workaround to fix Prawn not adding fill and stroke commands
|
876
|
+
# on page that only has an image; breakage occurs when line numbers are added
|
877
|
+
# NOTE this no longer seems to be an issue
|
878
|
+
fill_color self.fill_color
|
879
|
+
stroke_color self.stroke_color
|
880
|
+
end
|
873
881
|
end
|
882
|
+
# NOTE must calculate link position before embedding to get proper boundaries
|
883
|
+
if (link = node.attr 'link')
|
884
|
+
img_x, img_y = image_position rendered_w, rendered_h, position: position
|
885
|
+
link_box = [img_x, (img_y - rendered_h), (img_x + rendered_w), img_y]
|
886
|
+
end
|
887
|
+
embed_image image_obj, image_info, width: rendered_w, position: position
|
888
|
+
if link
|
889
|
+
link_annotation link_box,
|
890
|
+
Border: [0, 0, 0],
|
891
|
+
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
892
|
+
end
|
893
|
+
rescue => e
|
894
|
+
warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
|
874
895
|
end
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
link_box = [img_x, (img_y - rendered_h), (img_x + rendered_w), img_y]
|
879
|
-
end
|
880
|
-
embed_image image_obj, image_info, width: rendered_w, position: position
|
881
|
-
if link
|
882
|
-
link_annotation link_box,
|
883
|
-
Border: [0, 0, 0],
|
884
|
-
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
885
|
-
end
|
886
|
-
rescue => e
|
887
|
-
warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
|
896
|
+
indent *overflow do
|
897
|
+
layout_caption node, position: :bottom
|
898
|
+
end if node.title?
|
888
899
|
end
|
889
|
-
layout_caption node, position: :bottom if node.title?
|
890
900
|
end
|
891
901
|
theme_margin :block, :bottom
|
892
902
|
ensure
|
@@ -940,6 +950,12 @@ class Converter < ::Prawn::Document
|
|
940
950
|
Helpers.require_library 'pygments', 'pygments.rb' unless defined? ::Pygments
|
941
951
|
lexer = ::Pygments::Lexer[node.attr 'language', 'text', false] || ::Pygments::Lexer['text']
|
942
952
|
pygments_config = { nowrap: true, noclasses: true, style: (node.document.attr 'pygments-style') || 'pastie' }
|
953
|
+
# TODO enable once we support background color on spans
|
954
|
+
#if node.attr? 'highlight', nil, false
|
955
|
+
# unless (hl_lines = node.resolve_lines_to_highlight(node.attr 'highlight', nil, false)).empty?
|
956
|
+
# pygments_config[:hl_lines] = hl_lines * ' '
|
957
|
+
# end
|
958
|
+
#end
|
943
959
|
source_string, conum_mapping = extract_conums source_string
|
944
960
|
result = lexer.highlight source_string, options: pygments_config
|
945
961
|
fragments = guard_indentation text_formatter.format result
|
@@ -1158,7 +1174,7 @@ class Converter < ::Prawn::Document
|
|
1158
1174
|
cell_data = {
|
1159
1175
|
content: cell.text,
|
1160
1176
|
inline_format: [normalize: true],
|
1161
|
-
text_color: (theme.
|
1177
|
+
text_color: (theme.table_font_color || @font_color),
|
1162
1178
|
size: theme.table_font_size,
|
1163
1179
|
font: theme.table_font_family,
|
1164
1180
|
colspan: cell.colspan || 1,
|
@@ -1209,8 +1225,11 @@ class Converter < ::Prawn::Document
|
|
1209
1225
|
end
|
1210
1226
|
|
1211
1227
|
border = {}
|
1228
|
+
table_border_color = theme.table_border_color
|
1212
1229
|
table_border_width = theme.table_border_width
|
1213
|
-
|
1230
|
+
table_grid_width = theme.table_grid_width || theme.table_border_width
|
1231
|
+
[:top, :bottom, :left, :right].each {|edge| border[edge] = table_border_width }
|
1232
|
+
[:cols, :rows].each {|edge| border[edge] = table_grid_width }
|
1214
1233
|
|
1215
1234
|
frame = (node.attr 'frame') || 'all'
|
1216
1235
|
grid = (node.attr 'grid') || 'all'
|
@@ -1258,7 +1277,8 @@ class Converter < ::Prawn::Document
|
|
1258
1277
|
cell_style: {
|
1259
1278
|
padding: theme.table_cell_padding,
|
1260
1279
|
border_width: 0,
|
1261
|
-
|
1280
|
+
# NOTE the border color of edges is set later
|
1281
|
+
border_color: theme.table_grid_color || theme.table_border_color
|
1262
1282
|
},
|
1263
1283
|
column_widths: column_widths,
|
1264
1284
|
row_colors: [odd_row_bg_color, even_row_bg_color]
|
@@ -1270,6 +1290,7 @@ class Converter < ::Prawn::Document
|
|
1270
1290
|
table table_data, table_settings do
|
1271
1291
|
if grid == 'none' && frame == 'none'
|
1272
1292
|
if table_header
|
1293
|
+
# FIXME allow header border bottom width to be set by theme
|
1273
1294
|
rows(0).border_bottom_width = 1.5
|
1274
1295
|
end
|
1275
1296
|
else
|
@@ -1277,17 +1298,33 @@ class Converter < ::Prawn::Document
|
|
1277
1298
|
cells.border_width = [border[:rows], border[:cols], border[:rows], border[:cols]]
|
1278
1299
|
|
1279
1300
|
if table_header
|
1301
|
+
# FIXME allow header border bottom width to be set by theme
|
1280
1302
|
rows(0).border_bottom_width = 1.5
|
1303
|
+
# QUESTION should we use the table border color for the bottom border color of the header row?
|
1304
|
+
#rows(0).border_bottom_color = table_border_color
|
1305
|
+
#rows(1).border_top_width = 0 if row_length > 1
|
1281
1306
|
end
|
1282
1307
|
|
1283
1308
|
# top edge of table
|
1284
|
-
rows(0).
|
1309
|
+
rows(0).tap do |r|
|
1310
|
+
r.border_top_width = border[:top]
|
1311
|
+
r.border_top_color = table_border_color
|
1312
|
+
end
|
1285
1313
|
# right edge of table
|
1286
|
-
columns(num_cols - 1).
|
1314
|
+
columns(num_cols - 1).tap do |r|
|
1315
|
+
r.border_right_width = border[:right]
|
1316
|
+
r.border_right_color = table_border_color
|
1317
|
+
end
|
1287
1318
|
# bottom edge of table
|
1288
|
-
rows(num_rows - 1).
|
1319
|
+
rows(num_rows - 1).tap do |r|
|
1320
|
+
r.border_bottom_width = border[:bottom]
|
1321
|
+
r.border_bottom_color = table_border_color
|
1322
|
+
end
|
1289
1323
|
# left edge of table
|
1290
|
-
columns(0).
|
1324
|
+
columns(0).tap do |r|
|
1325
|
+
r.border_left_width = border[:left]
|
1326
|
+
r.border_left_color = table_border_color
|
1327
|
+
end
|
1291
1328
|
end
|
1292
1329
|
|
1293
1330
|
# QUESTION should cell padding be configurable for foot row cells?
|
@@ -1337,9 +1374,9 @@ class Converter < ::Prawn::Document
|
|
1337
1374
|
end
|
1338
1375
|
#attrs << %( title="#{node.attr 'title'}") if node.attr? 'title'
|
1339
1376
|
attrs << %( target="#{node.attr 'window'}") if node.attr? 'window'
|
1340
|
-
if (node.document.attr? '
|
1377
|
+
if (((doc = node.document).attr? 'media', 'print') || (doc.attr? 'show-link-uri')) && !(node.has_role? 'bare')
|
1341
1378
|
# TODO allow style of visible link to be controlled by theme
|
1342
|
-
%(<a href="#{target = node.target}"#{attrs.join}>#{node.text}</a> <font size="0.
|
1379
|
+
%(<a href="#{target = node.target}"#{attrs.join}>#{node.text}</a> <font size="0.9em">[#{target}]</font>)
|
1343
1380
|
else
|
1344
1381
|
%(<a href="#{node.target}"#{attrs.join}>#{node.text}</a>)
|
1345
1382
|
end
|
@@ -1460,10 +1497,11 @@ class Converter < ::Prawn::Document
|
|
1460
1497
|
|
1461
1498
|
def convert_inline_menu node
|
1462
1499
|
menu = node.attr 'menu'
|
1500
|
+
caret = @theme.menu_caret_content || %( \u203a )
|
1463
1501
|
if !(submenus = node.attr 'submenus').empty?
|
1464
|
-
%(<strong>#{[menu, *submenus, (node.attr 'menuitem')] *
|
1502
|
+
%(<strong>#{[menu, *submenus, (node.attr 'menuitem')] * caret}</strong>)
|
1465
1503
|
elsif (menuitem = node.attr 'menuitem')
|
1466
|
-
%(<strong>#{menu}
|
1504
|
+
%(<strong>#{menu}#{caret}#{menuitem}</strong>)
|
1467
1505
|
else
|
1468
1506
|
%(<strong>#{menu}</strong>)
|
1469
1507
|
end
|
@@ -1543,7 +1581,7 @@ class Converter < ::Prawn::Document
|
|
1543
1581
|
logo_image_attrs['align'] ||= (@theme.title_page_logo_align || title_align.to_s)
|
1544
1582
|
logo_image_top = (logo_image_attrs['top'] || @theme.title_page_logo_top)
|
1545
1583
|
# FIXME delegate to method to convert page % to y value
|
1546
|
-
logo_image_top = [(page_height - page_height * (logo_image_top.
|
1584
|
+
logo_image_top = [(page_height - page_height * (logo_image_top.to_f / 100.0)), bounds.absolute_top].min
|
1547
1585
|
float do
|
1548
1586
|
@y = logo_image_top
|
1549
1587
|
# FIXME add API to Asciidoctor for creating blocks like this (extract from extensions module?)
|
@@ -1559,7 +1597,7 @@ class Converter < ::Prawn::Document
|
|
1559
1597
|
doctitle = doc.doctitle partition: true
|
1560
1598
|
if (title_top = @theme.title_page_title_top)
|
1561
1599
|
# FIXME delegate to method to convert page % to y value
|
1562
|
-
@y = [(page_height - page_height * (title_top.
|
1600
|
+
@y = [(page_height - page_height * (title_top.to_f / 100.0)), bounds.absolute_top].min
|
1563
1601
|
end
|
1564
1602
|
move_down (@theme.title_page_title_margin_top || 0)
|
1565
1603
|
theme_font :title_page_title do
|
@@ -1631,8 +1669,8 @@ class Converter < ::Prawn::Document
|
|
1631
1669
|
start_new_page
|
1632
1670
|
end
|
1633
1671
|
|
1634
|
-
def layout_chapter_title node, title
|
1635
|
-
layout_heading title
|
1672
|
+
def layout_chapter_title node, title, opts = {}
|
1673
|
+
layout_heading title, opts
|
1636
1674
|
end
|
1637
1675
|
|
1638
1676
|
# QUESTION why doesn't layout_heading set the font??
|
@@ -1720,7 +1758,7 @@ class Converter < ::Prawn::Document
|
|
1720
1758
|
go_to_page toc_page_number unless (page_number == toc_page_number) || scratch?
|
1721
1759
|
start_page_number = page_number
|
1722
1760
|
theme_font :heading, level: 2 do
|
1723
|
-
layout_heading doc.attr('toc-title')
|
1761
|
+
layout_heading doc.attr('toc-title'), align: (@theme.toc_title_align || :left).to_sym
|
1724
1762
|
end
|
1725
1763
|
# QUESTION shouldn't we skip this whole method if num_levels == 0?
|
1726
1764
|
if num_levels > 0
|
@@ -1973,10 +2011,10 @@ class Converter < ::Prawn::Document
|
|
1973
2011
|
when ::Hash
|
1974
2012
|
# NOTE image placement respects padding; use negative image_vertical_align value to revert
|
1975
2013
|
trim_v_padding = trim_padding[0] + trim_padding[2]
|
1976
|
-
# NOTE
|
1977
|
-
|
1978
|
-
# NOTE
|
1979
|
-
|
2014
|
+
# NOTE float ensures cursor position is restored and returns us to current page if we overrun
|
2015
|
+
float do
|
2016
|
+
# NOTE bounding_box is redundant if trim_v_padding is 0
|
2017
|
+
bounding_box [0, cursor - trim_padding[0]], width: bounds.width, height: (bounds.height - trim_v_padding) do
|
1980
2018
|
#image content[:path], vposition: trim_img_valign, position: align, width: content[:width]
|
1981
2019
|
# NOTE use :fit to prevent image from overflowing page (at the cost of scaling it)
|
1982
2020
|
image content[:path], vposition: trim_img_valign, position: align, fit: [content[:width], bounds.height]
|
@@ -2357,7 +2395,7 @@ class Converter < ::Prawn::Document
|
|
2357
2395
|
if (imagesdir = doc.attr 'imagesdir').nil_or_empty? || (imagesdir = imagesdir.chomp '/') == '.'
|
2358
2396
|
nil
|
2359
2397
|
else
|
2360
|
-
|
2398
|
+
imagesdir
|
2361
2399
|
end
|
2362
2400
|
end
|
2363
2401
|
|
@@ -2380,7 +2418,7 @@ class Converter < ::Prawn::Document
|
|
2380
2418
|
image_type ||= ::Asciidoctor::Image.image_type image_path
|
2381
2419
|
# handle case when image is a URI
|
2382
2420
|
if (node.is_uri? image_path) || (imagesdir && (node.is_uri? imagesdir) &&
|
2383
|
-
(image_path = (node.normalize_web_path image_path,
|
2421
|
+
(image_path = (node.normalize_web_path image_path, imagesdir, false)))
|
2384
2422
|
unless doc.attr? 'allow-uri-read'
|
2385
2423
|
unless scratch?
|
2386
2424
|
warn %(asciidoctor: WARNING: allow-uri-read is not enabled; cannot embed remote image: #{image_path})
|
@@ -2389,6 +2427,8 @@ class Converter < ::Prawn::Document
|
|
2389
2427
|
end
|
2390
2428
|
if doc.attr? 'cache-uri'
|
2391
2429
|
Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
|
2430
|
+
else
|
2431
|
+
::OpenURI
|
2392
2432
|
end
|
2393
2433
|
tmp_image = ::Tempfile.new ['image-', %(.#{image_type})]
|
2394
2434
|
tmp_image.binmode if (binary = image_type != 'svg')
|
@@ -2447,6 +2487,8 @@ class Converter < ::Prawn::Document
|
|
2447
2487
|
if attrs.key? 'pdfwidth'
|
2448
2488
|
if (pdfwidth = attrs['pdfwidth']).end_with? '%'
|
2449
2489
|
(pdfwidth.to_f / 100) * max_width
|
2490
|
+
elsif pdfwidth.end_with? 'vw'
|
2491
|
+
(pdfwidth.to_f / 100) * page_width
|
2450
2492
|
else
|
2451
2493
|
str_to_pt pdfwidth
|
2452
2494
|
end
|
@@ -3,7 +3,7 @@ module Pdf
|
|
3
3
|
module FormattedText
|
4
4
|
class Formatter
|
5
5
|
FormattingSnifferPattern = /[<&]/
|
6
|
-
|
6
|
+
WHITESPACE = " \t\n"
|
7
7
|
|
8
8
|
def initialize options = {}
|
9
9
|
@parser = MarkupParser.new
|
@@ -12,7 +12,7 @@ class Formatter
|
|
12
12
|
|
13
13
|
def format string, *args
|
14
14
|
options = args[0] || {}
|
15
|
-
string = string.tr_s(
|
15
|
+
string = string.tr_s(WHITESPACE, ' ') if options[:normalize]
|
16
16
|
return [text: string] unless string.match(FormattingSnifferPattern)
|
17
17
|
if (parsed = @parser.parse(string))
|
18
18
|
@transform.apply(parsed.content)
|
@@ -3,13 +3,14 @@ module Pdf
|
|
3
3
|
module FormattedText
|
4
4
|
class Transform
|
5
5
|
EOL = "\n"
|
6
|
-
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
6
|
+
CharEntityTable = {
|
7
|
+
lt: '<',
|
8
|
+
gt: '>',
|
9
|
+
amp: '&',
|
10
|
+
quot: '"',
|
11
|
+
apos: '\''
|
12
12
|
}
|
13
|
+
CharRefRx = /&(?:#(\d{2,6})|(#{CharEntityTable.keys * '|'}));/
|
13
14
|
#ZeroWidthSpace = %(\u200b)
|
14
15
|
|
15
16
|
def initialize(options = {})
|
@@ -103,7 +104,7 @@ class Transform
|
|
103
104
|
previous_fragment_is_text = true
|
104
105
|
when :entity
|
105
106
|
if (name = node[:name])
|
106
|
-
text =
|
107
|
+
text = CharEntityTable[name]
|
107
108
|
else
|
108
109
|
# FIXME AFM fonts do not include a thin space glyph; set fallback_fonts to allow glyph to be resolved
|
109
110
|
text = [node[:number]].pack('U*')
|
@@ -183,15 +184,15 @@ class Transform
|
|
183
184
|
# fragment[:character_spacing] = value.to_f
|
184
185
|
#end
|
185
186
|
when :a
|
187
|
+
# QUESTION shouldn't anchor, link and name be mutually exclusive?
|
186
188
|
if !fragment[:anchor] && (value = attrs[:anchor])
|
187
189
|
fragment[:anchor] = value
|
188
190
|
end
|
189
191
|
if !fragment[:link] && (value = attrs[:href])
|
190
|
-
fragment[:link] = value
|
192
|
+
fragment[:link] = (value.include? '&') ? value.gsub(CharRefRx) {
|
193
|
+
$2 ? CharEntityTable[$2.to_sym] : [$1.to_i].pack('U*')
|
194
|
+
} : value
|
191
195
|
end
|
192
|
-
#if !fragment[:local] && (value = attrs[:local])
|
193
|
-
# fragment[:local] = value
|
194
|
-
#end
|
195
196
|
if !fragment[:name] && (value = attrs[:name])
|
196
197
|
fragment[:name] = value
|
197
198
|
fragment[:callback] = InlineDestinationMarker
|