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.
@@ -299,9 +299,9 @@ class Converter < ::Prawn::Document
299
299
  end
300
300
 
301
301
  def convert_section sect, opts = {}
302
- heading_level = sect.level + 1
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 || %(section-#{page_number}-#{dest_y.ceil}))
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
- theme_font :heading, level: (node.level + 1) do
331
- layout_heading node.title
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
- case image_type
816
- when 'svg'
817
- begin
818
- svg_data = ::IO.read image_path
819
- svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: width, fallback_font_name: default_svg_font
820
- svg_size = svg_obj.document.sizing
821
- rendered_w = svg_size.output_width
822
- if !width && (svg_obj.document.root.attributes.key? 'width')
823
- # NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
824
- if (adjusted_w = [bounds.width, rendered_w * 0.75].min) != rendered_w
825
- # FIXME would be nice to have a resize/recalculate method; instead, just reconstruct
826
- svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: (rendered_w = adjusted_w), fallback_font_name: default_svg_font
827
- svg_size = svg_obj.document.sizing
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
- end
830
- # TODO shrink image to fit on a single page if height exceeds page height
831
- rendered_h = svg_size.output_height
832
- # TODO layout SVG without using keep_together (since we know the dimensions already); always render caption
833
- keep_together do |box_height = nil|
834
- svg_obj.instance_variable_set :@prawn, self
835
- svg_obj.draw
836
- if box_height && (link = node.attr 'link')
837
- link_annotation [(abs_left = svg_obj.position[0] + bounds.absolute_left), y, (abs_left + rendered_w), (y + rendered_h)],
838
- Border: [0, 0, 0],
839
- A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
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
- layout_caption node, position: :bottom if node.title?
850
+ rescue => e
851
+ warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
842
852
  end
843
- rescue => e
844
- warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message})
845
- end
846
- else
847
- begin
848
- # FIXME this code really needs to be better organized!
849
- # FIXME temporary workaround to group caption & image
850
- # NOTE use low-level API to access intrinsic dimensions; build_image_object caches image data previously loaded
851
- image_obj, image_info = build_image_object image_path
852
- if width
853
- rendered_w, rendered_h = image_info.calc_image_dimensions width: width
854
- else
855
- # NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
856
- rendered_w = [bounds.width, image_info.width * 0.75].min
857
- rendered_h = (rendered_w * image_info.height) / image_info.width
858
- end
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
- rendered_w = (rendered_w * available_height) / rendered_h
867
- rendered_h = available_height
868
- # FIXME workaround to fix Prawn not adding fill and stroke commands
869
- # on page that only has an image; breakage occurs when line numbers are added
870
- # NOTE this no longer seems to be an issue
871
- fill_color self.fill_color
872
- stroke_color self.stroke_color
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
- # NOTE must calculate link position before embedding to get proper boundaries
876
- if (link = node.attr 'link')
877
- img_x, img_y = image_position rendered_w, rendered_h, position: position
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.table_body_font_color || @font_color),
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
- [:top, :bottom, :left, :right, :cols, :rows].each {|edge| border[edge] = table_border_width }
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
- border_color: theme.table_border_color
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).border_top_width = border[:top]
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).border_right_width = border[:right]
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).border_bottom_width = border[:bottom]
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).border_left_width = border[:left]
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? 'showlinks') && !(node.has_role? 'bare')
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.9"><em>(#{target})</em></font>)
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')] * ' | '}</strong>)
1502
+ %(<strong>#{[menu, *submenus, (node.attr 'menuitem')] * caret}</strong>)
1465
1503
  elsif (menuitem = node.attr 'menuitem')
1466
- %(<strong>#{menu} | #{menuitem}</strong>)
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.to_i / 100.0)), bounds.absolute_top].min
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.to_i / 100.0)), bounds.absolute_top].min
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 bounding_box is redundant if trim_v_padding is 0
1977
- bounding_box [0, cursor - trim_padding[0]], width: bounds.width, height: (bounds.height - trim_v_padding) do
1978
- # NOTE float ensures cursor position is restored and returns us to current page if we overrun
1979
- float do
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
- %(#{imagesdir}/)
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, image_base_uri, false)))
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
- EOL = "\n"
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(EOL, ' ') if options[:normalize]
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)
@@ -901,7 +901,7 @@ module Markup
901
901
  else
902
902
  break
903
903
  end
904
- if s0.size == 4
904
+ if s0.size == 6
905
905
  break
906
906
  end
907
907
  end
@@ -108,7 +108,8 @@ grammar Markup
108
108
  end
109
109
 
110
110
  rule entity_number
111
- [0-9] 2..4
111
+ # NOTE 6 decimals only supported in Asciidoctor 1.5.5 and up
112
+ [0-9] 2..6
112
113
  end
113
114
 
114
115
  rule entity_name
@@ -3,13 +3,14 @@ module Pdf
3
3
  module FormattedText
4
4
  class Transform
5
5
  EOL = "\n"
6
- NamedEntityTable = {
7
- :lt => '<',
8
- :gt => '>',
9
- :amp => '&',
10
- :quot => '"',
11
- :apos => '\''
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 = NamedEntityTable[name]
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