write_xlsx 1.11.2 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/README.md +1 -1
- data/lib/write_xlsx/format.rb +5 -5
- data/lib/write_xlsx/package/content_types.rb +22 -0
- data/lib/write_xlsx/package/metadata.rb +139 -22
- data/lib/write_xlsx/package/packager.rb +120 -3
- data/lib/write_xlsx/package/relationships.rb +25 -0
- data/lib/write_xlsx/package/rich_value.rb +70 -0
- data/lib/write_xlsx/package/rich_value_rel.rb +70 -0
- data/lib/write_xlsx/package/rich_value_structure.rb +83 -0
- data/lib/write_xlsx/package/rich_value_types.rb +103 -0
- data/lib/write_xlsx/utility.rb +172 -0
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +46 -166
- data/lib/write_xlsx/worksheet/cell_data.rb +19 -0
- data/lib/write_xlsx/worksheet/hyperlink.rb +2 -1
- data/lib/write_xlsx/worksheet.rb +93 -22
- metadata +7 -3
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'write_xlsx/package/xml_writer_simple'
|
5
|
+
require 'write_xlsx/utility'
|
6
|
+
|
7
|
+
module Writexlsx
|
8
|
+
module Package
|
9
|
+
#
|
10
|
+
# RichValueStructure - A class for writing the Excel XLSX rdrichvaluestructure.xml
|
11
|
+
# file.
|
12
|
+
#
|
13
|
+
# Used in conjunction with Excel::Writer::XLSX
|
14
|
+
#
|
15
|
+
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
|
16
|
+
#
|
17
|
+
# Convert to Ruby by Hideo NAKAMURA, nakamura.hideo@gmail.com
|
18
|
+
#
|
19
|
+
class RichValueStructure
|
20
|
+
include Writexlsx::Utility
|
21
|
+
|
22
|
+
attr_writer :has_embedded_descriptions
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@writer = Package::XMLWriterSimple.new
|
26
|
+
@has_embedded_descriptions = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_xml_writer(filename)
|
30
|
+
@writer.set_xml_writer(filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assemble_xml_file
|
34
|
+
write_xml_declaration do
|
35
|
+
write_rv_structures
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
#
|
42
|
+
# Write the <rvStructures> element.
|
43
|
+
#
|
44
|
+
def write_rv_structures
|
45
|
+
xmlns = 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata'
|
46
|
+
|
47
|
+
attributes = [
|
48
|
+
['xmlns', xmlns],
|
49
|
+
['count', 1]
|
50
|
+
]
|
51
|
+
|
52
|
+
@writer.tag_elements('rvStructures', attributes) do
|
53
|
+
write_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Write the <s> element.
|
59
|
+
#
|
60
|
+
def write_s
|
61
|
+
attributes = [%w[t _localImage]]
|
62
|
+
|
63
|
+
@writer.tag_elements('s', attributes) do
|
64
|
+
write_k('_rvRel:LocalImageIdentifier', 'i')
|
65
|
+
write_k('CalcOrigin', 'i')
|
66
|
+
write_k('Text', 's') if @has_embedded_descriptions
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Write the <k> element.
|
72
|
+
#
|
73
|
+
def write_k(n, t)
|
74
|
+
attributes = [
|
75
|
+
['n', n],
|
76
|
+
['t', t]
|
77
|
+
]
|
78
|
+
|
79
|
+
@writer.empty_tag('k', attributes)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'write_xlsx/package/xml_writer_simple'
|
5
|
+
require 'write_xlsx/utility'
|
6
|
+
|
7
|
+
module Writexlsx
|
8
|
+
module Package
|
9
|
+
#
|
10
|
+
# RichValueTypes - A class for writing the Excel XLSX rdRichValueTypes.xml file.
|
11
|
+
#
|
12
|
+
# Used in conjunction with Excel::Writer::XLSX
|
13
|
+
#
|
14
|
+
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
|
15
|
+
#
|
16
|
+
# Convert to Ruby by Hideo NAKAMURA, nakamura.hideo@gmail.com
|
17
|
+
#
|
18
|
+
class RichValueTypes
|
19
|
+
include Writexlsx::Utility
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@writer = Package::XMLWriterSimple.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_xml_writer(filename)
|
26
|
+
@writer.set_xml_writer(filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
def assemble_xml_file
|
30
|
+
write_xml_declaration do
|
31
|
+
write_rv_types_info
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
#
|
38
|
+
# Write the <rvTypesInfo> element.
|
39
|
+
#
|
40
|
+
def write_rv_types_info
|
41
|
+
xmlns = 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2'
|
42
|
+
xmlns_mc = 'http://schemas.openxmlformats.org/markup-compatibility/2006'
|
43
|
+
xmlns_x = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
44
|
+
|
45
|
+
attributes = [
|
46
|
+
['xmlns', xmlns],
|
47
|
+
['xmlns:mc', xmlns_mc],
|
48
|
+
['mc:Ignorable', 'x'],
|
49
|
+
['xmlns:x', xmlns_x]
|
50
|
+
]
|
51
|
+
|
52
|
+
key_flags = [
|
53
|
+
['_Self', %w[ExcludeFromFile ExcludeFromCalcComparison]],
|
54
|
+
['_DisplayString', ['ExcludeFromCalcComparison']],
|
55
|
+
['_Flags', ['ExcludeFromCalcComparison']],
|
56
|
+
['_Format', ['ExcludeFromCalcComparison']],
|
57
|
+
['_SubLabel', ['ExcludeFromCalcComparison']],
|
58
|
+
['_Attribution', ['ExcludeFromCalcComparison']],
|
59
|
+
['_Icon', ['ExcludeFromCalcComparison']],
|
60
|
+
['_Display', ['ExcludeFromCalcComparison']],
|
61
|
+
['_CanonicalPropertyNames', ['ExcludeFromCalcComparison']],
|
62
|
+
['_ClassificationId', ['ExcludeFromCalcComparison']]
|
63
|
+
]
|
64
|
+
|
65
|
+
@writer.tag_elements('rvTypesInfo', attributes) do
|
66
|
+
@writer.tag_elements('global') do
|
67
|
+
@writer.tag_elements('keyFlags') do
|
68
|
+
# Write the keyFlags element.
|
69
|
+
key_flags.each do |key_flag|
|
70
|
+
write_key(key_flag[0], key_flag[1])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Write the <key> element.
|
79
|
+
#
|
80
|
+
def write_key(name, flags = [])
|
81
|
+
attributes = [['name', name]]
|
82
|
+
|
83
|
+
@writer.tag_elements('key', attributes) do
|
84
|
+
flags.each do |flag|
|
85
|
+
write_flag(flag)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Write the <flag> element.
|
92
|
+
#
|
93
|
+
def write_flag(name)
|
94
|
+
attributes = [
|
95
|
+
['name', name],
|
96
|
+
['value', 1]
|
97
|
+
]
|
98
|
+
|
99
|
+
@writer.empty_tag('flag', attributes)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/write_xlsx/utility.rb
CHANGED
@@ -988,6 +988,178 @@ module Writexlsx
|
|
988
988
|
def write_a_end_para_rpr # :nodoc:
|
989
989
|
@writer.empty_tag('a:endParaRPr', [%w[lang en-US]])
|
990
990
|
end
|
991
|
+
|
992
|
+
#
|
993
|
+
# Extract information from the image file such as dimension, type, filename,
|
994
|
+
# and extension. Also keep track of previously seen images to optimise out
|
995
|
+
# any duplicates.
|
996
|
+
#
|
997
|
+
def get_image_properties(filename)
|
998
|
+
# Note the image_id, and previous_images mechanism isn't currently used.
|
999
|
+
x_dpi = 96
|
1000
|
+
y_dpi = 96
|
1001
|
+
|
1002
|
+
workbook = @workbook || self
|
1003
|
+
|
1004
|
+
# Open the image file and import the data.
|
1005
|
+
data = File.binread(filename)
|
1006
|
+
md5 = Digest::MD5.hexdigest(data)
|
1007
|
+
if data.unpack1('x A3') == 'PNG'
|
1008
|
+
# Test for PNGs.
|
1009
|
+
type, width, height, x_dpi, y_dpi = process_png(data)
|
1010
|
+
workbook.image_types[:png] = 1
|
1011
|
+
elsif data.unpack1('n') == 0xFFD8
|
1012
|
+
# Test for JPEG files.
|
1013
|
+
type, width, height, x_dpi, y_dpi = process_jpg(data, filename)
|
1014
|
+
workbook.image_types[:jpeg] = 1
|
1015
|
+
elsif data.unpack1('A4') == 'GIF8'
|
1016
|
+
# Test for GIFs.
|
1017
|
+
type, width, height, x_dpi, y_dpi = process_gif(data, filename)
|
1018
|
+
workbook.image_types[:gif] = 1
|
1019
|
+
elsif data.unpack1('A2') == 'BM'
|
1020
|
+
# Test for BMPs.
|
1021
|
+
type, width, height = process_bmp(data, filename)
|
1022
|
+
workbook.image_types[:bmp] = 1
|
1023
|
+
else
|
1024
|
+
# TODO. Add Image::Size to support other types.
|
1025
|
+
raise "Unsupported image format for file: #{filename}\n"
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
# Set a default dpi for images with 0 dpi.
|
1029
|
+
x_dpi = 96 if x_dpi == 0
|
1030
|
+
y_dpi = 96 if y_dpi == 0
|
1031
|
+
|
1032
|
+
[type, width, height, File.basename(filename), x_dpi, y_dpi, md5]
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
#
|
1036
|
+
# Extract width and height information from a PNG file.
|
1037
|
+
#
|
1038
|
+
def process_png(data)
|
1039
|
+
type = 'png'
|
1040
|
+
width = 0
|
1041
|
+
height = 0
|
1042
|
+
x_dpi = 96
|
1043
|
+
y_dpi = 96
|
1044
|
+
|
1045
|
+
offset = 8
|
1046
|
+
data_length = data.size
|
1047
|
+
|
1048
|
+
# Search through the image data to read the height and width in th the
|
1049
|
+
# IHDR element. Also read the DPI in the pHYs element.
|
1050
|
+
while offset < data_length
|
1051
|
+
|
1052
|
+
length = data[offset + 0, 4].unpack1("N")
|
1053
|
+
png_type = data[offset + 4, 4].unpack1("A4")
|
1054
|
+
|
1055
|
+
case png_type
|
1056
|
+
when "IHDR"
|
1057
|
+
width = data[offset + 8, 4].unpack1("N")
|
1058
|
+
height = data[offset + 12, 4].unpack1("N")
|
1059
|
+
when "pHYs"
|
1060
|
+
x_ppu = data[offset + 8, 4].unpack1("N")
|
1061
|
+
y_ppu = data[offset + 12, 4].unpack1("N")
|
1062
|
+
units = data[offset + 16, 1].unpack1("C")
|
1063
|
+
|
1064
|
+
if units == 1
|
1065
|
+
x_dpi = x_ppu * 0.0254
|
1066
|
+
y_dpi = y_ppu * 0.0254
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
offset = offset + length + 12
|
1071
|
+
|
1072
|
+
break if png_type == "IEND"
|
1073
|
+
end
|
1074
|
+
raise "#{filename}: no size data found in png image.\n" unless height
|
1075
|
+
|
1076
|
+
[type, width, height, x_dpi, y_dpi]
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def process_jpg(data, filename)
|
1080
|
+
type = 'jpeg'
|
1081
|
+
x_dpi = 96
|
1082
|
+
y_dpi = 96
|
1083
|
+
|
1084
|
+
offset = 2
|
1085
|
+
data_length = data.bytesize
|
1086
|
+
|
1087
|
+
# Search through the image data to read the JPEG markers.
|
1088
|
+
while offset < data_length
|
1089
|
+
marker = data[offset + 0, 2].unpack1("n")
|
1090
|
+
length = data[offset + 2, 2].unpack1("n")
|
1091
|
+
|
1092
|
+
# Read the height and width in the 0xFFCn elements
|
1093
|
+
# (Except C4, C8 and CC which aren't SOF markers).
|
1094
|
+
if (marker & 0xFFF0) == 0xFFC0 &&
|
1095
|
+
marker != 0xFFC4 && marker != 0xFFCC
|
1096
|
+
height = data[offset + 5, 2].unpack1("n")
|
1097
|
+
width = data[offset + 7, 2].unpack1("n")
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
# Read the DPI in the 0xFFE0 element.
|
1101
|
+
if marker == 0xFFE0
|
1102
|
+
units = data[offset + 11, 1].unpack1("C")
|
1103
|
+
x_density = data[offset + 12, 2].unpack1("n")
|
1104
|
+
y_density = data[offset + 14, 2].unpack1("n")
|
1105
|
+
|
1106
|
+
if units == 1
|
1107
|
+
x_dpi = x_density
|
1108
|
+
y_dpi = y_density
|
1109
|
+
elsif units == 2
|
1110
|
+
x_dpi = x_density * 2.54
|
1111
|
+
y_dpi = y_density * 2.54
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
offset += length + 2
|
1116
|
+
break if marker == 0xFFDA
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
raise "#{filename}: no size data found in jpeg image.\n" unless height
|
1120
|
+
|
1121
|
+
[type, width, height, x_dpi, y_dpi]
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
#
|
1125
|
+
# Extract width and height information from a GIF file.
|
1126
|
+
#
|
1127
|
+
def process_gif(data, filename)
|
1128
|
+
type = 'gif'
|
1129
|
+
x_dpi = 96
|
1130
|
+
y_dpi = 96
|
1131
|
+
|
1132
|
+
width = data[6, 2].unpack1("v")
|
1133
|
+
height = data[8, 2].unpack1("v")
|
1134
|
+
|
1135
|
+
raise "#{filename}: no size data found in gif image.\n" if height.nil?
|
1136
|
+
|
1137
|
+
[type, width, height, x_dpi, y_dpi]
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
# Extract width and height information from a BMP file.
|
1141
|
+
def process_bmp(data, filename) # :nodoc:
|
1142
|
+
type = 'bmp'
|
1143
|
+
|
1144
|
+
# Check that the file is big enough to be a bitmap.
|
1145
|
+
raise "#{filename} doesn't contain enough data." if data.bytesize <= 0x36
|
1146
|
+
|
1147
|
+
# Read the bitmap width and height. Verify the sizes.
|
1148
|
+
width, height = data.unpack("x18 V2")
|
1149
|
+
raise "#{filename}: largest image width #{width} supported is 65k." if width > 0xFFFF
|
1150
|
+
raise "#{filename}: largest image height supported is 65k." if height > 0xFFFF
|
1151
|
+
|
1152
|
+
# Read the bitmap planes and bpp data. Verify them.
|
1153
|
+
planes, bitcount = data.unpack("x26 v2")
|
1154
|
+
raise "#{filename} isn't a 24bit true color bitmap." unless bitcount == 24
|
1155
|
+
raise "#{filename}: only 1 plane supported in bitmap image." unless planes == 1
|
1156
|
+
|
1157
|
+
# Read the bitmap compression. Verify compression.
|
1158
|
+
compression = data.unpack1("x30 V")
|
1159
|
+
raise "#{filename}: compression not supported in bitmap image." unless compression == 0
|
1160
|
+
|
1161
|
+
[type, width, height]
|
1162
|
+
end
|
991
1163
|
end
|
992
1164
|
|
993
1165
|
module WriteDPtPoint
|
data/lib/write_xlsx/version.rb
CHANGED
data/lib/write_xlsx/workbook.rb
CHANGED
@@ -35,6 +35,9 @@ module Writexlsx
|
|
35
35
|
attr_reader :max_url_length # :nodoc:
|
36
36
|
attr_reader :strings_to_urls # :nodoc:
|
37
37
|
attr_reader :read_only # :nodoc:
|
38
|
+
attr_reader :embedded_image_indexes # :nodec:
|
39
|
+
attr_reader :embedded_images # :nodoc:
|
40
|
+
attr_reader :embedded_descriptions # :nodoc:
|
38
41
|
|
39
42
|
def initialize(file, *option_params)
|
40
43
|
options, default_formats = process_workbook_options(*option_params)
|
@@ -80,6 +83,9 @@ module Writexlsx
|
|
80
83
|
@has_comments = false
|
81
84
|
@read_only = 0
|
82
85
|
@has_metadata = false
|
86
|
+
@has_embedded_images = false
|
87
|
+
@has_embedded_descriptions = false
|
88
|
+
|
83
89
|
if options[:max_url_length]
|
84
90
|
@max_url_length = options[:max_url_length]
|
85
91
|
|
@@ -88,6 +94,10 @@ module Writexlsx
|
|
88
94
|
# Structures for the shared strings data.
|
89
95
|
@shared_strings = Package::SharedStrings.new
|
90
96
|
|
97
|
+
# Structures for embedded images.
|
98
|
+
@embedded_image_indexes = {}
|
99
|
+
@embedded_images = []
|
100
|
+
|
91
101
|
# Formula calculation default settings.
|
92
102
|
@calc_id = 124519
|
93
103
|
@calc_mode = 'auto'
|
@@ -530,6 +540,10 @@ module Writexlsx
|
|
530
540
|
!!@date_1904
|
531
541
|
end
|
532
542
|
|
543
|
+
def has_dynamic_functions?
|
544
|
+
@has_dynamic_functions
|
545
|
+
end
|
546
|
+
|
533
547
|
#
|
534
548
|
# Add a string to the shared string table, if it isn't already there, and
|
535
549
|
# return the string index.
|
@@ -597,6 +611,14 @@ module Writexlsx
|
|
597
611
|
@has_metadata
|
598
612
|
end
|
599
613
|
|
614
|
+
def has_embedded_images?
|
615
|
+
@has_embedded_images
|
616
|
+
end
|
617
|
+
|
618
|
+
def has_embedded_descriptions?
|
619
|
+
@has_embedded_descriptions
|
620
|
+
end
|
621
|
+
|
600
622
|
private
|
601
623
|
|
602
624
|
def filename
|
@@ -1226,10 +1248,11 @@ module Writexlsx
|
|
1226
1248
|
#
|
1227
1249
|
def prepare_metadata
|
1228
1250
|
@worksheets.each do |sheet|
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1251
|
+
next unless sheet.has_dynamic_functions? || sheet.has_embedded_images?
|
1252
|
+
|
1253
|
+
@has_metadata = true
|
1254
|
+
@has_dynamic_functions ||= sheet.has_dynamic_functions?
|
1255
|
+
@has_embedded_images ||= sheet.has_embedded_images?
|
1233
1256
|
end
|
1234
1257
|
end
|
1235
1258
|
|
@@ -1380,12 +1403,22 @@ module Writexlsx
|
|
1380
1403
|
#
|
1381
1404
|
def prepare_drawings # :nodoc:
|
1382
1405
|
chart_ref_id = 0
|
1383
|
-
image_ref_id = 0
|
1384
1406
|
drawing_id = 0
|
1385
1407
|
ref_id = 0
|
1386
1408
|
image_ids = {}
|
1387
1409
|
header_image_ids = {}
|
1388
1410
|
background_ids = {}
|
1411
|
+
|
1412
|
+
# Store the image types for any embedded images.
|
1413
|
+
@embedded_images.each do |image_data|
|
1414
|
+
store_image_types(image_data[1])
|
1415
|
+
|
1416
|
+
@has_embedded_descriptions = true if ptrue?(image_data[2])
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
# The image IDs start from after the embedded images.
|
1420
|
+
image_ref_id = @embedded_images.size
|
1421
|
+
|
1389
1422
|
@worksheets.each do |sheet|
|
1390
1423
|
chart_count = sheet.charts.size
|
1391
1424
|
image_count = sheet.images.size
|
@@ -1508,173 +1541,20 @@ module Writexlsx
|
|
1508
1541
|
end
|
1509
1542
|
|
1510
1543
|
#
|
1511
|
-
#
|
1512
|
-
#
|
1513
|
-
# any duplicates.
|
1544
|
+
# Store the image types (PNG/JPEG/etc) used in the workbook to use in these
|
1545
|
+
# Content_Types file.
|
1514
1546
|
#
|
1515
|
-
def
|
1516
|
-
|
1517
|
-
|
1518
|
-
y_dpi = 96
|
1519
|
-
|
1520
|
-
# Open the image file and import the data.
|
1521
|
-
data = File.binread(filename)
|
1522
|
-
md5 = Digest::MD5.hexdigest(data)
|
1523
|
-
if data.unpack1('x A3') == 'PNG'
|
1524
|
-
# Test for PNGs.
|
1525
|
-
type, width, height, x_dpi, y_dpi = process_png(data)
|
1547
|
+
def store_image_types(type)
|
1548
|
+
case type
|
1549
|
+
when 'png'
|
1526
1550
|
@image_types[:png] = 1
|
1527
|
-
|
1528
|
-
# Test for JPEG files.
|
1529
|
-
type, width, height, x_dpi, y_dpi = process_jpg(data, filename)
|
1551
|
+
when 'jpeg'
|
1530
1552
|
@image_types[:jpeg] = 1
|
1531
|
-
|
1532
|
-
# Test for GIFs.
|
1533
|
-
type, width, height, x_dpi, y_dpi = process_gif(data, filename)
|
1553
|
+
when 'gif'
|
1534
1554
|
@image_types[:gif] = 1
|
1535
|
-
|
1536
|
-
# Test for BMPs.
|
1537
|
-
type, width, height = process_bmp(data, filename)
|
1555
|
+
when 'bmp'
|
1538
1556
|
@image_types[:bmp] = 1
|
1539
|
-
else
|
1540
|
-
# TODO. Add Image::Size to support other types.
|
1541
|
-
raise "Unsupported image format for file: #{filename}\n"
|
1542
1557
|
end
|
1543
|
-
|
1544
|
-
# Set a default dpi for images with 0 dpi.
|
1545
|
-
x_dpi = 96 if x_dpi == 0
|
1546
|
-
y_dpi = 96 if y_dpi == 0
|
1547
|
-
|
1548
|
-
[type, width, height, File.basename(filename), x_dpi, y_dpi, md5]
|
1549
|
-
end
|
1550
|
-
|
1551
|
-
#
|
1552
|
-
# Extract width and height information from a PNG file.
|
1553
|
-
#
|
1554
|
-
def process_png(data)
|
1555
|
-
type = 'png'
|
1556
|
-
width = 0
|
1557
|
-
height = 0
|
1558
|
-
x_dpi = 96
|
1559
|
-
y_dpi = 96
|
1560
|
-
|
1561
|
-
offset = 8
|
1562
|
-
data_length = data.size
|
1563
|
-
|
1564
|
-
# Search through the image data to read the height and width in th the
|
1565
|
-
# IHDR element. Also read the DPI in the pHYs element.
|
1566
|
-
while offset < data_length
|
1567
|
-
|
1568
|
-
length = data[offset + 0, 4].unpack1("N")
|
1569
|
-
png_type = data[offset + 4, 4].unpack1("A4")
|
1570
|
-
|
1571
|
-
case png_type
|
1572
|
-
when "IHDR"
|
1573
|
-
width = data[offset + 8, 4].unpack1("N")
|
1574
|
-
height = data[offset + 12, 4].unpack1("N")
|
1575
|
-
when "pHYs"
|
1576
|
-
x_ppu = data[offset + 8, 4].unpack1("N")
|
1577
|
-
y_ppu = data[offset + 12, 4].unpack1("N")
|
1578
|
-
units = data[offset + 16, 1].unpack1("C")
|
1579
|
-
|
1580
|
-
if units == 1
|
1581
|
-
x_dpi = x_ppu * 0.0254
|
1582
|
-
y_dpi = y_ppu * 0.0254
|
1583
|
-
end
|
1584
|
-
end
|
1585
|
-
|
1586
|
-
offset = offset + length + 12
|
1587
|
-
|
1588
|
-
break if png_type == "IEND"
|
1589
|
-
end
|
1590
|
-
raise "#{filename}: no size data found in png image.\n" unless height
|
1591
|
-
|
1592
|
-
[type, width, height, x_dpi, y_dpi]
|
1593
|
-
end
|
1594
|
-
|
1595
|
-
def process_jpg(data, filename)
|
1596
|
-
type = 'jpeg'
|
1597
|
-
x_dpi = 96
|
1598
|
-
y_dpi = 96
|
1599
|
-
|
1600
|
-
offset = 2
|
1601
|
-
data_length = data.bytesize
|
1602
|
-
|
1603
|
-
# Search through the image data to read the JPEG markers.
|
1604
|
-
while offset < data_length
|
1605
|
-
marker = data[offset + 0, 2].unpack1("n")
|
1606
|
-
length = data[offset + 2, 2].unpack1("n")
|
1607
|
-
|
1608
|
-
# Read the height and width in the 0xFFCn elements
|
1609
|
-
# (Except C4, C8 and CC which aren't SOF markers).
|
1610
|
-
if (marker & 0xFFF0) == 0xFFC0 &&
|
1611
|
-
marker != 0xFFC4 && marker != 0xFFCC
|
1612
|
-
height = data[offset + 5, 2].unpack1("n")
|
1613
|
-
width = data[offset + 7, 2].unpack1("n")
|
1614
|
-
end
|
1615
|
-
|
1616
|
-
# Read the DPI in the 0xFFE0 element.
|
1617
|
-
if marker == 0xFFE0
|
1618
|
-
units = data[offset + 11, 1].unpack1("C")
|
1619
|
-
x_density = data[offset + 12, 2].unpack1("n")
|
1620
|
-
y_density = data[offset + 14, 2].unpack1("n")
|
1621
|
-
|
1622
|
-
if units == 1
|
1623
|
-
x_dpi = x_density
|
1624
|
-
y_dpi = y_density
|
1625
|
-
elsif units == 2
|
1626
|
-
x_dpi = x_density * 2.54
|
1627
|
-
y_dpi = y_density * 2.54
|
1628
|
-
end
|
1629
|
-
end
|
1630
|
-
|
1631
|
-
offset += length + 2
|
1632
|
-
break if marker == 0xFFDA
|
1633
|
-
end
|
1634
|
-
|
1635
|
-
raise "#{filename}: no size data found in jpeg image.\n" unless height
|
1636
|
-
|
1637
|
-
[type, width, height, x_dpi, y_dpi]
|
1638
|
-
end
|
1639
|
-
|
1640
|
-
#
|
1641
|
-
# Extract width and height information from a GIF file.
|
1642
|
-
#
|
1643
|
-
def process_gif(data, filename)
|
1644
|
-
type = 'gif'
|
1645
|
-
x_dpi = 96
|
1646
|
-
y_dpi = 96
|
1647
|
-
|
1648
|
-
width = data[6, 2].unpack1("v")
|
1649
|
-
height = data[8, 2].unpack1("v")
|
1650
|
-
|
1651
|
-
raise "#{filename}: no size data found in gif image.\n" if height.nil?
|
1652
|
-
|
1653
|
-
[type, width, height, x_dpi, y_dpi]
|
1654
|
-
end
|
1655
|
-
|
1656
|
-
# Extract width and height information from a BMP file.
|
1657
|
-
def process_bmp(data, filename) # :nodoc:
|
1658
|
-
type = 'bmp'
|
1659
|
-
|
1660
|
-
# Check that the file is big enough to be a bitmap.
|
1661
|
-
raise "#{filename} doesn't contain enough data." if data.bytesize <= 0x36
|
1662
|
-
|
1663
|
-
# Read the bitmap width and height. Verify the sizes.
|
1664
|
-
width, height = data.unpack("x18 V2")
|
1665
|
-
raise "#{filename}: largest image width #{width} supported is 65k." if width > 0xFFFF
|
1666
|
-
raise "#{filename}: largest image height supported is 65k." if height > 0xFFFF
|
1667
|
-
|
1668
|
-
# Read the bitmap planes and bpp data. Verify them.
|
1669
|
-
planes, bitcount = data.unpack("x26 v2")
|
1670
|
-
raise "#{filename} isn't a 24bit true color bitmap." unless bitcount == 24
|
1671
|
-
raise "#{filename}: only 1 plane supported in bitmap image." unless planes == 1
|
1672
|
-
|
1673
|
-
# Read the bitmap compression. Verify compression.
|
1674
|
-
compression = data.unpack1("x30 V")
|
1675
|
-
raise "#{filename}: compression not supported in bitmap image." unless compression == 0
|
1676
|
-
|
1677
|
-
[type, width, height]
|
1678
1558
|
end
|
1679
1559
|
end
|
1680
1560
|
end
|
@@ -206,5 +206,24 @@ module Writexlsx
|
|
206
206
|
worksheet.writer.empty_tag('c', cell_attributes(worksheet, row, row_name, col))
|
207
207
|
end
|
208
208
|
end
|
209
|
+
|
210
|
+
class EmbedImageCellData < CellData # :nodoc:
|
211
|
+
def initialize(image_index, xf)
|
212
|
+
@image_index = image_index
|
213
|
+
@xf = xf
|
214
|
+
end
|
215
|
+
|
216
|
+
def write_cell(worksheet, row, row_name, col)
|
217
|
+
attributes = cell_attributes(worksheet, row, row_name, col)
|
218
|
+
|
219
|
+
# Write a error value (mainly for embedded images).
|
220
|
+
attributes << %w[t e]
|
221
|
+
attributes << ['vm', @image_index]
|
222
|
+
|
223
|
+
worksheet.writer.tag_elements('c', attributes) do
|
224
|
+
worksheet.write_cell_value('#VALUE!')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
209
228
|
end
|
210
229
|
end
|