standard-procedure-consolidate 0.4.3 → 0.4.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3a7f02d5159a44a47af30100fe1743b2b5bff5ab845a0da2170ffaf44302f03
4
- data.tar.gz: b041806b3d682a4c5ccc0011025a1d3d5b25a7c1a65fda56e9b3fcce34b364a9
3
+ metadata.gz: 81a63ebba28600dbc0e124ab27e8c6cf150f8dafc80942a12c5e22691bbf8b88
4
+ data.tar.gz: cbfad371ee9b13baa9eb1fd8aefa9aecf8e1979d581e9b490df7ebdf01706ec3
5
5
  SHA512:
6
- metadata.gz: 2bc84355758ef68cd525c684fe1ef6973858eb3ba360f4a37a026cc54a11c9eb143cc364bd034cdd4452bec75a91a768b7f96ccbb727039da5e6ba727fb643bd
7
- data.tar.gz: b8061eb3ff1a61deb3c1b153be11d9c5b416be6a576d559c0846236a1e8dabdf9dcf339f33479e39589d3c247930cb28d437a360becbe5e498e90b99eb26ffea
6
+ metadata.gz: 856086986d7a075cefe919716d039481ac80480ee169b00d6038d9aeef751338daf4fab90889c96fbd00d183708fa610c450df3d14f704aa9c6d515f5a1a5b54
7
+ data.tar.gz: ef54556ae1258cf20b08a3a519c1744f6010d80c955e3d26469bf2710a76b2ddf70734c4fe9d76920ab06ca8fbc0d08741a7b2f2dfdf5a0f1769524dcc642b3d
@@ -0,0 +1 @@
1
+ c1a13d3d2b2c50741b839e2ca20c730b2689fac090e9c67660950a506ce83b3b9e07e31f3de205ec67dcbf4b1eff462f8fc507468fdb378e864e9a5952b9decf
@@ -0,0 +1 @@
1
+ cb04bbf111020966f8acc5dbe0b4d8917d4b6603e6dae83afc15d81e1c9c9163072d3ba7c177a2a0a127d9dc4a5d2e3a5f7177d957bb02e4411ec7301587106b
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "zip"
4
4
  require "nokogiri"
5
-
6
5
  module Consolidate
7
6
  module Docx
8
7
  class Image < SimpleDelegator
@@ -12,21 +11,26 @@ module Consolidate
12
11
  # Path to use when storing this image within the docx
13
12
  def storage_path = "word/#{media_path}"
14
13
 
15
- # Convert width from pixels to EMU
16
- def width = super * emu_per_width_pixel
14
+ # Convert width from pixels to EMU with proper DPI scaling
15
+ def width_in_emu = width * emu_per_width_pixel
17
16
 
18
- # Convert height from pixels to EMU
19
- def height = super * emu_per_height_pixel
17
+ # Convert height from pixels to EMU with proper DPI scaling
18
+ def height_in_emu = height * emu_per_height_pixel
20
19
 
21
- # Get the width of this image in EMU up to a maximum page width (also in EMU)
22
- def clamped_width(maximum = 7_772_400) = [width, maximum].min
20
+ def emu_per_width_pixel = EMU_PER_PIXEL * 72 / dpi[:x]
23
21
 
24
- # Get the height of this image in EMU adjusted for a maximum page width (also in EMU)
25
- def clamped_height(maximum = 7_772_400) = (height * clamped_width(maximum).to_f / width.to_f).to_i
22
+ def emu_per_height_pixel = EMU_PER_PIXEL * 72 / dpi[:y]
26
23
 
27
- def emu_per_width_pixel = 914_400 / dpi[:x]
24
+ # Constants
25
+ DEFAULT_PAGE_WIDTH = 12_240
26
+ TWENTIETHS_OF_A_POINT_TO_EMU = 635
27
+ DEFAULT_PAGE_WIDTH_IN_EMU = DEFAULT_PAGE_WIDTH * TWENTIETHS_OF_A_POINT_TO_EMU
28
+ EMU_PER_PIXEL = 9525
29
+ DEFAULT_PAGE_HEIGHT = DEFAULT_PAGE_WIDTH * 11 / 8.5 # Assuming US Letter size
30
+ DEFAULT_PAGE_HEIGHT_IN_EMU = DEFAULT_PAGE_HEIGHT * TWENTIETHS_OF_A_POINT_TO_EMU
28
31
 
29
- def emu_per_height_pixel = 914_400 / dpi[:y]
32
+ # Common page margins in EMU (0.75 inches)
33
+ DEFAULT_MARGIN_IN_EMU = 685800
30
34
  end
31
35
  end
32
36
  end
@@ -2,148 +2,202 @@
2
2
 
3
3
  require "zip"
4
4
  require "nokogiri"
5
-
5
+ # To test image scaling manually, from the console:
6
+ # Consolidate::Docx::Merge.open "spec/files/logo-doc.docx" do |doc|
7
+ # doc.data logo_image: Consolidate::Image.new(name: "logo.png", width: 4096, height: 1122, path: "spec/files/logo.png")
8
+ # doc.write_to "tmp/doc-with-logos.docx"
9
+ # end
6
10
  module Consolidate
7
11
  module Docx
8
12
  class ImageReferenceNodeBuilder < Data.define(:field_name, :image, :node_id, :image_number, :document)
9
13
  def call
10
- max_width, max_height = max_dimensions_from(document)
11
- scaled_width, scaled_height = scale_dimensions(image.width, image.height, max_width, max_height)
14
+ usable_width, usable_height = usable_dimensions_from(document)
15
+ scaled_width, scaled_height = scale_dimensions(image.width_in_emu, image.height_in_emu, usable_width, usable_height)
12
16
 
13
17
  Nokogiri::XML::Node.new("w:drawing", document).tap do |drawing|
14
18
  drawing["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
15
- drawing << Nokogiri::XML::Node.new("wp:anchor", document).tap do |anchor|
16
- anchor["behindDoc"] = "0"
17
- anchor["distT"] = "0"
18
- anchor["distB"] = "0"
19
- anchor["distL"] = "0"
20
- anchor["distR"] = "0"
21
- anchor["simplePos"] = "0"
22
- anchor["locked"] = "0"
23
- anchor["layoutInCell"] = "0"
24
- anchor["allowOverlap"] = "1"
25
- anchor["relativeHeight"] = "2"
26
- anchor << Nokogiri::XML::Node.new("wp:simplePos", document).tap do |pos|
27
- pos["x"] = "0"
28
- pos["y"] = "0"
29
- end
30
- anchor << Nokogiri::XML::Node.new("wp:positionH", document).tap do |posh|
31
- posh["relativeFrom"] = "column"
32
- posh << Nokogiri::XML::Node.new("wp:align", document).tap do |align|
33
- align.content = "center"
34
- end
35
- end
36
- anchor << Nokogiri::XML::Node.new("wp:positionV", document).tap do |posv|
37
- posv["relativeFrom"] = "paragraph"
38
- posv << Nokogiri::XML::Node.new("wp:posOffset", document).tap do |offset|
39
- offset.content = "635"
40
- end
41
- end
42
- anchor << Nokogiri::XML::Node.new("wp:extent", document).tap do |extent|
43
- extent["cx"] = scaled_width
44
- extent["cy"] = scaled_height
45
- end
46
- anchor << Nokogiri::XML::Node.new("wp:effectExtent", document).tap do |effect_extent|
47
- effect_extent["l"] = "0"
48
- effect_extent["t"] = "0"
49
- effect_extent["r"] = "0"
50
- effect_extent["b"] = "0"
51
- end
52
- anchor << Nokogiri::XML::Node.new("wp:wrapSquare", document).tap do |wrap_square|
53
- wrap_square["wrapText"] = "largest"
54
- end
55
- anchor << Nokogiri::XML::Node.new("wp:docPr", document).tap do |doc_pr|
56
- doc_pr["id"] = image_number
57
- doc_pr["name"] = image.name
58
- doc_pr["descr"] = ""
59
- doc_pr["title"] = ""
19
+ drawing << create_inline_node(scaled_width, scaled_height)
20
+ end
21
+ rescue => ex
22
+ puts ex.backtrace
23
+ end
24
+
25
+ private def create_inline_node(scaled_width, scaled_height)
26
+ Nokogiri::XML::Node.new("wp:inline", document).tap do |inline|
27
+ # Set the inline properties
28
+ inline["distT"] = "0"
29
+ inline["distB"] = "0"
30
+ inline["distL"] = "0"
31
+ inline["distR"] = "0"
32
+
33
+ # Size node (extent)
34
+ inline << Nokogiri::XML::Node.new("wp:extent", document).tap do |extent|
35
+ extent["cx"] = scaled_width
36
+ extent["cy"] = scaled_height
37
+ end
38
+
39
+ # Effect extent
40
+ inline << create_effect_extent_node
41
+
42
+ # Document properties
43
+ inline << create_doc_properties_node
44
+
45
+ # Non-visual properties
46
+ inline << create_non_visual_properties_node
47
+
48
+ # Graphic node
49
+ inline << create_graphic_node(scaled_width, scaled_height)
50
+ end
51
+ end
52
+
53
+ private def create_effect_extent_node
54
+ Nokogiri::XML::Node.new("wp:effectExtent", document).tap do |effect_extent|
55
+ effect_extent["l"] = "0"
56
+ effect_extent["t"] = "0"
57
+ effect_extent["r"] = "0"
58
+ effect_extent["b"] = "0"
59
+ end
60
+ end
61
+
62
+ private def create_doc_properties_node
63
+ Nokogiri::XML::Node.new("wp:docPr", document).tap do |doc_pr|
64
+ doc_pr["id"] = image_number
65
+ doc_pr["name"] = image.name
66
+ doc_pr["descr"] = image.name
67
+ end
68
+ end
69
+
70
+ private def create_non_visual_properties_node
71
+ Nokogiri::XML::Node.new("wp:cNvGraphicFramePr", document).tap do |c_nv_graphic_frame_pr|
72
+ c_nv_graphic_frame_pr << Nokogiri::XML::Node.new("a:graphicFrameLocks", document).tap do |graphic_frame_locks|
73
+ graphic_frame_locks["noChangeAspect"] = "1"
74
+ end
75
+ end
76
+ end
77
+
78
+ private def create_graphic_node(scaled_width, scaled_height)
79
+ Nokogiri::XML::Node.new("a:graphic", document).tap do |graphic|
80
+ graphic["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
81
+ graphic << Nokogiri::XML::Node.new("a:graphicData", document).tap do |graphic_data|
82
+ graphic_data["uri"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
83
+ graphic_data << create_picture_node(scaled_width, scaled_height)
84
+ end
85
+ end
86
+ end
87
+
88
+ private def create_picture_node(scaled_width, scaled_height)
89
+ Nokogiri::XML::Node.new("pic:pic", document).tap do |pic|
90
+ pic["xmlns:pic"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
91
+
92
+ # Non-visual picture properties
93
+ pic << create_non_visual_picture_properties
94
+
95
+ # Blip fill (the image reference)
96
+ pic << create_blip_fill
97
+
98
+ # Shape properties
99
+ pic << create_shape_properties(scaled_width, scaled_height)
100
+ end
101
+ end
102
+
103
+ private def create_non_visual_picture_properties
104
+ Nokogiri::XML::Node.new("pic:nvPicPr", document).tap do |nv_pic_pr|
105
+ nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPr", document).tap do |c_nv_pr|
106
+ c_nv_pr["id"] = image_number
107
+ c_nv_pr["name"] = image.name
108
+ c_nv_pr["descr"] = image.name
109
+ end
110
+ nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPicPr", document).tap do |c_nv_pic_pr|
111
+ c_nv_pic_pr << Nokogiri::XML::Node.new("a:picLocks", document).tap do |pic_locks|
112
+ pic_locks["noChangeAspect"] = "1"
113
+ pic_locks["noChangeArrowheads"] = "0"
60
114
  end
61
- anchor << Nokogiri::XML::Node.new("wp:cNvGraphicFramePr", document).tap do |c_nv_graphic_frame_pr|
62
- c_nv_graphic_frame_pr << Nokogiri::XML::Node.new("a:graphicFrameLocks", document).tap do |graphic_frame_locks|
63
- graphic_frame_locks["noChangeAspect"] = true
64
- end
115
+ end
116
+ end
117
+ end
118
+
119
+ private def create_blip_fill
120
+ Nokogiri::XML::Node.new("pic:blipFill", document).tap do |blip_fill|
121
+ blip_fill << Nokogiri::XML::Node.new("a:blip", document).tap do |blip|
122
+ blip["r:embed"] = node_id
123
+ blip["cstate"] = "print"
124
+ end
125
+ blip_fill << Nokogiri::XML::Node.new("a:stretch", document).tap do |stretch|
126
+ stretch << Nokogiri::XML::Node.new("a:fillRect", document)
127
+ end
128
+ end
129
+ end
130
+
131
+ private def create_shape_properties(scaled_width, scaled_height)
132
+ Nokogiri::XML::Node.new("pic:spPr", document).tap do |sp_pr|
133
+ sp_pr["bwMode"] = "auto"
134
+ # Transform
135
+ sp_pr << Nokogiri::XML::Node.new("a:xfrm", document).tap do |xfrm|
136
+ xfrm << Nokogiri::XML::Node.new("a:off", document).tap do |off|
137
+ off["x"] = "0"
138
+ off["y"] = "0"
65
139
  end
66
- anchor << Nokogiri::XML::Node.new("a:graphic", document).tap do |graphic|
67
- graphic["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
68
- graphic << Nokogiri::XML::Node.new("a:graphicData", document).tap do |graphic_data|
69
- graphic_data["uri"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
70
- graphic_data << Nokogiri::XML::Node.new("pic:pic", document).tap do |pic|
71
- pic["xmlns:pic"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
72
- pic << Nokogiri::XML::Node.new("pic:nvPicPr", document).tap do |nv_pic_pr|
73
- nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPr", document).tap do |c_nv_pr|
74
- c_nv_pr["id"] = image_number
75
- c_nv_pr["name"] = image.name
76
- c_nv_pr["descr"] = image.name
77
- c_nv_pr["hidden"] = false
78
- c_nv_pr << Nokogiri::XML::Node.new("pic:cNvPicPr", document).tap do |c_nv_pic_pr|
79
- c_nv_pic_pr << Nokogiri::XML::Node.new("a:picLocks", document).tap do |pic_locks|
80
- pic_locks["noChangeAspect"] = "1"
81
- pic_locks["noChangeArrowheads"] = "1"
82
- end
83
- end
84
- end
85
- end
86
- pic << Nokogiri::XML::Node.new("pic:blipFill", document).tap do |blip_fill|
87
- blip_fill << Nokogiri::XML::Node.new("a:blip", document).tap do |blip|
88
- blip["r:embed"] = node_id
89
- end
90
- blip_fill << Nokogiri::XML::Node.new("a:stretch", document).tap do |stretch|
91
- stretch << Nokogiri::XML::Node.new("a:fillRect", document)
92
- end
93
- end
94
- pic << Nokogiri::XML::Node.new("pic:spPr", document).tap do |sp_pr|
95
- sp_pr["bwMode"] = "auto"
96
- sp_pr << Nokogiri::XML::Node.new("a:xfrm", document).tap do |xfrm|
97
- xfrm << Nokogiri::XML::Node.new("a:off", document).tap do |off|
98
- off["x"] = "0"
99
- off["y"] = "0"
100
- end
101
- xfrm << Nokogiri::XML::Node.new("a:ext", document).tap do |ext|
102
- ext["cx"] = scaled_width
103
- ext["cy"] = scaled_height
104
- end
105
- end
106
- sp_pr << Nokogiri::XML::Node.new("a:prstGeom", document).tap do |prst_geom|
107
- prst_geom["prst"] = "rect"
108
- prst_geom << Nokogiri::XML::Node.new("a:avLst", document)
109
- end
110
- sp_pr << Nokogiri::XML::Node.new("a:noFill", document)
111
- end
112
- end
113
- end
140
+ xfrm << Nokogiri::XML::Node.new("a:ext", document).tap do |ext|
141
+ ext["cx"] = scaled_width
142
+ ext["cy"] = scaled_height
114
143
  end
115
144
  end
145
+ # Pre-set geometry
146
+ sp_pr << Nokogiri::XML::Node.new("a:prstGeom", document).tap do |prst_geom|
147
+ prst_geom["prst"] = "rect"
148
+ prst_geom << Nokogiri::XML::Node.new("a:avLst", document)
149
+ end
150
+ # No fill
151
+ sp_pr << Nokogiri::XML::Node.new("a:noFill", document)
116
152
  end
117
153
  end
118
154
 
119
- DEFAULT_PAGE_WIDTH = 12_240
120
- TWENTIETHS_OF_A_POINT_TO_EMU = 635
121
- DEFAULT_PAGE_WIDTH_IN_EMU = DEFAULT_PAGE_WIDTH * TWENTIETHS_OF_A_POINT_TO_EMU
122
- EMU_PER_PIXEL = 9525
123
- DEFAULT_PAGE_HEIGHT = DEFAULT_PAGE_WIDTH * 11 / 8.5 # Assuming standard page ratio
124
- DEFAULT_PAGE_HEIGHT_IN_EMU = DEFAULT_PAGE_HEIGHT * TWENTIETHS_OF_A_POINT_TO_EMU
155
+ private def usable_dimensions_from(document)
156
+ # Get page dimensions
157
+ page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || Image::DEFAULT_PAGE_WIDTH).to_i
158
+ page_height = (document.at_xpath("//w:sectPr/w:pgSz/@w:h")&.value || Image::DEFAULT_PAGE_HEIGHT).to_i
125
159
 
126
- private def max_width_from document
127
- page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || DEFAULT_PAGE_WIDTH).to_i
128
- page_width * TWENTIETHS_OF_A_POINT_TO_EMU
129
- end
160
+ # Convert to EMU
161
+ width_emu = page_width * Image::TWENTIETHS_OF_A_POINT_TO_EMU
162
+ height_emu = page_height * Image::TWENTIETHS_OF_A_POINT_TO_EMU
163
+
164
+ # Account for margins
165
+ left_margin = (document.at_xpath("//w:sectPr/w:pgMar/@w:left")&.value || 0).to_i * Image::TWENTIETHS_OF_A_POINT_TO_EMU
166
+ right_margin = (document.at_xpath("//w:sectPr/w:pgMar/@w:right")&.value || 0).to_i * Image::TWENTIETHS_OF_A_POINT_TO_EMU
167
+ top_margin = (document.at_xpath("//w:sectPr/w:pgMar/@w:top")&.value || 0).to_i * Image::TWENTIETHS_OF_A_POINT_TO_EMU
168
+ bottom_margin = (document.at_xpath("//w:sectPr/w:pgMar/@w:bottom")&.value || 0).to_i * Image::TWENTIETHS_OF_A_POINT_TO_EMU
130
169
 
131
- private def max_dimensions_from(document)
132
- page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || DEFAULT_PAGE_WIDTH).to_i
133
- page_height = (document.at_xpath("//w:sectPr/w:pgSz/@w:h")&.value || DEFAULT_PAGE_HEIGHT).to_i
170
+ # If no margins found, use defaults
171
+ if left_margin == 0 && right_margin == 0
172
+ left_margin = right_margin = Image::DEFAULT_MARGIN_IN_EMU
173
+ end
134
174
 
135
- width_emu = page_width * TWENTIETHS_OF_A_POINT_TO_EMU
136
- height_emu = page_height * TWENTIETHS_OF_A_POINT_TO_EMU
175
+ if top_margin == 0 && bottom_margin == 0
176
+ top_margin = bottom_margin = Image::DEFAULT_MARGIN_IN_EMU
177
+ end
137
178
 
138
- [width_emu, height_emu]
179
+ # Usable area
180
+ usable_width = width_emu - left_margin - right_margin
181
+ usable_height = height_emu - top_margin - bottom_margin
182
+
183
+ # Add a small buffer (10%) to ensure image fits
184
+ usable_width = (usable_width * 0.9).to_i
185
+ usable_height = (usable_height * 0.9).to_i
186
+
187
+ [usable_width, usable_height]
139
188
  end
140
189
 
141
- private def scale_dimensions(width, height, max_width, max_height)
142
- width_ratio = max_width.to_f / width
143
- height_ratio = max_height.to_f / height
144
- scale = [width_ratio, height_ratio, 1.0].min # Never scale up
190
+ private def scale_dimensions(width_emu, height_emu, max_width_emu, max_height_emu)
191
+ # Ensure we're working with EMU values throughout
192
+ width_ratio = max_width_emu.to_f / width_emu
193
+ height_ratio = max_height_emu.to_f / height_emu
194
+
195
+ # Take the smaller ratio to ensure image fits within boundaries
196
+ # but never scale up (keep at 1.0 if the image is smaller than available space)
197
+ scale = [width_ratio, height_ratio, 1.0].min
145
198
 
146
- [(width * scale).to_i, (height * scale).to_i]
199
+ # Apply scaling factor and ensure integer values
200
+ [(width_emu * scale).to_i, (height_emu * scale).to_i]
147
201
  end
148
202
  end
149
203
  end
@@ -3,8 +3,7 @@
3
3
  module Consolidate
4
4
  class Image
5
5
  attr_reader :name, :width, :height, :aspect_ratio, :dpi
6
-
7
- def initialize name:, width:, height:, path: nil, url: nil, contents: nil
6
+ def initialize name:, width:, height:, path: nil, url: nil, contents: nil, dpix: nil, dpiy: nil
8
7
  @name = name
9
8
  @width = width
10
9
  @height = height
@@ -13,7 +12,7 @@ module Consolidate
13
12
  @contents = contents
14
13
  @aspect_ratio = width.to_f / height.to_f
15
14
  #  TODO: Read this from the contents
16
- @dpi = {x: 72, y: 72}
15
+ @dpi = {x: dpix || 72, y: dpiy || 72}
17
16
  end
18
17
 
19
18
  def to_s = name
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consolidate
4
- VERSION = "0.4.3"
4
+ VERSION = "0.4.5"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-consolidate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-24 00:00:00.000000000 Z
10
+ date: 2025-02-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rubyzip
@@ -67,6 +67,8 @@ files:
67
67
  - checksums/standard-procedure-consolidate-0.4.1.gem.sha512
68
68
  - checksums/standard-procedure-consolidate-0.4.2.gem.sha512
69
69
  - checksums/standard-procedure-consolidate-0.4.3.gem.sha512
70
+ - checksums/standard-procedure-consolidate-0.4.4.gem.sha512
71
+ - checksums/standard-procedure-consolidate-0.4.5.gem.sha512
70
72
  - exe/consolidate
71
73
  - exe/examine
72
74
  - lib/consolidate.rb