pdf-writer 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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