write_xlsx 1.11.2 → 1.12.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.
- 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
|