standard-procedure-consolidate 0.4.4 → 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: f54b1e9134c3e9ec1a9a87ba47dd0a0842e430f9bc2ad2801fdaaecb6d4784c1
4
- data.tar.gz: 881e583371a1cc58b7742ea74cb90e47b934198d03a6d76d538a7c48330378cf
3
+ metadata.gz: 81a63ebba28600dbc0e124ab27e8c6cf150f8dafc80942a12c5e22691bbf8b88
4
+ data.tar.gz: cbfad371ee9b13baa9eb1fd8aefa9aecf8e1979d581e9b490df7ebdf01706ec3
5
5
  SHA512:
6
- metadata.gz: a48b68b17ba644b23b4bd81ff30a195260a00343aeba22cfcfd77151e1d22f4d13dff7b54409d87737b9610f888fd574b624a4aac5bcd3c114500be3626f9267
7
- data.tar.gz: 14acf38a2067475f7a5cd25e2783aed5f2299d764b051906c833dba4d1ae4f515ab2929606990bdb87da74a139aa587ef23682d5297d896a6f9293d6c16dcdd1
6
+ metadata.gz: 856086986d7a075cefe919716d039481ac80480ee169b00d6038d9aeef751338daf4fab90889c96fbd00d183708fa610c450df3d14f704aa9c6d515f5a1a5b54
7
+ data.tar.gz: ef54556ae1258cf20b08a3a519c1744f6010d80c955e3d26469bf2710a76b2ddf70734c4fe9d76920ab06ca8fbc0d08741a7b2f2dfdf5a0f1769524dcc642b3d
@@ -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,22 +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
20
  def emu_per_width_pixel = EMU_PER_PIXEL * 72 / dpi[:x]
22
21
 
23
22
  def emu_per_height_pixel = EMU_PER_PIXEL * 72 / dpi[:y]
24
23
 
24
+ # Constants
25
25
  DEFAULT_PAGE_WIDTH = 12_240
26
26
  TWENTIETHS_OF_A_POINT_TO_EMU = 635
27
27
  DEFAULT_PAGE_WIDTH_IN_EMU = DEFAULT_PAGE_WIDTH * TWENTIETHS_OF_A_POINT_TO_EMU
28
28
  EMU_PER_PIXEL = 9525
29
29
  DEFAULT_PAGE_HEIGHT = DEFAULT_PAGE_WIDTH * 11 / 8.5 # Assuming US Letter size
30
30
  DEFAULT_PAGE_HEIGHT_IN_EMU = DEFAULT_PAGE_HEIGHT * TWENTIETHS_OF_A_POINT_TO_EMU
31
+
32
+ # Common page margins in EMU (0.75 inches)
33
+ DEFAULT_MARGIN_IN_EMU = 685800
31
34
  end
32
35
  end
33
36
  end
@@ -2,141 +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"] = ""
60
- 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
65
- 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
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"
114
114
  end
115
115
  end
116
116
  end
117
117
  end
118
118
 
119
- private def max_width_from document
120
- page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || Image::DEFAULT_PAGE_WIDTH).to_i
121
- page_width * Image::TWENTIETHS_OF_A_POINT_TO_EMU
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
122
129
  end
123
130
 
124
- private def max_dimensions_from(document)
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"
139
+ end
140
+ xfrm << Nokogiri::XML::Node.new("a:ext", document).tap do |ext|
141
+ ext["cx"] = scaled_width
142
+ ext["cy"] = scaled_height
143
+ end
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)
152
+ end
153
+ end
154
+
155
+ private def usable_dimensions_from(document)
156
+ # Get page dimensions
125
157
  page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || Image::DEFAULT_PAGE_WIDTH).to_i
126
158
  page_height = (document.at_xpath("//w:sectPr/w:pgSz/@w:h")&.value || Image::DEFAULT_PAGE_HEIGHT).to_i
127
159
 
160
+ # Convert to EMU
128
161
  width_emu = page_width * Image::TWENTIETHS_OF_A_POINT_TO_EMU
129
162
  height_emu = page_height * Image::TWENTIETHS_OF_A_POINT_TO_EMU
130
163
 
131
- [width_emu, height_emu]
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
169
+
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
174
+
175
+ if top_margin == 0 && bottom_margin == 0
176
+ top_margin = bottom_margin = Image::DEFAULT_MARGIN_IN_EMU
177
+ end
178
+
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]
132
188
  end
133
189
 
134
- private def scale_dimensions(width, height, max_width, max_height)
135
- width_ratio = max_width.to_f / width
136
- height_ratio = max_height.to_f / height
137
- 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
138
198
 
139
- [(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]
140
201
  end
141
202
  end
142
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.4"
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.4
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
@@ -68,6 +68,7 @@ files:
68
68
  - checksums/standard-procedure-consolidate-0.4.2.gem.sha512
69
69
  - checksums/standard-procedure-consolidate-0.4.3.gem.sha512
70
70
  - checksums/standard-procedure-consolidate-0.4.4.gem.sha512
71
+ - checksums/standard-procedure-consolidate-0.4.5.gem.sha512
71
72
  - exe/consolidate
72
73
  - exe/examine
73
74
  - lib/consolidate.rb