pd4ml 1.0.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.
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require 'net/http'
7
+ require 'time'
8
+
9
+ module Pd4ml
10
+ # Downloads a PD4ML build from the PD4ML Maven repository and makes it the gem's default:
11
+ #
12
+ # pd4ml install the most recent 4.* release
13
+ # pd4ml install --snapshots the most recent 4.* build, snapshots included
14
+ # pd4ml install 4.0.23fx2 an exact version
15
+ #
16
+ # or Pd4ml.install from code. The jar is stored in the user's cache directory (PD4ML_HOME to
17
+ # choose another) with the Bouncy Castle jars PD4ML needs for encryption and signing, every
18
+ # file verified against its published SHA-1 checksum. With no build installed or configured,
19
+ # the first use of PD4ML downloads one (PD4ML_AUTO_INSTALL=0 turns that off).
20
+ module Installer
21
+ REPOSITORY = 'https://pd4ml.com/maven2'
22
+ CENTRAL = 'https://repo1.maven.org/maven2'
23
+ GROUP_PATH = 'com/pd4ml/pd4ml'
24
+ # The Bouncy Castle version PD4ML is built against (https://pd4ml.com/docs/#2.-obtaining-pd4ml).
25
+ BOUNCYCASTLE_VERSION = '1.84'
26
+ BOUNCYCASTLE = %w[bcprov-jdk18on bcpkix-jdk18on bcutil-jdk18on].freeze
27
+ MANIFEST = 'installed.json'
28
+ VERSION_PATTERN = /\A(\d+)\.(\d+)\.(\d+)(?:fx(\d+)(?:a(\d+))?)?(-jakarta)?(-SNAPSHOT)?\z/
29
+
30
+ module_function
31
+
32
+ # ----------------------------------------------------------------------------- versions
33
+
34
+ # An ordering key for a PD4ML version (4.1.1, 4.0.23fx2, 4.0.23fx1a1, 4.1.2-SNAPSHOT), or nil
35
+ # for an unknown form. A snapshot sorts below its release, an fxNaM pre-release below fxN.
36
+ def version_key(version)
37
+ m = VERSION_PATTERN.match(version)
38
+ return nil unless m
39
+
40
+ [m[1].to_i, m[2].to_i, m[3].to_i, m[4].to_i, m[5] ? 0 : 1, m[5].to_i, m[7] ? 0 : 1]
41
+ end
42
+
43
+ # Versions sorted oldest first (unknown forms dropped).
44
+ def sort_versions(versions) = versions.select { |v| version_key(v) }.sort_by { |v| version_key(v) }
45
+
46
+ # The most recent `major`.* version: releases only unless `snapshots`, -jakarta only with `jakarta`.
47
+ def select_version(versions, major: 4, snapshots: false, jakarta: false)
48
+ candidates = versions.select do |v|
49
+ k = version_key(v)
50
+ k && k[0] == major && (snapshots || !v.end_with?('-SNAPSHOT')) && v.include?('-jakarta') == jakarta
51
+ end
52
+ raise InstallError, "no #{major}.* version in the repository" if candidates.empty?
53
+
54
+ sort_versions(candidates).last
55
+ end
56
+
57
+ # The versions the repository lists.
58
+ def available_versions(repository = REPOSITORY)
59
+ fetch("#{repository}/#{GROUP_PATH}/maven-metadata.xml").scan(%r{<version>([^<]+)</version>}).flatten.map(&:strip)
60
+ end
61
+
62
+ # ------------------------------------------------------------------------------ install
63
+
64
+ # Downloads a PD4ML build (unless already there) and makes it the default one; returns the
65
+ # jar's path. version: 'latest' (the most recent 4.* release; with snapshots: true, the most
66
+ # recent 4.* build) or an exact one such as '4.0.23fx2'.
67
+ def install(version = 'latest', snapshots: false, jakarta: false, repository: REPOSITORY, home: nil,
68
+ force: false, quiet: false, bouncycastle: true)
69
+ home ||= default_home
70
+ version = select_version(available_versions(repository), snapshots: snapshots, jakarta: jakarta) if version == 'latest'
71
+ name = jar_name(version, repository)
72
+ jar = File.join(home, name)
73
+ download("#{repository}/#{GROUP_PATH}/#{version}/#{name}", jar, force, quiet)
74
+ libs = []
75
+ if bouncycastle
76
+ BOUNCYCASTLE.each do |artifact|
77
+ lib = "#{artifact}-#{BOUNCYCASTLE_VERSION}.jar"
78
+ download("#{CENTRAL}/org/bouncycastle/#{artifact}/#{BOUNCYCASTLE_VERSION}/#{lib}", File.join(home, 'lib', lib),
79
+ force, quiet)
80
+ libs << "lib/#{lib}"
81
+ end
82
+ end
83
+ manifest = read_manifest(home).merge('version' => version, 'jar' => name, 'libs' => libs,
84
+ 'repository' => repository, 'installed' => Time.now.utc.iso8601)
85
+ tmp = File.join(home, "#{MANIFEST}.#{Process.pid}.part") # several processes may install at once
86
+ File.write(tmp, JSON.pretty_generate(manifest))
87
+ File.rename(tmp, File.join(home, MANIFEST))
88
+ warn "PD4ML #{version}: #{jar}" unless quiet
89
+ jar
90
+ end
91
+
92
+ # Where downloaded builds go: PD4ML_HOME, else the user's cache directory.
93
+ def default_home
94
+ return ENV['PD4ML_HOME'] if ENV['PD4ML_HOME'].to_s != ''
95
+
96
+ if Gem.win_platform?
97
+ File.join(ENV['LOCALAPPDATA'] || File.join(Dir.home, 'AppData', 'Local'), 'pd4ml-ruby')
98
+ else
99
+ File.join(ENV['XDG_CACHE_HOME'].to_s.empty? ? File.join(Dir.home, '.cache') : ENV['XDG_CACHE_HOME'], 'pd4ml-ruby')
100
+ end
101
+ end
102
+
103
+ # The jar install made the default, or nil.
104
+ def installed_jar(home = default_home)
105
+ jar = read_manifest(home)['jar']
106
+ path = jar && File.join(home, jar)
107
+ path && File.exist?(path) ? path : nil
108
+ end
109
+
110
+ # The installed build's classpath: the PD4ML jar and its companion jars; empty if none.
111
+ def installed_classpath(home = default_home)
112
+ jar = installed_jar(home)
113
+ return [] unless jar
114
+
115
+ [jar, *Array(read_manifest(home)['libs']).map { |l| File.join(home, l) }.select { |p| File.exist?(p) }]
116
+ end
117
+
118
+ # The version install made the default, or nil.
119
+ def installed_version(home = default_home)
120
+ installed_jar(home) ? read_manifest(home)['version'] : nil
121
+ end
122
+
123
+ # Whether the first use of PD4ML may download a build (PD4ML_AUTO_INSTALL, default on).
124
+ def auto_install?
125
+ !%w[0 false no off].include?(ENV.fetch('PD4ML_AUTO_INSTALL', '1').strip.downcase)
126
+ end
127
+
128
+ # ---------------------------------------------------------------------------- internals
129
+
130
+ def jar_name(version, repository)
131
+ return "pd4ml-#{version}.jar" unless version.end_with?('-SNAPSHOT')
132
+
133
+ xml = fetch("#{repository}/#{GROUP_PATH}/#{version}/maven-metadata.xml")
134
+ xml.scan(%r{<snapshotVersion>(.*?)</snapshotVersion>}m).flatten.each do |sv|
135
+ tag = ->(t) { sv[%r{<#{t}>([^<]*)</#{t}>}, 1] }
136
+ return "pd4ml-#{tag.call('value')}.jar" if tag.call('extension') == 'jar' && tag.call('classifier').to_s.empty? && tag.call('value')
137
+ end
138
+ ts = xml[%r{<timestamp>([^<]+)</timestamp>}, 1]
139
+ build = xml[%r{<buildNumber>([^<]+)</buildNumber>}, 1]
140
+ return "pd4ml-#{version.delete_suffix('-SNAPSHOT')}-#{ts}-#{build}.jar" if ts && build
141
+
142
+ raise InstallError, "no jar listed for #{version}"
143
+ end
144
+
145
+ def download(url, path, force, quiet)
146
+ return if !force && File.exist?(path)
147
+
148
+ warn "downloading #{url}" unless quiet
149
+ data = fetch(url)
150
+ expected = fetch("#{url}.sha1").strip.split.first.to_s.downcase
151
+ actual = Digest::SHA1.hexdigest(data)
152
+ raise InstallError, "checksum mismatch for #{url}: #{actual} != #{expected}" if !expected.empty? && actual != expected
153
+
154
+ FileUtils.mkdir_p(File.dirname(path))
155
+ tmp = "#{path}.#{Process.pid}.part"
156
+ File.binwrite(tmp, data)
157
+ File.rename(tmp, path)
158
+ end
159
+
160
+ def fetch(url, redirects = 5)
161
+ uri = URI(url)
162
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 30, read_timeout: 300) do |http|
163
+ http.get(uri.request_uri, 'User-Agent' => 'pd4ml-ruby')
164
+ end
165
+ case response
166
+ when Net::HTTPSuccess then response.body.b
167
+ when Net::HTTPRedirection
168
+ raise InstallError, "too many redirects: #{url}" if redirects.zero?
169
+
170
+ fetch(URI.join(url, response['location']).to_s, redirects - 1)
171
+ else raise InstallError, "cannot download #{url} (HTTP #{response.code})"
172
+ end
173
+ rescue SocketError, SystemCallError, Timeout::Error, OpenSSL::SSL::SSLError => e
174
+ raise InstallError, "cannot download #{url}: #{e.message}"
175
+ end
176
+
177
+ def read_manifest(home)
178
+ JSON.parse(File.read(File.join(home, MANIFEST)))
179
+ rescue Errno::ENOENT, JSON::ParserError
180
+ {}
181
+ end
182
+ end
183
+ end
data/lib/pd4ml/java.rb ADDED
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pd4ml
4
+ # Converts a Ruby-style method name to the Java one: set_page_size -> setPageSize,
5
+ # demo_mode? -> isDemoMode. Names without underscores (Java names) pass through.
6
+ module Naming
7
+ @methods = {}
8
+ @lock = Mutex.new
9
+
10
+ module_function
11
+
12
+ # The Java method `name` (Ruby-style or Java) means on the class: use_ttf -> useTTF,
13
+ # demo_mode? -> isDemoMode, read_html -> readHTML. Ruby-style names are matched against the
14
+ # class's real method names ignoring case and underscores, so acronyms resolve too.
15
+ def resolve(bridge, java_class, name, static)
16
+ n = name.to_s
17
+ return n unless n.include?('_') || n.end_with?('?')
18
+
19
+ key = (n.end_with?('?') ? "is#{n[0..-2]}" : n).delete('_').downcase
20
+ table = @lock.synchronize do
21
+ @methods[[bridge.object_id, java_class, static]] ||= begin
22
+ info = bridge.describe(java_class)
23
+ names = static ? info['methods'] : Array(info['instanceMethods']) + Array(info['methods'])
24
+ names.to_h { |m| [m.downcase, m] }
25
+ end
26
+ end
27
+ table[key] || java_name(n)
28
+ end
29
+
30
+ def java_name(name)
31
+ n = name.to_s
32
+ if n.end_with?('?')
33
+ base = java_name(n[0..-2])
34
+ return "is#{base[0].upcase}#{base[1..]}"
35
+ end
36
+ return n unless n.include?('_') && n == n.downcase
37
+
38
+ parts = n.split('_').reject(&:empty?)
39
+ parts.first + parts.drop(1).map(&:capitalize).join
40
+ end
41
+ end
42
+
43
+ # A Java object living in the bridge. Call its Java methods by their Java names or in Ruby
44
+ # style (`size.rotate`, `pd.set_page_size(...)`, `pd.demo_mode?`). Public fields: `obj[:width]`
45
+ # / `obj[:width] = v`. Released in Java when garbage collected.
46
+ class JavaObject < BasicObject
47
+ def initialize(bridge, handle, java_class, enum_name)
48
+ @bridge = bridge
49
+ @handle = handle
50
+ @java_class = java_class
51
+ @enum_name = enum_name
52
+ ::ObjectSpace.define_finalizer(self, JavaObject.releaser(bridge, handle))
53
+ end
54
+
55
+ def self.releaser(bridge, handle)
56
+ proc { bridge.release(handle) }
57
+ end
58
+
59
+ # internal: the bridge handle
60
+ def __handle = @handle
61
+
62
+ # The Java class name ('com.pd4ml.PageSize').
63
+ def java_class = @java_class
64
+
65
+ # For an enum constant, its name ('v42', 'MM'); else nil.
66
+ def enum_name = @enum_name
67
+
68
+ # Reads a public field.
69
+ def [](field) = @bridge.get_field(self, field.to_s)
70
+
71
+ # Writes a public field.
72
+ def []=(field, value)
73
+ @bridge.set_field(self, field.to_s, value)
74
+ end
75
+
76
+ # Calls a Java method by its exact Java name.
77
+ def call_java(method, *args) = @bridge.call(self, method.to_s, args)
78
+
79
+ # Whether the object is an instance of the Java class or interface.
80
+ def instance_of_java?(name) = @bridge.instance_of?(self, name)
81
+
82
+ # Java toString().
83
+ def to_s = @bridge.call(self, 'toString', []).to_s
84
+ alias to_str to_s
85
+
86
+ def inspect = "#<Java #{@java_class} #{@enum_name || to_s}>"
87
+
88
+ # Java equals() (and the same object).
89
+ def ==(other)
90
+ equal?(other) || (JavaObject === other && @bridge.call(self, 'equals', [other]) == true)
91
+ end
92
+
93
+ def hash = @bridge.call(self, 'hashCode', [])
94
+
95
+ def eql?(other) = equal?(other)
96
+
97
+ def is_a?(klass) = klass == JavaObject || klass == ::BasicObject
98
+ alias kind_of? is_a?
99
+
100
+ def class = JavaObject
101
+
102
+ def nil? = false
103
+
104
+ def respond_to?(name, _include_all = false)
105
+ %i[java_class enum_name to_s inspect [] []= call_java instance_of_java?].include?(name.to_sym) ||
106
+ !name.to_s.end_with?('=')
107
+ end
108
+
109
+ def method_missing(name, *args, &block)
110
+ ::Kernel.raise ::NoMethodError, "set Java fields with obj[:#{name.to_s.chomp('=')}] = value" if name.to_s.end_with?('=')
111
+ ::Kernel.raise ::ArgumentError, 'Java methods take no block' if block
112
+
113
+ @bridge.call(self, Naming.resolve(@bridge, @java_class, name, false), args)
114
+ end
115
+
116
+ def respond_to_missing?(_name, _include_private = false) = true
117
+ end
118
+
119
+ # A Java class as a Ruby class: constants and static fields (`PageSize::A4`, `PageSize.A4`,
120
+ # `LayoutEngine.v42`), static methods (`PdfSpec.get_instance('pdf/a-2b')`), constructors
121
+ # (`PageMargins.new(10, 10, 10, 10, Units::MM)`).
122
+ class JavaClass
123
+ class << self
124
+ attr_reader :java_name
125
+
126
+ def new(*args) = Bridge.get.new_object(@java_name, args)
127
+
128
+ def to_s = @java_name
129
+ alias inspect to_s
130
+
131
+ # Reads a static field.
132
+ def [](field) = Bridge.get.get_field(@java_name, field.to_s)
133
+
134
+ # Writes a static field.
135
+ def []=(field, value)
136
+ Bridge.get.set_field(@java_name, field.to_s, value)
137
+ end
138
+
139
+ # Calls a static method by its exact Java name.
140
+ def call_java(method, *args) = Bridge.get.call_static(@java_name, method.to_s, args)
141
+
142
+ def const_missing(name)
143
+ value_of(name.to_s) { super }
144
+ end
145
+
146
+ def method_missing(name, *args, &block)
147
+ n = name.to_s
148
+ return super if block || n.end_with?('=', '!')
149
+
150
+ d = description
151
+ if d[:fields].include?(n) && args.empty?
152
+ value_of(n)
153
+ else
154
+ java = Naming.resolve(Bridge.get, @java_name, n, true)
155
+ return super unless d[:methods].include?(java)
156
+
157
+ Bridge.get.call_static(@java_name, java, args)
158
+ end
159
+ end
160
+
161
+ def respond_to_missing?(name, include_private = false)
162
+ d = description
163
+ d[:fields].include?(name.to_s) || d[:methods].include?(Naming.resolve(Bridge.get, @java_name, name, true)) || super
164
+ end
165
+
166
+ private
167
+
168
+ # A static field: constants are read once per bridge.
169
+ def value_of(field)
170
+ d = description
171
+ return yield unless d[:fields].include?(field)
172
+ return Bridge.get.get_field(@java_name, field) unless d[:constants].include?(field)
173
+
174
+ d[:values].fetch(field) { d[:values][field] = Bridge.get.get_field(@java_name, field) }
175
+ end
176
+
177
+ def description
178
+ bridge = Bridge.get
179
+ if @description.nil? || !@description[:bridge].equal?(bridge)
180
+ info = bridge.describe(@java_name)
181
+ @description = { bridge: bridge, fields: info['fields'], constants: info['constants'],
182
+ methods: info['methods'], values: {} }
183
+ end
184
+ @description
185
+ end
186
+ end
187
+ end
188
+
189
+ # Java classes by name, Java interfaces implemented by Ruby blocks.
190
+ module Java
191
+ @classes = {}
192
+ @lock = Mutex.new
193
+
194
+ # A Java class (`Pd4ml::Java.type('java.util.Locale').get_default`); nested classes by '.' or '$'.
195
+ def self.type(name)
196
+ @lock.synchronize do
197
+ @classes[name] ||= Class.new(JavaClass) { @java_name = name }
198
+ end
199
+ end
200
+
201
+ # A Java object implementing the interface, its methods calling the block with
202
+ # (method, args) on this thread while PD4ML runs.
203
+ def self.proxy(iface, &block)
204
+ raise ArgumentError, 'a block is required' unless block
205
+
206
+ Bridge.get.proxy(iface, &block)
207
+ end
208
+ end
209
+ end
Binary file
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pd4ml
4
+ # An HTML to PDF/DOCX/RTF/image converter: com.pd4ml.PD4ML with Ruby I/O and the v42 layout
5
+ # engine by default. Every Java method is available by its Java name or in Ruby style
6
+ # (`set_page_size`, `add_style`, `use_ttf`); these take Ruby I/O: read_html (a path, URL or
7
+ # binary String/IO), read_html_string, write_pdf/write_docx/write_rtf (the document returned as
8
+ # a binary String, or written to a path or IO), render_as_images and monitor_progress_with (a block).
9
+ #
10
+ # pd = Pd4ml::PD4ML.new
11
+ # pd.set_page_size(Pd4ml::PageSize::A4)
12
+ # pd.read_html_string('<h1>Hello</h1>')
13
+ # pdf = pd.write_pdf
14
+ class PD4ML
15
+ DEFAULT_ENGINE = 'v42'
16
+
17
+ @warned_no_engines = false
18
+ class << self
19
+ attr_accessor :warned_no_engines
20
+
21
+ # Static Java members: PD4ML.get_version / PD4ML.getVersion.
22
+ def method_missing(name, *args, &block)
23
+ java_class.respond_to?(name) ? java_class.public_send(name, *args, &block) : super
24
+ end
25
+
26
+ def respond_to_missing?(name, include_private = false)
27
+ java_class.respond_to?(name) || super
28
+ end
29
+
30
+ def java_class = Java.type('com.pd4ml.PD4ML')
31
+ end
32
+
33
+ # The Java object (com.pd4ml.PD4ML).
34
+ attr_reader :java
35
+ # The layout engine chosen ('v42', 'v40'), or nil for the build's own.
36
+ attr_reader :engine
37
+
38
+ # license: a license serial number or license file URL (default: pd4ml.lic on the classpath
39
+ # or in the working directory, else evaluation mode); html_width: the width in pixels the
40
+ # HTML is laid out in; engine: 'v42', 'v40' (or :v42, a LayoutEngine), default v42 where the
41
+ # build has layout engines, else the build's own; false: the Java default.
42
+ def initialize(license: nil, html_width: nil, engine: nil)
43
+ args = [license, html_width].compact
44
+ @java = Bridge.get.new_object('com.pd4ml.PD4ML', args)
45
+ @engine = nil
46
+ if engine.nil?
47
+ if Pd4ml.layout_engines?
48
+ engine = DEFAULT_ENGINE
49
+ else
50
+ engine = false
51
+ unless self.class.warned_no_engines
52
+ self.class.warned_no_engines = true
53
+ warn "PD4ML #{Pd4ml.version} has no #{DEFAULT_ENGINE} layout engine (from 4.2): its own engine is used"
54
+ end
55
+ end
56
+ end
57
+ use_html_renderer(engine) unless engine == false
58
+ end
59
+
60
+ # Java methods not defined here.
61
+ def method_missing(name, *args, &block)
62
+ return super if block
63
+
64
+ @java.__send__(name, *args)
65
+ end
66
+
67
+ def respond_to_missing?(_name, _include_private = false) = true
68
+
69
+ # ------------------------------------------------------------------------- layout engine
70
+
71
+ # Chooses the layout engine ('v42', 'v40', a LayoutEngine), optionally with the HTML width
72
+ # and engine parameters (a Hash).
73
+ def use_html_renderer(engine = DEFAULT_ENGINE, html_width: nil, params: nil)
74
+ unless Pd4ml.layout_engines?
75
+ raise "PD4ML #{Pd4ml.version} has no layout engines to choose from (Constants.LayoutEngine is new in 4.2)"
76
+ end
77
+
78
+ e = engine.is_a?(JavaObject) ? engine : Java.type('com.pd4ml.Constants$LayoutEngine').value_of(engine.to_s)
79
+ if params
80
+ @java.useHtmlRenderer(e, html_width || -1, params.to_h { |k, v| [k.to_s, v.to_s] })
81
+ elsif html_width
82
+ @java.useHtmlRenderer(e, html_width)
83
+ else
84
+ @java.useHtmlRenderer(e)
85
+ end
86
+ @engine = e.enum_name
87
+ end
88
+ alias useHtmlRenderer use_html_renderer
89
+
90
+ # --------------------------------------------------------------------------------- input
91
+
92
+ # Reads the document to convert: a URL ('https://...', 'file:...') or a local path (a String,
93
+ # Pathname or URI), its content (a binary String - File.binread - or an IO), or a Java
94
+ # URL/InputStream. base_url: where relative links resolve, for content (a URL or a local
95
+ # directory/file); encoding: the document's encoding when it does not declare one.
96
+ def read_html(source, base_url = nil, encoding = nil)
97
+ if location?(source)
98
+ url = Pd4ml.to_url(source)
99
+ encoding ? @java.readHTML(url, encoding) : @java.readHTML(url)
100
+ return
101
+ end
102
+ content = source.is_a?(JavaObject) ? source : Pd4ml.binary(source)
103
+ base = base_url && Pd4ml.to_url(base_url)
104
+ if base_url.nil? && encoding.nil?
105
+ @java.readHTML(content)
106
+ elsif encoding.nil?
107
+ @java.readHTML(content, base)
108
+ else
109
+ @java.readHTML(content, base, encoding)
110
+ end
111
+ end
112
+ alias readHTML read_html
113
+
114
+ # Reads the document from a String of HTML.
115
+ def read_html_string(html, base_url = nil)
116
+ read_html(html.to_s.encode(Encoding::UTF_8).b, base_url, 'UTF-8')
117
+ end
118
+ alias readHtmlString read_html_string
119
+
120
+ # -------------------------------------------------------------------------------- output
121
+
122
+ # Writes the PDF: to `target` (a path or an IO), or returned as a binary String. spec: a
123
+ # PdfSpec (PdfSpec::PDFA_2B), one of its keywords ('pdf/a-2b', see PdfSpec.list_keywords),
124
+ # or 'PDF'/'PDFA'/'PDFUA'.
125
+ def write_pdf(target = nil, spec: nil)
126
+ s = Pd4ml.pdf_spec(spec)
127
+ outs = []
128
+ Bridge.get.call(@java, 'writePDF', s.nil? ? [Output.new(0)] : [Output.new(0), s], outs)
129
+ Pd4ml.deliver(outs[0] || ''.b, target)
130
+ end
131
+ alias writePDF write_pdf
132
+
133
+ # Writes a DOCX document (see write_pdf for `target`).
134
+ def write_docx(target = nil)
135
+ outs = []
136
+ Bridge.get.call(@java, 'writeDOCX', [Output.new(0)], outs)
137
+ Pd4ml.deliver(outs[0] || ''.b, target)
138
+ end
139
+ alias writeDOCX write_docx
140
+
141
+ # Writes an RTF document; rtf_wmf: images as WMF (see write_pdf for `target`).
142
+ def write_rtf(target = nil, rtf_wmf: false)
143
+ outs = []
144
+ Bridge.get.call(@java, 'writeRTF', [Output.new(0), rtf_wmf], outs)
145
+ Pd4ml.deliver(outs[0] || ''.b, target)
146
+ end
147
+ alias writeRTF write_rtf
148
+
149
+ # render_as_images(format): the pages as encoded images (Constants::PNG24...), binary Strings;
150
+ # render_as_images(dir, name, format): image files written to a directory;
151
+ # render_as_images: Java BufferedImage objects.
152
+ def render_as_images(*args)
153
+ if args.size == 3 && (args[0].is_a?(String) || args[0].is_a?(Pathname))
154
+ FileUtils.mkdir_p(args[0].to_s)
155
+ return @java.renderAsImages(Java.type('java.io.File').new(File.expand_path(args[0].to_s)), args[1], args[2])
156
+ end
157
+ @java.renderAsImages(*args)
158
+ end
159
+ alias renderAsImages render_as_images
160
+
161
+ # The pages as 24-bit PNG images (binary Strings).
162
+ def render_png = @java.renderAsImages(Java.type('com.pd4ml.Constants')::PNG24)
163
+
164
+ # ------------------------------------------------------------------------------ progress
165
+
166
+ # Reports conversion progress to a block |message_id, progress, note, msec| - it runs on this
167
+ # thread while PD4ML works - or to a Java ProgressListener.
168
+ def monitor_progress_with(listener = nil, &block)
169
+ listener ||= Java.proxy('com.pd4ml.ProgressListener') do |_method, a|
170
+ block.call(a[0].to_i, a[1].to_i, a[2], a[3].to_i)
171
+ end
172
+ @java.monitorProgressWith(listener)
173
+ end
174
+ alias monitorProgressWith monitor_progress_with
175
+
176
+ private
177
+
178
+ def location?(source)
179
+ source.is_a?(Pathname) || source.is_a?(URI::Generic) ||
180
+ (source.is_a?(String) && source.encoding != Encoding::BINARY && !source.lstrip.start_with?('<')) ||
181
+ (source.is_a?(JavaObject) && source.java_class == 'java.net.URL')
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pd4ml
4
+ VERSION = '1.0.0'
5
+ end