pdf-writer 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/pdf/writer.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
7
  # for full licensing information.
8
8
  #
9
- # $Id: writer.rb,v 1.32 2005/06/13 19:32:37 austin Exp $
9
+ # $Id: writer.rb,v 1.37 2005/06/28 21:32:17 austin Exp $
10
10
  #++
11
11
  require 'thread'
12
12
  require 'open-uri'
@@ -19,7 +19,7 @@ require 'color'
19
19
  module PDF
20
20
  class Writer
21
21
  # The version of PDF::Writer.
22
- VERSION = '1.0.1'
22
+ VERSION = '1.1.0'
23
23
 
24
24
  # Escape the text so that it's safe for insertion into the PDF
25
25
  # document.
@@ -433,8 +433,12 @@ class PDF::Writer
433
433
  # external clients.
434
434
  attr_accessor :procset #:nodoc:
435
435
  # Sets the document to compressed (+true+) or uncompressed (+false+).
436
- # Defaults to uncompressed.
436
+ # Defaults to uncompressed. This can ONLY be set once and should be set
437
+ # as early as possible in the document creation process.
437
438
  attr_accessor :compressed
439
+ def compressed=(cc) #:nodoc:
440
+ @compressed = cc if @compressed.nil?
441
+ end
438
442
  # Returns +true+ if the document is compressed.
439
443
  def compressed?
440
444
  @compressed == true
@@ -804,20 +808,21 @@ class PDF::Writer
804
808
  def load_font(font, encoding = nil)
805
809
  metrics = load_font_metrics(font)
806
810
 
807
- name = File.basename(font).gsub(/\.afm$/o, "")
811
+ name = File.basename(font).gsub(/\.afm$/o, "")
808
812
 
809
813
  encoding_diff = nil
810
814
  case encoding
811
815
  when Hash
812
816
  encoding_name = encoding[:encoding]
813
817
  encoding_diff = encoding[:differences]
818
+ encoding = PDF::Writer::Object::FontEncoding.new(self, encoding_name, encoding_diff)
814
819
  when NilClass
815
- encoding_name = 'WinAnsiEncoding'
820
+ encoding_name = encoding = 'WinAnsiEncoding'
816
821
  else
817
822
  encoding_name = encoding
818
823
  end
819
824
 
820
- wfo = PDF::Writer::Object::Font.new(self, name, encoding_name)
825
+ wfo = PDF::Writer::Object::Font.new(self, name, encoding)
821
826
 
822
827
  # We have an Adobe Font Metrics (.afm) file. We need to find the
823
828
  # associated Type1 (.pfb) or TrueType (.ttf) files (we do not yet
@@ -853,7 +858,6 @@ class PDF::Writer
853
858
  # Adjust the widths for the differences array.
854
859
  if encoding_diff
855
860
  encoding_diff.each do |cnum, cname|
856
- # warn "Differences is ignored for now."
857
861
  (cnum - last_char).times { widths << 0 } if cnum > last_char
858
862
  last_char = cnum
859
863
  widths[cnum - firstchar] = fonts.c[cname]['WX'] if metrics.c[cname]
@@ -872,10 +876,40 @@ class PDF::Writer
872
876
  widthid << "]"
873
877
 
874
878
  # Load the pfb file, and put that into an object too. Note that PDF
875
- # supports only binary format Type 1 font files, though there is a
876
- # simple utility to convert them from pfa to pfb.
877
- data = nil
878
- File.open(fbfile, "rb") { |ff| data = ff.read }
879
+ # supports only binary format Type1 font files and TrueType font
880
+ # files. There is a simple utility to convert Type1 from pfa to pfb.
881
+ data = File.open(fbfile, "rb") { |ff| ff.read }
882
+
883
+ # Check to see if the font licence allows embedding.
884
+ if fbtype =~ /\.ttf$/o
885
+ offset = 4
886
+ tables = data[offset, 2].unpack('n')[0]
887
+ offset += 8
888
+
889
+ found = false
890
+ tables.times do
891
+ if data[offset, 4] == 'OS/2'
892
+ found = true
893
+ break
894
+ end
895
+ offset += 4 + 12
896
+ end
897
+
898
+ if found
899
+ offset += 4
900
+ newoff = data[offset, 4].unpack('N')[0]
901
+ offset = newoff + 8
902
+ licence = data[offset, 2].unpack('n')[0]
903
+
904
+ rl = ((licence & 0x02) != 0)
905
+ pp = ((licence & 0x04) != 0)
906
+ ee = ((licence & 0x08) != 0)
907
+
908
+ if rl and pp and ee
909
+ warn PDF::Writer::Lang[:ttf_licence_no_embedding] % name
910
+ end
911
+ end
912
+ end
879
913
 
880
914
  # Create the font descriptor.
881
915
  fdsc = PDF::Writer::Object::FontDescriptor.new(self)
@@ -885,9 +919,42 @@ class PDF::Writer
885
919
  # Determine flags (more than a little flakey, hopefully will not
886
920
  # matter much).
887
921
  flags = 0
888
- flags += 2 ** 6 if metrics.italicangle.nonzero?
889
- flags += 1 if metrics.isfixedpitch == "true"
890
- flags += 2 ** 5 # Assume a non-symbolic font
922
+ if encoding == "none"
923
+ flags += 2 ** 2
924
+ else
925
+ flags += 2 ** 6 if metrics.italicangle.nonzero?
926
+ flags += 2 ** 0 if metrics.isfixedpitch == "true"
927
+ flags += 2 ** 5 # Assume a non-symbolic font
928
+ end
929
+
930
+ # 1: FixedPitch: All glyphs have the same width (as opposed to
931
+ # proportional or variable-pitch fonts, which have
932
+ # different widths).
933
+ # 2: Serif: Glyphs have serifs, which are short strokes drawn
934
+ # at an angle on the top and bottom of glyph stems.
935
+ # (Sans serif fonts do not have serifs.)
936
+ # 3: Symbolic Font contains glyphs outside the Adobe standard
937
+ # Latin character set. This flag and the Nonsymbolic
938
+ # flag cannot both be set or both be clear (see
939
+ # below).
940
+ # 4: Script: Glyphs resemble cursive handwriting.
941
+ # 6: Nonsymbolic: Font uses the Adobe standard Latin character set
942
+ # or a subset of it (see below).
943
+ # 7: Italic: Glyphs have dominant vertical strokes that are
944
+ # slanted.
945
+ # 17: AllCap: Font contains no lowercase letters; typically used
946
+ # for display purposes, such as for titles or
947
+ # headlines.
948
+ # 18: SmallCap: Font contains both uppercase and lowercase
949
+ # letters. The uppercase letters are similar to
950
+ # those in the regular version of the same typeface
951
+ # family. The glyphs for the lowercase letters have
952
+ # the same shapes as the corresponding uppercase
953
+ # letters, but they are sized and their proportions
954
+ # adjusted so that they have the same size and
955
+ # stroke weight as lowercase glyphs in the same
956
+ # typeface family.
957
+ # 19: ForceBold: See below.
891
958
 
892
959
  list = {
893
960
  'Ascent' => 'Ascender',
@@ -1029,8 +1096,11 @@ class PDF::Writer
1029
1096
  @current_contents << cc
1030
1097
  end
1031
1098
 
1032
- # Return the height in units of the current font in the given size.
1033
- def font_height(size)
1099
+ # Return the height in units of the current font in the given size. Uses
1100
+ # the current #font_size if size is not provided.
1101
+ def font_height(size = nil)
1102
+ size = @font_size if size.nil? or size <= 0
1103
+
1034
1104
  select_font("Helvetica") if @fonts.empty?
1035
1105
  hh = @fonts[@current_font].fontbbox[3].to_f - @fonts[@current_font].fontbbox[1].to_f
1036
1106
  (size * hh / 1000.0)
@@ -1038,8 +1108,11 @@ class PDF::Writer
1038
1108
 
1039
1109
  # Return the font descender, this will normally return a negative
1040
1110
  # number. If you add this number to the baseline, you get the level of
1041
- # the bottom of the font it is in the PDF user units.
1042
- def font_descender(size)
1111
+ # the bottom of the font it is in the PDF user units. Uses the current
1112
+ # #font_size if size is not provided.
1113
+ def font_descender(size = nil)
1114
+ size = @font_size if size.nil? or size <= 0
1115
+
1043
1116
  select_font("Helvetica") if @fonts.empty?
1044
1117
  hi = @fonts[@current_font].fontbbox[1].to_f
1045
1118
  (size * hi / 1000.0)
@@ -1047,13 +1120,13 @@ class PDF::Writer
1047
1120
 
1048
1121
  # Given a start position and information about how text is to be laid
1049
1122
  # out, calculate where on the page the text will end.
1050
- def text_position(x, y, angle, size, wa, text)
1051
- width = text_width(size, text)
1123
+ def text_end_position(x, y, angle, size, wa, text)
1124
+ width = text_width(text, size)
1052
1125
  width += wa * (text.count(" "))
1053
1126
  rad = PDF::Math.deg2rad(angle)
1054
1127
  [Math.cos(rad) * width + x, ((-Math.sin(rad)) * width + y)]
1055
1128
  end
1056
- private :text_position
1129
+ private :text_end_position
1057
1130
 
1058
1131
  # Wrapper function for #text_tags
1059
1132
  def quick_text_tags(text, ii, font_change)
@@ -1205,7 +1278,8 @@ class PDF::Writer
1205
1278
 
1206
1279
  if tag
1207
1280
  text[pos, tag_size] = tag[self, params]
1208
- tag_size, text, font_change, x, y = text_tags(text, pos, font_change,
1281
+ tag_size, text, font_change, x, y = text_tags(text, pos,
1282
+ font_change,
1209
1283
  final, x, y, size,
1210
1284
  angle,
1211
1285
  word_space_adjust)
@@ -1230,8 +1304,8 @@ class PDF::Writer
1230
1304
  if final
1231
1305
  # Only call the function if this is the "final" call. Assess
1232
1306
  # the text position. Calculate the text width to this point.
1233
- x, y = text_position(x, y, angle, size, word_space_adjust,
1234
- text[0, pos])
1307
+ x, y = text_end_position(x, y, angle, size, word_space_adjust,
1308
+ text[0, pos])
1235
1309
  info = {
1236
1310
  :x => x,
1237
1311
  :y => y,
@@ -1275,8 +1349,8 @@ class PDF::Writer
1275
1349
  if final
1276
1350
  # Only call the function if this is the "final" call. Assess
1277
1351
  # the text position. Calculate the text width to this point.
1278
- x, y = text_position(x, y, angle, size, word_space_adjust,
1279
- text[0, pos])
1352
+ x, y = text_end_position(x, y, angle, size, word_space_adjust,
1353
+ text[0, pos])
1280
1354
  info = {
1281
1355
  :x => x,
1282
1356
  :y => y,
@@ -1345,9 +1419,22 @@ class PDF::Writer
1345
1419
  end
1346
1420
  private :parse_tag_params
1347
1421
 
1348
- # Add text to the document at the x, y location with the text size at
1349
- # the specified angle.
1350
- def add_text(x, y, size, text, angle = 0, word_space_adjust = 0)
1422
+ # Add +text+ to the document at <tt>(x, y)</tt> location at +size+ and
1423
+ # +angle+. The +word_space_adjust+ parameter is an internal parameter
1424
+ # that should not be used.
1425
+ #
1426
+ # As of PDF::Writer 1.1, +size+ and +text+ have been reversed and +size+
1427
+ # is now optional, defaulting to the current #font_size if unset.
1428
+ def add_text(x, y, text, size = nil, angle = 0, word_space_adjust = 0)
1429
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1430
+ text, size = size, text
1431
+ warn PDF::Writer::Lang[:add_text_parameters_reversed] % caller[0]
1432
+ end
1433
+
1434
+ if size.nil? or size <= 0
1435
+ size = @font_size
1436
+ end
1437
+
1351
1438
  select_font("Helvetica") if @fonts.empty?
1352
1439
 
1353
1440
  text = text.to_s
@@ -1368,7 +1455,7 @@ class PDF::Writer
1368
1455
  else
1369
1456
  rad = PDF::Math.deg2rad(angle)
1370
1457
  tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm"
1371
- tt = tt % [Math.cos(rad), -1 * Math.sin(rad), Math.sin(rad), Math.cos(rad), x, y]
1458
+ tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ]
1372
1459
  add_content(tt)
1373
1460
  end
1374
1461
 
@@ -1408,7 +1495,7 @@ class PDF::Writer
1408
1495
  else
1409
1496
  rad = PDF::Math.deg2rad(angle)
1410
1497
  tt = "\nBT %.3f %.3f %.3f %.3f %.3f %.3f Tm"
1411
- tt = tt % [Math.cos(rad), -1 * Math.sin(rad), Math.sin(rad), Math.cos(rad), xp, yp]
1498
+ tt = tt % [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), xp, yp ]
1412
1499
  add_content(tt)
1413
1500
  end
1414
1501
 
@@ -1460,9 +1547,21 @@ class PDF::Writer
1460
1547
  private :char_width
1461
1548
 
1462
1549
  # Calculate how wide a given text string will be on a page, at a given
1463
- # size. This can be called externally, but is alse used by the other
1464
- # class functions.
1465
- def text_line_width(size, text)
1550
+ # size. This may be called externally, but is alse used by #text_width.
1551
+ # If +size+ is not specified, PDF::Writer will use the current
1552
+ # #font_size.
1553
+ #
1554
+ # The argument list is reversed from earlier versions.
1555
+ def text_line_width(text, size = nil)
1556
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1557
+ text, size = size, text
1558
+ warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0]
1559
+ end
1560
+
1561
+ if size.nil? or size <= 0
1562
+ size = @font_size
1563
+ end
1564
+
1466
1565
  # This function should not change any of the settings, though it will
1467
1566
  # need to track any tag which change during calculation, so copy them
1468
1567
  # at the start and put them back at the end.
@@ -1511,13 +1610,27 @@ class PDF::Writer
1511
1610
  (width * size / 1000.0)
1512
1611
  end
1513
1612
 
1514
- # Will calculate the maximum width, taking into account that the text
1515
- # may be broken by line breaks.
1516
- def text_width(size, text)
1613
+ # Calculate how wide a given text string will be on a page, at a given
1614
+ # size. If +size+ is not specified, PDF::Writer will use the current
1615
+ # #font_size. The difference between this method and #text_line_width is
1616
+ # that this method will iterate over lines separated with newline
1617
+ # characters.
1618
+ #
1619
+ # The argument list is reversed from earlier versions.
1620
+ def text_width(text, size = nil)
1621
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1622
+ text, size = size, text
1623
+ warn PDF::Writer::Lang[:text_width_parameters_reversed] % caller[0]
1624
+ end
1625
+
1626
+ if size.nil? or size <= 0
1627
+ size = @font_size
1628
+ end
1629
+
1517
1630
  max = 0
1518
1631
 
1519
1632
  text.to_s.each do |line|
1520
- width = text_line_width(size, line)
1633
+ width = text_line_width(line, size)
1521
1634
  max = width if width > max
1522
1635
  end
1523
1636
  max
@@ -1554,7 +1667,16 @@ class PDF::Writer
1554
1667
  # remainder of the text.
1555
1668
  #
1556
1669
  # +justification+:: :left, :right, :center, or :full
1557
- def add_text_wrap(x, y, width, size, text, justification = :left, angle = 0, test = false)
1670
+ def add_text_wrap(x, y, width, text, size = nil, justification = :left, angle = 0, test = false)
1671
+ if text.kind_of?(Numeric) and size.kind_of?(String)
1672
+ text, size = size, text
1673
+ warn PDF::Writer::Lang[:add_textw_parameters_reversed] % caller[0]
1674
+ end
1675
+
1676
+ if size.nil? or size <= 0
1677
+ size = @font_size
1678
+ end
1679
+
1558
1680
  # Need to store the initial text state, as this will change during the
1559
1681
  # width calculation, but will need to be re-set before printing, so
1560
1682
  # that the chars work out right
@@ -1594,7 +1716,7 @@ class PDF::Writer
1594
1716
  # Reset the text state
1595
1717
  @current_text_state = t_CTS.dup
1596
1718
  current_font!
1597
- add_text(x, y, size, tmp, angle, adjust) unless test
1719
+ add_text(x, y, tmp, size, angle, adjust) unless test
1598
1720
  return text[brk + 1..-1]
1599
1721
  else # just break before the current character
1600
1722
  tmp = text[0, pos]
@@ -1604,7 +1726,7 @@ class PDF::Writer
1604
1726
  # Reset the text state
1605
1727
  @current_text_state = t_CTS.dup
1606
1728
  current_font!
1607
- add_text(x, y, size, tmp, angle, adjust) unless test
1729
+ add_text(x, y, tmp, size, angle, adjust) unless test
1608
1730
  return text[pos..-1]
1609
1731
  end
1610
1732
  end
@@ -1631,7 +1753,7 @@ class PDF::Writer
1631
1753
  # reset the text state
1632
1754
  @current_text_state = t_CTS.dup
1633
1755
  current_font!
1634
- add_text(x, y, size, text, angle, adjust) unless test
1756
+ add_text(x, y, text, size, angle, adjust) unless test
1635
1757
  return ""
1636
1758
  end
1637
1759
 
@@ -2184,7 +2306,11 @@ class PDF::Writer
2184
2306
  if tmp[page_num].kind_of?(Hash) # This must be the starting page #s
2185
2307
  status = 1
2186
2308
  info = tmp[page_num]
2187
- info[:delta] = info[:starting] - page_num
2309
+ if info[:starting]
2310
+ info[:delta] = info[:starting] - page_num
2311
+ else
2312
+ info[:delta] = page_num
2313
+ end
2188
2314
  # Also check for the special case of the numbering stopping
2189
2315
  # and starting on the same page.
2190
2316
  status = 2 if info["stopn"] or info["stoptn"]
@@ -2209,11 +2335,11 @@ class PDF::Writer
2209
2335
  when :right
2210
2336
  w = 0
2211
2337
  when :left
2212
- w = text_width(info[:size], pat)
2338
+ w = text_width(pat, info[:size])
2213
2339
  when :center
2214
- w = text_width(info[:size], pat) / 2.0
2340
+ w = text_width(pat, info[:size]) / 2.0
2215
2341
  end
2216
- add_text(info[:x] + w, info[:y], info[:size], pat)
2342
+ add_text(info[:x] + w, info[:y], pat, info[:size])
2217
2343
  close_object
2218
2344
  status = 0 if status == 2
2219
2345
  end
@@ -2242,6 +2368,8 @@ class PDF::Writer
2242
2368
  # <tt>:font_size</tt>:: The font size to be used. If not
2243
2369
  # specified, is either the last font size or
2244
2370
  # the default font size of 12 points.
2371
+ # Setting this value *changes* the current
2372
+ # #font_size.
2245
2373
  # <tt>:left</tt>:: number, gap to leave from the left margin
2246
2374
  # <tt>:right</tt>:: number, gap to leave from the right margin
2247
2375
  # <tt>:absolute_left</tt>:: number, absolute left position (overrides
@@ -2340,7 +2468,7 @@ class PDF::Writer
2340
2468
  end
2341
2469
  end
2342
2470
 
2343
- line = add_text_wrap(left, @y, right - left, size, line, just, 0, options[:test])
2471
+ line = add_text_wrap(left, @y, right - left, line, size, just, 0, options[:test])
2344
2472
  end
2345
2473
  end
2346
2474
 
@@ -2583,7 +2711,7 @@ class PDF::Writer
2583
2711
  ss.cap = :butt
2584
2712
  ss.join = :miter
2585
2713
  pdf.stroke_style! ss
2586
- pdf.stroke_color @style
2714
+ pdf.stroke_color @color
2587
2715
  pdf.circle_at(xpos, ypos, 1).stroke
2588
2716
  pdf.restore_state
2589
2717
  end
@@ -2651,11 +2779,7 @@ class PDF::Writer
2651
2779
  end
2652
2780
 
2653
2781
  # Save the PDF as a file to disk.
2654
- def save_as(name, compressed = false)
2655
- old_compressed = self.compressed
2656
- self.compressed = compressed
2782
+ def save_as(name)
2657
2783
  File.open(name, "wb") { |f| f.write self.render }
2658
- ensure
2659
- self.compressed = old_compressed
2660
2784
  end
2661
2785
  end
@@ -6,8 +6,9 @@
6
6
  # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
7
  # for full licensing information.
8
8
  #
9
- # $Id: fontmetrics.rb,v 1.3 2005/06/01 19:01:36 austin Exp $
9
+ # $Id: fontmetrics.rb,v 1.4 2005/06/16 04:28:25 austin Exp $
10
10
  #++
11
+
11
12
  class PDF::Writer::FontMetrics
12
13
  METRICS_PATH = [ File.join(File.dirname(File.expand_path(__FILE__)), 'fonts') ]
13
14
 
@@ -6,7 +6,7 @@
6
6
  # Licensed under a MIT-style licence. See LICENCE in the main distribution
7
7
  # for full licensing information.
8
8
  #
9
- # $Id: graphics.rb,v 1.10 2005/06/08 12:16:11 austin Exp $
9
+ # $Id: graphics.rb,v 1.12 2005/06/28 21:32:17 austin Exp $
10
10
  #++
11
11
  # Points for use in the drawing of polygons.
12
12
  class PDF::Writer::PolygonPoint
@@ -525,7 +525,19 @@ module PDF::Writer::Graphics
525
525
  # The +image+ parameter may be a filename or an object that returns the
526
526
  # full image data when #read is called with no parameters (such as an IO
527
527
  # object). If 'open-uri' is loaded, then the image name may be an URI.
528
- def add_image_from_file(image, x, y, width = nil, height = nil)
528
+ #
529
+ # In PDF::Writer 1.1 or later, the new +link+ parameter is a hash with
530
+ # two keys:
531
+ #
532
+ # <tt>:type</tt>:: The type of link, either <tt>:internal</tt> or
533
+ # <tt>:external</tt>.
534
+ # <tt>:target</tt>:: The destination of the link. For an
535
+ # <tt>:internal</tt> link, this is an internal
536
+ # cross-reference destination. For an
537
+ # <tt>:external</tt> link, this is an URI.
538
+ #
539
+ # This will automatically make the image a clickable link if set.
540
+ def add_image_from_file(image, x, y, width = nil, height = nil, link = nil)
529
541
  data = nil
530
542
 
531
543
  if image.respond_to?(:read)
@@ -534,15 +546,31 @@ module PDF::Writer::Graphics
534
546
  open(image, 'rb') { |ff| data = ff.read }
535
547
  end
536
548
 
537
- add_image(data, x, y, width, height)
549
+ add_image(data, x, y, width, height, nil, link)
538
550
  end
539
551
 
540
552
  # Add an image from a loaded image (JPEG or PNG) resource at position
541
553
  # <tt>(x, y)</tt> (the upper left-hand corner of the image) and scaled
542
554
  # to +width+ by +height+ units. If provided, +image_info+ is a
543
555
  # PDF::Writer::Graphics::ImageInfo object.
544
- def add_image(image, x, y, width = nil, height = nil, image_info = nil)
545
- unless image.kind_of?(PDF::Writer::External::Image)
556
+ #
557
+ # In PDF::Writer 1.1 or later, the new +link+ parameter is a hash with
558
+ # two keys:
559
+ #
560
+ # <tt>:type</tt>:: The type of link, either <tt>:internal</tt> or
561
+ # <tt>:external</tt>.
562
+ # <tt>:target</tt>:: The destination of the link. For an
563
+ # <tt>:internal</tt> link, this is an internal
564
+ # cross-reference destination. For an
565
+ # <tt>:external</tt> link, this is an URI.
566
+ #
567
+ # This will automatically make the image a clickable link if set.
568
+ def add_image(image, x, y, width = nil, height = nil, image_info = nil, link = nil)
569
+ if image.kind_of?(PDF::Writer::External::Image)
570
+ label = image.label
571
+ image_obj = image
572
+ image_info ||= image.image_info
573
+ else
546
574
  image_info ||= PDF::Writer::Graphics::ImageInfo.new(image)
547
575
 
548
576
  tt = Time.now
@@ -551,10 +579,6 @@ module PDF::Writer::Graphics
551
579
  label = "I#{id}"
552
580
  image_obj = PDF::Writer::External::Image.new(self, image, image_info, label)
553
581
  @images[id] = image_obj
554
- else
555
- label = image.label
556
- image_obj = image
557
- image_info ||= image.image_info
558
582
  end
559
583
 
560
584
  if width.nil? and height.nil?
@@ -567,6 +591,16 @@ module PDF::Writer::Graphics
567
591
 
568
592
  tt = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
569
593
  add_content(tt % [ width, height, x, y, label ])
594
+
595
+ if link
596
+ case link[:type]
597
+ when :internal
598
+ add_internal_link(link[:target], x, y, x + width, y + height)
599
+ when :external
600
+ add_link(link[:target], x, y, x + width, y + height)
601
+ end
602
+ end
603
+
570
604
  image_obj
571
605
  end
572
606
 
@@ -595,18 +629,29 @@ module PDF::Writer::Graphics
595
629
  # <tt>:border</tt>:: The border options. No default border. If
596
630
  # specified, must be either +true+, which uses
597
631
  # the default border, or a Hash.
632
+ # <tt>:link</tt>:: Makes the image a clickable link.
598
633
  #
599
634
  # Image borders are specified as a hash with two options:
600
635
  #
601
636
  # <tt>:color</tt>:: The colour of the border. Defaults to 50% grey.
602
637
  # <tt>:style</tt>:: The stroke style of the border. This must be a
603
638
  # StrokeStyle object and defaults to the default line.
639
+ #
640
+ # Image links are defined as a hash with two options:
641
+ #
642
+ # <tt>:type</tt>:: The type of link, either <tt>:internal</tt> or
643
+ # <tt>:external</tt>.
644
+ # <tt>:target</tt>:: The destination of the link. For an
645
+ # <tt>:internal</tt> link, this is an internal
646
+ # cross-reference destination. For an
647
+ # <tt>:external</tt> link, this is an URI.
604
648
  def image(image, options = {})
605
649
  width = options[:width]
606
650
  pad = options[:pad] || 5
607
651
  resize = options[:resize]
608
652
  just = options[:justification] || :left
609
653
  border = options[:border]
654
+ link = options[:link]
610
655
 
611
656
  if image.kind_of?(PDF::Writer::External::Image)
612
657
  info = image.image_info
@@ -691,6 +736,15 @@ module PDF::Writer::Graphics
691
736
  restore_state
692
737
  end
693
738
 
739
+ if link
740
+ case link[:type]
741
+ when :internal
742
+ add_internal_link(link[:target], x, y - pad, x + width, y + height - pad)
743
+ when :external
744
+ add_link(link[:target], x, y - pad, x + width, y + height - pad)
745
+ end
746
+ end
747
+
694
748
  @y = @y - pad - height
695
749
 
696
750
  image_obj
@@ -707,7 +761,9 @@ module PDF::Writer::Graphics
707
761
  # angle.
708
762
  def rotate_axis(angle)
709
763
  rad = PDF::Math.deg2rad(angle)
710
- add_content("\n1 %.3f %.3f 1 0 0 cm" % [ rad, -rad ])
764
+ tt = "\n%.3f %.3f %.3f %.3f 0 0 cm"
765
+ tx = [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad) ]
766
+ add_content(tt % tx)
711
767
  self
712
768
  end
713
769
 
@@ -721,7 +777,37 @@ module PDF::Writer::Graphics
721
777
  def skew_axis(xangle = 0, yangle = 0)
722
778
  xr = PDF::Math.deg2rad(xangle)
723
779
  yr = PDF::Math.deg2rad(yangle)
724
- add_content("\n1 %.3f %.3f 1 0 0 cm" % [ xr, -yr ])
780
+
781
+ xr = Math.tan(xr) if xangle != 0
782
+ yr = Math.tan(yr) if yangle != 0
783
+
784
+ add_content("\n1 %.3f %.3f 1 0 0 cm" % [ xr, yr ])
725
785
  self
726
786
  end
787
+
788
+ # Transforms the coordinate axis with the appended matrix. All
789
+ # transformations (including those above) are performed with this
790
+ # matrix. The transformation matrix is:
791
+ #
792
+ # +- -+
793
+ # | a c e |
794
+ # | b d f |
795
+ # | 0 0 1 |
796
+ # +- -+
797
+ #
798
+ # The six values are represented as a six-digit vector: [ a b c d e f ]
799
+ #
800
+ # * Axis translation uses [ 1 0 0 1 x y ] where x and y are the new
801
+ # (0,0) coordinates in the old axis system.
802
+ # * Scaling uses [ sx 0 0 sy 0 0 ] where sx and sy are the scaling
803
+ # factors.
804
+ # * Rotation uses [ cos(a) sin(a) -sin(a) cos(a) 0 0 ] where a is the
805
+ # angle, measured in radians.
806
+ # * X axis skewing uses [ 1 0 tan(a) 1 0 0 ] where a is the angle,
807
+ # measured in radians.
808
+ # * Y axis skewing uses [ 1 tan(a) 0 1 0 0 ] where a is the angle,
809
+ # measured in radians.
810
+ def transform_matrix(a, b, c, d, e, f)
811
+ add_content("\n%.3f %.3f %.3f %.3f %3.f %.3f cm" % [ a, b, c, d, e, f ])
812
+ end
727
813
  end