psd 2.1.2 → 3.9.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.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -0
  3. data/README.md +60 -20
  4. data/lib/psd/blend_mode.rb +46 -38
  5. data/lib/psd/channel_image.rb +11 -5
  6. data/lib/psd/color.rb +14 -5
  7. data/lib/psd/descriptor.rb +39 -16
  8. data/lib/psd/file.rb +9 -6
  9. data/lib/psd/header.rb +38 -33
  10. data/lib/psd/helpers.rb +2 -2
  11. data/lib/psd/image.rb +21 -16
  12. data/lib/psd/image_formats/layer_rle.rb +2 -2
  13. data/lib/psd/image_formats/rle.rb +4 -10
  14. data/lib/psd/image_modes/cmyk.rb +18 -5
  15. data/lib/psd/image_modes/greyscale.rb +6 -1
  16. data/lib/psd/image_modes/rgb.rb +17 -5
  17. data/lib/psd/layer/blend_modes.rb +15 -13
  18. data/lib/psd/layer/helpers.rb +8 -10
  19. data/lib/psd/layer/info/black_white.rb +33 -0
  20. data/lib/psd/{layer_info → layer/info}/blend_clipping_elements.rb +4 -2
  21. data/lib/psd/{layer_info → layer/info}/blend_interior_elements.rb +4 -2
  22. data/lib/psd/layer/info/brightness_contrast.rb +21 -0
  23. data/lib/psd/layer/info/channel_blending_restrictions.rb +27 -0
  24. data/lib/psd/layer/info/channel_mixer.rb +27 -0
  25. data/lib/psd/layer/info/color_balance.rb +23 -0
  26. data/lib/psd/layer/info/color_lookup.rb +14 -0
  27. data/lib/psd/layer/info/content_generator.rb +42 -0
  28. data/lib/psd/layer/info/curves.rb +78 -0
  29. data/lib/psd/layer/info/effects_layer.rb +174 -0
  30. data/lib/psd/layer/info/exposure.rb +20 -0
  31. data/lib/psd/{layer_info → layer/info}/fill_opacity.rb +4 -2
  32. data/lib/psd/layer/info/gradient_fill.rb +14 -0
  33. data/lib/psd/layer/info/gradient_map.rb +69 -0
  34. data/lib/psd/layer/info/hue_saturation.rb +41 -0
  35. data/lib/psd/layer/info/invert.rb +17 -0
  36. data/lib/psd/layer/info/knockout.rb +22 -0
  37. data/lib/psd/layer/info/layer_effects.rb +16 -0
  38. data/lib/psd/{layer_info → layer/info}/layer_group.rb +4 -2
  39. data/lib/psd/{layer_info → layer/info}/layer_id.rb +4 -2
  40. data/lib/psd/layer/info/layer_mask_as_global_mask.rb +16 -0
  41. data/lib/psd/{layer_info → layer/info}/layer_name_source.rb +4 -2
  42. data/lib/psd/{layer_info → layer/info}/layer_section_divider.rb +4 -2
  43. data/lib/psd/{layer_info → layer/info}/legacy_typetool.rb +6 -4
  44. data/lib/psd/layer/info/levels.rb +48 -0
  45. data/lib/psd/{layer_info → layer/info}/locked.rb +4 -2
  46. data/lib/psd/{layer_info → layer/info}/metadata_setting.rb +5 -3
  47. data/lib/psd/{layer_info → layer/info}/object_effects.rb +5 -5
  48. data/lib/psd/layer/info/pattern.rb +14 -0
  49. data/lib/psd/layer/info/pattern_fill.rb +15 -0
  50. data/lib/psd/layer/info/photo_filter.rb +44 -0
  51. data/lib/psd/{layer_info → layer/info}/placed_layer.rb +4 -2
  52. data/lib/psd/layer/info/posterize.rb +16 -0
  53. data/lib/psd/{layer_info → layer/info}/reference_point.rb +4 -2
  54. data/lib/psd/layer/info/selective_color.rb +32 -0
  55. data/lib/psd/layer/info/sheet_color.rb +36 -0
  56. data/lib/psd/layer/info/solid_color.rb +36 -0
  57. data/lib/psd/layer/info/threshold.rb +16 -0
  58. data/lib/psd/layer/info/transparency_shapes_layer.rb +16 -0
  59. data/lib/psd/{layer_info → layer/info}/typetool.rb +27 -13
  60. data/lib/psd/{layer_info → layer/info}/unicode_name.rb +4 -2
  61. data/lib/psd/{layer_info → layer/info}/vector_mask.rb +7 -5
  62. data/lib/psd/layer/info/vector_mask_as_global_mask.rb +16 -0
  63. data/lib/psd/layer/info/vector_origination.rb +14 -0
  64. data/lib/psd/{layer_info → layer/info}/vector_stroke.rb +4 -2
  65. data/lib/psd/{layer_info → layer/info}/vector_stroke_content.rb +4 -2
  66. data/lib/psd/layer/info/vibrance.rb +22 -0
  67. data/lib/psd/layer/info.rb +106 -19
  68. data/lib/psd/layer/position_and_channels.rb +2 -6
  69. data/lib/psd/layer.rb +13 -16
  70. data/lib/psd/layer_info.rb +0 -4
  71. data/lib/psd/layer_mask.rb +61 -50
  72. data/lib/psd/lazy_execute.rb +5 -1
  73. data/lib/psd/mask.rb +7 -1
  74. data/lib/psd/node.rb +142 -49
  75. data/lib/psd/nodes/ancestry.rb +7 -6
  76. data/lib/psd/nodes/build_preview.rb +4 -4
  77. data/lib/psd/nodes/group.rb +36 -0
  78. data/lib/psd/nodes/layer.rb +45 -0
  79. data/lib/psd/nodes/layer_comps.rb +93 -0
  80. data/lib/psd/nodes/locking.rb +36 -0
  81. data/lib/psd/nodes/root.rb +87 -0
  82. data/lib/psd/nodes/search.rb +8 -65
  83. data/lib/psd/path_record.rb +5 -70
  84. data/lib/psd/renderer/blender.rb +10 -5
  85. data/lib/psd/renderer/cairo_helpers.rb +46 -0
  86. data/lib/psd/renderer/canvas.rb +41 -20
  87. data/lib/psd/renderer/canvas_management.rb +2 -2
  88. data/lib/psd/renderer/clipping_mask.rb +5 -4
  89. data/lib/psd/renderer/compose.rb +59 -69
  90. data/lib/psd/renderer/layer_styles/color_overlay.rb +45 -27
  91. data/lib/psd/renderer/layer_styles.rb +15 -5
  92. data/lib/psd/renderer/mask.rb +26 -22
  93. data/lib/psd/renderer/mask_canvas.rb +12 -0
  94. data/lib/psd/renderer/vector_shape.rb +239 -0
  95. data/lib/psd/renderer.rb +18 -7
  96. data/lib/psd/resource_section.rb +13 -6
  97. data/lib/psd/resources/base.rb +33 -0
  98. data/lib/psd/resources/guides.rb +6 -4
  99. data/lib/psd/resources/layer_comps.rb +6 -4
  100. data/lib/psd/resources/resolution_info.rb +48 -0
  101. data/lib/psd/resources/saved_path.rb +19 -0
  102. data/lib/psd/resources/slices.rb +7 -5
  103. data/lib/psd/resources/work_path.rb +22 -0
  104. data/lib/psd/resources/xmp_metadata.rb +46 -0
  105. data/lib/psd/resources.rb +17 -21
  106. data/lib/psd/slice.rb +44 -0
  107. data/lib/psd/slices.rb +13 -0
  108. data/lib/psd/util.rb +4 -2
  109. data/lib/psd/version.rb +1 -1
  110. data/lib/psd.rb +31 -45
  111. data/psd.gemspec +2 -3
  112. data/spec/files/alignment_modes.psd +0 -0
  113. data/spec/files/blendmodes.psd +0 -0
  114. data/spec/files/example.psb +0 -0
  115. data/spec/hierarchy_spec.rb +5 -0
  116. data/spec/locked_spec.rb +8 -8
  117. data/spec/psb_parsing_spec.rb +57 -0
  118. data/spec/text_spec.rb +13 -1
  119. metadata +115 -75
  120. data/circle.yml +0 -6
  121. data/lib/psd/layer_info/vector_mask_2.rb +0 -10
  122. data/lib/psd/node_exporting.rb +0 -20
  123. data/lib/psd/node_group.rb +0 -86
  124. data/lib/psd/node_layer.rb +0 -81
  125. data/lib/psd/node_root.rb +0 -93
  126. data/lib/psd/nodes/has_children.rb +0 -13
  127. data/lib/psd/nodes/lock_to_origin.rb +0 -7
  128. data/lib/psd/nodes/parse_layers.rb +0 -18
  129. data/lib/psd/renderer/layer_styles/drop_shadow.rb +0 -75
  130. data/lib/psd/section.rb +0 -26
data/lib/psd/mask.rb CHANGED
@@ -15,6 +15,8 @@ class PSD
15
15
  @left = 0
16
16
  @bottom = 0
17
17
  @right = 0
18
+ @default_color = 0
19
+ @flags = 0
18
20
  end
19
21
 
20
22
  def parse
@@ -54,6 +56,10 @@ class PSD
54
56
  (@flags & (0x01 << 2)) > 0
55
57
  end
56
58
 
59
+ def from_other_data
60
+ (@flags & (0x01 << 3)) > 0
61
+ end
62
+
57
63
  def to_hash
58
64
  return {} if @size == 0
59
65
 
@@ -71,4 +77,4 @@ class PSD
71
77
  }
72
78
  end
73
79
  end
74
- end
80
+ end
data/lib/psd/node.rb CHANGED
@@ -1,70 +1,163 @@
1
- require_relative 'nodes/ancestry'
2
- require_relative 'nodes/search'
1
+ require 'psd/nodes/ancestry'
2
+ require 'psd/nodes/build_preview'
3
+ require 'psd/nodes/search'
4
+ require 'psd/nodes/layer_comps'
5
+ require 'psd/nodes/locking'
3
6
 
4
7
  # Internal structure to help us build trees of a Photoshop documents.
5
8
  # A lot of method names borrowed from the Ruby ancestry gem.
6
9
  class PSD
7
- class Node
8
- include ParseLayers
9
- include Ancestry
10
- include Search
11
- include BuildPreview
10
+ module Node
11
+ class Base
12
+ extend Forwardable
12
13
 
13
- # Default properties that all nodes contain
14
- PROPERTIES = [:name, :left, :right, :top, :bottom, :height, :width]
14
+ include Enumerable
15
+ include Ancestry
16
+ include Search
17
+ include LayerComps
18
+ include BuildPreview
19
+ include Locking
15
20
 
16
- attr_accessor :parent, :children, :layer, :force_visible, :top, :left
21
+ # Default properties that all nodes contain
22
+ PROPERTIES = [:name, :left, :right, :top, :bottom, :height, :width]
17
23
 
18
- def initialize(layers=[])
19
- parse_layers(layers)
24
+ attr_reader :id, :name, :parent
25
+ attr_accessor :children, :layer, :force_visible, :top_offset, :left_offset
20
26
 
21
- @force_visible = nil
22
- @top = @layer.top
23
- @left = @layer.left
24
- end
27
+ def_delegators :parent, :psd, :document_dimensions
28
+ def_delegator :layer, :name
29
+ def_delegator :children, :each
25
30
 
26
- def hidden?
27
- !visible?
28
- end
31
+ def initialize(layer, parent = nil)
32
+ @layer = layer
33
+ @layer.node = self
29
34
 
30
- def visible?
31
- return false if @layer.clipped? && !next_sibling.visible?
32
- force_visible.nil? ? @layer.visible? : force_visible
33
- end
35
+ @parent = parent
36
+ @children = []
37
+
38
+ @id = begin layer.layer_id.id rescue nil end
39
+ @force_visible = nil
40
+ @top = @layer.top.to_i
41
+ @bottom = @layer.bottom.to_i
42
+ @left = @layer.left.to_i
43
+ @right = @layer.right.to_i
34
44
 
35
- def psd
36
- parent.psd
37
- end
45
+ @top_offset = 0
46
+ @left_offset = 0
47
+ end
38
48
 
39
- def layer?
40
- is_a?(PSD::Node::Layer)
41
- end
49
+ def top
50
+ @top + @top_offset
51
+ end
42
52
 
43
- def group?
44
- is_a?(PSD::Node::Group) || is_a?(PSD::Node::Root)
45
- end
53
+ def bottom
54
+ @bottom + @top_offset
55
+ end
46
56
 
47
- def debug_name
48
- root? ? ":root:" : name
49
- end
57
+ def left
58
+ @left + @left_offset
59
+ end
50
60
 
51
- def to_hash
52
- hash = {
53
- type: nil,
54
- visible: visible?,
55
- opacity: @layer.opacity / 255.0,
56
- blending_mode: @layer.blending_mode
57
- }
61
+ def right
62
+ @right + @left_offset
63
+ end
58
64
 
59
- PROPERTIES.each do |p|
60
- hash[p] = self.send(p)
65
+ def width
66
+ right - left
61
67
  end
62
68
 
63
- hash
64
- end
69
+ def height
70
+ bottom - top
71
+ end
72
+
73
+ def hidden?
74
+ !visible?
75
+ end
76
+
77
+ def visible?
78
+ return false if @layer.clipped? && !clipping_mask.visible?
79
+ @force_visible.nil? ? @layer.visible? : @force_visible
80
+ end
81
+
82
+ def clipping_mask
83
+ return nil unless @layer.clipped?
84
+
85
+ @clipping_mask ||= (
86
+ mask_node = next_sibling
87
+ while mask_node.clipped?
88
+ mask_node = mask_node.next_sibling
89
+ end
90
+
91
+ mask_node
92
+ )
93
+ end
94
+ alias_method :clipped_by, :clipping_mask
95
+
96
+ # Color label is a little tricky. If you set the color of a group, all
97
+ # of it's descendants inhert the color unless manually overridden. So,
98
+ # if this node has no defined color, we have to walk up the ancestry tree
99
+ # to make sure the color isn't set somewhere else.
100
+ def color_label
101
+ color = layer.sheet_color.color
102
+ return color if color != :no_color || node.parent.root?
103
+
104
+ parent.color_label
105
+ end
106
+
107
+ def layer?
108
+ is_a?(PSD::Node::Layer)
109
+ end
110
+
111
+ def group?(include_root = true)
112
+ is_a?(PSD::Node::Group) || (include_root && is_a?(PSD::Node::Root))
113
+ end
114
+
115
+ def debug_name
116
+ root? ? ":root:" : name
117
+ end
118
+
119
+ def to_hash
120
+ hash = {
121
+ type: nil,
122
+ visible: visible?,
123
+ opacity: @layer.opacity / 255.0,
124
+ blending_mode: @layer.blending_mode,
125
+ layer_comps: {}
126
+ }
65
127
 
66
- def document_dimensions
67
- @parent.document_dimensions
128
+ PROPERTIES.each do |p|
129
+ hash[p] = self.send(p)
130
+ end
131
+
132
+ root.psd.layer_comps.each do |comp|
133
+ hash[:layer_comps][comp[:name]] = {
134
+ visible: visible_in_comp?(comp[:id]),
135
+ position: position_in_comp(comp[:id])
136
+ }
137
+ end
138
+
139
+ hash
140
+ end
141
+
142
+ protected
143
+
144
+ def update_dimensions!
145
+ return if layer?
146
+
147
+ children.each { |child| child.update_dimensions! }
148
+
149
+ return if root?
150
+
151
+ non_empty_children = children.reject(&:empty?)
152
+ @left = non_empty_children.map(&:left).min || 0
153
+ @top = non_empty_children.map(&:top).min || 0
154
+ @bottom = non_empty_children.map(&:bottom).max || 0
155
+ @right = non_empty_children.map(&:right).max || 0
156
+ end
68
157
  end
69
158
  end
70
- end
159
+ end
160
+
161
+ require 'psd/nodes/group'
162
+ require 'psd/nodes/layer'
163
+ require 'psd/nodes/root'
@@ -1,5 +1,5 @@
1
1
  class PSD
2
- class Node
2
+ module Node
3
3
  # Collection of methods to help in traversing the PSD tree structure.
4
4
  module Ancestry
5
5
  # Returns the root node
@@ -75,8 +75,9 @@ class PSD
75
75
  return ancestors.length + 1
76
76
  end
77
77
 
78
- def path
79
- (ancestors.map(&:name) + [name]).join('/')
78
+ def path(as_array = false)
79
+ path = (ancestors.map(&:name) + [name])
80
+ as_array ? path : path.join('/')
80
81
  end
81
82
 
82
83
  def method_missing(method, *args, &block)
@@ -91,8 +92,8 @@ class PSD
91
92
 
92
93
  private
93
94
 
94
- def layers_only(d); d.is_a?(PSD::Node::Layer); end
95
- def groups_only(d); d.is_a?(PSD::Node::Group); end
95
+ def layers_only(d); d.layer?; end
96
+ def groups_only(d); d.group?(false); end
96
97
  end
97
98
  end
98
- end
99
+ end
@@ -1,12 +1,12 @@
1
1
  class PSD
2
- class Node
2
+ module Node
3
3
  module BuildPreview
4
- def renderer
5
- PSD::Renderer.new(self)
4
+ def renderer(opts = {})
5
+ PSD::Renderer.new(self, opts)
6
6
  end
7
7
 
8
8
  def to_png
9
- renderer.to_png
9
+ @png ||= renderer(render_hidden: self.layer?).to_png
10
10
  end
11
11
 
12
12
  def save_as_png(output)
@@ -0,0 +1,36 @@
1
+ require 'psd/node'
2
+
3
+ class PSD
4
+ module Node
5
+ # Represents a group, or folder, in the PSD document. It can have
6
+ # zero or more children nodes.
7
+ class Group < PSD::Node::Base
8
+ def passthru_blending?
9
+ blending_mode == 'passthru'
10
+ end
11
+
12
+ def empty?
13
+ children.each do |child|
14
+ return false unless child.empty?
15
+ end
16
+
17
+ return true
18
+ end
19
+
20
+ # Export this layer and it's children to a hash recursively.
21
+ def to_hash
22
+ super.merge({
23
+ type: :group,
24
+ open: section_divider ? section_divider.layer_type == 'open folder' : false,
25
+ children: children.map(&:to_hash)
26
+ })
27
+ end
28
+
29
+ # If the method is missing, we blindly send it to the layer.
30
+ # The layer handles the case in which the method doesn't exist.
31
+ def method_missing(method, *args, &block)
32
+ @layer.send(method, *args, &block)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'psd/node'
2
+
3
+ class PSD
4
+ module Node
5
+ class Layer < PSD::Node::Base
6
+ extend Forwardable
7
+
8
+ attr_reader :layer
9
+
10
+ def_delegators :@layer, :text, :ref_x, :ref_y, :blending_mode
11
+ def_delegators :@layer, :text=, :ref_x=, :ref_y=, :blending_mode=
12
+
13
+ def empty?
14
+ width == 0 || height == 0
15
+ end
16
+
17
+ # Exports this layer to a Hash.
18
+ def to_hash
19
+ super.merge({
20
+ type: :layer,
21
+ text: @layer.text,
22
+ ref_x: reference_point.x,
23
+ ref_y: reference_point.y,
24
+ mask: @layer.mask.to_hash,
25
+ image: {
26
+ width: @layer.image.width,
27
+ height: @layer.image.height,
28
+ channels: @layer.channels_info
29
+ }
30
+ })
31
+ end
32
+
33
+ # In case the layer doesn't have a reference point
34
+ def reference_point
35
+ @layer.reference_point || Struct.new(:x, :y).new(0, 0)
36
+ end
37
+
38
+ # If the method is missing, we blindly send it to the layer.
39
+ # The layer handles the case in which the method doesn't exist.
40
+ def method_missing(method, *args, &block)
41
+ @layer.send(method, *args, &block)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,93 @@
1
+ class PSD
2
+ module Node
3
+ module LayerComps
4
+ # Given a layer comp ID or name, create a new
5
+ # tree with layer/group visibility altered based on the layer comp.
6
+ def filter_by_comp(id)
7
+ comp = find_comp(id)
8
+ root = PSD::Node::Root.new(psd)
9
+
10
+ # Force layers to be visible if they are enabled for the comp
11
+ root.descendants.each do |c|
12
+ set_visibility(comp, c) if Resource::Section::LayerComps.visibility_captured?(comp)
13
+ set_position(comp, c) if Resource::Section::LayerComps.position_captured?(comp)
14
+
15
+ PSD.logger.debug "#{c.path}: visible = #{c.visible?}, position = #{c.left}, #{c.top}"
16
+ end
17
+
18
+ return root
19
+ end
20
+
21
+ def visible_in_comp?(id)
22
+ determine_visibility(find_comp(id), self)
23
+ end
24
+
25
+ def position_in_comp(id)
26
+ offset = determine_position_offset(find_comp(id), self)
27
+
28
+ {
29
+ top: top + offset[:y],
30
+ right: right + offset[:x],
31
+ bottom: bottom + offset[:y],
32
+ left: left + offset[:x]
33
+ }
34
+ end
35
+
36
+ private
37
+
38
+ def find_comp(id)
39
+ comp = if id.is_a?(String)
40
+ psd.layer_comps.select { |c| c[:name] == id }.first
41
+ else
42
+ psd.layer_comps.select { |c| c[:id] == id }.first
43
+ end
44
+
45
+ raise "Layer comp not found" if comp.nil?
46
+ comp
47
+ end
48
+
49
+ def set_visibility(comp, c)
50
+ c.force_visible = determine_visibility(comp, c)
51
+ end
52
+
53
+ def determine_visibility(comp, c)
54
+ visible = true
55
+ found = false
56
+
57
+ c
58
+ .metadata
59
+ .data[:layer_comp]['layerSettings'].each do |l|
60
+ visible = l['enab'] if l.has_key?('enab')
61
+ found = true and break if l['compList'].include?(comp[:id])
62
+ end
63
+
64
+ found && visible
65
+ end
66
+
67
+ def set_position(comp, c)
68
+ offset = determine_position_offset(comp, c)
69
+
70
+ c.left_offset = offset[:x]
71
+ c.top_offset = offset[:y]
72
+ end
73
+
74
+ def determine_position_offset(comp, c)
75
+ x = 0
76
+ y = 0
77
+
78
+ c
79
+ .metadata
80
+ .data[:layer_comp]['layerSettings'].each do |l|
81
+ if l.has_key?('Ofst')
82
+ x = l['Ofst']['Hrzn']
83
+ y = l['Ofst']['Vrtc']
84
+ end
85
+
86
+ break if l['compList'].include?(comp[:id])
87
+ end
88
+
89
+ { x: x, y: y }
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ class PSD
2
+ module Node
3
+ # Locking is inherited by descendants, but Photoshop doesn't reflect
4
+ # this directly in the file, so we have to recursively traverse the
5
+ # ancestry tree to make sure an ancestor isn't locked.
6
+ module Locking
7
+ def all_locked?
8
+ return true if layer.all_locked?
9
+ return false if parent.root?
10
+ return parent.all_locked?
11
+ end
12
+
13
+ def any_locked?
14
+ position_locked? || composite_locked? || transparency_locked?
15
+ end
16
+
17
+ def position_locked?
18
+ return true if layer.position_locked?
19
+ return false if parent.root?
20
+ return parent.position_locked?
21
+ end
22
+
23
+ def composite_locked?
24
+ return true if layer.composite_locked?
25
+ return false if parent.root?
26
+ return parent.composite_locked?
27
+ end
28
+
29
+ def transparency_locked?
30
+ return true if layer.transparency_locked?
31
+ return false if parent.root?
32
+ return parent.transparency_locked?
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,87 @@
1
+ require 'psd/node'
2
+
3
+ class PSD
4
+ module Node
5
+ # Represents the root node of a Photoshop document
6
+ class Root < PSD::Node::Base
7
+ attr_accessor :children
8
+ attr_reader :psd
9
+
10
+ alias_method :document_width, :width
11
+ alias_method :document_height, :height
12
+
13
+ RootLayer = Struct.new("RootLayer", :node, *Base::PROPERTIES)
14
+
15
+ def self.layer_for_psd(psd)
16
+ RootLayer.new.tap do |layer|
17
+ layer.top = 0
18
+ layer.left = 0
19
+ layer.right = psd.header.width.to_i
20
+ layer.bottom = psd.header.height.to_i
21
+ end
22
+ end
23
+
24
+ # Stores a reference to the parsed PSD and builds the
25
+ # tree hierarchy.
26
+ def initialize(psd)
27
+ super self.class.layer_for_psd(psd)
28
+
29
+ @psd = psd
30
+ build_hierarchy
31
+ end
32
+
33
+ # Returns the width and height of the entire PSD document.
34
+ def document_dimensions
35
+ [document_width, document_height]
36
+ end
37
+
38
+ # The depth of the root node is always 0.
39
+ def depth
40
+ 0
41
+ end
42
+
43
+ def opacity; 255; end
44
+ def fill_opacity; 255; end
45
+
46
+ # Recursively exports the hierarchy to a Hash
47
+ def to_hash
48
+ {
49
+ children: children.map(&:to_hash),
50
+ document: {
51
+ width: document_width,
52
+ height: document_height,
53
+ depth: psd.header.depth,
54
+ resources: {
55
+ layer_comps: @psd.layer_comps,
56
+ guides: @psd.guides,
57
+ slices: @psd.slices.map(&:to_hash)
58
+ }
59
+ }
60
+ }
61
+ end
62
+
63
+ private
64
+
65
+ def build_hierarchy
66
+ current_group = self
67
+ parse_stack = []
68
+
69
+ # First we build the hierarchy
70
+ @psd.layers.each do |layer|
71
+ if layer.folder?
72
+ parse_stack.push current_group
73
+ current_group = PSD::Node::Group.new(layer, parse_stack.last)
74
+ elsif layer.folder_end?
75
+ parent = parse_stack.pop
76
+ parent.children.push current_group
77
+ current_group = parent
78
+ else
79
+ current_group.children.push PSD::Node::Layer.new(layer, current_group)
80
+ end
81
+ end
82
+
83
+ update_dimensions!
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,6 +1,10 @@
1
1
  class PSD
2
- class Node
2
+ module Node
3
3
  module Search
4
+ def find_by_id(id)
5
+ subtree.select { |n| n.id == id }.first
6
+ end
7
+
4
8
  # Searches the tree structure for a node at the given path. The path is
5
9
  # defined by the layer/folder names. Because the PSD format does not
6
10
  # require unique layer/folder names, we always return an array of all
@@ -8,6 +12,7 @@ class PSD
8
12
  def children_at_path(path, opts={})
9
13
  path = path.split('/').delete_if { |p| p == "" } unless path.is_a?(Array)
10
14
 
15
+ path = path.dup
11
16
  query = path.shift
12
17
  matches = children.select do |c|
13
18
  if opts[:case_sensitive]
@@ -20,72 +25,10 @@ class PSD
20
25
  if path.length == 0
21
26
  return matches
22
27
  else
23
- return matches.map { |m| m.children_at_path(path, opts) }.flatten
28
+ return matches.map { |m| m.children_at_path(path.dup, opts) }.flatten
24
29
  end
25
30
  end
26
31
  alias :children_with_path :children_at_path
27
-
28
- # Given a layer comp ID, name, or :last for last document state, create a new
29
- # tree with layer/group visibility altered based on the layer comp.
30
- def filter_by_comp(id)
31
- if id.is_a?(String)
32
- comp = psd.layer_comps.select { |c| c[:name] == id }.first
33
- raise "Layer comp not found" if comp.nil?
34
-
35
- id = comp[:id]
36
- else
37
- comp = psd.layer_comps.select { |c| c[:id] == id }.first
38
- raise "Layer comp not found" if comp.nil?
39
- end
40
-
41
- root = PSD::Node::Root.new(psd)
42
- filter_for_comp!(comp, root)
43
-
44
- return root
45
- end
46
-
47
- private
48
-
49
- def filter_for_comp!(comp, node)
50
- # Force layers to be visible if they are enabled for the comp
51
- node.children.each do |c|
52
- set_visibility(comp, c) if Resource::Section::LayerComps.visibility_captured?(comp)
53
- set_position(comp, c) if Resource::Section::LayerComps.position_captured?(comp)
54
-
55
- filter_for_comp!(comp, c) if c.group?
56
- end
57
- end
58
-
59
- def set_visibility(comp, c)
60
- visible = true
61
-
62
- c
63
- .metadata
64
- .data[:layer_comp]['layerSettings'].each do |l|
65
- visible = l['enab'] if l.has_key?('enab')
66
- break if l['compList'].include?(comp[:id])
67
- end
68
-
69
- c.force_visible = visible
70
- end
71
-
72
- def set_position(comp, c)
73
- x = 0
74
- y = 0
75
-
76
- c
77
- .metadata
78
- .data[:layer_comp]['layerSettings'].each do |l|
79
- next unless l.has_key?('Ofst')
80
-
81
- x = l['Ofst']['Hrzn']
82
- y = l['Ofst']['Vrtc']
83
- break if l['compList'].include?(comp[:id])
84
- end
85
-
86
- c.left += x
87
- c.top += y
88
- end
89
32
  end
90
33
  end
91
- end
34
+ end