wkhtml 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/.travis_setup.sh +9 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +52 -0
- data/Rakefile +17 -0
- data/ext/wkhtml/extconf.rb +33 -0
- data/ext/wkhtml/wkhtml.c +388 -0
- data/ext/wkhtml/wkhtml.h +52 -0
- data/lib/wkhtml.rb +14 -0
- data/lib/wkhtml/common_settings.rb +52 -0
- data/lib/wkhtml/converter.rb +125 -0
- data/lib/wkhtml/header_settings.rb +27 -0
- data/lib/wkhtml/load_settings.rb +41 -0
- data/lib/wkhtml/settings.rb +77 -0
- data/lib/wkhtml/to_image/global_settings.rb +65 -0
- data/lib/wkhtml/to_pdf/global_settings.rb +73 -0
- data/lib/wkhtml/to_pdf/object_settings.rb +59 -0
- data/lib/wkhtml/version.rb +3 -0
- data/lib/wkhtml/web_settings.rb +28 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/format.rb +18 -0
- data/spec/support/settings.rb +65 -0
- data/spec/support/test.html +1 -0
- data/spec/support/to_x_settings.rb +39 -0
- data/spec/wkhtml/converter_spec.rb +80 -0
- data/spec/wkhtml/to_image_converter_spec.rb +77 -0
- data/spec/wkhtml/to_image_global_settings_spec.rb +8 -0
- data/spec/wkhtml/to_pdf_converter_spec.rb +98 -0
- data/spec/wkhtml/to_pdf_global_settings_spec.rb +8 -0
- data/spec/wkhtml/to_pdf_object_settings_spec.rb +8 -0
- data/spec/wkhtml/to_pdf_spec.rb +4 -0
- data/spec/wkhtml_spec.rb +7 -0
- data/test/leaktest.rb +73 -0
- data/wkhtml.gemspec +24 -0
- metadata +151 -0
data/ext/wkhtml/wkhtml.h
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/encoding.h>
|
3
|
+
#include <wkhtmltox/pdf.h>
|
4
|
+
#include <wkhtmltox/image.h>
|
5
|
+
|
6
|
+
static ID idReady;
|
7
|
+
static VALUE mWkHtml = Qnil;
|
8
|
+
static VALUE mWkHtmlToPdf = Qnil;
|
9
|
+
static VALUE cWkHtmlToPdfGlobalSettings = Qnil;
|
10
|
+
static VALUE cWkHtmlToPdfObjectSettings = Qnil;
|
11
|
+
static VALUE cWkHtmlToPdfConverter = Qnil;
|
12
|
+
static VALUE mWkHtmlToImage = Qnil;
|
13
|
+
static VALUE cWkHtmlToImageGlobalSettings = Qnil;
|
14
|
+
static VALUE cWkHtmlToImageConverter = Qnil;
|
15
|
+
|
16
|
+
void Init_wkhtml();
|
17
|
+
void Deinit_wkhtml_native(VALUE data);
|
18
|
+
|
19
|
+
//WkHtml::ToPdf
|
20
|
+
|
21
|
+
//WkHtml::ToPdf::GlobalSettings
|
22
|
+
VALUE wkhtml_topdf_globalsettings_alloc(VALUE self);
|
23
|
+
VALUE wkhtml_topdf_globalsettings_aset(VALUE self, VALUE key, VALUE val);
|
24
|
+
VALUE wkhtml_topdf_globalsettings_aref(VALUE self, VALUE key);
|
25
|
+
|
26
|
+
//WkHtml::ToPdf::ObjectSettings
|
27
|
+
VALUE wkhtml_topdf_objectsettings_alloc(VALUE self);
|
28
|
+
VALUE wkhtml_topdf_objectsettings_aset(VALUE self, VALUE key, VALUE val);
|
29
|
+
VALUE wkhtml_topdf_objectsettings_aref(VALUE self, VALUE key);
|
30
|
+
|
31
|
+
//WkHtml::ToPdf::Converter
|
32
|
+
void wkhtml_topdf_converter_free(wkhtmltopdf_converter* converter);
|
33
|
+
VALUE wkhtml_topdf_converter_create(VALUE self, VALUE settings);
|
34
|
+
VALUE wkhtml_topdf_converter_add_object(VALUE self, VALUE settings, VALUE data);
|
35
|
+
VALUE wkhtml_topdf_converter_convert(VALUE self);
|
36
|
+
VALUE wkhtml_topdf_converter_http_error_code(VALUE self);
|
37
|
+
VALUE wkhtml_topdf_converter_get_output(VALUE self);
|
38
|
+
|
39
|
+
//WkHtml::ToImage
|
40
|
+
|
41
|
+
//WkHtml::ToImage::GlobalSettings
|
42
|
+
void wkhtml_toimage_globalsettings_free(wkhtmltoimage_global_settings* settings);
|
43
|
+
VALUE wkhtml_toimage_globalsettings_alloc(VALUE self);
|
44
|
+
VALUE wkhtml_toimage_globalsettings_aset(VALUE self, VALUE key, VALUE val);
|
45
|
+
VALUE wkhtml_toimage_globalsettings_aref(VALUE self, VALUE key);
|
46
|
+
|
47
|
+
//WkHtml::ToImage::Converter
|
48
|
+
void wkhtml_toimage_converter_free(wkhtmltoimage_converter* converter);
|
49
|
+
VALUE wkhtml_toimage_converter_create(VALUE self, VALUE settings, VALUE data);
|
50
|
+
VALUE wkhtml_toimage_converter_convert(VALUE self);
|
51
|
+
VALUE wkhtml_toimage_converter_http_error_code(VALUE self);
|
52
|
+
VALUE wkhtml_toimage_converter_get_output(VALUE self);
|
data/lib/wkhtml.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'wkhtml/wkhtml_native'
|
2
|
+
require 'wkhtml/version'
|
3
|
+
require 'wkhtml/settings'
|
4
|
+
require 'wkhtml/common_settings'
|
5
|
+
require 'wkhtml/header_settings'
|
6
|
+
require 'wkhtml/load_settings'
|
7
|
+
require 'wkhtml/web_settings'
|
8
|
+
require 'wkhtml/to_image/global_settings'
|
9
|
+
require 'wkhtml/to_pdf/global_settings'
|
10
|
+
require 'wkhtml/to_pdf/object_settings'
|
11
|
+
require 'wkhtml/converter'
|
12
|
+
|
13
|
+
module WkHtml
|
14
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module WkHtml
|
6
|
+
module CommonSettings
|
7
|
+
TRUE = 'true'
|
8
|
+
FALSE = 'false'
|
9
|
+
STDIN = STDOUT = '-'
|
10
|
+
PDF = 'pdf'
|
11
|
+
JPG = 'jpg'
|
12
|
+
PNG = 'png'
|
13
|
+
BMP = 'bmp'
|
14
|
+
SVG = 'svg'
|
15
|
+
REGEXP_URI = /\A#{URI.regexp(['http', 'https'])}/
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def cleanup_path(path)
|
19
|
+
path = path.path if path.is_a?(File) || path.is_a?(Tempfile)
|
20
|
+
|
21
|
+
if path == STDIN || path == STDOUT
|
22
|
+
path
|
23
|
+
elsif path =~ REGEXP_URI
|
24
|
+
URI.parse(path).to_s()
|
25
|
+
else
|
26
|
+
File.expand_path(path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def readable?(path)
|
31
|
+
unless path =~ REGEXP_URI
|
32
|
+
File.exists?(path) && File.readable?(path)
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def writable?(path)
|
39
|
+
unless File.writable?(path)
|
40
|
+
begin
|
41
|
+
FileUtils.touch(path)
|
42
|
+
true
|
43
|
+
rescue
|
44
|
+
false
|
45
|
+
end
|
46
|
+
else
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module WkHtml
|
4
|
+
class Converter
|
5
|
+
#
|
6
|
+
class Failed < StandardError
|
7
|
+
attr_reader :http_error_code
|
8
|
+
|
9
|
+
def initialize(http_error_code)
|
10
|
+
@http_error_code = http_error_code
|
11
|
+
super(@http_error_code.nil?() ? "Conversion failed" : "Conversion failed with HTTP status #{@http_error_code}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(data, options = {})
|
16
|
+
@data = data
|
17
|
+
#Thanks http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
|
18
|
+
@options = options.inject({}){|memo,(k,v)| memo[k.to_s()] = v; memo} #Force string for easy comparison with allowed settings
|
19
|
+
|
20
|
+
#Handle multiple inbound types
|
21
|
+
@use_data = case @data
|
22
|
+
when String
|
23
|
+
!(@data =~ CommonSettings::REGEXP_URI)
|
24
|
+
when File, Tempfile
|
25
|
+
@data = @data.path
|
26
|
+
false
|
27
|
+
when URI
|
28
|
+
@data = @data.to_s()
|
29
|
+
false
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
#
|
37
|
+
#
|
38
|
+
def to_file(path, format = nil)
|
39
|
+
path = CommonSettings::cleanup_path(path)
|
40
|
+
|
41
|
+
unless format
|
42
|
+
#Use file extension if available
|
43
|
+
format = File.extname(path)
|
44
|
+
format.sub!(/^\./, '')
|
45
|
+
format = nil if format.empty?()
|
46
|
+
end
|
47
|
+
format ||= CommonSettings::JPG
|
48
|
+
format = format.to_s()
|
49
|
+
|
50
|
+
native_converter = if format == CommonSettings::PDF
|
51
|
+
create_pdf_converter do |s|
|
52
|
+
s.out = path
|
53
|
+
end
|
54
|
+
else
|
55
|
+
create_image_converter do |s|
|
56
|
+
s.fmt = format
|
57
|
+
s.out = path
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
unless native_converter.convert()
|
62
|
+
raise Failed.new(native_converter.http_error_code())
|
63
|
+
end
|
64
|
+
|
65
|
+
path
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
#
|
70
|
+
#
|
71
|
+
def to_image(format = CommonSettings::JPG)
|
72
|
+
native_converter = if format == CommonSettings::PDF
|
73
|
+
create_pdf_converter()
|
74
|
+
else
|
75
|
+
create_image_converter do |s|
|
76
|
+
s.fmt = format
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless native_converter.convert()
|
81
|
+
raise Failed.new(native_converter.http_error_code())
|
82
|
+
end
|
83
|
+
native_converter.get_output()
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_pdf(); self.to_image(CommonSettings::PDF); end
|
87
|
+
def to_jpg(); self.to_image(CommonSettings::JPG); end
|
88
|
+
def to_png(); self.to_image(CommonSettings::PNG); end
|
89
|
+
def to_bmp(); self.to_image(CommonSettings::BMP); end
|
90
|
+
def to_svg(); self.to_image(CommonSettings::SVG); end
|
91
|
+
|
92
|
+
|
93
|
+
protected
|
94
|
+
def create_pdf_converter(&block)
|
95
|
+
global_settings = build_settings(WkHtml::ToPdf::GlobalSettings)
|
96
|
+
object_settings = build_settings(WkHtml::ToPdf::ObjectSettings)
|
97
|
+
object_settings.page = @data unless @use_data
|
98
|
+
yield global_settings, object_settings if block_given?()
|
99
|
+
native_converter = WkHtml::ToPdf::Converter.create(global_settings)
|
100
|
+
native_converter.add_object(object_settings, @use_data ? @data : nil)
|
101
|
+
native_converter
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_image_converter(&block)
|
105
|
+
global_settings = build_settings(WkHtml::ToImage::GlobalSettings)
|
106
|
+
global_settings.in = @data unless @use_data
|
107
|
+
yield global_settings if block_given?()
|
108
|
+
native_converter = WkHtml::ToImage::Converter.create(global_settings, @use_data ? @data : nil)
|
109
|
+
native_converter
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_settings(klass)
|
113
|
+
settings = klass.new()
|
114
|
+
(@options.keys & settings.class.settings).each do |key|
|
115
|
+
local_key = key.tr('.', '_')
|
116
|
+
if settings.class.public_method_defined?(local_key) #Use the instance method instead
|
117
|
+
settings.__send__(:"#{local_key}=", @options[key])
|
118
|
+
else #Does not exist, just go for raw []=
|
119
|
+
settings[key] = @options[key]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
settings
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module WkHtml
|
2
|
+
module HeaderSettings
|
3
|
+
HEADER_PREFIX = 'header'
|
4
|
+
FOOTER_PREFIX = 'footer'
|
5
|
+
KEYS = %w(
|
6
|
+
fontSize
|
7
|
+
fontName
|
8
|
+
left
|
9
|
+
center
|
10
|
+
right
|
11
|
+
line
|
12
|
+
spacing
|
13
|
+
htmlUrl
|
14
|
+
).map!{|k| ["#{HEADER_PREFIX}.#{k}", "#{FOOTER_PREFIX}.#{k}"] }.flatten()
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
__END__
|
20
|
+
header.fontSize The font size to use for the header, e.g. "13"
|
21
|
+
header.fontName The name of the font to use for the header. e.g. "times"
|
22
|
+
header.left The string to print in the left part of the header, note that some sequences are replaced in this string, see the wkhtmltopdf manual.
|
23
|
+
header.center The text to print in the center part of the header.
|
24
|
+
header.right The text to print in the right part of the header.
|
25
|
+
header.line Whether a line should be printed under the header (either "true" or "false").
|
26
|
+
header.spacing The amount of space to put between the header and the content, e.g. "1.8". Be aware that if this is too large the header will be printed outside the pdf document. This can be corrected with the margin.top setting.
|
27
|
+
header.htmlUrl Url for a HTML document to use for the header.
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module WkHtml
|
2
|
+
module LoadSettings
|
3
|
+
PREFIX = 'load'
|
4
|
+
KEYS = %w(
|
5
|
+
username
|
6
|
+
password
|
7
|
+
jsdelay
|
8
|
+
zoomFactor
|
9
|
+
customHeaders
|
10
|
+
repertCustomHeaders
|
11
|
+
cookies
|
12
|
+
post
|
13
|
+
blockLocalFileAccess
|
14
|
+
stopSlowScript
|
15
|
+
debugJavascript
|
16
|
+
loadErrorHandling
|
17
|
+
proxy
|
18
|
+
runScript
|
19
|
+
).map!{|k| "#{PREFIX}.#{k}" }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
__END__
|
25
|
+
load.username The user name to use when loging into a website, E.g. "bart"
|
26
|
+
load.password The password to used when logging into a website, E.g. "elbarto"
|
27
|
+
load.jsdelay The mount of time in milliseconds to wait after a page has done loading until it is actually printed. E.g. "1200". We will wait this amount of time or until, javascript calls window.print().
|
28
|
+
load.zoomFactor How much should we zoom in on the content? E.g. "2.2".
|
29
|
+
load.customHeaders TODO
|
30
|
+
load.repertCustomHeaders Should the custom headers be sent all elements loaded instead of only the main page? Must be either "true" or "false".
|
31
|
+
load.cookies TODO
|
32
|
+
load.post TODO
|
33
|
+
load.blockLocalFileAccess Disallow local and piped files to access other local files. Must be either "true" or "false".
|
34
|
+
load.stopSlowScript Stop slow running javascript. Must be either "true" or "false".
|
35
|
+
load.debugJavascript Forward javascript warnings and errors to the warning callback. Must be either "true" or "false".
|
36
|
+
load.loadErrorHandling How should we handle obejcts that fail to load. Must be one of:
|
37
|
+
"abort" Abort the convertion process
|
38
|
+
"skip" Do not add the object to the final output
|
39
|
+
"ignore" Try to add the object to the final output.
|
40
|
+
load.proxy String describing what proxy to use when loading the object.
|
41
|
+
load.runScript TODO
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module WkHtml
|
2
|
+
module Settings
|
3
|
+
TRUE = 'true'
|
4
|
+
FALSE = 'false'
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(initial = {})
|
11
|
+
initial = self.class.default_settings.merge(initial)
|
12
|
+
initial.each{|key,value| self[key] = value}
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Convert all attributes to Hash. This will ignore errors from retrieving
|
17
|
+
# the setting.
|
18
|
+
#
|
19
|
+
def to_hash()
|
20
|
+
Hash[
|
21
|
+
self.class.settings.map do |key|
|
22
|
+
begin
|
23
|
+
[key, self[key]]
|
24
|
+
rescue ArgumentError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end.compact()
|
28
|
+
]
|
29
|
+
end
|
30
|
+
alias_method :to_h, :to_hash
|
31
|
+
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
#
|
35
|
+
# List of settings defined
|
36
|
+
#
|
37
|
+
def settings()
|
38
|
+
@settings ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Assign the attributes and define instance methods
|
43
|
+
#
|
44
|
+
def settings=(keys)
|
45
|
+
@settings = keys
|
46
|
+
keys.each do |key|
|
47
|
+
#Change key to local method
|
48
|
+
local_key = key.tr('.', '_')
|
49
|
+
|
50
|
+
#Getter
|
51
|
+
define_method(local_key.to_sym()) do
|
52
|
+
self[key]
|
53
|
+
end unless method_defined?(local_key.to_sym())
|
54
|
+
|
55
|
+
#Setter
|
56
|
+
define_method(:"#{local_key}=") do |value|
|
57
|
+
self[key] = value
|
58
|
+
end unless method_defined?(:"#{local_key}=")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# List of default attibute values
|
64
|
+
#
|
65
|
+
def default_settings()
|
66
|
+
@default_settings ||= {}
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Assign default attributes that will be set when created
|
71
|
+
#
|
72
|
+
def default_settings=(hash)
|
73
|
+
@default_settings = hash
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module WkHtml
|
2
|
+
module ToImage
|
3
|
+
class GlobalSettings
|
4
|
+
include Settings
|
5
|
+
|
6
|
+
KEYS = %w(
|
7
|
+
crop.left
|
8
|
+
crop.top
|
9
|
+
crop.width
|
10
|
+
crop.height
|
11
|
+
load.cookieJar
|
12
|
+
transparent
|
13
|
+
in
|
14
|
+
out
|
15
|
+
fmt
|
16
|
+
screenWidth
|
17
|
+
smartWidth
|
18
|
+
quality
|
19
|
+
)
|
20
|
+
DEFAULTS = {
|
21
|
+
'fmt' => CommonSettings::JPG
|
22
|
+
}
|
23
|
+
|
24
|
+
self.settings = WebSettings::KEYS + LoadSettings::KEYS + KEYS
|
25
|
+
self.default_settings = DEFAULTS
|
26
|
+
|
27
|
+
def in=(v)
|
28
|
+
v = CommonSettings::cleanup_path(v)
|
29
|
+
raise ArgumentError.new("#{v} is missing or not readable") unless CommonSettings::readable?(v)
|
30
|
+
self['in'] = v
|
31
|
+
end
|
32
|
+
|
33
|
+
def out=(v)
|
34
|
+
v = CommonSettings::cleanup_path(v)
|
35
|
+
raise ArgumentError.new("#{v} is not writeable") unless CommonSettings::writable?(v)
|
36
|
+
self['out'] = v
|
37
|
+
end
|
38
|
+
|
39
|
+
def stdin=(v)
|
40
|
+
self.in = v ? CommonSettings::STDIN : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def stdout=(v)
|
44
|
+
self.out = v ? CommonSettings::STDOUT : nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
__END__
|
52
|
+
crop.left left/x coordinate of the window to capture in pixels. E.g. "200"
|
53
|
+
crop.top top/y coordinate of the window to capture in pixels. E.g. "200"
|
54
|
+
crop.width Width of the window to capture in pixels. E.g. "200"
|
55
|
+
crop.height Height of the window to capture in pixels. E.g. "200"
|
56
|
+
load.cookieJar Path of file used to load and store cookies.
|
57
|
+
load.* Page specific settings related to loading content, see Object Specific loading settings.
|
58
|
+
web.* See Web page specific settings.
|
59
|
+
transparent When outputting a PNG or SVG, make the white background transparent. Must be either "true" or "false"
|
60
|
+
in The URL or path of the input file, if "-" stdin is used. E.g. "http://google.com"
|
61
|
+
out The path of the output file, if "-" stdout is used, if empty the content is stored to a internalBuffer.
|
62
|
+
fmt The output format to use, must be either "", "jpg", "png", "bmp" or "svg".
|
63
|
+
screenWidth The with of the screen used to render is pixels, e.g "800".
|
64
|
+
smartWidth Should we expand the screenWidth if the content does not fit? must be either "true" or "false".
|
65
|
+
quality The compression factor to use when outputting a JPEG image. E.g. "94".
|