silk_layout 0.1.0 → 0.2.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/CHANGELOG.md +36 -1
- data/README.md +74 -29
- data/lib/silk_layout/css/cascade.rb +5 -2
- data/lib/silk_layout/css/color.rb +415 -0
- data/lib/silk_layout/css/computed_style.rb +43 -11
- data/lib/silk_layout/css/page_rule.rb +104 -0
- data/lib/silk_layout/css/parser.rb +122 -27
- data/lib/silk_layout/css/properties.rb +301 -3
- data/lib/silk_layout/css/selector.rb +246 -30
- data/lib/silk_layout/css/values.rb +169 -0
- data/lib/silk_layout/html/node.rb +1 -0
- data/lib/silk_layout/html/parser.rb +17 -3
- data/lib/silk_layout/layout/block_layout.rb +131 -12
- data/lib/silk_layout/layout/box.rb +39 -1
- data/lib/silk_layout/layout/context.rb +7 -2
- data/lib/silk_layout/layout/engine.rb +12 -2
- data/lib/silk_layout/layout/flex_layout.rb +1 -0
- data/lib/silk_layout/layout/formatting_builder.rb +73 -19
- data/lib/silk_layout/layout/inline_formatter.rb +88 -10
- data/lib/silk_layout/layout/root.rb +4 -5
- data/lib/silk_layout/render/painter.rb +17 -23
- data/lib/silk_layout/render/pdf_renderer.rb +39 -5
- data/lib/silk_layout/resource/image.rb +151 -0
- data/lib/silk_layout/version.rb +1 -1
- data/lib/silk_layout.rb +54 -4
- metadata +5 -1
|
@@ -5,10 +5,13 @@ module SilkLayout
|
|
|
5
5
|
class Selector
|
|
6
6
|
def initialize(raw)
|
|
7
7
|
@raw = raw.to_s.strip
|
|
8
|
-
@
|
|
8
|
+
@valid = !@raw.empty?
|
|
9
|
+
@steps = @valid ? parse_steps(@raw) : []
|
|
10
|
+
@valid &&= @steps.any?
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def match?(node)
|
|
14
|
+
return false unless @valid
|
|
12
15
|
return false unless node.respond_to?(:element?)
|
|
13
16
|
return false unless node.element?
|
|
14
17
|
return false if node.tag.nil?
|
|
@@ -29,6 +32,10 @@ module SilkLayout
|
|
|
29
32
|
when :descendant
|
|
30
33
|
current = find_ancestor(current.parent, simple)
|
|
31
34
|
return false unless current
|
|
35
|
+
when :adjacent
|
|
36
|
+
current = previous_element_sibling(current)
|
|
37
|
+
return false unless current
|
|
38
|
+
return false unless step_match?(simple, current)
|
|
32
39
|
else
|
|
33
40
|
return false
|
|
34
41
|
end
|
|
@@ -45,10 +52,10 @@ module SilkLayout
|
|
|
45
52
|
elements = 0
|
|
46
53
|
|
|
47
54
|
@steps.each do |step|
|
|
48
|
-
|
|
49
|
-
ids +=
|
|
50
|
-
classes +=
|
|
51
|
-
elements +=
|
|
55
|
+
simple_ids, simple_classes, simple_elements = simple_specificity(step[:simple])
|
|
56
|
+
ids += simple_ids
|
|
57
|
+
classes += simple_classes
|
|
58
|
+
elements += simple_elements
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
[ids, classes, elements]
|
|
@@ -56,52 +63,103 @@ module SilkLayout
|
|
|
56
63
|
|
|
57
64
|
private
|
|
58
65
|
|
|
66
|
+
IDENT = /[a-zA-Z][a-zA-Z0-9_-]*/
|
|
67
|
+
NAME = /[a-zA-Z0-9_-]+/
|
|
68
|
+
|
|
59
69
|
def parse_steps(raw)
|
|
60
70
|
tokens = tokenize(raw)
|
|
61
71
|
parts = []
|
|
62
72
|
pending_combinator = nil
|
|
73
|
+
expecting_simple = true
|
|
63
74
|
|
|
64
75
|
tokens.each do |tok|
|
|
65
|
-
if tok == "
|
|
66
|
-
pending_combinator
|
|
76
|
+
if tok == " "
|
|
77
|
+
pending_combinator ||= :descendant unless expecting_simple || parts.empty?
|
|
67
78
|
next
|
|
68
79
|
end
|
|
69
80
|
|
|
70
|
-
if tok == " "
|
|
71
|
-
|
|
81
|
+
if tok == ">" || tok == "+"
|
|
82
|
+
return invalid_steps if parts.empty? || expecting_simple
|
|
83
|
+
|
|
84
|
+
pending_combinator = (tok == ">") ? :child : :adjacent
|
|
85
|
+
expecting_simple = true
|
|
72
86
|
next
|
|
73
87
|
end
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
simple = parse_simple(tok)
|
|
90
|
+
return invalid_steps unless simple
|
|
91
|
+
|
|
92
|
+
parts << {
|
|
93
|
+
simple: simple,
|
|
94
|
+
combinator: parts.empty? ? nil : (pending_combinator || :descendant)
|
|
95
|
+
}
|
|
76
96
|
pending_combinator = nil
|
|
97
|
+
expecting_simple = false
|
|
77
98
|
end
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
# Combinator is stored on the RIGHT step to indicate how it relates to the LEFT.
|
|
81
|
-
parts.each_with_index do |part, idx|
|
|
82
|
-
next if idx == 0
|
|
83
|
-
part[:combinator] ||= :descendant
|
|
84
|
-
end
|
|
100
|
+
return invalid_steps if expecting_simple && parts.any?
|
|
85
101
|
|
|
86
102
|
parts
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
def tokenize(raw)
|
|
90
|
-
raw = raw.strip
|
|
91
106
|
tokens = []
|
|
92
107
|
buf = +""
|
|
93
108
|
in_space = false
|
|
109
|
+
quote = nil
|
|
110
|
+
bracket_depth = 0
|
|
111
|
+
paren_depth = 0
|
|
112
|
+
|
|
113
|
+
raw.strip.each_char do |ch|
|
|
114
|
+
if quote
|
|
115
|
+
buf << ch
|
|
116
|
+
quote = nil if ch == quote
|
|
117
|
+
next
|
|
118
|
+
end
|
|
94
119
|
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
if ch == '"' || ch == "'"
|
|
121
|
+
quote = ch
|
|
122
|
+
buf << ch
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if ch == "["
|
|
127
|
+
bracket_depth += 1
|
|
128
|
+
buf << ch
|
|
129
|
+
next
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if ch == "]"
|
|
133
|
+
bracket_depth -= 1
|
|
134
|
+
return invalid_steps if bracket_depth.negative?
|
|
135
|
+
|
|
136
|
+
buf << ch
|
|
137
|
+
next
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if ch == "("
|
|
141
|
+
paren_depth += 1
|
|
142
|
+
buf << ch
|
|
143
|
+
next
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if ch == ")"
|
|
147
|
+
paren_depth -= 1
|
|
148
|
+
return invalid_steps if paren_depth.negative?
|
|
149
|
+
|
|
150
|
+
buf << ch
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if (ch == ">" || ch == "+") && bracket_depth.zero? && paren_depth.zero?
|
|
97
155
|
tokens << buf unless buf.empty?
|
|
98
156
|
buf = +""
|
|
99
|
-
tokens <<
|
|
157
|
+
tokens << ch
|
|
100
158
|
in_space = false
|
|
101
159
|
next
|
|
102
160
|
end
|
|
103
161
|
|
|
104
|
-
if ch.match?(/\s/)
|
|
162
|
+
if ch.match?(/\s/) && bracket_depth.zero? && paren_depth.zero?
|
|
105
163
|
tokens << buf unless buf.empty?
|
|
106
164
|
buf = +""
|
|
107
165
|
tokens << " " unless in_space
|
|
@@ -113,31 +171,85 @@ module SilkLayout
|
|
|
113
171
|
buf << ch
|
|
114
172
|
end
|
|
115
173
|
|
|
174
|
+
return invalid_steps if quote || !bracket_depth.zero? || !paren_depth.zero?
|
|
175
|
+
|
|
116
176
|
tokens << buf unless buf.empty?
|
|
117
177
|
tokens.reject(&:empty?)
|
|
118
178
|
end
|
|
119
179
|
|
|
120
|
-
def parse_simple(token)
|
|
180
|
+
def parse_simple(token, allow_not: true)
|
|
121
181
|
tag = nil
|
|
122
182
|
id = nil
|
|
123
183
|
classes = []
|
|
184
|
+
attributes = []
|
|
185
|
+
pseudo_classes = []
|
|
186
|
+
negations = []
|
|
187
|
+
universal = false
|
|
124
188
|
|
|
125
189
|
rest = token.to_s
|
|
126
|
-
if rest.
|
|
127
|
-
|
|
128
|
-
|
|
190
|
+
if rest.start_with?("*")
|
|
191
|
+
universal = true
|
|
192
|
+
rest = rest[1..].to_s
|
|
193
|
+
elsif (m = rest.match(/\A(#{IDENT.source})/))
|
|
194
|
+
tag = m[1].downcase
|
|
129
195
|
rest = rest[m[1].length..]
|
|
130
196
|
end
|
|
131
197
|
|
|
132
|
-
rest.
|
|
133
|
-
if
|
|
134
|
-
id =
|
|
198
|
+
until rest.empty?
|
|
199
|
+
if (m = rest.match(/\A#(#{NAME.source})/))
|
|
200
|
+
id = m[1]
|
|
201
|
+
rest = rest[m[0].length..].to_s
|
|
202
|
+
elsif (m = rest.match(/\A\.(#{NAME.source})/))
|
|
203
|
+
classes << m[1]
|
|
204
|
+
rest = rest[m[0].length..].to_s
|
|
205
|
+
elsif rest.start_with?("[")
|
|
206
|
+
closing = closing_index(rest, "]")
|
|
207
|
+
return invalid_simple unless closing
|
|
208
|
+
|
|
209
|
+
attribute = parse_attribute(rest[1...closing])
|
|
210
|
+
return invalid_simple unless attribute
|
|
211
|
+
|
|
212
|
+
attributes << attribute
|
|
213
|
+
rest = rest[(closing + 1)..].to_s
|
|
214
|
+
elsif rest.start_with?(":first-child")
|
|
215
|
+
pseudo_classes << :first_child
|
|
216
|
+
rest = rest[12..].to_s
|
|
217
|
+
elsif rest.start_with?(":last-child")
|
|
218
|
+
pseudo_classes << :last_child
|
|
219
|
+
rest = rest[11..].to_s
|
|
220
|
+
elsif rest.start_with?(":not(")
|
|
221
|
+
return invalid_simple unless allow_not
|
|
222
|
+
|
|
223
|
+
closing = closing_index(rest, ")")
|
|
224
|
+
return invalid_simple unless closing
|
|
225
|
+
|
|
226
|
+
inner = rest[5...closing].to_s.strip
|
|
227
|
+
inner_tokens = tokenize(inner)
|
|
228
|
+
return invalid_simple unless @valid && inner_tokens.length == 1
|
|
229
|
+
|
|
230
|
+
negation = parse_simple(inner_tokens.first, allow_not: false)
|
|
231
|
+
return invalid_simple unless negation
|
|
232
|
+
|
|
233
|
+
negations << negation
|
|
234
|
+
rest = rest[(closing + 1)..].to_s
|
|
135
235
|
else
|
|
136
|
-
|
|
236
|
+
return invalid_simple
|
|
137
237
|
end
|
|
138
238
|
end
|
|
139
239
|
|
|
140
|
-
|
|
240
|
+
if tag.nil? && id.nil? && classes.empty? && attributes.empty? && pseudo_classes.empty? && negations.empty? && !universal
|
|
241
|
+
return invalid_simple
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
tag: tag,
|
|
246
|
+
id: id,
|
|
247
|
+
classes: classes,
|
|
248
|
+
attributes: attributes,
|
|
249
|
+
pseudo_classes: pseudo_classes,
|
|
250
|
+
negations: negations,
|
|
251
|
+
universal: universal
|
|
252
|
+
}
|
|
141
253
|
end
|
|
142
254
|
|
|
143
255
|
def step_match?(simple, node)
|
|
@@ -149,6 +261,26 @@ module SilkLayout
|
|
|
149
261
|
return false unless simple[:classes].all? { |c| node_classes.include?(c) }
|
|
150
262
|
end
|
|
151
263
|
|
|
264
|
+
simple[:attributes].each do |attribute|
|
|
265
|
+
return false unless node.attributes.key?(attribute[:name])
|
|
266
|
+
return false if attribute.key?(:value) && node.attributes[attribute[:name]] != attribute[:value]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
simple[:pseudo_classes].each do |pseudo_class|
|
|
270
|
+
case pseudo_class
|
|
271
|
+
when :first_child
|
|
272
|
+
return false unless first_element_child?(node)
|
|
273
|
+
when :last_child
|
|
274
|
+
return false unless last_element_child?(node)
|
|
275
|
+
else
|
|
276
|
+
return false
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
simple[:negations].each do |negation|
|
|
281
|
+
return false if step_match?(negation, node)
|
|
282
|
+
end
|
|
283
|
+
|
|
152
284
|
true
|
|
153
285
|
end
|
|
154
286
|
|
|
@@ -160,6 +292,90 @@ module SilkLayout
|
|
|
160
292
|
end
|
|
161
293
|
nil
|
|
162
294
|
end
|
|
295
|
+
|
|
296
|
+
def simple_specificity(simple)
|
|
297
|
+
ids = simple[:id] ? 1 : 0
|
|
298
|
+
classes = simple[:classes].length + simple[:attributes].length + simple[:pseudo_classes].length
|
|
299
|
+
elements = simple[:tag] ? 1 : 0
|
|
300
|
+
|
|
301
|
+
simple[:negations].each do |negation|
|
|
302
|
+
negation_ids, negation_classes, negation_elements = simple_specificity(negation)
|
|
303
|
+
ids += negation_ids
|
|
304
|
+
classes += negation_classes
|
|
305
|
+
elements += negation_elements
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
[ids, classes, elements]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def parse_attribute(raw)
|
|
312
|
+
m = raw.to_s.strip.match(/\A(#{IDENT.source})(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=\]]+)))?\z/)
|
|
313
|
+
return nil unless m
|
|
314
|
+
|
|
315
|
+
attribute = {name: m[1].downcase}
|
|
316
|
+
value = m[2] || m[3] || m[4]
|
|
317
|
+
attribute[:value] = value unless value.nil?
|
|
318
|
+
attribute
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def closing_index(raw, character)
|
|
322
|
+
quote = nil
|
|
323
|
+
|
|
324
|
+
raw.each_char.with_index do |ch, index|
|
|
325
|
+
next if index.zero?
|
|
326
|
+
|
|
327
|
+
if quote
|
|
328
|
+
quote = nil if ch == quote
|
|
329
|
+
next
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
if ch == '"' || ch == "'"
|
|
333
|
+
quote = ch
|
|
334
|
+
next
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
return index if ch == character
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
nil
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def first_element_child?(node)
|
|
344
|
+
element_siblings(node).first == node
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def last_element_child?(node)
|
|
348
|
+
element_siblings(node).last == node
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def element_siblings(node)
|
|
352
|
+
node.parent&.children&.select { |child| child.respond_to?(:element?) && child.element? } || []
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def previous_element_sibling(node)
|
|
356
|
+
siblings = node.parent&.children
|
|
357
|
+
return nil unless siblings
|
|
358
|
+
|
|
359
|
+
index = siblings.index(node)
|
|
360
|
+
return nil unless index
|
|
361
|
+
|
|
362
|
+
(index - 1).downto(0) do |candidate_index|
|
|
363
|
+
candidate = siblings[candidate_index]
|
|
364
|
+
return candidate if candidate.respond_to?(:element?) && candidate.element?
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
nil
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def invalid_steps
|
|
371
|
+
@valid = false
|
|
372
|
+
[]
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def invalid_simple
|
|
376
|
+
@valid = false
|
|
377
|
+
nil
|
|
378
|
+
end
|
|
163
379
|
end
|
|
164
380
|
end
|
|
165
381
|
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module SilkLayout
|
|
6
|
+
module CSS
|
|
7
|
+
module Values
|
|
8
|
+
NUMBER = /[-+]?(?:\d+(?:\.\d+)?|\.\d+)/
|
|
9
|
+
LENGTH = /\A(?<number>#{NUMBER.source})(?<unit>px|%)?\z/i
|
|
10
|
+
CALC = /\Acalc\((?<expression>.*)\)\z/i
|
|
11
|
+
|
|
12
|
+
NAMED_LENGTHS = {
|
|
13
|
+
"thin" => 1,
|
|
14
|
+
"medium" => 3,
|
|
15
|
+
"thick" => 5
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
Term = Struct.new(:value, :unit)
|
|
19
|
+
|
|
20
|
+
class Length
|
|
21
|
+
attr_reader :type, :value, :unit, :terms
|
|
22
|
+
|
|
23
|
+
def self.parse(value)
|
|
24
|
+
raw = value.to_s.strip
|
|
25
|
+
normalized = raw.downcase
|
|
26
|
+
return new(:auto) if normalized.empty? || normalized == "auto" || normalized == "none"
|
|
27
|
+
|
|
28
|
+
named = NAMED_LENGTHS[normalized]
|
|
29
|
+
return new(:length, value: named.to_f, unit: "px") if named
|
|
30
|
+
|
|
31
|
+
calc_match = raw.match(CALC)
|
|
32
|
+
if calc_match
|
|
33
|
+
parsed = parse_calc(calc_match[:expression])
|
|
34
|
+
return parsed if parsed
|
|
35
|
+
|
|
36
|
+
return new(:length, value: raw.to_f, unit: "px")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
match = raw.match(LENGTH)
|
|
40
|
+
return new(:length, value: match[:number].to_f, unit: (match[:unit] || "px").downcase) if match
|
|
41
|
+
|
|
42
|
+
new(:length, value: raw.to_f, unit: "px")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.parse_calc(expression)
|
|
46
|
+
scanner = StringScanner.new(expression)
|
|
47
|
+
terms = []
|
|
48
|
+
sign = 1
|
|
49
|
+
|
|
50
|
+
until scanner.eos?
|
|
51
|
+
scanner.skip(/\s+/)
|
|
52
|
+
|
|
53
|
+
operator = scanner.scan(/[+-]/)
|
|
54
|
+
sign = (operator == "-") ? -1 : 1 if operator
|
|
55
|
+
scanner.skip(/\s+/)
|
|
56
|
+
|
|
57
|
+
token = scanner.scan(/(?:\d+(?:\.\d+)?|\.\d+)(?:px|%)?/i)
|
|
58
|
+
return nil unless token
|
|
59
|
+
|
|
60
|
+
length = parse(token)
|
|
61
|
+
return nil unless length.type == :length
|
|
62
|
+
|
|
63
|
+
terms << Term.new(length.value * sign, length.unit)
|
|
64
|
+
scanner.skip(/\s+/)
|
|
65
|
+
|
|
66
|
+
return nil unless scanner.eos? || scanner.peek(1).match?(/[+-]/)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return nil if terms.empty?
|
|
70
|
+
|
|
71
|
+
new(:calc, terms: terms)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def initialize(type, value: 0, unit: nil, terms: [])
|
|
75
|
+
@type = type
|
|
76
|
+
@value = value
|
|
77
|
+
@unit = unit
|
|
78
|
+
@terms = terms
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def resolve(reference: nil, default: 0)
|
|
82
|
+
case type
|
|
83
|
+
when :auto
|
|
84
|
+
default
|
|
85
|
+
when :length
|
|
86
|
+
return default if unit == "%" && reference.nil?
|
|
87
|
+
|
|
88
|
+
(unit == "%") ? reference * value / 100.0 : value
|
|
89
|
+
when :calc
|
|
90
|
+
return default if terms.any? { |term| term.unit == "%" } && reference.nil?
|
|
91
|
+
|
|
92
|
+
terms.sum do |term|
|
|
93
|
+
(term.unit == "%") ? reference * term.value / 100.0 : term.value
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
default
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def reference_relative?
|
|
101
|
+
return true if type == :length && unit == "%"
|
|
102
|
+
return true if type == :calc && terms.any? { |term| term.unit == "%" }
|
|
103
|
+
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
module_function
|
|
109
|
+
|
|
110
|
+
def length(value)
|
|
111
|
+
value.is_a?(Length) ? value : Length.parse(value)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def resolve_length(value, reference: nil, default: 0)
|
|
115
|
+
length(value).resolve(reference: reference, default: default)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def reference_relative?(value)
|
|
119
|
+
length(value).reference_relative?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def split_tokens(value)
|
|
123
|
+
tokens = []
|
|
124
|
+
current = +""
|
|
125
|
+
depth = 0
|
|
126
|
+
|
|
127
|
+
value.to_s.each_char do |char|
|
|
128
|
+
case char
|
|
129
|
+
when "("
|
|
130
|
+
depth += 1
|
|
131
|
+
current << char
|
|
132
|
+
when ")"
|
|
133
|
+
depth -= 1 if depth.positive?
|
|
134
|
+
current << char
|
|
135
|
+
when /\s/
|
|
136
|
+
if depth.zero?
|
|
137
|
+
tokens << current unless current.empty?
|
|
138
|
+
current = +""
|
|
139
|
+
else
|
|
140
|
+
current << char
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
current << char
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
tokens << current unless current.empty?
|
|
148
|
+
tokens
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def expanded_edges(value)
|
|
152
|
+
tokens = split_tokens(value)
|
|
153
|
+
|
|
154
|
+
case tokens.length
|
|
155
|
+
when 0
|
|
156
|
+
{top: nil, right: nil, bottom: nil, left: nil}
|
|
157
|
+
when 1
|
|
158
|
+
{top: tokens[0], right: tokens[0], bottom: tokens[0], left: tokens[0]}
|
|
159
|
+
when 2
|
|
160
|
+
{top: tokens[0], right: tokens[1], bottom: tokens[0], left: tokens[1]}
|
|
161
|
+
when 3
|
|
162
|
+
{top: tokens[0], right: tokens[1], bottom: tokens[2], left: tokens[1]}
|
|
163
|
+
else
|
|
164
|
+
{top: tokens[0], right: tokens[1], bottom: tokens[2], left: tokens[3]}
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -13,7 +13,10 @@ module SilkLayout
|
|
|
13
13
|
base_uri = base_uri_for(document, url)
|
|
14
14
|
stylesheets = extract_stylesheets!(document, base_uri)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
root = Node.from_nokogiri(document.root)
|
|
17
|
+
resolve_resource_urls!(root, base_uri)
|
|
18
|
+
|
|
19
|
+
[root, stylesheets]
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def self.extract_stylesheets!(document, base_uri)
|
|
@@ -75,7 +78,7 @@ module SilkLayout
|
|
|
75
78
|
dir = p.to_s
|
|
76
79
|
dir = "#{dir}/" unless dir.end_with?("/")
|
|
77
80
|
|
|
78
|
-
URI::Generic.build(scheme: "file", path: dir)
|
|
81
|
+
URI::Generic.build(scheme: "file", path: URI::RFC2396_PARSER.escape(dir, /[^A-Za-z0-9\-._~\/]/))
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
def self.fetch_stylesheet(href, base_uri, cache)
|
|
@@ -107,7 +110,18 @@ module SilkLayout
|
|
|
107
110
|
|
|
108
111
|
URI.join(base_uri.to_s, href)
|
|
109
112
|
rescue URI::InvalidURIError
|
|
110
|
-
URI.join(base_uri.to_s, URI::
|
|
113
|
+
URI.join(base_uri.to_s, URI::RFC2396_PARSER.escape(href))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.resolve_resource_urls!(node, base_uri)
|
|
117
|
+
return unless node
|
|
118
|
+
|
|
119
|
+
if node.element? && node.tag == "img"
|
|
120
|
+
src = node.attributes["src"].to_s.strip
|
|
121
|
+
node.resolved_source_url = normalize_href(src, base_uri) unless src.empty?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
node.children.each { |child| resolve_resource_urls!(child, base_uri) }
|
|
111
125
|
end
|
|
112
126
|
|
|
113
127
|
def self.fetch_http(uri)
|