write_xlsx 1.12.1 → 1.12.2

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/Changes +3 -0
  4. data/LICENSE.txt +1 -1
  5. data/examples/autofilter.rb +1 -2
  6. data/examples/colors.rb +4 -4
  7. data/examples/formats.rb +14 -14
  8. data/lib/write_xlsx/chart/area.rb +1 -1
  9. data/lib/write_xlsx/chart/axis.rb +4 -4
  10. data/lib/write_xlsx/chart/bar.rb +1 -1
  11. data/lib/write_xlsx/chart/caption.rb +8 -4
  12. data/lib/write_xlsx/chart/column.rb +1 -1
  13. data/lib/write_xlsx/chart/doughnut.rb +2 -2
  14. data/lib/write_xlsx/chart/line.rb +1 -1
  15. data/lib/write_xlsx/chart/pie.rb +2 -2
  16. data/lib/write_xlsx/chart/radar.rb +1 -1
  17. data/lib/write_xlsx/chart/scatter.rb +1 -1
  18. data/lib/write_xlsx/chart/series.rb +10 -20
  19. data/lib/write_xlsx/chart/stock.rb +1 -1
  20. data/lib/write_xlsx/chart.rb +14 -21
  21. data/lib/write_xlsx/chartsheet.rb +3 -3
  22. data/lib/write_xlsx/drawing.rb +108 -114
  23. data/lib/write_xlsx/format.rb +20 -24
  24. data/lib/write_xlsx/image.rb +89 -0
  25. data/lib/write_xlsx/image_property.rb +163 -0
  26. data/lib/write_xlsx/inserted_chart.rb +42 -0
  27. data/lib/write_xlsx/package/button.rb +58 -5
  28. data/lib/write_xlsx/package/conditional_format.rb +4 -4
  29. data/lib/write_xlsx/package/packager.rb +22 -27
  30. data/lib/write_xlsx/package/rich_value.rb +1 -1
  31. data/lib/write_xlsx/package/styles.rb +1 -1
  32. data/lib/write_xlsx/package/vml.rb +10 -19
  33. data/lib/write_xlsx/shape.rb +3 -2
  34. data/lib/write_xlsx/sparkline.rb +1 -1
  35. data/lib/write_xlsx/utility.rb +8 -203
  36. data/lib/write_xlsx/version.rb +1 -1
  37. data/lib/write_xlsx/workbook.rb +87 -175
  38. data/lib/write_xlsx/worksheet/data_validation.rb +1 -1
  39. data/lib/write_xlsx/worksheet/hyperlink.rb +2 -2
  40. data/lib/write_xlsx/worksheet.rb +478 -484
  41. data/lib/write_xlsx/zip_file_utils.rb +1 -1
  42. data/write_xlsx.gemspec +3 -3
  43. metadata +11 -11
@@ -6,8 +6,11 @@ require 'write_xlsx/utility'
6
6
 
7
7
  module Writexlsx
8
8
  class Drawing
9
+ include Writexlsx::Utility
10
+
9
11
  attr_accessor :type, :dimensions, :width, :height, :shape, :anchor, :rel_index, :url_rel_index, :name, :description
10
12
  attr_reader :tip, :decorative
13
+ attr_writer :writer, :embedded, :orientation
11
14
 
12
15
  def initialize(type, dimensions, width, height, shape, anchor, rel_index = nil, url_rel_index = nil, tip = nil, name = nil, description = nil, decorative = nil)
13
16
  @type = type
@@ -23,93 +26,11 @@ module Writexlsx
23
26
  @description = description
24
27
  @decorative = decorative
25
28
  end
26
- end
27
-
28
- class Drawings
29
- include Writexlsx::Utility
30
-
31
- attr_writer :embedded, :orientation
32
-
33
- def initialize
34
- @writer = Package::XMLWriterSimple.new
35
- @drawings = []
36
- @embedded = false
37
- @orientation = false
38
- end
39
-
40
- def xml_str
41
- @writer.string
42
- end
43
-
44
- def set_xml_writer(filename)
45
- @writer.set_xml_writer(filename)
46
- end
47
-
48
- #
49
- # Assemble and write the XML file.
50
- #
51
- def assemble_xml_file
52
- write_xml_declaration do
53
- # Write the xdr:wsDr element.
54
- write_drawing_workspace do
55
- if @embedded
56
- index = 0
57
- @drawings.each do |drawing|
58
- # Write the xdr:twoCellAnchor element.
59
- index += 1
60
- write_two_cell_anchor(index, drawing)
61
- end
62
- else
63
- # Write the xdr:absoluteAnchor element.
64
- write_absolute_anchor(1)
65
- end
66
- end
67
- end
68
- end
69
-
70
- #
71
- # Add a chart, image or shape sub object to the drawing.
72
- #
73
- def add_drawing_object(drawing)
74
- @drawings << drawing
75
- end
76
-
77
- private
78
-
79
- #
80
- # Write the <xdr:wsDr> element.
81
- #
82
- def write_drawing_workspace(&block)
83
- schema = 'http://schemas.openxmlformats.org/drawingml/'
84
- attributes = [
85
- ['xmlns:xdr', "#{schema}2006/spreadsheetDrawing"],
86
- ['xmlns:a', "#{schema}2006/main"]
87
- ]
88
-
89
- @writer.tag_elements('xdr:wsDr', attributes, &block)
90
- end
91
29
 
92
30
  #
93
31
  # Write the <xdr:twoCellAnchor> element.
94
32
  #
95
- def write_two_cell_anchor(*args)
96
- index, drawing = args
97
-
98
- type = drawing.type
99
- width = drawing.width
100
- height = drawing.height
101
- shape = drawing.shape
102
- anchor = drawing.anchor
103
- rel_index = drawing.rel_index
104
- url_rel_index = drawing.url_rel_index
105
- tip = drawing.tip
106
- name = drawing.name
107
- description = drawing.description
108
- decorative = drawing.decorative
109
-
110
- col_from, row_from, col_from_offset, row_from_offset,
111
- col_to, row_to, col_to_offset, row_to_offset, col_absolute, row_absolute = drawing.dimensions
112
-
33
+ def write_two_cell_anchor(index)
113
34
  attributes = []
114
35
 
115
36
  # Add attribute for images.
@@ -124,9 +45,9 @@ module Writexlsx
124
45
 
125
46
  @writer.tag_elements('xdr:twoCellAnchor', attributes) do
126
47
  # Write the xdr:from element.
127
- write_from(col_from, row_from, col_from_offset, row_from_offset)
48
+ write_from
128
49
  # Write the xdr:to element.
129
- write_to(col_to, row_to, col_to_offset, row_to_offset)
50
+ write_to
130
51
 
131
52
  if type == 1
132
53
  # Graphic frame.
@@ -135,14 +56,10 @@ module Writexlsx
135
56
  write_graphic_frame(index, rel_index, name, description, decorative)
136
57
  elsif type == 2
137
58
  # Write the xdr:pic element.
138
- write_pic(
139
- index, rel_index, col_absolute,
140
- row_absolute, width, height,
141
- description, url_rel_index, tip, decorative
142
- )
59
+ write_pic(index)
143
60
  else
144
61
  # Write the xdr:sp element for shapes.
145
- write_sp(index, col_absolute, row_absolute, width, height, shape)
62
+ write_sp(index, shape)
146
63
  end
147
64
 
148
65
  # Write the xdr:clientData element.
@@ -182,7 +99,9 @@ module Writexlsx
182
99
  #
183
100
  # Write the <xdr:from> element.
184
101
  #
185
- def write_from(col, row, col_offset, row_offset)
102
+ def write_from
103
+ col, row, col_offset, row_offset = dimensions
104
+
186
105
  @writer.tag_elements('xdr:from') do
187
106
  # Write the xdr:col element.
188
107
  write_col(col)
@@ -198,7 +117,9 @@ module Writexlsx
198
117
  #
199
118
  # Write the <xdr:to> element.
200
119
  #
201
- def write_to(col, row, col_offset, row_offset)
120
+ def write_to
121
+ col, row, col_offset, row_offset = dimensions[4, 4]
122
+
202
123
  @writer.tag_elements('xdr:to') do
203
124
  # Write the xdr:col element.
204
125
  write_col(col)
@@ -290,7 +211,6 @@ module Writexlsx
290
211
  def write_nv_graphic_frame_pr(
291
212
  index, name = nil, description = nil, decorative = nil
292
213
  )
293
-
294
214
  name = "Chart #{index}" unless ptrue?(name)
295
215
 
296
216
  @writer.tag_elements('xdr:nvGraphicFramePr') do
@@ -522,7 +442,7 @@ module Writexlsx
522
442
  #
523
443
  # Write the <xdr:sp> element.
524
444
  #
525
- def write_sp(index, col_absolute, row_absolute, width, height, shape)
445
+ def write_sp(index, shape)
526
446
  if shape.connect == 0
527
447
  # Add attribute for shapes.
528
448
  attributes = [
@@ -534,7 +454,7 @@ module Writexlsx
534
454
  write_nv_sp_pr(index, shape)
535
455
 
536
456
  # Write the xdr:spPr element.
537
- write_xdr_sp_pr(col_absolute, row_absolute, width, height, shape)
457
+ write_xdr_sp_pr(shape)
538
458
 
539
459
  # Write the xdr:txBody element.
540
460
  write_tx_body(shape) if shape.text != 0
@@ -546,7 +466,7 @@ module Writexlsx
546
466
  write_nv_cxn_sp_pr(index, shape)
547
467
 
548
468
  # Write the xdr:spPr element.
549
- write_xdr_sp_pr(col_absolute, row_absolute, width, height, shape)
469
+ write_xdr_sp_pr(shape)
550
470
  end
551
471
  end
552
472
  end
@@ -601,26 +521,29 @@ module Writexlsx
601
521
  #
602
522
  # Write the <xdr:pic> element.
603
523
  #
604
- def write_pic(index, rel_index, col_absolute, row_absolute, width, height, description, url_rel_index, tip, decorative)
524
+ def write_pic(index)
525
+ col_absolute, row_absolute = dimensions[8, 2]
526
+
605
527
  @writer.tag_elements('xdr:pic') do
606
528
  # Write the xdr:nvPicPr element.
607
- write_nv_pic_pr(index, rel_index, description, url_rel_index, tip, decorative)
529
+ write_nv_pic_pr(index)
608
530
  # Write the xdr:blipFill element.
609
- write_blip_fill(rel_index)
531
+ write_blip_fill
610
532
 
611
533
  # Pictures are rectangle shapes by default.
612
- shape = Shape.new
613
- shape.type = 'rect'
534
+ pic_shape = Shape.new
535
+ pic_shape.type = 'rect'
614
536
 
615
537
  # Write the xdr:spPr element.
616
- write_sp_pr(col_absolute, row_absolute, width, height, shape)
538
+ write_sp_pr(pic_shape)
617
539
  end
618
540
  end
619
541
 
620
542
  #
621
543
  # Write the <xdr:nvPicPr> element.
622
544
  #
623
- def write_nv_pic_pr(index, _rel_index, description, url_rel_index, tip, decorative)
545
+ # def write_nv_pic_pr(index, _rel_index, description, url_rel_index, tip, decorative)
546
+ def write_nv_pic_pr(index)
624
547
  @writer.tag_elements('xdr:nvPicPr') do
625
548
  # Write the xdr:cNvPr element.
626
549
  write_c_nv_pr(
@@ -656,10 +579,10 @@ module Writexlsx
656
579
  #
657
580
  # Write the <xdr:blipFill> element.
658
581
  #
659
- def write_blip_fill(index)
582
+ def write_blip_fill
660
583
  @writer.tag_elements('xdr:blipFill') do
661
584
  # Write the a:blip element.
662
- write_a_blip(index)
585
+ write_a_blip(rel_index)
663
586
  # Write the a:stretch element.
664
587
  write_a_stretch
665
588
  end
@@ -701,10 +624,10 @@ module Writexlsx
701
624
  #
702
625
  # Write the <xdr:spPr> element, for charts.
703
626
  #
704
- def write_sp_pr(col_absolute, row_absolute, width, height, shape = {})
627
+ def write_sp_pr(shape)
705
628
  @writer.tag_elements('xdr:spPr') do
706
629
  # Write the a:xfrm element.
707
- write_a_xfrm(col_absolute, row_absolute, width, height)
630
+ write_a_xfrm
708
631
  # Write the a:prstGeom element.
709
632
  write_a_prst_geom(shape)
710
633
  end
@@ -713,12 +636,12 @@ module Writexlsx
713
636
  #
714
637
  # Write the <xdr:spPr> element for shapes.
715
638
  #
716
- def write_xdr_sp_pr(col_absolute, row_absolute, width, height, shape)
639
+ def write_xdr_sp_pr(shape)
717
640
  attributes = [%w[bwMode auto]]
718
641
 
719
642
  @writer.tag_elements('xdr:spPr', attributes) do
720
643
  # Write the a:xfrm element.
721
- write_a_xfrm(col_absolute, row_absolute, width, height, shape)
644
+ write_a_xfrm(shape)
722
645
 
723
646
  # Write the a:prstGeom element.
724
647
  write_a_prst_geom(shape)
@@ -738,7 +661,7 @@ module Writexlsx
738
661
  #
739
662
  # Write the <a:xfrm> element.
740
663
  #
741
- def write_a_xfrm(col_absolute, row_absolute, width, height, shape = nil)
664
+ def write_a_xfrm(shape = nil)
742
665
  attributes = []
743
666
 
744
667
  rotation = shape ? shape.rotation : 0
@@ -750,7 +673,7 @@ module Writexlsx
750
673
 
751
674
  @writer.tag_elements('a:xfrm', attributes) do
752
675
  # Write the a:off element.
753
- write_a_off(col_absolute, row_absolute)
676
+ write_a_off
754
677
  # Write the a:ext element.
755
678
  write_a_ext(width, height)
756
679
  end
@@ -759,7 +682,9 @@ module Writexlsx
759
682
  #
760
683
  # Write the <a:off> element.
761
684
  #
762
- def write_a_off(x, y)
685
+ def write_a_off
686
+ x, y = dimensions[8, 2]
687
+
763
688
  attributes = [
764
689
  ['x', x],
765
690
  ['y', y]
@@ -923,7 +848,7 @@ module Writexlsx
923
848
  @writer.tag_elements('a:rPr', attributes) do
924
849
  color = shape.format[:color]
925
850
  if color
926
- color = shape.palette_color(color)
851
+ color = shape.palette_color_from_index(color)
927
852
  color = color.sub(/^FF/, '') # Remove leading FF from rgb for shape color.
928
853
  else
929
854
  color = '000000'
@@ -944,4 +869,73 @@ module Writexlsx
944
869
  end
945
870
  end
946
871
  end
872
+
873
+ class Drawings
874
+ include Writexlsx::Utility
875
+
876
+ attr_writer :embedded, :orientation
877
+
878
+ def initialize
879
+ @writer = Package::XMLWriterSimple.new
880
+ @drawings = []
881
+ @embedded = false
882
+ @orientation = false
883
+ end
884
+
885
+ def xml_str
886
+ @writer.string
887
+ end
888
+
889
+ def set_xml_writer(filename)
890
+ @writer.set_xml_writer(filename)
891
+ end
892
+
893
+ #
894
+ # Assemble and write the XML file.
895
+ #
896
+ def assemble_xml_file
897
+ write_xml_declaration do
898
+ # Write the xdr:wsDr element.
899
+ write_drawing_workspace do
900
+ if @embedded
901
+ @drawings.each_with_index do |drawing, index|
902
+ # Write the xdr:twoCellAnchor element.
903
+ drawing.writer = @writer
904
+ drawing.embedded = @embedded
905
+ drawing.write_two_cell_anchor(index + 1)
906
+ end
907
+ else
908
+ # Write the xdr:absoluteAnchor element.
909
+ drawing = Drawing.new(nil, nil, nil, nil, nil, nil)
910
+ drawing.writer = @writer
911
+ drawing.embedded = @embedded
912
+ drawing.orientation = @orientation
913
+ drawing.write_absolute_anchor(1)
914
+ end
915
+ end
916
+ end
917
+ end
918
+
919
+ #
920
+ # Add a chart, image or shape sub object to the drawing.
921
+ #
922
+ def add_drawing_object(drawing)
923
+ @drawings << drawing
924
+ end
925
+
926
+ private
927
+
928
+ #
929
+ # Write the <xdr:wsDr> element.
930
+ #
931
+ def write_drawing_workspace(&block)
932
+ schema = 'http://schemas.openxmlformats.org/drawingml/'
933
+ attributes = [
934
+ ['xmlns:xdr', "#{schema}2006/spreadsheetDrawing"],
935
+ ['xmlns:a', "#{schema}2006/main"]
936
+ ]
937
+
938
+ @writer.tag_elements('xdr:wsDr', attributes, &block)
939
+ end
940
+ end
947
941
  end
@@ -9,10 +9,10 @@ module Writexlsx
9
9
 
10
10
  attr_reader :xf_index, :dxf_index, :num_format # :nodoc:
11
11
  attr_reader :underline, :font_script, :size, :theme, :font, :font_family, :hyperlink, :xf_id # :nodoc:
12
- attr_reader :diag_type, :diag_color, :font_only, :color_indexed # :nodoc:
12
+ attr_reader :diag_type, :diag_color, :font_only, :color_indexed # :nodoc:
13
13
  attr_reader :left, :left_color, :right, :right_color, :top, :top_color, :bottom, :bottom_color # :nodoc:
14
14
  attr_reader :font_scheme # :nodoc:
15
- attr_accessor :quote_prefix, :num_format_index, :border_index, :font_index # :nodoc:
15
+ attr_accessor :quote_prefix, :num_format_index, :border_index, :font_index # :nodoc:
16
16
  attr_accessor :fill_index, :font_condense, :font_extend, :diag_border # :nodoc:
17
17
  attr_accessor :bg_color, :fg_color, :pattern # :nodoc:
18
18
 
@@ -166,7 +166,7 @@ module Writexlsx
166
166
  align << %w[horizontal right] if @text_h_align == 3
167
167
  align << %w[horizontal fill] if @text_h_align == 4
168
168
  align << %w[horizontal justify] if @text_h_align == 5
169
- align << ['horizontal', continuous] if @text_h_align == 6
169
+ align << ['horizontal', continuous] if @text_h_align == 6
170
170
  align << %w[horizontal distributed] if @text_h_align == 7
171
171
 
172
172
  align << ['justifyLastLine', 1] if @just_distrib != 0
@@ -345,28 +345,24 @@ module Writexlsx
345
345
 
346
346
  location = location.downcase
347
347
 
348
- set_text_h_align(1) if location == 'left'
349
- set_text_h_align(2) if location == 'centre'
350
- set_text_h_align(2) if location == 'center'
351
- set_text_h_align(3) if location == 'right'
352
- set_text_h_align(4) if location == 'fill'
353
- set_text_h_align(5) if location == 'justify'
354
- set_text_h_align(6) if location == 'center_across'
355
- set_text_h_align(6) if location == 'centre_across'
356
- set_text_h_align(6) if location == 'merge' # Legacy.
357
- set_text_h_align(7) if location == 'distributed'
358
- set_text_h_align(7) if location == 'equal_space' # S::PE.
359
- set_text_h_align(7) if location == 'justify_distributed'
348
+ case location
349
+ when 'left' then set_text_h_align(1)
350
+ when 'centre', 'center' then set_text_h_align(2)
351
+ when 'right' then set_text_h_align(3)
352
+ when 'fill' then set_text_h_align(4)
353
+ when 'justify' then set_text_h_align(5)
354
+ when 'center_across', 'centre_across', 'merge'
355
+ set_text_h_align(6)
356
+ when 'distributed', 'equal_space', 'justify_distributed'
357
+ set_text_h_align(7)
358
+ when 'top' then set_text_v_align(1)
359
+ when 'vcentre', 'vcenter' then set_text_v_align(2)
360
+ when 'bottom' then set_text_v_align(3)
361
+ when 'vjustify' then set_text_v_align(4)
362
+ when 'vdistributed', 'vequal_space' then set_text_v_align(5)
363
+ end
360
364
 
361
365
  @just_distrib = 1 if location == 'justify_distributed'
362
-
363
- set_text_v_align(1) if location == 'top'
364
- set_text_v_align(2) if location == 'vcentre'
365
- set_text_v_align(2) if location == 'vcenter'
366
- set_text_v_align(3) if location == 'bottom'
367
- set_text_v_align(4) if location == 'vjustify'
368
- set_text_v_align(5) if location == 'vdistributed'
369
- set_text_v_align(5) if location == 'vequal_space' # S::PE.
370
366
  end
371
367
 
372
368
  #
@@ -419,7 +415,7 @@ module Writexlsx
419
415
  def set_rotation(rotation)
420
416
  if rotation == 270
421
417
  rotation = 255
422
- elsif rotation >= -90 && rotation <= 90
418
+ elsif rotation.between?(-90, 90)
423
419
  rotation = -rotation + 90 if rotation < 0
424
420
  else
425
421
  raise "Rotation #{rotation} outside range: -90 <= angle <= 90"
@@ -0,0 +1,89 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'write_xlsx/image_property'
5
+
6
+ module Writexlsx
7
+ class Image
8
+ attr_reader :row, :col, :x_offset, :y_offset, :x_scale, :y_scale
9
+ attr_reader :url, :tip, :anchor, :description, :decorative
10
+
11
+ def initialize(
12
+ row, col, image, x_offset, y_offset, x_scale, y_scale,
13
+ url, tip, anchor, description, decorative
14
+ )
15
+ @row = row
16
+ @col = col
17
+ @image = ImageProperty.new(image)
18
+ @x_offset = x_offset
19
+ @y_offset = y_offset
20
+ @x_scale = x_scale
21
+ @y_scale = y_scale
22
+ @url = url
23
+ @tip = tip
24
+ @anchor = anchor
25
+ @description = description
26
+ @decorative = decorative
27
+ end
28
+
29
+ def scaled_width
30
+ width * x_scale * 96.0 / x_dpi
31
+ end
32
+
33
+ def scaled_height
34
+ height * y_scale * 96.0 / y_dpi
35
+ end
36
+
37
+ def width_emus
38
+ (0.5 + (scaled_width * 9_525)).to_i
39
+ end
40
+
41
+ def height_emus
42
+ (0.5 + (scaled_height * 9_525)).to_i
43
+ end
44
+
45
+ def image
46
+ @image.filename
47
+ end
48
+
49
+ def type
50
+ @image.type
51
+ end
52
+
53
+ def width
54
+ @image.width
55
+ end
56
+
57
+ def height
58
+ @image.height
59
+ end
60
+
61
+ def name
62
+ @image.name
63
+ end
64
+
65
+ def x_dpi
66
+ @image.x_dpi
67
+ end
68
+
69
+ def y_dpi
70
+ @image.y_dpi
71
+ end
72
+
73
+ def md5
74
+ @image.md5
75
+ end
76
+
77
+ def filename
78
+ @image.filename
79
+ end
80
+
81
+ def position
82
+ @image.position
83
+ end
84
+
85
+ def ref_id
86
+ @image.ref_id
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,163 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'digest/md5'
5
+
6
+ module Writexlsx
7
+ class ImageProperty
8
+ attr_reader :type, :width, :height, :name, :x_dpi, :y_dpi, :md5
9
+ attr_reader :filename, :description, :decorative
10
+ attr_accessor :ref_id, :body, :position
11
+
12
+ def initialize(filename, options = {})
13
+ @filename = filename
14
+ @description = options[:description]
15
+ @decorative = options[:decorative]
16
+ @position = options[:position]
17
+ @name = File.basename(filename)
18
+
19
+ # Open the image file and import the data.
20
+ data = File.binread(filename)
21
+ @md5 = Digest::MD5.hexdigest(data)
22
+ if data.unpack1('x A3') == 'PNG'
23
+ process_png(data)
24
+ elsif data.unpack1('n') == 0xFFD8
25
+ process_jpg(data)
26
+ elsif data.unpack1('A4') == 'GIF8'
27
+ process_gif(data)
28
+ elsif data.unpack1('A2') == 'BM'
29
+ process_bmp(data)
30
+ else
31
+ # TODO. Add Image::Size to support other types.
32
+ raise "Unsupported image format for file: #{filename}\n"
33
+ end
34
+
35
+ # Set a default dpi for images with 0 dpi.
36
+ @x_dpi = 96 if @x_dpi == 0
37
+ @y_dpi = 96 if @y_dpi == 0
38
+ end
39
+
40
+ #
41
+ # Extract width and height information from a PNG file.
42
+ #
43
+ def process_png(data)
44
+ @type = 'png'
45
+ @width = 0
46
+ @height = 0
47
+ @x_dpi = 96
48
+ @y_dpi = 96
49
+
50
+ offset = 8
51
+ data_length = data.size
52
+
53
+ # Search through the image data to read the height and width in th the
54
+ # IHDR element. Also read the DPI in the pHYs element.
55
+ while offset < data_length
56
+
57
+ length = data[offset + 0, 4].unpack1("N")
58
+ png_type = data[offset + 4, 4].unpack1("A4")
59
+
60
+ case png_type
61
+ when "IHDR"
62
+ @width = data[offset + 8, 4].unpack1("N")
63
+ @height = data[offset + 12, 4].unpack1("N")
64
+ when "pHYs"
65
+ x_ppu = data[offset + 8, 4].unpack1("N")
66
+ y_ppu = data[offset + 12, 4].unpack1("N")
67
+ units = data[offset + 16, 1].unpack1("C")
68
+
69
+ if units == 1
70
+ @x_dpi = x_ppu * 0.0254
71
+ @y_dpi = y_ppu * 0.0254
72
+ end
73
+ end
74
+
75
+ offset = offset + length + 12
76
+
77
+ break if png_type == "IEND"
78
+ end
79
+ raise "#{@filename}: no size data found in png image.\n" unless @height
80
+ end
81
+
82
+ def process_jpg(data)
83
+ @type = 'jpeg'
84
+ @x_dpi = 96
85
+ @y_dpi = 96
86
+
87
+ offset = 2
88
+ data_length = data.bytesize
89
+
90
+ # Search through the image data to read the JPEG markers.
91
+ while offset < data_length
92
+ marker = data[offset + 0, 2].unpack1("n")
93
+ length = data[offset + 2, 2].unpack1("n")
94
+
95
+ # Read the height and width in the 0xFFCn elements
96
+ # (Except C4, C8 and CC which aren't SOF markers).
97
+ if (marker & 0xFFF0) == 0xFFC0 &&
98
+ marker != 0xFFC4 && marker != 0xFFCC
99
+ @height = data[offset + 5, 2].unpack1("n")
100
+ @width = data[offset + 7, 2].unpack1("n")
101
+ end
102
+
103
+ # Read the DPI in the 0xFFE0 element.
104
+ if marker == 0xFFE0
105
+ units = data[offset + 11, 1].unpack1("C")
106
+ x_density = data[offset + 12, 2].unpack1("n")
107
+ y_density = data[offset + 14, 2].unpack1("n")
108
+
109
+ if units == 1
110
+ @x_dpi = x_density
111
+ @y_dpi = y_density
112
+ elsif units == 2
113
+ @x_dpi = x_density * 2.54
114
+ @y_dpi = y_density * 2.54
115
+ end
116
+ end
117
+
118
+ offset += length + 2
119
+ break if marker == 0xFFDA
120
+ end
121
+
122
+ raise "#{@filename}: no size data found in jpeg image.\n" unless @height
123
+ end
124
+
125
+ #
126
+ # Extract width and height information from a GIF file.
127
+ #
128
+ def process_gif(data)
129
+ @type = 'gif'
130
+ @x_dpi = 96
131
+ @y_dpi = 96
132
+
133
+ @width = data[6, 2].unpack1("v")
134
+ @height = data[8, 2].unpack1("v")
135
+
136
+ raise "#{@filename}: no size data found in gif image.\n" if @height.nil?
137
+ end
138
+
139
+ # Extract width and height information from a BMP file.
140
+ def process_bmp(data) # :nodoc:
141
+ @type = 'bmp'
142
+
143
+ # Check that the file is big enough to be a bitmap.
144
+ raise "#{@filename} doesn't contain enough data." if data.bytesize <= 0x36
145
+
146
+ # Read the bitmap width and height. Verify the sizes.
147
+ @width, @height = data.unpack("x18 V2")
148
+ raise "#{@filename}: largest image width #{width} supported is 65k." if width > 0xFFFF
149
+ raise "#{@filename}: largest image height supported is 65k." if @height > 0xFFFF
150
+
151
+ # Read the bitmap planes and bpp data. Verify them.
152
+ planes, bitcount = data.unpack("x26 v2")
153
+ raise "#{@filename} isn't a 24bit true color bitmap." unless bitcount == 24
154
+ raise "#{@filename}: only 1 plane supported in bitmap image." unless planes == 1
155
+
156
+ # Read the bitmap compression. Verify compression.
157
+ compression = data.unpack1("x30 V")
158
+ raise "#{@filename}: compression not supported in bitmap image." unless compression == 0
159
+
160
+ @x_dpi = @y_dpi = 96
161
+ end
162
+ end
163
+ end