silk_layout 0.1.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 +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +21 -0
- data/README.md +154 -0
- data/lib/silk_layout/css/cascade.rb +49 -0
- data/lib/silk_layout/css/computed_style.rb +83 -0
- data/lib/silk_layout/css/parser.rb +50 -0
- data/lib/silk_layout/css/properties.rb +19 -0
- data/lib/silk_layout/css/rule.rb +15 -0
- data/lib/silk_layout/css/selector.rb +165 -0
- data/lib/silk_layout/html/node.rb +56 -0
- data/lib/silk_layout/html/parser.rb +188 -0
- data/lib/silk_layout/layout/block_layout.rb +93 -0
- data/lib/silk_layout/layout/box.rb +80 -0
- data/lib/silk_layout/layout/box_builder.rb +11 -0
- data/lib/silk_layout/layout/context.rb +13 -0
- data/lib/silk_layout/layout/engine.rb +22 -0
- data/lib/silk_layout/layout/flex_layout.rb +508 -0
- data/lib/silk_layout/layout/formatting_builder.rb +425 -0
- data/lib/silk_layout/layout/inline.rb +88 -0
- data/lib/silk_layout/layout/inline_formatter.rb +132 -0
- data/lib/silk_layout/layout/root.rb +15 -0
- data/lib/silk_layout/render/font_library.rb +127 -0
- data/lib/silk_layout/render/painter.rb +247 -0
- data/lib/silk_layout/render/pdf_renderer.rb +31 -0
- data/lib/silk_layout/version.rb +5 -0
- data/lib/silk_layout.rb +55 -0
- metadata +251 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hexapdf"
|
|
4
|
+
|
|
5
|
+
module SilkLayout
|
|
6
|
+
module Render
|
|
7
|
+
class FontLibrary
|
|
8
|
+
BASE_FONTS = {
|
|
9
|
+
helvetica: {
|
|
10
|
+
normal: {
|
|
11
|
+
normal: "Helvetica",
|
|
12
|
+
italic: "Helvetica-Oblique"
|
|
13
|
+
},
|
|
14
|
+
bold: {
|
|
15
|
+
normal: "Helvetica-Bold",
|
|
16
|
+
italic: "Helvetica-BoldOblique"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
times: {
|
|
20
|
+
normal: {
|
|
21
|
+
normal: "Times-Roman",
|
|
22
|
+
italic: "Times-Italic"
|
|
23
|
+
},
|
|
24
|
+
bold: {
|
|
25
|
+
normal: "Times-Bold",
|
|
26
|
+
italic: "Times-BoldItalic"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
courier: {
|
|
30
|
+
normal: {
|
|
31
|
+
normal: "Courier",
|
|
32
|
+
italic: "Courier-Oblique"
|
|
33
|
+
},
|
|
34
|
+
bold: {
|
|
35
|
+
normal: "Courier-Bold",
|
|
36
|
+
italic: "Courier-BoldOblique"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
FAMILY_ALIASES = {
|
|
42
|
+
"arial" => :helvetica,
|
|
43
|
+
"helvetica" => :helvetica,
|
|
44
|
+
"sans-serif" => :helvetica,
|
|
45
|
+
"sans serif" => :helvetica,
|
|
46
|
+
"times" => :times,
|
|
47
|
+
"times new roman" => :times,
|
|
48
|
+
"serif" => :times,
|
|
49
|
+
"courier" => :courier,
|
|
50
|
+
"courier new" => :courier,
|
|
51
|
+
"monospace" => :courier
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
def metrics(font_family, font_weight: "normal", font_style: "normal")
|
|
56
|
+
font_name = resolve_font_name(font_family, font_weight: font_weight, font_style: font_style)
|
|
57
|
+
font = font_wrapper(font_name)
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
font_name: font_name,
|
|
61
|
+
font: font,
|
|
62
|
+
ascender: normalized_metric(font, :ascender),
|
|
63
|
+
descender: normalized_metric(font, :descender)
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def measure_text(text, font_size:, font_family:, font_weight: "normal", font_style: "normal")
|
|
68
|
+
return 0 if text.to_s.empty?
|
|
69
|
+
|
|
70
|
+
font = metrics(font_family, font_weight: font_weight, font_style: font_style)[:font]
|
|
71
|
+
glyph_width = font.decode_utf8(text.to_s).sum(&:width)
|
|
72
|
+
glyph_width * font_size / 1000.0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def resolve_font_name(font_family, font_weight: "normal", font_style: "normal")
|
|
76
|
+
family = normalized_family(font_family)
|
|
77
|
+
weight = bold?(font_weight) ? :bold : :normal
|
|
78
|
+
style = italic?(font_style) ? :italic : :normal
|
|
79
|
+
|
|
80
|
+
BASE_FONTS.fetch(family, BASE_FONTS[:helvetica]).fetch(weight).fetch(style)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def font_wrapper(font_name)
|
|
86
|
+
@document ||= HexaPDF::Document.new
|
|
87
|
+
@fonts ||= {}
|
|
88
|
+
@fonts[font_name] ||= @document.fonts.add(font_name)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def normalized_metric(font, metric_name)
|
|
92
|
+
wrapped_font = font.instance_variable_get(:@wrapped_font)
|
|
93
|
+
return 0 unless wrapped_font
|
|
94
|
+
|
|
95
|
+
wrapped_font.public_send(metric_name).to_f * font.scaling_factor
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def normalized_family(font_family)
|
|
99
|
+
candidates(font_family).each do |candidate|
|
|
100
|
+
mapped = FAMILY_ALIASES[candidate]
|
|
101
|
+
return mapped if mapped
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
:helvetica
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def candidates(font_family)
|
|
108
|
+
font_family.to_s.split(",").map do |family|
|
|
109
|
+
family.to_s.strip.delete_prefix('"').delete_suffix('"').delete_prefix("'").delete_suffix("'").downcase
|
|
110
|
+
end.reject(&:empty?)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def bold?(font_weight)
|
|
114
|
+
weight = font_weight.to_s.strip.downcase
|
|
115
|
+
return true if weight == "bold"
|
|
116
|
+
return false if weight.empty? || weight == "normal"
|
|
117
|
+
|
|
118
|
+
weight.to_i >= 600
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def italic?(font_style)
|
|
122
|
+
%w[italic oblique].include?(font_style.to_s.strip.downcase)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SilkLayout
|
|
4
|
+
module Render
|
|
5
|
+
class Painter
|
|
6
|
+
def self.paint(canvas, box, page_height_pt)
|
|
7
|
+
paint_box(canvas, box, page_height_pt)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.paint_box(canvas, box, page_height_pt)
|
|
11
|
+
draw_background(canvas, box, page_height_pt)
|
|
12
|
+
draw_borders(canvas, box, page_height_pt)
|
|
13
|
+
|
|
14
|
+
if box.is_a?(SilkLayout::Layout::TextBox)
|
|
15
|
+
paint_text(canvas, box, page_height_pt)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
box.children.each do |child|
|
|
19
|
+
paint_box(canvas, child, page_height_pt)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.paint_text(canvas, box, page_height_pt)
|
|
24
|
+
font_size_px = box.font_size || 16
|
|
25
|
+
font_name = box.font_name || box.font_family || "Helvetica"
|
|
26
|
+
|
|
27
|
+
font_size_pt = PdfRenderer.px_to_pt(font_size_px)
|
|
28
|
+
ascent_pt = PdfRenderer.px_to_pt(box.respond_to?(:ascender) ? box.ascender : font_size_px * 0.8)
|
|
29
|
+
|
|
30
|
+
canvas.font(font_name, size: font_size_pt)
|
|
31
|
+
|
|
32
|
+
canvas = set_color(canvas, color_to_symbol(box.respond_to?(:color) ? box.color : nil) || :black)
|
|
33
|
+
|
|
34
|
+
x_pt = PdfRenderer.px_to_pt(box.x)
|
|
35
|
+
y_pt = page_height_pt - PdfRenderer.px_to_pt(box.y) - ascent_pt
|
|
36
|
+
|
|
37
|
+
canvas.text(box.text, at: [x_pt, y_pt])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.draw_background(canvas, box, page_height_pt)
|
|
41
|
+
return if box.is_a?(SilkLayout::Layout::AnonymousBlockBox)
|
|
42
|
+
return unless box.respond_to?(:background_color) && box.background_color
|
|
43
|
+
|
|
44
|
+
rgb = rgb_color(box.background_color)
|
|
45
|
+
return unless rgb
|
|
46
|
+
|
|
47
|
+
x = PdfRenderer.px_to_pt(box.border_box_x)
|
|
48
|
+
y = page_height_pt - PdfRenderer.px_to_pt(box.border_box_y + box.border_box_height)
|
|
49
|
+
w = PdfRenderer.px_to_pt(box.border_box_width)
|
|
50
|
+
h = PdfRenderer.px_to_pt(box.border_box_height)
|
|
51
|
+
|
|
52
|
+
canvas.fill_color(*rgb)
|
|
53
|
+
canvas.rectangle(x, y, w, h).fill
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.draw_borders(canvas, box, page_height_pt)
|
|
57
|
+
return if box.is_a?(SilkLayout::Layout::AnonymousBlockBox)
|
|
58
|
+
return unless box.has_border
|
|
59
|
+
|
|
60
|
+
x = PdfRenderer.px_to_pt(box.border_box_x)
|
|
61
|
+
y = page_height_pt - PdfRenderer.px_to_pt(box.border_box_y + box.border_box_height)
|
|
62
|
+
w = PdfRenderer.px_to_pt(box.border_box_width)
|
|
63
|
+
h = PdfRenderer.px_to_pt(box.border_box_height)
|
|
64
|
+
|
|
65
|
+
top = PdfRenderer.px_to_pt(box.border[:top])
|
|
66
|
+
bottom = PdfRenderer.px_to_pt(box.border[:bottom])
|
|
67
|
+
left = PdfRenderer.px_to_pt(box.border[:left])
|
|
68
|
+
right = PdfRenderer.px_to_pt(box.border[:right])
|
|
69
|
+
|
|
70
|
+
colors = [box.border_color[:top], box.border_color[:right], box.border_color[:bottom], box.border_color[:left]]
|
|
71
|
+
if colors.uniq.length <= 1
|
|
72
|
+
if top > 0
|
|
73
|
+
canvas = set_color(canvas, box.border_color[:top])
|
|
74
|
+
canvas.rectangle(x, y + h - top, w, top).fill
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if bottom > 0
|
|
78
|
+
canvas = set_color(canvas, box.border_color[:bottom])
|
|
79
|
+
canvas.rectangle(x, y, w, bottom).fill
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if left > 0
|
|
83
|
+
canvas = set_color(canvas, box.border_color[:left])
|
|
84
|
+
canvas.rectangle(x, y, left, h).fill
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if right > 0
|
|
88
|
+
canvas = set_color(canvas, box.border_color[:right])
|
|
89
|
+
canvas.rectangle(x + w - right, y, right, h).fill
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
inner_w = [w - left - right, 0].max
|
|
96
|
+
inner_h = [h - top - bottom, 0].max
|
|
97
|
+
|
|
98
|
+
if top > 0 && inner_w > 0
|
|
99
|
+
canvas = set_color(canvas, box.border_color[:top])
|
|
100
|
+
canvas.rectangle(x + left, y + h - top, inner_w, top).fill
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if bottom > 0 && inner_w > 0
|
|
104
|
+
canvas = set_color(canvas, box.border_color[:bottom])
|
|
105
|
+
canvas.rectangle(x + left, y, inner_w, bottom).fill
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if left > 0 && inner_h > 0
|
|
109
|
+
canvas = set_color(canvas, box.border_color[:left])
|
|
110
|
+
canvas.rectangle(x, y + bottom, left, inner_h).fill
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if right > 0 && inner_h > 0
|
|
114
|
+
canvas = set_color(canvas, box.border_color[:right])
|
|
115
|
+
canvas.rectangle(x + w - right, y + bottom, right, inner_h).fill
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
draw_corner(
|
|
119
|
+
canvas,
|
|
120
|
+
x,
|
|
121
|
+
y + h - top,
|
|
122
|
+
left,
|
|
123
|
+
top,
|
|
124
|
+
box.border_color[:left],
|
|
125
|
+
box.border_color[:top],
|
|
126
|
+
:top_left
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
draw_corner(
|
|
130
|
+
canvas,
|
|
131
|
+
x + w - right,
|
|
132
|
+
y + h - top,
|
|
133
|
+
right,
|
|
134
|
+
top,
|
|
135
|
+
box.border_color[:right],
|
|
136
|
+
box.border_color[:top],
|
|
137
|
+
:top_right
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
draw_corner(
|
|
141
|
+
canvas,
|
|
142
|
+
x,
|
|
143
|
+
y,
|
|
144
|
+
left,
|
|
145
|
+
bottom,
|
|
146
|
+
box.border_color[:left],
|
|
147
|
+
box.border_color[:bottom],
|
|
148
|
+
:bottom_left
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
draw_corner(
|
|
152
|
+
canvas,
|
|
153
|
+
x + w - right,
|
|
154
|
+
y,
|
|
155
|
+
right,
|
|
156
|
+
bottom,
|
|
157
|
+
box.border_color[:right],
|
|
158
|
+
box.border_color[:bottom],
|
|
159
|
+
:bottom_right
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.set_color(canvas, color)
|
|
164
|
+
return canvas unless color
|
|
165
|
+
|
|
166
|
+
rgb = rgb_color(color)
|
|
167
|
+
if rgb
|
|
168
|
+
canvas.fill_color(*rgb)
|
|
169
|
+
else
|
|
170
|
+
canvas.fill_color(0, 0, 0)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
canvas
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.color_to_symbol(color)
|
|
177
|
+
return nil unless color
|
|
178
|
+
|
|
179
|
+
normalized = color.to_s.strip
|
|
180
|
+
return nil if normalized.empty?
|
|
181
|
+
|
|
182
|
+
normalized.to_sym
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.rgb_color(color)
|
|
186
|
+
normalized = color.to_s.strip.downcase
|
|
187
|
+
return nil if normalized.empty? || normalized == "transparent"
|
|
188
|
+
|
|
189
|
+
return hex_color(normalized) if normalized.start_with?("#")
|
|
190
|
+
|
|
191
|
+
NAMED_COLORS[normalized.to_sym]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def self.hex_color(color)
|
|
195
|
+
hex = color.delete_prefix("#")
|
|
196
|
+
hex = hex.chars.flat_map { |char| [char, char] }.join if hex.length == 3
|
|
197
|
+
return nil unless hex.match?(/\A[0-9a-f]{6}\z/)
|
|
198
|
+
|
|
199
|
+
hex.scan(/../).map { |component| component.to_i(16) }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def self.draw_corner(canvas, x, y, w, h, vertical_color, horizontal_color, kind)
|
|
203
|
+
return if w <= 0 || h <= 0
|
|
204
|
+
|
|
205
|
+
if vertical_color == horizontal_color
|
|
206
|
+
canvas = set_color(canvas, vertical_color)
|
|
207
|
+
canvas.rectangle(x, y, w, h).fill
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
case kind
|
|
212
|
+
when :top_left
|
|
213
|
+
fill_triangle(canvas, horizontal_color, [x, y + h], [x + w, y + h], [x + w, y])
|
|
214
|
+
fill_triangle(canvas, vertical_color, [x, y + h], [x + w, y], [x, y])
|
|
215
|
+
when :top_right
|
|
216
|
+
fill_triangle(canvas, horizontal_color, [x, y + h], [x + w, y + h], [x, y])
|
|
217
|
+
fill_triangle(canvas, vertical_color, [x + w, y + h], [x + w, y], [x, y])
|
|
218
|
+
when :bottom_left
|
|
219
|
+
fill_triangle(canvas, horizontal_color, [x, y], [x + w, y], [x + w, y + h])
|
|
220
|
+
fill_triangle(canvas, vertical_color, [x, y], [x + w, y + h], [x, y + h])
|
|
221
|
+
when :bottom_right
|
|
222
|
+
fill_triangle(canvas, horizontal_color, [x, y], [x + w, y], [x, y + h])
|
|
223
|
+
fill_triangle(canvas, vertical_color, [x + w, y], [x + w, y + h], [x, y + h])
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def self.fill_triangle(canvas, color, p1, p2, p3)
|
|
228
|
+
canvas = set_color(canvas, color)
|
|
229
|
+
canvas.move_to(*p1)
|
|
230
|
+
canvas.line_to(*p2)
|
|
231
|
+
canvas.line_to(*p3)
|
|
232
|
+
canvas.close_subpath.fill
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
private_class_method :paint_box
|
|
236
|
+
|
|
237
|
+
NAMED_COLORS = {
|
|
238
|
+
black: [0, 0, 0],
|
|
239
|
+
blue: [0, 0, 255],
|
|
240
|
+
green: [0, 128, 0],
|
|
241
|
+
lightblue: [173, 216, 230],
|
|
242
|
+
red: [255, 0, 0],
|
|
243
|
+
white: [255, 255, 255]
|
|
244
|
+
}.freeze
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hexapdf"
|
|
4
|
+
|
|
5
|
+
module SilkLayout
|
|
6
|
+
module Render
|
|
7
|
+
class PdfRenderer
|
|
8
|
+
CSS_DPI = 96.0
|
|
9
|
+
PDF_DPI = 72.0
|
|
10
|
+
|
|
11
|
+
PAGE_WIDTH_PX = 800
|
|
12
|
+
PAGE_HEIGHT_PX = 1000
|
|
13
|
+
|
|
14
|
+
def self.render(box_tree, output_path)
|
|
15
|
+
doc = HexaPDF::Document.new
|
|
16
|
+
|
|
17
|
+
page_width_pt = px_to_pt(PAGE_WIDTH_PX)
|
|
18
|
+
page_height_pt = px_to_pt(PAGE_HEIGHT_PX)
|
|
19
|
+
|
|
20
|
+
page = doc.pages.add([0, 0, page_width_pt, page_height_pt])
|
|
21
|
+
Painter.paint(page.canvas, box_tree, page_height_pt)
|
|
22
|
+
|
|
23
|
+
doc.write(output_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.px_to_pt(px)
|
|
27
|
+
px * PDF_DPI / CSS_DPI
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/silk_layout.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/all"
|
|
4
|
+
|
|
5
|
+
module SilkLayout
|
|
6
|
+
autoload :VERSION, "silk_layout/version"
|
|
7
|
+
|
|
8
|
+
module HTML
|
|
9
|
+
autoload :Parser, "silk_layout/html/parser"
|
|
10
|
+
autoload :Node, "silk_layout/html/node"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module CSS
|
|
14
|
+
autoload :Parser, "silk_layout/css/parser"
|
|
15
|
+
autoload :Cascade, "silk_layout/css/cascade"
|
|
16
|
+
autoload :ComputedStyle, "silk_layout/css/computed_style"
|
|
17
|
+
autoload :Rule, "silk_layout/css/rule"
|
|
18
|
+
autoload :Declaration, "silk_layout/css/rule"
|
|
19
|
+
autoload :Selector, "silk_layout/css/selector"
|
|
20
|
+
autoload :Properties, "silk_layout/css/properties"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Layout
|
|
24
|
+
autoload :Box, "silk_layout/layout/box"
|
|
25
|
+
autoload :BlockBox, "silk_layout/layout/box"
|
|
26
|
+
autoload :FlexBox, "silk_layout/layout/box"
|
|
27
|
+
autoload :InlineBox, "silk_layout/layout/box"
|
|
28
|
+
autoload :AnonymousBlockBox, "silk_layout/layout/box"
|
|
29
|
+
autoload :Inline, "silk_layout/layout/inline"
|
|
30
|
+
autoload :TextBox, "silk_layout/layout/inline"
|
|
31
|
+
autoload :LineBox, "silk_layout/layout/inline"
|
|
32
|
+
autoload :InlineFormatter, "silk_layout/layout/inline_formatter"
|
|
33
|
+
autoload :FormattingBuilder, "silk_layout/layout/formatting_builder"
|
|
34
|
+
autoload :BoxBuilder, "silk_layout/layout/box_builder"
|
|
35
|
+
autoload :Context, "silk_layout/layout/context"
|
|
36
|
+
autoload :BlockLayout, "silk_layout/layout/block_layout"
|
|
37
|
+
autoload :FlexLayout, "silk_layout/layout/flex_layout"
|
|
38
|
+
autoload :Engine, "silk_layout/layout/engine"
|
|
39
|
+
autoload :Root, "silk_layout/layout/root"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module Render
|
|
43
|
+
autoload :FontLibrary, "silk_layout/render/font_library"
|
|
44
|
+
autoload :Painter, "silk_layout/render/painter"
|
|
45
|
+
autoload :PdfRenderer, "silk_layout/render/pdf_renderer"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.render_document(html_document, out, url: nil)
|
|
49
|
+
dom, stylesheets = SilkLayout::HTML::Parser.parse_document(html_document, url: url)
|
|
50
|
+
rules = SilkLayout::CSS::Parser.parse_all(stylesheets)
|
|
51
|
+
box_tree = SilkLayout::Layout::Engine.layout(dom, rules)
|
|
52
|
+
|
|
53
|
+
SilkLayout::Render::PdfRenderer.render(box_tree, out)
|
|
54
|
+
end
|
|
55
|
+
end
|